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