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