#![expect(missing_docs)]
#![forbid(unsafe_code)]
pub mod resolver;
#[cfg(feature = "test_utilities")]
pub mod test_utilities;
use async_trait::async_trait;
use core::mem::size_of;
use disk_backend::Disk;
use futures::FutureExt;
use futures::StreamExt;
use get_protocol::BatteryStatusFlags;
use get_protocol::BatteryStatusNotification;
use get_protocol::GspCleartextContent;
use get_protocol::GspExtendedStatusFlags;
use get_protocol::HeaderGeneric;
use get_protocol::HostNotifications;
use get_protocol::HostRequests;
use get_protocol::IgvmAttestRequest;
use get_protocol::MAX_PAYLOAD_SIZE;
use get_protocol::RegisterState;
use get_protocol::SaveGuestVtl2StateFlags;
use get_protocol::SecureBootTemplateType;
use get_protocol::StartVtl0Status;
use get_protocol::UefiConsoleMode;
use get_protocol::VmgsIoStatus;
use get_protocol::dps_json::HclSecureBootTemplateId;
use get_protocol::dps_json::PcatBootDevice;
use get_resources::ged::FirmwareEvent;
use get_resources::ged::GuestEmulationRequest;
use get_resources::ged::GuestServicingFlags;
use get_resources::ged::ModifyVtl2SettingsError;
use get_resources::ged::SaveRestoreError;
use get_resources::ged::Vtl0StartError;
use guestmem::GuestMemory;
use guid::Guid;
use inspect::Inspect;
use inspect::InspectMut;
use jiff::Zoned;
use jiff::civil::DateTime;
use jiff::civil::date;
use jiff::tz::TimeZone;
use mesh::error::RemoteError;
use mesh::rpc::Rpc;
use openhcl_attestation_protocol::igvm_attest::get::AK_CERT_RESPONSE_HEADER_VERSION;
use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestType;
use power_resources::PowerRequest;
use power_resources::PowerRequestClient;
use scsi_buffers::OwnedRequestBuffers;
use std::io::IoSlice;
use task_control::StopTask;
use thiserror::Error;
use video_core::FramebufferControl;
use vmbus_async::async_dgram::AsyncRecvExt;
use vmbus_async::pipe::MessagePipe;
use vmbus_channel::RawAsyncChannel;
use vmbus_channel::bus::ChannelType;
use vmbus_channel::bus::OfferParams;
use vmbus_channel::channel::ChannelOpenError;
use vmbus_channel::gpadl_ring::GpadlRingMem;
use vmbus_channel::simple::SimpleVmbusDevice;
use vmbus_ring::RingMem;
use vmcore::save_restore::SavedStateNotSupported;
use zerocopy::FromBytes;
use zerocopy::FromZeros;
use zerocopy::IntoBytes;
#[derive(Debug, Error)]
enum Error {
#[error("cancelled")]
Cancelled(task_control::Cancelled),
#[error("vmbus channel i/o error")]
Vmbus(#[source] std::io::Error),
#[error("accepting vmbus channel")]
Accept(#[from] vmbus_channel::offer::Error),
#[error("message too small")]
MessageTooSmall,
#[error("serializing device platform settings v2")]
SerializeDpsV2(#[source] serde_json::Error),
#[error("invalid packet sequence")]
InvalidSequence,
#[error("failed to parse host request")]
HostRequest,
#[error("invalid header version: {0:?}")]
HeaderVersion(get_protocol::MessageVersions),
#[error("data was received with an invalid field value")]
InvalidFieldValue,
#[error("large device platform settings v2 is currently unimplemented")]
LargeDpsV2Unimplemented,
#[error("invalid IGVM_ATTEST request")]
InvalidIgvmAttestRequest,
#[error("unsupported igvm attest request type: {0:?}")]
UnsupportedIgvmAttestRequestType(u32),
#[error("failed to write to shared memory")]
SharedMemoryWriteFailed(#[source] guestmem::GuestMemoryError),
}
impl From<task_control::Cancelled> for Error {
fn from(value: task_control::Cancelled) -> Self {
Error::Cancelled(value)
}
}
#[derive(Debug, Clone, Inspect)]
pub struct GuestConfig {
pub firmware: GuestFirmwareConfig,
pub com1: bool,
pub com2: bool,
pub vmbus_redirection: bool,
pub enable_tpm: bool,
#[inspect(with = "Option::is_some")]
pub vtl2_settings: Option<Vec<u8>>,
pub secure_boot_enabled: bool,
#[inspect(debug)]
pub secure_boot_template: SecureBootTemplateType,
pub enable_battery: bool,
pub no_persistent_secrets: bool,
}
#[derive(Debug, Clone, Inspect)]
#[inspect(external_tag)]
pub enum GuestFirmwareConfig {
Uefi {
enable_vpci_boot: bool,
firmware_debug: bool,
disable_frontpage: bool,
#[inspect(debug)]
console_mode: UefiConsoleMode,
},
Pcat {
#[inspect(with = "|x| inspect::iter_by_index(x).map_value(inspect::AsDebug)")]
boot_order: [PcatBootDevice; 4],
},
}
#[derive(Debug)]
pub enum GuestEvent {
BootSuccess,
BootSuccessSecureBootFailed,
BootFailure,
BootFailureSecureBootFailed,
NoBootDevice,
AttestationFailed,
VmgsFileClear,
VmgsInitFailed,
VmgsInvalidFormat,
VmgsCorruptFormat,
KeyNotReleased,
DekDecryptionFailed,
WatchdogTimeoutReset,
BootAttempt,
}
#[derive(Debug)]
enum IgvmAttestState {
Init,
SendingAkCert,
}
#[derive(InspectMut)]
pub struct GuestEmulationDevice {
config: GuestConfig,
#[inspect(skip)]
power_client: PowerRequestClient,
#[inspect(skip)]
firmware_event_send: Option<mesh::Sender<FirmwareEvent>>,
#[inspect(skip)]
framebuffer_control: Option<Box<dyn FramebufferControl>>,
#[inspect(skip)]
guest_request_recv: mesh::Receiver<GuestEmulationRequest>,
#[inspect(skip)]
waiting_for_vtl0_start: Vec<Rpc<(), Result<(), Vtl0StartError>>>,
vmgs: Option<VmgsState>,
#[inspect(with = "Option::is_some")]
save_restore_buf: Option<Vec<u8>>,
last_save_restore_buf_len: usize,
#[inspect(skip)]
igvm_attest_state: IgvmAttestState,
}
#[derive(Inspect)]
struct VmgsState {
disk: Disk,
mem: GuestMemory,
}
impl GuestEmulationDevice {
pub fn new(
config: GuestConfig,
power_client: PowerRequestClient,
firmware_event_send: Option<mesh::Sender<FirmwareEvent>>,
guest_request_recv: mesh::Receiver<GuestEmulationRequest>,
framebuffer_control: Option<Box<dyn FramebufferControl>>,
vmgs_disk: Option<Disk>,
) -> Self {
Self {
config,
power_client,
firmware_event_send,
framebuffer_control,
guest_request_recv,
vmgs: vmgs_disk.map(|disk| VmgsState {
disk,
mem: GuestMemory::allocate(MAX_PAYLOAD_SIZE),
}),
save_restore_buf: None,
waiting_for_vtl0_start: Vec::new(),
last_save_restore_buf_len: 0,
igvm_attest_state: IgvmAttestState::Init,
}
}
fn send_event(&self, event: FirmwareEvent) {
if let Some(sender) = &self.firmware_event_send {
sender.send(event);
}
}
}
#[async_trait]
impl SimpleVmbusDevice for GuestEmulationDevice {
type Runner = GedChannel;
type SavedState = SavedStateNotSupported;
fn offer(&self) -> OfferParams {
OfferParams {
interface_name: "get".to_owned(),
interface_id: get_protocol::GUEST_EMULATION_INTERFACE_TYPE,
instance_id: get_protocol::GUEST_EMULATION_INTERFACE_INSTANCE,
channel_type: ChannelType::Pipe { message_mode: true },
..Default::default()
}
}
fn inspect(&mut self, req: inspect::Request<'_>, mut channel: Option<&mut GedChannel>) {
req.respond().merge(self).field_mut("channel", &mut channel);
}
fn open(
&mut self,
channel: RawAsyncChannel<GpadlRingMem>,
guest_memory: GuestMemory,
) -> Result<Self::Runner, ChannelOpenError> {
let pipe = MessagePipe::new(channel)?;
Ok(GedChannel::new(pipe, guest_memory))
}
async fn run(
&mut self,
stop: &mut StopTask<'_>,
task_state: &mut GedChannel,
) -> Result<(), task_control::Cancelled> {
match task_state.process(stop, self).await {
Ok(()) => Ok(()),
Err(Error::Cancelled(err)) => Err(err),
Err(err) => {
tracing::error!(error = &err as &dyn std::error::Error, "ged error");
Ok(())
}
}
}
fn supports_save_restore(
&mut self,
) -> Option<
&mut dyn vmbus_channel::simple::SaveRestoreSimpleVmbusDevice<
SavedState = Self::SavedState,
Runner = Self::Runner,
>,
> {
None
}
}
#[derive(InspectMut)]
pub struct GedChannel<T: RingMem = GpadlRingMem> {
#[inspect(mut)]
channel: MessagePipe<T>,
#[inspect(skip)]
state: GedState,
#[inspect(with = "Option::is_some")]
save: Option<InProgressSave>,
#[inspect(with = "Option::is_some")]
vtl0_start_report: Option<Result<(), Vtl0StartError>>,
#[inspect(with = "Option::is_some")]
modify: Option<Rpc<(), Result<(), ModifyVtl2SettingsError>>>,
#[inspect(skip)]
gm: GuestMemory,
}
struct InProgressSave {
rpc: Rpc<GuestServicingFlags, Result<(), SaveRestoreError>>,
buffer: Vec<u8>,
}
enum GedState {
Init,
Ready,
SendingRestore { written: usize },
}
impl<T: RingMem + Unpin> GedChannel<T> {
fn new(channel: MessagePipe<T>, guest_memory: GuestMemory) -> Self {
Self {
channel,
save: None,
state: GedState::Init,
vtl0_start_report: None,
modify: None,
gm: guest_memory,
}
}
async fn process(
&mut self,
stop: &mut StopTask<'_>,
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
tracing::trace!("Begin GetChannel process()");
loop {
stop.until_stopped(
self.channel
.wait_write_ready(get_protocol::MAX_MESSAGE_SIZE),
)
.await?
.map_err(Error::Vmbus)?;
match &mut self.state {
GedState::Init => {
let mut version_request = get_protocol::VersionRequest::new_zeroed();
stop.until_stopped(self.channel.recv_exact(version_request.as_mut_bytes()))
.await?
.map_err(Error::Vmbus)?;
if version_request.message_header.message_id != HostRequests::VERSION {
return Err(Error::InvalidSequence);
}
let version_response = get_protocol::VersionResponse::new(true);
self.channel
.try_send(version_response.as_bytes())
.map_err(Error::Vmbus)?;
tracing::info!("version negotiated successfully!");
self.state = GedState::Ready;
let _ = self.send_hardcoded_battery_update();
}
GedState::Ready => {
let mut message_buf = [0; get_protocol::MAX_MESSAGE_SIZE];
futures::select! { pipe_input = self.channel.recv(&mut message_buf).fuse() => {
let bytes_read = pipe_input.map_err(Error::Vmbus)?;
self.handle_pipe_input(&message_buf[..bytes_read], state).await?;
},
guest_request = state.guest_request_recv.select_next_some() => {
self.handle_guest_request_input(state, guest_request)?;
}
_ = stop.fuse() => {
return Err(Error::Cancelled(task_control::Cancelled));
}
}
}
GedState::SendingRestore { written } => {
let buffer = state
.save_restore_buf
.as_ref()
.ok_or(Error::InvalidSequence)?;
let saved_state_size = buffer.len();
if *written >= saved_state_size {
self.state = GedState::Ready;
state.last_save_restore_buf_len = saved_state_size;
state.save_restore_buf = None;
continue;
}
let status_code = if *written + MAX_PAYLOAD_SIZE >= saved_state_size {
get_protocol::GuestVtl2SaveRestoreStatus::SUCCESS
} else {
get_protocol::GuestVtl2SaveRestoreStatus::MORE_DATA
};
let payload_len = (saved_state_size - *written).min(MAX_PAYLOAD_SIZE);
let host_response_header = get_protocol::RestoreGuestVtl2StateResponse::new(
payload_len.try_into().unwrap(),
status_code,
);
tracing::debug!(
?status_code,
written,
saved_state_size,
payload_len,
"more data"
);
self.channel
.try_send_vectored(&[
IoSlice::new(host_response_header.as_bytes()),
IoSlice::new(&buffer[*written..][..payload_len]),
])
.map_err(Error::Vmbus)?;
*written += payload_len;
}
}
}
}
async fn handle_pipe_input(
&mut self,
message_buf: &[u8],
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
let header = get_protocol::HeaderRaw::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; if header.message_version != get_protocol::MessageVersions::HEADER_VERSION_1 {
return Err(Error::HeaderVersion(header.message_version));
}
match header.message_type {
get_protocol::MessageTypes::HOST_NOTIFICATION => self.handle_host_notification(
header.try_into().expect("validated message type"),
message_buf,
state,
)?,
get_protocol::MessageTypes::HOST_REQUEST => {
self.handle_host_request(
header.try_into().expect("validated message type"),
message_buf,
state,
)
.await?
}
_ => {
return Err(Error::HostRequest);
}
}
Ok(())
}
fn handle_guest_request_input(
&mut self,
state: &mut GuestEmulationDevice,
guest_request: GuestEmulationRequest,
) -> Result<(), Error> {
match guest_request {
GuestEmulationRequest::WaitForConnect(rpc) => rpc.handle_sync(|()| ()),
GuestEmulationRequest::WaitForVtl0Start(rpc) => {
if let Some(result) = self.vtl0_start_report.clone() {
rpc.complete(result);
} else {
state.waiting_for_vtl0_start.push(rpc);
}
}
GuestEmulationRequest::ModifyVtl2Settings(rpc) => {
let (data, response) = rpc.split();
if self.modify.is_some() {
response.complete(Err(ModifyVtl2SettingsError::OperationInProgress));
return Ok(());
}
if data.len() > MAX_PAYLOAD_SIZE {
response.complete(Err(ModifyVtl2SettingsError::LargeSettingsNotSupported));
return Ok(());
}
let header = get_protocol::ModifyVtl2SettingsRev1Notification {
message_header: HeaderGeneric::new(
get_protocol::GuestNotifications::MODIFY_VTL2_SETTINGS_REV1,
),
size: data.len() as u32,
payload_state: get_protocol::LargePayloadState::END,
};
self.channel
.try_send_vectored(&[IoSlice::new(header.as_bytes()), IoSlice::new(&data)])
.map_err(Error::Vmbus)?;
self.modify = Some(response);
}
GuestEmulationRequest::SaveGuestVtl2State(rpc) => {
let r = (|| {
if self.save.is_some() {
return Err(SaveRestoreError::OperationInProgress);
}
let save_notif_packet = get_protocol::SaveGuestVtl2StateNotification {
message_header: HeaderGeneric::new(
get_protocol::GuestNotifications::SAVE_GUEST_VTL2_STATE,
),
correlation_id: Guid::ZERO,
capabilities_flags: SaveGuestVtl2StateFlags::new()
.with_enable_nvme_keepalive(rpc.input().nvme_keepalive),
timeout_hint_secs: 60,
};
self.channel
.try_send(save_notif_packet.as_bytes())
.map_err(|err| SaveRestoreError::Io(RemoteError::new(err)))?;
Ok(())
})();
match r {
Ok(()) => {
self.save = Some(InProgressSave {
rpc,
buffer: Vec::new(),
})
}
Err(err) => rpc.complete(Err(err)),
}
}
};
Ok(())
}
async fn handle_host_request(
&mut self,
header: get_protocol::HeaderHostRequest,
message_buf: &[u8],
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
match header.message_id {
HostRequests::TIME => self.handle_time()?,
HostRequests::BIOS_BOOT_FINALIZE => self.handle_bios_boot_finalize(message_buf)?,
HostRequests::VMGS_GET_DEVICE_INFO => self.handle_vmgs_get_device_info(state)?,
HostRequests::VMGS_READ => self.handle_vmgs_read(state, message_buf).await?,
HostRequests::VMGS_WRITE => self.handle_vmgs_write(state, message_buf).await?,
HostRequests::VMGS_FLUSH => self.handle_vmgs_flush(state).await?,
HostRequests::GUEST_STATE_PROTECTION => {
self.handle_guest_state_protection(message_buf)?
}
HostRequests::GUEST_STATE_PROTECTION_BY_ID => {
self.handle_guest_state_protection_by_id()?;
}
HostRequests::IGVM_ATTEST => self.handle_igvm_attest(message_buf, state)?,
HostRequests::DEVICE_PLATFORM_SETTINGS_V2 => {
self.handle_device_platform_settings_v2(state)?
}
HostRequests::SAVE_GUEST_VTL2_STATE => {
self.handle_save_guest_vtl2_state(message_buf, state)?
}
HostRequests::RESTORE_GUEST_VTL2_STATE => self.handle_restore_guest_vtl2_state(),
HostRequests::MAP_FRAMEBUFFER => {
self.handle_map_framebuffer(state, message_buf).await?
}
HostRequests::UNMAP_FRAMEBUFFER => self.handle_unmap_framebuffer(state).await?,
HostRequests::CREATE_RAM_GPA_RANGE => self.handle_create_ram_gpa_range(message_buf)?,
HostRequests::RESET_RAM_GPA_RANGE => self.handle_reset_ram_gpa_range(message_buf)?,
_ => {
tracing::error!(message_id = ?header.message_id, "unexpected message");
return Err(Error::InvalidSequence);
}
};
Ok(())
}
fn handle_bios_boot_finalize(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let msg = get_protocol::BiosBootFinalizeRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; tracing::trace!(?msg, "Bios Boot Finalize request");
let response = get_protocol::BiosBootFinalizeResponse::new();
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_time(&mut self) -> Result<(), Error> {
const WINDOWS_EPOCH: DateTime = date(1601, 1, 1).at(0, 0, 0, 0);
let now = Zoned::now();
let since_win_epoch = (WINDOWS_EPOCH
.to_zoned(TimeZone::UTC)
.expect("windows epoch value to be valid")
.timestamp()
.duration_until(now.timestamp())
.as_nanos()
/ 100) as i64;
let tz_offset = (now.offset().seconds() / 60) as i16;
let response = get_protocol::TimeResponse::new(0, since_win_epoch, tz_offset, false);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_vmgs_get_device_info(
&mut self,
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
let response = if let Some(vmgs) = &state.vmgs {
get_protocol::VmgsGetDeviceInfoResponse::new(
VmgsIoStatus::SUCCESS,
vmgs.disk.sector_count(),
vmgs.disk.sector_size().try_into().unwrap(),
vmgs.disk.physical_sector_size().try_into().unwrap(),
MAX_PAYLOAD_SIZE as u32,
)
} else {
get_protocol::VmgsGetDeviceInfoResponse::new(VmgsIoStatus::DEVICE_ERROR, 0, 0, 0, 0)
};
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
async fn handle_vmgs_read(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let message = get_protocol::VmgsReadRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; let (status, payload) = if let Some(vmgs) = &mut state.vmgs {
let len = message.sector_count as u64 * vmgs.disk.sector_size() as u64;
if len > MAX_PAYLOAD_SIZE as u64 {
return Err(Error::InvalidFieldValue);
}
match vmgs
.disk
.read_vectored(
&OwnedRequestBuffers::linear(0, len as usize, true).buffer(&vmgs.mem),
message.sector_offset,
)
.await
{
Ok(()) => (
VmgsIoStatus::SUCCESS,
&vmgs
.mem
.inner_buf_mut()
.expect("memory should not be aliased")[..len as usize],
),
Err(err) => {
tracelimit::error_ratelimited!(
error = &err as &dyn std::error::Error,
"vmgs read error"
);
(VmgsIoStatus::DEVICE_ERROR, &[] as _)
}
}
} else {
(VmgsIoStatus::DEVICE_ERROR, &[] as _)
};
let response = get_protocol::VmgsReadResponse::new(status);
self.channel
.try_send_vectored(&[IoSlice::new(response.as_bytes()), IoSlice::new(payload)])
.map_err(Error::Vmbus)?;
Ok(())
}
async fn handle_vmgs_write(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let (message, rest) = get_protocol::VmgsWriteRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?; let status = if let Some(vmgs) = &mut state.vmgs {
let len = message.sector_count as u64 * vmgs.disk.sector_size() as u64;
if len > MAX_PAYLOAD_SIZE as u64 {
return Err(Error::InvalidFieldValue);
}
vmgs.mem
.write_at(0, rest.get(..len as usize).ok_or(Error::MessageTooSmall)?)
.unwrap();
match vmgs
.disk
.write_vectored(
&OwnedRequestBuffers::linear(0, len as usize, false).buffer(&vmgs.mem),
message.sector_offset,
false,
)
.await
{
Ok(()) => VmgsIoStatus::SUCCESS,
Err(err) => {
tracelimit::error_ratelimited!(
error = &err as &dyn std::error::Error,
"vmgs write error"
);
VmgsIoStatus::DEVICE_ERROR
}
}
} else {
VmgsIoStatus::DEVICE_ERROR
};
let response = get_protocol::VmgsWriteResponse::new(status);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
async fn handle_vmgs_flush(&mut self, state: &mut GuestEmulationDevice) -> Result<(), Error> {
let status = if let Some(vmgs) = &mut state.vmgs {
match vmgs.disk.sync_cache().await {
Ok(()) => VmgsIoStatus::SUCCESS,
Err(err) => {
tracelimit::error_ratelimited!(
error = &err as &dyn std::error::Error,
"vmgs flush error"
);
VmgsIoStatus::DEVICE_ERROR
}
}
} else {
VmgsIoStatus::DEVICE_ERROR
};
let response = get_protocol::VmgsFlushResponse::new(status);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_guest_state_protection(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let _message = get_protocol::GuestStateProtectionRequest::read_from_prefix(
&message_buf.as_bytes()[..size_of::<get_protocol::GuestStateProtectionRequest>()],
)
.map_err(|_| Error::MessageTooSmall)?
.0; let mut response = get_protocol::GuestStateProtectionResponse::new_zeroed();
response.message_header = HeaderGeneric::new(HostRequests::GUEST_STATE_PROTECTION);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_guest_state_protection_by_id(&mut self) -> Result<(), Error> {
let response = get_protocol::GuestStateProtectionByIdResponse {
message_header: HeaderGeneric::new(HostRequests::GUEST_STATE_PROTECTION_BY_ID),
seed: GspCleartextContent::new_zeroed(),
extended_status_flags: GspExtendedStatusFlags::new().with_no_registry_file(true),
};
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_igvm_attest(
&mut self,
message_buf: &[u8],
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
tracing::info!(state = ?state.igvm_attest_state, "Handle IGVM Attest request");
let request = IgvmAttestRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; if request.agent_data_length as usize > request.agent_data.len()
|| request.report_length as usize > request.report.len()
|| request.number_gpa as usize > get_protocol::IGVM_ATTEST_MSG_MAX_SHARED_GPA
{
Err(Error::InvalidIgvmAttestRequest)?
}
let request_payload = IgvmAttestRequestHeader::read_from_prefix(&request.report)
.map_err(|_| Error::MessageTooSmall)?
.0; let response = match request_payload.request_type {
IgvmAttestRequestType::AK_CERT_REQUEST
if matches!(state.igvm_attest_state, IgvmAttestState::Init) =>
{
let data = vec![0xab; 2500];
let header = IgvmAttestAkCertResponseHeader {
data_size: (data.len() + size_of::<IgvmAttestAkCertResponseHeader>()) as u32,
version: AK_CERT_RESPONSE_HEADER_VERSION,
};
let payload = [header.as_bytes(), &data].concat();
self.gm
.write_at(request.shared_gpa[0], &payload)
.map_err(Error::SharedMemoryWriteFailed)?;
state.igvm_attest_state = IgvmAttestState::SendingAkCert;
tracing::info!("Sending AK_CERT_REQEUST");
get_protocol::IgvmAttestResponse {
message_header: HeaderGeneric::new(HostRequests::IGVM_ATTEST),
length: payload.len() as u32,
}
}
IgvmAttestRequestType::AK_CERT_REQUEST => {
tracing::info!("Skip AK_CERT_REQEUST");
return Ok(());
}
ty => return Err(Error::UnsupportedIgvmAttestRequestType(ty.0)),
};
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_save_guest_vtl2_state(
&mut self,
message_buf: &[u8],
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
let save = self.save.as_mut().ok_or(Error::InvalidSequence)?;
let (request_header, remaining) =
get_protocol::SaveGuestVtl2StateRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?; let r = match request_header.save_status {
get_protocol::GuestVtl2SaveRestoreStatus::MORE_DATA => {
save.buffer.extend_from_slice(remaining);
None
}
get_protocol::GuestVtl2SaveRestoreStatus::SUCCESS => {
save.buffer.extend_from_slice(remaining);
tracing::debug!("Received all guest VTL2 save state");
let response = get_protocol::SaveGuestVtl2StateResponse::new(
get_protocol::GuestVtl2SaveRestoreStatus::SUCCESS,
);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
tracing::debug!("Notifying completion channel that save guest VTL2 op is complete");
Some(Ok(()))
}
get_protocol::GuestVtl2SaveRestoreStatus::FAILURE => {
Some(Err(SaveRestoreError::GuestError))
}
_ => {
return Err(Error::InvalidFieldValue);
}
};
if let Some(r) = r {
let save = self.save.take().unwrap();
if r.is_ok() {
state.save_restore_buf = Some(save.buffer);
}
save.rpc.complete(r);
}
Ok(())
}
fn handle_restore_guest_vtl2_state(&mut self) {
self.state = GedState::SendingRestore { written: 0 };
}
async fn handle_map_framebuffer(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let response = get_protocol::MapFramebufferResponse::new(
if let Some(framebuffer_control) = state.framebuffer_control.as_mut() {
let message = get_protocol::MapFramebufferRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; let gpa = message.gpa;
tracing::debug!("Received map framebuffer request from guest {:#x}", gpa);
framebuffer_control.map(gpa).await;
get_protocol::MapFramebufferStatus::SUCCESS
} else {
tracing::warn!(
"Guest requested framebuffer mapping but no framebuffer control was provided to the GET"
);
get_protocol::MapFramebufferStatus::FAILURE
},
);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
async fn handle_unmap_framebuffer(
&mut self,
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
let response = get_protocol::UnmapFramebufferResponse::new(
if let Some(framebuffer_control) = state.framebuffer_control.as_mut() {
tracing::debug!("Received unmap framebuffer request from guest");
framebuffer_control.unmap().await;
get_protocol::UnmapFramebufferStatus::SUCCESS
} else {
tracing::warn!(
"Guest requested framebuffer mapping but no framebuffer control was provided to the GET"
);
get_protocol::UnmapFramebufferStatus::FAILURE
},
);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_create_ram_gpa_range(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let request = get_protocol::CreateRamGpaRangeRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; tracing::info!(?request, "create ram gpa range request");
let response = get_protocol::CreateRamGpaRangeResponse::new(
get_protocol::CreateRamGpaRangeStatus::FAILED,
);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_reset_ram_gpa_range(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let _request = get_protocol::ResetRamGpaRangeRequest::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; let response = get_protocol::ResetRamGpaRangeResponse::new();
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
fn handle_host_notification(
&mut self,
header: get_protocol::HeaderHostNotification,
message_buf: &[u8],
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
match header.message_id {
HostNotifications::POWER_OFF => {
self.handle_power_off(state);
}
HostNotifications::RESET => {
self.handle_reset(state);
}
HostNotifications::EVENT_LOG => {
self.handle_event_log(state, message_buf)?;
}
HostNotifications::RESTORE_GUEST_VTL2_STATE_COMPLETED => {
self.handle_restore_guest_vtl2_state_completed(message_buf)?;
}
HostNotifications::START_VTL0_COMPLETED => {
self.handle_start_vtl0_completed(state, message_buf)?;
}
HostNotifications::VTL_CRASH => {
self.handle_vtl_crash(message_buf)?;
}
HostNotifications::TRIPLE_FAULT => {
self.handle_triple_fault(state, message_buf)?;
}
HostNotifications::MODIFY_VTL2_SETTINGS_COMPLETED => {
self.handle_modify_vtl2_settings_completed(message_buf)?;
}
_ => {
return Err(Error::InvalidFieldValue);
}
}
Ok(())
}
fn handle_power_off(&mut self, state: &mut GuestEmulationDevice) {
state.power_client.power_request(PowerRequest::PowerOff);
}
fn handle_reset(&mut self, state: &mut GuestEmulationDevice) {
state.power_client.power_request(PowerRequest::Reset);
}
fn handle_event_log(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let msg = get_protocol::EventLogNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; tracing::trace!("[Event Log] {:?}", msg);
let event = match msg.event_log_id {
get_protocol::EventLogId::BOOT_SUCCESS => GuestEvent::BootSuccess,
get_protocol::EventLogId::BOOT_SUCCESS_SECURE_BOOT_FAILED => {
GuestEvent::BootSuccessSecureBootFailed
}
get_protocol::EventLogId::BOOT_FAILURE => GuestEvent::BootFailure,
get_protocol::EventLogId::BOOT_FAILURE_SECURE_BOOT_FAILED => {
GuestEvent::BootFailureSecureBootFailed
}
get_protocol::EventLogId::NO_BOOT_DEVICE => GuestEvent::NoBootDevice,
get_protocol::EventLogId::ATTESTATION_FAILED => GuestEvent::AttestationFailed,
get_protocol::EventLogId::VMGS_FILE_CLEAR => GuestEvent::VmgsFileClear,
get_protocol::EventLogId::VMGS_INIT_FAILED => GuestEvent::VmgsInitFailed,
get_protocol::EventLogId::VMGS_INVALID_FORMAT => GuestEvent::VmgsInvalidFormat,
get_protocol::EventLogId::VMGS_CORRUPT_FORMAT => GuestEvent::VmgsCorruptFormat,
get_protocol::EventLogId::KEY_NOT_RELEASED => GuestEvent::KeyNotReleased,
get_protocol::EventLogId::DEK_DECRYPTION_FAILED => GuestEvent::DekDecryptionFailed,
get_protocol::EventLogId::BOOT_ATTEMPT => GuestEvent::BootAttempt,
get_protocol::EventLogId::WATCHDOG_TIMEOUT_RESET => GuestEvent::WatchdogTimeoutReset,
_ => {
tracing::error!(event_log_id = msg.event_log_id.0, "unknown event log id");
return Ok(());
}
};
tracing::info!(?event, "GET event");
match event {
GuestEvent::BootAttempt => state.send_event(FirmwareEvent::BootAttempt),
GuestEvent::BootSuccess | GuestEvent::BootSuccessSecureBootFailed => {
state.send_event(FirmwareEvent::BootSuccess);
}
GuestEvent::BootFailure | GuestEvent::BootFailureSecureBootFailed => {
state.send_event(FirmwareEvent::BootFailed);
}
GuestEvent::NoBootDevice => state.send_event(FirmwareEvent::NoBootDevice),
_ => {}
}
Ok(())
}
fn handle_restore_guest_vtl2_state_completed(
&mut self,
message_buf: &[u8],
) -> Result<(), Error> {
let message =
get_protocol::RestoreGuestVtl2StateHostNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; let success = match message.status {
get_protocol::GuestVtl2SaveRestoreStatus::SUCCESS => true,
get_protocol::GuestVtl2SaveRestoreStatus::FAILURE => false,
_ => return Err(Error::InvalidFieldValue),
};
tracing::info!(success, "restore vtl2 complete");
Ok(())
}
fn handle_start_vtl0_completed(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let (message, remaining) =
get_protocol::StartVtl0CompleteNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?; let expected_len = message.result_document_size as usize;
if remaining.len() != expected_len {
return Err(Error::InvalidFieldValue);
}
let result = match message.status {
StartVtl0Status::SUCCESS => {
tracing::info!("guest reported vtl0 started successfully");
Ok(())
}
StartVtl0Status::FAILURE => {
let err = Vtl0StartError(String::from_utf8_lossy(remaining).into_owned());
tracing::error!(
error = &err as &dyn std::error::Error,
"guest reported vtl0 failed to start"
);
state.power_client.power_request(PowerRequest::PowerOff);
Err(err)
}
_ => return Err(Error::InvalidFieldValue),
};
for response in state.waiting_for_vtl0_start.drain(..) {
response.complete(result.clone());
}
self.vtl0_start_report = Some(result);
Ok(())
}
fn handle_vtl_crash(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let msg = get_protocol::VtlCrashNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?
.0; tracing::info!("Guest has reported a system crash {msg:x?}");
Ok(())
}
fn handle_triple_fault(
&mut self,
state: &mut GuestEmulationDevice,
message_buf: &[u8],
) -> Result<(), Error> {
let (msg, remaining) = get_protocol::TripleFaultNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?; let expected_len = msg.register_count as usize * size_of::<RegisterState>();
if remaining.len() != expected_len {
return Err(Error::InvalidFieldValue);
}
let registers = <[RegisterState]>::ref_from_bytes(remaining).unwrap();
tracing::info!("Guest has reported a triple fault {msg:x?} {registers:?}");
state
.power_client
.power_request(PowerRequest::TripleFault { vp: msg.vp_index });
Ok(())
}
fn handle_modify_vtl2_settings_completed(&mut self, message_buf: &[u8]) -> Result<(), Error> {
let (msg, remaining) =
get_protocol::ModifyVtl2SettingsCompleteNotification::read_from_prefix(message_buf)
.map_err(|_| Error::MessageTooSmall)?; let modify = self.modify.take().ok_or(Error::InvalidSequence)?;
let r = match msg.modify_status {
get_protocol::ModifyVtl2SettingsStatus::SUCCESS => Ok(()),
get_protocol::ModifyVtl2SettingsStatus::FAILURE => {
let errors = std::str::from_utf8(
remaining
.get(..msg.result_document_size as usize)
.ok_or(Error::MessageTooSmall)?,
)
.map_err(|_| Error::InvalidFieldValue)?;
Err(ModifyVtl2SettingsError::Guest(errors.to_owned()))
}
_ => return Err(Error::InvalidFieldValue),
};
modify.complete(r);
Ok(())
}
fn handle_device_platform_settings_v2(
&mut self,
state: &mut GuestEmulationDevice,
) -> Result<(), Error> {
let vpci_boot_enabled;
let enable_firmware_debugging;
let disable_frontpage;
let firmware_mode_is_pcat;
let pcat_boot_device_order;
let uefi_console_mode;
match state.config.firmware {
GuestFirmwareConfig::Uefi {
enable_vpci_boot,
firmware_debug,
disable_frontpage: v_disable_frontpage,
console_mode,
} => {
vpci_boot_enabled = enable_vpci_boot;
enable_firmware_debugging = firmware_debug;
disable_frontpage = v_disable_frontpage;
firmware_mode_is_pcat = false;
pcat_boot_device_order = None;
uefi_console_mode = Some(console_mode);
}
GuestFirmwareConfig::Pcat { boot_order } => {
vpci_boot_enabled = false;
enable_firmware_debugging = false;
disable_frontpage = false;
firmware_mode_is_pcat = true;
pcat_boot_device_order = Some(boot_order);
uefi_console_mode = None;
}
}
let json = get_protocol::dps_json::DevicePlatformSettingsV2Json {
v1: get_protocol::dps_json::HclDevicePlatformSettings {
com1: get_protocol::dps_json::HclUartSettings {
enable_port: state.config.com1,
debugger_mode: false,
enable_vmbus_redirector: state.config.com1,
},
com2: get_protocol::dps_json::HclUartSettings {
enable_port: state.config.com2,
debugger_mode: false,
enable_vmbus_redirector: state.config.com2,
},
enable_firmware_debugging,
enable_tpm: state.config.enable_tpm,
secure_boot_enabled: state.config.secure_boot_enabled,
secure_boot_template_id: match state.config.secure_boot_template {
SecureBootTemplateType::SECURE_BOOT_DISABLED => HclSecureBootTemplateId::None,
SecureBootTemplateType::MICROSOFT_WINDOWS => {
HclSecureBootTemplateId::MicrosoftWindows
}
SecureBootTemplateType::MICROSOFT_UEFI_CERTIFICATE_AUTHORITY => {
HclSecureBootTemplateId::MicrosoftUEFICertificateAuthority
}
_ => panic!("Invalid secure boot template"),
},
enable_battery: state.config.enable_battery,
console_mode: uefi_console_mode.unwrap_or(UefiConsoleMode::DEFAULT).0,
..Default::default()
},
v2: get_protocol::dps_json::HclDevicePlatformSettingsV2 {
r#static: get_protocol::dps_json::HclDevicePlatformSettingsV2Static {
disable_frontpage,
vmbus_redirection_enabled: state.config.vmbus_redirection,
vtl2_settings: state.config.vtl2_settings.clone(),
firmware_mode_is_pcat,
no_persistent_secrets: state.config.no_persistent_secrets,
legacy_memory_map: false,
pause_after_boot_failure: false,
pxe_ip_v6: false,
measure_additional_pcrs: true,
disable_sha384_pcr: false,
media_present_enabled_by_default: false,
memory_protection_mode: 0,
vpci_boot_enabled,
vpci_instance_filter: None,
num_lock_enabled: false,
pcat_boot_device_order,
smbios: Default::default(),
watchdog_enabled: false,
always_relay_host_mmio: false,
imc_enabled: false,
cxl_memory_enabled: false,
},
dynamic: get_protocol::dps_json::HclDevicePlatformSettingsV2Dynamic {
is_servicing_scenario: state.save_restore_buf.is_some(),
..Default::default()
},
},
};
let json_data = serde_json::to_vec(&json).map_err(Error::SerializeDpsV2)?;
if json_data.len() > MAX_PAYLOAD_SIZE {
return Err(Error::LargeDpsV2Unimplemented);
}
let response = get_protocol::DevicePlatformSettingsResponseV2Rev1 {
message_header: HeaderGeneric::new(HostRequests::DEVICE_PLATFORM_SETTINGS_V2_REV1),
size: json_data.len() as u32,
payload_state: get_protocol::LargePayloadState::END,
};
self.channel
.try_send_vectored(&[IoSlice::new(response.as_bytes()), IoSlice::new(&json_data)])
.map_err(Error::Vmbus)?;
Ok(())
}
fn send_hardcoded_battery_update(&mut self) -> Result<(), Error> {
let mut flags = BatteryStatusFlags::new();
flags.set_ac_online(true);
flags.set_battery_present(true);
flags.set_charging(true);
flags.set_discharging(false);
flags.set_reserved(0);
let response = BatteryStatusNotification::new(flags, 1000, 950, 1);
self.channel
.try_send(response.as_bytes())
.map_err(Error::Vmbus)?;
Ok(())
}
}