Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ version = "0.0.0"
dependencies = [
"arbitrary",
"inspect",
"ipmi_protocol",
"local_clock",
"memory_range",
"mesh",
Expand Down Expand Up @@ -2939,6 +2940,7 @@ version = "0.0.0"
dependencies = [
"bitfield-struct 0.11.0",
"guid",
"ipmi_protocol",
"open_enum",
"serde",
"serde_helpers",
Expand All @@ -2952,6 +2954,7 @@ name = "get_resources"
version = "0.0.0"
dependencies = [
"inspect",
"ipmi_protocol",
"mesh",
"smbios_defs",
"thiserror 2.0.16",
Expand Down Expand Up @@ -3167,6 +3170,7 @@ dependencies = [
"hvdef",
"inspect",
"inspect_counters",
"ipmi_protocol",
"jiff",
"mesh",
"pal_async",
Expand Down Expand Up @@ -4075,6 +4079,34 @@ dependencies = [
"thiserror 2.0.16",
]

[[package]]
name = "ipmi_kcs"
version = "0.0.0"
dependencies = [
"async-trait",
"chipset_device",
"chipset_device_resources",
"chipset_resources",
"inspect",
"ipmi_protocol",
"local_clock",
"mesh",
"parking_lot",
"test_with_tracing",
"thiserror 2.0.16",
"vm_resource",
"vmcore",
"zerocopy",
]

[[package]]
name = "ipmi_protocol"
version = "0.0.0"
dependencies = [
"static_assertions",
"zerocopy",
]

[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
Expand Down Expand Up @@ -6027,6 +6059,7 @@ dependencies = [
"firmware_uefi",
"guest_watchdog",
"hyperv_ic",
"ipmi_kcs",
"mesh_worker",
"missing_dev",
"nvme",
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ cvm_tracing = { path = "vm/cvm_tracing" }
chipset = { path = "vm/devices/chipset" }
chipset_legacy = { path = "vm/devices/chipset_legacy" }
chipset_resources = { path = "vm/devices/chipset_resources" }
ipmi_kcs = { path = "vm/devices/chipset/ipmi_kcs" }
ipmi_protocol = { path = "vm/devices/chipset/ipmi_protocol" }
firmware_pcat = { path = "vm/devices/firmware/firmware_pcat" }
firmware_uefi = { path = "vm/devices/firmware/firmware_uefi" }
firmware_uefi_custom_vars = { path = "vm/devices/firmware/firmware_uefi_custom_vars" }
Expand Down
1 change: 1 addition & 0 deletions Guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
- [framebuffer]()
- [input]()
- [Emulated]()
- [IPMI KCS](./reference/emulated/ipmi_kcs.md)
- [vTPM]()
- [NVMe]()
- [Overview](./reference/emulated/NVMe/overview.md)
Expand Down
54 changes: 54 additions & 0 deletions Guide/src/reference/emulated/ipmi_kcs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# IPMI KCS

OpenVMM provides a minimal virtual IPMI baseboard management controller (BMC)
using the Keyboard Controller Style (KCS) system interface. The device allows
UEFI guests to write and query a bounded System Event Log (SEL).

The implementation is provided by the `ipmi_kcs` crate. Wire-level command and
record definitions are in the `ipmi_protocol` crate.

## Configuration

The device is created when the platform configuration enables IPMI. OpenHCL
accepts this setting only for UEFI guests; PCAT and Linux-direct boot modes are
not supported.

The guest-visible register interface depends on the architecture:

| Architecture | Interface | Registers |
| --- | --- | --- |
| x86-64 | Port I/O | Data at `0xCA2`; status/command at `0xCA3` |
| AArch64 | MMIO | Data at `0xEFFE7000`; status/command at `0xEFFE7004` |

Only one-byte register accesses are supported.

## Supported commands

The virtual BMC implements the IPMI application `Get Device ID` command and
these SEL storage commands:

- Get SEL Info
- Reserve SEL
- Get SEL Entry
- Add SEL Entry
- Clear SEL
- Get SEL Time
- Set SEL Time

The SEL stores at most 128 records. Reservations are exposed for guest software
compatibility but are not enforced because the device has one serialized KCS
requestor and no independent SEL mutators.

Completed SEL records are retained by the device and forwarded to the host on
a best-effort basis. Forwarding is limited to 256 records per trusted
wall-clock second; reaching that limit does not remove records from the SEL.

## Servicing

Saved state includes the KCS transaction, SEL records, record allocation state,
reservation identifier, and guest-selected SEL time offset. Diagnostic
forwarding counters and rate-limiter state are reset after restore.

See the
[`ipmi_kcs` rustdoc](https://openvmm.dev/rustdoc/ipmi_kcs/index.html) for the
device API.
2 changes: 2 additions & 0 deletions openhcl/openhcl_attestation_protocol/src/igvm_attest/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ pub mod runtime_claims {
pub console_enabled: bool,
/// Whether the serial console, if enabled, is interactive
pub interactive_console_enabled: bool,
/// Whether the IPMI KCS interface is enabled
pub ipmi_enabled: bool,
Comment thread
ayusharora221204 marked this conversation as resolved.
/// Whether secure boot is enabled
pub secure_boot: bool,
/// Whether the TPM is enabled
Expand Down
1 change: 1 addition & 0 deletions openhcl/openvmm_hcl_resources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ hyperv_ic.workspace = true
missing_dev.workspace = true
serial_16550.workspace = true
guest_watchdog.workspace = true
ipmi_kcs.workspace = true
tpm_device = { workspace = true, optional = true, features = ["tpm"] }
vmgs_broker.workspace = true

Expand Down
1 change: 1 addition & 0 deletions openhcl/openvmm_hcl_resources/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ vm_resource::register_static_resolvers! {
serial_pl011::resolver::SerialPl011Resolver,
chipset::battery::resolver::BatteryResolver,
guest_watchdog::resolver::HyperVGuestWatchdogResolver,
ipmi_kcs::resolver::IpmiKcsResolver,

// Non-volatile stores
vmcore::non_volatile_store::resources::EphemeralNonVolatileStoreResolver,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ mod tests {
root_cert_thumbprint: "".to_string(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand Down
6 changes: 4 additions & 2 deletions openhcl/underhill_attestation/src/igvm_attest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,13 +533,14 @@ mod tests {

#[test]
fn test_vm_configuration_no_time() {
const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-version":"1.38","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"signer"}"#;
const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"ipmi-enabled":true,"secure-boot":false,"tpm-enabled":false,"tpm-version":"1.38","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"signer"}"#;

let attestation_vm_config = AttestationVmConfig {
current_time: None,
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: true,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand All @@ -558,13 +559,14 @@ mod tests {

#[test]
fn test_vm_configuration_with_time() {
const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-version":"185","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"hash"}"#;
const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"ipmi-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-version":"185","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"hash"}"#;

let attestation_vm_config = AttestationVmConfig {
current_time: None,
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V185,
Expand Down
6 changes: 6 additions & 0 deletions openhcl/underhill_attestation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: true,
tpm_version: AttestationTpmVersion::V138,
Expand Down Expand Up @@ -2151,6 +2152,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: true,
tpm_version: AttestationTpmVersion::V138,
Expand Down Expand Up @@ -2726,6 +2728,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand Down Expand Up @@ -2806,6 +2809,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand Down Expand Up @@ -2851,6 +2855,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand Down Expand Up @@ -2922,6 +2927,7 @@ mod tests {
root_cert_thumbprint: String::new(),
console_enabled: false,
interactive_console_enabled: false,
ipmi_enabled: false,
secure_boot: false,
tpm_enabled: false,
tpm_version: AttestationTpmVersion::V138,
Expand Down
1 change: 1 addition & 0 deletions openhcl/underhill_core/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ pub fn write_uefi_config(
flags.set_cxl_memory_enabled(platform_config.general.cxl_memory_enabled);
flags.set_default_boot_always_attempt(platform_config.general.default_boot_always_attempt);
flags.set_force_dma_bounce_enabled(platform_config.general.force_dma_bounce_enabled);
flags.set_ipmi_enabled(platform_config.general.ipmi_enabled);
flags.set_disable_sha1_pcr(disable_sha1_pcr);

// Some settings do not depend on host config
Expand Down
67 changes: 39 additions & 28 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,36 @@ async fn new_underhill_vm(
tracing::warn!(CVM_ALLOWED, "confidential debug enabled");
}

// Validate UEFI-only settings before deriving runtime claims and hardware keys.
let (firmware_type, mut measured_vtl0_info, load_kind) = {
if let Some(firmware_type) = servicing_state.firmware_type {
(firmware_type.into(), None, LoadKind::None)
} else {
let config = MeasuredVtl0Info::read_from_memory(gm.vtl0())
.context("failed to read measured vtl0 info")?;
let load_kind = if let Some(kind) = env_cfg.force_load_vtl0_image {
tracing::info!(CVM_ALLOWED, kind, "overriding dps load type");
match kind.as_str() {
"pcat" => LoadKind::Pcat,
"uefi" => LoadKind::Uefi,
"linux" => LoadKind::Linux,
_ => anyhow::bail!("unexpected force load vtl0 type {kind}"),
}
} else if dps.general.firmware_mode_is_pcat {
LoadKind::Pcat
} else {
LoadKind::Uefi
};

let firmware_type: FirmwareType = load_kind.into();
(firmware_type, Some(config), load_kind)
}
};

if dps.general.ipmi_enabled && !matches!(firmware_type, FirmwareType::Uefi) {
anyhow::bail!("IPMI KCS is only supported with UEFI firmware");
}

// Get VMGS provenance claims. If the provenance doc can't be read or if it
// isn't valid, proceed as if it doesn't exist. In that case, OpenHCL will
// not produce attestation claims for provenance. It's up to the VM owner's
Expand Down Expand Up @@ -2152,6 +2182,7 @@ async fn new_underhill_vm(
root_cert_thumbprint: String::new(),
console_enabled,
interactive_console_enabled: interactive_console,
ipmi_enabled: dps.general.ipmi_enabled,
secure_boot: dps.general.secure_boot_enabled,
tpm_enabled: dps.general.tpm_enabled,
tpm_version: match tpm_version {
Expand Down Expand Up @@ -2253,6 +2284,9 @@ async fn new_underhill_vm(
let mut resolver = ResourceResolver::new();
// Make the GET available for other resources.
resolver.add_resolver(get_client.clone());
resolver.add_resolver(
guest_emulation_transport::resolver::IpmiSelEventSinkResolver(get_client.clone()),
);

let (vmgs_client, vmgs) = if let Some((meta, vmgs)) = vmgs {
// Spawn the VMGS client for multi-task access.
Expand All @@ -2276,34 +2310,6 @@ async fn new_underhill_vm(
),
);

// Read measured config from VTL0 memory. When restoring, it is already gone.
let (firmware_type, mut measured_vtl0_info, load_kind) = {
Comment thread
ayusharora221204 marked this conversation as resolved.
if let Some(firmware_type) = servicing_state.firmware_type {
(firmware_type.into(), None, LoadKind::None)
} else {
let config = MeasuredVtl0Info::read_from_memory(gm.vtl0())
.context("failed to read measured vtl0 info")?;
let load_kind = if let Some(kind) = env_cfg.force_load_vtl0_image {
tracing::info!(CVM_ALLOWED, kind, "overriding dps load type");
match kind.as_str() {
"pcat" => LoadKind::Pcat,
"uefi" => LoadKind::Uefi,
"linux" => LoadKind::Linux,
_ => anyhow::bail!("unexpected force load vtl0 type {kind}"),
}
} else {
if dps.general.firmware_mode_is_pcat {
LoadKind::Pcat
} else {
LoadKind::Uefi
}
};

let firmware_type: FirmwareType = load_kind.into();
(firmware_type, Some(config), load_kind)
}
};

// Only advertise extended IOAPIC on non-PCAT systems.
#[cfg(guest_arch = "x86_64")]
let cpuid = {
Expand Down Expand Up @@ -2551,6 +2557,10 @@ async fn new_underhill_vm(
chipset = chipset.with_platform_pm_timer_assist();
}

if dps.general.ipmi_enabled {
chipset = chipset.with_ipmi_kcs();
}

if with_serial {
chipset = chipset.with_serial(serial_inputs);
if env_cfg.emulated_serial_wait_for_rts {
Expand Down Expand Up @@ -4012,6 +4022,7 @@ fn validate_isolated_configuration(dps: &DevicePlatformSettings) -> Result<(), a
// Attested to
secure_boot_enabled,
tpm_enabled: _,
ipmi_enabled: _,
com1_enabled: _,
com1_vmbus_redirector: _,
com2_enabled: _,
Expand Down
2 changes: 2 additions & 0 deletions openvmm/openvmm_entry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,7 @@ async fn vm_config_from_command_line(
TpmVersion::V185 => get_resources::ged::GedTpmVersion::V185,
}),
firmware_event_send: None,
ipmi_sel_event_send: None,
secure_boot_enabled: opt.secure_boot,
secure_boot_template: match opt.secure_boot_template {
Some(SecureBootTemplateCli::Windows) => {
Expand All @@ -1594,6 +1595,7 @@ async fn vm_config_from_command_line(
},
},
enable_battery: opt.battery,
enable_ipmi: false,
enable_hibernation: opt.hibernation,
no_persistent_secrets: true,
igvm_attest_test_config: None,
Expand Down
Loading
Loading