xref: /cloud-hypervisor/arch/src/x86_64/smbios.rs (revision eea9bcea38e0c5649f444c829f3a4f9c22aa486c)
1 // Copyright © 2020 Intel Corporation
2 //
3 // Copyright 2019 The Chromium OS Authors. All rights reserved.
4 // Use of this source code is governed by a BSD-style license that can be
5 // found in the LICENSE file.
6 //
7 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
8 
9 use crate::layout::SMBIOS_START;
10 use crate::GuestMemoryMmap;
11 use std::fmt::{self, Display};
12 use std::mem;
13 use std::result;
14 use std::slice;
15 use uuid::Uuid;
16 use vm_memory::ByteValued;
17 use vm_memory::{Address, Bytes, GuestAddress};
18 
19 #[derive(Debug)]
20 pub enum Error {
21     /// There was too little guest memory to store the entire SMBIOS table.
22     NotEnoughMemory,
23     /// 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     Clear,
27     /// Failure to write SMBIOS entrypoint structure
28     WriteSmbiosEp,
29     /// Failure to write additional data to memory
30     WriteData,
31     /// Failure to parse uuid, uuid format may be error
32     ParseUuid(uuid::Error),
33 }
34 
35 impl std::error::Error for Error {}
36 
37 impl Display for Error {
38     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39         use self::Error::*;
40 
41         let description = match self {
42             NotEnoughMemory => {
43                 "There was too little guest memory to store the SMBIOS table".to_string()
44             }
45             AddressOverflow => {
46                 "The SMBIOS table has too little address space to be stored".to_string()
47             }
48             Clear => "Failure while zeroing out the memory for the SMBIOS table".to_string(),
49             WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure".to_string(),
50             WriteData => "Failure to write additional data to memory".to_string(),
51             ParseUuid(e) => format!("Failure to parse uuid: {}", e),
52         };
53 
54         write!(f, "SMBIOS error: {}", description)
55     }
56 }
57 
58 pub type Result<T> = result::Result<T, Error>;
59 
60 // Constants sourced from SMBIOS Spec 3.2.0.
61 const SM3_MAGIC_IDENT: &[u8; 5usize] = b"_SM3_";
62 const BIOS_INFORMATION: u8 = 0;
63 const SYSTEM_INFORMATION: u8 = 1;
64 const OEM_STRINGS: u8 = 11;
65 const END_OF_TABLE: u8 = 127;
66 const PCI_SUPPORTED: u64 = 1 << 7;
67 const IS_VIRTUAL_MACHINE: u8 = 1 << 4;
68 
69 fn compute_checksum<T: Copy>(v: &T) -> u8 {
70     // Safe because we are only reading the bytes within the size of the `T` reference `v`.
71     let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
72     let mut checksum: u8 = 0;
73     for i in v_slice.iter() {
74         checksum = checksum.wrapping_add(*i);
75     }
76     (!checksum).wrapping_add(1)
77 }
78 
79 #[repr(C)]
80 #[repr(packed)]
81 #[derive(Default, Copy, Clone)]
82 struct Smbios30Entrypoint {
83     signature: [u8; 5usize],
84     checksum: u8,
85     length: u8,
86     majorver: u8,
87     minorver: u8,
88     docrev: u8,
89     revision: u8,
90     reserved: u8,
91     max_size: u32,
92     physptr: u64,
93 }
94 
95 #[repr(C)]
96 #[repr(packed)]
97 #[derive(Default, Copy, Clone)]
98 struct SmbiosBiosInfo {
99     r#type: u8,
100     length: u8,
101     handle: u16,
102     vendor: u8,
103     version: u8,
104     start_addr: u16,
105     release_date: u8,
106     rom_size: u8,
107     characteristics: u64,
108     characteristics_ext1: u8,
109     characteristics_ext2: u8,
110 }
111 
112 #[repr(C)]
113 #[repr(packed)]
114 #[derive(Default, Copy, Clone)]
115 struct SmbiosSysInfo {
116     r#type: u8,
117     length: u8,
118     handle: u16,
119     manufacturer: u8,
120     product_name: u8,
121     version: u8,
122     serial_number: u8,
123     uuid: [u8; 16usize],
124     wake_up_type: u8,
125     sku: u8,
126     family: u8,
127 }
128 
129 #[repr(C)]
130 #[repr(packed)]
131 #[derive(Default, Copy, Clone)]
132 struct SmbiosOemStrings {
133     r#type: u8,
134     length: u8,
135     handle: u16,
136     count: u8,
137 }
138 
139 #[repr(C)]
140 #[repr(packed)]
141 #[derive(Default, Copy, Clone)]
142 struct SmbiosEndOfTable {
143     r#type: u8,
144     length: u8,
145     handle: u16,
146 }
147 
148 // SAFETY: These data structures only contain a series of integers
149 unsafe impl ByteValued for Smbios30Entrypoint {}
150 unsafe impl ByteValued for SmbiosBiosInfo {}
151 unsafe impl ByteValued for SmbiosSysInfo {}
152 unsafe impl ByteValued for SmbiosOemStrings {}
153 unsafe impl ByteValued for SmbiosEndOfTable {}
154 
155 fn write_and_incr<T: ByteValued>(
156     mem: &GuestMemoryMmap,
157     val: T,
158     mut curptr: GuestAddress,
159 ) -> Result<GuestAddress> {
160     mem.write_obj(val, curptr).map_err(|_| Error::WriteData)?;
161     curptr = curptr
162         .checked_add(mem::size_of::<T>() as u64)
163         .ok_or(Error::NotEnoughMemory)?;
164     Ok(curptr)
165 }
166 
167 fn write_string(
168     mem: &GuestMemoryMmap,
169     val: &str,
170     mut curptr: GuestAddress,
171 ) -> Result<GuestAddress> {
172     for c in val.as_bytes().iter() {
173         curptr = write_and_incr(mem, *c, curptr)?;
174     }
175     curptr = write_and_incr(mem, 0u8, curptr)?;
176     Ok(curptr)
177 }
178 
179 pub fn setup_smbios(
180     mem: &GuestMemoryMmap,
181     serial_number: Option<&str>,
182     uuid: Option<&str>,
183     oem_strings: Option<&[&str]>,
184 ) -> Result<u64> {
185     let physptr = GuestAddress(SMBIOS_START)
186         .checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
187         .ok_or(Error::NotEnoughMemory)?;
188     let mut curptr = physptr;
189     let mut handle = 0;
190 
191     {
192         handle += 1;
193         let smbios_biosinfo = SmbiosBiosInfo {
194             r#type: BIOS_INFORMATION,
195             length: mem::size_of::<SmbiosBiosInfo>() as u8,
196             handle,
197             vendor: 1,  // First string written in this section
198             version: 2, // Second string written in this section
199             characteristics: PCI_SUPPORTED,
200             characteristics_ext2: IS_VIRTUAL_MACHINE,
201             ..Default::default()
202         };
203         curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
204         curptr = write_string(mem, "cloud-hypervisor", curptr)?;
205         curptr = write_string(mem, "0", curptr)?;
206         curptr = write_and_incr(mem, 0u8, curptr)?;
207     }
208 
209     {
210         handle += 1;
211 
212         let uuid_number = uuid
213             .map(Uuid::parse_str)
214             .transpose()
215             .map_err(Error::ParseUuid)?
216             .unwrap_or(Uuid::nil());
217         let smbios_sysinfo = SmbiosSysInfo {
218             r#type: SYSTEM_INFORMATION,
219             length: mem::size_of::<SmbiosSysInfo>() as u8,
220             handle,
221             manufacturer: 1, // First string written in this section
222             product_name: 2, // Second string written in this section
223             serial_number: serial_number.map(|_| 3).unwrap_or_default(), // 3rd string
224             uuid: uuid_number.to_bytes_le(), // set uuid
225             ..Default::default()
226         };
227         curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
228         curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
229         curptr = write_string(mem, "cloud-hypervisor", curptr)?;
230         if let Some(serial_number) = serial_number {
231             curptr = write_string(mem, serial_number, curptr)?;
232         }
233         curptr = write_and_incr(mem, 0u8, curptr)?;
234     }
235 
236     if let Some(oem_strings) = oem_strings {
237         handle += 1;
238 
239         let smbios_oemstrings = SmbiosOemStrings {
240             r#type: OEM_STRINGS,
241             length: mem::size_of::<SmbiosOemStrings>() as u8,
242             handle,
243             count: oem_strings.len() as u8,
244         };
245 
246         curptr = write_and_incr(mem, smbios_oemstrings, curptr)?;
247 
248         for s in oem_strings {
249             curptr = write_string(mem, s, curptr)?;
250         }
251 
252         curptr = write_and_incr(mem, 0u8, curptr)?;
253     }
254 
255     {
256         handle += 1;
257         let smbios_end = SmbiosEndOfTable {
258             r#type: END_OF_TABLE,
259             length: mem::size_of::<SmbiosEndOfTable>() as u8,
260             handle,
261         };
262         curptr = write_and_incr(mem, smbios_end, curptr)?;
263         curptr = write_and_incr(mem, 0u8, curptr)?;
264         curptr = write_and_incr(mem, 0u8, curptr)?;
265     }
266 
267     {
268         let mut smbios_ep = Smbios30Entrypoint {
269             signature: *SM3_MAGIC_IDENT,
270             length: mem::size_of::<Smbios30Entrypoint>() as u8,
271             // SMBIOS rev 3.2.0
272             majorver: 0x03,
273             minorver: 0x02,
274             docrev: 0x00,
275             revision: 0x01, // SMBIOS 3.0
276             max_size: curptr.unchecked_offset_from(physptr) as u32,
277             physptr: physptr.0,
278             ..Default::default()
279         };
280         smbios_ep.checksum = compute_checksum(&smbios_ep);
281         mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
282             .map_err(|_| Error::WriteSmbiosEp)?;
283     }
284 
285     Ok(curptr.unchecked_offset_from(physptr) + std::mem::size_of::<Smbios30Entrypoint>() as u64)
286 }
287 
288 #[cfg(test)]
289 mod tests {
290     use super::*;
291 
292     #[test]
293     fn struct_size() {
294         assert_eq!(
295             mem::size_of::<Smbios30Entrypoint>(),
296             0x18usize,
297             concat!("Size of: ", stringify!(Smbios30Entrypoint))
298         );
299         assert_eq!(
300             mem::size_of::<SmbiosBiosInfo>(),
301             0x14usize,
302             concat!("Size of: ", stringify!(SmbiosBiosInfo))
303         );
304         assert_eq!(
305             mem::size_of::<SmbiosSysInfo>(),
306             0x1busize,
307             concat!("Size of: ", stringify!(SmbiosSysInfo))
308         );
309     }
310 
311     #[test]
312     fn entrypoint_checksum() {
313         let mem = GuestMemoryMmap::from_ranges(&[(GuestAddress(SMBIOS_START), 4096)]).unwrap();
314 
315         setup_smbios(&mem, None, None, None).unwrap();
316 
317         let smbios_ep: Smbios30Entrypoint = mem.read_obj(GuestAddress(SMBIOS_START)).unwrap();
318 
319         assert_eq!(compute_checksum(&smbios_ep), 0);
320     }
321 }
322