From 7976cd410b5d5a8d9eb1c87e5dfdbc997202df53 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Feb 2026 15:28:03 +0100 Subject: [PATCH 1/5] arch: x86_64: refactor SMBIOS helpers Split the System Information write into helper functions and reuse the string writer so the table layout and inputs are unchanged. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- arch/src/x86_64/mod.rs | 10 ++-- arch/src/x86_64/smbios.rs | 116 ++++++++++++++++++++++++-------------- vmm/src/config.rs | 5 +- vmm/src/vm.rs | 9 +-- vmm/src/vm_config.rs | 2 +- 5 files changed, 87 insertions(+), 55 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 614a2c4bf9..a17b2434e0 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -1103,7 +1103,7 @@ pub fn configure_system( rsdp_addr: Option, serial_number: Option<&str>, uuid: Option<&str>, - oem_strings: Option<&[&str]>, + oem_strings: Vec, topology: Option<(u16, u16, u16, u16)>, ) -> super::Result<()> { // Write EBDA address to location where ACPICA expects to find it @@ -1657,7 +1657,7 @@ mod unit_tests { Some(layout::RSDP_POINTER), None, None, - None, + Vec::new(), None, ); config_err.unwrap_err(); @@ -1681,7 +1681,7 @@ mod unit_tests { None, None, None, - None, + Vec::new(), None, ) .unwrap(); @@ -1710,7 +1710,7 @@ mod unit_tests { None, None, None, - None, + Vec::new(), None, ) .unwrap(); @@ -1725,7 +1725,7 @@ mod unit_tests { None, None, None, - None, + Vec::new(), None, ) .unwrap(); diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index ee1499982e..e46a92d0ba 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -47,6 +47,8 @@ const OEM_STRINGS: u8 = 11; const END_OF_TABLE: u8 = 127; const PCI_SUPPORTED: u64 = 1 << 7; const IS_VIRTUAL_MACHINE: u8 = 1 << 4; +pub const DEFAULT_SYSTEM_MANUFACTURER: &str = "Cloud Hypervisor"; +pub const DEFAULT_SYSTEM_PRODUCT_NAME: &str = "cloud-hypervisor"; fn compute_checksum(v: &T) -> u8 { // SAFETY: we are only reading the bytes within the size of the `T` reference `v`. @@ -58,8 +60,7 @@ fn compute_checksum(v: &T) -> u8 { (!checksum).wrapping_add(1) } -#[repr(C)] -#[repr(packed)] +#[repr(C, packed)] #[derive(Default, Copy, Clone)] struct Smbios30Entrypoint { signature: [u8; 5usize], @@ -74,8 +75,7 @@ struct Smbios30Entrypoint { physptr: u64, } -#[repr(C)] -#[repr(packed)] +#[repr(C, packed)] #[derive(Default, Copy, Clone)] struct SmbiosBiosInfo { r#type: u8, @@ -91,8 +91,7 @@ struct SmbiosBiosInfo { characteristics_ext2: u8, } -#[repr(C)] -#[repr(packed)] +#[repr(C, packed)] #[derive(Default, Copy, Clone)] struct SmbiosSysInfo { r#type: u8, @@ -108,8 +107,7 @@ struct SmbiosSysInfo { family: u8, } -#[repr(C)] -#[repr(packed)] +#[repr(C, packed)] #[derive(Default, Copy, Clone)] struct SmbiosOemStrings { r#type: u8, @@ -118,8 +116,7 @@ struct SmbiosOemStrings { count: u8, } -#[repr(C)] -#[repr(packed)] +#[repr(C, packed)] #[derive(Default, Copy, Clone)] struct SmbiosEndOfTable { r#type: u8, @@ -162,11 +159,73 @@ fn write_string( Ok(curptr) } +fn write_opt_string( + mem: &GuestMemoryMmap, + s: Option<&str>, + cur: GuestAddress, +) -> Result { + if let Some(v) = s { + write_string(mem, v, cur) + } else { + Ok(cur) + } +} + +fn write_string_terminator( + mem: &GuestMemoryMmap, + cur: GuestAddress, + has_strings: bool, +) -> Result { + // SMBIOS DSP0134 §6.1.3: if all string-reference fields are 0, follow the + // formatted section with two null bytes (empty string-set). + if has_strings { + write_and_incr(mem, 0u8, cur) + } else { + let cur = write_and_incr(mem, 0u8, cur)?; + write_and_incr(mem, 0u8, cur) + } +} + +fn write_type1_system( + mem: &GuestMemoryMmap, + curptr: &mut GuestAddress, + handle: &mut u16, + serial_number: Option<&str>, + uuid: Option<&str>, +) -> Result<()> { + *handle += 1; + + let uuid_number = uuid + .map(Uuid::parse_str) + .transpose() + .map_err(Error::ParseUuid)? + .unwrap_or(Uuid::nil()); + let serial_idx = serial_number.map(|_| 3).unwrap_or_default(); + + let smbios_sysinfo = SmbiosSysInfo { + r#type: SYSTEM_INFORMATION, + length: mem::size_of::() as u8, + handle: *handle, + manufacturer: 1, // First string written in this section + product_name: 2, // Second string written in this section + serial_number: serial_idx, + uuid: uuid_number.to_bytes_le(), + ..Default::default() + }; + + *curptr = write_and_incr(mem, smbios_sysinfo, *curptr)?; + *curptr = write_string(mem, DEFAULT_SYSTEM_MANUFACTURER, *curptr)?; + *curptr = write_string(mem, DEFAULT_SYSTEM_PRODUCT_NAME, *curptr)?; + *curptr = write_opt_string(mem, serial_number, *curptr)?; + *curptr = write_and_incr(mem, 0u8, *curptr)?; + Ok(()) +} + pub fn setup_smbios( mem: &GuestMemoryMmap, serial_number: Option<&str>, uuid: Option<&str>, - oem_strings: Option<&[&str]>, + oem_strings: Vec, ) -> Result { let physptr = GuestAddress(SMBIOS_START) .checked_add(mem::size_of::() as u64) @@ -192,34 +251,9 @@ pub fn setup_smbios( curptr = write_and_incr(mem, 0u8, curptr)?; } - { - handle += 1; + write_type1_system(mem, &mut curptr, &mut handle, serial_number, uuid)?; - let uuid_number = uuid - .map(Uuid::parse_str) - .transpose() - .map_err(Error::ParseUuid)? - .unwrap_or(Uuid::nil()); - let smbios_sysinfo = SmbiosSysInfo { - r#type: SYSTEM_INFORMATION, - length: mem::size_of::() as u8, - handle, - manufacturer: 1, // First string written in this section - product_name: 2, // Second string written in this section - serial_number: serial_number.map(|_| 3).unwrap_or_default(), // 3rd string - uuid: uuid_number.to_bytes_le(), // set uuid - ..Default::default() - }; - curptr = write_and_incr(mem, smbios_sysinfo, curptr)?; - curptr = write_string(mem, "Cloud Hypervisor", curptr)?; - curptr = write_string(mem, "cloud-hypervisor", curptr)?; - if let Some(serial_number) = serial_number { - curptr = write_string(mem, serial_number, curptr)?; - } - curptr = write_and_incr(mem, 0u8, curptr)?; - } - - if let Some(oem_strings) = oem_strings { + if !oem_strings.is_empty() { handle += 1; let smbios_oemstrings = SmbiosOemStrings { @@ -232,10 +266,10 @@ pub fn setup_smbios( curptr = write_and_incr(mem, smbios_oemstrings, curptr)?; for s in oem_strings { - curptr = write_string(mem, s, curptr)?; + curptr = write_string(mem, &s, curptr)?; } - curptr = write_and_incr(mem, 0u8, curptr)?; + curptr = write_string_terminator(mem, curptr, true)?; } { @@ -298,7 +332,7 @@ mod unit_tests { fn entrypoint_checksum() { let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); - setup_smbios(&mem, None, None, None).unwrap(); + setup_smbios(&mem, None, None, Vec::new()).unwrap(); let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d4ba1495f4..54866c252e 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -788,7 +788,8 @@ impl PlatformConfig { let oem_strings = parser .convert::("oem_strings") .map_err(Error::ParsePlatform)? - .map(|v| v.0); + .map(|v| v.0) + .unwrap_or_default(); #[cfg(feature = "tdx")] let tdx = parser .convert::("tdx") @@ -4291,7 +4292,7 @@ mod unit_tests { iommu_address_width_bits: MAX_IOMMU_ADDRESS_WIDTH_BITS, serial_number: None, uuid: None, - oem_strings: None, + oem_strings: Vec::new(), #[cfg(feature = "tdx")] tdx: false, #[cfg(feature = "sev_snp")] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 5b930e9ac4..414259f516 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1440,11 +1440,8 @@ impl Vm { .unwrap() .platform .as_ref() - .and_then(|p| p.oem_strings.clone()); - - let oem_strings = oem_strings - .as_deref() - .map(|strings| strings.iter().map(|s| s.as_ref()).collect::>()); + .map(|p| p.oem_strings.clone()) + .unwrap_or_default(); let topology = self.cpu_manager.lock().unwrap().get_vcpu_topology(); @@ -1458,7 +1455,7 @@ impl Vm { rsdp_addr, serial_number.as_deref(), uuid.as_deref(), - oem_strings.as_deref(), + oem_strings, topology, ) .map_err(Error::ConfigureSystem)?; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 394c79df43..3114878a20 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -117,7 +117,7 @@ pub struct PlatformConfig { #[serde(default)] pub uuid: Option, #[serde(default)] - pub oem_strings: Option>, + pub oem_strings: Vec, #[cfg(feature = "tdx")] #[serde(default)] pub tdx: bool, From 3e4412baf2b77010fe10b59301f4ae74c2f4ce47 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Feb 2026 15:34:16 +0100 Subject: [PATCH 2/5] vmm: plumb legacy SMBIOS config Add a small SMBIOS config that carries serial_number, uuid, and OEM strings, and pass it from platform config into x86_64 setup. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- arch/src/x86_64/mod.rs | 16 +++------------- arch/src/x86_64/smbios.rs | 21 +++++++++++++-------- vmm/src/vm.rs | 26 +++----------------------- vmm/src/vm_config.rs | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index a17b2434e0..4e2724438a 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -32,6 +32,7 @@ use linux_loader::loader::elf::start_info::{ }; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; +pub use smbios::SmbiosConfig; use thiserror::Error; use vm_memory::{ Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, @@ -1101,9 +1102,7 @@ pub fn configure_system( _num_cpus: u32, setup_header: Option, rsdp_addr: Option, - serial_number: Option<&str>, - uuid: Option<&str>, - oem_strings: Vec, + smbios: Option<&SmbiosConfig>, topology: Option<(u16, u16, u16, u16)>, ) -> super::Result<()> { // Write EBDA address to location where ACPICA expects to find it @@ -1111,8 +1110,7 @@ pub fn configure_system( .write_obj((layout::EBDA_START.0 >> 4) as u16, layout::EBDA_POINTER) .map_err(Error::EbdaSetup)?; - let size = smbios::setup_smbios(guest_mem, serial_number, uuid, oem_strings) - .map_err(Error::SmbiosSetup)?; + let size = smbios::setup_smbios(guest_mem, smbios).map_err(Error::SmbiosSetup)?; // Place the MP table after the SMIOS table aligned to 16 bytes let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size); @@ -1657,8 +1655,6 @@ mod unit_tests { Some(layout::RSDP_POINTER), None, None, - Vec::new(), - None, ); config_err.unwrap_err(); @@ -1681,8 +1677,6 @@ mod unit_tests { None, None, None, - Vec::new(), - None, ) .unwrap(); @@ -1710,8 +1704,6 @@ mod unit_tests { None, None, None, - Vec::new(), - None, ) .unwrap(); @@ -1725,8 +1717,6 @@ mod unit_tests { None, None, None, - Vec::new(), - None, ) .unwrap(); } diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index e46a92d0ba..41c180809a 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -50,6 +50,13 @@ const IS_VIRTUAL_MACHINE: u8 = 1 << 4; pub const DEFAULT_SYSTEM_MANUFACTURER: &str = "Cloud Hypervisor"; pub const DEFAULT_SYSTEM_PRODUCT_NAME: &str = "cloud-hypervisor"; +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SmbiosConfig { + pub serial_number: Option, + pub uuid: Option, + pub oem_strings: Vec, +} + fn compute_checksum(v: &T) -> u8 { // SAFETY: we are only reading the bytes within the size of the `T` reference `v`. let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::()) }; @@ -221,12 +228,10 @@ fn write_type1_system( Ok(()) } -pub fn setup_smbios( - mem: &GuestMemoryMmap, - serial_number: Option<&str>, - uuid: Option<&str>, - oem_strings: Vec, -) -> Result { +pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Result { + let serial_number = smbios.and_then(|cfg| cfg.serial_number.as_deref()); + let uuid = smbios.and_then(|cfg| cfg.uuid.as_deref()); + let oem_strings: &[String] = smbios.map_or(&[] as &[String], |cfg| cfg.oem_strings.as_slice()); let physptr = GuestAddress(SMBIOS_START) .checked_add(mem::size_of::() as u64) .ok_or(Error::NotEnoughMemory)?; @@ -266,7 +271,7 @@ pub fn setup_smbios( curptr = write_and_incr(mem, smbios_oemstrings, curptr)?; for s in oem_strings { - curptr = write_string(mem, &s, curptr)?; + curptr = write_string(mem, s, curptr)?; } curptr = write_string_terminator(mem, curptr, true)?; @@ -332,7 +337,7 @@ mod unit_tests { fn entrypoint_checksum() { let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); - setup_smbios(&mem, None, None, Vec::new()).unwrap(); + setup_smbios(&mem, None).unwrap(); let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 414259f516..0b02e2fbd0 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -1417,31 +1417,13 @@ impl Vm { let boot_vcpus = self.cpu_manager.lock().unwrap().boot_vcpus(); let rsdp_addr = Some(rsdp_addr); - - let serial_number = self - .config - .lock() - .unwrap() - .platform - .as_ref() - .and_then(|p| p.serial_number.clone()); - - let uuid = self - .config - .lock() - .unwrap() - .platform - .as_ref() - .and_then(|p| p.uuid.clone()); - - let oem_strings = self + let smbios = self .config .lock() .unwrap() .platform .as_ref() - .map(|p| p.oem_strings.clone()) - .unwrap_or_default(); + .and_then(|p| p.smbios_config()); let topology = self.cpu_manager.lock().unwrap().get_vcpu_topology(); @@ -1453,9 +1435,7 @@ impl Vm { boot_vcpus, entry_addr.setup_header, rsdp_addr, - serial_number.as_deref(), - uuid.as_deref(), - oem_strings, + smbios.as_ref(), topology, ) .map_err(Error::ConfigureSystem)?; diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 3114878a20..d0ca17e110 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -126,6 +126,24 @@ pub struct PlatformConfig { pub sev_snp: bool, } +#[cfg(target_arch = "x86_64")] +impl PlatformConfig { + pub fn smbios_config(&self) -> Option { + let smbios = arch::x86_64::SmbiosConfig { + serial_number: self.serial_number.clone(), + uuid: self.uuid.clone(), + oem_strings: self.oem_strings.clone(), + }; + + if smbios.serial_number.is_none() && smbios.uuid.is_none() && smbios.oem_strings.is_empty() + { + None + } else { + Some(smbios) + } + } +} + pub const DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT: u32 = 1; fn default_pci_segment_aperture_weight() -> u32 { From d58f034925cfd243156ac63d8fe283ce0567919a Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Feb 2026 15:44:24 +0100 Subject: [PATCH 3/5] vmm: platform: add structured SMBIOS config Extend SMBIOS System Information with manufacturer, product, version, family, sku, serial, and uuid fields, add a chassis asset tag, and pass a structured SMBIOS config from --platform into arch setup. Keep OEM strings and legacy serial_number/uuid options working for compatibility. The platform option naming follows `dmidecode -s `. Fields: - system_manufacturer - system_product_name - system_version - system_family - system_serial_number - system_uuid - chassis_asset_tag On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- arch/src/x86_64/mod.rs | 2 +- arch/src/x86_64/smbios.rs | 156 ++++++++++++++++++++-- cloud-hypervisor/src/main.rs | 2 +- cloud-hypervisor/tests/integration.rs | 7 +- vmm/src/api/openapi/cloud-hypervisor.yaml | 16 +++ vmm/src/config.rs | 124 ++++++++++++++--- vmm/src/vm_config.rs | 55 +++++++- 7 files changed, 317 insertions(+), 45 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 4e2724438a..639725c910 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -32,7 +32,7 @@ use linux_loader::loader::elf::start_info::{ }; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; -pub use smbios::SmbiosConfig; +pub use smbios::{SmbiosChassisConfig, SmbiosConfig, SmbiosSystem}; use thiserror::Error; use vm_memory::{ Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index 41c180809a..4cb73271fc 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -35,6 +35,9 @@ pub enum Error { /// Failure to parse uuid, uuid format may be error #[error("Failure to parse uuid")] ParseUuid(#[source] uuid::Error), + /// SMBIOS string index overflow (u8 limit reached). + #[error("SMBIOS string index overflow (u8 limit reached)")] + TooManyStrings, } pub type Result = result::Result; @@ -44,6 +47,7 @@ const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_"; const BIOS_INFORMATION: u8 = 0; const SYSTEM_INFORMATION: u8 = 1; const OEM_STRINGS: u8 = 11; +const SYSTEM_ENCLOSURE: u8 = 3; const END_OF_TABLE: u8 = 127; const PCI_SUPPORTED: u64 = 1 << 7; const IS_VIRTUAL_MACHINE: u8 = 1 << 4; @@ -52,9 +56,29 @@ pub const DEFAULT_SYSTEM_PRODUCT_NAME: &str = "cloud-hypervisor"; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct SmbiosConfig { + pub system: Option, + pub chassis: Option, + pub oem_strings: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SmbiosSystem { + pub manufacturer: Option, + pub product_name: Option, + pub version: Option, pub serial_number: Option, pub uuid: Option, - pub oem_strings: Vec, + pub sku_number: Option, + pub family: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SmbiosChassisConfig { + pub manufacturer: Option, + pub chassis_type: Option, + pub version: Option, + pub serial_number: Option, + pub asset_tag: Option, } fn compute_checksum(v: &T) -> u8 { @@ -123,6 +147,33 @@ struct SmbiosOemStrings { count: u8, } +/// SMBIOS Chassis Table (Type 3) as defined in DMTF SMBIOS 3.9.0: +/// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.9.0.pdf +/// Note: trailing fields are omitted, so this structure is not complete. +#[repr(C, packed)] +#[derive(Default, Copy, Clone)] +struct SmbiosChassis { + r#type: u8, + length: u8, + handle: u16, + manufacturer: u8, + chassis_type: u8, + version: u8, + serial_number: u8, + asset_tag: u8, + bootup_state: u8, + power_supply_state: u8, + thermal_state: u8, + security_status: u8, + oem_defined: u32, + height: u8, + number_of_power_cords: u8, + contained_element_count: u8, + contained_element_record_length: u8, + // followed by contained element records (optional, variable-length) + // followed by sku_number: u8, rack_type: u8, rack_height: u8 +} + #[repr(C, packed)] #[derive(Default, Copy, Clone)] struct SmbiosEndOfTable { @@ -140,6 +191,8 @@ unsafe impl ByteValued for SmbiosSysInfo {} // SAFETY: data structure only contain a series of integers unsafe impl ByteValued for SmbiosOemStrings {} // SAFETY: data structure only contain a series of integers +unsafe impl ByteValued for SmbiosChassis {} +// SAFETY: data structure only contain a series of integers unsafe impl ByteValued for SmbiosEndOfTable {} fn write_and_incr( @@ -193,44 +246,115 @@ fn write_string_terminator( } } +fn alloc_index(next: &mut u8, present: bool) -> Result { + if !present { + return Ok(0) + } + + let idx = *next; + if idx == 0 { + // wrapped around, next starts always initially at 1 + return Err(Error::TooManyStrings) + } + + *next = next.wrapping_add(1); + Ok(idx) +} + fn write_type1_system( mem: &GuestMemoryMmap, curptr: &mut GuestAddress, handle: &mut u16, - serial_number: Option<&str>, - uuid: Option<&str>, + system: Option<&SmbiosSystem>, ) -> Result<()> { *handle += 1; + let manufacturer = system + .and_then(|s| s.manufacturer.as_deref()) + .unwrap_or(DEFAULT_SYSTEM_MANUFACTURER); + let product = system + .and_then(|s| s.product_name.as_deref()) + .unwrap_or(DEFAULT_SYSTEM_PRODUCT_NAME); + let version = system.and_then(|s| s.version.as_deref()); + let serial = system.and_then(|s| s.serial_number.as_deref()); + let uuid = system.and_then(|s| s.uuid.as_deref()); + let sku = system.and_then(|s| s.sku_number.as_deref()); + let family = system.and_then(|s| s.family.as_deref()); + let uuid_number = uuid .map(Uuid::parse_str) .transpose() .map_err(Error::ParseUuid)? .unwrap_or(Uuid::nil()); - let serial_idx = serial_number.map(|_| 3).unwrap_or_default(); - let smbios_sysinfo = SmbiosSysInfo { + let mut next = 1u8; + let manufacturer_idx = alloc_index(&mut next, true)?; + let product_idx = alloc_index(&mut next, true)?; + let version_idx = alloc_index(&mut next, version.is_some())?; + let serial_idx = alloc_index(&mut next, serial.is_some())?; + let sku_idx = alloc_index(&mut next, sku.is_some())?; + let family_idx = alloc_index(&mut next, family.is_some())?; + + let sys = SmbiosSysInfo { r#type: SYSTEM_INFORMATION, length: mem::size_of::() as u8, handle: *handle, - manufacturer: 1, // First string written in this section - product_name: 2, // Second string written in this section + manufacturer: manufacturer_idx, + product_name: product_idx, + version: version_idx, serial_number: serial_idx, uuid: uuid_number.to_bytes_le(), + sku: sku_idx, + family: family_idx, ..Default::default() }; - *curptr = write_and_incr(mem, smbios_sysinfo, *curptr)?; - *curptr = write_string(mem, DEFAULT_SYSTEM_MANUFACTURER, *curptr)?; - *curptr = write_string(mem, DEFAULT_SYSTEM_PRODUCT_NAME, *curptr)?; - *curptr = write_opt_string(mem, serial_number, *curptr)?; + *curptr = write_and_incr(mem, sys, *curptr)?; + *curptr = write_string(mem, manufacturer, *curptr)?; + *curptr = write_string(mem, product, *curptr)?; + *curptr = write_opt_string(mem, version, *curptr)?; + *curptr = write_opt_string(mem, serial, *curptr)?; + *curptr = write_opt_string(mem, sku, *curptr)?; + *curptr = write_opt_string(mem, family, *curptr)?; *curptr = write_and_incr(mem, 0u8, *curptr)?; Ok(()) } +fn write_type3_chassis( + mem: &GuestMemoryMmap, + curptr: &mut GuestAddress, + handle: &mut u16, + chassis: &SmbiosChassisConfig, +) -> Result<()> { + *handle += 1; + + let asset_tag = chassis.asset_tag.as_deref(); + let mut next = 1u8; + let asset_idx = alloc_index(&mut next, asset_tag.is_some())?; + + let ch = SmbiosChassis { + r#type: SYSTEM_ENCLOSURE, + length: mem::size_of::() as u8, + handle: *handle, + manufacturer: 0, + chassis_type: 0, + version: 0, + serial_number: 0, + asset_tag: asset_idx, + contained_element_count: 0, + contained_element_record_length: 0, + ..Default::default() + }; + + *curptr = write_and_incr(mem, ch, *curptr)?; + *curptr = write_opt_string(mem, asset_tag, *curptr)?; + *curptr = write_string_terminator(mem, *curptr, asset_tag.is_some())?; + Ok(()) +} + pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Result { - let serial_number = smbios.and_then(|cfg| cfg.serial_number.as_deref()); - let uuid = smbios.and_then(|cfg| cfg.uuid.as_deref()); + let system = smbios.and_then(|cfg| cfg.system.as_ref()); + let chassis = smbios.and_then(|cfg| cfg.chassis.as_ref()); let oem_strings: &[String] = smbios.map_or(&[] as &[String], |cfg| cfg.oem_strings.as_slice()); let physptr = GuestAddress(SMBIOS_START) .checked_add(mem::size_of::() as u64) @@ -256,7 +380,11 @@ pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Res curptr = write_and_incr(mem, 0u8, curptr)?; } - write_type1_system(mem, &mut curptr, &mut handle, serial_number, uuid)?; + write_type1_system(mem, &mut curptr, &mut handle, system)?; + + if let Some(chassis) = chassis { + write_type3_chassis(mem, &mut curptr, &mut handle, chassis)?; + } if !oem_strings.is_empty() { handle += 1; diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 8a63e0685e..67dc0c4c1b 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -375,7 +375,7 @@ fn get_cli_options_sorted( Arg::new("platform") .long("platform") .help( - "num_pci_segments=,iommu_segments=,iommu_address_width=,serial_number=,uuid=,oem_strings=" + "num_pci_segments=,iommu_segments=,iommu_address_width=,system_manufacturer=,system_product_name=,system_version=,system_serial_number=,system_uuid=,system_sku_number=,system_family=,oem_strings=,chassis_asset_tag=", ) .num_args(1) .group("vm-config"), diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index 336b11776c..edc0bcf3a5 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -3894,7 +3894,7 @@ mod common_parallel { .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--platform", "serial_number=a=b;c=d"]) + .args(["--platform", "system_serial_number=a=b;c=d"]) .default_disks() .default_net() .capture_output() @@ -3930,7 +3930,10 @@ mod common_parallel { .args(["--memory", "size=512M"]) .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .args(["--platform", "uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a"]) + .args([ + "--platform", + "system_uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a", + ]) .default_disks() .default_net() .capture_output() diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 04079e36f3..54982b70dc 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -739,14 +739,30 @@ components: iommu_address_width: type: integer format: uint8 + system_serial_number: + type: string serial_number: type: string + system_uuid: + type: string uuid: type: string oem_strings: type: array items: type: string + system_manufacturer: + type: string + system_product_name: + type: string + system_version: + type: string + system_family: + type: string + system_sku_number: + type: string + chassis_asset_tag: + type: string tdx: type: boolean default: false diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 54866c252e..04142bb86a 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -755,14 +755,57 @@ impl PciSegmentConfig { impl PlatformConfig { pub fn parse(platform: &str) -> Result { + struct StringField { + key: &'static str, + apply: fn(&mut PlatformConfig, String), + } + + const SMBIOS_STRING_FIELDS: &[StringField] = &[ + StringField { + key: "system_manufacturer", + apply: |config, value| config.system_manufacturer = Some(value), + }, + StringField { + key: "system_product_name", + apply: |config, value| config.system_product_name = Some(value), + }, + StringField { + key: "system_version", + apply: |config, value| config.system_version = Some(value), + }, + StringField { + key: "system_serial_number", + apply: |config, value| config.system_serial_number = Some(value), + }, + StringField { + key: "system_uuid", + apply: |config, value| config.system_uuid = Some(value), + }, + StringField { + key: "system_sku_number", + apply: |config, value| config.system_sku_number = Some(value), + }, + StringField { + key: "system_family", + apply: |config, value| config.system_family = Some(value), + }, + StringField { + key: "chassis_asset_tag", + apply: |config, value| config.chassis_asset_tag = Some(value), + }, + ]; + let mut parser = OptionParser::new(); parser .add("num_pci_segments") .add("iommu_segments") .add("iommu_address_width") + .add("oem_strings") .add("serial_number") - .add("uuid") - .add("oem_strings"); + .add("uuid"); + for field in SMBIOS_STRING_FIELDS { + parser.add(field.key); + } #[cfg(feature = "tdx")] parser.add("tdx"); #[cfg(feature = "sev_snp")] @@ -781,15 +824,51 @@ impl PlatformConfig { .convert("iommu_address_width") .map_err(Error::ParsePlatform)? .unwrap_or(MAX_IOMMU_ADDRESS_WIDTH_BITS); - let serial_number = parser - .convert("serial_number") - .map_err(Error::ParsePlatform)?; - let uuid = parser.convert("uuid").map_err(Error::ParsePlatform)?; let oem_strings = parser .convert::("oem_strings") .map_err(Error::ParsePlatform)? .map(|v| v.0) .unwrap_or_default(); + + let mut platform_config = PlatformConfig { + num_pci_segments, + iommu_segments, + iommu_address_width_bits, + system_serial_number: None, + system_uuid: None, + oem_strings, + system_manufacturer: None, + system_product_name: None, + system_version: None, + system_family: None, + system_sku_number: None, + chassis_asset_tag: None, + #[cfg(feature = "tdx")] + tdx: false, + #[cfg(feature = "sev_snp")] + sev_snp: false, + }; + + for field in SMBIOS_STRING_FIELDS { + if let Some(value) = parser + .convert::(field.key) + .map_err(Error::ParsePlatform)? + { + (field.apply)(&mut platform_config, value); + } + } + + let legacy_serial_number = parser + .convert::("serial_number") + .map_err(Error::ParsePlatform)?; + platform_config.system_serial_number = platform_config + .system_serial_number + .or(legacy_serial_number); + + let legacy_uuid = parser + .convert::("uuid") + .map_err(Error::ParsePlatform)?; + platform_config.system_uuid = platform_config.system_uuid.or(legacy_uuid); #[cfg(feature = "tdx")] let tdx = parser .convert::("tdx") @@ -802,18 +881,17 @@ impl PlatformConfig { .map_err(Error::ParsePlatform)? .unwrap_or(Toggle(false)) .0; - Ok(PlatformConfig { - num_pci_segments, - iommu_segments, - iommu_address_width_bits, - serial_number, - uuid, - oem_strings, - #[cfg(feature = "tdx")] - tdx, - #[cfg(feature = "sev_snp")] - sev_snp, - }) + + #[cfg(feature = "tdx")] + { + platform_config.tdx = tdx; + } + #[cfg(feature = "sev_snp")] + { + platform_config.sev_snp = sev_snp; + } + + Ok(platform_config) } pub fn validate(&self) -> ValidationResult<()> { @@ -4290,9 +4368,15 @@ mod unit_tests { num_pci_segments: MAX_NUM_PCI_SEGMENTS, iommu_segments: None, iommu_address_width_bits: MAX_IOMMU_ADDRESS_WIDTH_BITS, - serial_number: None, - uuid: None, + system_serial_number: None, + system_uuid: None, oem_strings: Vec::new(), + system_manufacturer: None, + system_product_name: None, + system_version: None, + system_family: None, + system_sku_number: None, + chassis_asset_tag: None, #[cfg(feature = "tdx")] tdx: false, #[cfg(feature = "sev_snp")] diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index d0ca17e110..6a07d9bef1 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -112,12 +112,24 @@ pub struct PlatformConfig { pub iommu_segments: Option>, #[serde(default = "default_platformconfig_iommu_address_width_bits")] pub iommu_address_width_bits: u8, + #[serde(default, alias = "serial_number")] + pub system_serial_number: Option, + #[serde(default, alias = "uuid")] + pub system_uuid: Option, #[serde(default)] - pub serial_number: Option, + pub oem_strings: Vec, #[serde(default)] - pub uuid: Option, + pub system_manufacturer: Option, #[serde(default)] - pub oem_strings: Vec, + pub system_product_name: Option, + #[serde(default)] + pub system_version: Option, + #[serde(default)] + pub system_family: Option, + #[serde(default)] + pub system_sku_number: Option, + #[serde(default)] + pub chassis_asset_tag: Option, #[cfg(feature = "tdx")] #[serde(default)] pub tdx: bool, @@ -129,14 +141,43 @@ pub struct PlatformConfig { #[cfg(target_arch = "x86_64")] impl PlatformConfig { pub fn smbios_config(&self) -> Option { + let has_system = [ + &self.system_serial_number, + &self.system_uuid, + &self.system_manufacturer, + &self.system_product_name, + &self.system_version, + &self.system_family, + &self.system_sku_number, + ] + .iter() + .any(|v| v.is_some()); + + let system = has_system.then_some(arch::x86_64::SmbiosSystem { + manufacturer: self.system_manufacturer.clone(), + product_name: self.system_product_name.clone(), + version: self.system_version.clone(), + serial_number: self.system_serial_number.clone(), + uuid: self.system_uuid.clone(), + sku_number: self.system_sku_number.clone(), + family: self.system_family.clone(), + }); + + let chassis = + self.chassis_asset_tag + .clone() + .map(|asset_tag| arch::x86_64::SmbiosChassisConfig { + asset_tag: Some(asset_tag), + ..Default::default() + }); + let smbios = arch::x86_64::SmbiosConfig { - serial_number: self.serial_number.clone(), - uuid: self.uuid.clone(), + system, + chassis, oem_strings: self.oem_strings.clone(), }; - if smbios.serial_number.is_none() && smbios.uuid.is_none() && smbios.oem_strings.is_empty() - { + if smbios.system.is_none() && smbios.chassis.is_none() && smbios.oem_strings.is_empty() { None } else { Some(smbios) From 897fd6537c389dab8d32a1bf41c91687517031ab Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Mon, 9 Feb 2026 11:53:00 +0100 Subject: [PATCH 4/5] vmm: deprecate legacy SMBIOS keys in API and CLI Mark serial_number/uuid as deprecated in the OpenAPI schema and emit warnings when those legacy --platform keys are used, while continuing to accept them for compatibility. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- vmm/src/api/openapi/cloud-hypervisor.yaml | 2 ++ vmm/src/config.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 54982b70dc..2363d76dba 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -743,10 +743,12 @@ components: type: string serial_number: type: string + deprecated: true system_uuid: type: string uuid: type: string + deprecated: true oem_strings: type: array items: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 04142bb86a..0bf25da12a 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -861,6 +861,9 @@ impl PlatformConfig { let legacy_serial_number = parser .convert::("serial_number") .map_err(Error::ParsePlatform)?; + if legacy_serial_number.is_some() { + warn!("'serial_number' in --platform is deprecated; use 'system_serial_number'."); + } platform_config.system_serial_number = platform_config .system_serial_number .or(legacy_serial_number); @@ -868,6 +871,9 @@ impl PlatformConfig { let legacy_uuid = parser .convert::("uuid") .map_err(Error::ParsePlatform)?; + if legacy_uuid.is_some() { + warn!("'uuid' in --platform is deprecated; use 'system_uuid'."); + } platform_config.system_uuid = platform_config.system_uuid.or(legacy_uuid); #[cfg(feature = "tdx")] let tdx = parser From 602510f87030dcba7a934ee9150d40bb05451a8e Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Tue, 10 Feb 2026 13:18:11 +0100 Subject: [PATCH 5/5] arch: smbios: add tests for table serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add unit tests that walk the SMBIOS binary layout in guest memory and verify structure ordering, string-set encoding, and error paths. Tests added: - smbios_chassis_empty_string_set_has_double_null: verify that a chassis with no strings emits the double-NUL terminator required by SMBIOS DSP0134 §6.1.3. - smbios_chassis_oem_strings_layout: verify the full chain (BIOS → System → Chassis → OEM → End) when a chassis asset tag and OEM strings are configured. - smbios_strings_terminators_default: verify the default table chain (BIOS → System → End) and check that string indices and string-set contents match for both structures. - smbios_strings_too_many: exercise alloc_index up to the u8 limit (255 strings) and verify the 256th is rejected. - smbios_uuid_invalid_rejected: ensure a malformed UUID string is rejected with Error::ParseUuid. - smbios_uuid_written_le: ensure the UUID is stored in little-endian byte order as required by SMBIOS Spec 7.2.1. - smbios_write_fails_with_too_small_memory: verify that setup_smbios fails with Error::WriteData when guest memory is too small to hold anything beyond the entry point. All tests also succeed when run with miri: cargo +nightly miri test -p arch smbios On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler --- arch/src/x86_64/smbios.rs | 229 +++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 5 deletions(-) diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs index 4cb73271fc..81eb4f1e74 100644 --- a/arch/src/x86_64/smbios.rs +++ b/arch/src/x86_64/smbios.rs @@ -248,13 +248,13 @@ fn write_string_terminator( fn alloc_index(next: &mut u8, present: bool) -> Result { if !present { - return Ok(0) + return Ok(0); } let idx = *next; if idx == 0 { // wrapped around, next starts always initially at 1 - return Err(Error::TooManyStrings) + return Err(Error::TooManyStrings); } *next = next.wrapping_add(1); @@ -442,8 +442,55 @@ pub fn setup_smbios(mem: &GuestMemoryMmap, smbios: Option<&SmbiosConfig>) -> Res mod unit_tests { use super::*; + /// Collects all strings after a SMBIOS structure, stopping at the double-NUL terminator and returns next addr. + fn read_string_set(mem: &GuestMemoryMmap, addr: GuestAddress) -> (Vec, GuestAddress) { + let mut cur = addr; + let read_byte = |addr: GuestAddress| -> u8 { mem.read_obj(addr).unwrap() }; + + // SMBIOS string-set: NUL-terminated strings, terminated by an extra NUL. + // Empty string-set is exactly "\0\0". + if read_byte(cur) == 0 { + let next = cur.checked_add(1).unwrap(); + assert_eq!(read_byte(next), 0); + return (Vec::new(), next.checked_add(1).unwrap()); + } + + let mut strings = Vec::new(); + loop { + let mut bytes = Vec::new(); + loop { + let b = read_byte(cur); + cur = cur.checked_add(1).unwrap(); + if b == 0 { + break; + } + bytes.push(b); + } + strings.push(String::from_utf8(bytes).unwrap()); + + // If the next byte is NUL, that's the extra terminator. + if read_byte(cur) == 0 { + cur = cur.checked_add(1).unwrap(); + break; + } + } + + (strings, cur) + } + + #[test] + fn entrypoint_checksum() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + + setup_smbios(&mem, None).unwrap(); + + let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + + assert_eq!(compute_checksum(&smbios_ep), 0); + } + #[test] - fn struct_size() { + fn entrypoint_struct_size() { assert_eq!( mem::size_of::(), 0x18usize, @@ -462,13 +509,185 @@ mod unit_tests { } #[test] - fn entrypoint_checksum() { + fn smbios_chassis_empty_string_set_has_double_null() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + let smbios = SmbiosConfig { + chassis: Some(SmbiosChassisConfig::default()), + ..Default::default() + }; + + setup_smbios(&mem, Some(&smbios)).unwrap(); + + let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + let mut cur = GuestAddress(smbios_ep.physptr); + + let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(bios.length as u64).unwrap(); + let (_, next) = read_string_set(&mem, cur); + cur = next; + + let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(sys.length as u64).unwrap(); + let (_, next) = read_string_set(&mem, cur); + cur = next; + + let chassis: SmbiosChassis = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(chassis.length as u64).unwrap(); + // SMBIOS DSP0134 §6.1.3: empty string-set ends with double NUL. + let b0: u8 = mem.read_obj(cur).unwrap(); + let b1: u8 = mem.read_obj(cur.checked_add(1).unwrap()).unwrap(); + assert_eq!(b0, 0); + assert_eq!(b1, 0); + cur = cur.checked_add(2).unwrap(); + + let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap(); + assert_eq!(end.r#type, END_OF_TABLE); + } + + #[test] + fn smbios_chassis_oem_strings_layout() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + + let smbios = SmbiosConfig { + chassis: Some(SmbiosChassisConfig { + asset_tag: Some("rack1".to_string()), + ..Default::default() + }), + oem_strings: vec!["o1".to_string(), "o2".to_string()], + ..Default::default() + }; + + setup_smbios(&mem, Some(&smbios)).unwrap(); + + let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + let mut cur = GuestAddress(smbios_ep.physptr); + + let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(bios.length as u64).unwrap(); + let (_, next) = read_string_set(&mem, cur); + cur = next; + + let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(sys.length as u64).unwrap(); + let (_, next) = read_string_set(&mem, cur); + cur = next; + + let chassis: SmbiosChassis = mem.read_obj(cur).unwrap(); + assert_eq!(chassis.r#type, SYSTEM_ENCLOSURE); + assert_eq!(chassis.asset_tag, 1); + cur = cur.checked_add(chassis.length as u64).unwrap(); + let (chassis_strings, next) = read_string_set(&mem, cur); + assert_eq!(chassis_strings, vec!["rack1"]); + cur = next; + + let oem: SmbiosOemStrings = mem.read_obj(cur).unwrap(); + assert_eq!(oem.r#type, OEM_STRINGS); + assert_eq!(oem.count, 2); + cur = cur.checked_add(oem.length as u64).unwrap(); + let (oem_strings, next) = read_string_set(&mem, cur); + assert_eq!(oem_strings, vec!["o1", "o2"]); + cur = next; + + let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap(); + assert_eq!(end.r#type, END_OF_TABLE); + } + + #[test] + fn smbios_strings_terminators_default() { let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); setup_smbios(&mem, None).unwrap(); let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + let mut cur = GuestAddress(smbios_ep.physptr); + + let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap(); + assert_eq!(bios.r#type, BIOS_INFORMATION); + cur = cur.checked_add(bios.length as u64).unwrap(); + let (bios_strings, next) = read_string_set(&mem, cur); + assert_eq!(bios_strings, vec!["cloud-hypervisor", "0"]); + cur = next; + + let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap(); + assert_eq!(sys.r#type, SYSTEM_INFORMATION); + assert_eq!(sys.manufacturer, 1); + assert_eq!(sys.product_name, 2); + assert_eq!(sys.version, 0); + assert_eq!(sys.serial_number, 0); + assert_eq!(sys.sku, 0); + assert_eq!(sys.family, 0); + cur = cur.checked_add(sys.length as u64).unwrap(); + let (sys_strings, next) = read_string_set(&mem, cur); + assert_eq!( + sys_strings, + vec![DEFAULT_SYSTEM_MANUFACTURER, DEFAULT_SYSTEM_PRODUCT_NAME] + ); + cur = next; - assert_eq!(compute_checksum(&smbios_ep), 0); + let end: SmbiosEndOfTable = mem.read_obj(cur).unwrap(); + assert_eq!(end.r#type, END_OF_TABLE); + } + + #[test] + fn smbios_strings_too_many() { + let mut next = 1u8; + for _ in 0..255 { + alloc_index(&mut next, true).unwrap(); + } + let err = alloc_index(&mut next, true).unwrap_err(); + assert!(matches!(err, Error::TooManyStrings)); + } + + #[test] + fn smbios_uuid_invalid_rejected() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + let smbios = SmbiosConfig { + system: Some(SmbiosSystem { + uuid: Some("not-a-uuid".to_string()), + ..Default::default() + }), + ..Default::default() + }; + + let err = setup_smbios(&mem, Some(&smbios)).unwrap_err(); + assert!(matches!(err, Error::ParseUuid(_))); + } + + #[test] + fn smbios_uuid_written_le() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + let uuid_str = "00112233-4455-6677-8899-aabbccddeeff"; + let smbios = SmbiosConfig { + system: Some(SmbiosSystem { + uuid: Some(uuid_str.to_string()), + ..Default::default() + }), + ..Default::default() + }; + + setup_smbios(&mem, Some(&smbios)).unwrap(); + + let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + let mut cur = GuestAddress(smbios_ep.physptr); + + let bios: SmbiosBiosInfo = mem.read_obj(cur).unwrap(); + cur = cur.checked_add(bios.length as u64).unwrap(); + let (_, next) = read_string_set(&mem, cur); + cur = next; + + let sys: SmbiosSysInfo = mem.read_obj(cur).unwrap(); + assert_eq!(sys.uuid, Uuid::parse_str(uuid_str).unwrap().to_bytes_le()); + } + + #[test] + fn smbios_write_fails_with_too_small_memory() { + let mem = GuestMemoryMmap::from_ranges(&[( + GuestAddress(SMBIOS_START), + mem::size_of::(), + )]) + .unwrap(); + + let err = setup_smbios(&mem, None).unwrap_err(); + assert!(matches!(err, Error::WriteData)); } }