• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::FromBytes;
16 use zerocopy::Immutable;
17 use zerocopy::IntoBytes;
18 use zerocopy::KnownLayout;
19 
20 #[sorted]
21 #[derive(Error, Debug)]
22 pub 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 
55 pub type Result<T> = result::Result<T, Error>;
56 
57 const SMBIOS_START: u64 = 0xf0000; // First possible location per the spec.
58 
59 // Constants sourced from SMBIOS Spec 3.2.0.
60 const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
61 const BIOS_INFORMATION: u8 = 0;
62 const SYSTEM_INFORMATION: u8 = 1;
63 const OEM_STRING: u8 = 11;
64 const END_OF_TABLE: u8 = 127;
65 const PCI_SUPPORTED: u64 = 1 << 7;
66 const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
67 
68 const DEFAULT_SMBIOS_BIOS_VENDOR: &str = "crosvm";
69 const DEFAULT_SMBIOS_BIOS_VERSION: &str = "0";
70 const DEFAULT_SMBIOS_MANUFACTURER: &str = "ChromiumOS";
71 const DEFAULT_SMBIOS_PRODUCT_NAME: &str = "crosvm";
72 
compute_checksum<T: Copy>(v: &T) -> u873 fn 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)]
86 pub 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)]
97 pub 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)]
111 pub 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)]
126 pub 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)]
142 pub 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)]
158 pub 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)]
167 pub struct SmbiosEndOfTable {
168     pub typ: u8,
169     pub length: u8,
170     pub handle: u16,
171 }
172 
write_and_incr<T: Immutable + IntoBytes + FromBytes>( mem: &GuestMemory, val: T, mut curptr: GuestAddress, ) -> Result<GuestAddress>173 fn 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 
write_string(mem: &GuestMemory, val: &str, mut curptr: GuestAddress) -> Result<GuestAddress>186 fn 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 
setup_smbios(mem: &GuestMemory, options: &SmbiosOptions, bios_size: u64) -> Result<()>197 pub 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)]
337 mod tests {
338     use super::*;
339 
340     #[test]
struct_size()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]
entrypoint_checksum()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 }
387