From d070c527c1107c43bc2d962e4908a8d568fd22a6 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 12:39:21 -0700 Subject: [PATCH 01/31] add openhcl_tdisp_resources and openhcl_tdisp modules --- openhcl/openhcl_tdisp/Cargo.toml | 18 ++++ openhcl/openhcl_tdisp/src/lib.rs | 20 +++++ openhcl/openhcl_tdisp_resources/Cargo.toml | 16 ++++ openhcl/openhcl_tdisp_resources/src/lib.rs | 97 ++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 openhcl/openhcl_tdisp/Cargo.toml create mode 100644 openhcl/openhcl_tdisp/src/lib.rs create mode 100644 openhcl/openhcl_tdisp_resources/Cargo.toml create mode 100644 openhcl/openhcl_tdisp_resources/src/lib.rs diff --git a/openhcl/openhcl_tdisp/Cargo.toml b/openhcl/openhcl_tdisp/Cargo.toml new file mode 100644 index 0000000000..1b16a925b4 --- /dev/null +++ b/openhcl/openhcl_tdisp/Cargo.toml @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "openhcl_tdisp" +rust-version.workspace = true +edition.workspace = true + +[dependencies] +tracing.workspace = true +inspect.workspace = true +tdisp.workspace = true +openhcl_tdisp_resources.workspace = true + +anyhow.workspace = true + +[lints] +workspace = true diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs new file mode 100644 index 0000000000..400baae958 --- /dev/null +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! +//! WARNING: *** This crate is a work in progress, do not use in production! *** +//! +//! This module provides an implementation of the TDISP client device +//! interface for OpenHCL devices. +//! +//! See: `vm/tdisp` for more information. + +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(missing_docs)] + +use openhcl_tdisp_resources::VpciTdispInterface; +use tdisp::GuestToHostCommand; +use tdisp::GuestToHostResponse; +use tdisp::TdispCommandId; +use tdisp::TdispCommandResponsePayload; diff --git a/openhcl/openhcl_tdisp_resources/Cargo.toml b/openhcl/openhcl_tdisp_resources/Cargo.toml new file mode 100644 index 0000000000..75f3f03108 --- /dev/null +++ b/openhcl/openhcl_tdisp_resources/Cargo.toml @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "openhcl_tdisp_resources" +rust-version.workspace = true +edition.workspace = true + +[dependencies] +inspect.workspace = true +tdisp.workspace = true + +anyhow.workspace = true + +[lints] +workspace = true diff --git a/openhcl/openhcl_tdisp_resources/src/lib.rs b/openhcl/openhcl_tdisp_resources/src/lib.rs new file mode 100644 index 0000000000..7ec1834ddb --- /dev/null +++ b/openhcl/openhcl_tdisp_resources/src/lib.rs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! +//! WARNING: *** This crate is a work in progress, do not use in production! *** +//! +//! This module provides resources and traits for a TDISP client device +//! interface for OpenHCL devices. +//! +//! See: `vm/tdisp` for more information. +//! See: `openhcl_tdisp` for more information. + +use inspect::Inspect; +use std::future::Future; +use std::sync::Arc; +use tdisp::GuestToHostCommand; +use tdisp::GuestToHostResponse; +pub use tdisp::TdispCommandId; +use tdisp::TdispDeviceReportType; +use tdisp::TdispGuestUnbindReason; +use tdisp::TdispUnbindReason; +use tdisp::devicereport::TdiReportStruct; +pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; + +/// Represents a TDISP device assigned to a guest partition. This trait allows +/// the guest to send TDISP commands to the host through the backing interface. +/// [TDISP TODO] Change out `anyhow` for a `TdispError` type. +pub trait ClientDevice: Send + Sync + Inspect { + /// Send a TDISP command to the host through the backing interface. + fn tdisp_command_to_host( + &self, + command: GuestToHostCommand, + ) -> anyhow::Result; + + /// Checks if the device is TDISP capable and returns the device interface info if so. + fn tdisp_get_device_interface_info(&self) -> anyhow::Result; + + /// Bind the device to the current partition and transition to Locked. + fn tdisp_bind_interface(&self) -> anyhow::Result<()>; +} + +/// Trait for registering TDISP devices. +pub trait RegisterTdisp: Send { + /// Registers a TDISP capable device on the host. + fn register(&mut self, target: Arc); +} + +/// No operation struct for tests to implement `RegisterTdisp`. +pub struct TestTdispRegisterNoOp {} + +impl RegisterTdisp for TestTdispRegisterNoOp { + fn register(&mut self, _target: Arc) { + todo!() + } +} + +pub trait VpciTdispInterface: Send + Sync { + /// Sends a TDISP command to the device through the VPCI channel. + fn send_tdisp_command( + &self, + payload: GuestToHostCommand, + ) -> impl Future> + Send; + + /// Get the TDISP interface info for the device. + fn tdisp_get_device_interface_info( + &self, + ) -> impl Future> + Send; + + /// Bind the device to the current partition and transition to Locked. + /// NOTE: While the device is in the Locked state, it can continue to + /// perform unencrypted operations until it is moved to the Running state. + /// The Locked state is a transitional state that is designed to keep + /// the device from modifying its resources prior to attestation. + fn tdisp_bind_interface(&self) -> impl Future> + Send; + + /// Start a bound device by transitioning it to the Run state from the Locked state. + /// This allows for attestation and for resources to be accepted into the guest context. + fn tdisp_start_device(&self) -> impl Future> + Send; + + /// Request a device report from the TDI or physical device depending on the report type. + fn tdisp_get_device_report( + &self, + report_type: &TdispDeviceReportType, + ) -> impl Future>> + Send; + + /// Request a TDI report from the TDI or physical device. + fn tdisp_get_tdi_report(&self) -> impl Future> + Send; + + /// Request the TDI device id from the vpci channel. + fn tdisp_get_tdi_device_id(&self) -> impl Future> + Send; + + /// Request to unbind the device and return to the Unlocked state. + fn tdisp_unbind( + &self, + reason: TdispGuestUnbindReason, + ) -> impl Future> + Send; +} From 23c2fc99e7b2ba8b1d21b6ecd0c94cb35326fbb6 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 12:46:24 -0700 Subject: [PATCH 02/31] comment cleanup --- openhcl/openhcl_tdisp/src/lib.rs | 2 -- openhcl/openhcl_tdisp_resources/src/lib.rs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 400baae958..3761c60706 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! -//! WARNING: *** This crate is a work in progress, do not use in production! *** //! //! This module provides an implementation of the TDISP client device //! interface for OpenHCL devices. diff --git a/openhcl/openhcl_tdisp_resources/src/lib.rs b/openhcl/openhcl_tdisp_resources/src/lib.rs index 7ec1834ddb..2f769a9cb9 100644 --- a/openhcl/openhcl_tdisp_resources/src/lib.rs +++ b/openhcl/openhcl_tdisp_resources/src/lib.rs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! -//! WARNING: *** This crate is a work in progress, do not use in production! *** //! //! This module provides resources and traits for a TDISP client device //! interface for OpenHCL devices. @@ -73,7 +71,7 @@ pub trait VpciTdispInterface: Send + Sync { /// the device from modifying its resources prior to attestation. fn tdisp_bind_interface(&self) -> impl Future> + Send; - /// Start a bound device by transitioning it to the Run state from the Locked state. + /// Start a bound device by transitioning it to the from the Locked state to the Run state. /// This allows for attestation and for resources to be accepted into the guest context. fn tdisp_start_device(&self) -> impl Future> + Send; From 480fe56ecf89363eb0cc6c16d7e32492e3b6a6da Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 12:55:50 -0700 Subject: [PATCH 03/31] add tdisp module --- vm/devices/tdisp/Cargo.toml | 20 + vm/devices/tdisp/src/command.rs | 175 ++++++ vm/devices/tdisp/src/devicereport.rs | 87 +++ vm/devices/tdisp/src/lib.rs | 796 +++++++++++++++++++++++++++ vm/devices/tdisp/src/serialize.rs | 238 ++++++++ 5 files changed, 1316 insertions(+) create mode 100644 vm/devices/tdisp/Cargo.toml create mode 100644 vm/devices/tdisp/src/command.rs create mode 100644 vm/devices/tdisp/src/devicereport.rs create mode 100644 vm/devices/tdisp/src/lib.rs create mode 100644 vm/devices/tdisp/src/serialize.rs diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml new file mode 100644 index 0000000000..1fe67eacff --- /dev/null +++ b/vm/devices/tdisp/Cargo.toml @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "tdisp" +edition.workspace = true +rust-version.workspace = true + +[dependencies] +inspect.workspace = true +anyhow.workspace = true +tracing.workspace = true +thiserror.workspace = true +zerocopy.workspace = true +parking_lot.workspace = true +bitfield-struct.workspace = true +static_assertions.workspace = true + +[lints] +workspace = true diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs new file mode 100644 index 0000000000..6de522b7d8 --- /dev/null +++ b/vm/devices/tdisp/src/command.rs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::TdispGuestOperationError; +use crate::TdispTdiState; +use std::fmt::Display; +use zerocopy::FromBytes; +use zerocopy::Immutable; +use zerocopy::IntoBytes; +use zerocopy::KnownLayout; + +/// Represents a TDISP command sent from the guest to the host. +#[derive(Debug, Copy, Clone)] +pub struct GuestToHostCommand { + /// Device ID of the target device. + pub device_id: u64, + /// The command ID. + pub command_id: TdispCommandId, + /// The payload of the command if it has one. + pub payload: TdispCommandRequestPayload, +} + +/// Represents a response from a TDISP command sent to the host by a guest. +#[derive(Debug, Clone)] +pub struct GuestToHostResponse { + /// The command ID. + pub command_id: TdispCommandId, + /// The result status of the command. + pub result: TdispGuestOperationError, + /// The state of the TDI before the command was executed. + pub tdi_state_before: TdispTdiState, + /// The state of the TDI after the command was executed. + pub tdi_state_after: TdispTdiState, + /// The payload of the response if it has one. + pub payload: TdispCommandResponsePayload, +} + +impl Display for GuestToHostCommand { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Display the Debug representation of the command. + f.debug_struct("GuestToHostCommand") + .field("command_id", &self.command_id) + .finish() + } +} + +/// Represents a TDISP command sent from the guest to the host. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TdispCommandId { + /// Invalid command id. + Unknown, + + /// Request the device's TDISP interface information. + GetDeviceInterfaceInfo, + + /// Bind the device to the current partition and transition to Locked. + Bind, + + /// Get the TDI report for attestation from the host for the device. + GetTdiReport, + + /// Transition the device to the Start state after successful attestation. + StartTdi, + + /// Unbind the device from the partition, reverting it back to the Unlocked state. + Unbind, +} + +impl From for u64 { + fn from(value: TdispCommandId) -> Self { + match value { + TdispCommandId::Unknown => 0, + TdispCommandId::GetDeviceInterfaceInfo => 1, + TdispCommandId::Bind => 2, + TdispCommandId::GetTdiReport => 3, + TdispCommandId::StartTdi => 4, + TdispCommandId::Unbind => 5, + } + } +} + +impl From for TdispCommandId { + fn from(value: u64) -> Self { + match value { + 0 => TdispCommandId::Unknown, + 1 => TdispCommandId::GetDeviceInterfaceInfo, + 2 => TdispCommandId::Bind, + 3 => TdispCommandId::GetTdiReport, + 4 => TdispCommandId::StartTdi, + 5 => TdispCommandId::Unbind, + _ => TdispCommandId::Unknown, + } + } +} + +/// Represents the TDISP device interface information, such as the version and supported features. +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct TdispDeviceInterfaceInfo { + /// The major version for the interface. This does not necessarily match to a TDISP specification version. + /// [TDISP TODO] dead_code + pub interface_version_major: u32, + + /// The minor version for the interface. This does not necessarily match to a TDISP specification version. + /// [TDISP TODO] dead_code + pub interface_version_minor: u32, + + /// [TDISP TODO] Placeholder for bitfield advertising feature set capabilities. + pub supported_features: u64, + + /// Device ID used to communicate with firmware for this particular device. + pub tdisp_device_id: u64, +} + +/// Serialized to and from the payload field of a TdispCommandResponse +#[derive(Debug, Clone)] +pub enum TdispCommandResponsePayload { + /// No payload. + None, + + /// TdispCommandId::GetDeviceInterfaceInfo + GetDeviceInterfaceInfo(TdispDeviceInterfaceInfo), + + /// TdispCommandId::GetTdiReport + GetTdiReport(TdispCommandResponseGetTdiReport), +} + +impl From for TdispCommandResponsePayload { + fn from(value: TdispDeviceInterfaceInfo) -> Self { + TdispCommandResponsePayload::GetDeviceInterfaceInfo(value) + } +} + +/// Serialized to and from the payload field of a TdispCommandRequest +#[derive(Debug, Copy, Clone)] +pub enum TdispCommandRequestPayload { + /// No payload. + None, + + /// TdispCommandId::Unbind + Unbind(TdispCommandRequestUnbind), + + /// TdispCommandId::GetTdiReport + GetTdiReport(TdispCommandRequestGetTdiReport), +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct TdispCommandRequestUnbind { + pub unbind_reason: u64, +} + +/// Represents a request to get a specific device report form the TDI. +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct TdispCommandRequestGetTdiReport { + /// The type of report to request. + /// See: `TdispDeviceReportType`` + pub report_type: u32, +} + +/// Represents the payload of the resposne for a TdispCommandId::GetTdiReport. +#[derive(Debug, Clone)] +pub struct TdispCommandResponseGetTdiReport { + /// The type of report requested. + /// See: `TdispDeviceReportType`` + pub report_type: u32, + + /// The buffer containing the requested report. + pub report_buffer: Vec, +} + +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct TdispSerializedCommandRequestGetTdiReport { + pub report_type: u32, + pub report_buffer_size: u32, + // Report buffer follows. +} diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs new file mode 100644 index 0000000000..590a3e82df --- /dev/null +++ b/vm/devices/tdisp/src/devicereport.rs @@ -0,0 +1,87 @@ +use bitfield_struct::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[bitfield(u16)] +#[derive(KnownLayout, FromBytes, Immutable)] +pub struct TdispTdiReportInterfaceInfo { + pub firmware_update_allowed: bool, + pub generate_dma_without_pasid: bool, + pub generate_dma_with_pasid: bool, + pub ats_support_enabled: bool, + pub prs_support_enabled: bool, + #[bits(11)] + _reserved0: u16, +} + +#[bitfield(u16)] +#[derive(KnownLayout, FromBytes, Immutable)] +pub struct TdispTdiReportMmioFlags { + pub range_maps_msix_table: bool, + pub range_maps_msix_pba: bool, + pub is_non_tee_mem: bool, + pub is_mem_attr_updatable: bool, + #[bits(12)] + _reserved0: u16, +} + +#[derive(KnownLayout, FromBytes, Immutable, Clone, Debug)] +pub struct TdispTdiReportMmioInterfaceInfo { + pub first_4k_page_offset: u64, + pub num_4k_pages: u32, + pub flags: TdispTdiReportMmioFlags, + pub range_id: u16, +} + +static_assertions::const_assert_eq!(size_of::(), 0x10); + +#[derive(KnownLayout, FromBytes, Immutable, Debug)] +#[repr(C)] +struct TdiReportStructSerialized { + pub interface_info: TdispTdiReportInterfaceInfo, + pub _reserved0: u16, + pub msi_x_message_control: u16, + pub lnr_control: u16, + pub tph_control: u32, + pub mmio_range_count: u32, + // Follows is a variable-sized # of `MmioInterfaceInfo` structs + // based on the value of `mmio_range_count`. +} + +static_assertions::const_assert_eq!(size_of::(), 0x10); + +/// The deserialized form of a TDI interface report. +#[derive(Debug)] +pub struct TdiReportStruct { + pub interface_info: TdispTdiReportInterfaceInfo, + pub msi_x_message_control: u16, + pub lnr_control: u16, + pub tph_control: u32, + pub mmio_interface_info: Vec, +} + +/// Reads a TDI interface report provided from the host into a struct. +pub fn deserialize_tdi_report(data: &[u8]) -> anyhow::Result { + // Deserialize the static part of the report. + let report_header = TdiReportStructSerialized::read_from_prefix(data) + .map_err(|e| anyhow::anyhow!("failed to deserialize TDI report header: {e:?}"))?; + let variable_portion_offset = report_header.1; + let report = report_header.0; + + // Deserialize the variable portion of the report. + let read_mmio_elems = <[TdispTdiReportMmioInterfaceInfo]>::ref_from_prefix_with_elems( + variable_portion_offset, + report.mmio_range_count as usize, + ) + .map_err(|e| anyhow::anyhow!("failed to deserialize TDI report mmio_interface_info: {e:?}"))?; + + // [TDISP TODO] Parse the vendor specific info + let _vendor_specific_info = read_mmio_elems.1.to_vec(); + + Ok(TdiReportStruct { + interface_info: report.interface_info, + msi_x_message_control: report.msi_x_message_control, + lnr_control: report.lnr_control, + tph_control: report.tph_control, + mmio_interface_info: read_mmio_elems.0.to_vec(), + }) +} diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs new file mode 100644 index 0000000000..d3d2ab3527 --- /dev/null +++ b/vm/devices/tdisp/src/lib.rs @@ -0,0 +1,796 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! +//! WARNING: *** This crate is a work in progress, do not use in production! *** +//! +//! TDISP is a standardized interface for end-to-end encryption and attestation +//! of trusted assigned devices to confidential/isolated partitions. This crate +//! implements structures and interfaces for the host and guest to prepare and +//! assign trusted devices. Examples of technologies that implement TDISP +//! include: +//! - IntelĀ® "TDX Connect" +//! - AMD SEV-TIO + +// [TDISP TODO] Remove this once the TDISP interface is stable. +#![allow(dead_code)] + +pub mod command; +pub mod devicereport; +pub mod serialize; +use bitfield_struct::bitfield; +use std::sync::Arc; + +use anyhow::Context; +pub use command::{ + GuestToHostCommand, GuestToHostResponse, TdispCommandId, TdispCommandResponsePayload, + TdispDeviceInterfaceInfo, +}; +use inspect::Inspect; +use parking_lot::Mutex; +use thiserror::Error; + +use crate::command::{TdispCommandRequestPayload, TdispCommandResponseGetTdiReport}; + +/// Major version of the TDISP guest-to-host interface. +pub const TDISP_INTERFACE_VERSION_MAJOR: u32 = 1; + +/// Minor version of the TDISP guest-to-host interface. +pub const TDISP_INTERFACE_VERSION_MINOR: u32 = 0; + +/// Callback for receiving TDISP commands from the guest. +pub type TdispCommandCallback = dyn Fn(&GuestToHostCommand) -> anyhow::Result<()> + Send + Sync; + +/// Represents a type of report that can be requested from the TDI (VF). +#[derive(Debug)] +pub enum TdispTdiReport { + TdiInfoInvalid, + TdiInfoGuestDeviceId, + TdiInfoInterfaceReport, +} + +/// Represents a type of report that can be requested from the physical device. +#[derive(Debug)] +pub enum TdispDeviceReport { + DeviceInfoInvalid, + DeviceInfoCertificateChain, + DeviceInfoMeasurements, + DeviceInfoIsRegistered, +} + +impl From<&TdispTdiReport> for u32 { + fn from(value: &TdispTdiReport) -> Self { + match value { + TdispTdiReport::TdiInfoInvalid => 0, + TdispTdiReport::TdiInfoGuestDeviceId => 1, + TdispTdiReport::TdiInfoInterfaceReport => 2, + } + } +} + +// Set to the number of enums in TdispTdiReport +pub const TDISP_TDI_REPORT_ENUM_COUNT: u32 = 3; + +impl From<&TdispDeviceReport> for u32 { + fn from(value: &TdispDeviceReport) -> Self { + match value { + TdispDeviceReport::DeviceInfoInvalid => TDISP_TDI_REPORT_ENUM_COUNT, + TdispDeviceReport::DeviceInfoCertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, + TdispDeviceReport::DeviceInfoMeasurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, + TdispDeviceReport::DeviceInfoIsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, + } + } +} + +impl From<&TdispDeviceReportType> for u32 { + fn from(value: &TdispDeviceReportType) -> Self { + match value { + TdispDeviceReportType::TdiReport(report_type) => report_type.into(), + TdispDeviceReportType::DeviceReport(report_type) => report_type.into(), + } + } +} + +impl From for TdispDeviceReportType { + fn from(value: u32) -> Self { + match value { + 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoGuestDeviceId), + 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInterfaceReport), + 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), + 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), + 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), + 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), + _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + } + } +} + +/// Represents a type of report that can be requested from an assigned TDISP device. +#[derive(Debug)] +pub enum TdispDeviceReportType { + /// A report produced by the device interface and not the physical interface. + TdiReport(TdispTdiReport), + + /// A report produced by the physical interface and not the device interface. + DeviceReport(TdispDeviceReport), +} + +/// Trait used by the emulator to call back into the host. +pub trait TdispHostDeviceInterface: Send + Sync { + /// Bind a tdi device to the current partition. Transitions device to the Locked + /// state from Unlocked. + fn tdisp_bind_device(&mut self) -> anyhow::Result<()> { + Err(anyhow::anyhow!("not implemented")) + } + + /// Start a bound device by transitioning it to the Run state from the Locked state. + /// This allows attestation and resources to be accepted into the guest context. + fn tdisp_start_device(&mut self) -> anyhow::Result<()> { + Err(anyhow::anyhow!("not implemented")) + } + + /// Unbind a tdi device from the current partition. + fn tdisp_unbind_device(&mut self) -> anyhow::Result<()> { + Err(anyhow::anyhow!("not implemented")) + } + + /// Get a device interface report for the device. + fn tdisp_get_device_report( + &mut self, + _report_type: &TdispDeviceReportType, + ) -> anyhow::Result> { + Err(anyhow::anyhow!("not implemented")) + } +} + +/// Trait added to host VPCI devices to allow them to dispatch TDISP commands from guests. +pub trait TdispHostDeviceTarget: Send + Sync { + /// [TDISP TODO] Highly subject to change as we work out the traits and semantics. + fn tdisp_handle_guest_command( + &mut self, + _command: GuestToHostCommand, + ) -> anyhow::Result { + tracing::warn!("TdispHostDeviceTarget not implemented: tdisp_dispatch"); + anyhow::bail!("TdispHostDeviceTarget not implemented: tdisp_dispatch") + } +} + +/// An emulator which runs the TDISP state machine for a synthetic device. +pub struct TdispHostDeviceTargetEmulator { + machine: TdispHostStateMachine, + debug_device_id: String, +} + +impl TdispHostDeviceTargetEmulator { + /// Create a new emulator which runs the TDISP state machine for a synthetic device. + pub fn new(host_interface: Arc>) -> Self { + Self { + machine: TdispHostStateMachine::new(host_interface), + debug_device_id: "".to_owned(), + } + } + + /// Set the debug device ID string. + pub fn set_debug_device_id(&mut self, debug_device_id: &str) { + self.machine.set_debug_device_id(debug_device_id.to_owned()); + self.debug_device_id = debug_device_id.to_owned(); + } + + /// Print a debug message to the log. + /// [TDISP TODO] Fix print type, make this accept a formatter instead. + fn debug_print(&self, msg: String) { + self.machine.debug_print(&msg); + } + + /// Print an error message to the log. + fn error_print(&self, msg: String) { + self.machine.error_print(&msg); + } + + /// Reset the emulator. + pub fn reset(&self) {} + + /// Get the device interface info for this device. + fn get_device_interface_info(&self) -> TdispDeviceInterfaceInfo { + TdispDeviceInterfaceInfo { + interface_version_major: TDISP_INTERFACE_VERSION_MAJOR, + interface_version_minor: TDISP_INTERFACE_VERSION_MINOR, + supported_features: 0, + tdisp_device_id: 0, + } + } +} + +impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { + fn tdisp_handle_guest_command( + &mut self, + command: GuestToHostCommand, + ) -> anyhow::Result { + self.debug_print(format!( + "tdisp_handle_guest_command: command = {:?}", + command + )); + + let mut error = TdispGuestOperationError::Success; + let mut payload = TdispCommandResponsePayload::None; + let state_before = self.machine.state(); + match command.command_id { + TdispCommandId::GetDeviceInterfaceInfo => { + let interface_info = self.get_device_interface_info(); + payload = TdispCommandResponsePayload::GetDeviceInterfaceInfo(interface_info); + } + TdispCommandId::Bind => { + let bind_res = self.machine.request_lock_device_resources(); + if let Err(err) = bind_res { + error = err; + } else { + payload = TdispCommandResponsePayload::None; + } + } + TdispCommandId::StartTdi => { + let start_tdi_res = self.machine.request_start_tdi(); + if let Err(err) = start_tdi_res { + error = err; + } else { + payload = TdispCommandResponsePayload::None; + } + } + TdispCommandId::Unbind => { + let unbind_reason: TdispGuestUnbindReason = match command.payload { + TdispCommandRequestPayload::Unbind(payload) => payload.unbind_reason.into(), + _ => TdispGuestUnbindReason::Unknown, + }; + let unbind_res = self.machine.request_unbind(unbind_reason); + if let Err(err) = unbind_res { + error = err; + } + } + TdispCommandId::GetTdiReport => { + let report_type = match &command.payload { + TdispCommandRequestPayload::GetTdiReport(payload) => { + TdispDeviceReportType::from(payload.report_type) + } + _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + }; + + let report_buffer = self.machine.request_attestation_report(&report_type); + if let Err(err) = report_buffer { + error = err; + } else { + payload = TdispCommandResponsePayload::GetTdiReport( + TdispCommandResponseGetTdiReport { + report_type: (&report_type).into(), + report_buffer: report_buffer.unwrap(), + }, + ); + } + } + TdispCommandId::Unknown => { + error = TdispGuestOperationError::InvalidGuestCommandId; + } + } + let state_after = self.machine.state(); + + match error { + TdispGuestOperationError::Success => { + self.debug_print("tdisp_handle_guest_command: Success".to_owned()); + } + _ => { + self.error_print(format!("tdisp_handle_guest_command: Error: {error:?}")); + } + } + + let resp = GuestToHostResponse { + command_id: command.command_id, + result: error, + tdi_state_before: state_before, + tdi_state_after: state_after, + payload, + }; + + self.debug_print(format!("tdisp_handle_guest_command: response = {resp:?}")); + + Ok(resp) + } +} + +/// Trait implemented by TDISP-capable devices on the client side. This includes devices that +/// are assigned to isolated partitions other than the host. +pub trait TdispClientDevice: Send + Sync { + /// Send a TDISP command to the host for this device. + /// [TDISP TODO] Async? Better handling of device_id in GuestToHostCommand? + fn tdisp_command_to_host(&self, command: GuestToHostCommand) -> anyhow::Result<()>; +} + +/// Represents the state of the TDISP host device emulator. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect)] +pub enum TdispTdiState { + /// The TDISP state is not initialized or indeterminate. + Uninitialized, + + /// `TDI.Unlocked`` - The device is in its default "reset" state. Resources can be configured + /// and no functionality can be used. Attestation cannot take place until the device has + /// been locked. + Unlocked, + + /// `TDI.Locked`` - The device resources have been locked and attestation can take place. The + /// device's resources have been mapped and configured in hardware, but the device has not + /// been attested. The platform will not allow the device to be functional until it has + /// passed attestation and all device resources have been accepted into the guest context. + Locked, + + /// `TDI.Run`` - The device is fully functional and attestation has succeeded. The device's + /// resources have been mapped and accepted into the guest context. The device is ready to + /// be used. + Run, +} + +impl From for u64 { + fn from(value: TdispTdiState) -> Self { + match value { + TdispTdiState::Uninitialized => 0, + TdispTdiState::Unlocked => 1, + TdispTdiState::Locked => 2, + TdispTdiState::Run => 3, + } + } +} + +impl From for TdispTdiState { + fn from(value: u64) -> Self { + match value { + 0 => TdispTdiState::Uninitialized, + 1 => TdispTdiState::Unlocked, + 2 => TdispTdiState::Locked, + 3 => TdispTdiState::Run, + _ => TdispTdiState::Uninitialized, + } + } +} + +/// The number of states to keep in the state history for debug. +const TDISP_STATE_HISTORY_LEN: usize = 10; + +/// The reason for an `Unbind` call. `Unbind` can be called any time during the assignment flow. +#[derive(Debug)] +pub enum TdispUnbindReason { + /// Unknown reason. + Unknown(anyhow::Error), + + /// The device was unbound manually by the guest or host for a non-error reason. + GuestInitiated(TdispGuestUnbindReason), + + /// The device attempted to perform an invalid state transition. + ImpossibleStateTransition(anyhow::Error), + + /// The guest tried to transition the device to the Locked state while the device was not + /// in the Unlocked state. + InvalidGuestTransitionToLocked, + + /// The guest tried to transition the device to the Run state while the device was not + /// in the Locked state. + InvalidGuestTransitionToRun, + + /// The guest tried to retrieve the attestation report while the device was not in the + /// Locked state. + InvalidGuestGetAttestationReportState, + + /// The guest tried to accept the attestation report while the device was not in the + /// Locked state. + InvalidGuestAcceptAttestationReportState, + + /// The guest tried to unbind the device while the device with an unbind reason that is + /// not recognized as a valid guest unbind reason. The unbind still succeeds but the + /// recorded reason is discarded. + InvalidGuestUnbindReason(anyhow::Error), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TdispGuestUnbindReason { + /// The guest requested to unbind the device for an unspecified reason. + Unknown, + + /// The guest requested to unbind the device because the device is being detached. + Graceful, +} + +impl From for u64 { + fn from(value: TdispGuestUnbindReason) -> Self { + match value { + TdispGuestUnbindReason::Unknown => 0, + TdispGuestUnbindReason::Graceful => 1, + } + } +} + +impl From for TdispGuestUnbindReason { + fn from(value: u64) -> Self { + match value { + 1 => TdispGuestUnbindReason::Graceful, + _ => TdispGuestUnbindReason::Unknown, + } + } +} + +/// The state machine for the TDISP assignment flow for a device. Both the guest and host +/// synchronize this state machine with each other as they move through the assignment flow. +pub struct TdispHostStateMachine { + /// The current state of the TDISP device emulator. + current_state: TdispTdiState, + /// A record of the last states the device was in. + state_history: Vec, + /// The device ID of the device being assigned. + debug_device_id: String, + /// A record of the last unbind reasons for the device. + unbind_reason_history: Vec, + /// Calls back into the host to perform TDISP actions. + host_interface: Arc>, +} + +impl TdispHostStateMachine { + /// Create a new TDISP state machine with the `Unlocked` state. + pub fn new(host_interface: Arc>) -> Self { + Self { + current_state: TdispTdiState::Unlocked, + state_history: Vec::new(), + debug_device_id: "".to_owned(), + unbind_reason_history: Vec::new(), + host_interface, + } + } + + /// Set the debug device ID string. + pub fn set_debug_device_id(&mut self, debug_device_id: String) { + self.debug_device_id = debug_device_id; + } + + /// Print a debug message to the log. + fn debug_print(&self, msg: &str) { + tracing::error!(msg = format!("[TdispEmu] [{}] {}", self.debug_device_id, msg)); + } + + /// Print an error message to the log. + fn error_print(&self, msg: &str) { + tracing::error!(msg = format!("[TdispEmu] [{}] {}", self.debug_device_id, msg)); + } + + /// Get the current state of the TDI. + fn state(&self) -> TdispTdiState { + self.current_state + } + + /// Check if the state machine can transition to the new state. This protects the underlying state machinery + /// while higher level transition machinery tries to avoid these conditions. If the new state is impossible, + /// `false` is returned. + fn is_valid_state_transition(&self, new_state: &TdispTdiState) -> bool { + match (self.current_state, new_state) { + // Valid forward progress states from Unlocked -> Run + (TdispTdiState::Unlocked, TdispTdiState::Locked) => true, + (TdispTdiState::Locked, TdispTdiState::Run) => true, + + // Device can always return to the Unlocked state with `Unbind` + (TdispTdiState::Run, TdispTdiState::Unlocked) => true, + (TdispTdiState::Locked, TdispTdiState::Unlocked) => true, + (TdispTdiState::Unlocked, TdispTdiState::Unlocked) => true, + + // Every other state transition is invalid + _ => false, + } + } + + /// Check if the guest unbind reason is valid. This is used for bookkeeping purposes to + /// ensure the guest unbind reason recorded in the unbind history is valid. + fn is_valid_guest_unbind_reason(&self, reason: &TdispGuestUnbindReason) -> bool { + !(matches!(reason, TdispGuestUnbindReason::Unknown)) + } + + /// Transitions the state machine to the new state if it is valid. If the new state is invalid, + /// the state of the device is reset to the `Unlocked` state. + fn transition_state_to(&mut self, new_state: TdispTdiState) -> anyhow::Result<()> { + self.debug_print(&format!( + "Request to transition from {:?} -> {:?}", + self.current_state, new_state + )); + + // Ensure the state transition is valid + if !self.is_valid_state_transition(&new_state) { + self.debug_print(&format!( + "Invalid state transition {:?} -> {:?}", + self.current_state, new_state + )); + return Err(anyhow::anyhow!( + "Invalid state transition {:?} -> {:?}", + self.current_state, + new_state + )); + } + + // Record the state history + if self.state_history.len() == TDISP_STATE_HISTORY_LEN { + self.state_history.remove(0); + } + self.state_history.push(self.current_state); + + // Transition to the new state + self.current_state = new_state; + self.debug_print(&format!("Transitioned to {:?}", self.current_state)); + + Ok(()) + } + + /// Transition the device to the `Unlocked` state regardless of the current state. + fn unbind_all(&mut self, reason: TdispUnbindReason) -> anyhow::Result<()> { + self.debug_print(&format!("Unbind called with reason {:?}", reason)); + + // All states can be reset to the Unlocked state. This can only happen if the + // state is corrupt beyond the state machine. + if let Err(reason) = self.transition_state_to(TdispTdiState::Unlocked) { + panic!( + "[{}] Impossible state machine violation during TDISP Unbind: {:?}", + self.debug_device_id, reason + ); + } + + // Call back into the host to bind the device. + let res = self + .host_interface + .lock() + .tdisp_unbind_device() + .context("host failed to unbind TDI"); + + if let Err(e) = res { + self.error_print(format!("Failed to unbind TDI: {:?}", e).as_str()); + return Err(e); + } + + // Record the unbind reason + if self.unbind_reason_history.len() == TDISP_STATE_HISTORY_LEN { + self.unbind_reason_history.remove(0); + } + self.unbind_reason_history.push(reason); + + Ok(()) + } +} + +/// Error returned by TDISP operations dispatched by the guest. +#[derive(Error, Debug, Copy, Clone)] +#[expect(missing_docs)] +pub enum TdispGuestOperationError { + #[error("the operation was successful")] + Success, + #[error("the current TDI state is incorrect for this operation")] + InvalidDeviceState, + #[error("the reason for this unbind is invalid")] + InvalidGuestUnbindReason, + #[error("invalid TDI command ID")] + InvalidGuestCommandId, + #[error("operation requested was not implemented")] + NotImplemented, + #[error("host failed to process command")] + HostFailedToProcessCommand, + #[error( + "the device was not in the Locked or Run state when the attestation report was requested" + )] + InvalidGuestAttestationReportState, + #[error("invalid attestation report type requested")] + InvalidGuestAttestationReportType, +} + +impl From for u64 { + fn from(err: TdispGuestOperationError) -> Self { + match err { + TdispGuestOperationError::Success => 0, + TdispGuestOperationError::InvalidDeviceState => 1, + TdispGuestOperationError::InvalidGuestUnbindReason => 2, + TdispGuestOperationError::InvalidGuestCommandId => 3, + TdispGuestOperationError::NotImplemented => 4, + TdispGuestOperationError::HostFailedToProcessCommand => 5, + TdispGuestOperationError::InvalidGuestAttestationReportState => 6, + TdispGuestOperationError::InvalidGuestAttestationReportType => 7, + } + } +} + +impl From for TdispGuestOperationError { + fn from(err: u64) -> Self { + match err { + 0 => TdispGuestOperationError::Success, + 1 => TdispGuestOperationError::InvalidDeviceState, + 2 => TdispGuestOperationError::InvalidGuestUnbindReason, + 3 => TdispGuestOperationError::InvalidGuestCommandId, + 4 => TdispGuestOperationError::NotImplemented, + 5 => TdispGuestOperationError::HostFailedToProcessCommand, + 6 => TdispGuestOperationError::InvalidGuestAttestationReportState, + 7 => TdispGuestOperationError::InvalidGuestAttestationReportType, + _ => panic!("invalid TdispGuestOperationError code: {err}"), + } + } +} + +/// Represents an interface by which guest commands can be dispatched to a +/// backing TDISP state handler. This could be an emulated TDISP device or an +/// assigned TDISP device that is actually connected to the guest. +pub trait TdispGuestRequestInterface { + /// Transition the device from the Unlocked to Locked state. This takes place after the + /// device has been assigned to the guest partition and the resources for the device have + /// been configured by the guest. The device will in the `Locked` state can still perform + /// unencrypted operations until it has been transitioned to the `Run` state. The device + /// will be attested in the `Run` state. + /// + /// Attempting to transition the device to the `Locked` state while the device is not in the + /// `Unlocked` state will cause an error and unbind the device. + fn request_lock_device_resources(&mut self) -> Result<(), TdispGuestOperationError>; + + /// Transition the device from the Locked to the Run state. This takes place after the + /// device has been assigned resources and the resources have been locked to the guest. + /// The device will then transition to the `Run` state, where it will be non-functional + /// until the guest undergoes attestation and resources are accepted into the guest context. + /// + /// Attempting to transition the device to the `Run` state while the device is not in the + /// `Locked` state will cause an error and unbind the device. + fn request_start_tdi(&mut self) -> Result<(), TdispGuestOperationError>; + + /// Transition the device from the Locked to the Run Retrieves the + /// attestation report for the device when the device is in the `Locked` or + /// `Run` state. The device resources will not be functional until the + /// resources have been accepted into the guest while the device is in the + /// `Run` state. + /// + /// Attempting to retrieve the attestation report while the device is not in + /// the `Locked` or `Run` state will cause an error and unbind the device. + fn request_attestation_report( + &mut self, + report_type: &TdispDeviceReportType, + ) -> Result, TdispGuestOperationError>; + + /// Guest initiates a graceful unbind of the device. The guest might + /// initiate an unbind for a variety of reasons: + /// - Device is being detached/deactivated and is no longer needed in a functional state + /// - Device is powering down or entering a reset + /// + /// The device will transition to the `Unlocked` state. The guest can call + /// this function at any time in any state to reset the device to the + /// `Unlocked` state. + fn request_unbind( + &mut self, + reason: TdispGuestUnbindReason, + ) -> Result<(), TdispGuestOperationError>; +} + +impl TdispGuestRequestInterface for TdispHostStateMachine { + fn request_lock_device_resources(&mut self) -> Result<(), TdispGuestOperationError> { + // If the guest attempts to transition the device to the Locked state while the device + // is not in the Unlocked state, the device is reset to the Unlocked state. + if self.current_state != TdispTdiState::Unlocked { + self.error_print( + "Unlocked to Locked state called while device was not in Unlocked state.", + ); + + self.unbind_all(TdispUnbindReason::InvalidGuestTransitionToLocked) + .map_err(|_| TdispGuestOperationError::HostFailedToProcessCommand)?; + return Err(TdispGuestOperationError::InvalidDeviceState); + } + + self.debug_print( + "Device bind requested, trying to transition from Unlocked to Locked state", + ); + + // Call back into the host to bind the device. + let res = self + .host_interface + .lock() + .tdisp_bind_device() + .context("failed to call to bind TDI"); + + if let Err(e) = res { + self.error_print(format!("Failed to bind TDI: {e:?}").as_str()); + return Err(TdispGuestOperationError::HostFailedToProcessCommand); + } + + self.debug_print("Device transition from Unlocked to Locked state"); + self.transition_state_to(TdispTdiState::Locked).unwrap(); + Ok(()) + } + + fn request_start_tdi(&mut self) -> Result<(), TdispGuestOperationError> { + if self.current_state != TdispTdiState::Locked { + self.error_print("StartTDI called while device was not in Locked state."); + self.unbind_all(TdispUnbindReason::InvalidGuestTransitionToRun) + .map_err(|_| TdispGuestOperationError::HostFailedToProcessCommand)?; + + return Err(TdispGuestOperationError::InvalidDeviceState); + } + + self.debug_print("Device start requested, trying to transition from Locked to Run state"); + + // Call back into the host to bind the device. + let res = self + .host_interface + .lock() + .tdisp_start_device() + .context("failed to call to start TDI"); + + if let Err(e) = res { + self.error_print(format!("Failed to start TDI: {e:?}").as_str()); + return Err(TdispGuestOperationError::HostFailedToProcessCommand); + } + + self.debug_print("Device transition from Locked to Run state"); + self.transition_state_to(TdispTdiState::Run).unwrap(); + + Ok(()) + } + + fn request_attestation_report( + &mut self, + report_type: &TdispDeviceReportType, + ) -> Result, TdispGuestOperationError> { + if self.current_state != TdispTdiState::Locked && self.current_state != TdispTdiState::Run { + self.error_print( + "Request to retrieve attestation report called while device was not in Locked or Run state.", + ); + self.unbind_all(TdispUnbindReason::InvalidGuestGetAttestationReportState) + .map_err(|_| TdispGuestOperationError::HostFailedToProcessCommand)?; + + return Err(TdispGuestOperationError::InvalidGuestAttestationReportState); + } + + match report_type { + TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid) => { + self.error_print("Invalid report type TdispTdiReport::TdiInfoInvalid requested"); + return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); + } + TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid) => { + self.error_print( + "Invalid report type TdispDeviceReport::DeviceInfoInvalid requested", + ); + return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); + } + _ => {} + }; + + let report_buffer = self + .host_interface + .lock() + .tdisp_get_device_report(report_type) + .context("failed to call to get device report from host"); + + if let Err(e) = report_buffer { + self.error_print(format!("Failed to get device report from host: {e:?}").as_str()); + return Err(TdispGuestOperationError::HostFailedToProcessCommand); + } + + self.debug_print("Retrieve attestation report called successfully"); + Ok(report_buffer.unwrap()) + } + + fn request_unbind( + &mut self, + reason: TdispGuestUnbindReason, + ) -> Result<(), TdispGuestOperationError> { + // The guest can provide a reason for the unbind. If the unbind reason isn't valid for a guest (such as + // if the guest says it is unbinding due to a host-related error), the reason is discarded and InvalidGuestUnbindReason + // is recorded in the unbind history. + let reason = if !self.is_valid_guest_unbind_reason(&reason) { + let error_txt = format!("Invalid guest unbind reason {reason:?} requested"); + + self.error_print(error_txt.as_str()); + + TdispUnbindReason::InvalidGuestUnbindReason(anyhow::anyhow!(error_txt)) + } else { + TdispUnbindReason::GuestInitiated(reason) + }; + + self.debug_print(&format!( + "Guest request to unbind succeeds while device is in {:?} (reason: {:?})", + self.current_state, reason + )); + + self.unbind_all(reason) + .map_err(|_| TdispGuestOperationError::HostFailedToProcessCommand)?; + + Ok(()) + } +} diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs new file mode 100644 index 0000000000..09ce4e4dc4 --- /dev/null +++ b/vm/devices/tdisp/src/serialize.rs @@ -0,0 +1,238 @@ +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, TryFromBytes}; + +use crate::command::{ + TdispCommandRequestGetTdiReport, TdispCommandRequestPayload, TdispCommandRequestUnbind, + TdispCommandResponseGetTdiReport, TdispSerializedCommandRequestGetTdiReport, +}; +use crate::{ + GuestToHostCommand, GuestToHostResponse, TdispCommandResponsePayload, TdispDeviceReport, + TdispDeviceReportType, TdispGuestOperationError, TdispTdiReport, +}; +use crate::{TdispCommandId, TdispDeviceInterfaceInfo}; + +/// Serialized form of the header for a GuestToHostCommand packet +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct GuestToHostCommandSerializedHeader { + pub device_id: u64, + pub command_id: u64, +} + +/// Serialized form of the header for a GuestToHostResponse packet +#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] +pub struct GuestToHostResponseSerializedHeader { + pub command_id: u64, + pub result: u64, + pub tdi_state_before: u64, + pub tdi_state_after: u64, +} + +// [TDISP TODO] There's probably a better way to do these conversions. +impl From<&GuestToHostCommand> for GuestToHostCommandSerializedHeader { + fn from(value: &GuestToHostCommand) -> Self { + GuestToHostCommandSerializedHeader { + device_id: value.device_id, + command_id: value.command_id.into(), + } + } +} + +impl From<&GuestToHostResponse> for GuestToHostResponseSerializedHeader { + fn from(value: &GuestToHostResponse) -> Self { + GuestToHostResponseSerializedHeader { + command_id: value.command_id.into(), + result: value.result.into(), + tdi_state_before: value.tdi_state_before.into(), + tdi_state_after: value.tdi_state_after.into(), + } + } +} + +impl From<&GuestToHostCommandSerializedHeader> for GuestToHostCommand { + fn from(value: &GuestToHostCommandSerializedHeader) -> Self { + GuestToHostCommand { + device_id: value.device_id, + command_id: value.command_id.into(), + payload: TdispCommandRequestPayload::None, + } + } +} + +impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { + fn from(value: &GuestToHostResponseSerializedHeader) -> Self { + GuestToHostResponse { + command_id: value.command_id.into(), + result: value.result.into(), + tdi_state_before: value.tdi_state_before.into(), + tdi_state_after: value.tdi_state_after.into(), + payload: TdispCommandResponsePayload::None, + } + } +} +pub trait SerializePacket: Sized { + fn serialize_to_bytes(self) -> Vec; + fn deserialize_from_bytes(bytes: &[u8]) -> Result; +} + +impl SerializePacket for GuestToHostCommand { + fn serialize_to_bytes(self) -> Vec { + let header = GuestToHostCommandSerializedHeader::from(&self); + let bytes = header.as_bytes(); + + let mut bytes = bytes.to_vec(); + match self.payload { + TdispCommandRequestPayload::None => {} + TdispCommandRequestPayload::Unbind(info) => bytes.extend_from_slice(info.as_bytes()), + TdispCommandRequestPayload::GetTdiReport(info) => { + bytes.extend_from_slice(info.as_bytes()) + } + }; + + bytes + } + + fn deserialize_from_bytes(bytes: &[u8]) -> Result { + let header_length = size_of::(); + tracing::error!(msg = format!("deserialize_from_bytes: header_length={header_length}")); + tracing::error!(msg = format!("deserialize_from_bytes: {:?}", bytes)); + + let header_bytes = &bytes[0..header_length]; + tracing::error!(msg = format!("deserialize_from_bytes: header_bytes={:?}", header_bytes)); + + let header = + GuestToHostCommandSerializedHeader::try_ref_from_bytes(header_bytes).map_err(|e| { + anyhow::anyhow!("failed to deserialize GuestToHostCommand header: {:?}", e) + })?; + + let payload_slice = &bytes[header_length..]; + + let mut packet: Self = header.into(); + let payload = match packet.command_id { + TdispCommandId::Unbind => TdispCommandRequestPayload::Unbind( + TdispCommandRequestUnbind::try_read_from_bytes(payload_slice).map_err(|e| { + anyhow::anyhow!("failed to deserialize TdispCommandRequestUnbind: {:?}", e) + })?, + ), + TdispCommandId::Bind => TdispCommandRequestPayload::None, + TdispCommandId::GetDeviceInterfaceInfo => TdispCommandRequestPayload::None, + TdispCommandId::StartTdi => TdispCommandRequestPayload::None, + TdispCommandId::GetTdiReport => TdispCommandRequestPayload::GetTdiReport( + TdispCommandRequestGetTdiReport::try_read_from_bytes(payload_slice).map_err( + |e| { + anyhow::anyhow!( + "failed to deserialize TdispCommandRequestGetTdiReport: {:?}", + e + ) + }, + )?, + ), + TdispCommandId::Unknown => { + return Err(anyhow::anyhow!( + "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", + header.command_id + )); + } + }; + + packet.payload = payload; + + Ok(packet) + } +} + +impl SerializePacket for GuestToHostResponse { + fn serialize_to_bytes(self) -> Vec { + let header = GuestToHostResponseSerializedHeader::from(&self); + let bytes = header.as_bytes(); + + let mut bytes = bytes.to_vec(); + match self.payload { + TdispCommandResponsePayload::None => {} + TdispCommandResponsePayload::GetDeviceInterfaceInfo(info) => { + bytes.extend_from_slice(info.as_bytes()) + } + TdispCommandResponsePayload::GetTdiReport(info) => { + let header = TdispSerializedCommandRequestGetTdiReport { + report_type: info.report_type, + report_buffer_size: info.report_buffer.len() as u32, + }; + + bytes.extend_from_slice(header.as_bytes()); + bytes.extend_from_slice(info.report_buffer.as_bytes()); + } + }; + + bytes + } + + // [TDISP TODO] Clean up this serialization code to be a bit more generic. + fn deserialize_from_bytes(bytes: &[u8]) -> Result { + let header_length = size_of::(); + let header = + GuestToHostResponseSerializedHeader::try_ref_from_bytes(&bytes[0..header_length]) + .map_err(|e| { + anyhow::anyhow!("failed to deserialize GuestToHostResponse header: {:?}", e) + })?; + + let mut packet: Self = header.into(); + + // If the result is not success, then we don't need to deserialize the payload. + match packet.result { + TdispGuestOperationError::Success => {} + _ => { + return Ok(packet); + } + } + + let payload_slice = &bytes[header_length..]; + + let payload = match packet.command_id { + TdispCommandId::GetDeviceInterfaceInfo => { + TdispCommandResponsePayload::GetDeviceInterfaceInfo( + TdispDeviceInterfaceInfo::try_read_from_bytes(payload_slice).map_err(|e| { + anyhow::anyhow!("failed to deserialize TdispDeviceInterfaceInfo: {:?}", e) + })?, + ) + } + TdispCommandId::Bind => TdispCommandResponsePayload::None, + TdispCommandId::Unbind => TdispCommandResponsePayload::None, + TdispCommandId::StartTdi => TdispCommandResponsePayload::None, + TdispCommandId::GetTdiReport => { + // Peel off the header from the payload + let payload_header_len = size_of::(); + let payload_header_slice = &payload_slice[0..payload_header_len]; + + // Read the header + let payload_header = + TdispSerializedCommandRequestGetTdiReport::try_read_from_bytes( + payload_header_slice, + ) + .map_err(|e| { + anyhow::anyhow!( + "failed to deserialize TdispSerializedCommandRequestGetTdiReport: {:?}", + e + ) + })?; + + // Determine the number of bytes to read from the payload for the report buffer + let payload_bytes = &payload_slice[payload_header_len + ..(payload_header_len + payload_header.report_buffer_size as usize)]; + + // Convert this to the response type + TdispCommandResponsePayload::GetTdiReport(TdispCommandResponseGetTdiReport { + report_type: payload_header.report_type, + report_buffer: payload_bytes.to_vec(), + }) + } + TdispCommandId::Unknown => { + return Err(anyhow::anyhow!( + "invalid payload type in GuestToHostResponse: {:?}", + header.result + )); + } + }; + + packet.payload = payload; + + Ok(packet) + } +} From 98d45c124e8c80d91b0926c8feebbecbbb6d68fb Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 12:55:54 -0700 Subject: [PATCH 04/31] add modules to root level cargo --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 8a4ed18ed6..23e5479673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,6 +167,8 @@ lower_vtl_permissions_guard = { path = "openhcl/lower_vtl_permissions_guard" } minimal_rt = { path = "openhcl/minimal_rt" } minimal_rt_build = { path = "openhcl/minimal_rt_build" } openhcl_dma_manager = { path = "openhcl/openhcl_dma_manager" } +openhcl_tdisp = { path = "openhcl/openhcl_tdisp" } +openhcl_tdisp_resources = { path = "openhcl/openhcl_tdisp_resources" } sidecar_client = { path = "openhcl/sidecar_client" } sidecar_defs = { path = "openhcl/sidecar_defs" } tee_call = { path = "openhcl/tee_call" } @@ -252,6 +254,7 @@ nvme_resources = { path = "vm/devices/storage/nvme_resources" } nvme_spec = { path = "vm/devices/storage/nvme_spec" } nvme_test = { path = "vm/devices/storage/nvme_test" } storage_string = { path = "vm/devices/storage/storage_string" } +tdisp = { path = "vm/devices/tdisp" } vmswitch = { path = "vm/devices/net/vmswitch" } pci_bus = { path = "vm/devices/pci/pci_bus" } pci_core = { path = "vm/devices/pci/pci_core" } From e3e8a858b6067fd4a216280ff041fa9895787890 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 13:23:20 -0700 Subject: [PATCH 05/31] cleanup docs in tdisp module --- vm/devices/tdisp/src/devicereport.rs | 76 +++++++++++++++ vm/devices/tdisp/src/lib.rs | 136 ++++++++------------------- 2 files changed, 114 insertions(+), 98 deletions(-) diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 590a3e82df..a9b7cab7f4 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -1,6 +1,82 @@ use bitfield_struct::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; +/// Represents a type of report that can be requested from the TDI (VF). +#[derive(Debug)] +pub enum TdispTdiReport { + TdiInfoInvalid, + TdiInfoGuestDeviceId, + TdiInfoInterfaceReport, +} + +/// Represents a type of report that can be requested from the physical device. +#[derive(Debug)] +pub enum TdispDeviceReport { + DeviceInfoInvalid, + DeviceInfoCertificateChain, + DeviceInfoMeasurements, + DeviceInfoIsRegistered, +} + +impl From<&TdispTdiReport> for u32 { + fn from(value: &TdispTdiReport) -> Self { + match value { + TdispTdiReport::TdiInfoInvalid => 0, + TdispTdiReport::TdiInfoGuestDeviceId => 1, + TdispTdiReport::TdiInfoInterfaceReport => 2, + } + } +} + +// Set to the number of enums in TdispTdiReport to assign an ID that is unique for this enum. +// [TDISP TODO] Is there a better way to do this with Rust const types? +pub const TDISP_TDI_REPORT_ENUM_COUNT: u32 = 3; + +impl From<&TdispDeviceReport> for u32 { + fn from(value: &TdispDeviceReport) -> Self { + match value { + TdispDeviceReport::DeviceInfoInvalid => TDISP_TDI_REPORT_ENUM_COUNT, + TdispDeviceReport::DeviceInfoCertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, + TdispDeviceReport::DeviceInfoMeasurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, + TdispDeviceReport::DeviceInfoIsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, + } + } +} + +impl From<&TdispDeviceReportType> for u32 { + fn from(value: &TdispDeviceReportType) -> Self { + match value { + TdispDeviceReportType::TdiReport(report_type) => report_type.into(), + TdispDeviceReportType::DeviceReport(report_type) => report_type.into(), + } + } +} + +impl From for TdispDeviceReportType { + fn from(value: u32) -> Self { + match value { + 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoGuestDeviceId), + 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInterfaceReport), + TDISP_TDI_REPORT_ENUM_COUNT + 0 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), + TDISP_TDI_REPORT_ENUM_COUNT + 1 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), + TDISP_TDI_REPORT_ENUM_COUNT + 2 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), + TDISP_TDI_REPORT_ENUM_COUNT + 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), + _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + } + } +} + +/// Represents a type of report that can be requested from an assigned TDISP device. +#[derive(Debug)] +pub enum TdispDeviceReportType { + /// A report produced by the device interface and not the physical interface. + TdiReport(TdispTdiReport), + + /// A report produced by the physical interface and not the device interface. + DeviceReport(TdispDeviceReport), +} + #[bitfield(u16)] #[derive(KnownLayout, FromBytes, Immutable)] pub struct TdispTdiReportInterfaceInfo { diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index d3d2ab3527..1f1a12e1d9 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! -//! WARNING: *** This crate is a work in progress, do not use in production! *** //! //! TDISP is a standardized interface for end-to-end encryption and attestation //! of trusted assigned devices to confidential/isolated partitions. This crate @@ -11,9 +9,25 @@ //! include: //! - IntelĀ® "TDX Connect" //! - AMD SEV-TIO - -// [TDISP TODO] Remove this once the TDISP interface is stable. -#![allow(dead_code)] +//! +//! This crate is primarily used to implement the host side of the guest-to-host +//! interface for TDISP as well as the serialization of guest-to-host commands for both +//! the host and HCL. +//! +//! These structures and interfaces are used by the host virtualization stack +//! to prepare and assign trusted devices to guest partitions. +//! +//! The host is responsible for dispatching guest commands to this machinery by +//! creating a `TdispHostDeviceTargetEmulator` and calling through appropriate +//! trait methods to pass guest commands received from the guest to the emulator. +//! +//! This crate will handle incoming guest message structs and manage the state transitions +//! of the TDISP device and ensure valid transitions are made. Once a valid transition is made, the +//! `TdispHostDeviceTargetEmulator` will call back into the host through the +//! `TdispHostDeviceInterface` trait to allow the host to perform platform actions +//! such as binding the device to a guest partition or retrieving attestation reports. +//! It is the responsibility of the host to provide a `TdispHostDeviceInterface` +//! implementation that performs the necessary platform actions. pub mod command; pub mod devicereport; @@ -41,81 +55,6 @@ pub const TDISP_INTERFACE_VERSION_MINOR: u32 = 0; /// Callback for receiving TDISP commands from the guest. pub type TdispCommandCallback = dyn Fn(&GuestToHostCommand) -> anyhow::Result<()> + Send + Sync; -/// Represents a type of report that can be requested from the TDI (VF). -#[derive(Debug)] -pub enum TdispTdiReport { - TdiInfoInvalid, - TdiInfoGuestDeviceId, - TdiInfoInterfaceReport, -} - -/// Represents a type of report that can be requested from the physical device. -#[derive(Debug)] -pub enum TdispDeviceReport { - DeviceInfoInvalid, - DeviceInfoCertificateChain, - DeviceInfoMeasurements, - DeviceInfoIsRegistered, -} - -impl From<&TdispTdiReport> for u32 { - fn from(value: &TdispTdiReport) -> Self { - match value { - TdispTdiReport::TdiInfoInvalid => 0, - TdispTdiReport::TdiInfoGuestDeviceId => 1, - TdispTdiReport::TdiInfoInterfaceReport => 2, - } - } -} - -// Set to the number of enums in TdispTdiReport -pub const TDISP_TDI_REPORT_ENUM_COUNT: u32 = 3; - -impl From<&TdispDeviceReport> for u32 { - fn from(value: &TdispDeviceReport) -> Self { - match value { - TdispDeviceReport::DeviceInfoInvalid => TDISP_TDI_REPORT_ENUM_COUNT, - TdispDeviceReport::DeviceInfoCertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, - TdispDeviceReport::DeviceInfoMeasurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, - TdispDeviceReport::DeviceInfoIsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, - } - } -} - -impl From<&TdispDeviceReportType> for u32 { - fn from(value: &TdispDeviceReportType) -> Self { - match value { - TdispDeviceReportType::TdiReport(report_type) => report_type.into(), - TdispDeviceReportType::DeviceReport(report_type) => report_type.into(), - } - } -} - -impl From for TdispDeviceReportType { - fn from(value: u32) -> Self { - match value { - 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), - 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoGuestDeviceId), - 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInterfaceReport), - 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), - 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), - 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), - 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), - _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), - } - } -} - -/// Represents a type of report that can be requested from an assigned TDISP device. -#[derive(Debug)] -pub enum TdispDeviceReportType { - /// A report produced by the device interface and not the physical interface. - TdiReport(TdispTdiReport), - - /// A report produced by the physical interface and not the device interface. - DeviceReport(TdispDeviceReport), -} - /// Trait used by the emulator to call back into the host. pub trait TdispHostDeviceInterface: Send + Sync { /// Bind a tdi device to the current partition. Transitions device to the Locked @@ -144,9 +83,8 @@ pub trait TdispHostDeviceInterface: Send + Sync { } } -/// Trait added to host VPCI devices to allow them to dispatch TDISP commands from guests. +/// Trait added to host virtual devices to dispatch TDISP commands from guests. pub trait TdispHostDeviceTarget: Send + Sync { - /// [TDISP TODO] Highly subject to change as we work out the traits and semantics. fn tdisp_handle_guest_command( &mut self, _command: GuestToHostCommand, @@ -178,7 +116,6 @@ impl TdispHostDeviceTargetEmulator { } /// Print a debug message to the log. - /// [TDISP TODO] Fix print type, make this accept a formatter instead. fn debug_print(&self, msg: String) { self.machine.debug_print(&msg); } @@ -316,13 +253,14 @@ pub enum TdispTdiState { /// `TDI.Locked`` - The device resources have been locked and attestation can take place. The /// device's resources have been mapped and configured in hardware, but the device has not - /// been attested. The platform will not allow the device to be functional until it has - /// passed attestation and all device resources have been accepted into the guest context. + /// been attested. Private DMA and MMIO will not be functional until the resources have + /// been accepted into the guest context. Unencrypted "bounced" operations are still allowed. Locked, - /// `TDI.Run`` - The device is fully functional and attestation has succeeded. The device's - /// resources have been mapped and accepted into the guest context. The device is ready to - /// be used. + /// `TDI.Run`` - The device is no longer functional for unencrypted operations. Device resources + /// are locked but encrypted operations might not be functional. The device + /// will not be functional for encrypted operations until it has been fully validated by the guest + /// calling to firmware to accept resources. Run, } @@ -352,7 +290,9 @@ impl From for TdispTdiState { /// The number of states to keep in the state history for debug. const TDISP_STATE_HISTORY_LEN: usize = 10; -/// The reason for an `Unbind` call. `Unbind` can be called any time during the assignment flow. +/// The reason for an `Unbind` call. This can be guest or host initiated. +/// `Unbind` can be called any time during the assignment flow. +/// This is used for telemetry and debugging. #[derive(Debug)] pub enum TdispUnbindReason { /// Unknown reason. @@ -373,11 +313,11 @@ pub enum TdispUnbindReason { InvalidGuestTransitionToRun, /// The guest tried to retrieve the attestation report while the device was not in the - /// Locked state. + /// Locked or Run state. InvalidGuestGetAttestationReportState, /// The guest tried to accept the attestation report while the device was not in the - /// Locked state. + /// Locked or Run state. InvalidGuestAcceptAttestationReportState, /// The guest tried to unbind the device while the device with an unbind reason that is @@ -386,6 +326,7 @@ pub enum TdispUnbindReason { InvalidGuestUnbindReason(anyhow::Error), } +/// For a guest initiated unbind, the guest can provide a reason for the unbind. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TdispGuestUnbindReason { /// The guest requested to unbind the device for an unspecified reason. @@ -413,7 +354,7 @@ impl From for TdispGuestUnbindReason { } } -/// The state machine for the TDISP assignment flow for a device. Both the guest and host +/// The state machine for the TDISP assignment flow for a device on the host. Both the guest and host /// synchronize this state machine with each other as they move through the assignment flow. pub struct TdispHostStateMachine { /// The current state of the TDISP device emulator. @@ -610,14 +551,14 @@ impl From for TdispGuestOperationError { } /// Represents an interface by which guest commands can be dispatched to a -/// backing TDISP state handler. This could be an emulated TDISP device or an +/// backing TDISP state handler in the host. This could be an emulated TDISP device or an /// assigned TDISP device that is actually connected to the guest. pub trait TdispGuestRequestInterface { /// Transition the device from the Unlocked to Locked state. This takes place after the /// device has been assigned to the guest partition and the resources for the device have - /// been configured by the guest. The device will in the `Locked` state can still perform - /// unencrypted operations until it has been transitioned to the `Run` state. The device - /// will be attested in the `Run` state. + /// been configured by the guest by not yet validated. + /// The device will in the `Locked` state can still perform unencrypted operations until it has + /// been transitioned to the `Run` state. The device will be attested and moved to the `Run` state. /// /// Attempting to transition the device to the `Locked` state while the device is not in the /// `Unlocked` state will cause an error and unbind the device. @@ -632,8 +573,7 @@ pub trait TdispGuestRequestInterface { /// `Locked` state will cause an error and unbind the device. fn request_start_tdi(&mut self) -> Result<(), TdispGuestOperationError>; - /// Transition the device from the Locked to the Run Retrieves the - /// attestation report for the device when the device is in the `Locked` or + /// Retrieves the attestation report for the device when the device is in the `Locked` or /// `Run` state. The device resources will not be functional until the /// resources have been accepted into the guest while the device is in the /// `Run` state. From f63fd42ed6d6aaea7a2e05778517c3efa315a149 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 13:28:20 -0700 Subject: [PATCH 06/31] old trait not needed --- openhcl/openhcl_tdisp_resources/src/lib.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openhcl/openhcl_tdisp_resources/src/lib.rs b/openhcl/openhcl_tdisp_resources/src/lib.rs index 2f769a9cb9..0dd13f01b5 100644 --- a/openhcl/openhcl_tdisp_resources/src/lib.rs +++ b/openhcl/openhcl_tdisp_resources/src/lib.rs @@ -37,21 +37,6 @@ pub trait ClientDevice: Send + Sync + Inspect { fn tdisp_bind_interface(&self) -> anyhow::Result<()>; } -/// Trait for registering TDISP devices. -pub trait RegisterTdisp: Send { - /// Registers a TDISP capable device on the host. - fn register(&mut self, target: Arc); -} - -/// No operation struct for tests to implement `RegisterTdisp`. -pub struct TestTdispRegisterNoOp {} - -impl RegisterTdisp for TestTdispRegisterNoOp { - fn register(&mut self, _target: Arc) { - todo!() - } -} - pub trait VpciTdispInterface: Send + Sync { /// Sends a TDISP command to the device through the VPCI channel. fn send_tdisp_command( From 3c9a604263a5199f80aec758afae6bf9de6c52a2 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 1 Oct 2025 14:07:50 -0700 Subject: [PATCH 07/31] refactor away openhcl_tdisp_resources crate into just openhcl_tdisp --- Cargo.lock | 25 +++++++ Cargo.toml | 1 - openhcl/openhcl_tdisp/Cargo.toml | 2 - openhcl/openhcl_tdisp/src/lib.rs | 78 ++++++++++++++++++--- openhcl/openhcl_tdisp_resources/Cargo.toml | 16 ----- openhcl/openhcl_tdisp_resources/src/lib.rs | 80 ---------------------- openhcl/underhill_core/Cargo.toml | 2 + vm/devices/tdisp/src/devicereport.rs | 8 +-- vm/devices/tdisp/src/lib.rs | 6 +- 9 files changed, 104 insertions(+), 114 deletions(-) delete mode 100644 openhcl/openhcl_tdisp_resources/Cargo.toml delete mode 100644 openhcl/openhcl_tdisp_resources/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f3c4d0a3c1..d8be7df8f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4999,6 +4999,15 @@ dependencies = [ "vmcore", ] +[[package]] +name = "openhcl_tdisp" +version = "0.0.0" +dependencies = [ + "anyhow", + "inspect", + "tdisp", +] + [[package]] name = "openssl" version = "0.10.73" @@ -7073,6 +7082,20 @@ dependencies = [ "x86defs", ] +[[package]] +name = "tdisp" +version = "0.0.0" +dependencies = [ + "anyhow", + "bitfield-struct 0.11.0", + "inspect", + "parking_lot", + "static_assertions", + "thiserror 2.0.16", + "tracing", + "zerocopy 0.8.25", +] + [[package]] name = "tdx_guest_device" version = "0.0.0" @@ -7841,6 +7864,7 @@ dependencies = [ "nvme_resources", "openhcl_attestation_protocol", "openhcl_dma_manager", + "openhcl_tdisp", "pal", "pal_async", "pal_uring", @@ -7862,6 +7886,7 @@ dependencies = [ "storvsp", "storvsp_resources", "string_page_buf", + "tdisp", "tee_call", "test_with_tracing", "thiserror 2.0.16", diff --git a/Cargo.toml b/Cargo.toml index 23e5479673..bfff8fc5a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,7 +168,6 @@ minimal_rt = { path = "openhcl/minimal_rt" } minimal_rt_build = { path = "openhcl/minimal_rt_build" } openhcl_dma_manager = { path = "openhcl/openhcl_dma_manager" } openhcl_tdisp = { path = "openhcl/openhcl_tdisp" } -openhcl_tdisp_resources = { path = "openhcl/openhcl_tdisp_resources" } sidecar_client = { path = "openhcl/sidecar_client" } sidecar_defs = { path = "openhcl/sidecar_defs" } tee_call = { path = "openhcl/tee_call" } diff --git a/openhcl/openhcl_tdisp/Cargo.toml b/openhcl/openhcl_tdisp/Cargo.toml index 1b16a925b4..926cecaf0f 100644 --- a/openhcl/openhcl_tdisp/Cargo.toml +++ b/openhcl/openhcl_tdisp/Cargo.toml @@ -7,10 +7,8 @@ rust-version.workspace = true edition.workspace = true [dependencies] -tracing.workspace = true inspect.workspace = true tdisp.workspace = true -openhcl_tdisp_resources.workspace = true anyhow.workspace = true diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 3761c60706..177e5b31d4 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -2,17 +2,77 @@ // Licensed under the MIT License. //! -//! This module provides an implementation of the TDISP client device -//! interface for OpenHCL devices. +//! This module provides resources and traits for a TDISP client device +//! interface for assigned deviecs in OpenHCL. //! //! See: `vm/tdisp` for more information. +//! See: `openhcl_tdisp` for more information. -#![allow(dead_code)] -#![allow(unused_variables)] -#![allow(missing_docs)] - -use openhcl_tdisp_resources::VpciTdispInterface; +use inspect::Inspect; +use std::future::Future; use tdisp::GuestToHostCommand; use tdisp::GuestToHostResponse; -use tdisp::TdispCommandId; -use tdisp::TdispCommandResponsePayload; +pub use tdisp::TdispCommandId; +use tdisp::TdispGuestUnbindReason; +use tdisp::devicereport::TdiReportStruct; +use tdisp::devicereport::TdispDeviceReportType; +pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; + +/// Represents a TDISP device assigned to a guest partition. This trait allows +/// the guest to send TDISP commands to the host through the backing interface. +/// [TDISP TODO] Change out `anyhow` for a `TdispError` type. +pub trait ClientDevice: Send + Sync + Inspect { + /// Send a TDISP command to the host through the backing interface. + fn tdisp_command_to_host( + &self, + command: GuestToHostCommand, + ) -> anyhow::Result; + + /// Checks if the device is TDISP capable and returns the device interface info if so. + fn tdisp_get_device_interface_info(&self) -> anyhow::Result; + + /// Bind the device to the current partition and transition to Locked. + fn tdisp_bind_interface(&self) -> anyhow::Result<()>; +} + +pub trait VpciTdispInterface: Send + Sync { + /// Sends a TDISP command to the device through the VPCI channel. + fn send_tdisp_command( + &self, + payload: GuestToHostCommand, + ) -> impl Future> + Send; + + /// Get the TDISP interface info for the device. + fn tdisp_get_device_interface_info( + &self, + ) -> impl Future> + Send; + + /// Bind the device to the current partition and transition to Locked. + /// NOTE: While the device is in the Locked state, it can continue to + /// perform unencrypted operations until it is moved to the Running state. + /// The Locked state is a transitional state that is designed to keep + /// the device from modifying its resources prior to attestation. + fn tdisp_bind_interface(&self) -> impl Future> + Send; + + /// Start a bound device by transitioning it to the from the Locked state to the Run state. + /// This allows for attestation and for resources to be accepted into the guest context. + fn tdisp_start_device(&self) -> impl Future> + Send; + + /// Request a device report from the TDI or physical device depending on the report type. + fn tdisp_get_device_report( + &self, + report_type: &TdispDeviceReportType, + ) -> impl Future>> + Send; + + /// Request a TDI report from the TDI or physical device. + fn tdisp_get_tdi_report(&self) -> impl Future> + Send; + + /// Request the TDI device id from the vpci channel. + fn tdisp_get_tdi_device_id(&self) -> impl Future> + Send; + + /// Request to unbind the device and return to the Unlocked state. + fn tdisp_unbind( + &self, + reason: TdispGuestUnbindReason, + ) -> impl Future> + Send; +} diff --git a/openhcl/openhcl_tdisp_resources/Cargo.toml b/openhcl/openhcl_tdisp_resources/Cargo.toml deleted file mode 100644 index 75f3f03108..0000000000 --- a/openhcl/openhcl_tdisp_resources/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -[package] -name = "openhcl_tdisp_resources" -rust-version.workspace = true -edition.workspace = true - -[dependencies] -inspect.workspace = true -tdisp.workspace = true - -anyhow.workspace = true - -[lints] -workspace = true diff --git a/openhcl/openhcl_tdisp_resources/src/lib.rs b/openhcl/openhcl_tdisp_resources/src/lib.rs deleted file mode 100644 index 0dd13f01b5..0000000000 --- a/openhcl/openhcl_tdisp_resources/src/lib.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! -//! This module provides resources and traits for a TDISP client device -//! interface for OpenHCL devices. -//! -//! See: `vm/tdisp` for more information. -//! See: `openhcl_tdisp` for more information. - -use inspect::Inspect; -use std::future::Future; -use std::sync::Arc; -use tdisp::GuestToHostCommand; -use tdisp::GuestToHostResponse; -pub use tdisp::TdispCommandId; -use tdisp::TdispDeviceReportType; -use tdisp::TdispGuestUnbindReason; -use tdisp::TdispUnbindReason; -use tdisp::devicereport::TdiReportStruct; -pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; - -/// Represents a TDISP device assigned to a guest partition. This trait allows -/// the guest to send TDISP commands to the host through the backing interface. -/// [TDISP TODO] Change out `anyhow` for a `TdispError` type. -pub trait ClientDevice: Send + Sync + Inspect { - /// Send a TDISP command to the host through the backing interface. - fn tdisp_command_to_host( - &self, - command: GuestToHostCommand, - ) -> anyhow::Result; - - /// Checks if the device is TDISP capable and returns the device interface info if so. - fn tdisp_get_device_interface_info(&self) -> anyhow::Result; - - /// Bind the device to the current partition and transition to Locked. - fn tdisp_bind_interface(&self) -> anyhow::Result<()>; -} - -pub trait VpciTdispInterface: Send + Sync { - /// Sends a TDISP command to the device through the VPCI channel. - fn send_tdisp_command( - &self, - payload: GuestToHostCommand, - ) -> impl Future> + Send; - - /// Get the TDISP interface info for the device. - fn tdisp_get_device_interface_info( - &self, - ) -> impl Future> + Send; - - /// Bind the device to the current partition and transition to Locked. - /// NOTE: While the device is in the Locked state, it can continue to - /// perform unencrypted operations until it is moved to the Running state. - /// The Locked state is a transitional state that is designed to keep - /// the device from modifying its resources prior to attestation. - fn tdisp_bind_interface(&self) -> impl Future> + Send; - - /// Start a bound device by transitioning it to the from the Locked state to the Run state. - /// This allows for attestation and for resources to be accepted into the guest context. - fn tdisp_start_device(&self) -> impl Future> + Send; - - /// Request a device report from the TDI or physical device depending on the report type. - fn tdisp_get_device_report( - &self, - report_type: &TdispDeviceReportType, - ) -> impl Future>> + Send; - - /// Request a TDI report from the TDI or physical device. - fn tdisp_get_tdi_report(&self) -> impl Future> + Send; - - /// Request the TDI device id from the vpci channel. - fn tdisp_get_tdi_device_id(&self) -> impl Future> + Send; - - /// Request to unbind the device and return to the Unlocked state. - fn tdisp_unbind( - &self, - reason: TdispGuestUnbindReason, - ) -> impl Future> + Send; -} diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 3021b22335..6c5be2de93 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -75,6 +75,7 @@ netvsp.workspace = true nvme_driver.workspace = true nvme_resources.workspace = true openhcl_dma_manager.workspace = true +openhcl_tdisp.workspace = true scsi_core.workspace = true scsidisk.workspace = true scsidisk_resources.workspace = true @@ -82,6 +83,7 @@ serial_16550_resources.workspace = true storage_string.workspace = true storvsp.workspace = true storvsp_resources.workspace = true +tdisp.workspace = true tpm_resources.workspace = true tpm = { workspace = true, features = ["tpm"] } tracelimit.workspace = true diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index a9b7cab7f4..5b6b8b4cca 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -58,10 +58,10 @@ impl From for TdispDeviceReportType { 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoGuestDeviceId), 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInterfaceReport), - TDISP_TDI_REPORT_ENUM_COUNT + 0 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), - TDISP_TDI_REPORT_ENUM_COUNT + 1 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), - TDISP_TDI_REPORT_ENUM_COUNT + 2 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), - TDISP_TDI_REPORT_ENUM_COUNT + 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), + 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), + 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), + 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), + 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), } } diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 1f1a12e1d9..3b5e7c7def 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -32,7 +32,6 @@ pub mod command; pub mod devicereport; pub mod serialize; -use bitfield_struct::bitfield; use std::sync::Arc; use anyhow::Context; @@ -44,7 +43,10 @@ use inspect::Inspect; use parking_lot::Mutex; use thiserror::Error; -use crate::command::{TdispCommandRequestPayload, TdispCommandResponseGetTdiReport}; +use crate::{ + command::{TdispCommandRequestPayload, TdispCommandResponseGetTdiReport}, + devicereport::{TdispDeviceReport, TdispDeviceReportType, TdispTdiReport}, +}; /// Major version of the TDISP guest-to-host interface. pub const TDISP_INTERFACE_VERSION_MAJOR: u32 = 1; From 02f1c8471220ba864b97a51b0c8217d241fe7c0a Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:16:05 -0700 Subject: [PATCH 08/31] cleanup devicereport --- vm/devices/tdisp/src/command.rs | 8 +- vm/devices/tdisp/src/devicereport.rs | 109 ++++++++++++++++++++------- vm/devices/tdisp/src/lib.rs | 6 +- 3 files changed, 93 insertions(+), 30 deletions(-) diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs index 6de522b7d8..e913106ba9 100644 --- a/vm/devices/tdisp/src/command.rs +++ b/vm/devices/tdisp/src/command.rs @@ -143,8 +143,10 @@ pub enum TdispCommandRequestPayload { GetTdiReport(TdispCommandRequestGetTdiReport), } +/// Represents a request to unbind the device back to the Unlocked state. #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct TdispCommandRequestUnbind { + /// The reason for the unbind. See: `TdispGuestUnbindReason` pub unbind_reason: u64, } @@ -167,9 +169,13 @@ pub struct TdispCommandResponseGetTdiReport { pub report_buffer: Vec, } +/// Represents the serialized form of a TdispCommandRequestGetTdiReport. #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct TdispSerializedCommandRequestGetTdiReport { + /// The type of report to request. See: `TdispDeviceReportType`` pub report_type: u32, + + /// The size of the report buffer. pub report_buffer_size: u32, - // Report buffer follows. + // The remainder of the `report_buffer_size` bytes to follow are the bytes of the returned report. } diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 5b6b8b4cca..cc3874469b 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -1,44 +1,57 @@ use bitfield_struct::bitfield; -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; +use zerocopy::{FromBytes, Immutable, KnownLayout}; /// Represents a type of report that can be requested from the TDI (VF). #[derive(Debug)] pub enum TdispTdiReport { - TdiInfoInvalid, - TdiInfoGuestDeviceId, - TdiInfoInterfaceReport, + /// Invalid report type. All usages of this report type should be treated as an error. + Invalid, + + /// Guest requests the guest device ID of the TDI. + GuestDeviceId, + + /// Guest requests the interface report of the TDI. + InterfaceReport, } /// Represents a type of report that can be requested from the physical device. #[derive(Debug)] pub enum TdispDeviceReport { - DeviceInfoInvalid, - DeviceInfoCertificateChain, - DeviceInfoMeasurements, - DeviceInfoIsRegistered, + /// Invalid report type. All usages of this report type should be treated as an error. + Invalid, + + /// Guest requests the certificate chain of the device. + CertificateChain, + + /// Guest requests the measurements of the device. + Measurements, + + /// Guest requests whether the device is registered. + /// [TDISP TODO] Remove this report type? Doesn't seem to serve a purpose. + IsRegistered, } impl From<&TdispTdiReport> for u32 { fn from(value: &TdispTdiReport) -> Self { match value { - TdispTdiReport::TdiInfoInvalid => 0, - TdispTdiReport::TdiInfoGuestDeviceId => 1, - TdispTdiReport::TdiInfoInterfaceReport => 2, + TdispTdiReport::Invalid => 0, + TdispTdiReport::GuestDeviceId => 1, + TdispTdiReport::InterfaceReport => 2, } } } -// Set to the number of enums in TdispTdiReport to assign an ID that is unique for this enum. -// [TDISP TODO] Is there a better way to do this with Rust const types? +/// Set to the number of enums in TdispTdiReport to assign an ID that is unique for this enum. +/// [TDISP TODO] Is there a better way to do this by calculating how many enums there are with Rust const types? pub const TDISP_TDI_REPORT_ENUM_COUNT: u32 = 3; impl From<&TdispDeviceReport> for u32 { fn from(value: &TdispDeviceReport) -> Self { match value { - TdispDeviceReport::DeviceInfoInvalid => TDISP_TDI_REPORT_ENUM_COUNT, - TdispDeviceReport::DeviceInfoCertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, - TdispDeviceReport::DeviceInfoMeasurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, - TdispDeviceReport::DeviceInfoIsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, + TdispDeviceReport::Invalid => TDISP_TDI_REPORT_ENUM_COUNT, + TdispDeviceReport::CertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, + TdispDeviceReport::Measurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, + TdispDeviceReport::IsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, } } } @@ -55,14 +68,14 @@ impl From<&TdispDeviceReportType> for u32 { impl From for TdispDeviceReportType { fn from(value: u32) -> Self { match value { - 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), - 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoGuestDeviceId), - 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInterfaceReport), - 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid), - 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoCertificateChain), - 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoMeasurements), - 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoIsRegistered), - _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), + 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::GuestDeviceId), + 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::InterfaceReport), + 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::Invalid), + 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::CertificateChain), + 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::Measurements), + 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::IsRegistered), + _ => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), } } } @@ -77,44 +90,75 @@ pub enum TdispDeviceReportType { DeviceReport(TdispDeviceReport), } +/// PCI Express Base Specification Revision 6.3 Section 11.3.11 DEVICE_INTERFACE_REPORT #[bitfield(u16)] #[derive(KnownLayout, FromBytes, Immutable)] pub struct TdispTdiReportInterfaceInfo { + /// When 1, indicates that device firmware updates are not permitted + /// while in CONFIG_LOCKED or RUN. When 0, indicates that firmware + /// updates are permitted while in these states pub firmware_update_allowed: bool, + + /// TDI generates DMA requests without PASID pub generate_dma_without_pasid: bool, + + /// TDI generates DMA requests with PASID pub generate_dma_with_pasid: bool, + + /// ATS supported and enabled for the TDI pub ats_support_enabled: bool, + + /// PRS supported and enabled for the TDI pub prs_support_enabled: bool, #[bits(11)] _reserved0: u16, } +/// PCI Express Base Specification Revision 6.3 Section 11.3.11 DEVICE_INTERFACE_REPORT #[bitfield(u16)] #[derive(KnownLayout, FromBytes, Immutable)] pub struct TdispTdiReportMmioFlags { + /// MSI-X Table – if the range maps MSI-X table. This must be reported only if locked by the LOCK_INTERFACE_REQUEST. pub range_maps_msix_table: bool, + + /// MSI-X PBA – if the range maps MSI-X PBA. This must be reported only if locked by the LOCK_INTERFACE_REQUEST. pub range_maps_msix_pba: bool, + + /// IS_NON_TEE_MEM – must be 1b if the range is non-TEE memory. + /// For attribute updatable ranges (see below), this field must indicate attribute of the range when the TDI was locked. pub is_non_tee_mem: bool, + + /// IS_MEM_ATTR_UPDATABLE – must be 1b if the attributes of this range is updatable using SET_MMIO_ATTRIBUTE_REQUEST pub is_mem_attr_updatable: bool, #[bits(12)] _reserved0: u16, } +/// PCI Express Base Specification Revision 6.3 Section 11.3.11 DEVICE_INTERFACE_REPORT #[derive(KnownLayout, FromBytes, Immutable, Clone, Debug)] pub struct TdispTdiReportMmioInterfaceInfo { + /// First 4K page with offset added pub first_4k_page_offset: u64, + + /// Number of 4K pages in this range pub num_4k_pages: u32, + + /// Range Attributes pub flags: TdispTdiReportMmioFlags, + + /// Range ID – a device specific identifier for the specified range. + /// The range ID may be used to logically group one or more MMIO ranges into a larger range. pub range_id: u16, } static_assertions::const_assert_eq!(size_of::(), 0x10); +/// PCI Express Base Specification Revision 6.3 Section 11.3.11 DEVICE_INTERFACE_REPORT #[derive(KnownLayout, FromBytes, Immutable, Debug)] #[repr(C)] struct TdiReportStructSerialized { pub interface_info: TdispTdiReportInterfaceInfo, - pub _reserved0: u16, + _reserved0: u16, pub msi_x_message_control: u16, pub lnr_control: u16, pub tph_control: u32, @@ -128,10 +172,23 @@ static_assertions::const_assert_eq!(size_of::(), 0x10 /// The deserialized form of a TDI interface report. #[derive(Debug)] pub struct TdiReportStruct { + /// See: `TdispTdiReportInterfaceInfo` pub interface_info: TdispTdiReportInterfaceInfo, + + /// MSI-X capability message control register state. Must be Clear if + /// a) capability is not supported or b) MSI-X table is not locked pub msi_x_message_control: u16, + + /// LNR control register from LN Requester Extended Capability. + /// Must be Clear if LNR capability is not supported. LN is deprecated in PCIe Revision 6.0. pub lnr_control: u16, + + /// TPH Requester Control Register from the TPH Requester Extended Capability. + /// Must be Clear if a) TPH capability is not support or b) MSI-X table is not locked pub tph_control: u32, + + /// Each MMIO Range of the TDI is reported with the MMIO reporting offset added. + /// Base and size in units of 4K pages pub mmio_interface_info: Vec, } diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 3b5e7c7def..d153738713 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -190,7 +190,7 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { TdispCommandRequestPayload::GetTdiReport(payload) => { TdispDeviceReportType::from(payload.report_type) } - _ => TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid), + _ => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), }; let report_buffer = self.machine.request_attestation_report(&report_type); @@ -680,11 +680,11 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { } match report_type { - TdispDeviceReportType::TdiReport(TdispTdiReport::TdiInfoInvalid) => { + TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid) => { self.error_print("Invalid report type TdispTdiReport::TdiInfoInvalid requested"); return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); } - TdispDeviceReportType::DeviceReport(TdispDeviceReport::DeviceInfoInvalid) => { + TdispDeviceReportType::DeviceReport(TdispDeviceReport::Invalid) => { self.error_print( "Invalid report type TdispDeviceReport::DeviceInfoInvalid requested", ); From a53f53264499000460c9ca7fa7f43211aa819da6 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:17:02 -0700 Subject: [PATCH 09/31] cleanup lib.rs --- vm/devices/tdisp/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index d153738713..09769b997e 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -29,9 +29,15 @@ //! It is the responsibility of the host to provide a `TdispHostDeviceInterface` //! implementation that performs the necessary platform actions. +/// Commands and responses for the TDISP guest-to-host interface. pub mod command; + +/// Retrieval and parsing of device reports. pub mod devicereport; + +/// Serialization of guest commands and responses. pub mod serialize; + use std::sync::Arc; use anyhow::Context; @@ -87,6 +93,7 @@ pub trait TdispHostDeviceInterface: Send + Sync { /// Trait added to host virtual devices to dispatch TDISP commands from guests. pub trait TdispHostDeviceTarget: Send + Sync { + /// Dispatch a TDISP command from a guest. fn tdisp_handle_guest_command( &mut self, _command: GuestToHostCommand, From e017631a99ffc228656333c6ea10cee723713162 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:18:30 -0700 Subject: [PATCH 10/31] cleanup serialize.rs --- vm/devices/tdisp/src/serialize.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 09ce4e4dc4..65355734f4 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -5,24 +5,33 @@ use crate::command::{ TdispCommandResponseGetTdiReport, TdispSerializedCommandRequestGetTdiReport, }; use crate::{ - GuestToHostCommand, GuestToHostResponse, TdispCommandResponsePayload, TdispDeviceReport, - TdispDeviceReportType, TdispGuestOperationError, TdispTdiReport, + GuestToHostCommand, GuestToHostResponse, TdispCommandResponsePayload, TdispGuestOperationError, }; use crate::{TdispCommandId, TdispDeviceInterfaceInfo}; /// Serialized form of the header for a GuestToHostCommand packet #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct GuestToHostCommandSerializedHeader { + /// The logical TDISP device ID of the device that the command is being sent to. pub device_id: u64, + + /// The command ID of the command that is being sent. See: `TdispCommandId` pub command_id: u64, } /// Serialized form of the header for a GuestToHostResponse packet #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct GuestToHostResponseSerializedHeader { + /// The command ID of the command that was processed. See: `TdispCommandId` pub command_id: u64, + + /// The result of the command. See: `TdispGuestOperationError` pub result: u64, + + /// The TDI state before the command was processed. See: `TdispTdiState` pub tdi_state_before: u64, + + /// The TDI state after the command was processed. See: `TdispTdiState` pub tdi_state_after: u64, } @@ -68,8 +77,13 @@ impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { } } } + +/// Trait implemented by the guest-to-host command and response structs to allow serialization and deserialization. pub trait SerializePacket: Sized { + /// Serialize the struct to a byte vector. fn serialize_to_bytes(self) -> Vec; + + /// Deserialize a byte slice into a struct. fn deserialize_from_bytes(bytes: &[u8]) -> Result; } From 04947931e5f2c509596437bdc9a4cbfb1abd7002 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:19:23 -0700 Subject: [PATCH 11/31] cleanup openhcl_tdisp lib.rs --- openhcl/openhcl_tdisp/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 177e5b31d4..99780fe638 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -20,7 +20,7 @@ pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; /// Represents a TDISP device assigned to a guest partition. This trait allows /// the guest to send TDISP commands to the host through the backing interface. -/// [TDISP TODO] Change out `anyhow` for a `TdispError` type. +/// [TDISP TODO] Change out `anyhow` for a `TdispError` type? pub trait ClientDevice: Send + Sync + Inspect { /// Send a TDISP command to the host through the backing interface. fn tdisp_command_to_host( @@ -35,6 +35,8 @@ pub trait ClientDevice: Send + Sync + Inspect { fn tdisp_bind_interface(&self) -> anyhow::Result<()>; } +/// Represents a TDISP device assigned to a guest partition that can be used to +/// send TDISP commands to the host through a backing interface. pub trait VpciTdispInterface: Send + Sync { /// Sends a TDISP command to the device through the VPCI channel. fn send_tdisp_command( From 5c69ec91e67b4428970ab24d4aa1e3cddf2945d9 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:41:22 -0700 Subject: [PATCH 12/31] cleanup tracing --- openhcl/openhcl_tdisp/src/lib.rs | 2 +- vm/devices/tdisp/src/lib.rs | 2 +- vm/devices/tdisp/src/serialize.rs | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 99780fe638..fe9ece64c9 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -3,7 +3,7 @@ //! //! This module provides resources and traits for a TDISP client device -//! interface for assigned deviecs in OpenHCL. +//! interface for assigned devices in OpenHCL. //! //! See: `vm/tdisp` for more information. //! See: `openhcl_tdisp` for more information. diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 09769b997e..7dd8c5af48 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -397,7 +397,7 @@ impl TdispHostStateMachine { /// Print a debug message to the log. fn debug_print(&self, msg: &str) { - tracing::error!(msg = format!("[TdispEmu] [{}] {}", self.debug_device_id, msg)); + tracing::debug!(msg = format!("[TdispEmu] [{}] {}", self.debug_device_id, msg)); } /// Print an error message to the log. diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 65355734f4..85e64ef878 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -91,6 +91,8 @@ impl SerializePacket for GuestToHostCommand { fn serialize_to_bytes(self) -> Vec { let header = GuestToHostCommandSerializedHeader::from(&self); let bytes = header.as_bytes(); + tracing::debug!(format!("serialize_to_bytes: header={:?}", header)); + tracing::debug!(format!("serialize_to_bytes: {:?}", bytes)); let mut bytes = bytes.to_vec(); match self.payload { @@ -106,11 +108,16 @@ impl SerializePacket for GuestToHostCommand { fn deserialize_from_bytes(bytes: &[u8]) -> Result { let header_length = size_of::(); - tracing::error!(msg = format!("deserialize_from_bytes: header_length={header_length}")); - tracing::error!(msg = format!("deserialize_from_bytes: {:?}", bytes)); + tracing::debug!(format!( + "deserialize_from_bytes: header_length={header_length}" + )); + tracing::debug!(format!("deserialize_from_bytes: {:?}", bytes)); let header_bytes = &bytes[0..header_length]; - tracing::error!(msg = format!("deserialize_from_bytes: header_bytes={:?}", header_bytes)); + tracing::debug!(format!( + "deserialize_from_bytes: header_bytes={:?}", + header_bytes + )); let header = GuestToHostCommandSerializedHeader::try_ref_from_bytes(header_bytes).map_err(|e| { From 55c9d67b81d99426f8eb1232391c30eb1b961fb9 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:43:01 -0700 Subject: [PATCH 13/31] remove panic in favor of error --- vm/devices/tdisp/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 7dd8c5af48..b4483de01f 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -476,10 +476,10 @@ impl TdispHostStateMachine { // All states can be reset to the Unlocked state. This can only happen if the // state is corrupt beyond the state machine. if let Err(reason) = self.transition_state_to(TdispTdiState::Unlocked) { - panic!( - "[{}] Impossible state machine violation during TDISP Unbind: {:?}", - self.debug_device_id, reason - ); + return Err(anyhow::anyhow!( + "Impossible state machine violation during TDISP Unbind: {:?}", + reason + )); } // Call back into the host to bind the device. From 8a09c393e3cc923d5afe209a3ab7fc02c35fdf0a Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:44:19 -0700 Subject: [PATCH 14/31] remove panic in favor of unknown state for TdispGuestOperationError --- vm/devices/tdisp/src/lib.rs | 38 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index b4483de01f..c57ce6eb88 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -508,6 +508,8 @@ impl TdispHostStateMachine { #[derive(Error, Debug, Copy, Clone)] #[expect(missing_docs)] pub enum TdispGuestOperationError { + #[error("unknown error code")] + Unknown, #[error("the operation was successful")] Success, #[error("the current TDI state is incorrect for this operation")] @@ -531,14 +533,15 @@ pub enum TdispGuestOperationError { impl From for u64 { fn from(err: TdispGuestOperationError) -> Self { match err { - TdispGuestOperationError::Success => 0, - TdispGuestOperationError::InvalidDeviceState => 1, - TdispGuestOperationError::InvalidGuestUnbindReason => 2, - TdispGuestOperationError::InvalidGuestCommandId => 3, - TdispGuestOperationError::NotImplemented => 4, - TdispGuestOperationError::HostFailedToProcessCommand => 5, - TdispGuestOperationError::InvalidGuestAttestationReportState => 6, - TdispGuestOperationError::InvalidGuestAttestationReportType => 7, + TdispGuestOperationError::Unknown => 0, + TdispGuestOperationError::Success => 1, + TdispGuestOperationError::InvalidDeviceState => 2, + TdispGuestOperationError::InvalidGuestUnbindReason => 3, + TdispGuestOperationError::InvalidGuestCommandId => 4, + TdispGuestOperationError::NotImplemented => 5, + TdispGuestOperationError::HostFailedToProcessCommand => 6, + TdispGuestOperationError::InvalidGuestAttestationReportState => 7, + TdispGuestOperationError::InvalidGuestAttestationReportType => 8, } } } @@ -546,15 +549,16 @@ impl From for u64 { impl From for TdispGuestOperationError { fn from(err: u64) -> Self { match err { - 0 => TdispGuestOperationError::Success, - 1 => TdispGuestOperationError::InvalidDeviceState, - 2 => TdispGuestOperationError::InvalidGuestUnbindReason, - 3 => TdispGuestOperationError::InvalidGuestCommandId, - 4 => TdispGuestOperationError::NotImplemented, - 5 => TdispGuestOperationError::HostFailedToProcessCommand, - 6 => TdispGuestOperationError::InvalidGuestAttestationReportState, - 7 => TdispGuestOperationError::InvalidGuestAttestationReportType, - _ => panic!("invalid TdispGuestOperationError code: {err}"), + 0 => TdispGuestOperationError::Unknown, + 1 => TdispGuestOperationError::Success, + 2 => TdispGuestOperationError::InvalidDeviceState, + 3 => TdispGuestOperationError::InvalidGuestUnbindReason, + 4 => TdispGuestOperationError::InvalidGuestCommandId, + 5 => TdispGuestOperationError::NotImplemented, + 6 => TdispGuestOperationError::HostFailedToProcessCommand, + 7 => TdispGuestOperationError::InvalidGuestAttestationReportState, + 8 => TdispGuestOperationError::InvalidGuestAttestationReportType, + _ => TdispGuestOperationError::Unknown, } } } From 6df2e7db3011b1eccd8d3dcd8bf9ac9fb18bc90f Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 13:50:50 -0700 Subject: [PATCH 15/31] fix trace --- vm/devices/tdisp/src/serialize.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 85e64ef878..2b8d4f26aa 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -91,8 +91,8 @@ impl SerializePacket for GuestToHostCommand { fn serialize_to_bytes(self) -> Vec { let header = GuestToHostCommandSerializedHeader::from(&self); let bytes = header.as_bytes(); - tracing::debug!(format!("serialize_to_bytes: header={:?}", header)); - tracing::debug!(format!("serialize_to_bytes: {:?}", bytes)); + tracing::debug!(msg = format!("serialize_to_bytes: header={:?}", header)); + tracing::debug!(msg = format!("serialize_to_bytes: {:?}", bytes)); let mut bytes = bytes.to_vec(); match self.payload { @@ -108,16 +108,11 @@ impl SerializePacket for GuestToHostCommand { fn deserialize_from_bytes(bytes: &[u8]) -> Result { let header_length = size_of::(); - tracing::debug!(format!( - "deserialize_from_bytes: header_length={header_length}" - )); - tracing::debug!(format!("deserialize_from_bytes: {:?}", bytes)); + tracing::debug!(msg = format!("deserialize_from_bytes: header_length={header_length}")); + tracing::debug!(msg = format!("deserialize_from_bytes: {:?}", bytes)); let header_bytes = &bytes[0..header_length]; - tracing::debug!(format!( - "deserialize_from_bytes: header_bytes={:?}", - header_bytes - )); + tracing::debug!(msg = format!("deserialize_from_bytes: header_bytes={:?}", header_bytes)); let header = GuestToHostCommandSerializedHeader::try_ref_from_bytes(header_bytes).map_err(|e| { From ece4b270178243ab7da99aa15f8e3b0921602feb Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:15:02 -0700 Subject: [PATCH 16/31] add license headers --- vm/devices/tdisp/src/devicereport.rs | 3 +++ vm/devices/tdisp/src/serialize.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index cc3874469b..92f39da9a8 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use bitfield_struct::bitfield; use zerocopy::{FromBytes, Immutable, KnownLayout}; diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 2b8d4f26aa..1d376a6cbc 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, TryFromBytes}; use crate::command::{ From 5208e084fc6c7317e997b19ee7546317b5b16ba2 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:19:55 -0700 Subject: [PATCH 17/31] ensure openhcl_tdisp builds in underhill core --- Cargo.lock | 1 - openhcl/underhill_core/Cargo.toml | 1 - openhcl/underhill_core/src/lib.rs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8be7df8f2..74083a25c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7886,7 +7886,6 @@ dependencies = [ "storvsp", "storvsp_resources", "string_page_buf", - "tdisp", "tee_call", "test_with_tracing", "thiserror 2.0.16", diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 6c5be2de93..1e65a09c68 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -83,7 +83,6 @@ serial_16550_resources.workspace = true storage_string.workspace = true storvsp.workspace = true storvsp_resources.workspace = true -tdisp.workspace = true tpm_resources.workspace = true tpm = { workspace = true, features = ["tpm"] } tracelimit.workspace = true diff --git a/openhcl/underhill_core/src/lib.rs b/openhcl/underhill_core/src/lib.rs index 280748b40c..23950e5824 100644 --- a/openhcl/underhill_core/src/lib.rs +++ b/openhcl/underhill_core/src/lib.rs @@ -27,7 +27,6 @@ mod vp; mod vpci; mod worker; mod wrapped_partition; - // `pub` so that the missing_docs warning fires for options without // documentation. pub use options::Options; @@ -67,6 +66,7 @@ use mesh_worker::WorkerHost; use mesh_worker::WorkerHostRunner; use mesh_worker::launch_local_worker; use mesh_worker::register_workers; +use openhcl_tdisp as _; use pal_async::DefaultDriver; use pal_async::DefaultPool; use pal_async::task::Spawn; From c940ce4bac80e2fd1b72fd5430ac4af97f96beb0 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:26:49 -0700 Subject: [PATCH 18/31] cr: sort pub use, remove bad doc --- openhcl/openhcl_tdisp/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index fe9ece64c9..eafe175735 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -6,17 +6,17 @@ //! interface for assigned devices in OpenHCL. //! //! See: `vm/tdisp` for more information. -//! See: `openhcl_tdisp` for more information. + +pub use tdisp::TdispCommandId; +pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; use inspect::Inspect; use std::future::Future; use tdisp::GuestToHostCommand; use tdisp::GuestToHostResponse; -pub use tdisp::TdispCommandId; use tdisp::TdispGuestUnbindReason; use tdisp::devicereport::TdiReportStruct; use tdisp::devicereport::TdispDeviceReportType; -pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; /// Represents a TDISP device assigned to a guest partition. This trait allows /// the guest to send TDISP commands to the host through the backing interface. From bbf7b3a6d7ee8aa9a2204eb5e8b0add41aa6d5e7 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:30:04 -0700 Subject: [PATCH 19/31] cr: split combined use statements and sort pub use --- openhcl/openhcl_tdisp/src/lib.rs | 3 ++- vm/devices/tdisp/src/devicereport.rs | 4 +++- vm/devices/tdisp/src/lib.rs | 20 +++++++++++--------- vm/devices/tdisp/src/serialize.rs | 20 ++++++++++++-------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index eafe175735..3e1c53bad9 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -7,8 +7,9 @@ //! //! See: `vm/tdisp` for more information. +pub use tdisp::TDISP_INTERFACE_VERSION_MAJOR; +pub use tdisp::TDISP_INTERFACE_VERSION_MINOR; pub use tdisp::TdispCommandId; -pub use tdisp::{TDISP_INTERFACE_VERSION_MAJOR, TDISP_INTERFACE_VERSION_MINOR}; use inspect::Inspect; use std::future::Future; diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 92f39da9a8..90b680f625 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -2,7 +2,9 @@ // Licensed under the MIT License. use bitfield_struct::bitfield; -use zerocopy::{FromBytes, Immutable, KnownLayout}; +use zerocopy::FromBytes; +use zerocopy::Immutable; +use zerocopy::KnownLayout; /// Represents a type of report that can be requested from the TDI (VF). #[derive(Debug)] diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index c57ce6eb88..84964c5300 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -38,21 +38,23 @@ pub mod devicereport; /// Serialization of guest commands and responses. pub mod serialize; -use std::sync::Arc; +pub use command::GuestToHostCommand; +pub use command::GuestToHostResponse; +pub use command::TdispCommandId; +pub use command::TdispCommandResponsePayload; +pub use command::TdispDeviceInterfaceInfo; use anyhow::Context; -pub use command::{ - GuestToHostCommand, GuestToHostResponse, TdispCommandId, TdispCommandResponsePayload, - TdispDeviceInterfaceInfo, -}; use inspect::Inspect; use parking_lot::Mutex; +use std::sync::Arc; use thiserror::Error; -use crate::{ - command::{TdispCommandRequestPayload, TdispCommandResponseGetTdiReport}, - devicereport::{TdispDeviceReport, TdispDeviceReportType, TdispTdiReport}, -}; +use crate::command::TdispCommandRequestPayload; +use crate::command::TdispCommandResponseGetTdiReport; +use crate::devicereport::TdispDeviceReport; +use crate::devicereport::TdispDeviceReportType; +use crate::devicereport::TdispTdiReport; /// Major version of the TDISP guest-to-host interface. pub const TDISP_INTERFACE_VERSION_MAJOR: u32 = 1; diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 1d376a6cbc..bff3daa4a9 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -3,14 +3,18 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, TryFromBytes}; -use crate::command::{ - TdispCommandRequestGetTdiReport, TdispCommandRequestPayload, TdispCommandRequestUnbind, - TdispCommandResponseGetTdiReport, TdispSerializedCommandRequestGetTdiReport, -}; -use crate::{ - GuestToHostCommand, GuestToHostResponse, TdispCommandResponsePayload, TdispGuestOperationError, -}; -use crate::{TdispCommandId, TdispDeviceInterfaceInfo}; +use crate::command::TdispCommandRequestGetTdiReport; +use crate::command::TdispCommandRequestPayload; +use crate::command::TdispCommandRequestUnbind; +use crate::command::TdispCommandResponseGetTdiReport; +use crate::command::TdispSerializedCommandRequestGetTdiReport; + +use crate::GuestToHostCommand; +use crate::GuestToHostResponse; +use crate::TdispCommandId; +use crate::TdispCommandResponsePayload; +use crate::TdispDeviceInterfaceInfo; +use crate::TdispGuestOperationError; /// Serialized form of the header for a GuestToHostCommand packet #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] From 064cca78c7c627d1fb8e42bdb135cbd6eb296a2b Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:30:27 -0700 Subject: [PATCH 20/31] cr: typo --- openhcl/openhcl_tdisp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 3e1c53bad9..9dcf30256b 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -57,7 +57,7 @@ pub trait VpciTdispInterface: Send + Sync { /// the device from modifying its resources prior to attestation. fn tdisp_bind_interface(&self) -> impl Future> + Send; - /// Start a bound device by transitioning it to the from the Locked state to the Run state. + /// Start a bound device by transitioning it from the Locked state to the Run state. /// This allows for attestation and for resources to be accepted into the guest context. fn tdisp_start_device(&self) -> impl Future> + Send; From 63a700200ae8429611f24fc6d0e6ec63064deebe Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:36:51 -0700 Subject: [PATCH 21/31] cr: remove accidential Debug implementation --- vm/devices/tdisp/src/command.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs index e913106ba9..b3429051a8 100644 --- a/vm/devices/tdisp/src/command.rs +++ b/vm/devices/tdisp/src/command.rs @@ -3,7 +3,6 @@ use crate::TdispGuestOperationError; use crate::TdispTdiState; -use std::fmt::Display; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::IntoBytes; @@ -35,15 +34,6 @@ pub struct GuestToHostResponse { pub payload: TdispCommandResponsePayload, } -impl Display for GuestToHostCommand { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // Display the Debug representation of the command. - f.debug_struct("GuestToHostCommand") - .field("command_id", &self.command_id) - .finish() - } -} - /// Represents a TDISP command sent from the guest to the host. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TdispCommandId { From 616ca28c8c3b4822c9cdaa4931e94231478f6f88 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 14:54:13 -0700 Subject: [PATCH 22/31] cr: refactor TdispCommandId to open_enum --- Cargo.lock | 1 + vm/devices/tdisp/Cargo.toml | 11 +++--- vm/devices/tdisp/src/command.rs | 58 +++++++++---------------------- vm/devices/tdisp/src/lib.rs | 15 ++++---- vm/devices/tdisp/src/serialize.rs | 47 ++++++++++++++++--------- 5 files changed, 63 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74083a25c0..dd55b1ac3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7089,6 +7089,7 @@ dependencies = [ "anyhow", "bitfield-struct 0.11.0", "inspect", + "open_enum", "parking_lot", "static_assertions", "thiserror 2.0.16", diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index 1fe67eacff..0eccb97ea7 100644 --- a/vm/devices/tdisp/Cargo.toml +++ b/vm/devices/tdisp/Cargo.toml @@ -7,14 +7,15 @@ edition.workspace = true rust-version.workspace = true [dependencies] -inspect.workspace = true anyhow.workspace = true -tracing.workspace = true -thiserror.workspace = true -zerocopy.workspace = true -parking_lot.workspace = true bitfield-struct.workspace = true +inspect.workspace = true +open_enum.workspace = true +parking_lot.workspace = true static_assertions.workspace = true +thiserror.workspace = true +tracing.workspace = true +zerocopy.workspace = true [lints] workspace = true diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs index b3429051a8..54444f65ca 100644 --- a/vm/devices/tdisp/src/command.rs +++ b/vm/devices/tdisp/src/command.rs @@ -3,6 +3,7 @@ use crate::TdispGuestOperationError; use crate::TdispTdiState; +use open_enum::open_enum; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::IntoBytes; @@ -34,52 +35,27 @@ pub struct GuestToHostResponse { pub payload: TdispCommandResponsePayload, } -/// Represents a TDISP command sent from the guest to the host. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum TdispCommandId { - /// Invalid command id. - Unknown, - - /// Request the device's TDISP interface information. - GetDeviceInterfaceInfo, - - /// Bind the device to the current partition and transition to Locked. - Bind, +open_enum! { + /// Represents the command type for a packet sent from the guest to the host or + /// the response from the host to the guest. + pub enum TdispCommandId: u64 { + /// Invalid command id. + UNKNOWN = 0, - /// Get the TDI report for attestation from the host for the device. - GetTdiReport, + /// Request the device's TDISP interface information. + GET_DEVICE_INTERFACE_INFO = 1, - /// Transition the device to the Start state after successful attestation. - StartTdi, + /// Bind the device to the current partition and transition to Locked. + BIND = 2, - /// Unbind the device from the partition, reverting it back to the Unlocked state. - Unbind, -} + /// Get the TDI report for attestation from the host for the device. + GET_TDI_REPORT = 3, -impl From for u64 { - fn from(value: TdispCommandId) -> Self { - match value { - TdispCommandId::Unknown => 0, - TdispCommandId::GetDeviceInterfaceInfo => 1, - TdispCommandId::Bind => 2, - TdispCommandId::GetTdiReport => 3, - TdispCommandId::StartTdi => 4, - TdispCommandId::Unbind => 5, - } - } -} + /// Transition the device to the Start state after successful attestation. + START_TDI = 4, -impl From for TdispCommandId { - fn from(value: u64) -> Self { - match value { - 0 => TdispCommandId::Unknown, - 1 => TdispCommandId::GetDeviceInterfaceInfo, - 2 => TdispCommandId::Bind, - 3 => TdispCommandId::GetTdiReport, - 4 => TdispCommandId::StartTdi, - 5 => TdispCommandId::Unbind, - _ => TdispCommandId::Unknown, - } + /// Unbind the device from the partition, reverting it back to the Unlocked state. + UNBIND = 5, } } diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 84964c5300..edc5b194bc 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -164,11 +164,11 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { let mut payload = TdispCommandResponsePayload::None; let state_before = self.machine.state(); match command.command_id { - TdispCommandId::GetDeviceInterfaceInfo => { + TdispCommandId::GET_DEVICE_INTERFACE_INFO => { let interface_info = self.get_device_interface_info(); payload = TdispCommandResponsePayload::GetDeviceInterfaceInfo(interface_info); } - TdispCommandId::Bind => { + TdispCommandId::BIND => { let bind_res = self.machine.request_lock_device_resources(); if let Err(err) = bind_res { error = err; @@ -176,7 +176,7 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { payload = TdispCommandResponsePayload::None; } } - TdispCommandId::StartTdi => { + TdispCommandId::START_TDI => { let start_tdi_res = self.machine.request_start_tdi(); if let Err(err) = start_tdi_res { error = err; @@ -184,7 +184,7 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { payload = TdispCommandResponsePayload::None; } } - TdispCommandId::Unbind => { + TdispCommandId::UNBIND => { let unbind_reason: TdispGuestUnbindReason = match command.payload { TdispCommandRequestPayload::Unbind(payload) => payload.unbind_reason.into(), _ => TdispGuestUnbindReason::Unknown, @@ -194,7 +194,7 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { error = err; } } - TdispCommandId::GetTdiReport => { + TdispCommandId::GET_TDI_REPORT => { let report_type = match &command.payload { TdispCommandRequestPayload::GetTdiReport(payload) => { TdispDeviceReportType::from(payload.report_type) @@ -214,7 +214,10 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { ); } } - TdispCommandId::Unknown => { + TdispCommandId::UNKNOWN => { + error = TdispGuestOperationError::InvalidGuestCommandId; + } + _ => { error = TdispGuestOperationError::InvalidGuestCommandId; } } diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index bff3daa4a9..42e630cc7a 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -47,7 +47,7 @@ impl From<&GuestToHostCommand> for GuestToHostCommandSerializedHeader { fn from(value: &GuestToHostCommand) -> Self { GuestToHostCommandSerializedHeader { device_id: value.device_id, - command_id: value.command_id.into(), + command_id: value.command_id.0, } } } @@ -55,7 +55,7 @@ impl From<&GuestToHostCommand> for GuestToHostCommandSerializedHeader { impl From<&GuestToHostResponse> for GuestToHostResponseSerializedHeader { fn from(value: &GuestToHostResponse) -> Self { GuestToHostResponseSerializedHeader { - command_id: value.command_id.into(), + command_id: value.command_id.0, result: value.result.into(), tdi_state_before: value.tdi_state_before.into(), tdi_state_after: value.tdi_state_after.into(), @@ -67,7 +67,7 @@ impl From<&GuestToHostCommandSerializedHeader> for GuestToHostCommand { fn from(value: &GuestToHostCommandSerializedHeader) -> Self { GuestToHostCommand { device_id: value.device_id, - command_id: value.command_id.into(), + command_id: TdispCommandId(value.command_id), payload: TdispCommandRequestPayload::None, } } @@ -76,7 +76,7 @@ impl From<&GuestToHostCommandSerializedHeader> for GuestToHostCommand { impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { fn from(value: &GuestToHostResponseSerializedHeader) -> Self { GuestToHostResponse { - command_id: value.command_id.into(), + command_id: TdispCommandId(value.command_id), result: value.result.into(), tdi_state_before: value.tdi_state_before.into(), tdi_state_after: value.tdi_state_after.into(), @@ -129,16 +129,17 @@ impl SerializePacket for GuestToHostCommand { let payload_slice = &bytes[header_length..]; let mut packet: Self = header.into(); + let payload = match packet.command_id { - TdispCommandId::Unbind => TdispCommandRequestPayload::Unbind( + TdispCommandId::UNBIND => TdispCommandRequestPayload::Unbind( TdispCommandRequestUnbind::try_read_from_bytes(payload_slice).map_err(|e| { anyhow::anyhow!("failed to deserialize TdispCommandRequestUnbind: {:?}", e) })?, ), - TdispCommandId::Bind => TdispCommandRequestPayload::None, - TdispCommandId::GetDeviceInterfaceInfo => TdispCommandRequestPayload::None, - TdispCommandId::StartTdi => TdispCommandRequestPayload::None, - TdispCommandId::GetTdiReport => TdispCommandRequestPayload::GetTdiReport( + TdispCommandId::BIND => TdispCommandRequestPayload::None, + TdispCommandId::GET_DEVICE_INTERFACE_INFO => TdispCommandRequestPayload::None, + TdispCommandId::START_TDI => TdispCommandRequestPayload::None, + TdispCommandId::GET_TDI_REPORT => TdispCommandRequestPayload::GetTdiReport( TdispCommandRequestGetTdiReport::try_read_from_bytes(payload_slice).map_err( |e| { anyhow::anyhow!( @@ -148,7 +149,13 @@ impl SerializePacket for GuestToHostCommand { }, )?, ), - TdispCommandId::Unknown => { + TdispCommandId::UNKNOWN => { + return Err(anyhow::anyhow!( + "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", + header.command_id + )); + } + _ => { return Err(anyhow::anyhow!( "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", header.command_id @@ -209,17 +216,17 @@ impl SerializePacket for GuestToHostResponse { let payload_slice = &bytes[header_length..]; let payload = match packet.command_id { - TdispCommandId::GetDeviceInterfaceInfo => { + TdispCommandId::GET_DEVICE_INTERFACE_INFO => { TdispCommandResponsePayload::GetDeviceInterfaceInfo( TdispDeviceInterfaceInfo::try_read_from_bytes(payload_slice).map_err(|e| { anyhow::anyhow!("failed to deserialize TdispDeviceInterfaceInfo: {:?}", e) })?, ) } - TdispCommandId::Bind => TdispCommandResponsePayload::None, - TdispCommandId::Unbind => TdispCommandResponsePayload::None, - TdispCommandId::StartTdi => TdispCommandResponsePayload::None, - TdispCommandId::GetTdiReport => { + TdispCommandId::BIND => TdispCommandResponsePayload::None, + TdispCommandId::UNBIND => TdispCommandResponsePayload::None, + TdispCommandId::START_TDI => TdispCommandResponsePayload::None, + TdispCommandId::GET_TDI_REPORT => { // Peel off the header from the payload let payload_header_len = size_of::(); let payload_header_slice = &payload_slice[0..payload_header_len]; @@ -246,9 +253,15 @@ impl SerializePacket for GuestToHostResponse { report_buffer: payload_bytes.to_vec(), }) } - TdispCommandId::Unknown => { + TdispCommandId::UNKNOWN => { + return Err(anyhow::anyhow!( + "invalid command id in GuestToHostResponse: {:?}", + header.result + )); + } + _ => { return Err(anyhow::anyhow!( - "invalid payload type in GuestToHostResponse: {:?}", + "invalid command id in GuestToHostResponse: {:?}", header.result )); } From 735fb95f5fcaaab4282bd5ea503cccfb8769c126 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:01:01 -0700 Subject: [PATCH 23/31] only try to deserialize a payload if there is one supplied in the packet --- vm/devices/tdisp/src/serialize.rs | 179 ++++++++++++++++-------------- 1 file changed, 94 insertions(+), 85 deletions(-) diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 42e630cc7a..8033c6bc23 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -130,40 +130,42 @@ impl SerializePacket for GuestToHostCommand { let mut packet: Self = header.into(); - let payload = match packet.command_id { - TdispCommandId::UNBIND => TdispCommandRequestPayload::Unbind( - TdispCommandRequestUnbind::try_read_from_bytes(payload_slice).map_err(|e| { - anyhow::anyhow!("failed to deserialize TdispCommandRequestUnbind: {:?}", e) - })?, - ), - TdispCommandId::BIND => TdispCommandRequestPayload::None, - TdispCommandId::GET_DEVICE_INTERFACE_INFO => TdispCommandRequestPayload::None, - TdispCommandId::START_TDI => TdispCommandRequestPayload::None, - TdispCommandId::GET_TDI_REPORT => TdispCommandRequestPayload::GetTdiReport( - TdispCommandRequestGetTdiReport::try_read_from_bytes(payload_slice).map_err( - |e| { - anyhow::anyhow!( - "failed to deserialize TdispCommandRequestGetTdiReport: {:?}", - e - ) - }, - )?, - ), - TdispCommandId::UNKNOWN => { - return Err(anyhow::anyhow!( - "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", - header.command_id - )); - } - _ => { - return Err(anyhow::anyhow!( - "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", - header.command_id - )); - } - }; - - packet.payload = payload; + if !payload_slice.is_empty() { + let payload = match packet.command_id { + TdispCommandId::UNBIND => TdispCommandRequestPayload::Unbind( + TdispCommandRequestUnbind::try_read_from_bytes(payload_slice).map_err(|e| { + anyhow::anyhow!("failed to deserialize TdispCommandRequestUnbind: {:?}", e) + })?, + ), + TdispCommandId::BIND => TdispCommandRequestPayload::None, + TdispCommandId::GET_DEVICE_INTERFACE_INFO => TdispCommandRequestPayload::None, + TdispCommandId::START_TDI => TdispCommandRequestPayload::None, + TdispCommandId::GET_TDI_REPORT => TdispCommandRequestPayload::GetTdiReport( + TdispCommandRequestGetTdiReport::try_read_from_bytes(payload_slice).map_err( + |e| { + anyhow::anyhow!( + "failed to deserialize TdispCommandRequestGetTdiReport: {:?}", + e + ) + }, + )?, + ), + TdispCommandId::UNKNOWN => { + return Err(anyhow::anyhow!( + "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", + header.command_id + )); + } + _ => { + return Err(anyhow::anyhow!( + "Unknown payload type for command id {:?} while deserializing GuestToHostCommand", + header.command_id + )); + } + }; + + packet.payload = payload; + } Ok(packet) } @@ -215,59 +217,66 @@ impl SerializePacket for GuestToHostResponse { let payload_slice = &bytes[header_length..]; - let payload = match packet.command_id { - TdispCommandId::GET_DEVICE_INTERFACE_INFO => { - TdispCommandResponsePayload::GetDeviceInterfaceInfo( - TdispDeviceInterfaceInfo::try_read_from_bytes(payload_slice).map_err(|e| { - anyhow::anyhow!("failed to deserialize TdispDeviceInterfaceInfo: {:?}", e) - })?, - ) - } - TdispCommandId::BIND => TdispCommandResponsePayload::None, - TdispCommandId::UNBIND => TdispCommandResponsePayload::None, - TdispCommandId::START_TDI => TdispCommandResponsePayload::None, - TdispCommandId::GET_TDI_REPORT => { - // Peel off the header from the payload - let payload_header_len = size_of::(); - let payload_header_slice = &payload_slice[0..payload_header_len]; - - // Read the header - let payload_header = - TdispSerializedCommandRequestGetTdiReport::try_read_from_bytes( - payload_header_slice, + if !payload_slice.is_empty() { + let payload = match packet.command_id { + TdispCommandId::GET_DEVICE_INTERFACE_INFO => { + TdispCommandResponsePayload::GetDeviceInterfaceInfo( + TdispDeviceInterfaceInfo::try_read_from_bytes(payload_slice).map_err( + |e| { + anyhow::anyhow!( + "failed to deserialize TdispDeviceInterfaceInfo: {:?}", + e + ) + }, + )?, ) - .map_err(|e| { - anyhow::anyhow!( - "failed to deserialize TdispSerializedCommandRequestGetTdiReport: {:?}", - e + } + TdispCommandId::BIND => TdispCommandResponsePayload::None, + TdispCommandId::UNBIND => TdispCommandResponsePayload::None, + TdispCommandId::START_TDI => TdispCommandResponsePayload::None, + TdispCommandId::GET_TDI_REPORT => { + // Peel off the header from the payload + let payload_header_len = size_of::(); + let payload_header_slice = &payload_slice[0..payload_header_len]; + + // Read the header + let payload_header = + TdispSerializedCommandRequestGetTdiReport::try_read_from_bytes( + payload_header_slice, ) - })?; - - // Determine the number of bytes to read from the payload for the report buffer - let payload_bytes = &payload_slice[payload_header_len - ..(payload_header_len + payload_header.report_buffer_size as usize)]; - - // Convert this to the response type - TdispCommandResponsePayload::GetTdiReport(TdispCommandResponseGetTdiReport { - report_type: payload_header.report_type, - report_buffer: payload_bytes.to_vec(), - }) - } - TdispCommandId::UNKNOWN => { - return Err(anyhow::anyhow!( - "invalid command id in GuestToHostResponse: {:?}", - header.result - )); - } - _ => { - return Err(anyhow::anyhow!( - "invalid command id in GuestToHostResponse: {:?}", - header.result - )); - } - }; - - packet.payload = payload; + .map_err(|e| { + anyhow::anyhow!( + "failed to deserialize TdispSerializedCommandRequestGetTdiReport: {:?}", + e + ) + })?; + + // Determine the number of bytes to read from the payload for the report buffer + let payload_bytes = &payload_slice[payload_header_len + ..(payload_header_len + payload_header.report_buffer_size as usize)]; + + // Convert this to the response type + TdispCommandResponsePayload::GetTdiReport(TdispCommandResponseGetTdiReport { + report_type: payload_header.report_type, + report_buffer: payload_bytes.to_vec(), + }) + } + TdispCommandId::UNKNOWN => { + return Err(anyhow::anyhow!( + "invalid command id in GuestToHostResponse: {:?}", + header.result + )); + } + _ => { + return Err(anyhow::anyhow!( + "invalid command id in GuestToHostResponse: {:?}", + header.result + )); + } + }; + + packet.payload = payload; + } Ok(packet) } From 09c80c3ab4ddc466f7f52d3b7e8f43b5e0e60b9b Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:16:25 -0700 Subject: [PATCH 24/31] cr: refactor TdispReportType for open_enum --- openhcl/openhcl_tdisp/src/lib.rs | 4 +- vm/devices/tdisp/src/devicereport.rs | 98 +++++----------------------- vm/devices/tdisp/src/lib.rs | 39 +++++------ 3 files changed, 34 insertions(+), 107 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 9dcf30256b..d43a9c59f6 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -17,7 +17,7 @@ use tdisp::GuestToHostCommand; use tdisp::GuestToHostResponse; use tdisp::TdispGuestUnbindReason; use tdisp::devicereport::TdiReportStruct; -use tdisp::devicereport::TdispDeviceReportType; +use tdisp::devicereport::TdispReportType; /// Represents a TDISP device assigned to a guest partition. This trait allows /// the guest to send TDISP commands to the host through the backing interface. @@ -64,7 +64,7 @@ pub trait VpciTdispInterface: Send + Sync { /// Request a device report from the TDI or physical device depending on the report type. fn tdisp_get_device_report( &self, - report_type: &TdispDeviceReportType, + report_type: &TdispReportType, ) -> impl Future>> + Send; /// Request a TDI report from the TDI or physical device. diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 90b680f625..342f20c6f8 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -2,99 +2,35 @@ // Licensed under the MIT License. use bitfield_struct::bitfield; +use open_enum::open_enum; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::KnownLayout; -/// Represents a type of report that can be requested from the TDI (VF). -#[derive(Debug)] -pub enum TdispTdiReport { - /// Invalid report type. All usages of this report type should be treated as an error. - Invalid, - - /// Guest requests the guest device ID of the TDI. - GuestDeviceId, - - /// Guest requests the interface report of the TDI. - InterfaceReport, -} - -/// Represents a type of report that can be requested from the physical device. -#[derive(Debug)] -pub enum TdispDeviceReport { - /// Invalid report type. All usages of this report type should be treated as an error. - Invalid, +open_enum! { + /// Represents a type of report that can be requested from the TDI (VF). + pub enum TdispReportType: u32 { + /// Invalid report type. All usages of this report type should be treated as an error. + INVALID = 0, - /// Guest requests the certificate chain of the device. - CertificateChain, + /// Guest requests the guest device ID of the TDI. + GUEST_DEVICE_ID = 1, - /// Guest requests the measurements of the device. - Measurements, + /// Guest requests the interface report of the TDI. + INTERFACE_REPORT = 2, - /// Guest requests whether the device is registered. - /// [TDISP TODO] Remove this report type? Doesn't seem to serve a purpose. - IsRegistered, -} + /// Guest requests the certificate chain of the physical device. + CERTIFICATE_CHAIN = 3, -impl From<&TdispTdiReport> for u32 { - fn from(value: &TdispTdiReport) -> Self { - match value { - TdispTdiReport::Invalid => 0, - TdispTdiReport::GuestDeviceId => 1, - TdispTdiReport::InterfaceReport => 2, - } - } -} + /// Guest requests the measurements of the physical device. + MEASUREMENTS = 4, -/// Set to the number of enums in TdispTdiReport to assign an ID that is unique for this enum. -/// [TDISP TODO] Is there a better way to do this by calculating how many enums there are with Rust const types? -pub const TDISP_TDI_REPORT_ENUM_COUNT: u32 = 3; - -impl From<&TdispDeviceReport> for u32 { - fn from(value: &TdispDeviceReport) -> Self { - match value { - TdispDeviceReport::Invalid => TDISP_TDI_REPORT_ENUM_COUNT, - TdispDeviceReport::CertificateChain => TDISP_TDI_REPORT_ENUM_COUNT + 1, - TdispDeviceReport::Measurements => TDISP_TDI_REPORT_ENUM_COUNT + 2, - TdispDeviceReport::IsRegistered => TDISP_TDI_REPORT_ENUM_COUNT + 3, - } + /// Guest requests whether the physical device is registered. + /// [TDISP TODO] Remove this report type? Doesn't seem to serve a purpose. + IS_REGISTERED = 5, } } -impl From<&TdispDeviceReportType> for u32 { - fn from(value: &TdispDeviceReportType) -> Self { - match value { - TdispDeviceReportType::TdiReport(report_type) => report_type.into(), - TdispDeviceReportType::DeviceReport(report_type) => report_type.into(), - } - } -} - -impl From for TdispDeviceReportType { - fn from(value: u32) -> Self { - match value { - 0 => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), - 1 => TdispDeviceReportType::TdiReport(TdispTdiReport::GuestDeviceId), - 2 => TdispDeviceReportType::TdiReport(TdispTdiReport::InterfaceReport), - 3 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::Invalid), - 4 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::CertificateChain), - 5 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::Measurements), - 6 => TdispDeviceReportType::DeviceReport(TdispDeviceReport::IsRegistered), - _ => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), - } - } -} - -/// Represents a type of report that can be requested from an assigned TDISP device. -#[derive(Debug)] -pub enum TdispDeviceReportType { - /// A report produced by the device interface and not the physical interface. - TdiReport(TdispTdiReport), - - /// A report produced by the physical interface and not the device interface. - DeviceReport(TdispDeviceReport), -} - /// PCI Express Base Specification Revision 6.3 Section 11.3.11 DEVICE_INTERFACE_REPORT #[bitfield(u16)] #[derive(KnownLayout, FromBytes, Immutable)] diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index edc5b194bc..1215dfa637 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -52,9 +52,7 @@ use thiserror::Error; use crate::command::TdispCommandRequestPayload; use crate::command::TdispCommandResponseGetTdiReport; -use crate::devicereport::TdispDeviceReport; -use crate::devicereport::TdispDeviceReportType; -use crate::devicereport::TdispTdiReport; +use crate::devicereport::TdispReportType; /// Major version of the TDISP guest-to-host interface. pub const TDISP_INTERFACE_VERSION_MAJOR: u32 = 1; @@ -87,7 +85,7 @@ pub trait TdispHostDeviceInterface: Send + Sync { /// Get a device interface report for the device. fn tdisp_get_device_report( &mut self, - _report_type: &TdispDeviceReportType, + _report_type: TdispReportType, ) -> anyhow::Result> { Err(anyhow::anyhow!("not implemented")) } @@ -197,19 +195,21 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { TdispCommandId::GET_TDI_REPORT => { let report_type = match &command.payload { TdispCommandRequestPayload::GetTdiReport(payload) => { - TdispDeviceReportType::from(payload.report_type) + TdispReportType(payload.report_type) } - _ => TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid), + _ => TdispReportType::INVALID, }; - let report_buffer = self.machine.request_attestation_report(&report_type); + let report_buffer = self.machine.request_attestation_report(report_type); if let Err(err) = report_buffer { error = err; } else { payload = TdispCommandResponsePayload::GetTdiReport( TdispCommandResponseGetTdiReport { - report_type: (&report_type).into(), - report_buffer: report_buffer.unwrap(), + report_type: report_type.0, + report_buffer: report_buffer + .context("expecting report buffer from request_attestation_report") + .unwrap(), }, ); } @@ -600,7 +600,7 @@ pub trait TdispGuestRequestInterface { /// the `Locked` or `Run` state will cause an error and unbind the device. fn request_attestation_report( &mut self, - report_type: &TdispDeviceReportType, + report_type: TdispReportType, ) -> Result, TdispGuestOperationError>; /// Guest initiates a graceful unbind of the device. The guest might @@ -683,7 +683,7 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { fn request_attestation_report( &mut self, - report_type: &TdispDeviceReportType, + report_type: TdispReportType, ) -> Result, TdispGuestOperationError> { if self.current_state != TdispTdiState::Locked && self.current_state != TdispTdiState::Run { self.error_print( @@ -695,19 +695,10 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { return Err(TdispGuestOperationError::InvalidGuestAttestationReportState); } - match report_type { - TdispDeviceReportType::TdiReport(TdispTdiReport::Invalid) => { - self.error_print("Invalid report type TdispTdiReport::TdiInfoInvalid requested"); - return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); - } - TdispDeviceReportType::DeviceReport(TdispDeviceReport::Invalid) => { - self.error_print( - "Invalid report type TdispDeviceReport::DeviceInfoInvalid requested", - ); - return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); - } - _ => {} - }; + if report_type == TdispReportType::INVALID { + self.error_print("Invalid report type TdispReportId::INVALID requested"); + return Err(TdispGuestOperationError::InvalidGuestAttestationReportType); + } let report_buffer = self .host_interface From f25f2fcead1e32d0585990e21b16df4a8df08f06 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:26:46 -0700 Subject: [PATCH 25/31] cr: refactor TdispTdiState for open_enum --- vm/devices/tdisp/src/lib.rs | 100 ++++++++++++------------------ vm/devices/tdisp/src/serialize.rs | 9 +-- 2 files changed, 44 insertions(+), 65 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 1215dfa637..2bf16f1e48 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -45,7 +45,7 @@ pub use command::TdispCommandResponsePayload; pub use command::TdispDeviceInterfaceInfo; use anyhow::Context; -use inspect::Inspect; +use open_enum::open_enum; use parking_lot::Mutex; use std::sync::Arc; use thiserror::Error; @@ -63,6 +63,31 @@ pub const TDISP_INTERFACE_VERSION_MINOR: u32 = 0; /// Callback for receiving TDISP commands from the guest. pub type TdispCommandCallback = dyn Fn(&GuestToHostCommand) -> anyhow::Result<()> + Send + Sync; +open_enum! { + /// Represents the state of the TDISP host device emulator. + pub enum TdispTdiState: u64 { + /// The TDISP state is not initialized or indeterminate. + UNINITIALIZED = 0, + + /// `TDI.Unlocked`` - The device is in its default "reset" state. Resources can be configured + /// and no functionality can be used. Attestation cannot take place until the device has + /// been locked. + UNLOCKED = 1, + + /// `TDI.Locked`` - The device resources have been locked and attestation can take place. The + /// device's resources have been mapped and configured in hardware, but the device has not + /// been attested. Private DMA and MMIO will not be functional until the resources have + /// been accepted into the guest context. Unencrypted "bounced" operations are still allowed. + LOCKED = 2, + + /// `TDI.Run`` - The device is no longer functional for unencrypted operations. Device resources + /// are locked but encrypted operations might not be functional. The device + /// will not be functional for encrypted operations until it has been fully validated by the guest + /// calling to firmware to accept resources. + RUN = 3, + } +} + /// Trait used by the emulator to call back into the host. pub trait TdispHostDeviceInterface: Send + Sync { /// Bind a tdi device to the current partition. Transitions device to the Locked @@ -254,53 +279,6 @@ pub trait TdispClientDevice: Send + Sync { fn tdisp_command_to_host(&self, command: GuestToHostCommand) -> anyhow::Result<()>; } -/// Represents the state of the TDISP host device emulator. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect)] -pub enum TdispTdiState { - /// The TDISP state is not initialized or indeterminate. - Uninitialized, - - /// `TDI.Unlocked`` - The device is in its default "reset" state. Resources can be configured - /// and no functionality can be used. Attestation cannot take place until the device has - /// been locked. - Unlocked, - - /// `TDI.Locked`` - The device resources have been locked and attestation can take place. The - /// device's resources have been mapped and configured in hardware, but the device has not - /// been attested. Private DMA and MMIO will not be functional until the resources have - /// been accepted into the guest context. Unencrypted "bounced" operations are still allowed. - Locked, - - /// `TDI.Run`` - The device is no longer functional for unencrypted operations. Device resources - /// are locked but encrypted operations might not be functional. The device - /// will not be functional for encrypted operations until it has been fully validated by the guest - /// calling to firmware to accept resources. - Run, -} - -impl From for u64 { - fn from(value: TdispTdiState) -> Self { - match value { - TdispTdiState::Uninitialized => 0, - TdispTdiState::Unlocked => 1, - TdispTdiState::Locked => 2, - TdispTdiState::Run => 3, - } - } -} - -impl From for TdispTdiState { - fn from(value: u64) -> Self { - match value { - 0 => TdispTdiState::Uninitialized, - 1 => TdispTdiState::Unlocked, - 2 => TdispTdiState::Locked, - 3 => TdispTdiState::Run, - _ => TdispTdiState::Uninitialized, - } - } -} - /// The number of states to keep in the state history for debug. const TDISP_STATE_HISTORY_LEN: usize = 10; @@ -387,7 +365,7 @@ impl TdispHostStateMachine { /// Create a new TDISP state machine with the `Unlocked` state. pub fn new(host_interface: Arc>) -> Self { Self { - current_state: TdispTdiState::Unlocked, + current_state: TdispTdiState::UNLOCKED, state_history: Vec::new(), debug_device_id: "".to_owned(), unbind_reason_history: Vec::new(), @@ -419,15 +397,15 @@ impl TdispHostStateMachine { /// while higher level transition machinery tries to avoid these conditions. If the new state is impossible, /// `false` is returned. fn is_valid_state_transition(&self, new_state: &TdispTdiState) -> bool { - match (self.current_state, new_state) { + match (self.current_state, *new_state) { // Valid forward progress states from Unlocked -> Run - (TdispTdiState::Unlocked, TdispTdiState::Locked) => true, - (TdispTdiState::Locked, TdispTdiState::Run) => true, + (TdispTdiState::UNLOCKED, TdispTdiState::LOCKED) => true, + (TdispTdiState::LOCKED, TdispTdiState::RUN) => true, // Device can always return to the Unlocked state with `Unbind` - (TdispTdiState::Run, TdispTdiState::Unlocked) => true, - (TdispTdiState::Locked, TdispTdiState::Unlocked) => true, - (TdispTdiState::Unlocked, TdispTdiState::Unlocked) => true, + (TdispTdiState::RUN, TdispTdiState::UNLOCKED) => true, + (TdispTdiState::LOCKED, TdispTdiState::UNLOCKED) => true, + (TdispTdiState::UNLOCKED, TdispTdiState::UNLOCKED) => true, // Every other state transition is invalid _ => false, @@ -480,7 +458,7 @@ impl TdispHostStateMachine { // All states can be reset to the Unlocked state. This can only happen if the // state is corrupt beyond the state machine. - if let Err(reason) = self.transition_state_to(TdispTdiState::Unlocked) { + if let Err(reason) = self.transition_state_to(TdispTdiState::UNLOCKED) { return Err(anyhow::anyhow!( "Impossible state machine violation during TDISP Unbind: {:?}", reason @@ -621,7 +599,7 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { fn request_lock_device_resources(&mut self) -> Result<(), TdispGuestOperationError> { // If the guest attempts to transition the device to the Locked state while the device // is not in the Unlocked state, the device is reset to the Unlocked state. - if self.current_state != TdispTdiState::Unlocked { + if self.current_state != TdispTdiState::UNLOCKED { self.error_print( "Unlocked to Locked state called while device was not in Unlocked state.", ); @@ -648,12 +626,12 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { } self.debug_print("Device transition from Unlocked to Locked state"); - self.transition_state_to(TdispTdiState::Locked).unwrap(); + self.transition_state_to(TdispTdiState::LOCKED).unwrap(); Ok(()) } fn request_start_tdi(&mut self) -> Result<(), TdispGuestOperationError> { - if self.current_state != TdispTdiState::Locked { + if self.current_state != TdispTdiState::LOCKED { self.error_print("StartTDI called while device was not in Locked state."); self.unbind_all(TdispUnbindReason::InvalidGuestTransitionToRun) .map_err(|_| TdispGuestOperationError::HostFailedToProcessCommand)?; @@ -676,7 +654,7 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { } self.debug_print("Device transition from Locked to Run state"); - self.transition_state_to(TdispTdiState::Run).unwrap(); + self.transition_state_to(TdispTdiState::RUN).unwrap(); Ok(()) } @@ -685,7 +663,7 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { &mut self, report_type: TdispReportType, ) -> Result, TdispGuestOperationError> { - if self.current_state != TdispTdiState::Locked && self.current_state != TdispTdiState::Run { + if self.current_state != TdispTdiState::LOCKED && self.current_state != TdispTdiState::RUN { self.error_print( "Request to retrieve attestation report called while device was not in Locked or Run state.", ); diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 8033c6bc23..4d89306127 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -15,6 +15,7 @@ use crate::TdispCommandId; use crate::TdispCommandResponsePayload; use crate::TdispDeviceInterfaceInfo; use crate::TdispGuestOperationError; +use crate::TdispTdiState; /// Serialized form of the header for a GuestToHostCommand packet #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] @@ -57,8 +58,8 @@ impl From<&GuestToHostResponse> for GuestToHostResponseSerializedHeader { GuestToHostResponseSerializedHeader { command_id: value.command_id.0, result: value.result.into(), - tdi_state_before: value.tdi_state_before.into(), - tdi_state_after: value.tdi_state_after.into(), + tdi_state_before: value.tdi_state_before.0, + tdi_state_after: value.tdi_state_after.0, } } } @@ -78,8 +79,8 @@ impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { GuestToHostResponse { command_id: TdispCommandId(value.command_id), result: value.result.into(), - tdi_state_before: value.tdi_state_before.into(), - tdi_state_after: value.tdi_state_after.into(), + tdi_state_before: TdispTdiState(value.tdi_state_before), + tdi_state_after: TdispTdiState(value.tdi_state_after), payload: TdispCommandResponsePayload::None, } } From 7b3d2abb400b2a330282fb1bebf7c0be5eac1c93 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:31:59 -0700 Subject: [PATCH 26/31] cr: refactor TdispGuestOperationError for open_enum (also allow it to convert to and from an Error, is this okay?) --- vm/devices/tdisp/src/lib.rs | 170 +++++++++++++++++++----------- vm/devices/tdisp/src/serialize.rs | 6 +- 2 files changed, 110 insertions(+), 66 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 2bf16f1e48..83f8865569 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -209,8 +209,10 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { } TdispCommandId::UNBIND => { let unbind_reason: TdispGuestUnbindReason = match command.payload { - TdispCommandRequestPayload::Unbind(payload) => payload.unbind_reason.into(), - _ => TdispGuestUnbindReason::Unknown, + TdispCommandRequestPayload::Unbind(payload) => { + TdispGuestUnbindReason(payload.unbind_reason) + } + _ => TdispGuestUnbindReason::UNKNOWN, }; let unbind_res = self.machine.request_unbind(unbind_reason); if let Err(err) = unbind_res { @@ -318,31 +320,14 @@ pub enum TdispUnbindReason { InvalidGuestUnbindReason(anyhow::Error), } -/// For a guest initiated unbind, the guest can provide a reason for the unbind. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum TdispGuestUnbindReason { - /// The guest requested to unbind the device for an unspecified reason. - Unknown, - - /// The guest requested to unbind the device because the device is being detached. - Graceful, -} - -impl From for u64 { - fn from(value: TdispGuestUnbindReason) -> Self { - match value { - TdispGuestUnbindReason::Unknown => 0, - TdispGuestUnbindReason::Graceful => 1, - } - } -} +open_enum! { + /// For a guest initiated unbind, the guest can provide a reason for the unbind. + pub enum TdispGuestUnbindReason: u64 { + /// The guest requested to unbind the device for an unspecified reason. + UNKNOWN = 0, -impl From for TdispGuestUnbindReason { - fn from(value: u64) -> Self { - match value { - 1 => TdispGuestUnbindReason::Graceful, - _ => TdispGuestUnbindReason::Unknown, - } + /// The guest requested to unbind the device because the device is being detached. + GRACEFUL = 1, } } @@ -412,12 +397,6 @@ impl TdispHostStateMachine { } } - /// Check if the guest unbind reason is valid. This is used for bookkeeping purposes to - /// ensure the guest unbind reason recorded in the unbind history is valid. - fn is_valid_guest_unbind_reason(&self, reason: &TdispGuestUnbindReason) -> bool { - !(matches!(reason, TdispGuestUnbindReason::Unknown)) - } - /// Transitions the state machine to the new state if it is valid. If the new state is invalid, /// the state of the device is reset to the `Unlocked` state. fn transition_state_to(&mut self, new_state: TdispTdiState) -> anyhow::Result<()> { @@ -513,35 +492,95 @@ pub enum TdispGuestOperationError { InvalidGuestAttestationReportType, } -impl From for u64 { - fn from(err: TdispGuestOperationError) -> Self { - match err { - TdispGuestOperationError::Unknown => 0, - TdispGuestOperationError::Success => 1, - TdispGuestOperationError::InvalidDeviceState => 2, - TdispGuestOperationError::InvalidGuestUnbindReason => 3, - TdispGuestOperationError::InvalidGuestCommandId => 4, - TdispGuestOperationError::NotImplemented => 5, - TdispGuestOperationError::HostFailedToProcessCommand => 6, - TdispGuestOperationError::InvalidGuestAttestationReportState => 7, - TdispGuestOperationError::InvalidGuestAttestationReportType => 8, +open_enum! { + /// Error returned by TDISP operations dispatched by the guest. + pub enum TdispGuestOperationErrorCode: u64 { + /// Unknown error code. + UNKNOWN = 0, + + /// The operation was successful. + SUCCESS = 1, + + /// The current TDI state is incorrect for this operation. + INVALID_DEVICE_STATE = 2, + + /// The reason for this unbind is invalid. + INVALID_GUEST_UNBIND_REASON = 3, + + /// Invalid TDI command ID. + INVALID_GUEST_COMMAND_ID = 4, + + /// Operation requested was not implemented. + NOT_IMPLEMENTED = 5, + + /// Host failed to process command. + HOST_FAILED_TO_PROCESS_COMMAND = 6, + + /// The device was not in the Locked or Run state when the attestation report was requested. + INVALID_GUEST_ATTESTATION_REPORT_STATE = 7, + + /// Invalid attestation report type requested. + INVALID_GUEST_ATTESTATION_REPORT_TYPE = 8, + } +} + +impl From for TdispGuestOperationError { + fn from(err_code: TdispGuestOperationErrorCode) -> Self { + match err_code { + TdispGuestOperationErrorCode::UNKNOWN => TdispGuestOperationError::Unknown, + TdispGuestOperationErrorCode::SUCCESS => TdispGuestOperationError::Success, + TdispGuestOperationErrorCode::INVALID_DEVICE_STATE => { + TdispGuestOperationError::InvalidDeviceState + } + TdispGuestOperationErrorCode::INVALID_GUEST_UNBIND_REASON => { + TdispGuestOperationError::InvalidGuestUnbindReason + } + TdispGuestOperationErrorCode::INVALID_GUEST_COMMAND_ID => { + TdispGuestOperationError::InvalidGuestCommandId + } + TdispGuestOperationErrorCode::NOT_IMPLEMENTED => { + TdispGuestOperationError::NotImplemented + } + TdispGuestOperationErrorCode::HOST_FAILED_TO_PROCESS_COMMAND => { + TdispGuestOperationError::HostFailedToProcessCommand + } + TdispGuestOperationErrorCode::INVALID_GUEST_ATTESTATION_REPORT_STATE => { + TdispGuestOperationError::InvalidGuestAttestationReportState + } + TdispGuestOperationErrorCode::INVALID_GUEST_ATTESTATION_REPORT_TYPE => { + TdispGuestOperationError::InvalidGuestAttestationReportType + } + _ => TdispGuestOperationError::Unknown, } } } -impl From for TdispGuestOperationError { - fn from(err: u64) -> Self { +impl From for TdispGuestOperationErrorCode { + fn from(err: TdispGuestOperationError) -> Self { match err { - 0 => TdispGuestOperationError::Unknown, - 1 => TdispGuestOperationError::Success, - 2 => TdispGuestOperationError::InvalidDeviceState, - 3 => TdispGuestOperationError::InvalidGuestUnbindReason, - 4 => TdispGuestOperationError::InvalidGuestCommandId, - 5 => TdispGuestOperationError::NotImplemented, - 6 => TdispGuestOperationError::HostFailedToProcessCommand, - 7 => TdispGuestOperationError::InvalidGuestAttestationReportState, - 8 => TdispGuestOperationError::InvalidGuestAttestationReportType, - _ => TdispGuestOperationError::Unknown, + TdispGuestOperationError::Unknown => TdispGuestOperationErrorCode::UNKNOWN, + TdispGuestOperationError::Success => TdispGuestOperationErrorCode::SUCCESS, + TdispGuestOperationError::InvalidDeviceState => { + TdispGuestOperationErrorCode::INVALID_DEVICE_STATE + } + TdispGuestOperationError::InvalidGuestUnbindReason => { + TdispGuestOperationErrorCode::INVALID_GUEST_UNBIND_REASON + } + TdispGuestOperationError::InvalidGuestCommandId => { + TdispGuestOperationErrorCode::INVALID_GUEST_COMMAND_ID + } + TdispGuestOperationError::NotImplemented => { + TdispGuestOperationErrorCode::NOT_IMPLEMENTED + } + TdispGuestOperationError::HostFailedToProcessCommand => { + TdispGuestOperationErrorCode::HOST_FAILED_TO_PROCESS_COMMAND + } + TdispGuestOperationError::InvalidGuestAttestationReportState => { + TdispGuestOperationErrorCode::INVALID_GUEST_ATTESTATION_REPORT_STATE + } + TdispGuestOperationError::InvalidGuestAttestationReportType => { + TdispGuestOperationErrorCode::INVALID_GUEST_ATTESTATION_REPORT_TYPE + } } } } @@ -700,14 +739,17 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { // The guest can provide a reason for the unbind. If the unbind reason isn't valid for a guest (such as // if the guest says it is unbinding due to a host-related error), the reason is discarded and InvalidGuestUnbindReason // is recorded in the unbind history. - let reason = if !self.is_valid_guest_unbind_reason(&reason) { - let error_txt = format!("Invalid guest unbind reason {reason:?} requested"); - - self.error_print(error_txt.as_str()); - - TdispUnbindReason::InvalidGuestUnbindReason(anyhow::anyhow!(error_txt)) - } else { - TdispUnbindReason::GuestInitiated(reason) + let reason = match reason { + TdispGuestUnbindReason::GRACEFUL => TdispUnbindReason::GuestInitiated(reason), + _ => { + self.error_print( + format!("Invalid guest unbind reason {} requested", reason.0).as_str(), + ); + TdispUnbindReason::InvalidGuestUnbindReason(anyhow::anyhow!( + "Invalid guest unbind reason {} requested", + reason.0 + )) + } }; self.debug_print(&format!( diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index 4d89306127..e1dcddb10a 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -15,6 +15,7 @@ use crate::TdispCommandId; use crate::TdispCommandResponsePayload; use crate::TdispDeviceInterfaceInfo; use crate::TdispGuestOperationError; +use crate::TdispGuestOperationErrorCode; use crate::TdispTdiState; /// Serialized form of the header for a GuestToHostCommand packet @@ -55,9 +56,10 @@ impl From<&GuestToHostCommand> for GuestToHostCommandSerializedHeader { impl From<&GuestToHostResponse> for GuestToHostResponseSerializedHeader { fn from(value: &GuestToHostResponse) -> Self { + let serialized_err_code: TdispGuestOperationErrorCode = value.result.into(); GuestToHostResponseSerializedHeader { command_id: value.command_id.0, - result: value.result.into(), + result: serialized_err_code.0, tdi_state_before: value.tdi_state_before.0, tdi_state_after: value.tdi_state_after.0, } @@ -78,7 +80,7 @@ impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { fn from(value: &GuestToHostResponseSerializedHeader) -> Self { GuestToHostResponse { command_id: TdispCommandId(value.command_id), - result: value.result.into(), + result: TdispGuestOperationErrorCode(value.result).into(), tdi_state_before: TdispTdiState(value.tdi_state_before), tdi_state_after: TdispTdiState(value.tdi_state_after), payload: TdispCommandResponsePayload::None, From 0adbc6d431d3568eddb20382069e2ffbfe6549bb Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:45:49 -0700 Subject: [PATCH 27/31] cr: remove default impls --- vm/devices/tdisp/src/lib.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 83f8865569..fe34270cba 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -92,28 +92,18 @@ open_enum! { pub trait TdispHostDeviceInterface: Send + Sync { /// Bind a tdi device to the current partition. Transitions device to the Locked /// state from Unlocked. - fn tdisp_bind_device(&mut self) -> anyhow::Result<()> { - Err(anyhow::anyhow!("not implemented")) - } + fn tdisp_bind_device(&mut self) -> anyhow::Result<()>; /// Start a bound device by transitioning it to the Run state from the Locked state. /// This allows attestation and resources to be accepted into the guest context. - fn tdisp_start_device(&mut self) -> anyhow::Result<()> { - Err(anyhow::anyhow!("not implemented")) - } + fn tdisp_start_device(&mut self) -> anyhow::Result<()>; /// Unbind a tdi device from the current partition. - fn tdisp_unbind_device(&mut self) -> anyhow::Result<()> { - Err(anyhow::anyhow!("not implemented")) - } + fn tdisp_unbind_device(&mut self) -> anyhow::Result<()>; /// Get a device interface report for the device. - fn tdisp_get_device_report( - &mut self, - _report_type: TdispReportType, - ) -> anyhow::Result> { - Err(anyhow::anyhow!("not implemented")) - } + fn tdisp_get_device_report(&mut self, _report_type: TdispReportType) + -> anyhow::Result>; } /// Trait added to host virtual devices to dispatch TDISP commands from guests. @@ -122,10 +112,7 @@ pub trait TdispHostDeviceTarget: Send + Sync { fn tdisp_handle_guest_command( &mut self, _command: GuestToHostCommand, - ) -> anyhow::Result { - tracing::warn!("TdispHostDeviceTarget not implemented: tdisp_dispatch"); - anyhow::bail!("TdispHostDeviceTarget not implemented: tdisp_dispatch") - } + ) -> anyhow::Result; } /// An emulator which runs the TDISP state machine for a synthetic device. From dfac5c06a2200037fb1084aa2736a0478af98ba6 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 15:52:23 -0700 Subject: [PATCH 28/31] cr: cleanup From<> and Into<> traits --- vm/devices/tdisp/src/command.rs | 6 --- vm/devices/tdisp/src/serialize.rs | 73 ++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs index 54444f65ca..de0feaecb8 100644 --- a/vm/devices/tdisp/src/command.rs +++ b/vm/devices/tdisp/src/command.rs @@ -90,12 +90,6 @@ pub enum TdispCommandResponsePayload { GetTdiReport(TdispCommandResponseGetTdiReport), } -impl From for TdispCommandResponsePayload { - fn from(value: TdispDeviceInterfaceInfo) -> Self { - TdispCommandResponsePayload::GetDeviceInterfaceInfo(value) - } -} - /// Serialized to and from the payload field of a TdispCommandRequest #[derive(Debug, Copy, Clone)] pub enum TdispCommandRequestPayload { diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index e1dcddb10a..dc03805b3b 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -44,45 +44,68 @@ pub struct GuestToHostResponseSerializedHeader { pub tdi_state_after: u64, } -// [TDISP TODO] There's probably a better way to do these conversions. -impl From<&GuestToHostCommand> for GuestToHostCommandSerializedHeader { - fn from(value: &GuestToHostCommand) -> Self { +/// Trait used to serialize a command or response header into its serializable form. +trait SerializeHeader { + type SerializedHeader; + + fn to_serializable_header(&self) -> Self::SerializedHeader; +} + +/// Trait used to deserialize a command or response header from its serializable form. +trait DeserializeHeader { + type DeserializedHeader; + + fn from_serializable_header(header: &Self::DeserializedHeader) -> Self; +} + +impl SerializeHeader for GuestToHostCommand { + type SerializedHeader = GuestToHostCommandSerializedHeader; + + fn to_serializable_header(&self) -> Self::SerializedHeader { GuestToHostCommandSerializedHeader { - device_id: value.device_id, - command_id: value.command_id.0, + device_id: self.device_id, + command_id: self.command_id.0, } } } -impl From<&GuestToHostResponse> for GuestToHostResponseSerializedHeader { - fn from(value: &GuestToHostResponse) -> Self { - let serialized_err_code: TdispGuestOperationErrorCode = value.result.into(); +impl SerializeHeader for GuestToHostResponse { + type SerializedHeader = GuestToHostResponseSerializedHeader; + + fn to_serializable_header(&self) -> Self::SerializedHeader { + let serialized_err_code: TdispGuestOperationErrorCode = self.result.into(); GuestToHostResponseSerializedHeader { - command_id: value.command_id.0, + command_id: self.command_id.0, result: serialized_err_code.0, - tdi_state_before: value.tdi_state_before.0, - tdi_state_after: value.tdi_state_after.0, + tdi_state_before: self.tdi_state_before.0, + tdi_state_after: self.tdi_state_after.0, } } } -impl From<&GuestToHostCommandSerializedHeader> for GuestToHostCommand { - fn from(value: &GuestToHostCommandSerializedHeader) -> Self { +impl DeserializeHeader for GuestToHostCommand { + type DeserializedHeader = GuestToHostCommandSerializedHeader; + + fn from_serializable_header(header: &Self::DeserializedHeader) -> Self { GuestToHostCommand { - device_id: value.device_id, - command_id: TdispCommandId(value.command_id), + device_id: header.device_id, + command_id: TdispCommandId(header.command_id), payload: TdispCommandRequestPayload::None, } } } -impl From<&GuestToHostResponseSerializedHeader> for GuestToHostResponse { - fn from(value: &GuestToHostResponseSerializedHeader) -> Self { +impl DeserializeHeader for GuestToHostResponse { + type DeserializedHeader = GuestToHostResponseSerializedHeader; + + fn from_serializable_header(header: &Self::DeserializedHeader) -> Self { + let serialized_err_code: TdispGuestOperationErrorCode = + TdispGuestOperationErrorCode(header.result); GuestToHostResponse { - command_id: TdispCommandId(value.command_id), - result: TdispGuestOperationErrorCode(value.result).into(), - tdi_state_before: TdispTdiState(value.tdi_state_before), - tdi_state_after: TdispTdiState(value.tdi_state_after), + command_id: TdispCommandId(header.command_id), + result: serialized_err_code.into(), + tdi_state_before: TdispTdiState(header.tdi_state_before), + tdi_state_after: TdispTdiState(header.tdi_state_after), payload: TdispCommandResponsePayload::None, } } @@ -99,7 +122,7 @@ pub trait SerializePacket: Sized { impl SerializePacket for GuestToHostCommand { fn serialize_to_bytes(self) -> Vec { - let header = GuestToHostCommandSerializedHeader::from(&self); + let header = self.to_serializable_header(); let bytes = header.as_bytes(); tracing::debug!(msg = format!("serialize_to_bytes: header={:?}", header)); tracing::debug!(msg = format!("serialize_to_bytes: {:?}", bytes)); @@ -131,7 +154,7 @@ impl SerializePacket for GuestToHostCommand { let payload_slice = &bytes[header_length..]; - let mut packet: Self = header.into(); + let mut packet: Self = GuestToHostCommand::from_serializable_header(header); if !payload_slice.is_empty() { let payload = match packet.command_id { @@ -176,7 +199,7 @@ impl SerializePacket for GuestToHostCommand { impl SerializePacket for GuestToHostResponse { fn serialize_to_bytes(self) -> Vec { - let header = GuestToHostResponseSerializedHeader::from(&self); + let header = self.to_serializable_header(); let bytes = header.as_bytes(); let mut bytes = bytes.to_vec(); @@ -208,7 +231,7 @@ impl SerializePacket for GuestToHostResponse { anyhow::anyhow!("failed to deserialize GuestToHostResponse header: {:?}", e) })?; - let mut packet: Self = header.into(); + let mut packet: Self = GuestToHostResponse::from_serializable_header(header); // If the result is not success, then we don't need to deserialize the payload. match packet.result { From d95b8306f72ff4455caef012d36f0e03939df77d Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 16:01:47 -0700 Subject: [PATCH 29/31] cr: repr(C) on structures to ensure they have a known serialized layout --- vm/devices/tdisp/src/serialize.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/devices/tdisp/src/serialize.rs b/vm/devices/tdisp/src/serialize.rs index dc03805b3b..64054669c7 100644 --- a/vm/devices/tdisp/src/serialize.rs +++ b/vm/devices/tdisp/src/serialize.rs @@ -19,6 +19,7 @@ use crate::TdispGuestOperationErrorCode; use crate::TdispTdiState; /// Serialized form of the header for a GuestToHostCommand packet +#[repr(C)] #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct GuestToHostCommandSerializedHeader { /// The logical TDISP device ID of the device that the command is being sent to. @@ -29,6 +30,7 @@ pub struct GuestToHostCommandSerializedHeader { } /// Serialized form of the header for a GuestToHostResponse packet +#[repr(C)] #[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable)] pub struct GuestToHostResponseSerializedHeader { /// The command ID of the command that was processed. See: `TdispCommandId` From 81c2bf02d96898ffdbcdab845ac75ee5ead001b4 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 16:05:16 -0700 Subject: [PATCH 30/31] cr: allow setting debug device id in constructor --- vm/devices/tdisp/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index fe34270cba..4883a83d2e 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -123,10 +123,13 @@ pub struct TdispHostDeviceTargetEmulator { impl TdispHostDeviceTargetEmulator { /// Create a new emulator which runs the TDISP state machine for a synthetic device. - pub fn new(host_interface: Arc>) -> Self { + pub fn new( + host_interface: Arc>, + debug_device_id: &str, + ) -> Self { Self { machine: TdispHostStateMachine::new(host_interface), - debug_device_id: "".to_owned(), + debug_device_id: debug_device_id.to_owned(), } } From ecb4b24913e8b5026079de503c66d448215c0968 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 16:07:19 -0700 Subject: [PATCH 31/31] xfmt --fix --- Cargo.lock | 1 - vm/devices/tdisp/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd55b1ac3c..73ed8a181e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7088,7 +7088,6 @@ version = "0.0.0" dependencies = [ "anyhow", "bitfield-struct 0.11.0", - "inspect", "open_enum", "parking_lot", "static_assertions", diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index 0eccb97ea7..1d5cb32300 100644 --- a/vm/devices/tdisp/Cargo.toml +++ b/vm/devices/tdisp/Cargo.toml @@ -9,7 +9,6 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true bitfield-struct.workspace = true -inspect.workspace = true open_enum.workspace = true parking_lot.workspace = true static_assertions.workspace = true