From 2c3c335de6ef3e0806afcb524b5b0fc4aeb37b27 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Mon, 29 Jun 2020 15:06:22 +0100 Subject: [PATCH] arch: x86_64: Add basic SMBIOS support Taken from crosvm: 44336b913126d73f9f8d6854f57aac92b5db809e and adapted for Cloud Hypervisor. This is basic and incomplete support but Linux correctly finds the DMI data based on this: root@clr-c6ed47bc1c9d473d9a3a8bddc50ee4cb ~ # dmesg | grep -i dmi [ 0.000000] DMI: Cloud Hypervisor cloud-hypervisor, BIOS 0 root@clr-c6ed47bc1c9d473d9a3a8bddc50ee4cb ~ # dmesg | grep -i smbio [ 0.000000] SMBIOS 3.2.0 present. Signed-off-by: Rob Bradford --- arch/src/x86_64/layout.rs | 2 + arch/src/x86_64/mod.rs | 6 + arch/src/x86_64/smbios.rs | 252 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 arch/src/x86_64/smbios.rs diff --git a/arch/src/x86_64/layout.rs b/arch/src/x86_64/layout.rs index c4a382fcf..d519b49e6 100644 --- a/arch/src/x86_64/layout.rs +++ b/arch/src/x86_64/layout.rs @@ -70,6 +70,8 @@ pub const EBDA_START: GuestAddress = GuestAddress(0xa0000); // ACPI RSDP table pub const RSDP_POINTER: GuestAddress = EBDA_START; +pub const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec. + // == End of "EBDA" range == // ** High RAM (start: 1MiB, length: 3071MiB) ** diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 63a0eb68d..c9aece6aa 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -25,6 +25,7 @@ use vm_memory::{ Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap, GuestMemoryRegion, GuestUsize, }; +mod smbios; #[derive(Debug, Copy, Clone)] pub enum BootProtocol { @@ -114,6 +115,9 @@ pub enum Error { /// Cannot set the local interruption due to bad configuration. LocalIntConfiguration(anyhow::Error), + + /// Error setting up SMBIOS table + SmbiosSetup(smbios::Error), } impl From for super::Error { @@ -296,6 +300,8 @@ pub fn configure_system( rsdp_addr: Option, boot_prot: BootProtocol, ) -> super::Result<()> { + smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?; + // Note that this puts the mptable at the last 1k of Linux's 640k base RAM #[cfg(not(feature = "acpi"))] mptable::setup_mptable(guest_mem, _num_cpus).map_err(Error::MpTableSetup)?; diff --git a/arch/src/x86_64/smbios.rs b/arch/src/x86_64/smbios.rs new file mode 100644 index 000000000..b0533c277 --- /dev/null +++ b/arch/src/x86_64/smbios.rs @@ -0,0 +1,252 @@ +// Copyright © 2020 Intel Corporation +// +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use layout::SMBIOS_START; +use std::fmt::{self, Display}; +use std::mem; +use std::result; +use std::slice; +use vm_memory::ByteValued; +use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap}; + +#[allow(unused_variables)] +#[derive(Debug)] +pub enum Error { + /// There was too little guest memory to store the entire SMBIOS table. + NotEnoughMemory, + /// The SMBIOS table has too little address space to be stored. + AddressOverflow, + /// Failure while zeroing out the memory for the SMBIOS table. + Clear, + /// Failure to write SMBIOS entrypoint structure + WriteSmbiosEp, + /// Failure to write additional data to memory + WriteData, +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + let description = match self { + NotEnoughMemory => "There was too little guest memory to store the SMBIOS table", + AddressOverflow => "The SMBIOS table has too little address space to be stored", + Clear => "Failure while zeroing out the memory for the SMBIOS table", + WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure", + WriteData => "Failure to write additional data to memory", + }; + + write!(f, "SMBIOS error: {}", description) + } +} + +pub type Result = result::Result; + +// Constants sourced from SMBIOS Spec 3.2.0. +const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_"; +const BIOS_INFORMATION: u8 = 0; +const SYSTEM_INFORMATION: u8 = 1; +const PCI_SUPPORTED: u64 = 1 << 7; +const IS_VIRTUAL_MACHINE: u8 = 1 << 4; + +fn compute_checksum(v: &T) -> u8 { + // Safe because 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::()) }; + let mut checksum: u8 = 0; + for i in v_slice.iter() { + checksum = checksum.wrapping_add(*i); + } + (!checksum).wrapping_add(1) +} + +#[repr(packed)] +#[derive(Default, Copy)] +pub struct Smbios30Entrypoint { + pub signature: [u8; 5usize], + pub checksum: u8, + pub length: u8, + pub majorver: u8, + pub minorver: u8, + pub docrev: u8, + pub revision: u8, + pub reserved: u8, + pub max_size: u32, + pub physptr: u64, +} +unsafe impl ByteValued for Smbios30Entrypoint {} + +impl Clone for Smbios30Entrypoint { + fn clone(&self) -> Self { + *self + } +} + +#[repr(packed)] +#[derive(Default, Copy)] +pub struct SmbiosBiosInfo { + pub typ: u8, + pub length: u8, + pub handle: u16, + pub vendor: u8, + pub version: u8, + pub start_addr: u16, + pub release_date: u8, + pub rom_size: u8, + pub characteristics: u64, + pub characteristics_ext1: u8, + pub characteristics_ext2: u8, +} + +impl Clone for SmbiosBiosInfo { + fn clone(&self) -> Self { + *self + } +} + +unsafe impl ByteValued for SmbiosBiosInfo {} + +#[repr(packed)] +#[derive(Default, Copy)] +pub struct SmbiosSysInfo { + pub typ: u8, + pub length: u8, + pub handle: u16, + pub manufacturer: u8, + pub product_name: u8, + pub version: u8, + pub serial_number: u8, + pub uuid: [u8; 16usize], + pub wake_up_type: u8, + pub sku: u8, + pub family: u8, +} + +impl Clone for SmbiosSysInfo { + fn clone(&self) -> Self { + *self + } +} + +unsafe impl ByteValued for SmbiosSysInfo {} + +fn write_and_incr( + mem: &GuestMemoryMmap, + val: T, + mut curptr: GuestAddress, +) -> Result { + mem.write_obj(val, curptr).map_err(|_| Error::WriteData)?; + curptr = curptr + .checked_add(mem::size_of::() as u64) + .ok_or(Error::NotEnoughMemory)?; + Ok(curptr) +} + +fn write_string( + mem: &GuestMemoryMmap, + val: &str, + mut curptr: GuestAddress, +) -> Result { + for c in val.as_bytes().iter() { + curptr = write_and_incr(mem, *c, curptr)?; + } + curptr = write_and_incr(mem, 0 as u8, curptr)?; + Ok(curptr) +} + +pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<()> { + let physptr = GuestAddress(SMBIOS_START) + .checked_add(mem::size_of::() as u64) + .ok_or(Error::NotEnoughMemory)?; + let mut curptr = physptr; + let mut handle = 0; + + { + handle += 1; + let mut smbios_biosinfo = SmbiosBiosInfo::default(); + smbios_biosinfo.typ = BIOS_INFORMATION; + smbios_biosinfo.length = mem::size_of::() as u8; + smbios_biosinfo.handle = handle; + smbios_biosinfo.vendor = 1; // First string written in this section + smbios_biosinfo.version = 2; // Second string written in this section + smbios_biosinfo.characteristics = PCI_SUPPORTED; + smbios_biosinfo.characteristics_ext2 = IS_VIRTUAL_MACHINE; + curptr = write_and_incr(mem, smbios_biosinfo, curptr)?; + curptr = write_string(mem, "cloud-hypervisor", curptr)?; + curptr = write_string(mem, "0", curptr)?; + curptr = write_and_incr(mem, 0 as u8, curptr)?; + } + + { + handle += 1; + let mut smbios_sysinfo = SmbiosSysInfo::default(); + smbios_sysinfo.typ = SYSTEM_INFORMATION; + smbios_sysinfo.length = mem::size_of::() as u8; + smbios_sysinfo.handle = handle; + smbios_sysinfo.manufacturer = 1; // First string written in this section + smbios_sysinfo.product_name = 2; // Second string written in this section + curptr = write_and_incr(mem, smbios_sysinfo, curptr)?; + curptr = write_string(mem, "Cloud Hypervisor", curptr)?; + curptr = write_string(mem, "cloud-hypervisor", curptr)?; + curptr = write_and_incr(mem, 0 as u8, curptr)?; + } + + { + let mut smbios_ep = Smbios30Entrypoint::default(); + smbios_ep.signature = *SM3_MAGIC_IDENT; + smbios_ep.length = mem::size_of::() as u8; + // SMBIOS rev 3.2.0 + smbios_ep.majorver = 0x03; + smbios_ep.minorver = 0x02; + smbios_ep.docrev = 0x00; + smbios_ep.revision = 0x01; // SMBIOS 3.0 + smbios_ep.max_size = curptr.unchecked_offset_from(physptr) as u32; + smbios_ep.physptr = physptr.0; + smbios_ep.checksum = compute_checksum(&smbios_ep); + mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START)) + .map_err(|_| Error::WriteSmbiosEp)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn struct_size() { + assert_eq!( + mem::size_of::(), + 0x18usize, + concat!("Size of: ", stringify!(Smbios30Entrypoint)) + ); + assert_eq!( + mem::size_of::(), + 0x14usize, + concat!("Size of: ", stringify!(SmbiosBiosInfo)) + ); + assert_eq!( + mem::size_of::(), + 0x1busize, + concat!("Size of: ", stringify!(SmbiosSysInfo)) + ); + } + + #[test] + fn entrypoint_checksum() { + let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap(); + + setup_smbios(&mem).unwrap(); + + let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap(); + + assert_eq!(compute_checksum(&smbios_ep), 0); + } +}