summary refs log blame commit diff
path: root/x86_64/src/smbios.rs
blob: 0ae3851164968805745786cfdea1276c582151ae (plain) (tree)
















































                                                                                             
                                                



































































































                                                                                                    
                                                  































































































                                                                                   
// 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: &[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, 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);
    }
}