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