From ad4911ed03cfbc7c876a97a7a5df75ea19f74517 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 2 Oct 2025 16:07:19 -0700 Subject: [PATCH 01/32] Add debug device id Add openhcl_tdisp dependency crate Add tdisp handlers to vpci protocol Handle case where packet is empty but host returns success --- Cargo.lock | 5 + openhcl/openhcl_tdisp/src/lib.rs | 62 ++++++ vm/devices/pci/vpci_client/Cargo.toml | 2 + vm/devices/pci/vpci_client/src/lib.rs | 241 ++++++++++++++++++++++++ vm/devices/pci/vpci_protocol/src/lib.rs | 28 +++ vm/devices/tdisp/Cargo.toml | 2 + vm/devices/tdisp/src/command.rs | 141 ++++++++++++++ vm/devices/tdisp/src/lib.rs | 11 +- 8 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 openhcl/openhcl_tdisp/src/lib.rs create mode 100644 vm/devices/tdisp/src/command.rs diff --git a/Cargo.lock b/Cargo.lock index 2e29b4f830..b22cb7525d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7375,6 +7375,8 @@ name = "tdisp" version = "0.0.0" dependencies = [ "anyhow", + "bitfield-struct 0.11.0", + "open_enum", "parking_lot", "prost", "tdisp_proto", @@ -7389,6 +7391,7 @@ dependencies = [ "inspect", "prost", "prost-build", + "zerocopy 0.8.27", ] [[package]] @@ -9909,11 +9912,13 @@ dependencies = [ "guid", "inspect", "mesh", + "openhcl_tdisp", "pal_async", "parking_lot", "pci_core", "slab", "task_control", + "tdisp", "test_with_tracing", "thiserror 2.0.16", "tracelimit", diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs new file mode 100644 index 0000000000..fcf4609795 --- /dev/null +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -0,0 +1,62 @@ +// 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/devices/tdisp` for more information. + +use std::future::Future; +use tdisp::GuestToHostCommand; +use tdisp::GuestToHostResponse; +pub use tdisp::TdispCommandId; +use tdisp::TdispGuestUnbindReason; +use tdisp::TdispReportType; +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 +/// implementations to send TDISP commands to the host through a backing interface +/// such as a VPCI channel. +/// +pub trait TdispVirtualDeviceInterface: 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: &TdispReportType, + ) -> 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/vm/devices/pci/vpci_client/Cargo.toml b/vm/devices/pci/vpci_client/Cargo.toml index 698d453bee..651e0eed8f 100644 --- a/vm/devices/pci/vpci_client/Cargo.toml +++ b/vm/devices/pci/vpci_client/Cargo.toml @@ -7,7 +7,9 @@ rust-version.workspace = true edition.workspace = true [dependencies] +openhcl_tdisp.workspace = true pci_core.workspace = true +tdisp.workspace = true vpci_protocol.workspace = true vmbus_async.workspace = true vmbus_channel.workspace = true diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 21b5b14555..961e49e24a 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -19,6 +19,7 @@ use inspect::Inspect; use inspect::InspectMut; use mesh::rpc::FailableRpc; use mesh::rpc::RpcSend; +use openhcl_tdisp::TdispVirtualDeviceInterface; use pal_async::task::Spawn; use pal_async::task::Task; use parking_lot::Mutex; @@ -28,6 +29,18 @@ use pci_core::spec::hwid::HardwareIds; use std::pin::Pin; use std::sync::Arc; use std::task::Poll; +use tdisp::GuestToHostCommand; +use tdisp::GuestToHostResponse; +use tdisp::TdispCommandId; +use tdisp::TdispCommandResponsePayload; +use tdisp::TdispGuestOperationError; +use tdisp::TdispGuestUnbindReason; +use tdisp::TdispReportType; +use tdisp::command::TdispCommandRequestGetTdiReport; +use tdisp::command::TdispCommandRequestPayload; +use tdisp::command::TdispCommandRequestUnbind; +use tdisp::devicereport::TdiReportStruct; +use tdisp::serialize::SerializePacket; use thiserror::Error; use vmbus_async::queue::IncomingPacket; use vmbus_async::queue::OutgoingPacket; @@ -70,6 +83,7 @@ enum WorkerRequest { QueryResourceRequirements(FailableRpc), Init(FailableRpc), Done(DeviceId), + TdispCommand(FailableRpc), } #[derive(Debug, Copy, Clone, Inspect)] @@ -576,6 +590,187 @@ impl MapVpciInterrupt for VpciDevice { } } +impl TdispVirtualDeviceInterface for VpciDevice { + async fn send_tdisp_command( + &self, + payload: GuestToHostCommand, + ) -> Result { + let serialized = payload.serialize_to_bytes(); + + // Ensure that the length does not exceed the VMBUS maximum packet size. + // This shouldn't be possible since the host should reject the command anyways, + // but fail earlier for safety. + if serialized.len() > vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE { + return Err(anyhow::anyhow!( + "serialized TDISP command exceeds VMBUS maximum packet size ({} > {})", + serialized.len(), + vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE + )); + } + + let res = self + .dev + .req + .call_failable( + WorkerRequest::TdispCommand, + protocol::VpciTdispCommand { + header: protocol::VpciTdispCommandHeader { + message_type: protocol::MessageType::VPCI_TDISP_COMMAND, + slot: self.dev.id.slot, + data_length: serialized.len() as u64, + }, + data: serialized, + }, + ) + .await + .map_err(|err: mesh::rpc::RpcError| { + tracing::error!( + error = &err as &dyn std::error::Error, + "failed to send tdisp command" + ); + anyhow::anyhow!("failed to send tdisp command") + })?; + + match res.result { + TdispGuestOperationError::Success => Ok(res), + _ => { + let err_msg = format!( + "send_tdisp_command {:?} failed because host responded with an error: {:?}", + payload.command_id, res.result + ); + + tracing::error!(msg = err_msg); + Err(anyhow::anyhow!(err_msg)) + } + } + } + + async fn tdisp_get_device_interface_info( + &self, + ) -> anyhow::Result { + let res = self + .send_tdisp_command(GuestToHostCommand { + device_id: self.dev.id.slot.into_bits() as u64, + command_id: TdispCommandId::GET_DEVICE_INTERFACE_INFO, + payload: TdispCommandRequestPayload::None, + }) + .await; + + match res { + Ok(resp) => match resp.payload { + TdispCommandResponsePayload::GetDeviceInterfaceInfo(info) => Ok(info), + _ => Err(anyhow::anyhow!("unexpected response payload")), + }, + Err(e) => Err(e), + } + } + + async fn tdisp_bind_interface(&self) -> anyhow::Result<()> { + let res = self + .send_tdisp_command(GuestToHostCommand { + device_id: self.dev.id.slot.into_bits() as u64, + command_id: TdispCommandId::BIND, + payload: TdispCommandRequestPayload::None, + }) + .await; + match res { + Ok(resp) => match resp.payload { + TdispCommandResponsePayload::None => Ok(()), + _ => Err(anyhow::anyhow!("unexpected response payload")), + }, + Err(e) => Err(e), + } + } + + async fn tdisp_start_device(&self) -> anyhow::Result<()> { + let res = self + .send_tdisp_command(GuestToHostCommand { + device_id: self.dev.id.slot.into_bits() as u64, + command_id: TdispCommandId::START_TDI, + payload: TdispCommandRequestPayload::None, + }) + .await; + match res { + Ok(resp) => match resp.payload { + TdispCommandResponsePayload::None => Ok(()), + _ => Err(anyhow::anyhow!("unexpected response payload")), + }, + Err(e) => Err(e), + } + } + + async fn tdisp_get_device_report( + &self, + report_type: &TdispReportType, + ) -> anyhow::Result> { + let res = self + .send_tdisp_command(GuestToHostCommand { + device_id: self.dev.id.slot.into_bits() as u64, + command_id: TdispCommandId::GET_TDI_REPORT, + payload: TdispCommandRequestPayload::GetTdiReport( + TdispCommandRequestGetTdiReport { + report_type: report_type.0, + }, + ), + }) + .await; + + match res { + Ok(resp) => match resp.payload { + TdispCommandResponsePayload::GetTdiReport(resp) => Ok(resp.report_buffer), + _ => Err(anyhow::anyhow!("unexpected response payload")), + }, + Err(e) => Err(e), + } + } + + async fn tdisp_get_tdi_report(&self) -> anyhow::Result { + let res = self + .tdisp_get_device_report(&TdispReportType::INTERFACE_REPORT) + .await; + + let buffer = res.context("failed to get TDI report")?; + tdisp::devicereport::deserialize_tdi_report(&buffer) + .context("failed to deserialize TDI report from host") + } + + async fn tdisp_get_tdi_device_id(&self) -> anyhow::Result { + let res = self + .tdisp_get_device_report(&TdispReportType::GUEST_DEVICE_ID) + .await; + + let buffer = res.context("failed to get TDI report")?; + + // Ensure it's a u64 + if buffer.len() != size_of::() { + return Err(anyhow::anyhow!("unexpected buffer size for TDI device ID")); + } + + // Convert to u64 + Ok(u64::from_le_bytes(buffer.try_into().unwrap())) + } + + /// Request to unbind the device and return to the Unlocked state. + async fn tdisp_unbind(&self, reason: TdispGuestUnbindReason) -> anyhow::Result<()> { + let res = self + .send_tdisp_command(GuestToHostCommand { + device_id: self.dev.id.slot.into_bits() as u64, + command_id: TdispCommandId::UNBIND, + payload: TdispCommandRequestPayload::Unbind(TdispCommandRequestUnbind { + unbind_reason: reason.0, + }), + }) + .await; + match res { + Ok(resp) => match resp.payload { + TdispCommandResponsePayload::None => Ok(()), + _ => Err(anyhow::anyhow!("unexpected response payload")), + }, + Err(e) => Err(e), + } + } +} + #[derive(InspectMut)] struct VpciClientWorker { conn: VpciConnection, @@ -627,6 +822,7 @@ enum Tx { #[inspect(skip)] FailableRpc<(), protocol::QueryResourceRequirementsReply>, ), AssignedResources(#[inspect(skip)] FailableRpc<(), ()>), + TdispCommand(#[inspect(skip)] FailableRpc<(), GuestToHostResponse>), } impl VpciClient { @@ -995,6 +1191,40 @@ impl WorkerState { )); } } + Tx::TdispCommand(rpc) => { + if status == protocol::Status::SUCCESS { + let mut reader = p.reader(); + + if reader.len() == 0 { + rpc.fail(anyhow::anyhow!("Unexpected empty response from host")); + return Ok(()); + } + + let header = reader + .read_plain::() + .context("failed to read tdisp command header")?; + + let data_len = header.data_length as usize; + + // Allocate a mutable vector with the correct size + let mut data: Vec = vec![0; data_len]; + + // Read data_len bytes from start_of_data into Vec + reader + .read(data.as_mut_slice()) + .context("failed to read tdisp command data")?; + + let host_response = + GuestToHostResponse::deserialize_from_bytes(data.as_slice()) + .context("failed to deserialize tdisp response"); + + rpc.complete(host_response.map_err(mesh::error::RemoteError::new)); + } else { + rpc.fail(anyhow::anyhow!( + "failed to send tdisp command: {status:#x?}", + )); + } + } } Ok(()) } @@ -1093,6 +1323,17 @@ impl WorkerState { send_eject_complete(write, id.slot).await?; } } + WorkerRequest::TdispCommand(rpc) => { + let (req, reply) = rpc.split(); + self.send_tx( + write, + Tx::TdispCommand(reply), + req.header, + req.data.as_slice(), + ) + .await + .context("failed to send tdisp command message")?; + } } Ok(None) } diff --git a/vm/devices/pci/vpci_protocol/src/lib.rs b/vm/devices/pci/vpci_protocol/src/lib.rs index 91d6a456cd..36410f15bd 100644 --- a/vm/devices/pci/vpci_protocol/src/lib.rs +++ b/vm/devices/pci/vpci_protocol/src/lib.rs @@ -106,6 +106,8 @@ open_enum! { CREATE_INTERRUPT3 = 0x4249001b, /// Reset a device RESET_DEVICE = 0x4249001c, + /// TDISP command from guest to host + VPCI_TDISP_COMMAND = 0x4249001D, } } @@ -806,3 +808,29 @@ pub struct PdoMessage { /// PCI slot number of the target device pub slot: SlotNumber, } + +/// A TDISP packet being sent to the host. +#[repr(C)] +#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct VpciTdispCommandHeader { + /// Type of message (must be VPCI_TDISP_COMMAND) + pub message_type: MessageType, + /// PCI slot number of the target device + pub slot: SlotNumber, + /// Length of the data payload to follow + pub data_length: u64, + // pub data: [u8; data_length...], +} + +/// A serialized TDISP VPCI VMBUS command packet. +#[derive(Debug, Clone)] +pub struct VpciTdispCommand { + /// Header of the VMBUS packet + pub header: VpciTdispCommandHeader, + + /// The payload of the command (serialized to Vec) + pub data: Vec, +} + +/// Maximum size of a TDISP command in bytes. Property of the VMBUS implementation on the host. +pub const MAX_VPCI_TDISP_COMMAND_SIZE: usize = 35000; diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index 4c3b1af7fc..3f16cb6421 100644 --- a/vm/devices/tdisp/Cargo.toml +++ b/vm/devices/tdisp/Cargo.toml @@ -8,6 +8,8 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true +bitfield-struct.workspace = true +open_enum.workspace = true parking_lot.workspace = true thiserror.workspace = true prost.workspace = true diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs new file mode 100644 index 0000000000..de0feaecb8 --- /dev/null +++ b/vm/devices/tdisp/src/command.rs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::TdispGuestOperationError; +use crate::TdispTdiState; +use open_enum::open_enum; +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, +} + +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, + + /// Request the device's TDISP interface information. + GET_DEVICE_INTERFACE_INFO = 1, + + /// Bind the device to the current partition and transition to Locked. + BIND = 2, + + /// Get the TDI report for attestation from the host for the device. + GET_TDI_REPORT = 3, + + /// Transition the device to the Start state after successful attestation. + START_TDI = 4, + + /// Unbind the device from the partition, reverting it back to the Unlocked state. + UNBIND = 5, + } +} + +/// 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), +} + +/// 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), +} + +/// 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, +} + +/// 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, +} + +/// 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, + // The remainder of the `report_buffer_size` bytes to follow are the bytes of the returned report. +} diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index de4d97dec6..d97576a5de 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -54,7 +54,16 @@ use tdisp_proto::TdispTdiState; use tdisp_proto::guest_to_host_command::Command; use tdisp_proto::guest_to_host_response::Response; use thiserror::Error; -use tracing::instrument; + +use crate::command::TdispCommandRequestPayload; +use crate::command::TdispCommandResponseGetTdiReport; +use crate::devicereport::TdispReportType; + +/// 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; From 5057213f776d99f479442e260692073389ffbc4c Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 14:58:44 -0800 Subject: [PATCH 02/32] add back cargo.toml --- Cargo.lock | 10 +++++++++- Cargo.toml | 1 + openhcl/openhcl_tdisp/Cargo.toml | 16 ++++++++++++++++ openhcl/openhcl_tdisp/src/lib.rs | 1 - 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 openhcl/openhcl_tdisp/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index b22cb7525d..f014e15544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5028,6 +5028,15 @@ dependencies = [ "vmcore", ] +[[package]] +name = "openhcl_tdisp" +version = "0.0.0" +dependencies = [ + "anyhow", + "inspect", + "tdisp", +] + [[package]] name = "openssl" version = "0.10.73" @@ -7391,7 +7400,6 @@ dependencies = [ "inspect", "prost", "prost-build", - "zerocopy 0.8.27", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 821ce5c58d..e6a7910812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,6 +192,7 @@ underhill_init = { path = "openhcl/underhill_init" } underhill_mem = { path = "openhcl/underhill_mem" } openhcl_attestation_protocol = { path = "openhcl/openhcl_attestation_protocol" } openvmm_hcl_resources = { path = "openhcl/openvmm_hcl_resources" } +openhcl_tdisp = { path = "openhcl/openhcl_tdisp" } underhill_threadpool = { path = "openhcl/underhill_threadpool" } virt_mshv_vtl = { path = "openhcl/virt_mshv_vtl" } diff --git a/openhcl/openhcl_tdisp/Cargo.toml b/openhcl/openhcl_tdisp/Cargo.toml new file mode 100644 index 0000000000..926cecaf0f --- /dev/null +++ b/openhcl/openhcl_tdisp/Cargo.toml @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "openhcl_tdisp" +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/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index fcf4609795..74e4342028 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -13,7 +13,6 @@ pub use tdisp::TdispCommandId; use tdisp::TdispGuestUnbindReason; use tdisp::TdispReportType; 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 /// implementations to send TDISP commands to the host through a backing interface From 6dde18bfd0c100295ced54d55e6056bf3d84a960 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:04:41 -0800 Subject: [PATCH 03/32] refactor openhcl_tdisp to new types from proto crate --- Cargo.lock | 3 +- openhcl/openhcl_tdisp/Cargo.toml | 1 + openhcl/openhcl_tdisp/src/lib.rs | 14 +-- vm/devices/tdisp/Cargo.toml | 2 - vm/devices/tdisp/src/command.rs | 141 ------------------------------- vm/devices/tdisp/src/lib.rs | 11 +-- 6 files changed, 10 insertions(+), 162 deletions(-) delete mode 100644 vm/devices/tdisp/src/command.rs diff --git a/Cargo.lock b/Cargo.lock index f014e15544..6066900acb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5035,6 +5035,7 @@ dependencies = [ "anyhow", "inspect", "tdisp", + "tdisp_proto", ] [[package]] @@ -7384,8 +7385,6 @@ name = "tdisp" version = "0.0.0" dependencies = [ "anyhow", - "bitfield-struct 0.11.0", - "open_enum", "parking_lot", "prost", "tdisp_proto", diff --git a/openhcl/openhcl_tdisp/Cargo.toml b/openhcl/openhcl_tdisp/Cargo.toml index 926cecaf0f..8bb66f5062 100644 --- a/openhcl/openhcl_tdisp/Cargo.toml +++ b/openhcl/openhcl_tdisp/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true [dependencies] inspect.workspace = true tdisp.workspace = true +tdisp_proto.workspace = true anyhow.workspace = true diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 74e4342028..09368f2432 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -7,12 +7,12 @@ //! See: `vm/devices/tdisp` for more information. use std::future::Future; -use tdisp::GuestToHostCommand; -use tdisp::GuestToHostResponse; -pub use tdisp::TdispCommandId; -use tdisp::TdispGuestUnbindReason; -use tdisp::TdispReportType; -use tdisp::devicereport::TdiReportStruct; +pub use tdisp_proto::GuestToHostCommand; +pub use tdisp_proto::GuestToHostResponse; +pub use tdisp_proto::TdiReportStruct; +pub use tdisp_proto::TdispDeviceInterfaceInfo; +pub use tdisp_proto::TdispGuestUnbindReason; +pub use tdisp_proto::TdispReportType; /// Represents a TDISP device assigned to a guest partition. This trait allows /// implementations to send TDISP commands to the host through a backing interface @@ -28,7 +28,7 @@ pub trait TdispVirtualDeviceInterface: Send + Sync { /// Get the TDISP interface info for the device. fn tdisp_get_device_interface_info( &self, - ) -> impl Future> + Send; + ) -> 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 diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index 3f16cb6421..4c3b1af7fc 100644 --- a/vm/devices/tdisp/Cargo.toml +++ b/vm/devices/tdisp/Cargo.toml @@ -8,8 +8,6 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true -bitfield-struct.workspace = true -open_enum.workspace = true parking_lot.workspace = true thiserror.workspace = true prost.workspace = true diff --git a/vm/devices/tdisp/src/command.rs b/vm/devices/tdisp/src/command.rs deleted file mode 100644 index de0feaecb8..0000000000 --- a/vm/devices/tdisp/src/command.rs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::TdispGuestOperationError; -use crate::TdispTdiState; -use open_enum::open_enum; -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, -} - -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, - - /// Request the device's TDISP interface information. - GET_DEVICE_INTERFACE_INFO = 1, - - /// Bind the device to the current partition and transition to Locked. - BIND = 2, - - /// Get the TDI report for attestation from the host for the device. - GET_TDI_REPORT = 3, - - /// Transition the device to the Start state after successful attestation. - START_TDI = 4, - - /// Unbind the device from the partition, reverting it back to the Unlocked state. - UNBIND = 5, - } -} - -/// 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), -} - -/// 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), -} - -/// 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, -} - -/// 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, -} - -/// 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, - // The remainder of the `report_buffer_size` bytes to follow are the bytes of the returned report. -} diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index d97576a5de..de4d97dec6 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -54,16 +54,7 @@ use tdisp_proto::TdispTdiState; use tdisp_proto::guest_to_host_command::Command; use tdisp_proto::guest_to_host_response::Response; use thiserror::Error; - -use crate::command::TdispCommandRequestPayload; -use crate::command::TdispCommandResponseGetTdiReport; -use crate::devicereport::TdispReportType; - -/// 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; +use tracing::instrument; /// Callback for receiving TDISP commands from the guest. pub type TdispCommandCallback = dyn Fn(&GuestToHostCommand) -> anyhow::Result<()> + Send + Sync; From 72341dd992f7d7a0032244e6370aa1d7d07f2582 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:43:02 -0800 Subject: [PATCH 04/32] refactor tdisp_proto to export needed deps into openhcl_tdisp --- openhcl/openhcl_tdisp/src/lib.rs | 31 ++++++ vm/devices/tdisp/Cargo.toml | 1 - vm/devices/tdisp/src/lib.rs | 96 +------------------ vm/devices/tdisp_proto/Cargo.toml | 2 +- vm/devices/tdisp_proto/src/errorcode.rs | 96 +++++++++++++++++++ vm/devices/tdisp_proto/src/lib.rs | 122 ++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 97 deletions(-) create mode 100644 vm/devices/tdisp_proto/src/errorcode.rs diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 09368f2432..aebe968c92 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -7,13 +7,29 @@ //! See: `vm/devices/tdisp` for more information. use std::future::Future; + +// Re-export the TDISP protocol types necessary for OpenbHCL from top level tdisp crates +// to avoid a direct dependency on tdisp_proto and tdisp. +pub use tdisp::TdispGuestOperationError; +pub use tdisp::serialize_proto::deserialize_command; +pub use tdisp::serialize_proto::deserialize_response; +pub use tdisp::serialize_proto::serialize_command; +pub use tdisp::serialize_proto::serialize_response; pub use tdisp_proto::GuestToHostCommand; +pub use tdisp_proto::GuestToHostCommandExt; pub use tdisp_proto::GuestToHostResponse; +pub use tdisp_proto::GuestToHostResponseExt; pub use tdisp_proto::TdiReportStruct; +pub use tdisp_proto::TdispCommandRequestGetDeviceInterfaceInfo; +pub use tdisp_proto::TdispCommandResponseGetDeviceInterfaceInfo; pub use tdisp_proto::TdispDeviceInterfaceInfo; +pub use tdisp_proto::TdispGuestOperationErrorCode; +pub use tdisp_proto::TdispGuestProtocolType; pub use tdisp_proto::TdispGuestUnbindReason; pub use tdisp_proto::TdispReportType; +use tdisp_proto::guest_to_host_command::Command; + /// Represents a TDISP device assigned to a guest partition. This trait allows /// implementations to send TDISP commands to the host through a backing interface /// such as a VPCI channel. @@ -59,3 +75,18 @@ pub trait TdispVirtualDeviceInterface: Send + Sync { reason: TdispGuestUnbindReason, ) -> impl Future> + Send; } + +/// Creates a [`GuestToHostCommand`] for the `GetDeviceInterfaceInfo` command. +pub fn make_get_device_interface_info_command( + device_id: u64, + guest_protocol_type: TdispGuestProtocolType, +) -> GuestToHostCommand { + GuestToHostCommand { + device_id, + command: Some(Command::GetDeviceInterfaceInfo( + TdispCommandRequestGetDeviceInterfaceInfo { + guest_protocol_type: guest_protocol_type as i32, + }, + )), + } +} diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index 4c3b1af7fc..f7ca7f952b 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 parking_lot.workspace = true -thiserror.workspace = true prost.workspace = true tracing.workspace = true tdisp_proto.workspace = true diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index de4d97dec6..cfbf978a56 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -46,6 +46,7 @@ use tdisp_proto::TdispCommandResponseGetTdiReport; use tdisp_proto::TdispCommandResponseStartTdi; use tdisp_proto::TdispCommandResponseUnbind; use tdisp_proto::TdispDeviceInterfaceInfo; +use tdisp_proto::TdispGuestOperationError; use tdisp_proto::TdispGuestOperationErrorCode; use tdisp_proto::TdispGuestProtocolType; use tdisp_proto::TdispGuestUnbindReason; @@ -53,7 +54,6 @@ use tdisp_proto::TdispReportType; use tdisp_proto::TdispTdiState; use tdisp_proto::guest_to_host_command::Command; use tdisp_proto::guest_to_host_response::Response; -use thiserror::Error; use tracing::instrument; /// Callback for receiving TDISP commands from the guest. @@ -444,100 +444,6 @@ impl TdispHostStateMachine { } } -/// Error returned by TDISP operations dispatched by the guest. -#[derive(Error, Debug, Copy, Clone)] -#[expect(missing_docs)] -pub enum TdispGuestOperationError { - #[error("unknown error code")] - Unknown, - #[error("the operation was successful")] - Success, - #[error("the requested guest protocol type was not valid for this host")] - InvalidGuestProtocolRequest, - #[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 TdispGuestOperationError { - fn from(err_code: TdispGuestOperationErrorCode) -> Self { - match err_code { - TdispGuestOperationErrorCode::Unknown => TdispGuestOperationError::Unknown, - TdispGuestOperationErrorCode::Success => TdispGuestOperationError::Success, - TdispGuestOperationErrorCode::InvalidGuestProtocolRequest => { - TdispGuestOperationError::InvalidGuestProtocolRequest - } - TdispGuestOperationErrorCode::InvalidDeviceState => { - TdispGuestOperationError::InvalidDeviceState - } - TdispGuestOperationErrorCode::InvalidGuestUnbindReason => { - TdispGuestOperationError::InvalidGuestUnbindReason - } - TdispGuestOperationErrorCode::InvalidGuestCommandId => { - TdispGuestOperationError::InvalidGuestCommandId - } - TdispGuestOperationErrorCode::NotImplemented => { - TdispGuestOperationError::NotImplemented - } - TdispGuestOperationErrorCode::HostFailedToProcessCommand => { - TdispGuestOperationError::HostFailedToProcessCommand - } - TdispGuestOperationErrorCode::InvalidGuestAttestationReportState => { - TdispGuestOperationError::InvalidGuestAttestationReportState - } - TdispGuestOperationErrorCode::InvalidGuestAttestationReportType => { - TdispGuestOperationError::InvalidGuestAttestationReportType - } - } - } -} - -impl From for TdispGuestOperationErrorCode { - fn from(err: TdispGuestOperationError) -> Self { - match err { - TdispGuestOperationError::Unknown => TdispGuestOperationErrorCode::Unknown, - TdispGuestOperationError::Success => TdispGuestOperationErrorCode::Success, - TdispGuestOperationError::InvalidGuestProtocolRequest => { - TdispGuestOperationErrorCode::InvalidGuestProtocolRequest - } - TdispGuestOperationError::InvalidDeviceState => { - TdispGuestOperationErrorCode::InvalidDeviceState - } - TdispGuestOperationError::InvalidGuestUnbindReason => { - TdispGuestOperationErrorCode::InvalidGuestUnbindReason - } - TdispGuestOperationError::InvalidGuestCommandId => { - TdispGuestOperationErrorCode::InvalidGuestCommandId - } - TdispGuestOperationError::NotImplemented => { - TdispGuestOperationErrorCode::NotImplemented - } - TdispGuestOperationError::HostFailedToProcessCommand => { - TdispGuestOperationErrorCode::HostFailedToProcessCommand - } - TdispGuestOperationError::InvalidGuestAttestationReportState => { - TdispGuestOperationErrorCode::InvalidGuestAttestationReportState - } - TdispGuestOperationError::InvalidGuestAttestationReportType => { - TdispGuestOperationErrorCode::InvalidGuestAttestationReportType - } - } - } -} - /// Represents an interface by which guest commands can be dispatched to a /// 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. diff --git a/vm/devices/tdisp_proto/Cargo.toml b/vm/devices/tdisp_proto/Cargo.toml index 9dcd1be369..deb70ddff3 100644 --- a/vm/devices/tdisp_proto/Cargo.toml +++ b/vm/devices/tdisp_proto/Cargo.toml @@ -8,7 +8,7 @@ rust-version.workspace = true [dependencies] inspect.workspace = true - +thiserror.workspace = true prost.workspace = true [build-dependencies] diff --git a/vm/devices/tdisp_proto/src/errorcode.rs b/vm/devices/tdisp_proto/src/errorcode.rs new file mode 100644 index 0000000000..2663d241f1 --- /dev/null +++ b/vm/devices/tdisp_proto/src/errorcode.rs @@ -0,0 +1,96 @@ +use crate::TdispGuestOperationErrorCode; +use thiserror::Error; + +/// Error returned by TDISP operations dispatched by the guest. +#[derive(Error, Debug, Copy, Clone)] +#[expect(missing_docs)] +pub enum TdispGuestOperationError { + #[error("unknown error code")] + Unknown, + #[error("the operation was successful")] + Success, + #[error("the requested guest protocol type was not valid for this host")] + InvalidGuestProtocolRequest, + #[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 TdispGuestOperationError { + fn from(err_code: TdispGuestOperationErrorCode) -> Self { + match err_code { + TdispGuestOperationErrorCode::Unknown => TdispGuestOperationError::Unknown, + TdispGuestOperationErrorCode::Success => TdispGuestOperationError::Success, + TdispGuestOperationErrorCode::InvalidGuestProtocolRequest => { + TdispGuestOperationError::InvalidGuestProtocolRequest + } + TdispGuestOperationErrorCode::InvalidDeviceState => { + TdispGuestOperationError::InvalidDeviceState + } + TdispGuestOperationErrorCode::InvalidGuestUnbindReason => { + TdispGuestOperationError::InvalidGuestUnbindReason + } + TdispGuestOperationErrorCode::InvalidGuestCommandId => { + TdispGuestOperationError::InvalidGuestCommandId + } + TdispGuestOperationErrorCode::NotImplemented => { + TdispGuestOperationError::NotImplemented + } + TdispGuestOperationErrorCode::HostFailedToProcessCommand => { + TdispGuestOperationError::HostFailedToProcessCommand + } + TdispGuestOperationErrorCode::InvalidGuestAttestationReportState => { + TdispGuestOperationError::InvalidGuestAttestationReportState + } + TdispGuestOperationErrorCode::InvalidGuestAttestationReportType => { + TdispGuestOperationError::InvalidGuestAttestationReportType + } + } + } +} + +impl From for TdispGuestOperationErrorCode { + fn from(err: TdispGuestOperationError) -> Self { + match err { + TdispGuestOperationError::Unknown => TdispGuestOperationErrorCode::Unknown, + TdispGuestOperationError::Success => TdispGuestOperationErrorCode::Success, + TdispGuestOperationError::InvalidGuestProtocolRequest => { + TdispGuestOperationErrorCode::InvalidGuestProtocolRequest + } + TdispGuestOperationError::InvalidDeviceState => { + TdispGuestOperationErrorCode::InvalidDeviceState + } + TdispGuestOperationError::InvalidGuestUnbindReason => { + TdispGuestOperationErrorCode::InvalidGuestUnbindReason + } + TdispGuestOperationError::InvalidGuestCommandId => { + TdispGuestOperationErrorCode::InvalidGuestCommandId + } + TdispGuestOperationError::NotImplemented => { + TdispGuestOperationErrorCode::NotImplemented + } + TdispGuestOperationError::HostFailedToProcessCommand => { + TdispGuestOperationErrorCode::HostFailedToProcessCommand + } + TdispGuestOperationError::InvalidGuestAttestationReportState => { + TdispGuestOperationErrorCode::InvalidGuestAttestationReportState + } + TdispGuestOperationError::InvalidGuestAttestationReportType => { + TdispGuestOperationErrorCode::InvalidGuestAttestationReportType + } + } + } +} diff --git a/vm/devices/tdisp_proto/src/lib.rs b/vm/devices/tdisp_proto/src/lib.rs index b2c2ef16a3..2e9be77008 100644 --- a/vm/devices/tdisp_proto/src/lib.rs +++ b/vm/devices/tdisp_proto/src/lib.rs @@ -8,7 +8,129 @@ // Crates used by generated code. Reference them explicitly to ensure that // automated tools do not remove them. +use crate::guest_to_host_command::Command; +use crate::guest_to_host_response::Response; use inspect as _; use prost as _; +mod errorcode; +pub use errorcode::*; include!(concat!(env!("OUT_DIR"), "/tdisp.rs")); + +pub trait GuestToHostCommandExt { + /// Returns the command type name of the command. + fn get_type_name(&self) -> Option<&str>; +} + +impl GuestToHostCommandExt for GuestToHostCommand { + fn get_type_name(&self) -> Option<&str> { + match self.command { + Some(Command::GetDeviceInterfaceInfo(_)) => Some("GetDeviceInterfaceInfo"), + Some(Command::Bind(_)) => Some("Bind"), + Some(Command::StartTdi(_)) => Some("StartTdi"), + Some(Command::Unbind(_)) => Some("Unbind"), + Some(Command::GetTdiReport(_)) => Some("GetTdiReport"), + None => None, + } + } +} + +/// Implemented by each response payload type so that [`GuestToHostResponseExt::get_response`] +/// can extract it generically from a [`Response`] oneof variant. +pub trait GuestToHostResponseVariant: Sized { + fn from_response_variant(response: Response) -> Option; +} + +impl GuestToHostResponseVariant for TdispCommandResponseGetDeviceInterfaceInfo { + fn from_response_variant(response: Response) -> Option { + match response { + Response::GetDeviceInterfaceInfo(r) => Some(r), + _ => None, + } + } +} + +impl GuestToHostResponseVariant for TdispCommandResponseBind { + fn from_response_variant(response: Response) -> Option { + match response { + Response::Bind(r) => Some(r), + _ => None, + } + } +} + +impl GuestToHostResponseVariant for TdispCommandResponseGetTdiReport { + fn from_response_variant(response: Response) -> Option { + match response { + Response::GetTdiReport(r) => Some(r), + _ => None, + } + } +} + +impl GuestToHostResponseVariant for TdispCommandResponseStartTdi { + fn from_response_variant(response: Response) -> Option { + match response { + Response::StartTdi(r) => Some(r), + _ => None, + } + } +} + +impl GuestToHostResponseVariant for TdispCommandResponseUnbind { + fn from_response_variant(response: Response) -> Option { + match response { + Response::Unbind(r) => Some(r), + _ => None, + } + } +} + +/// Provides helper methods for common operations on [`GuestToHostResponse`]. +pub trait GuestToHostResponseExt { + /// Returns the error code of the response, if any. + fn get_error_code(&self) -> Option; + + /// Returns the packet type name of the response. + fn get_type_name(&self) -> Option<&str>; + + /// Consumes the response and returns the inner payload if the result is + /// [`TdispGuestOperationError::Success`] and the oneof variant matches `T`. + /// Returns the error code otherwise. + /// + /// # Example + /// ```ignore + /// let bind = resp.get_response::()?; + /// ``` + fn get_response(self) -> Result; +} + +impl GuestToHostResponseExt for GuestToHostResponse { + fn get_error_code(&self) -> Option { + TdispGuestOperationErrorCode::from_i32(self.result) + } + + fn get_type_name(&self) -> Option<&str> { + match self.response { + Some(Response::GetDeviceInterfaceInfo(_)) => Some("GetDeviceInterfaceInfo"), + Some(Response::Bind(_)) => Some("Bind"), + Some(Response::StartTdi(_)) => Some("StartTdi"), + Some(Response::Unbind(_)) => Some("Unbind"), + Some(Response::GetTdiReport(_)) => Some("GetTdiReport"), + None => None, + } + } + + fn get_response(self) -> Result { + match self.get_error_code() { + Some(TdispGuestOperationErrorCode::Success) => { + match self.response.and_then(T::from_response_variant) { + Some(r) => Ok(r), + None => Err(TdispGuestOperationErrorCode::Unknown.into()), + } + } + Some(err) => Err(err.into()), + None => Err(TdispGuestOperationErrorCode::Unknown.into()), + } + } +} From 6f485c23144806e7e08445d04a26cd7fd84c1bc4 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:43:02 -0800 Subject: [PATCH 05/32] Add device report deserialization code back --- vm/devices/tdisp/src/devicereport.rs | 136 +++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 vm/devices/tdisp/src/devicereport.rs diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs new file mode 100644 index 0000000000..6c4c5943e5 --- /dev/null +++ b/vm/devices/tdisp/src/devicereport.rs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use bitfield_struct::bitfield; +use zerocopy::FromBytes; +use zerocopy::Immutable; +use zerocopy::KnownLayout; + +/// 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, + _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 { + /// 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, +} + +/// 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(), + }) +} From 5c7a9061a7bb1977b3757956e655e8a95eeddedb Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:43:02 -0800 Subject: [PATCH 06/32] Remove device report structures from proto because these will still parsed from C structs --- vm/devices/tdisp_proto/src/tdisp.proto | 84 +------------------------- 1 file changed, 1 insertion(+), 83 deletions(-) diff --git a/vm/devices/tdisp_proto/src/tdisp.proto b/vm/devices/tdisp_proto/src/tdisp.proto index eda73ae15d..dcdb106ead 100644 --- a/vm/devices/tdisp_proto/src/tdisp.proto +++ b/vm/devices/tdisp_proto/src/tdisp.proto @@ -237,86 +237,4 @@ message TdispDeviceInterfaceInfo { // Device ID used to communicate with firmware for this particular device. uint64 tdisp_device_id = 3; -} - -// ---------------------------------------------------------------------------- -// Device report structures -// ---------------------------------------------------------------------------- - -// PCI Express Base Specification Revision 6.3 Section 11.3.11 -// DEVICE_INTERFACE_REPORT interface info flags. -message TdispTdiReportInterfaceInfo { - // When true, device firmware updates are not permitted while in - // CONFIG_LOCKED or RUN. - bool firmware_update_allowed = 1; - - // TDI generates DMA requests without PASID. - bool generate_dma_without_pasid = 2; - - // TDI generates DMA requests with PASID. - bool generate_dma_with_pasid = 3; - - // ATS supported and enabled for the TDI. - bool ats_support_enabled = 4; - - // PRS supported and enabled for the TDI. - bool prs_support_enabled = 5; -} - -// PCI Express Base Specification Revision 6.3 Section 11.3.11 -// DEVICE_INTERFACE_REPORT MMIO range attribute flags. -message TdispTdiReportMmioFlags { - // MSI-X Table – if the range maps MSI-X table. - bool range_maps_msix_table = 1; - - // MSI-X PBA – if the range maps MSI-X PBA. - bool range_maps_msix_pba = 2; - - // IS_NON_TEE_MEM – must be true if the range is non-TEE memory. - bool is_non_tee_mem = 3; - - // IS_MEM_ATTR_UPDATABLE – must be true if the attributes of this range are - // updatable using SET_MMIO_ATTRIBUTE_REQUEST. - bool is_mem_attr_updatable = 4; -} - -// PCI Express Base Specification Revision 6.3 Section 11.3.11 -// DEVICE_INTERFACE_REPORT per-MMIO-range info. -// Note: range_id is uint32 here because protobuf has no uint16; callers -// should treat it as a 16-bit value. -message TdispTdiReportMmioInterfaceInfo { - // First 4K page with offset added. - uint64 first_4k_page_offset = 1; - - // Number of 4K pages in this range. - uint32 num_4k_pages = 2; - - // Range attributes. - TdispTdiReportMmioFlags flags = 3; - - // Range ID – a device-specific identifier for the specified range. - // Stored as uint32 because protobuf has no uint16; valid range is 0–65535. - uint32 range_id = 4; -} - -// The deserialized form of a TDI interface report. -// Note: msi_x_message_control and lnr_control are uint32 here because -// protobuf has no uint16; callers should treat them as 16-bit values. -message TdiReportStruct { - // Interface info flags. - TdispTdiReportInterfaceInfo interface_info = 1; - - // MSI-X capability message control register state. - // Stored as uint32; valid range is 0–65535. - uint32 msi_x_message_control = 2; - - // LNR control register from LN Requester Extended Capability. - // Stored as uint32; valid range is 0–65535. - uint32 lnr_control = 3; - - // TPH Requester Control Register from the TPH Requester Extended Capability. - uint32 tph_control = 4; - - // MMIO range info for each range reported by the TDI. - repeated TdispTdiReportMmioInterfaceInfo mmio_interface_info = 5; -} +} \ No newline at end of file From 6237620c588bbc40f93b9f62321d96e800a9b6ef Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:43:02 -0800 Subject: [PATCH 07/32] add helpers to get responses, error codes, and making new packets --- Cargo.lock | 5 +++- openhcl/openhcl_tdisp/src/lib.rs | 49 ++++++++++++++++++++++++++++++- vm/devices/tdisp/Cargo.toml | 3 ++ vm/devices/tdisp/src/lib.rs | 5 +++- vm/devices/tdisp_proto/src/lib.rs | 1 + 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6066900acb..1e9ea36a4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7385,11 +7385,13 @@ name = "tdisp" version = "0.0.0" dependencies = [ "anyhow", + "bitfield-struct 0.11.0", "parking_lot", "prost", + "static_assertions", "tdisp_proto", - "thiserror 2.0.16", "tracing", + "zerocopy 0.8.27", ] [[package]] @@ -7399,6 +7401,7 @@ dependencies = [ "inspect", "prost", "prost-build", + "thiserror 2.0.16", ] [[package]] diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index aebe968c92..c67e2c9763 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -11,6 +11,7 @@ use std::future::Future; // Re-export the TDISP protocol types necessary for OpenbHCL from top level tdisp crates // to avoid a direct dependency on tdisp_proto and tdisp. pub use tdisp::TdispGuestOperationError; +pub use tdisp::devicereport::TdiReportStruct; pub use tdisp::serialize_proto::deserialize_command; pub use tdisp::serialize_proto::deserialize_response; pub use tdisp::serialize_proto::serialize_command; @@ -19,15 +20,22 @@ pub use tdisp_proto::GuestToHostCommand; pub use tdisp_proto::GuestToHostCommandExt; pub use tdisp_proto::GuestToHostResponse; pub use tdisp_proto::GuestToHostResponseExt; -pub use tdisp_proto::TdiReportStruct; pub use tdisp_proto::TdispCommandRequestGetDeviceInterfaceInfo; +pub use tdisp_proto::TdispCommandResponseBind; pub use tdisp_proto::TdispCommandResponseGetDeviceInterfaceInfo; +pub use tdisp_proto::TdispCommandResponseGetTdiReport; +pub use tdisp_proto::TdispCommandResponseStartTdi; +pub use tdisp_proto::TdispCommandResponseUnbind; pub use tdisp_proto::TdispDeviceInterfaceInfo; pub use tdisp_proto::TdispGuestOperationErrorCode; pub use tdisp_proto::TdispGuestProtocolType; pub use tdisp_proto::TdispGuestUnbindReason; pub use tdisp_proto::TdispReportType; +use tdisp_proto::TdispCommandRequestBind; +use tdisp_proto::TdispCommandRequestGetTdiReport; +use tdisp_proto::TdispCommandRequestStartTdi; +use tdisp_proto::TdispCommandRequestUnbind; use tdisp_proto::guest_to_host_command::Command; /// Represents a TDISP device assigned to a guest partition. This trait allows @@ -90,3 +98,42 @@ pub fn make_get_device_interface_info_command( )), } } + +/// Creates a [`GuestToHostCommand`] for the `Bind` command. +pub fn make_bind_command(device_id: u64) -> GuestToHostCommand { + GuestToHostCommand { + device_id, + command: Some(Command::Bind(TdispCommandRequestBind {})), + } +} + +/// Creates a [`GuestToHostCommand`] for the `StartTdi` command. +pub fn make_start_tdi_command(device_id: u64) -> GuestToHostCommand { + GuestToHostCommand { + device_id, + command: Some(Command::StartTdi(TdispCommandRequestStartTdi {})), + } +} + +/// Creates a [`GuestToHostCommand`] for the `GetTdiReport` command. +pub fn make_get_tdi_report_command( + device_id: u64, + report_type: TdispReportType, +) -> GuestToHostCommand { + GuestToHostCommand { + device_id, + command: Some(Command::GetTdiReport(TdispCommandRequestGetTdiReport { + report_type: report_type as i32, + })), + } +} + +/// Creates a [`GuestToHostCommand`] for the `Unbind` command. +pub fn make_unbind_command(device_id: u64, reason: TdispGuestUnbindReason) -> GuestToHostCommand { + GuestToHostCommand { + device_id, + command: Some(Command::Unbind(TdispCommandRequestUnbind { + unbind_reason: reason as i32, + })), + } +} diff --git a/vm/devices/tdisp/Cargo.toml b/vm/devices/tdisp/Cargo.toml index f7ca7f952b..f6040673b8 100644 --- a/vm/devices/tdisp/Cargo.toml +++ b/vm/devices/tdisp/Cargo.toml @@ -12,6 +12,9 @@ parking_lot.workspace = true prost.workspace = true tracing.workspace = true tdisp_proto.workspace = true +bitfield-struct.workspace = true +zerocopy.workspace = true +static_assertions.workspace = true [lints] workspace = true diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index cfbf978a56..2da909f6b9 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -32,6 +32,9 @@ /// Protobuf serialization of guest commands and responses. pub mod serialize_proto; +/// Serialization code from PCI standard structures reported from the TDISP device directly. +pub mod devicereport; + #[cfg(test)] mod tests; @@ -46,7 +49,7 @@ use tdisp_proto::TdispCommandResponseGetTdiReport; use tdisp_proto::TdispCommandResponseStartTdi; use tdisp_proto::TdispCommandResponseUnbind; use tdisp_proto::TdispDeviceInterfaceInfo; -use tdisp_proto::TdispGuestOperationError; +pub use tdisp_proto::TdispGuestOperationError; use tdisp_proto::TdispGuestOperationErrorCode; use tdisp_proto::TdispGuestProtocolType; use tdisp_proto::TdispGuestUnbindReason; diff --git a/vm/devices/tdisp_proto/src/lib.rs b/vm/devices/tdisp_proto/src/lib.rs index 2e9be77008..cdaa56eda3 100644 --- a/vm/devices/tdisp_proto/src/lib.rs +++ b/vm/devices/tdisp_proto/src/lib.rs @@ -5,6 +5,7 @@ #![expect(missing_docs)] #![forbid(unsafe_code)] +#![allow(unused_qualifications)] // Crates used by generated code. Reference them explicitly to ensure that // automated tools do not remove them. From c569f4187b2e9cad18c68fa167faccd172118b99 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 15:43:02 -0800 Subject: [PATCH 08/32] add vpci_client interfaces to dispatch proto commands --- vm/devices/pci/vpci_client/src/lib.rs | 182 ++++++++++++-------------- 1 file changed, 84 insertions(+), 98 deletions(-) diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 961e49e24a..366796c637 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -19,6 +19,20 @@ use inspect::Inspect; use inspect::InspectMut; use mesh::rpc::FailableRpc; use mesh::rpc::RpcSend; +use openhcl_tdisp::GuestToHostCommand; +use openhcl_tdisp::GuestToHostCommandExt; +use openhcl_tdisp::GuestToHostResponse; +use openhcl_tdisp::GuestToHostResponseExt; +use openhcl_tdisp::TdispCommandResponseBind; +use openhcl_tdisp::TdispCommandResponseGetDeviceInterfaceInfo; +use openhcl_tdisp::TdispCommandResponseGetTdiReport; +use openhcl_tdisp::TdispCommandResponseStartTdi; +use openhcl_tdisp::TdispCommandResponseUnbind; +use openhcl_tdisp::TdispDeviceInterfaceInfo; +use openhcl_tdisp::TdispGuestOperationErrorCode; +use openhcl_tdisp::TdispGuestProtocolType; +use openhcl_tdisp::TdispGuestUnbindReason; +use openhcl_tdisp::TdispReportType; use openhcl_tdisp::TdispVirtualDeviceInterface; use pal_async::task::Spawn; use pal_async::task::Task; @@ -29,18 +43,7 @@ use pci_core::spec::hwid::HardwareIds; use std::pin::Pin; use std::sync::Arc; use std::task::Poll; -use tdisp::GuestToHostCommand; -use tdisp::GuestToHostResponse; -use tdisp::TdispCommandId; -use tdisp::TdispCommandResponsePayload; -use tdisp::TdispGuestOperationError; -use tdisp::TdispGuestUnbindReason; -use tdisp::TdispReportType; -use tdisp::command::TdispCommandRequestGetTdiReport; -use tdisp::command::TdispCommandRequestPayload; -use tdisp::command::TdispCommandRequestUnbind; use tdisp::devicereport::TdiReportStruct; -use tdisp::serialize::SerializePacket; use thiserror::Error; use vmbus_async::queue::IncomingPacket; use vmbus_async::queue::OutgoingPacket; @@ -595,7 +598,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { &self, payload: GuestToHostCommand, ) -> Result { - let serialized = payload.serialize_to_bytes(); + let serialized = openhcl_tdisp::serialize_command(&payload); // Ensure that the length does not exceed the VMBUS maximum packet size. // This shouldn't be possible since the host should reject the command anyways, @@ -608,6 +611,8 @@ impl TdispVirtualDeviceInterface for VpciDevice { )); } + // Make a mesh call to send the VMBUS packet to the host and await a response + // packet from the host. let res = self .dev .req @@ -631,12 +636,13 @@ impl TdispVirtualDeviceInterface for VpciDevice { anyhow::anyhow!("failed to send tdisp command") })?; - match res.result { - TdispGuestOperationError::Success => Ok(res), + match res.get_error_code() { + Some(TdispGuestOperationErrorCode::Success) => Ok(res), _ => { let err_msg = format!( "send_tdisp_command {:?} failed because host responded with an error: {:?}", - payload.command_id, res.result + payload.get_type_name(), + res.result ); tracing::error!(msg = err_msg); @@ -645,57 +651,52 @@ impl TdispVirtualDeviceInterface for VpciDevice { } } - async fn tdisp_get_device_interface_info( - &self, - ) -> anyhow::Result { + async fn tdisp_get_device_interface_info(&self) -> anyhow::Result { + // TDISP TODO: Configure the correct guest protocol type when TDX support is added. + let target_protocol_type = TdispGuestProtocolType::AmdSevTioV10; + let res = self - .send_tdisp_command(GuestToHostCommand { - device_id: self.dev.id.slot.into_bits() as u64, - command_id: TdispCommandId::GET_DEVICE_INTERFACE_INFO, - payload: TdispCommandRequestPayload::None, - }) - .await; + .send_tdisp_command(openhcl_tdisp::make_get_device_interface_info_command( + self.dev.id.slot.into_bits() as u64, + target_protocol_type, + )) + .await?; - match res { - Ok(resp) => match resp.payload { - TdispCommandResponsePayload::GetDeviceInterfaceInfo(info) => Ok(info), - _ => Err(anyhow::anyhow!("unexpected response payload")), - }, - Err(e) => Err(e), + match res.get_response::() { + Ok(info) => Ok(info.interface_info.unwrap()), + Err(err) => Err(anyhow::anyhow!( + "error response in get_device_interface_info: {err}" + )), } } async fn tdisp_bind_interface(&self) -> anyhow::Result<()> { let res = self - .send_tdisp_command(GuestToHostCommand { - device_id: self.dev.id.slot.into_bits() as u64, - command_id: TdispCommandId::BIND, - payload: TdispCommandRequestPayload::None, - }) - .await; - match res { - Ok(resp) => match resp.payload { - TdispCommandResponsePayload::None => Ok(()), - _ => Err(anyhow::anyhow!("unexpected response payload")), - }, - Err(e) => Err(e), + .send_tdisp_command(openhcl_tdisp::make_bind_command( + self.dev.id.slot.into_bits() as u64, + )) + .await?; + + match res.get_response::() { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::anyhow!( + "error response in tdisp_bind_interface: {err}" + )), } } async fn tdisp_start_device(&self) -> anyhow::Result<()> { let res = self - .send_tdisp_command(GuestToHostCommand { - device_id: self.dev.id.slot.into_bits() as u64, - command_id: TdispCommandId::START_TDI, - payload: TdispCommandRequestPayload::None, - }) - .await; - match res { - Ok(resp) => match resp.payload { - TdispCommandResponsePayload::None => Ok(()), - _ => Err(anyhow::anyhow!("unexpected response payload")), - }, - Err(e) => Err(e), + .send_tdisp_command(openhcl_tdisp::make_start_tdi_command( + self.dev.id.slot.into_bits() as u64, + )) + .await?; + + match res.get_response::() { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::anyhow!( + "error response in tdisp_start_device: {err}" + )), } } @@ -704,69 +705,55 @@ impl TdispVirtualDeviceInterface for VpciDevice { report_type: &TdispReportType, ) -> anyhow::Result> { let res = self - .send_tdisp_command(GuestToHostCommand { - device_id: self.dev.id.slot.into_bits() as u64, - command_id: TdispCommandId::GET_TDI_REPORT, - payload: TdispCommandRequestPayload::GetTdiReport( - TdispCommandRequestGetTdiReport { - report_type: report_type.0, - }, - ), - }) - .await; + .send_tdisp_command(openhcl_tdisp::make_get_tdi_report_command( + self.dev.id.slot.into_bits() as u64, + *report_type, + )) + .await?; - match res { - Ok(resp) => match resp.payload { - TdispCommandResponsePayload::GetTdiReport(resp) => Ok(resp.report_buffer), - _ => Err(anyhow::anyhow!("unexpected response payload")), - }, - Err(e) => Err(e), + match res.get_response::() { + Ok(r) => Ok(r.report_buffer), + Err(err) => Err(anyhow::anyhow!( + "error response in tdisp_get_device_report: {err}" + )), } } async fn tdisp_get_tdi_report(&self) -> anyhow::Result { - let res = self - .tdisp_get_device_report(&TdispReportType::INTERFACE_REPORT) - .await; + let buffer = self + .tdisp_get_device_report(&TdispReportType::InterfaceReport) + .await + .context("failed to get TDI report")?; - let buffer = res.context("failed to get TDI report")?; tdisp::devicereport::deserialize_tdi_report(&buffer) .context("failed to deserialize TDI report from host") } async fn tdisp_get_tdi_device_id(&self) -> anyhow::Result { - let res = self - .tdisp_get_device_report(&TdispReportType::GUEST_DEVICE_ID) - .await; - - let buffer = res.context("failed to get TDI report")?; + let buffer = self + .tdisp_get_device_report(&TdispReportType::GuestDeviceId) + .await + .context("failed to get TDI device ID")?; // Ensure it's a u64 if buffer.len() != size_of::() { return Err(anyhow::anyhow!("unexpected buffer size for TDI device ID")); } - // Convert to u64 Ok(u64::from_le_bytes(buffer.try_into().unwrap())) } - /// Request to unbind the device and return to the Unlocked state. async fn tdisp_unbind(&self, reason: TdispGuestUnbindReason) -> anyhow::Result<()> { let res = self - .send_tdisp_command(GuestToHostCommand { - device_id: self.dev.id.slot.into_bits() as u64, - command_id: TdispCommandId::UNBIND, - payload: TdispCommandRequestPayload::Unbind(TdispCommandRequestUnbind { - unbind_reason: reason.0, - }), - }) - .await; - match res { - Ok(resp) => match resp.payload { - TdispCommandResponsePayload::None => Ok(()), - _ => Err(anyhow::anyhow!("unexpected response payload")), - }, - Err(e) => Err(e), + .send_tdisp_command(openhcl_tdisp::make_unbind_command( + self.dev.id.slot.into_bits() as u64, + reason, + )) + .await?; + + match res.get_response::() { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::anyhow!("error response in tdisp_unbind: {err}")), } } } @@ -1214,9 +1201,8 @@ impl WorkerState { .read(data.as_mut_slice()) .context("failed to read tdisp command data")?; - let host_response = - GuestToHostResponse::deserialize_from_bytes(data.as_slice()) - .context("failed to deserialize tdisp response"); + let host_response = openhcl_tdisp::deserialize_response(data.as_slice()) + .context("failed to deserialize tdisp response"); rpc.complete(host_response.map_err(mesh::error::RemoteError::new)); } else { From fa8cc97e87bb9a661e8bd7e4cc5e2c33e020c219 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 16:22:34 -0800 Subject: [PATCH 09/32] todo reword --- vm/devices/tdisp/src/devicereport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 6c4c5943e5..01bbb8c16f 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -123,7 +123,7 @@ pub fn deserialize_tdi_report(data: &[u8]) -> anyhow::Result { ) .map_err(|e| anyhow::anyhow!("failed to deserialize TDI report mmio_interface_info: {e:?}"))?; - // [TDISP TODO] Parse the vendor specific info + // TDISP TODO: Parse the vendor specific info let _vendor_specific_info = read_mmio_elems.1.to_vec(); Ok(TdiReportStruct { From b5291ac0f5262bcc23bb96799b0cc1dd494b8ea1 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 16:36:50 -0800 Subject: [PATCH 10/32] cleanup --- openhcl/openhcl_tdisp/src/lib.rs | 2 +- vm/devices/tdisp/src/devicereport.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index c67e2c9763..0739b1d30c 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -8,7 +8,7 @@ use std::future::Future; -// Re-export the TDISP protocol types necessary for OpenbHCL from top level tdisp crates +// Re-export the TDISP protocol types necessary for OpenHCL from top level tdisp crates // to avoid a direct dependency on tdisp_proto and tdisp. pub use tdisp::TdispGuestOperationError; pub use tdisp::devicereport::TdiReportStruct; diff --git a/vm/devices/tdisp/src/devicereport.rs b/vm/devices/tdisp/src/devicereport.rs index 01bbb8c16f..2f8926ace0 100644 --- a/vm/devices/tdisp/src/devicereport.rs +++ b/vm/devices/tdisp/src/devicereport.rs @@ -44,7 +44,7 @@ pub struct TdispTdiReportMmioFlags { /// 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 + /// 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, From bad4ed45a517fb006453a9d7b86ab2782bd7cc84 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 16:44:39 -0800 Subject: [PATCH 11/32] add explicit size check --- vm/devices/pci/vpci_client/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 366796c637..9ff8830a6b 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -54,6 +54,7 @@ use vmcore::vpci_msi::MapVpciInterrupt; use vmcore::vpci_msi::MsiAddressData; use vmcore::vpci_msi::RegisterInterruptError; use vpci_protocol as protocol; +use vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE; use vpci_protocol::SlotNumber; use zerocopy::FromBytes; use zerocopy::FromZeros; @@ -603,11 +604,11 @@ impl TdispVirtualDeviceInterface for VpciDevice { // Ensure that the length does not exceed the VMBUS maximum packet size. // This shouldn't be possible since the host should reject the command anyways, // but fail earlier for safety. - if serialized.len() > vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE { + if serialized.len() > MAX_VPCI_TDISP_COMMAND_SIZE { return Err(anyhow::anyhow!( "serialized TDISP command exceeds VMBUS maximum packet size ({} > {})", serialized.len(), - vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE + MAX_VPCI_TDISP_COMMAND_SIZE )); } @@ -1192,6 +1193,14 @@ impl WorkerState { .context("failed to read tdisp command header")?; let data_len = header.data_length as usize; + if data_len > MAX_VPCI_TDISP_COMMAND_SIZE { + rpc.fail(anyhow::anyhow!( + "Received TdispCommand data length exceeds maximum allowed: {} > {}", + data_len, + MAX_VPCI_TDISP_COMMAND_SIZE + )); + return Ok(()); + } // Allocate a mutable vector with the correct size let mut data: Vec = vec![0; data_len]; From e39a1b7dc383b92a7dd0c898da09b3017afc0060 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 23 Feb 2026 16:46:35 -0800 Subject: [PATCH 12/32] appease the copilot --- vm/devices/pci/vpci_client/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 9ff8830a6b..cd10c8e530 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -664,7 +664,9 @@ impl TdispVirtualDeviceInterface for VpciDevice { .await?; match res.get_response::() { - Ok(info) => Ok(info.interface_info.unwrap()), + Ok(info) => info.interface_info.ok_or_else(|| { + anyhow::anyhow!("missing interface_info after validation, this should never happen") + }), Err(err) => Err(anyhow::anyhow!( "error response in get_device_interface_info: {err}" )), From 99f9d599c44130df6e1bac264e5e47df8a1d65f4 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Tue, 24 Feb 2026 14:52:25 -0800 Subject: [PATCH 13/32] initial framework for tdisp vpci unit tests --- Cargo.lock | 2 + vm/chipset_device/Cargo.toml | 1 + vm/chipset_device/src/lib.rs | 7 ++ vm/devices/pci/vpci/Cargo.toml | 1 + vm/devices/pci/vpci/src/device.rs | 155 ++++++++++++++------------- vm/devices/tdisp/src/lib.rs | 37 ++++--- vm/devices/tdisp/src/test_helpers.rs | 49 +++++++++ vm/devices/tdisp/src/tests/mod.rs | 2 +- 8 files changed, 165 insertions(+), 89 deletions(-) create mode 100644 vm/devices/tdisp/src/test_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 1e9ea36a4c..6ed24e9e94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,6 +553,7 @@ version = "0.0.0" dependencies = [ "inspect", "mesh", + "tdisp", ] [[package]] @@ -9897,6 +9898,7 @@ dependencies = [ "parking_lot", "pci_core", "task_control", + "tdisp", "test_with_tracing", "thiserror 2.0.16", "tracelimit", diff --git a/vm/chipset_device/Cargo.toml b/vm/chipset_device/Cargo.toml index 298415d096..e948b36fbf 100644 --- a/vm/chipset_device/Cargo.toml +++ b/vm/chipset_device/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] mesh.workspace = true inspect.workspace = true +tdisp.workspace = true [lints] workspace = true diff --git a/vm/chipset_device/src/lib.rs b/vm/chipset_device/src/lib.rs index cf4ae4f420..853e9d11c8 100644 --- a/vm/chipset_device/src/lib.rs +++ b/vm/chipset_device/src/lib.rs @@ -61,6 +61,13 @@ pub trait ChipsetDevice: 'static + Send /* see DEVNOTE before adding bounds */ { ) -> Option<&mut dyn interrupt::AcknowledgePicInterrupt> { None } + + /// Optionally returns a trait object which implements TDISP host + /// communication. + #[inline(always)] + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispGuestRequestInterface> { + None + } } /// Shared by `mmio` and `pio` diff --git a/vm/devices/pci/vpci/Cargo.toml b/vm/devices/pci/vpci/Cargo.toml index dbd2a4b11e..fcea009384 100644 --- a/vm/devices/pci/vpci/Cargo.toml +++ b/vm/devices/pci/vpci/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] pci_core.workspace = true +tdisp.workspace = true vpci_protocol.workspace = true device_emulators.workspace = true diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index 38a7ffb180..0968db5fa8 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -1345,6 +1345,7 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; + use tdisp::TdispHostStateMachine; use test_with_tracing::test; use thiserror::Error; use vmbus_async::queue::IncomingPacket; @@ -1797,14 +1798,14 @@ mod tests { assert_eq!(value, 0x20); } - #[async_test] - async fn verify_simple_device_registers(driver: DefaultDriver) { - let msi_controller = TestVpciInterruptController::new(); - - struct TestDevice(ConfigSpaceType0Emulator); - impl TestDevice { - fn new(register_mmio: &mut dyn RegisterMmioIntercept) -> Self { - Self(ConfigSpaceType0Emulator::new( + struct TestDevice { + config_space: ConfigSpaceType0Emulator, + tdisp_interface: TdispHostStateMachine, + } + impl TestDevice { + fn new(register_mmio: &mut dyn RegisterMmioIntercept) -> Self { + Self { + config_space: ConfigSpaceType0Emulator::new( HardwareIds { vendor_id: 0x123, device_id: 0x789, @@ -1825,90 +1826,100 @@ mod tests { 0x2000, BarMemoryKind::Intercept(register_mmio.new_io_region("bar2", 0x2000)), ), - )) + ), + tdisp_interface: tdisp::test_helpers::make_null_tdisp_interface(), } + } - fn read_bar_u32(&self, bar: u8, offset: u16) -> u32 { - if bar == 0 && offset == 0 { - 1 - } else if bar == 0 && offset == 4 { - 2 - } else if bar == 2 && offset == 0 { - 3 - } else if bar == 2 && offset == HV_PAGE_SIZE as u16 { - 4 - } else { - panic!("Unexpected address {}/{:#x}", bar, offset); - } + fn read_bar_u32(&self, bar: u8, offset: u16) -> u32 { + if bar == 0 && offset == 0 { + 1 + } else if bar == 0 && offset == 4 { + 2 + } else if bar == 2 && offset == 0 { + 3 + } else if bar == 2 && offset == HV_PAGE_SIZE as u16 { + 4 + } else { + panic!("Unexpected address {}/{:#x}", bar, offset); } + } - fn write_bar_u32(&mut self, bar: u8, offset: u16, val: u32) { - if bar == 0 && offset == 0 { - assert_eq!(val, 1); - } else if bar == 0 && offset == 4 { - assert_eq!(val, 2); - } else if bar == 2 && offset == 0 { - assert_eq!(val, 3); - } else if bar == 2 && offset == HV_PAGE_SIZE as u16 { - assert_eq!(val, 4); - } else { - panic!("Unexpected address {}/{:#x}", bar, offset); - } + fn write_bar_u32(&mut self, bar: u8, offset: u16, val: u32) { + if bar == 0 && offset == 0 { + assert_eq!(val, 1); + } else if bar == 0 && offset == 4 { + assert_eq!(val, 2); + } else if bar == 2 && offset == 0 { + assert_eq!(val, 3); + } else if bar == 2 && offset == HV_PAGE_SIZE as u16 { + assert_eq!(val, 4); + } else { + panic!("Unexpected address {}/{:#x}", bar, offset); } } + } - impl InspectMut for TestDevice { - fn inspect_mut(&mut self, req: inspect::Request<'_>) { - req.ignore(); - } + impl InspectMut for TestDevice { + fn inspect_mut(&mut self, req: inspect::Request<'_>) { + req.ignore(); } + } - impl Inspect for TestDevice { - fn inspect(&self, req: inspect::Request<'_>) { - req.ignore(); - } + impl Inspect for TestDevice { + fn inspect(&self, req: inspect::Request<'_>) { + req.ignore(); } + } - impl ChipsetDevice for TestDevice { - fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> { - Some(self) - } + impl ChipsetDevice for TestDevice { + fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> { + Some(self) + } - fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> { - Some(self) - } + fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> { + Some(self) } - impl MmioIntercept for TestDevice { - fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult { - if let Some((bar, offset)) = self.0.find_bar(address) { - read_as_u32_chunks(offset, data, |offset| self.read_bar_u32(bar, offset)) - } - IoResult::Ok - } + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispGuestRequestInterface> { + Some(&mut self.tdisp_interface) + } + } - fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult { - if let Some((bar, offset)) = self.0.find_bar(address) { - write_as_u32_chunks(offset, data, |offset, request_type| match request_type { - ReadWriteRequestType::Write(value) => { - self.write_bar_u32(bar, offset, value); - None - } - ReadWriteRequestType::Read => Some(self.read_bar_u32(bar, offset)), - }) - } - IoResult::Ok + impl MmioIntercept for TestDevice { + fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult { + if let Some((bar, offset)) = self.config_space.find_bar(address) { + read_as_u32_chunks(offset, data, |offset| self.read_bar_u32(bar, offset)) } + IoResult::Ok } - impl PciConfigSpace for TestDevice { - fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult { - self.0.read_u32(offset, value) - } - fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult { - self.0.write_u32(offset, value) + fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult { + if let Some((bar, offset)) = self.config_space.find_bar(address) { + write_as_u32_chunks(offset, data, |offset, request_type| match request_type { + ReadWriteRequestType::Write(value) => { + self.write_bar_u32(bar, offset, value); + None + } + ReadWriteRequestType::Read => Some(self.read_bar_u32(bar, offset)), + }) } + IoResult::Ok + } + } + + impl PciConfigSpace for TestDevice { + fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult { + self.config_space.read_u32(offset, value) + } + fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult { + self.config_space.write_u32(offset, value) } + } + + #[async_test] + async fn verify_simple_device_registers(driver: DefaultDriver) { + let msi_controller = TestVpciInterruptController::new(); let vm_chipset = TestChipset::default(); let pci = vm_chipset diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 2da909f6b9..cb9104fc51 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -38,31 +38,36 @@ pub mod devicereport; #[cfg(test)] mod tests; +/// Mocks for the host interface and the emulator. +pub mod test_helpers; + use anyhow::Context; use parking_lot::Mutex; use std::sync::Arc; -use tdisp_proto::GuestToHostCommand; -use tdisp_proto::GuestToHostResponse; -use tdisp_proto::TdispCommandResponseBind; -use tdisp_proto::TdispCommandResponseGetDeviceInterfaceInfo; -use tdisp_proto::TdispCommandResponseGetTdiReport; -use tdisp_proto::TdispCommandResponseStartTdi; -use tdisp_proto::TdispCommandResponseUnbind; -use tdisp_proto::TdispDeviceInterfaceInfo; +pub use tdisp_proto::GuestToHostCommand; +pub use tdisp_proto::GuestToHostResponse; +pub use tdisp_proto::TdispCommandResponseBind; +pub use tdisp_proto::TdispCommandResponseGetDeviceInterfaceInfo; +pub use tdisp_proto::TdispCommandResponseGetTdiReport; +pub use tdisp_proto::TdispCommandResponseStartTdi; +pub use tdisp_proto::TdispCommandResponseUnbind; +pub use tdisp_proto::TdispDeviceInterfaceInfo; pub use tdisp_proto::TdispGuestOperationError; -use tdisp_proto::TdispGuestOperationErrorCode; -use tdisp_proto::TdispGuestProtocolType; -use tdisp_proto::TdispGuestUnbindReason; -use tdisp_proto::TdispReportType; -use tdisp_proto::TdispTdiState; -use tdisp_proto::guest_to_host_command::Command; -use tdisp_proto::guest_to_host_response::Response; +pub use tdisp_proto::TdispGuestOperationErrorCode; +pub use tdisp_proto::TdispGuestProtocolType; +pub use tdisp_proto::TdispGuestUnbindReason; +pub use tdisp_proto::TdispReportType; +pub use tdisp_proto::TdispTdiState; +pub use tdisp_proto::guest_to_host_command::Command; +pub use tdisp_proto::guest_to_host_response::Response; use tracing::instrument; /// Callback for receiving TDISP commands from the guest. pub type TdispCommandCallback = dyn Fn(&GuestToHostCommand) -> anyhow::Result<()> + Send + Sync; -/// Trait used by the emulator to call back into the host. +/// Describes the interface that host software should implement to provide TDISP +/// functionality for a device. These interfaces might dispatch to a physical +/// device, or might be implemented by a software emulator. pub trait TdispHostDeviceInterface: Send + Sync { /// Request versioning and protocol negotiation from the host. fn tdisp_negotiate_protocol( diff --git a/vm/devices/tdisp/src/test_helpers.rs b/vm/devices/tdisp/src/test_helpers.rs new file mode 100644 index 0000000000..fe20342edb --- /dev/null +++ b/vm/devices/tdisp/src/test_helpers.rs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::TdispHostDeviceInterface; +use crate::TdispHostStateMachine; +use parking_lot::Mutex; +use std::sync::Arc; +use tdisp_proto::TdispDeviceInterfaceInfo; +use tdisp_proto::TdispGuestProtocolType; +use tdisp_proto::TdispReportType; + +/// Implements the host side of the TDISP interface for the mock NullDevice. +pub struct NullTdispHostInterface {} +impl TdispHostDeviceInterface for NullTdispHostInterface { + fn tdisp_negotiate_protocol( + &mut self, + _requested_guest_protocol: TdispGuestProtocolType, + ) -> anyhow::Result { + Ok(TdispDeviceInterfaceInfo { + guest_protocol_type: TdispGuestProtocolType::AmdSevTioV10 as i32, + supported_features: 0xDEAD, + tdisp_device_id: 99, + }) + } + + fn tdisp_bind_device(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn tdisp_start_device(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn tdisp_unbind_device(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn tdisp_get_device_report( + &mut self, + _report_type: TdispReportType, + ) -> anyhow::Result> { + Ok(vec![]) + } +} + +/// Implements the host side of the TDISP interface for a mock device that does nothing. +pub fn make_null_tdisp_interface() -> TdispHostStateMachine { + TdispHostStateMachine::new(Arc::new(Mutex::new(NullTdispHostInterface {}))) +} diff --git a/vm/devices/tdisp/src/tests/mod.rs b/vm/devices/tdisp/src/tests/mod.rs index d540784644..1873336c96 100644 --- a/vm/devices/tdisp/src/tests/mod.rs +++ b/vm/devices/tdisp/src/tests/mod.rs @@ -4,7 +4,7 @@ //! Unit tests for the TDISP guest-to-host interface. /// Mocks for the host interface and the emulator. -mod mocks; +pub mod mocks; /// Unit tests for serialization and deserialization of TDISP guest-to-host commands and responses. pub mod serialize_tests; From 6d1476c83897038c3dafb48da6e21744a81a5062 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Tue, 24 Feb 2026 15:24:18 -0800 Subject: [PATCH 14/32] add tdisp command packet processing for openvmm vpci impl --- vm/chipset_device/src/lib.rs | 2 +- vm/devices/pci/vpci/src/device.rs | 74 +++++++++++++++++++++++-- vm/devices/pci/vpci_protocol/src/lib.rs | 3 +- vm/devices/tdisp/src/test_helpers.rs | 9 ++- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/vm/chipset_device/src/lib.rs b/vm/chipset_device/src/lib.rs index 853e9d11c8..cba9380eca 100644 --- a/vm/chipset_device/src/lib.rs +++ b/vm/chipset_device/src/lib.rs @@ -65,7 +65,7 @@ pub trait ChipsetDevice: 'static + Send /* see DEVNOTE before adding bounds */ { /// Optionally returns a trait object which implements TDISP host /// communication. #[inline(always)] - fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispGuestRequestInterface> { + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispHostDeviceTarget> { None } } diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index 0968db5fa8..56dce5092a 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -44,6 +44,7 @@ use vmcore::vpci_msi::RegisterInterruptError; use vmcore::vpci_msi::VpciInterruptMapper; use vmcore::vpci_msi::VpciInterruptParameters; use vpci_protocol as protocol; +use vpci_protocol::MAX_VPCI_TDISP_COMMAND_SIZE; use vpci_protocol::SlotNumber; use zerocopy::FromBytes; use zerocopy::FromZeros; @@ -229,6 +230,8 @@ enum PacketError { RegisterInterrupt(#[source] RegisterInterruptError), #[error("unknown interrupt address {:#x}/data {:#x}", .0.address, .0.data)] UnknownInterrupt(MsiAddressData), + #[error("invalid packet serialization")] + InvalidSerialization(#[source] anyhow::Error), } #[derive(Debug)] @@ -266,6 +269,9 @@ enum DeviceRequest { }, ReleaseResources, Reset, + TdispCommand { + data: Vec, + }, } #[derive(Debug)] @@ -482,6 +488,25 @@ fn parse_packet(packet: &queue::DataPacket<'_, T>) -> Result { + let (header, rest) = Ref::<_, protocol::VpciTdispCommandHeader>::from_prefix(buf) + .map_err(|_| PacketError::PacketTooSmall("tdisp_command_header"))?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759) + + let data_len = header.data_length as usize; + if data_len > MAX_VPCI_TDISP_COMMAND_SIZE { + return Err(PacketError::PacketTooLarge); + } + + let data = rest + .get(..data_len) + .ok_or(PacketError::PacketTooSmall("tdisp_command_data"))? + .to_vec(); + + PacketData::DeviceRequest { + slot: header.slot, + request: DeviceRequest::TdispCommand { data }, + } + } typ => return Err(PacketError::UnknownType(typ)), }; Ok(data) @@ -883,6 +908,47 @@ impl ReadyState { &[], )?; } + DeviceRequest::TdispCommand { data } => { + let command = match tdisp::serialize_proto::deserialize_command(&data) { + Ok(cmd) => cmd, + Err(err) => { + tracelimit::warn_ratelimited!( + error = err.as_ref() as &dyn std::error::Error, + "failed to deserialize TDISP command" + ); + conn.send_completion( + transaction_id, + &protocol::Status::BAD_DATA, + &[], + )?; + return Ok(()); + } + }; + + tracing::debug!(?command, "received TDISP command over vpci channel"); + + let mut locked_dev = dev.device.lock(); + if let Some(tdisp) = locked_dev.supports_tdisp() { + let response = tdisp + .tdisp_handle_guest_command(command) + .map_err(PacketError::InvalidSerialization)?; + + let response_serialized = + tdisp::serialize_proto::serialize_response(&response); + + conn.send_completion( + transaction_id, + &protocol::Status::SUCCESS, + response_serialized.as_bytes(), + )?; + } else { + conn.send_completion( + transaction_id, + &protocol::Status::NOT_SUPPORTED, + &[], + )?; + } + } } } } @@ -1345,7 +1411,7 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; - use tdisp::TdispHostStateMachine; + use tdisp::TdispHostDeviceTargetEmulator; use test_with_tracing::test; use thiserror::Error; use vmbus_async::queue::IncomingPacket; @@ -1800,7 +1866,7 @@ mod tests { struct TestDevice { config_space: ConfigSpaceType0Emulator, - tdisp_interface: TdispHostStateMachine, + tdisp_interface: TdispHostDeviceTargetEmulator, } impl TestDevice { fn new(register_mmio: &mut dyn RegisterMmioIntercept) -> Self { @@ -1827,7 +1893,7 @@ mod tests { BarMemoryKind::Intercept(register_mmio.new_io_region("bar2", 0x2000)), ), ), - tdisp_interface: tdisp::test_helpers::make_null_tdisp_interface(), + tdisp_interface: tdisp::test_helpers::make_null_tdisp_interface("vpci-unit-test"), } } @@ -1881,7 +1947,7 @@ mod tests { Some(self) } - fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispGuestRequestInterface> { + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispHostDeviceTarget> { Some(&mut self.tdisp_interface) } } diff --git a/vm/devices/pci/vpci_protocol/src/lib.rs b/vm/devices/pci/vpci_protocol/src/lib.rs index 36410f15bd..bf34f13ad5 100644 --- a/vm/devices/pci/vpci_protocol/src/lib.rs +++ b/vm/devices/pci/vpci_protocol/src/lib.rs @@ -833,4 +833,5 @@ pub struct VpciTdispCommand { } /// Maximum size of a TDISP command in bytes. Property of the VMBUS implementation on the host. -pub const MAX_VPCI_TDISP_COMMAND_SIZE: usize = 35000; +pub const MAX_VPCI_TDISP_COMMAND_SIZE: usize = + MAXIMUM_PACKET_SIZE - size_of::(); diff --git a/vm/devices/tdisp/src/test_helpers.rs b/vm/devices/tdisp/src/test_helpers.rs index fe20342edb..4da59665da 100644 --- a/vm/devices/tdisp/src/test_helpers.rs +++ b/vm/devices/tdisp/src/test_helpers.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::TdispHostDeviceInterface; -use crate::TdispHostStateMachine; +use crate::TdispHostDeviceTargetEmulator; use parking_lot::Mutex; use std::sync::Arc; use tdisp_proto::TdispDeviceInterfaceInfo; @@ -44,6 +44,9 @@ impl TdispHostDeviceInterface for NullTdispHostInterface { } /// Implements the host side of the TDISP interface for a mock device that does nothing. -pub fn make_null_tdisp_interface() -> TdispHostStateMachine { - TdispHostStateMachine::new(Arc::new(Mutex::new(NullTdispHostInterface {}))) +pub fn make_null_tdisp_interface(debug_device_id: &str) -> TdispHostDeviceTargetEmulator { + TdispHostDeviceTargetEmulator::new( + Arc::new(Mutex::new(NullTdispHostInterface {})), + debug_device_id, + ) } From 19fff9b3bf100fbb9f227f8205f5289d7dcd12b9 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Tue, 24 Feb 2026 17:19:22 -0800 Subject: [PATCH 15/32] test passing now --- Cargo.lock | 1 + vm/devices/pci/vpci/Cargo.toml | 3 + vm/devices/pci/vpci/src/device.rs | 141 +++++++++++++++++++++++- vm/devices/pci/vpci_protocol/src/lib.rs | 13 +++ 4 files changed, 157 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed24e9e94..e9f246928f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9899,6 +9899,7 @@ dependencies = [ "pci_core", "task_control", "tdisp", + "tdisp_proto", "test_with_tracing", "thiserror 2.0.16", "tracelimit", diff --git a/vm/devices/pci/vpci/Cargo.toml b/vm/devices/pci/vpci/Cargo.toml index fcea009384..390547a1fb 100644 --- a/vm/devices/pci/vpci/Cargo.toml +++ b/vm/devices/pci/vpci/Cargo.toml @@ -37,5 +37,8 @@ thiserror.workspace = true tracelimit.workspace = true tracing.workspace = true zerocopy.workspace = true + +[dev-dependencies] +tdisp_proto.workspace = true [lints] workspace = true diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index 56dce5092a..e3613bce5e 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -936,9 +936,25 @@ impl ReadyState { let response_serialized = tdisp::serialize_proto::serialize_response(&response); + let response_header = protocol::VpciTdispCommandHeaderReply { + status: protocol::Status::SUCCESS, + slot, + data_length: response_serialized.len() as u64, + }; + + tracing::debug!(?response_header, "guest response"); + tracing::debug!( + response_header_len = response_header.as_bytes().len(), + "guest response header size" + ); + tracing::debug!( + payload_size = response_serialized.len(), + "guest response payload size" + ); + conn.send_completion( transaction_id, - &protocol::Status::SUCCESS, + &response_header, response_serialized.as_bytes(), )?; } else { @@ -1411,7 +1427,9 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; + use tdisp::TdispCommandResponseGetDeviceInterfaceInfo; use tdisp::TdispHostDeviceTargetEmulator; + use tdisp_proto::GuestToHostResponseExt; use test_with_tracing::test; use thiserror::Error; use vmbus_async::queue::IncomingPacket; @@ -1547,6 +1565,28 @@ mod tests { .map_err(GuestError::Queue) } + async fn write_packet_with_header( + &mut self, + transaction_id: Option, + header: &T, + extra: &[u8], + ) -> Result<(), GuestError> { + self.host_queue + .split() + .1 + .write(OutgoingPacket { + transaction_id: transaction_id.unwrap_or(0), + packet_type: if transaction_id.is_some() { + OutgoingPacketType::InBandWithCompletion + } else { + OutgoingPacketType::InBandNoCompletion + }, + payload: &[header.as_bytes(), extra], + }) + .await + .map_err(GuestError::Queue) + } + async fn negotiate_version(&mut self) { if let Err(vsp_version) = self.try_negotiate_version().await { self.protocol_version = vsp_version; @@ -1674,6 +1714,65 @@ mod tests { assert_eq!(reply.interrupt.message_count, 1); (reply.interrupt.address, reply.interrupt.data_payload) } + + /// Serializes `command` to a `VPCI_TDISP_COMMAND` vmbus packet, sends it + /// to the server requesting a completion, then reads the completion and + /// deserializes the payload back to a [`tdisp::GuestToHostResponse`]. + async fn send_tdisp_command( + &mut self, + command: tdisp::GuestToHostCommand, + ) -> tdisp::GuestToHostResponse { + let serialized = tdisp::serialize_proto::serialize_command(&command); + + let header = protocol::VpciTdispCommandHeader { + message_type: protocol::MessageType::VPCI_TDISP_COMMAND, + slot: SlotNumber::new(), + data_length: serialized.len() as u64, + }; + let transaction_id = self.transaction_id.fetch_add(1, Ordering::Relaxed); + self.write_packet_with_header(Some(transaction_id), &header, serialized.as_bytes()) + .await + .unwrap(); + + let mut queue = self.host_queue.split().0; + let packet = queue.read().await.map_err(GuestError::Queue).unwrap(); + match &*packet { + IncomingPacket::Completion(completion) => { + assert_eq!(completion.transaction_id(), transaction_id); + + // Read the entire completion payload at once before splitting it. + // reader.len() returns the *total* payload size regardless of any + // subsequent reads, so we must snapshot all bytes in one shot and + // then parse them manually to avoid zero-padding artefacts. + let mut reader = completion.reader(); + let mut all_bytes = vec![0u8; reader.len()]; + reader.read(&mut all_bytes).unwrap(); + + let (reply_header, proto_bytes) = + protocol::VpciTdispCommandHeaderReply::read_from_prefix(&all_bytes) + .expect("completion payload too small to contain status"); + + assert_eq!( + reply_header.status, + protocol::Status::SUCCESS, + "tdisp command completion returned non-success status" + ); + + tracing::debug!( + reply_header_size = reply_header.as_bytes().len(), + "completion header size" + ); + tracing::debug!(payload_size = proto_bytes.len(), "completion payload size"); + + // Read only data_length bytes from the payload. + let proto_bytes_shaved = &proto_bytes[..reply_header.data_length as usize]; + + tdisp::serialize_proto::deserialize_response(proto_bytes_shaved) + .expect("failed to deserialize GuestToHostResponse") + } + _ => panic!("unexpected incoming packet type"), + } + } } struct NullDevice { @@ -2045,6 +2144,46 @@ mod tests { write_u32(bar_address2 + HV_PAGE_SIZE, 4); } + #[async_test] + async fn verify_tdisp_get_device_interface_info(driver: DefaultDriver) { + let msi_controller = TestVpciInterruptController::new(); + let vm_chipset = TestChipset::default(); + let pci = vm_chipset + .device_builder("test") + .with_external_pci() + .add(|services| TestDevice::new(&mut services.register_mmio())) + .unwrap(); + let mut guest_driver = connected_device(&driver, pci.clone(), msi_controller); + guest_driver.start_device(0x1000000).await; + + let guest_protocol_type = tdisp::TdispGuestProtocolType::AmdSevTioV10 as i32; + let command = tdisp::GuestToHostCommand { + device_id: SlotNumber::new().into_bits() as u64, + command: Some(tdisp::Command::GetDeviceInterfaceInfo( + tdisp_proto::TdispCommandRequestGetDeviceInterfaceInfo { + guest_protocol_type, + }, + )), + }; + let response = guest_driver.send_tdisp_command(command).await; + + let response = response.get_response::(); + match response { + Ok(info_resp) => { + let interface_info = info_resp + .interface_info + .expect("interface_info must be set"); + + assert_eq!(interface_info.guest_protocol_type, guest_protocol_type); + assert_eq!(interface_info.supported_features, 0xDEAD); + } + _ => panic!( + "expected GetDeviceInterfaceInfo response, got {:?}", + response + ), + } + } + #[async_test] async fn verify_simple_device_interrupt(driver: DefaultDriver) { let msi_controller = TestVpciInterruptController::new(); diff --git a/vm/devices/pci/vpci_protocol/src/lib.rs b/vm/devices/pci/vpci_protocol/src/lib.rs index bf34f13ad5..e04558953b 100644 --- a/vm/devices/pci/vpci_protocol/src/lib.rs +++ b/vm/devices/pci/vpci_protocol/src/lib.rs @@ -822,6 +822,19 @@ pub struct VpciTdispCommandHeader { // pub data: [u8; data_length...], } +/// A TDISP packet response from the host to the guest. +#[repr(C)] +#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)] +pub struct VpciTdispCommandHeaderReply { + /// Status of the translation operation + pub status: Status, + /// PCI slot number of the target device + pub slot: SlotNumber, + /// Length of the data payload to follow + pub data_length: u64, + // pub data: [u8; data_length...], +} + /// A serialized TDISP VPCI VMBUS command packet. #[derive(Debug, Clone)] pub struct VpciTdispCommand { From 1565f673d9af511451f732db53a01a2e24bfc768 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 25 Feb 2026 10:41:05 -0800 Subject: [PATCH 16/32] more tests within vpci_client --- vm/devices/pci/vpci/src/device.rs | 18 +++-- vm/devices/pci/vpci_client/src/tests.rs | 73 ++++++++++++++++++- vm/devices/tdisp/src/test_helpers.rs | 15 +++- vm/devices/tdisp/src/tests/endtoend_tests.rs | 2 +- vm/devices/tdisp/src/tests/mocks.rs | 9 ++- vm/devices/tdisp/src/tests/serialize_tests.rs | 2 +- 6 files changed, 103 insertions(+), 16 deletions(-) diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index e3613bce5e..d02f2d8dae 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -1429,6 +1429,9 @@ mod tests { use std::sync::atomic::Ordering; use tdisp::TdispCommandResponseGetDeviceInterfaceInfo; use tdisp::TdispHostDeviceTargetEmulator; + use tdisp::test_helpers::TDISP_MOCK_DEVICE_ID; + use tdisp::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; + use tdisp::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; use tdisp_proto::GuestToHostResponseExt; use test_with_tracing::test; use thiserror::Error; @@ -1741,9 +1744,6 @@ mod tests { assert_eq!(completion.transaction_id(), transaction_id); // Read the entire completion payload at once before splitting it. - // reader.len() returns the *total* payload size regardless of any - // subsequent reads, so we must snapshot all bytes in one shot and - // then parse them manually to avoid zero-padding artefacts. let mut reader = completion.reader(); let mut all_bytes = vec![0u8; reader.len()]; reader.read(&mut all_bytes).unwrap(); @@ -2144,6 +2144,10 @@ mod tests { write_u32(bar_address2 + HV_PAGE_SIZE, 4); } + /// Verifies that the TDISP guest protocol can be negotiated correctly over a hosted VMBUS channel. + /// This test covers: + /// - Some basic VMBUS VPCI packet serialization for VpciTdispCommand + /// - VPCI VMBUS server interface receiving and responding to TDISP commands #[async_test] async fn verify_tdisp_get_device_interface_info(driver: DefaultDriver) { let msi_controller = TestVpciInterruptController::new(); @@ -2156,7 +2160,7 @@ mod tests { let mut guest_driver = connected_device(&driver, pci.clone(), msi_controller); guest_driver.start_device(0x1000000).await; - let guest_protocol_type = tdisp::TdispGuestProtocolType::AmdSevTioV10 as i32; + let guest_protocol_type = TDISP_MOCK_GUEST_PROTOCOL as i32; let command = tdisp::GuestToHostCommand { device_id: SlotNumber::new().into_bits() as u64, command: Some(tdisp::Command::GetDeviceInterfaceInfo( @@ -2175,7 +2179,11 @@ mod tests { .expect("interface_info must be set"); assert_eq!(interface_info.guest_protocol_type, guest_protocol_type); - assert_eq!(interface_info.supported_features, 0xDEAD); + assert_eq!( + interface_info.supported_features, + TDISP_MOCK_SUPPORTED_FEATURES + ); + assert_eq!(interface_info.tdisp_device_id, TDISP_MOCK_DEVICE_ID); } _ => panic!( "expected GetDeviceInterfaceInfo response, got {:?}", diff --git a/vm/devices/pci/vpci_client/src/tests.rs b/vm/devices/pci/vpci_client/src/tests.rs index 44d72d0cbf..1756bd121d 100644 --- a/vm/devices/pci/vpci_client/src/tests.rs +++ b/vm/devices/pci/vpci_client/src/tests.rs @@ -12,11 +12,17 @@ use chipset_device::pci::PciConfigSpace; use closeable_mutex::CloseableMutex; use guestmem::GuestMemory; use guid::Guid; +use openhcl_tdisp::TdispVirtualDeviceInterface; use pal_async::DefaultDriver; use pal_async::async_test; use pal_async::task::Spawn; use std::sync::Arc; use task_control::StopTask; +use tdisp::TdispHostDeviceTargetEmulator; +use tdisp::test_helpers::TDISP_MOCK_DEVICE_ID; +use tdisp::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; +use tdisp::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; +use tdisp::test_helpers::make_null_tdisp_interface; use test_with_tracing::test; use vmbus_channel::simple::SimpleVmbusDevice; use vmcore::vpci_msi::MapVpciInterrupt; @@ -26,12 +32,18 @@ use vmcore::vpci_msi::VpciInterruptParameters; use vpci::bus::VpciBusDevice; use vpci::test_helpers::TestVpciInterruptController; -struct NoopDevice; +struct NoopDevice { + tdisp_interface: TdispHostDeviceTargetEmulator, +} impl ChipsetDevice for NoopDevice { fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> { Some(self) } + + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispHostDeviceTarget> { + Some(&mut self.tdisp_interface) + } } impl PciConfigSpace for NoopDevice { @@ -71,9 +83,15 @@ impl super::MemoryAccess for BusWrapper { } } +fn make_noop_device() -> Arc> { + Arc::new(CloseableMutex::new(NoopDevice { + tdisp_interface: make_null_tdisp_interface("vpci-unit-test"), + })) +} + #[async_test] async fn test_negotiate_version(driver: DefaultDriver) { - let device = Arc::new(CloseableMutex::new(NoopDevice)); + let device = make_noop_device(); let msi_controller = TestVpciInterruptController::new(); let (bus, mut channel) = VpciBusDevice::new( Guid::new_random(), @@ -116,3 +134,54 @@ async fn test_negotiate_version(driver: DefaultDriver) { device.unregister_interrupt(address, data).await; } + +/// Tests that VPCI can negotiate basic TDISP commands with a device. +/// This test covers: +/// - VMBUS VPCI packet serialization for VpciTdispCommand +/// - TDISP command serialization +/// - VPCI VMBUS server interface receiving and responding to TDISP commands +/// - VPCI VMBUS client interface sending and receiving TDISP commands +/// - Basic TDISP state machine processing +#[async_test] +async fn test_tdisp_interface_get_device_interface_info(driver: DefaultDriver) { + let device = make_noop_device(); + let msi_controller = TestVpciInterruptController::new(); + let (bus, mut channel) = VpciBusDevice::new( + Guid::new_random(), + device, + &mut ExternallyManagedMmioIntercepts, + VpciInterruptMapper::new(msi_controller), + None, + ) + .unwrap(); + + let (host, guest) = vmbus_channel::connected_async_channels(32768); + + let mut runner = channel.open(host, GuestMemory::empty()).unwrap(); + let _task = driver.spawn("server", async move { + StopTask::run_with(std::future::pending(), async |stop| { + let _ = channel.run(stop, &mut runner).await; + }) + .await + }); + + let (_client, devices) = + super::VpciClient::connect(&driver, guest, Box::new(BusWrapper(bus)), mesh::channel().0) + .await + .unwrap(); + + let (device, _removed) = devices.into_iter().next().unwrap().init().await.unwrap(); + let interface = device.tdisp_get_device_interface_info().await; + + match interface { + Ok(interface) => { + assert_eq!( + interface.guest_protocol_type, + TDISP_MOCK_GUEST_PROTOCOL as i32 + ); + assert_eq!(interface.supported_features, TDISP_MOCK_SUPPORTED_FEATURES); + assert_eq!(interface.tdisp_device_id, TDISP_MOCK_DEVICE_ID); + } + Err(err) => panic!("unexpected error: {err}"), + } +} diff --git a/vm/devices/tdisp/src/test_helpers.rs b/vm/devices/tdisp/src/test_helpers.rs index 4da59665da..88a509f2d4 100644 --- a/vm/devices/tdisp/src/test_helpers.rs +++ b/vm/devices/tdisp/src/test_helpers.rs @@ -9,6 +9,15 @@ use tdisp_proto::TdispDeviceInterfaceInfo; use tdisp_proto::TdispGuestProtocolType; use tdisp_proto::TdispReportType; +/// Guest protocol that will be negotiated by the mock device. +pub const TDISP_MOCK_GUEST_PROTOCOL: TdispGuestProtocolType = TdispGuestProtocolType::AmdSevTioV10; + +/// Device features that will be negotiated by the mock device. +pub const TDISP_MOCK_SUPPORTED_FEATURES: u64 = 0xDEAD; + +/// Device ID that will be negotiated by the mock device. +pub const TDISP_MOCK_DEVICE_ID: u64 = 99; + /// Implements the host side of the TDISP interface for the mock NullDevice. pub struct NullTdispHostInterface {} impl TdispHostDeviceInterface for NullTdispHostInterface { @@ -17,9 +26,9 @@ impl TdispHostDeviceInterface for NullTdispHostInterface { _requested_guest_protocol: TdispGuestProtocolType, ) -> anyhow::Result { Ok(TdispDeviceInterfaceInfo { - guest_protocol_type: TdispGuestProtocolType::AmdSevTioV10 as i32, - supported_features: 0xDEAD, - tdisp_device_id: 99, + guest_protocol_type: TDISP_MOCK_GUEST_PROTOCOL as i32, + supported_features: TDISP_MOCK_SUPPORTED_FEATURES, + tdisp_device_id: TDISP_MOCK_DEVICE_ID, }) } diff --git a/vm/devices/tdisp/src/tests/endtoend_tests.rs b/vm/devices/tdisp/src/tests/endtoend_tests.rs index 95e3414c08..6e8408dabb 100644 --- a/vm/devices/tdisp/src/tests/endtoend_tests.rs +++ b/vm/devices/tdisp/src/tests/endtoend_tests.rs @@ -13,8 +13,8 @@ use crate::serialize_proto::deserialize_command; use crate::serialize_proto::deserialize_response; use crate::serialize_proto::serialize_command; use crate::serialize_proto::serialize_response; +use crate::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; use crate::tests::mocks::LastCall; -use crate::tests::mocks::TDISP_MOCK_GUEST_PROTOCOL; use crate::tests::mocks::new_emulator; use tdisp_proto::GuestToHostCommand; use tdisp_proto::TdispCommandRequestBind; diff --git a/vm/devices/tdisp/src/tests/mocks.rs b/vm/devices/tdisp/src/tests/mocks.rs index 25faf5e260..413d937393 100644 --- a/vm/devices/tdisp/src/tests/mocks.rs +++ b/vm/devices/tdisp/src/tests/mocks.rs @@ -5,14 +5,15 @@ use crate::TdispGuestRequestInterface; use crate::TdispHostDeviceInterface; use crate::TdispHostDeviceTargetEmulator; use crate::TdispHostStateMachine; +use crate::test_helpers::TDISP_MOCK_DEVICE_ID; +use crate::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; +use crate::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; use parking_lot::Mutex; use std::sync::Arc; use tdisp_proto::TdispDeviceInterfaceInfo; use tdisp_proto::TdispGuestProtocolType; use tdisp_proto::TdispReportType; -pub const TDISP_MOCK_GUEST_PROTOCOL: TdispGuestProtocolType = TdispGuestProtocolType::AmdSevTioV10; - #[derive(Debug, PartialEq, Clone)] pub enum LastCall { NegotiateProtocol, @@ -63,8 +64,8 @@ impl TdispHostDeviceInterface for TrackingHostInterface { *self.last_call.lock() = Some(LastCall::NegotiateProtocol); Ok(TdispDeviceInterfaceInfo { guest_protocol_type: TDISP_MOCK_GUEST_PROTOCOL as i32, - supported_features: 0xDEAD, - tdisp_device_id: 99, + supported_features: TDISP_MOCK_SUPPORTED_FEATURES, + tdisp_device_id: TDISP_MOCK_DEVICE_ID, }) } } diff --git a/vm/devices/tdisp/src/tests/serialize_tests.rs b/vm/devices/tdisp/src/tests/serialize_tests.rs index 2a2b9fbcc9..26132ab637 100644 --- a/vm/devices/tdisp/src/tests/serialize_tests.rs +++ b/vm/devices/tdisp/src/tests/serialize_tests.rs @@ -9,7 +9,7 @@ use crate::serialize_proto::deserialize_command; use crate::serialize_proto::deserialize_response; use crate::serialize_proto::serialize_command; use crate::serialize_proto::serialize_response; -use crate::tests::mocks::TDISP_MOCK_GUEST_PROTOCOL; +use crate::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; use tdisp_proto::GuestToHostCommand; use tdisp_proto::GuestToHostResponse; use tdisp_proto::TdispCommandRequestBind; From 7ab51f82b6dc93465ee5881af441acbe724f9100 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 25 Feb 2026 13:47:15 -0800 Subject: [PATCH 17/32] xfmt --- Cargo.lock | 1 - openhcl/openhcl_tdisp/Cargo.toml | 1 - vm/devices/tdisp_proto/src/errorcode.rs | 3 +++ vm/devices/tdisp_proto/src/tdisp.proto | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9f246928f..e3f4d9653b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5034,7 +5034,6 @@ name = "openhcl_tdisp" version = "0.0.0" dependencies = [ "anyhow", - "inspect", "tdisp", "tdisp_proto", ] diff --git a/openhcl/openhcl_tdisp/Cargo.toml b/openhcl/openhcl_tdisp/Cargo.toml index 8bb66f5062..73617f827c 100644 --- a/openhcl/openhcl_tdisp/Cargo.toml +++ b/openhcl/openhcl_tdisp/Cargo.toml @@ -7,7 +7,6 @@ rust-version.workspace = true edition.workspace = true [dependencies] -inspect.workspace = true tdisp.workspace = true tdisp_proto.workspace = true diff --git a/vm/devices/tdisp_proto/src/errorcode.rs b/vm/devices/tdisp_proto/src/errorcode.rs index 2663d241f1..67eb0ac77b 100644 --- a/vm/devices/tdisp_proto/src/errorcode.rs +++ b/vm/devices/tdisp_proto/src/errorcode.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use crate::TdispGuestOperationErrorCode; use thiserror::Error; diff --git a/vm/devices/tdisp_proto/src/tdisp.proto b/vm/devices/tdisp_proto/src/tdisp.proto index dcdb106ead..516996a96c 100644 --- a/vm/devices/tdisp_proto/src/tdisp.proto +++ b/vm/devices/tdisp_proto/src/tdisp.proto @@ -237,4 +237,4 @@ message TdispDeviceInterfaceInfo { // Device ID used to communicate with firmware for this particular device. uint64 tdisp_device_id = 3; -} \ No newline at end of file +} From 1f0cad6acecd859d331051100667cc882492115e Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 25 Feb 2026 14:07:59 -0800 Subject: [PATCH 18/32] change V1.0 to V1 in interface names --- vm/devices/pci/vpci_client/src/lib.rs | 2 +- vm/devices/tdisp/src/test_helpers.rs | 2 +- vm/devices/tdisp_proto/src/tdisp.proto | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index cd10c8e530..678f5f0277 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -654,7 +654,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { async fn tdisp_get_device_interface_info(&self) -> anyhow::Result { // TDISP TODO: Configure the correct guest protocol type when TDX support is added. - let target_protocol_type = TdispGuestProtocolType::AmdSevTioV10; + let target_protocol_type = TdispGuestProtocolType::AmdSevTioV1; let res = self .send_tdisp_command(openhcl_tdisp::make_get_device_interface_info_command( diff --git a/vm/devices/tdisp/src/test_helpers.rs b/vm/devices/tdisp/src/test_helpers.rs index 88a509f2d4..5188f61d44 100644 --- a/vm/devices/tdisp/src/test_helpers.rs +++ b/vm/devices/tdisp/src/test_helpers.rs @@ -10,7 +10,7 @@ use tdisp_proto::TdispGuestProtocolType; use tdisp_proto::TdispReportType; /// Guest protocol that will be negotiated by the mock device. -pub const TDISP_MOCK_GUEST_PROTOCOL: TdispGuestProtocolType = TdispGuestProtocolType::AmdSevTioV10; +pub const TDISP_MOCK_GUEST_PROTOCOL: TdispGuestProtocolType = TdispGuestProtocolType::AmdSevTioV1; /// Device features that will be negotiated by the mock device. pub const TDISP_MOCK_SUPPORTED_FEATURES: u64 = 0xDEAD; diff --git a/vm/devices/tdisp_proto/src/tdisp.proto b/vm/devices/tdisp_proto/src/tdisp.proto index 516996a96c..ca371a49e2 100644 --- a/vm/devices/tdisp_proto/src/tdisp.proto +++ b/vm/devices/tdisp_proto/src/tdisp.proto @@ -112,11 +112,11 @@ enum TdispGuestProtocolType { // Invalid guest protocol type. TDISP_GUEST_PROTOCOL_TYPE_INVALID = 0; - // Guest is utilizing OpenHCL's V1.0 TDISP interface for AMD® SEV-TIO - TDISP_GUEST_PROTOCOL_TYPE_AMD_SEV_TIO_V1_0 = 1; + // Guest is utilizing OpenHCL's V1 TDISP interface for AMD® SEV-TIO + TDISP_GUEST_PROTOCOL_TYPE_AMD_SEV_TIO_V1 = 1; - // Guest is utilizing OpenHCL's V1.0 TDISP interface for Intel® TDX Connect - TDISP_GUEST_PROTOCOL_TYPE_INTEL_TDX_CONNECT_V1_0 = 2; + // Guest is utilizing OpenHCL's V1 TDISP interface for Intel® TDX Connect + TDISP_GUEST_PROTOCOL_TYPE_INTEL_TDX_CONNECT_V1 = 2; } // ---------------------------------------------------------------------------- From c38badd204f880ffc28549e486c91fbcb0be90a9 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Wed, 25 Feb 2026 14:16:41 -0800 Subject: [PATCH 19/32] Add support for OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST --- Cargo.lock | 1 + openhcl/underhill_core/src/options.rs | 4 ++ openhcl/underhill_core/src/worker.rs | 7 ++++ vm/devices/pci/vpci_relay/Cargo.toml | 1 + vm/devices/pci/vpci_relay/src/lib.rs | 58 +++++++++++++++++++++++++++ 5 files changed, 71 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e3f4d9653b..fb7378380e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9967,6 +9967,7 @@ dependencies = [ "inspect", "memory_range", "mesh", + "openhcl_tdisp", "pci_core", "slab", "sparse_mmap", diff --git a/openhcl/underhill_core/src/options.rs b/openhcl/underhill_core/src/options.rs index 2113901236..d6b44aae39 100644 --- a/openhcl/underhill_core/src/options.rs +++ b/openhcl/underhill_core/src/options.rs @@ -21,6 +21,9 @@ pub enum TestScenarioConfig { SaveFail, RestoreStuck, SaveStuck, + + /// Exercises a mocked TDISP flow for emulated TDISP devices produced by OpenVMM tests. + VpciTdispFlow, } impl FromStr for TestScenarioConfig { @@ -31,6 +34,7 @@ impl FromStr for TestScenarioConfig { "SERVICING_SAVE_FAIL" => Ok(TestScenarioConfig::SaveFail), "SERVICING_RESTORE_STUCK" => Ok(TestScenarioConfig::RestoreStuck), "SERVICING_SAVE_STUCK" => Ok(TestScenarioConfig::SaveStuck), + "TDISP_VPCI_FLOW_TEST" => Ok(TestScenarioConfig::VpciTdispFlow), _ => Err(anyhow::anyhow!("Invalid test config: {}", s)), } } diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index 7d61326e62..706ea85c02 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -3219,6 +3219,13 @@ async fn new_underhill_vm( ) }, vtom, + VpciRelayOptions { + // Exercises a mocked TDISP flow for emulated TDISP devices produced by OpenVMM tests. + test_tdisp_flow: matches!( + env_cfg.test_configuration, + Some(TestScenarioConfig::VpciTdispFlow) + ), + }, ); // Allow NVMe devices. diff --git a/vm/devices/pci/vpci_relay/Cargo.toml b/vm/devices/pci/vpci_relay/Cargo.toml index cfbc1a7b50..5232fd72e5 100644 --- a/vm/devices/pci/vpci_relay/Cargo.toml +++ b/vm/devices/pci/vpci_relay/Cargo.toml @@ -13,6 +13,7 @@ pci_core.workspace = true sparse_mmap.workspace = true state_unit.workspace = true tdisp.workspace = true +openhcl_tdisp.workspace = true user_driver.workspace = true vpci_client.workspace = true vpci.workspace = true diff --git a/vm/devices/pci/vpci_relay/src/lib.rs b/vm/devices/pci/vpci_relay/src/lib.rs index a8fe562d2e..cc1d05e1c3 100644 --- a/vm/devices/pci/vpci_relay/src/lib.rs +++ b/vm/devices/pci/vpci_relay/src/lib.rs @@ -24,6 +24,7 @@ use futures::StreamExt as _; use inspect::Inspect; use inspect::InspectMut; use memory_range::MemoryRange; +use openhcl_tdisp::TdispVirtualDeviceInterface; use pci_core::spec::hwid::HardwareIds; use state_unit::StateUnits; use std::future::poll_fn; @@ -49,6 +50,9 @@ use vpci_client::VpciDeviceEject; /// TODO TDISP: Required for the tdisp crate to be built in the meantime. #[expect(unused_imports)] use tdisp::TdispHostDeviceInterface; +use tdisp::test_helpers::TDISP_MOCK_DEVICE_ID; +use tdisp::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; +use tdisp::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; /// Trait for creating memory access instances. pub trait CreateMemoryAccess: 'static + Send + Sync { @@ -59,6 +63,14 @@ pub trait CreateMemoryAccess: 'static + Send + Sync { /// The size of the MMIO region required for each VPCI device. pub const VPCI_RELAY_MMIO_PER_DEVICE: u64 = vpci_client::MMIO_SIZE; +/// Flags for controlling optional behavior of the VPCI relay. +#[derive(Inspect, Debug, Default, Copy, Clone)] +pub struct VpciRelayOptions { + /// When set, the relay will exercise a mock TDISP flow for emulated TDISP + /// devices produced by OpenVMM tests. + pub test_tdisp_flow: bool, +} + /// Virtual PCI relay. #[derive(Inspect)] pub struct VpciRelay { @@ -80,6 +92,7 @@ pub struct VpciRelay { allowed_devices: Vec, #[inspect(hex)] vtom: Option, + options: VpciRelayOptions, } #[derive(Inspect)] @@ -164,6 +177,7 @@ impl VpciRelay { mmio_range: MemoryRange, mmio_access: Box, vtom: Option, + options: VpciRelayOptions, ) -> Self { Self { driver_source, @@ -176,6 +190,7 @@ impl VpciRelay { mmio_access, allowed_devices: Vec::new(), vtom, + options, } } @@ -299,6 +314,16 @@ impl VpciRelay { .context("failed to initialize vpci device")?; let vpci_device = Arc::new(vpci_device); + if self.options.test_tdisp_flow { + match Self::tdisp_test_mock_flow(vpci_device.clone()).await { + Ok(_) => {} + Err(err) => { + // Panic on error, as this is a test and we want Petri to fail the test. + panic!("failed to exercise TDISP flow test: {:#}", err); + } + } + } + let device_name = format!("assigned_device:vpci-{instance_id}"); let (device_unit, device) = chipset .add_dyn_device(&self.driver_source, state_units, device_name, async |_| { @@ -345,6 +370,39 @@ impl VpciRelay { state_units.start_stopped_units().await; Ok(()) } + + /// Exercises a mocked TDISP flow for emulated TDISP devices produced by OpenVMM tests. + /// Configured with the OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST environment variable. + async fn tdisp_test_mock_flow(device: Arc) -> anyhow::Result<()> { + // For now, exercise just the "get device interface" flow and ensure that the device responds as + // TDISP capable and with the right mocked device information. + + tracing::info!( + "tdisp_test_mock_flow: exercising TDISP flow because OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST was set" + ); + + let device_interface_info = device + .tdisp_get_device_interface_info() + .await + .context("tdisp_test_mock_flow: failed to get device interface info over vpci")?; + + tracing::info!( + "tdisp_test_mock_flow: device interface info: {:?}", + device_interface_info + ); + + assert_eq!( + device_interface_info.guest_protocol_type, + TDISP_MOCK_GUEST_PROTOCOL as i32 + ); + assert_eq!(device_interface_info.tdisp_device_id, TDISP_MOCK_DEVICE_ID); + assert_eq!( + device_interface_info.supported_features, + TDISP_MOCK_SUPPORTED_FEATURES + ); + + Ok(()) + } } #[derive(InspectMut)] From 6491888eb6940a60a623e1f86c8c96eeee5ef5c4 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Thu, 26 Feb 2026 11:09:24 -0800 Subject: [PATCH 20/32] Implement end-to-end petri test for basic TDISP flow using the fault controller nvme device in openvmm --- Cargo.lock | 2 + vm/chipset_device_resources/Cargo.toml | 1 + vm/chipset_device_resources/src/lib.rs | 4 ++ vm/devices/pci/vpci/src/device.rs | 7 +++ vm/devices/pci/vpci_client/src/lib.rs | 12 +++- vm/devices/storage/nvme_resources/src/lib.rs | 2 + vm/devices/storage/nvme_test/Cargo.toml | 1 + vm/devices/storage/nvme_test/src/pci.rs | 33 +++++++++++ vm/devices/storage/nvme_test/src/resolver.rs | 1 + .../nvme_test/src/tests/controller_tests.rs | 1 + vm/devices/tdisp/src/lib.rs | 10 ++-- .../tests/multiarch/openhcl_servicing.rs | 1 + vmm_tests/vmm_tests/tests/tests/x86_64.rs | 57 +++++++++++++++++++ 13 files changed, 123 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb7378380e..9089c97e35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,6 +578,7 @@ dependencies = [ "chipset_device", "guestmem", "inspect", + "tdisp", "vm_resource", "vmcore", ] @@ -4865,6 +4866,7 @@ dependencies = [ "pci_resources", "scsi_buffers", "task_control", + "tdisp", "thiserror 2.0.16", "tracelimit", "tracing", diff --git a/vm/chipset_device_resources/Cargo.toml b/vm/chipset_device_resources/Cargo.toml index 7ae989e819..4421fb5cc7 100644 --- a/vm/chipset_device_resources/Cargo.toml +++ b/vm/chipset_device_resources/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] chipset_device.workspace = true guestmem.workspace = true +tdisp.workspace = true vmcore.workspace = true vm_resource.workspace = true diff --git a/vm/chipset_device_resources/src/lib.rs b/vm/chipset_device_resources/src/lib.rs index 519e6e93fe..f8ba3fa07a 100644 --- a/vm/chipset_device_resources/src/lib.rs +++ b/vm/chipset_device_resources/src/lib.rs @@ -173,6 +173,10 @@ impl ChipsetDevice for ErasedChipsetDevice { ) -> Option<&mut dyn chipset_device::interrupt::AcknowledgePicInterrupt> { self.0.supports_acknowledge_pic_interrupt() } + + fn supports_tdisp(&mut self) -> Option<&mut dyn tdisp::TdispHostDeviceTarget> { + self.0.supports_tdisp() + } } impl ProtobufSaveRestore for ErasedChipsetDevice { diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index d02f2d8dae..b5561fef39 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -929,10 +929,14 @@ impl ReadyState { let mut locked_dev = dev.device.lock(); if let Some(tdisp) = locked_dev.supports_tdisp() { + tracelimit::info_ratelimited!( + "chipset device supports TDISP, handing off command for processing" + ); let response = tdisp .tdisp_handle_guest_command(command) .map_err(PacketError::InvalidSerialization)?; + tracing::debug!("host interface responded successfully with payload"); let response_serialized = tdisp::serialize_proto::serialize_response(&response); @@ -958,6 +962,9 @@ impl ReadyState { response_serialized.as_bytes(), )?; } else { + tracelimit::info_ratelimited!( + "chipset device reported that TDISP is not supported, returning NOT_SUPPORTED" + ); conn.send_completion( transaction_id, &protocol::Status::NOT_SUPPORTED, diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 678f5f0277..2a532385e3 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -1217,9 +1217,15 @@ impl WorkerState { rpc.complete(host_response.map_err(mesh::error::RemoteError::new)); } else { - rpc.fail(anyhow::anyhow!( - "failed to send tdisp command: {status:#x?}", - )); + if status == protocol::Status::NOT_SUPPORTED { + rpc.fail(anyhow::anyhow!( + "TDISP interface is not supported by this device or host" + )); + } else { + rpc.fail(anyhow::anyhow!( + "vmbus server responded error status: {status:#x?}", + )); + } } } } diff --git a/vm/devices/storage/nvme_resources/src/lib.rs b/vm/devices/storage/nvme_resources/src/lib.rs index feace060fd..73eb000254 100644 --- a/vm/devices/storage/nvme_resources/src/lib.rs +++ b/vm/devices/storage/nvme_resources/src/lib.rs @@ -57,6 +57,8 @@ pub struct NvmeFaultControllerHandle { pub namespaces: Vec, /// Configuration for the fault pub fault_config: FaultConfiguration, + /// Enable TDISP capabilities for the OpenVMM host. + pub tdisp_capable: bool, } impl ResourceId for NvmeFaultControllerHandle { diff --git a/vm/devices/storage/nvme_test/Cargo.toml b/vm/devices/storage/nvme_test/Cargo.toml index 1e0f49d914..bce8004032 100644 --- a/vm/devices/storage/nvme_test/Cargo.toml +++ b/vm/devices/storage/nvme_test/Cargo.toml @@ -16,6 +16,7 @@ scsi_buffers.workspace = true device_emulators.workspace = true pci_core.workspace = true pci_resources.workspace = true +tdisp.workspace = true chipset_device.workspace = true guestmem.workspace = true diff --git a/vm/devices/storage/nvme_test/src/pci.rs b/vm/devices/storage/nvme_test/src/pci.rs index d66464812a..49d2de3d09 100644 --- a/vm/devices/storage/nvme_test/src/pci.rs +++ b/vm/devices/storage/nvme_test/src/pci.rs @@ -43,6 +43,9 @@ use pci_core::spec::hwid::HardwareIds; use pci_core::spec::hwid::ProgrammingInterface; use pci_core::spec::hwid::Subclass; use std::sync::Arc; +use tdisp::TdispHostDeviceTarget; +use tdisp::TdispHostDeviceTargetEmulator; +use tdisp::test_helpers::make_null_tdisp_interface; use vmcore::device_state::ChangeDeviceState; use vmcore::save_restore::SaveError; use vmcore::save_restore::SaveRestore; @@ -64,6 +67,10 @@ pub struct NvmeFaultController { pci_fault_config: PciFaultConfig, #[inspect(skip)] fault_active: mesh::Cell, + + /// The NVMe fault controller is repurposed for use in TDISP tests. + #[inspect(skip)] + tdisp_controller: Option, } #[derive(Inspect)] @@ -120,6 +127,7 @@ impl NvmeFaultController { register_mmio: &mut dyn RegisterMmioIntercept, caps: NvmeFaultControllerCaps, mut fault_configuration: FaultConfiguration, + tdisp_capable: bool, ) -> Self { let (msix, msix_cap) = MsixEmulator::new(4, caps.msix_count, msi_target); let bars = DeviceBars::new() @@ -170,6 +178,15 @@ impl NvmeFaultController { fault_configuration, ); + tracing::debug!("creating fault controller: tdisp_capable = {tdisp_capable}"); + + // The fault controller is repurposed for use in TDISP tests. + let tdisp_controller = if tdisp_capable { + Some(make_null_tdisp_interface("fault-controller-test")) + } else { + None + }; + Self { cfg_space, msix, @@ -178,6 +195,7 @@ impl NvmeFaultController { qe_sizes, pci_fault_config, fault_active, + tdisp_controller, } } @@ -467,6 +485,7 @@ impl ChangeDeviceState for NvmeFaultController { workers, pci_fault_config: _, fault_active: _, + tdisp_controller: _, } = self; workers.reset().await; cfg_space.reset(); @@ -483,6 +502,20 @@ impl ChipsetDevice for NvmeFaultController { fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> { Some(self) } + + /// The NVMe fault controller is repurposed for use in TDISP tests. + fn supports_tdisp(&mut self) -> Option<&mut dyn TdispHostDeviceTarget> { + match &mut self.tdisp_controller { + Some(tdisp) => { + tracing::debug!("fault controller reporting TDISP support in ChipsetDevice"); + Some(tdisp) + } + None => { + tracing::debug!("fault controller not reporting TDISP support in ChipsetDevice"); + None + } + } + } } impl MmioIntercept for NvmeFaultController { diff --git a/vm/devices/storage/nvme_test/src/resolver.rs b/vm/devices/storage/nvme_test/src/resolver.rs index fc792d4be4..b7cec08919 100644 --- a/vm/devices/storage/nvme_test/src/resolver.rs +++ b/vm/devices/storage/nvme_test/src/resolver.rs @@ -65,6 +65,7 @@ impl AsyncResolveResource subsystem_id: resource.subsystem_id, }, resource.fault_config, + resource.tdisp_capable, ); for NamespaceDefinition { nsid, diff --git a/vm/devices/storage/nvme_test/src/tests/controller_tests.rs b/vm/devices/storage/nvme_test/src/tests/controller_tests.rs index 2bf57e6090..5e0cfb32fb 100644 --- a/vm/devices/storage/nvme_test/src/tests/controller_tests.rs +++ b/vm/devices/storage/nvme_test/src/tests/controller_tests.rs @@ -52,6 +52,7 @@ fn instantiate_controller( subsystem_id: Guid::new_random(), }, fault_configuration, + false, ); if let Some(intc) = int_controller { diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index cb9104fc51..f4098fec12 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -543,11 +543,6 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { match res { Ok(interface_info) => { - tracing::info!( - "Guest protocol negotiated successfully to: {:?}", - interface_info - ); - match TdispGuestProtocolType::from_i32(interface_info.guest_protocol_type) { Some(guest_protocol_type) => { if guest_protocol_type == TdispGuestProtocolType::Invalid { @@ -557,7 +552,10 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { Err(TdispGuestOperationError::InvalidGuestProtocolRequest) } else { self.guest_protocol_type = guest_protocol_type; - tracing::info!("Guest protocol negotiated: {interface_info:?}"); + tracing::info!( + "Guest protocol negotiated successfully to: {:?}", + interface_info + ); Ok(interface_info) } } diff --git a/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs b/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs index 6a141e1996..6a36c7ac01 100644 --- a/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs +++ b/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs @@ -864,6 +864,7 @@ async fn create_keepalive_test_config( .into_resource(), }], fault_config: fault_configuration, + tdisp_capable: false, } .into_resource(), }) diff --git a/vmm_tests/vmm_tests/tests/tests/x86_64.rs b/vmm_tests/vmm_tests/tests/tests/x86_64.rs index 39d7722fe7..e999dc910d 100644 --- a/vmm_tests/vmm_tests/tests/tests/x86_64.rs +++ b/vmm_tests/vmm_tests/tests/tests/x86_64.rs @@ -8,9 +8,13 @@ mod openhcl_uefi; mod storage; use anyhow::Context; +use guid::Guid; +use mesh::CellUpdater; use net_backend_resources::mac_address::MacAddress; use net_backend_resources::null::NullHandle; use nvme_resources::NvmeControllerHandle; +use nvme_resources::NvmeFaultControllerHandle; +use nvme_resources::fault::FaultConfiguration; use openvmm_defs::config::DeviceVtl; use openvmm_defs::config::VpciDeviceConfig; use petri::ApicMode; @@ -213,3 +217,56 @@ async fn vpci_filter(config: PetriVmBuilder) -> anyhow::Res vm.wait_for_clean_teardown().await?; Ok(()) } + +#[openvmm_test(openhcl_linux_direct_x64)] +async fn vpci_relay_tdisp_device( + config: PetriVmBuilder, +) -> anyhow::Result<()> { + const NVME_INSTANCE: Guid = guid::guid!("dce4ebad-182f-46c0-8d30-8446c1c62ab3"); + + // Create a VPCI device to relay to VTL0 and run basic TDISP end-to-end + // tests on it. + let (vm, agent) = config + .with_openhcl_command_line("OPENHCL_ENABLE_VPCI_RELAY=1") + // Tells VPCI relay that it should take the device through a mock TDISP + // flow with the OpenVMM host. + .with_openhcl_command_line("OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST") + .with_vmbus_redirect(true) + .modify_backend(move |b| { + b.with_custom_config(|c| { + c.vpci_devices.extend([VpciDeviceConfig { + vtl: DeviceVtl::Vtl0, + instance_id: NVME_INSTANCE, + + // The NVMe fault controller device is a fake NVMe + // controller that is repurposed for use in the TDISP test + // flow. + resource: NvmeFaultControllerHandle { + subsystem_id: Guid::new_random(), + msix_count: 1, + max_io_queues: 1, + namespaces: Vec::new(), + fault_config: FaultConfiguration::new(CellUpdater::new(false).cell()), + tdisp_capable: true, + } + .into_resource(), + }]) + }) + }) + .run() + .await?; + + let sh = agent.unix_shell(); + let lspci_output = cmd!(sh, "lspci").read().await?; + let devices = lspci_output + .lines() + .map(|line| line.trim().split_once(' ').ok_or_else(|| line.trim())) + .collect::>(); + + // The NVMe controller should be present after the HCL performs its TDISP test. + assert_eq!(devices, vec![Ok(("00:00.0", "Class 0108: 1414:00a9"))]); + + agent.power_off().await?; + vm.wait_for_clean_teardown().await?; + Ok(()) +} From 7e236205fb019b6655e9c19e1e7552f0ff31b357 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 10:40:25 -0800 Subject: [PATCH 21/32] refactor tdisp tests on NvmeFaultControllerHandle to allow a bit more flexibility --- Cargo.lock | 1 + .../disk_nvme/nvme_driver/src/tests.rs | 1 + vm/devices/storage/nvme_resources/Cargo.toml | 1 + vm/devices/storage/nvme_resources/src/lib.rs | 4 ++-- vm/devices/storage/nvme_test/src/pci.rs | 23 +++++-------------- vm/devices/storage/nvme_test/src/resolver.rs | 12 +++++++++- .../nvme_test/src/tests/controller_tests.rs | 2 +- .../tests/multiarch/openhcl_servicing.rs | 2 +- vmm_tests/vmm_tests/tests/tests/x86_64.rs | 2 +- 9 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9089c97e35..718c45dd84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4826,6 +4826,7 @@ dependencies = [ "guid", "mesh", "nvme_spec", + "tdisp", "vm_resource", "zerocopy 0.8.27", ] diff --git a/vm/devices/storage/disk_nvme/nvme_driver/src/tests.rs b/vm/devices/storage/disk_nvme/nvme_driver/src/tests.rs index 94ff743bce..e5df5ec20b 100644 --- a/vm/devices/storage/disk_nvme/nvme_driver/src/tests.rs +++ b/vm/devices/storage/disk_nvme/nvme_driver/src/tests.rs @@ -470,6 +470,7 @@ async fn test_nvme_fault_injection(driver: DefaultDriver, fault_configuration: F subsystem_id: Guid::new_random(), }, fault_configuration, + None, ); nvme.client() // 2MB namespace diff --git a/vm/devices/storage/nvme_resources/Cargo.toml b/vm/devices/storage/nvme_resources/Cargo.toml index 4c361fb6de..0bb859d67e 100644 --- a/vm/devices/storage/nvme_resources/Cargo.toml +++ b/vm/devices/storage/nvme_resources/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] +tdisp.workspace = true vm_resource.workspace = true nvme_spec.workspace = true mesh.workspace = true diff --git a/vm/devices/storage/nvme_resources/src/lib.rs b/vm/devices/storage/nvme_resources/src/lib.rs index 73eb000254..fc384ebb5b 100644 --- a/vm/devices/storage/nvme_resources/src/lib.rs +++ b/vm/devices/storage/nvme_resources/src/lib.rs @@ -57,8 +57,8 @@ pub struct NvmeFaultControllerHandle { pub namespaces: Vec, /// Configuration for the fault pub fault_config: FaultConfiguration, - /// Enable TDISP capabilities for the OpenVMM host. - pub tdisp_capable: bool, + /// Enable TDISP testing on this device when presented by a TDISP host. + pub enable_tdisp_tests: bool, } impl ResourceId for NvmeFaultControllerHandle { diff --git a/vm/devices/storage/nvme_test/src/pci.rs b/vm/devices/storage/nvme_test/src/pci.rs index 49d2de3d09..543b3d6152 100644 --- a/vm/devices/storage/nvme_test/src/pci.rs +++ b/vm/devices/storage/nvme_test/src/pci.rs @@ -44,8 +44,6 @@ use pci_core::spec::hwid::ProgrammingInterface; use pci_core::spec::hwid::Subclass; use std::sync::Arc; use tdisp::TdispHostDeviceTarget; -use tdisp::TdispHostDeviceTargetEmulator; -use tdisp::test_helpers::make_null_tdisp_interface; use vmcore::device_state::ChangeDeviceState; use vmcore::save_restore::SaveError; use vmcore::save_restore::SaveRestore; @@ -70,7 +68,7 @@ pub struct NvmeFaultController { /// The NVMe fault controller is repurposed for use in TDISP tests. #[inspect(skip)] - tdisp_controller: Option, + tdisp_interface: Option>, } #[derive(Inspect)] @@ -127,7 +125,7 @@ impl NvmeFaultController { register_mmio: &mut dyn RegisterMmioIntercept, caps: NvmeFaultControllerCaps, mut fault_configuration: FaultConfiguration, - tdisp_capable: bool, + tdisp_interface: Option>, ) -> Self { let (msix, msix_cap) = MsixEmulator::new(4, caps.msix_count, msi_target); let bars = DeviceBars::new() @@ -178,15 +176,6 @@ impl NvmeFaultController { fault_configuration, ); - tracing::debug!("creating fault controller: tdisp_capable = {tdisp_capable}"); - - // The fault controller is repurposed for use in TDISP tests. - let tdisp_controller = if tdisp_capable { - Some(make_null_tdisp_interface("fault-controller-test")) - } else { - None - }; - Self { cfg_space, msix, @@ -195,7 +184,7 @@ impl NvmeFaultController { qe_sizes, pci_fault_config, fault_active, - tdisp_controller, + tdisp_interface, } } @@ -485,7 +474,7 @@ impl ChangeDeviceState for NvmeFaultController { workers, pci_fault_config: _, fault_active: _, - tdisp_controller: _, + tdisp_interface: _, } = self; workers.reset().await; cfg_space.reset(); @@ -505,10 +494,10 @@ impl ChipsetDevice for NvmeFaultController { /// The NVMe fault controller is repurposed for use in TDISP tests. fn supports_tdisp(&mut self) -> Option<&mut dyn TdispHostDeviceTarget> { - match &mut self.tdisp_controller { + match &mut self.tdisp_interface { Some(tdisp) => { tracing::debug!("fault controller reporting TDISP support in ChipsetDevice"); - Some(tdisp) + Some(tdisp.as_mut()) } None => { tracing::debug!("fault controller not reporting TDISP support in ChipsetDevice"); diff --git a/vm/devices/storage/nvme_test/src/resolver.rs b/vm/devices/storage/nvme_test/src/resolver.rs index b7cec08919..5e5984982b 100644 --- a/vm/devices/storage/nvme_test/src/resolver.rs +++ b/vm/devices/storage/nvme_test/src/resolver.rs @@ -12,6 +12,7 @@ use nvme_resources::NamespaceDefinition; use nvme_resources::NvmeFaultControllerHandle; use pci_resources::ResolvePciDeviceHandleParams; use pci_resources::ResolvedPciDevice; +use tdisp::test_helpers::make_null_tdisp_interface; use thiserror::Error; use vm_resource::AsyncResolveResource; use vm_resource::ResolveError; @@ -54,6 +55,15 @@ impl AsyncResolveResource resource: NvmeFaultControllerHandle, input: ResolvePciDeviceHandleParams<'_>, ) -> Result { + // If TDISP tests are enabled, create a mock TDISP interface to expose + // for the device from OpenVMM. + let tdisp_interface: Option> = + if resource.enable_tdisp_tests { + Some(Box::new(make_null_tdisp_interface("fault-controller-test"))) + } else { + None + }; + let controller = NvmeFaultController::new( input.driver_source, input.guest_memory.clone(), @@ -65,7 +75,7 @@ impl AsyncResolveResource subsystem_id: resource.subsystem_id, }, resource.fault_config, - resource.tdisp_capable, + tdisp_interface, ); for NamespaceDefinition { nsid, diff --git a/vm/devices/storage/nvme_test/src/tests/controller_tests.rs b/vm/devices/storage/nvme_test/src/tests/controller_tests.rs index 5e0cfb32fb..98e5295feb 100644 --- a/vm/devices/storage/nvme_test/src/tests/controller_tests.rs +++ b/vm/devices/storage/nvme_test/src/tests/controller_tests.rs @@ -52,7 +52,7 @@ fn instantiate_controller( subsystem_id: Guid::new_random(), }, fault_configuration, - false, + None, ); if let Some(intc) = int_controller { diff --git a/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs b/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs index 6a36c7ac01..c41418eb62 100644 --- a/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs +++ b/vmm_tests/vmm_tests/tests/tests/multiarch/openhcl_servicing.rs @@ -864,7 +864,7 @@ async fn create_keepalive_test_config( .into_resource(), }], fault_config: fault_configuration, - tdisp_capable: false, + enable_tdisp_tests: false, } .into_resource(), }) diff --git a/vmm_tests/vmm_tests/tests/tests/x86_64.rs b/vmm_tests/vmm_tests/tests/tests/x86_64.rs index e999dc910d..bfc439b1d7 100644 --- a/vmm_tests/vmm_tests/tests/tests/x86_64.rs +++ b/vmm_tests/vmm_tests/tests/tests/x86_64.rs @@ -247,7 +247,7 @@ async fn vpci_relay_tdisp_device( max_io_queues: 1, namespaces: Vec::new(), fault_config: FaultConfiguration::new(CellUpdater::new(false).cell()), - tdisp_capable: true, + enable_tdisp_tests: true, } .into_resource(), }]) From 7888df60fb675b4ad56d527022ae9a1f6fc93156 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 10:52:32 -0800 Subject: [PATCH 22/32] cr: code cleanup --- vm/devices/storage/nvme_test/src/pci.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/vm/devices/storage/nvme_test/src/pci.rs b/vm/devices/storage/nvme_test/src/pci.rs index 543b3d6152..d8c8b22657 100644 --- a/vm/devices/storage/nvme_test/src/pci.rs +++ b/vm/devices/storage/nvme_test/src/pci.rs @@ -65,7 +65,6 @@ pub struct NvmeFaultController { pci_fault_config: PciFaultConfig, #[inspect(skip)] fault_active: mesh::Cell, - /// The NVMe fault controller is repurposed for use in TDISP tests. #[inspect(skip)] tdisp_interface: Option>, @@ -494,15 +493,14 @@ impl ChipsetDevice for NvmeFaultController { /// The NVMe fault controller is repurposed for use in TDISP tests. fn supports_tdisp(&mut self) -> Option<&mut dyn TdispHostDeviceTarget> { + tracing::debug!( + supported = self.tdisp_interface.is_some(), + "fault controller TDISP support in ChipsetDevice" + ); + match &mut self.tdisp_interface { - Some(tdisp) => { - tracing::debug!("fault controller reporting TDISP support in ChipsetDevice"); - Some(tdisp.as_mut()) - } - None => { - tracing::debug!("fault controller not reporting TDISP support in ChipsetDevice"); - None - } + Some(tdisp) => Some(tdisp.as_mut()), + None => None, } } } From 3520757b2aefff233bd3f8060b0d4fb2d93609e8 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 11:03:24 -0800 Subject: [PATCH 23/32] fmt --- Cargo.lock | 1 - vm/devices/storage/nvme_resources/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 718c45dd84..9089c97e35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4826,7 +4826,6 @@ dependencies = [ "guid", "mesh", "nvme_spec", - "tdisp", "vm_resource", "zerocopy 0.8.27", ] diff --git a/vm/devices/storage/nvme_resources/Cargo.toml b/vm/devices/storage/nvme_resources/Cargo.toml index 0bb859d67e..4c361fb6de 100644 --- a/vm/devices/storage/nvme_resources/Cargo.toml +++ b/vm/devices/storage/nvme_resources/Cargo.toml @@ -7,7 +7,6 @@ edition.workspace = true rust-version.workspace = true [dependencies] -tdisp.workspace = true vm_resource.workspace = true nvme_spec.workspace = true mesh.workspace = true From b8aecb13b17d92514d59165afbbb6915d132e830 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 11:22:55 -0800 Subject: [PATCH 24/32] fix doc tests --- vm/devices/storage/nvme_resources/src/fault.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/devices/storage/nvme_resources/src/fault.rs b/vm/devices/storage/nvme_resources/src/fault.rs index 319742395a..8fcad86766 100644 --- a/vm/devices/storage/nvme_resources/src/fault.rs +++ b/vm/devices/storage/nvme_resources/src/fault.rs @@ -141,6 +141,7 @@ pub struct PciFaultConfig { /// // Define `NamespaceDefinitions` here /// ], /// fault_config: fault_configuration, +/// enable_tdisp_tests: false, /// }; /// /// // Send the namespace change notification and await processing. @@ -368,6 +369,7 @@ pub struct CommandMatch { /// // Define NamespaceDefinitions here /// ], /// fault_config: fault_configuration, +/// enable_tdisp_tests: false, /// }; /// // Pass the controller handle in to the vm config to create and attach the fault controller. At this point the fault is inactive. /// fault_start_updater.set(true); // Activate the fault injection. From 68d67f8917f8698799730b4cef7056c689b294cc Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 12:12:26 -0800 Subject: [PATCH 25/32] cr: use expect() --- vm/devices/pci/vpci_relay/src/lib.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/vm/devices/pci/vpci_relay/src/lib.rs b/vm/devices/pci/vpci_relay/src/lib.rs index cc1d05e1c3..e12c3acb48 100644 --- a/vm/devices/pci/vpci_relay/src/lib.rs +++ b/vm/devices/pci/vpci_relay/src/lib.rs @@ -314,15 +314,9 @@ impl VpciRelay { .context("failed to initialize vpci device")?; let vpci_device = Arc::new(vpci_device); - if self.options.test_tdisp_flow { - match Self::tdisp_test_mock_flow(vpci_device.clone()).await { - Ok(_) => {} - Err(err) => { - // Panic on error, as this is a test and we want Petri to fail the test. - panic!("failed to exercise TDISP flow test: {:#}", err); - } - } - } + Self::tdisp_test_mock_flow(vpci_device.clone()) + .await + .expect("failed to exercise TDISP flow test"); let device_name = format!("assigned_device:vpci-{instance_id}"); let (device_unit, device) = chipset From 09543d4749f6d677260d51cf904eec585bd43fb2 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 12:25:03 -0800 Subject: [PATCH 26/32] cleanup how test creates the command payload --- Cargo.lock | 2 +- vm/devices/pci/vpci/Cargo.toml | 3 +-- vm/devices/pci/vpci/src/device.rs | 22 +++++++++++----------- vm/devices/pci/vpci_protocol/src/lib.rs | 2 ++ vm/devices/tdisp/src/lib.rs | 3 +++ 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9089c97e35..629c22d239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9895,12 +9895,12 @@ dependencies = [ "hvdef", "inspect", "mesh", + "openhcl_tdisp", "pal_async", "parking_lot", "pci_core", "task_control", "tdisp", - "tdisp_proto", "test_with_tracing", "thiserror 2.0.16", "tracelimit", diff --git a/vm/devices/pci/vpci/Cargo.toml b/vm/devices/pci/vpci/Cargo.toml index 390547a1fb..2116542bf6 100644 --- a/vm/devices/pci/vpci/Cargo.toml +++ b/vm/devices/pci/vpci/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] pci_core.workspace = true tdisp.workspace = true +openhcl_tdisp.workspace = true vpci_protocol.workspace = true device_emulators.workspace = true @@ -38,7 +39,5 @@ tracelimit.workspace = true tracing.workspace = true zerocopy.workspace = true -[dev-dependencies] -tdisp_proto.workspace = true [lints] workspace = true diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index b5561fef39..7ea9d26818 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -1417,6 +1417,7 @@ mod tests { use hvdef::HV_PAGE_SIZE; use inspect::Inspect; use inspect::InspectMut; + use openhcl_tdisp::make_get_device_interface_info_command; use pal_async::DefaultDriver; use pal_async::async_test; use pal_async::driver::SpawnDriver; @@ -1434,12 +1435,12 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; + use tdisp::GuestToHostResponseExt; use tdisp::TdispCommandResponseGetDeviceInterfaceInfo; use tdisp::TdispHostDeviceTargetEmulator; use tdisp::test_helpers::TDISP_MOCK_DEVICE_ID; use tdisp::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; use tdisp::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; - use tdisp_proto::GuestToHostResponseExt; use test_with_tracing::test; use thiserror::Error; use vmbus_async::queue::IncomingPacket; @@ -2167,15 +2168,11 @@ mod tests { let mut guest_driver = connected_device(&driver, pci.clone(), msi_controller); guest_driver.start_device(0x1000000).await; - let guest_protocol_type = TDISP_MOCK_GUEST_PROTOCOL as i32; - let command = tdisp::GuestToHostCommand { - device_id: SlotNumber::new().into_bits() as u64, - command: Some(tdisp::Command::GetDeviceInterfaceInfo( - tdisp_proto::TdispCommandRequestGetDeviceInterfaceInfo { - guest_protocol_type, - }, - )), - }; + let guest_protocol_type: tdisp::TdispGuestProtocolType = TDISP_MOCK_GUEST_PROTOCOL; + let command = make_get_device_interface_info_command( + SlotNumber::new().into_bits() as u64, + TDISP_MOCK_GUEST_PROTOCOL, + ); let response = guest_driver.send_tdisp_command(command).await; let response = response.get_response::(); @@ -2185,7 +2182,10 @@ mod tests { .interface_info .expect("interface_info must be set"); - assert_eq!(interface_info.guest_protocol_type, guest_protocol_type); + assert_eq!( + interface_info.guest_protocol_type, + guest_protocol_type as i32 + ); assert_eq!( interface_info.supported_features, TDISP_MOCK_SUPPORTED_FEATURES diff --git a/vm/devices/pci/vpci_protocol/src/lib.rs b/vm/devices/pci/vpci_protocol/src/lib.rs index e04558953b..e10a292260 100644 --- a/vm/devices/pci/vpci_protocol/src/lib.rs +++ b/vm/devices/pci/vpci_protocol/src/lib.rs @@ -819,6 +819,7 @@ pub struct VpciTdispCommandHeader { pub slot: SlotNumber, /// Length of the data payload to follow pub data_length: u64, + // // pub data: [u8; data_length...], } @@ -832,6 +833,7 @@ pub struct VpciTdispCommandHeaderReply { pub slot: SlotNumber, /// Length of the data payload to follow pub data_length: u64, + // // pub data: [u8; data_length...], } diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index f4098fec12..3d6afbf54e 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -45,7 +45,9 @@ use anyhow::Context; use parking_lot::Mutex; use std::sync::Arc; pub use tdisp_proto::GuestToHostCommand; +pub use tdisp_proto::GuestToHostCommandExt; pub use tdisp_proto::GuestToHostResponse; +pub use tdisp_proto::GuestToHostResponseExt; pub use tdisp_proto::TdispCommandResponseBind; pub use tdisp_proto::TdispCommandResponseGetDeviceInterfaceInfo; pub use tdisp_proto::TdispCommandResponseGetTdiReport; @@ -60,6 +62,7 @@ pub use tdisp_proto::TdispReportType; pub use tdisp_proto::TdispTdiState; pub use tdisp_proto::guest_to_host_command::Command; pub use tdisp_proto::guest_to_host_response::Response; + use tracing::instrument; /// Callback for receiving TDISP commands from the guest. From 3048e5131732d730f8425561a730dfc6443bb0e7 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Fri, 27 Feb 2026 13:41:35 -0800 Subject: [PATCH 27/32] fix petri failures due to missing if statement --- vm/devices/pci/vpci_relay/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vm/devices/pci/vpci_relay/src/lib.rs b/vm/devices/pci/vpci_relay/src/lib.rs index e12c3acb48..083a61c1d1 100644 --- a/vm/devices/pci/vpci_relay/src/lib.rs +++ b/vm/devices/pci/vpci_relay/src/lib.rs @@ -314,9 +314,11 @@ impl VpciRelay { .context("failed to initialize vpci device")?; let vpci_device = Arc::new(vpci_device); - Self::tdisp_test_mock_flow(vpci_device.clone()) - .await - .expect("failed to exercise TDISP flow test"); + if self.options.test_tdisp_flow { + Self::tdisp_test_mock_flow(vpci_device.clone()) + .await + .expect("failed to exercise TDISP flow test"); + } let device_name = format!("assigned_device:vpci-{instance_id}"); let (device_unit, device) = chipset From fed5384daa536b9ba6b401b047e0ef871ea4fc42 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 2 Mar 2026 14:46:15 -0800 Subject: [PATCH 28/32] cr: make -> new --- openhcl/openhcl_tdisp/src/lib.rs | 10 +++++----- vm/devices/pci/vpci/src/device.rs | 6 +++--- vm/devices/pci/vpci_client/src/lib.rs | 10 +++++----- vm/devices/pci/vpci_client/src/tests.rs | 4 ++-- vm/devices/storage/nvme_test/src/resolver.rs | 4 ++-- vm/devices/tdisp/src/test_helpers.rs | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/openhcl/openhcl_tdisp/src/lib.rs b/openhcl/openhcl_tdisp/src/lib.rs index 0739b1d30c..68a3f06cc4 100644 --- a/openhcl/openhcl_tdisp/src/lib.rs +++ b/openhcl/openhcl_tdisp/src/lib.rs @@ -85,7 +85,7 @@ pub trait TdispVirtualDeviceInterface: Send + Sync { } /// Creates a [`GuestToHostCommand`] for the `GetDeviceInterfaceInfo` command. -pub fn make_get_device_interface_info_command( +pub fn new_get_device_interface_info_command( device_id: u64, guest_protocol_type: TdispGuestProtocolType, ) -> GuestToHostCommand { @@ -100,7 +100,7 @@ pub fn make_get_device_interface_info_command( } /// Creates a [`GuestToHostCommand`] for the `Bind` command. -pub fn make_bind_command(device_id: u64) -> GuestToHostCommand { +pub fn new_bind_command(device_id: u64) -> GuestToHostCommand { GuestToHostCommand { device_id, command: Some(Command::Bind(TdispCommandRequestBind {})), @@ -108,7 +108,7 @@ pub fn make_bind_command(device_id: u64) -> GuestToHostCommand { } /// Creates a [`GuestToHostCommand`] for the `StartTdi` command. -pub fn make_start_tdi_command(device_id: u64) -> GuestToHostCommand { +pub fn new_start_tdi_command(device_id: u64) -> GuestToHostCommand { GuestToHostCommand { device_id, command: Some(Command::StartTdi(TdispCommandRequestStartTdi {})), @@ -116,7 +116,7 @@ pub fn make_start_tdi_command(device_id: u64) -> GuestToHostCommand { } /// Creates a [`GuestToHostCommand`] for the `GetTdiReport` command. -pub fn make_get_tdi_report_command( +pub fn new_get_tdi_report_command( device_id: u64, report_type: TdispReportType, ) -> GuestToHostCommand { @@ -129,7 +129,7 @@ pub fn make_get_tdi_report_command( } /// Creates a [`GuestToHostCommand`] for the `Unbind` command. -pub fn make_unbind_command(device_id: u64, reason: TdispGuestUnbindReason) -> GuestToHostCommand { +pub fn new_unbind_command(device_id: u64, reason: TdispGuestUnbindReason) -> GuestToHostCommand { GuestToHostCommand { device_id, command: Some(Command::Unbind(TdispCommandRequestUnbind { diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index 7ea9d26818..385d278693 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -1417,7 +1417,7 @@ mod tests { use hvdef::HV_PAGE_SIZE; use inspect::Inspect; use inspect::InspectMut; - use openhcl_tdisp::make_get_device_interface_info_command; + use openhcl_tdisp::new_get_device_interface_info_command; use pal_async::DefaultDriver; use pal_async::async_test; use pal_async::driver::SpawnDriver; @@ -2000,7 +2000,7 @@ mod tests { BarMemoryKind::Intercept(register_mmio.new_io_region("bar2", 0x2000)), ), ), - tdisp_interface: tdisp::test_helpers::make_null_tdisp_interface("vpci-unit-test"), + tdisp_interface: tdisp::test_helpers::new_null_tdisp_interface("vpci-unit-test"), } } @@ -2169,7 +2169,7 @@ mod tests { guest_driver.start_device(0x1000000).await; let guest_protocol_type: tdisp::TdispGuestProtocolType = TDISP_MOCK_GUEST_PROTOCOL; - let command = make_get_device_interface_info_command( + let command = new_get_device_interface_info_command( SlotNumber::new().into_bits() as u64, TDISP_MOCK_GUEST_PROTOCOL, ); diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index 2a532385e3..edc4ed4c04 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -657,7 +657,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { let target_protocol_type = TdispGuestProtocolType::AmdSevTioV1; let res = self - .send_tdisp_command(openhcl_tdisp::make_get_device_interface_info_command( + .send_tdisp_command(openhcl_tdisp::new_get_device_interface_info_command( self.dev.id.slot.into_bits() as u64, target_protocol_type, )) @@ -675,7 +675,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { async fn tdisp_bind_interface(&self) -> anyhow::Result<()> { let res = self - .send_tdisp_command(openhcl_tdisp::make_bind_command( + .send_tdisp_command(openhcl_tdisp::new_bind_command( self.dev.id.slot.into_bits() as u64, )) .await?; @@ -690,7 +690,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { async fn tdisp_start_device(&self) -> anyhow::Result<()> { let res = self - .send_tdisp_command(openhcl_tdisp::make_start_tdi_command( + .send_tdisp_command(openhcl_tdisp::new_start_tdi_command( self.dev.id.slot.into_bits() as u64, )) .await?; @@ -708,7 +708,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { report_type: &TdispReportType, ) -> anyhow::Result> { let res = self - .send_tdisp_command(openhcl_tdisp::make_get_tdi_report_command( + .send_tdisp_command(openhcl_tdisp::new_get_tdi_report_command( self.dev.id.slot.into_bits() as u64, *report_type, )) @@ -748,7 +748,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { async fn tdisp_unbind(&self, reason: TdispGuestUnbindReason) -> anyhow::Result<()> { let res = self - .send_tdisp_command(openhcl_tdisp::make_unbind_command( + .send_tdisp_command(openhcl_tdisp::new_unbind_command( self.dev.id.slot.into_bits() as u64, reason, )) diff --git a/vm/devices/pci/vpci_client/src/tests.rs b/vm/devices/pci/vpci_client/src/tests.rs index 1756bd121d..80c57da924 100644 --- a/vm/devices/pci/vpci_client/src/tests.rs +++ b/vm/devices/pci/vpci_client/src/tests.rs @@ -22,7 +22,7 @@ use tdisp::TdispHostDeviceTargetEmulator; use tdisp::test_helpers::TDISP_MOCK_DEVICE_ID; use tdisp::test_helpers::TDISP_MOCK_GUEST_PROTOCOL; use tdisp::test_helpers::TDISP_MOCK_SUPPORTED_FEATURES; -use tdisp::test_helpers::make_null_tdisp_interface; +use tdisp::test_helpers::new_null_tdisp_interface; use test_with_tracing::test; use vmbus_channel::simple::SimpleVmbusDevice; use vmcore::vpci_msi::MapVpciInterrupt; @@ -85,7 +85,7 @@ impl super::MemoryAccess for BusWrapper { fn make_noop_device() -> Arc> { Arc::new(CloseableMutex::new(NoopDevice { - tdisp_interface: make_null_tdisp_interface("vpci-unit-test"), + tdisp_interface: new_null_tdisp_interface("vpci-unit-test"), })) } diff --git a/vm/devices/storage/nvme_test/src/resolver.rs b/vm/devices/storage/nvme_test/src/resolver.rs index 5e5984982b..bfa8efc832 100644 --- a/vm/devices/storage/nvme_test/src/resolver.rs +++ b/vm/devices/storage/nvme_test/src/resolver.rs @@ -12,7 +12,7 @@ use nvme_resources::NamespaceDefinition; use nvme_resources::NvmeFaultControllerHandle; use pci_resources::ResolvePciDeviceHandleParams; use pci_resources::ResolvedPciDevice; -use tdisp::test_helpers::make_null_tdisp_interface; +use tdisp::test_helpers::new_null_tdisp_interface; use thiserror::Error; use vm_resource::AsyncResolveResource; use vm_resource::ResolveError; @@ -59,7 +59,7 @@ impl AsyncResolveResource // for the device from OpenVMM. let tdisp_interface: Option> = if resource.enable_tdisp_tests { - Some(Box::new(make_null_tdisp_interface("fault-controller-test"))) + Some(Box::new(new_null_tdisp_interface("fault-controller-test"))) } else { None }; diff --git a/vm/devices/tdisp/src/test_helpers.rs b/vm/devices/tdisp/src/test_helpers.rs index 5188f61d44..bd55ff27e0 100644 --- a/vm/devices/tdisp/src/test_helpers.rs +++ b/vm/devices/tdisp/src/test_helpers.rs @@ -53,7 +53,7 @@ impl TdispHostDeviceInterface for NullTdispHostInterface { } /// Implements the host side of the TDISP interface for a mock device that does nothing. -pub fn make_null_tdisp_interface(debug_device_id: &str) -> TdispHostDeviceTargetEmulator { +pub fn new_null_tdisp_interface(debug_device_id: &str) -> TdispHostDeviceTargetEmulator { TdispHostDeviceTargetEmulator::new( Arc::new(Mutex::new(NullTdispHostInterface {})), debug_device_id, From e0181945f8a3f76d9b133a89bb6956c13621b83d Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 2 Mar 2026 14:53:29 -0800 Subject: [PATCH 29/32] cr: use read_all() instead of read() --- vm/devices/pci/vpci/src/device.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index 385d278693..be2c49efb7 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -490,7 +490,7 @@ fn parse_packet(packet: &queue::DataPacket<'_, T>) -> Result { let (header, rest) = Ref::<_, protocol::VpciTdispCommandHeader>::from_prefix(buf) - .map_err(|_| PacketError::PacketTooSmall("tdisp_command_header"))?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759) + .map_err(|_| PacketError::PacketTooSmall("tdisp_command_header"))?; let data_len = header.data_length as usize; if data_len > MAX_VPCI_TDISP_COMMAND_SIZE { @@ -1752,9 +1752,10 @@ mod tests { assert_eq!(completion.transaction_id(), transaction_id); // Read the entire completion payload at once before splitting it. - let mut reader = completion.reader(); - let mut all_bytes = vec![0u8; reader.len()]; - reader.read(&mut all_bytes).unwrap(); + let all_bytes = completion + .reader() + .read_all() + .expect("reader should read entire payload"); let (reply_header, proto_bytes) = protocol::VpciTdispCommandHeaderReply::read_from_prefix(&all_bytes) From 144f9c557bb80447eaf944594c09b36ab986deb5 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 2 Mar 2026 14:54:02 -0800 Subject: [PATCH 30/32] cr: remove reference to envvar --- vm/devices/pci/vpci_relay/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/devices/pci/vpci_relay/src/lib.rs b/vm/devices/pci/vpci_relay/src/lib.rs index 083a61c1d1..4e1f52bb46 100644 --- a/vm/devices/pci/vpci_relay/src/lib.rs +++ b/vm/devices/pci/vpci_relay/src/lib.rs @@ -368,7 +368,6 @@ impl VpciRelay { } /// Exercises a mocked TDISP flow for emulated TDISP devices produced by OpenVMM tests. - /// Configured with the OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST environment variable. async fn tdisp_test_mock_flow(device: Arc) -> anyhow::Result<()> { // For now, exercise just the "get device interface" flow and ensure that the device responds as // TDISP capable and with the right mocked device information. From 7a018fc88098e5c4bd0f0c8dd047729b34bc78c1 Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 2 Mar 2026 15:00:56 -0800 Subject: [PATCH 31/32] cr: cleanup tracing to be more trace and less log --- vm/devices/tdisp/src/lib.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/vm/devices/tdisp/src/lib.rs b/vm/devices/tdisp/src/lib.rs index 3d6afbf54e..809ffc21d6 100644 --- a/vm/devices/tdisp/src/lib.rs +++ b/vm/devices/tdisp/src/lib.rs @@ -230,16 +230,6 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { } } let state_after = self.machine.state(); - - match error { - TdispGuestOperationError::Success => { - tracing::info!("tdisp_handle_guest_command: Success"); - } - _ => { - tracing::error!("tdisp_handle_guest_command: Error: {error:?}"); - } - } - let error_code: TdispGuestOperationErrorCode = error.into(); let resp = GuestToHostResponse { result: error_code.into(), @@ -248,7 +238,14 @@ impl TdispHostDeviceTarget for TdispHostDeviceTargetEmulator { response, }; - tracing::info!("tdisp_handle_guest_command: response = {resp:?}"); + match error { + TdispGuestOperationError::Success => { + tracing::info!(?resp, "tdisp_handle_guest_command success"); + } + _ => { + tracing::error!(?resp, "tdisp_handle_guest_command error"); + } + } Ok(resp) } @@ -550,28 +547,30 @@ impl TdispGuestRequestInterface for TdispHostStateMachine { Some(guest_protocol_type) => { if guest_protocol_type == TdispGuestProtocolType::Invalid { tracing::error!( - "Guest protocol negotiated with invalid value: {guest_protocol_type:?}" + ?guest_protocol_type, + "Guest protocol negotiated with invalid value" ); Err(TdispGuestOperationError::InvalidGuestProtocolRequest) } else { self.guest_protocol_type = guest_protocol_type; tracing::info!( - "Guest protocol negotiated successfully to: {:?}", - interface_info + ?interface_info, + "Guest protocol negotiated successfully to" ); Ok(interface_info) } } None => { tracing::error!( - "Guest protocol negotiated with none value: {interface_info:?}" + ?interface_info, + "Guest protocol negotiated with none value" ); Err(TdispGuestOperationError::InvalidGuestProtocolRequest) } } } Err(e) => { - tracing::error!("Failed to negotiate protocol with host interface: {e:?}"); + tracing::error!(?e, "Failed to negotiate protocol with host interface"); Err(TdispGuestOperationError::HostFailedToProcessCommand) } } From c8418f284b719840fa0828ea74883f1be62bf87a Mon Sep 17 00:00:00 2001 From: mfrohlich Date: Mon, 2 Mar 2026 15:03:01 -0800 Subject: [PATCH 32/32] cr: remove get_ from ext trait names --- vm/devices/pci/vpci/src/device.rs | 2 +- vm/devices/pci/vpci_client/src/lib.rs | 14 +++++++------- vm/devices/tdisp_proto/src/lib.rs | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/vm/devices/pci/vpci/src/device.rs b/vm/devices/pci/vpci/src/device.rs index be2c49efb7..496a1bae82 100644 --- a/vm/devices/pci/vpci/src/device.rs +++ b/vm/devices/pci/vpci/src/device.rs @@ -2176,7 +2176,7 @@ mod tests { ); let response = guest_driver.send_tdisp_command(command).await; - let response = response.get_response::(); + let response = response.response::(); match response { Ok(info_resp) => { let interface_info = info_resp diff --git a/vm/devices/pci/vpci_client/src/lib.rs b/vm/devices/pci/vpci_client/src/lib.rs index edc4ed4c04..6a5798d1ce 100644 --- a/vm/devices/pci/vpci_client/src/lib.rs +++ b/vm/devices/pci/vpci_client/src/lib.rs @@ -637,12 +637,12 @@ impl TdispVirtualDeviceInterface for VpciDevice { anyhow::anyhow!("failed to send tdisp command") })?; - match res.get_error_code() { + match res.error_code() { Some(TdispGuestOperationErrorCode::Success) => Ok(res), _ => { let err_msg = format!( "send_tdisp_command {:?} failed because host responded with an error: {:?}", - payload.get_type_name(), + payload.type_name(), res.result ); @@ -663,7 +663,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { )) .await?; - match res.get_response::() { + match res.response::() { Ok(info) => info.interface_info.ok_or_else(|| { anyhow::anyhow!("missing interface_info after validation, this should never happen") }), @@ -680,7 +680,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { )) .await?; - match res.get_response::() { + match res.response::() { Ok(_) => Ok(()), Err(err) => Err(anyhow::anyhow!( "error response in tdisp_bind_interface: {err}" @@ -695,7 +695,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { )) .await?; - match res.get_response::() { + match res.response::() { Ok(_) => Ok(()), Err(err) => Err(anyhow::anyhow!( "error response in tdisp_start_device: {err}" @@ -714,7 +714,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { )) .await?; - match res.get_response::() { + match res.response::() { Ok(r) => Ok(r.report_buffer), Err(err) => Err(anyhow::anyhow!( "error response in tdisp_get_device_report: {err}" @@ -754,7 +754,7 @@ impl TdispVirtualDeviceInterface for VpciDevice { )) .await?; - match res.get_response::() { + match res.response::() { Ok(_) => Ok(()), Err(err) => Err(anyhow::anyhow!("error response in tdisp_unbind: {err}")), } diff --git a/vm/devices/tdisp_proto/src/lib.rs b/vm/devices/tdisp_proto/src/lib.rs index cdaa56eda3..8cddd3d86a 100644 --- a/vm/devices/tdisp_proto/src/lib.rs +++ b/vm/devices/tdisp_proto/src/lib.rs @@ -20,11 +20,11 @@ include!(concat!(env!("OUT_DIR"), "/tdisp.rs")); pub trait GuestToHostCommandExt { /// Returns the command type name of the command. - fn get_type_name(&self) -> Option<&str>; + fn type_name(&self) -> Option<&str>; } impl GuestToHostCommandExt for GuestToHostCommand { - fn get_type_name(&self) -> Option<&str> { + fn type_name(&self) -> Option<&str> { match self.command { Some(Command::GetDeviceInterfaceInfo(_)) => Some("GetDeviceInterfaceInfo"), Some(Command::Bind(_)) => Some("Bind"), @@ -36,7 +36,7 @@ impl GuestToHostCommandExt for GuestToHostCommand { } } -/// Implemented by each response payload type so that [`GuestToHostResponseExt::get_response`] +/// Implemented by each response payload type so that [`GuestToHostResponseExt::response`] /// can extract it generically from a [`Response`] oneof variant. pub trait GuestToHostResponseVariant: Sized { fn from_response_variant(response: Response) -> Option; @@ -90,10 +90,10 @@ impl GuestToHostResponseVariant for TdispCommandResponseUnbind { /// Provides helper methods for common operations on [`GuestToHostResponse`]. pub trait GuestToHostResponseExt { /// Returns the error code of the response, if any. - fn get_error_code(&self) -> Option; + fn error_code(&self) -> Option; /// Returns the packet type name of the response. - fn get_type_name(&self) -> Option<&str>; + fn type_name(&self) -> Option<&str>; /// Consumes the response and returns the inner payload if the result is /// [`TdispGuestOperationError::Success`] and the oneof variant matches `T`. @@ -101,17 +101,17 @@ pub trait GuestToHostResponseExt { /// /// # Example /// ```ignore - /// let bind = resp.get_response::()?; + /// let bind = resp.response::()?; /// ``` - fn get_response(self) -> Result; + fn response(self) -> Result; } impl GuestToHostResponseExt for GuestToHostResponse { - fn get_error_code(&self) -> Option { + fn error_code(&self) -> Option { TdispGuestOperationErrorCode::from_i32(self.result) } - fn get_type_name(&self) -> Option<&str> { + fn type_name(&self) -> Option<&str> { match self.response { Some(Response::GetDeviceInterfaceInfo(_)) => Some("GetDeviceInterfaceInfo"), Some(Response::Bind(_)) => Some("Bind"), @@ -122,8 +122,8 @@ impl GuestToHostResponseExt for GuestToHostResponse { } } - fn get_response(self) -> Result { - match self.get_error_code() { + fn response(self) -> Result { + match self.error_code() { Some(TdispGuestOperationErrorCode::Success) => { match self.response.and_then(T::from_response_variant) { Some(r) => Ok(r),