Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ad4911e
Add debug device id
mfrohlich-msft Oct 2, 2025
5057213
add back cargo.toml
mfrohlich-msft Feb 23, 2026
6dde18b
refactor openhcl_tdisp to new types from proto crate
mfrohlich-msft Feb 23, 2026
72341dd
refactor tdisp_proto to export needed deps into openhcl_tdisp
mfrohlich-msft Feb 23, 2026
6f485c2
Add device report deserialization code back
mfrohlich-msft Feb 23, 2026
5c7a906
Remove device report structures from proto because these will still p…
mfrohlich-msft Feb 23, 2026
6237620
add helpers to get responses, error codes, and making new packets
mfrohlich-msft Feb 23, 2026
c569f41
add vpci_client interfaces to dispatch proto commands
mfrohlich-msft Feb 23, 2026
fa8cc97
todo reword
mfrohlich-msft Feb 24, 2026
b5291ac
cleanup
mfrohlich-msft Feb 24, 2026
bad4ed4
add explicit size check
mfrohlich-msft Feb 24, 2026
e39a1b7
appease the copilot
mfrohlich-msft Feb 24, 2026
99f9d59
initial framework for tdisp vpci unit tests
mfrohlich-msft Feb 24, 2026
6d1476c
add tdisp command packet processing for openvmm vpci impl
mfrohlich-msft Feb 24, 2026
19fff9b
test passing now
mfrohlich-msft Feb 25, 2026
1565f67
more tests within vpci_client
mfrohlich-msft Feb 25, 2026
7ab51f8
xfmt
mfrohlich-msft Feb 25, 2026
1f0cad6
change V1.0 to V1 in interface names
mfrohlich-msft Feb 25, 2026
c38badd
Add support for OPENHCL_TEST_CONFIG=TDISP_VPCI_FLOW_TEST
mfrohlich-msft Feb 25, 2026
6491888
Implement end-to-end petri test for basic TDISP flow using the fault …
mfrohlich-msft Feb 26, 2026
7e23620
refactor tdisp tests on NvmeFaultControllerHandle to allow a bit more…
mfrohlich-msft Feb 27, 2026
7888df6
cr: code cleanup
mfrohlich-msft Feb 27, 2026
3520757
fmt
mfrohlich-msft Feb 27, 2026
b8aecb1
fix doc tests
mfrohlich-msft Feb 27, 2026
68d67f8
cr: use expect()
mfrohlich-msft Feb 27, 2026
09543d4
cleanup how test creates the command payload
mfrohlich-msft Feb 27, 2026
3048e51
fix petri failures due to missing if statement
mfrohlich-msft Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ version = "0.0.0"
dependencies = [
"inspect",
"mesh",
"tdisp",
]

[[package]]
Expand All @@ -577,6 +578,7 @@ dependencies = [
"chipset_device",
"guestmem",
"inspect",
"tdisp",
"vm_resource",
"vmcore",
]
Expand Down Expand Up @@ -4864,6 +4866,7 @@ dependencies = [
"pci_resources",
"scsi_buffers",
"task_control",
"tdisp",
"thiserror 2.0.16",
"tracelimit",
"tracing",
Expand Down Expand Up @@ -5028,6 +5031,15 @@ dependencies = [
"vmcore",
]

[[package]]
name = "openhcl_tdisp"
version = "0.0.0"
dependencies = [
"anyhow",
"tdisp",
"tdisp_proto",
]

[[package]]
name = "openssl"
version = "0.10.73"
Expand Down Expand Up @@ -7375,11 +7387,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]]
Expand All @@ -7389,6 +7403,7 @@ dependencies = [
"inspect",
"prost",
"prost-build",
"thiserror 2.0.16",
]

[[package]]
Expand Down Expand Up @@ -9880,10 +9895,12 @@ dependencies = [
"hvdef",
"inspect",
"mesh",
"openhcl_tdisp",
"pal_async",
"parking_lot",
"pci_core",
"task_control",
"tdisp",
"test_with_tracing",
"thiserror 2.0.16",
"tracelimit",
Expand All @@ -9909,11 +9926,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",
Expand Down Expand Up @@ -9950,6 +9969,7 @@ dependencies = [
"inspect",
"memory_range",
"mesh",
"openhcl_tdisp",
"pci_core",
"slab",
"sparse_mmap",
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
16 changes: 16 additions & 0 deletions openhcl/openhcl_tdisp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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]
tdisp.workspace = true
tdisp_proto.workspace = true

anyhow.workspace = true

[lints]
workspace = true
139 changes: 139 additions & 0 deletions openhcl/openhcl_tdisp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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;

// 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;
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::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
/// 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<Output = Result<GuestToHostResponse, anyhow::Error>> + Send;

/// Get the TDISP interface info for the device.
fn tdisp_get_device_interface_info(
&self,
) -> impl Future<Output = anyhow::Result<TdispDeviceInterfaceInfo>> + 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<Output = anyhow::Result<()>> + 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<Output = anyhow::Result<()>> + 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<Output = anyhow::Result<Vec<u8>>> + Send;

/// Request a TDI report from the TDI or physical device.
fn tdisp_get_tdi_report(&self) -> impl Future<Output = anyhow::Result<TdiReportStruct>> + Send;

/// Request the TDI device id from the vpci channel.
fn tdisp_get_tdi_device_id(&self) -> impl Future<Output = anyhow::Result<u64>> + Send;

/// Request to unbind the device and return to the Unlocked state.
fn tdisp_unbind(
&self,
reason: TdispGuestUnbindReason,
) -> impl Future<Output = anyhow::Result<()>> + 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,
},
)),
}
}

/// 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,
})),
}
}
4 changes: 4 additions & 0 deletions openhcl/underhill_core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)),
}
}
Expand Down
7 changes: 7 additions & 0 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions vm/chipset_device/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rust-version.workspace = true
[dependencies]
mesh.workspace = true
inspect.workspace = true
tdisp.workspace = true

[lints]
workspace = true
7 changes: 7 additions & 0 deletions vm/chipset_device/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::TdispHostDeviceTarget> {
None
}
}

/// Shared by `mmio` and `pio`
Expand Down
1 change: 1 addition & 0 deletions vm/chipset_device_resources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions vm/chipset_device_resources/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions vm/devices/pci/vpci/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ rust-version.workspace = true

[dependencies]
pci_core.workspace = true
tdisp.workspace = true
openhcl_tdisp.workspace = true
vpci_protocol.workspace = true

device_emulators.workspace = true
Expand Down Expand Up @@ -36,5 +38,6 @@ thiserror.workspace = true
tracelimit.workspace = true
tracing.workspace = true
zerocopy.workspace = true

[lints]
workspace = true
Loading