Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d070c52
add openhcl_tdisp_resources and openhcl_tdisp modules
mfrohlich-msft Oct 1, 2025
23c2fc9
comment cleanup
mfrohlich-msft Oct 1, 2025
480fe56
add tdisp module
mfrohlich-msft Oct 1, 2025
98d45c1
add modules to root level cargo
mfrohlich-msft Oct 1, 2025
e3e8a85
cleanup docs in tdisp module
mfrohlich-msft Oct 1, 2025
f63fd42
old trait not needed
mfrohlich-msft Oct 1, 2025
3c9a604
refactor away openhcl_tdisp_resources crate into just openhcl_tdisp
mfrohlich-msft Oct 1, 2025
02f1c84
cleanup devicereport
mfrohlich-msft Oct 2, 2025
a53f532
cleanup lib.rs
mfrohlich-msft Oct 2, 2025
e017631
cleanup serialize.rs
mfrohlich-msft Oct 2, 2025
0494793
cleanup openhcl_tdisp lib.rs
mfrohlich-msft Oct 2, 2025
5c69ec9
cleanup tracing
mfrohlich-msft Oct 2, 2025
55c9d67
remove panic in favor of error
mfrohlich-msft Oct 2, 2025
8a09c39
remove panic in favor of unknown state for TdispGuestOperationError
mfrohlich-msft Oct 2, 2025
6df2e7d
fix trace
mfrohlich-msft Oct 2, 2025
ece4b27
add license headers
mfrohlich-msft Oct 2, 2025
5208e08
ensure openhcl_tdisp builds in underhill core
mfrohlich-msft Oct 2, 2025
c940ce4
cr: sort pub use, remove bad doc
mfrohlich-msft Oct 2, 2025
bbf7b3a
cr: split combined use statements and sort pub use
mfrohlich-msft Oct 2, 2025
064cca7
cr: typo
mfrohlich-msft Oct 2, 2025
63a7002
cr: remove accidential Debug implementation
mfrohlich-msft Oct 2, 2025
616ca28
cr: refactor TdispCommandId to open_enum
mfrohlich-msft Oct 2, 2025
735fb95
only try to deserialize a payload if there is one supplied in the packet
mfrohlich-msft Oct 2, 2025
09c80c3
cr: refactor TdispReportType for open_enum
mfrohlich-msft Oct 2, 2025
f25f2fc
cr: refactor TdispTdiState for open_enum
mfrohlich-msft Oct 2, 2025
7b3d2ab
cr: refactor TdispGuestOperationError for open_enum (also allow it to…
mfrohlich-msft Oct 2, 2025
0adbc6d
cr: remove default impls
mfrohlich-msft Oct 2, 2025
dfac5c0
cr: cleanup From<> and Into<> traits
mfrohlich-msft Oct 2, 2025
d95b830
cr: repr(C) on structures to ensure they have a known serialized layout
mfrohlich-msft Oct 2, 2025
81c2bf0
cr: allow setting debug device id in constructor
mfrohlich-msft Oct 2, 2025
ecb4b24
xfmt --fix
mfrohlich-msft Oct 2, 2025
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
24 changes: 24 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4999,6 +4999,15 @@ dependencies = [
"vmcore",
]

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

[[package]]
name = "openssl"
version = "0.10.73"
Expand Down Expand Up @@ -7073,6 +7082,20 @@ dependencies = [
"x86defs",
]

[[package]]
name = "tdisp"
version = "0.0.0"
dependencies = [
"anyhow",
"bitfield-struct 0.11.0",
"open_enum",
"parking_lot",
"static_assertions",
"thiserror 2.0.16",
"tracing",
"zerocopy 0.8.25",
]

[[package]]
name = "tdx_guest_device"
version = "0.0.0"
Expand Down Expand Up @@ -7841,6 +7864,7 @@ dependencies = [
"nvme_resources",
"openhcl_attestation_protocol",
"openhcl_dma_manager",
"openhcl_tdisp",
"pal",
"pal_async",
"pal_uring",
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ lower_vtl_permissions_guard = { path = "openhcl/lower_vtl_permissions_guard" }
minimal_rt = { path = "openhcl/minimal_rt" }
minimal_rt_build = { path = "openhcl/minimal_rt_build" }
openhcl_dma_manager = { path = "openhcl/openhcl_dma_manager" }
openhcl_tdisp = { path = "openhcl/openhcl_tdisp" }
sidecar_client = { path = "openhcl/sidecar_client" }
sidecar_defs = { path = "openhcl/sidecar_defs" }
tee_call = { path = "openhcl/tee_call" }
Expand Down Expand Up @@ -252,6 +253,7 @@ nvme_resources = { path = "vm/devices/storage/nvme_resources" }
nvme_spec = { path = "vm/devices/storage/nvme_spec" }
nvme_test = { path = "vm/devices/storage/nvme_test" }
storage_string = { path = "vm/devices/storage/storage_string" }
tdisp = { path = "vm/devices/tdisp" }
vmswitch = { path = "vm/devices/net/vmswitch" }
pci_bus = { path = "vm/devices/pci/pci_bus" }
pci_core = { path = "vm/devices/pci/pci_core" }
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]
inspect.workspace = true
tdisp.workspace = true

anyhow.workspace = true

[lints]
workspace = true
81 changes: 81 additions & 0 deletions openhcl/openhcl_tdisp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//!
//! This module provides resources and traits for a TDISP client device
//! interface for assigned devices in OpenHCL.
//!
//! See: `vm/tdisp` for more information.

pub use tdisp::TDISP_INTERFACE_VERSION_MAJOR;
pub use tdisp::TDISP_INTERFACE_VERSION_MINOR;
pub use tdisp::TdispCommandId;

use inspect::Inspect;
use std::future::Future;
use tdisp::GuestToHostCommand;
use tdisp::GuestToHostResponse;
use tdisp::TdispGuestUnbindReason;
use tdisp::devicereport::TdiReportStruct;
use tdisp::devicereport::TdispReportType;

/// Represents a TDISP device assigned to a guest partition. This trait allows
/// the guest to send TDISP commands to the host through the backing interface.
/// [TDISP TODO] Change out `anyhow` for a `TdispError` type?
pub trait ClientDevice: Send + Sync + Inspect {
/// Send a TDISP command to the host through the backing interface.
fn tdisp_command_to_host(
&self,
command: GuestToHostCommand,
) -> anyhow::Result<GuestToHostResponse>;

/// Checks if the device is TDISP capable and returns the device interface info if so.
fn tdisp_get_device_interface_info(&self) -> anyhow::Result<tdisp::TdispDeviceInterfaceInfo>;

/// Bind the device to the current partition and transition to Locked.
fn tdisp_bind_interface(&self) -> anyhow::Result<()>;
}

/// Represents a TDISP device assigned to a guest partition that can be used to
/// send TDISP commands to the host through a backing interface.
pub trait VpciTdispInterface: Send + Sync {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can all these functions just be async instead of doing manual impl Futures?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick jump on this PR :)

I was recommended to use impl Future for traits instead of async by rust_analyzer. I believe this has to do with the way that async needs to express itself in traits vs. its implementations. I do not really understand it further than that.

/// 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<tdisp::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 from the Locked state to the Run state.
/// This allows for attestation and for resources to be accepted into the guest context.
fn tdisp_start_device(&self) -> impl Future<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;
}
1 change: 1 addition & 0 deletions openhcl/underhill_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ netvsp.workspace = true
nvme_driver.workspace = true
nvme_resources.workspace = true
openhcl_dma_manager.workspace = true
openhcl_tdisp.workspace = true
scsi_core.workspace = true
scsidisk.workspace = true
scsidisk_resources.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion openhcl/underhill_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ mod vp;
mod vpci;
mod worker;
mod wrapped_partition;

// `pub` so that the missing_docs warning fires for options without
// documentation.
pub use options::Options;
Expand Down Expand Up @@ -67,6 +66,7 @@ use mesh_worker::WorkerHost;
use mesh_worker::WorkerHostRunner;
use mesh_worker::launch_local_worker;
use mesh_worker::register_workers;
use openhcl_tdisp as _;
use pal_async::DefaultDriver;
use pal_async::DefaultPool;
use pal_async::task::Spawn;
Expand Down
20 changes: 20 additions & 0 deletions vm/devices/tdisp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[package]
name = "tdisp"
edition.workspace = true
rust-version.workspace = true

[dependencies]
anyhow.workspace = true
bitfield-struct.workspace = true
open_enum.workspace = true
parking_lot.workspace = true
static_assertions.workspace = true
thiserror.workspace = true
tracing.workspace = true
zerocopy.workspace = true

[lints]
workspace = true
141 changes: 141 additions & 0 deletions vm/devices/tdisp/src/command.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
}

/// 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.
}
Loading
Loading