use std::mem;
use std::result;
use std::slice;
use arch::SmbiosOptions;
use remain::sorted;
use thiserror::Error;
use uuid::Uuid;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
#[sorted]
#[derive(Error, Debug)]
pub enum Error {
#[error("The SMBIOS table has too little address space to be stored")]
AddressOverflow,
#[error("Failure while zeroing out the memory for the SMBIOS table")]
Clear,
#[error("Failure to verify host SMBIOS entry checksum")]
InvalidChecksum,
#[error("Failure to read host SMBIOS data")]
InvalidInput,
#[error("Failure while reading SMBIOS data file")]
IoFailed,
#[error("There was too little guest memory to store the SMBIOS table")]
NotEnoughMemory,
#[error("a provided SMBIOS string contains a null character")]
StringHasNullCharacter,
#[error("Too many OEM strings were provided, limited to 255")]
TooManyOemStrings,
#[error("Failure to write additional data to memory")]
WriteData,
#[error("Failure to write SMBIOS entrypoint structure")]
WriteSmbiosEp,
}
pub type Result<T> = result::Result<T, Error>;
const SMBIOS_START: u64 = 0xf0000; const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
const BIOS_INFORMATION: u8 = 0;
const SYSTEM_INFORMATION: u8 = 1;
const OEM_STRING: u8 = 11;
const END_OF_TABLE: u8 = 127;
const PCI_SUPPORTED: u64 = 1 << 7;
const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
const DEFAULT_SMBIOS_BIOS_VENDOR: &str = "crosvm";
const DEFAULT_SMBIOS_BIOS_VERSION: &str = "0";
const DEFAULT_SMBIOS_MANUFACTURER: &str = "ChromiumOS";
const DEFAULT_SMBIOS_PRODUCT_NAME: &str = "crosvm";
fn compute_checksum<T: Copy>(v: &T) -> u8 {
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(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
pub struct Smbios23Intermediate {
pub signature: [u8; 5usize],
pub checksum: u8,
pub length: u16,
pub address: u32,
pub count: u16,
pub revision: u8,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
pub struct Smbios23Entrypoint {
pub signature: [u8; 4usize],
pub checksum: u8,
pub length: u8,
pub majorver: u8,
pub minorver: u8,
pub max_size: u16,
pub revision: u8,
pub reserved: [u8; 5usize],
pub dmi: Smbios23Intermediate,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
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,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
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,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
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,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
pub struct SmbiosOemStrings {
pub typ: u8,
pub length: u8,
pub handle: u16,
pub count: u8,
}
#[repr(C, packed)]
#[derive(Default, Clone, Copy, FromZeroes, FromBytes, AsBytes)]
pub struct SmbiosEndOfTable {
pub typ: u8,
pub length: u8,
pub handle: u16,
}
fn write_and_incr<T: AsBytes + FromBytes>(
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() {
if *c == 0 {
return Err(Error::StringHasNullCharacter);
}
curptr = write_and_incr(mem, *c, curptr)?;
}
curptr = write_and_incr(mem, 0_u8, curptr)?;
Ok(curptr)
}
pub fn setup_smbios(mem: &GuestMemory, options: &SmbiosOptions, bios_size: u64) -> 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 rom_size = (bios_size >> 16)
.saturating_sub(1)
.try_into()
.unwrap_or(0xFF);
let smbios_biosinfo = SmbiosBiosInfo {
typ: BIOS_INFORMATION,
length: mem::size_of::<SmbiosBiosInfo>() as u8,
handle,
vendor: 1, version: 2, characteristics: PCI_SUPPORTED,
characteristics_ext2: IS_VIRTUAL_MACHINE,
rom_size,
..Default::default()
};
curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
curptr = write_string(
mem,
options
.bios_vendor
.as_deref()
.unwrap_or(DEFAULT_SMBIOS_BIOS_VENDOR),
curptr,
)?;
curptr = write_string(
mem,
options
.bios_version
.as_deref()
.unwrap_or(DEFAULT_SMBIOS_BIOS_VERSION),
curptr,
)?;
curptr = write_and_incr(mem, 0_u8, curptr)?;
}
{
handle += 1;
let smbios_sysinfo = SmbiosSysInfo {
typ: SYSTEM_INFORMATION,
length: mem::size_of::<SmbiosSysInfo>() as u8,
handle,
uuid: options.uuid.unwrap_or(Uuid::nil()).to_bytes_le(),
manufacturer: 1, product_name: 2, serial_number: if options.serial_number.is_some() {
3 } else {
0 },
..Default::default()
};
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_string(
mem,
options
.manufacturer
.as_deref()
.unwrap_or(DEFAULT_SMBIOS_MANUFACTURER),
curptr,
)?;
curptr = write_string(
mem,
options
.product_name
.as_deref()
.unwrap_or(DEFAULT_SMBIOS_PRODUCT_NAME),
curptr,
)?;
if let Some(serial_number) = options.serial_number.as_deref() {
curptr = write_string(mem, serial_number, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
if !options.oem_strings.is_empty() {
if options.oem_strings.len() > u8::MAX.into() {
return Err(Error::TooManyOemStrings);
}
handle += 1;
let smbios_oemstring = SmbiosOemStrings {
typ: OEM_STRING,
length: mem::size_of::<SmbiosOemStrings>() as u8,
handle,
count: options.oem_strings.len() as u8,
};
curptr = write_and_incr(mem, smbios_oemstring, curptr)?;
for oem_string in &options.oem_strings {
curptr = write_string(mem, oem_string, curptr)?;
}
curptr = write_and_incr(mem, 0u8, curptr)?;
}
{
handle += 1;
let smbios_sysinfo = SmbiosEndOfTable {
typ: END_OF_TABLE,
length: mem::size_of::<SmbiosEndOfTable>() as u8,
handle,
};
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
curptr = write_and_incr(mem, 0_u8, curptr)?; curptr = write_and_incr(mem, 0_u8, curptr)?; }
{
let mut smbios_ep = Smbios30Entrypoint::default();
smbios_ep.signature = *SM3_MAGIC_IDENT;
smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
smbios_ep.majorver = 0x03;
smbios_ep.minorver = 0x02;
smbios_ep.docrev = 0x00;
smbios_ep.revision = 0x01; 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::<Smbios23Entrypoint>(),
0x1fusize,
concat!("Size of: ", stringify!(Smbios23Entrypoint))
);
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))
);
assert_eq!(
mem::size_of::<SmbiosOemStrings>(),
0x5usize,
concat!("Size of: ", stringify!(SmbiosOemStrings))
);
assert_eq!(
mem::size_of::<SmbiosEndOfTable>(),
0x4usize,
concat!("Size of: ", stringify!(SmbiosEndOfTable))
);
}
#[test]
fn entrypoint_checksum() {
let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
setup_smbios(&mem, &SmbiosOptions::default(), 0).unwrap();
let smbios_ep: Smbios30Entrypoint =
mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();
assert_eq!(compute_checksum(&smbios_ep), 0);
}
}