mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
Add rudimentary support for SMBIOS 3.0.
SMBIOS allows to pass down system information from the BIOS to the OS. Its information is used on Linux to construct modalias entries for the system in order to facilite module autoloading on specific hardware. Adding it also allows package management software to target specific packages when crosvm is used as the hypervisor (e.g. to pre-install the helper daemons or a special vTPM driver). This change has only been tested with Linux and implements the bare minimum necessary to make a crosvm hypervisor detectable through DMI data. As such it also skips over some required structures like BIOS information that do not technically apply. The result is a hodgepodge of standards: SMBIOS 3.0 provides a convenient 64-bit entrypoint that we implement. However the structures are cut short to SMBIOS 2.0 standards as most of the fields are skipped. Linux deals just fine with this, although some of the dmi/id files in sysfs will be empty as a result. The resulting modalias looks like this: dmi:bvncrosvm:bvr0:bd:svnChromiumOS:pncrosvm:pvr: The kernel prints this as part of startup: SMBIOS 3.2.0 present. DMI: ChromiumOS crosvm, BIOS 0 And for oops/panic: Hardware name: ChromiumOS crosvm, BIOS 0 dmidecode's view on the tables (which uses its own parser): Getting SMBIOS data from sysfs. SMBIOS 3.2.0 present. Table at 0x000F0018. Handle 0x0001, DMI type 0, 20 bytes BIOS Information Vendor: crosvm Version: 0 Release Date: Not Specified ROM Size: 64 kB Characteristics: PCI is supported System is a virtual machine Handle 0x0002, DMI type 1, 27 bytes System Information Manufacturer: ChromiumOS Product Name: crosvm Version: Not Specified Serial Number: Not Specified UUID: Not Settable Wake-up Type: Reserved SKU Number: Not Specified Family: Not Specified TEST=cargo test Change-Id: Ie27105711a9bc14941d387b720da350349dff265 Reviewed-on: https://chromium-review.googlesource.com/1571565 Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com> Tested-by: David Tolnay <dtolnay@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
ecf81e0f05
commit
9ce95b3037
2 changed files with 252 additions and 0 deletions
|
@ -51,6 +51,7 @@ mod gdt;
|
|||
mod interrupts;
|
||||
mod mptable;
|
||||
mod regs;
|
||||
mod smbios;
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
@ -103,6 +104,7 @@ pub enum Error {
|
|||
SetupMptable(mptable::Error),
|
||||
SetupMsrs(regs::Error),
|
||||
SetupRegs(regs::Error),
|
||||
SetupSmbios(smbios::Error),
|
||||
SetupSregs(regs::Error),
|
||||
ZeroPagePastRamEnd,
|
||||
ZeroPageSetup,
|
||||
|
@ -144,6 +146,7 @@ impl Display for Error {
|
|||
SetupMptable(e) => write!(f, "failed to set up mptable: {}", e),
|
||||
SetupMsrs(e) => write!(f, "failed to set up MSRs: {}", e),
|
||||
SetupRegs(e) => write!(f, "failed to set up registers: {}", e),
|
||||
SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", e),
|
||||
SetupSregs(e) => write!(f, "failed to set up sregs: {}", e),
|
||||
ZeroPagePastRamEnd => write!(f, "the zero page extends past the end of guest_mem"),
|
||||
ZeroPageSetup => write!(f, "error writing the zero page of guest memory"),
|
||||
|
@ -190,6 +193,8 @@ fn configure_system(
|
|||
// Note that this puts the mptable at 0x0 in guest physical memory.
|
||||
mptable::setup_mptable(guest_mem, num_cpus, pci_irqs).map_err(Error::SetupMptable)?;
|
||||
|
||||
smbios::setup_smbios(guest_mem).map_err(Error::SetupSmbios)?;
|
||||
|
||||
let mut params: boot_params = Default::default();
|
||||
|
||||
params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
|
|
247
x86_64/src/smbios.rs
Normal file
247
x86_64/src/smbios.rs
Normal file
|
@ -0,0 +1,247 @@
|
|||
// 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.
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
|
||||
use data_model::DataInit;
|
||||
use sys_util::{GuestAddress, GuestMemory};
|
||||
|
||||
#[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<T> = result::Result<T, Error>;
|
||||
|
||||
const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
|
||||
|
||||
// Constants sourced from SMBIOS Spec 3.2.0.
|
||||
const SM3_MAGIC_IDENT: &'static [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<T: Copy>(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::<T>()) };
|
||||
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 data_model::DataInit 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 data_model::DataInit 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 data_model::DataInit for SmbiosSysInfo {}
|
||||
|
||||
fn write_and_incr<T: DataInit>(
|
||||
mem: &GuestMemory,
|
||||
val: T,
|
||||
mut curptr: GuestAddress,
|
||||
) -> Result<GuestAddress> {
|
||||
mem.write_obj_at_addr(val, curptr)
|
||||
.map_err(|_| Error::WriteData)?;
|
||||
curptr = curptr
|
||||
.checked_add(mem::size_of::<T>() as u64)
|
||||
.ok_or(Error::NotEnoughMemory)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
fn write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress> {
|
||||
for c in val.as_bytes().iter() {
|
||||
curptr = write_and_incr(mem, c.clone(), curptr)?;
|
||||
}
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
pub fn setup_smbios(mem: &GuestMemory) -> Result<()> {
|
||||
let physptr = GuestAddress(SMBIOS_START)
|
||||
.checked_add(mem::size_of::<Smbios30Entrypoint>() 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::<SmbiosBiosInfo>() 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, "crosvm", 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::<SmbiosSysInfo>() 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, "ChromiumOS", curptr)?;
|
||||
curptr = write_string(mem, "crosvm", 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::<Smbios30Entrypoint>() 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.offset_from(physptr) as u32;
|
||||
smbios_ep.physptr = physptr.offset();
|
||||
smbios_ep.checksum = compute_checksum(&smbios_ep);
|
||||
mem.write_obj_at_addr(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::<Smbios30Entrypoint>(),
|
||||
0x18usize,
|
||||
concat!("Size of: ", stringify!(Smbios30Entrypoint))
|
||||
);
|
||||
assert_eq!(
|
||||
mem::size_of::<SmbiosBiosInfo>(),
|
||||
0x14usize,
|
||||
concat!("Size of: ", stringify!(SmbiosBiosInfo))
|
||||
);
|
||||
assert_eq!(
|
||||
mem::size_of::<SmbiosSysInfo>(),
|
||||
0x1busize,
|
||||
concat!("Size of: ", stringify!(SmbiosSysInfo))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrypoint_checksum() {
|
||||
let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
|
||||
|
||||
setup_smbios(&mem).unwrap();
|
||||
|
||||
let smbios_ep: Smbios30Entrypoint =
|
||||
mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();
|
||||
|
||||
assert_eq!(compute_checksum(&smbios_ep), 0);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue