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