x86_64/
smbios.rs

1// Copyright 2019 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::mem;
6use std::result;
7use std::slice;
8
9use arch::SmbiosOptions;
10use remain::sorted;
11use thiserror::Error;
12use uuid::Uuid;
13use vm_memory::GuestAddress;
14use vm_memory::GuestMemory;
15use zerocopy::FromBytes;
16use zerocopy::Immutable;
17use zerocopy::IntoBytes;
18use zerocopy::KnownLayout;
19
20#[sorted]
21#[derive(Error, Debug)]
22pub enum Error {
23    /// The SMBIOS table has too little address space to be stored.
24    #[error("The SMBIOS table has too little address space to be stored")]
25    AddressOverflow,
26    /// Failure while zeroing out the memory for the SMBIOS table.
27    #[error("Failure while zeroing out the memory for the SMBIOS table")]
28    Clear,
29    /// Invalid table entry point checksum
30    #[error("Failure to verify host SMBIOS entry checksum")]
31    InvalidChecksum,
32    /// Incorrect or not readable host SMBIOS data
33    #[error("Failure to read host SMBIOS data")]
34    InvalidInput,
35    /// Failure while reading SMBIOS data file
36    #[error("Failure while reading SMBIOS data file")]
37    IoFailed,
38    /// There was too little guest memory to store the entire SMBIOS table.
39    #[error("There was too little guest memory to store the SMBIOS table")]
40    NotEnoughMemory,
41    /// A provided string contained a null character
42    #[error("a provided SMBIOS string contains a null character")]
43    StringHasNullCharacter,
44    /// Too many OEM strings provided
45    #[error("Too many OEM strings were provided, limited to 255")]
46    TooManyOemStrings,
47    /// Failure to write additional data to memory
48    #[error("Failure to write additional data to memory")]
49    WriteData,
50    /// Failure to write SMBIOS entrypoint structure
51    #[error("Failure to write SMBIOS entrypoint structure")]
52    WriteSmbiosEp,
53}
54
55pub type Result<T> = result::Result<T, Error>;
56
57const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
58
59// Constants sourced from SMBIOS Spec 3.2.0.
60const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
61const BIOS_INFORMATION: u8 = 0;
62const SYSTEM_INFORMATION: u8 = 1;
63const OEM_STRING: u8 = 11;
64const END_OF_TABLE: u8 = 127;
65const PCI_SUPPORTED: u64 = 1 << 7;
66const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
67
68const DEFAULT_SMBIOS_BIOS_VENDOR: &str = "crosvm";
69const DEFAULT_SMBIOS_BIOS_VERSION: &str = "0";
70const DEFAULT_SMBIOS_MANUFACTURER: &str = "ChromiumOS";
71const DEFAULT_SMBIOS_PRODUCT_NAME: &str = "crosvm";
72
73fn compute_checksum<T: Copy>(v: &T) -> u8 {
74    // SAFETY:
75    // Safe because we are only reading the bytes within the size of the `T` reference `v`.
76    let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
77    let mut checksum: u8 = 0;
78    for i in v_slice.iter() {
79        checksum = checksum.wrapping_add(*i);
80    }
81    (!checksum).wrapping_add(1)
82}
83
84#[repr(C, packed)]
85#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
86pub struct Smbios23Intermediate {
87    pub signature: [u8; 5usize],
88    pub checksum: u8,
89    pub length: u16,
90    pub address: u32,
91    pub count: u16,
92    pub revision: u8,
93}
94
95#[repr(C, packed)]
96#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
97pub struct Smbios23Entrypoint {
98    pub signature: [u8; 4usize],
99    pub checksum: u8,
100    pub length: u8,
101    pub majorver: u8,
102    pub minorver: u8,
103    pub max_size: u16,
104    pub revision: u8,
105    pub reserved: [u8; 5usize],
106    pub dmi: Smbios23Intermediate,
107}
108
109#[repr(C, packed)]
110#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
111pub struct Smbios30Entrypoint {
112    pub signature: [u8; 5usize],
113    pub checksum: u8,
114    pub length: u8,
115    pub majorver: u8,
116    pub minorver: u8,
117    pub docrev: u8,
118    pub revision: u8,
119    pub reserved: u8,
120    pub max_size: u32,
121    pub physptr: u64,
122}
123
124#[repr(C, packed)]
125#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
126pub struct SmbiosBiosInfo {
127    pub typ: u8,
128    pub length: u8,
129    pub handle: u16,
130    pub vendor: u8,
131    pub version: u8,
132    pub start_addr: u16,
133    pub release_date: u8,
134    pub rom_size: u8,
135    pub characteristics: u64,
136    pub characteristics_ext1: u8,
137    pub characteristics_ext2: u8,
138}
139
140#[repr(C, packed)]
141#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
142pub struct SmbiosSysInfo {
143    pub typ: u8,
144    pub length: u8,
145    pub handle: u16,
146    pub manufacturer: u8,
147    pub product_name: u8,
148    pub version: u8,
149    pub serial_number: u8,
150    pub uuid: [u8; 16usize],
151    pub wake_up_type: u8,
152    pub sku: u8,
153    pub family: u8,
154}
155
156#[repr(C, packed)]
157#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
158pub struct SmbiosOemStrings {
159    pub typ: u8,
160    pub length: u8,
161    pub handle: u16,
162    pub count: u8,
163}
164
165#[repr(C, packed)]
166#[derive(Default, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
167pub struct SmbiosEndOfTable {
168    pub typ: u8,
169    pub length: u8,
170    pub handle: u16,
171}
172
173fn write_and_incr<T: Immutable + IntoBytes + FromBytes>(
174    mem: &GuestMemory,
175    val: T,
176    mut curptr: GuestAddress,
177) -> Result<GuestAddress> {
178    mem.write_obj_at_addr(val, curptr)
179        .map_err(|_| Error::WriteData)?;
180    curptr = curptr
181        .checked_add(mem::size_of::<T>() as u64)
182        .ok_or(Error::NotEnoughMemory)?;
183    Ok(curptr)
184}
185
186fn write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress> {
187    for c in val.as_bytes().iter() {
188        if *c == 0 {
189            return Err(Error::StringHasNullCharacter);
190        }
191        curptr = write_and_incr(mem, *c, curptr)?;
192    }
193    curptr = write_and_incr(mem, 0_u8, curptr)?;
194    Ok(curptr)
195}
196
197pub fn setup_smbios(mem: &GuestMemory, options: &SmbiosOptions, bios_size: u64) -> Result<()> {
198    let physptr = GuestAddress(SMBIOS_START)
199        .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
200        .ok_or(Error::NotEnoughMemory)?;
201    let mut curptr = physptr;
202    let mut handle = 0;
203
204    {
205        handle += 1;
206
207        // BIOS ROM size is encoded as 64K * (n + 1)
208        let rom_size = (bios_size >> 16)
209            .saturating_sub(1)
210            .try_into()
211            .unwrap_or(0xFF);
212
213        let smbios_biosinfo = SmbiosBiosInfo {
214            typ: BIOS_INFORMATION,
215            length: mem::size_of::<SmbiosBiosInfo>() as u8,
216            handle,
217            vendor: 1,  // First string written in this section
218            version: 2, // Second string written in this section
219            characteristics: PCI_SUPPORTED,
220            characteristics_ext2: IS_VIRTUAL_MACHINE,
221            rom_size,
222            ..Default::default()
223        };
224        curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
225        curptr = write_string(
226            mem,
227            options
228                .bios_vendor
229                .as_deref()
230                .unwrap_or(DEFAULT_SMBIOS_BIOS_VENDOR),
231            curptr,
232        )?;
233        curptr = write_string(
234            mem,
235            options
236                .bios_version
237                .as_deref()
238                .unwrap_or(DEFAULT_SMBIOS_BIOS_VERSION),
239            curptr,
240        )?;
241        curptr = write_and_incr(mem, 0_u8, curptr)?;
242    }
243
244    {
245        handle += 1;
246        let smbios_sysinfo = SmbiosSysInfo {
247            typ: SYSTEM_INFORMATION,
248            length: mem::size_of::<SmbiosSysInfo>() as u8,
249            handle,
250            // PC vendors consistently use little-endian ordering for reasons
251            uuid: options.uuid.unwrap_or(Uuid::nil()).to_bytes_le(),
252            manufacturer: 1, // First string written in this section
253            product_name: 2, // Second string written in this section
254            serial_number: if options.serial_number.is_some() {
255                3 // Third string written in this section
256            } else {
257                0 // Serial number not specified
258            },
259            ..Default::default()
260        };
261        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
262        curptr = write_string(
263            mem,
264            options
265                .manufacturer
266                .as_deref()
267                .unwrap_or(DEFAULT_SMBIOS_MANUFACTURER),
268            curptr,
269        )?;
270        curptr = write_string(
271            mem,
272            options
273                .product_name
274                .as_deref()
275                .unwrap_or(DEFAULT_SMBIOS_PRODUCT_NAME),
276            curptr,
277        )?;
278        if let Some(serial_number) = options.serial_number.as_deref() {
279            curptr = write_string(mem, serial_number, curptr)?;
280        }
281        curptr = write_and_incr(mem, 0u8, curptr)?;
282    }
283
284    if !options.oem_strings.is_empty() {
285        // AFAIK nothing prevents us from creating multiple OEM string tables
286        // if we have more than 255 strings, but 255 already seems pretty
287        // excessive.
288        if options.oem_strings.len() > u8::MAX.into() {
289            return Err(Error::TooManyOemStrings);
290        }
291        handle += 1;
292        let smbios_oemstring = SmbiosOemStrings {
293            typ: OEM_STRING,
294            length: mem::size_of::<SmbiosOemStrings>() as u8,
295            handle,
296            count: options.oem_strings.len() as u8,
297        };
298        curptr = write_and_incr(mem, smbios_oemstring, curptr)?;
299        for oem_string in &options.oem_strings {
300            curptr = write_string(mem, oem_string, curptr)?;
301        }
302        curptr = write_and_incr(mem, 0u8, curptr)?;
303    }
304
305    {
306        handle += 1;
307        let smbios_sysinfo = SmbiosEndOfTable {
308            typ: END_OF_TABLE,
309            length: mem::size_of::<SmbiosEndOfTable>() as u8,
310            handle,
311        };
312        curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
313        curptr = write_and_incr(mem, 0_u8, curptr)?; // No strings
314        curptr = write_and_incr(mem, 0_u8, curptr)?; // Structure terminator
315    }
316
317    {
318        let mut smbios_ep = Smbios30Entrypoint::default();
319        smbios_ep.signature = *SM3_MAGIC_IDENT;
320        smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
321        // SMBIOS rev 3.2.0
322        smbios_ep.majorver = 0x03;
323        smbios_ep.minorver = 0x02;
324        smbios_ep.docrev = 0x00;
325        smbios_ep.revision = 0x01; // SMBIOS 3.0
326        smbios_ep.max_size = curptr.offset_from(physptr) as u32;
327        smbios_ep.physptr = physptr.offset();
328        smbios_ep.checksum = compute_checksum(&smbios_ep);
329        mem.write_obj_at_addr(smbios_ep, GuestAddress(SMBIOS_START))
330            .map_err(|_| Error::WriteSmbiosEp)?;
331    }
332
333    Ok(())
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn struct_size() {
342        assert_eq!(
343            mem::size_of::<Smbios23Entrypoint>(),
344            0x1fusize,
345            concat!("Size of: ", stringify!(Smbios23Entrypoint))
346        );
347        assert_eq!(
348            mem::size_of::<Smbios30Entrypoint>(),
349            0x18usize,
350            concat!("Size of: ", stringify!(Smbios30Entrypoint))
351        );
352        assert_eq!(
353            mem::size_of::<SmbiosBiosInfo>(),
354            0x14usize,
355            concat!("Size of: ", stringify!(SmbiosBiosInfo))
356        );
357        assert_eq!(
358            mem::size_of::<SmbiosSysInfo>(),
359            0x1busize,
360            concat!("Size of: ", stringify!(SmbiosSysInfo))
361        );
362        assert_eq!(
363            mem::size_of::<SmbiosOemStrings>(),
364            0x5usize,
365            concat!("Size of: ", stringify!(SmbiosOemStrings))
366        );
367        assert_eq!(
368            mem::size_of::<SmbiosEndOfTable>(),
369            0x4usize,
370            concat!("Size of: ", stringify!(SmbiosEndOfTable))
371        );
372    }
373
374    #[test]
375    fn entrypoint_checksum() {
376        let mem = GuestMemory::new(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
377
378        // Use default 3.0 SMBIOS format.
379        setup_smbios(&mem, &SmbiosOptions::default(), 0).unwrap();
380
381        let smbios_ep: Smbios30Entrypoint =
382            mem.read_obj_from_addr(GuestAddress(SMBIOS_START)).unwrap();
383
384        assert_eq!(compute_checksum(&smbios_ep), 0);
385    }
386}