1mod spec;
18
19use spec::Smbios30EntryPoint;
20use spec::SmbiosType0;
21use spec::SmbiosType1;
22use spec::SmbiosType127;
23use zerocopy::IntoBytes;
24use zerocopy::LE;
25use zerocopy::U16;
26
27#[derive(Debug, Copy, Clone)]
29pub struct SmbiosBiosInfo<'a> {
30 pub vendor: &'a str,
32 pub version: &'a str,
34 pub release_date: &'a str,
36 pub major: u8,
38 pub minor: u8,
40}
41
42#[derive(Debug, Copy, Clone)]
44pub struct SmbiosSystemInfo<'a> {
45 pub manufacturer: &'a str,
47 pub product_name: &'a str,
49 pub version: &'a str,
51 pub serial_number: &'a str,
53 pub sku_number: &'a str,
55 pub family: &'a str,
57 pub uuid: [u8; 16],
60}
61
62#[derive(Debug, Copy, Clone)]
65pub struct SmbiosTables<'a> {
66 pub bios: SmbiosBiosInfo<'a>,
68 pub system: SmbiosSystemInfo<'a>,
70}
71
72pub const ENTRY_POINT_SIZE: usize = size_of::<Smbios30EntryPoint>();
77
78#[derive(Debug, Clone)]
80pub struct BuiltSmbios {
81 pub entry_point: Vec<u8>,
83 pub structure_table: Vec<u8>,
85}
86
87#[derive(Default)]
90struct StringSet {
91 strings: Vec<String>,
92}
93
94impl StringSet {
95 fn add(&mut self, s: &str) -> u8 {
104 let s = s.split('\0').next().unwrap_or("");
105 if s.is_empty() {
106 return 0;
107 }
108 self.strings.push(s.to_string());
109 self.strings.len().try_into().unwrap()
110 }
111
112 fn write_to(&self, out: &mut Vec<u8>) {
116 if self.strings.is_empty() {
117 out.extend_from_slice(&[0, 0]);
118 return;
119 }
120 for s in &self.strings {
121 out.extend_from_slice(s.as_bytes());
122 out.push(0);
123 }
124 out.push(0);
125 }
126}
127
128pub fn build(tables: &SmbiosTables<'_>, table_gpa: u64) -> BuiltSmbios {
134 let mut structure_table = Vec::new();
135
136 let mut next_handle = 0u16;
139 let mut handle = || {
140 let h = next_handle;
141 next_handle += 1;
142 U16::<LE>::new(h)
143 };
144
145 {
147 let mut strings = StringSet::default();
148 let vendor = strings.add(tables.bios.vendor);
149 let bios_version = strings.add(tables.bios.version);
150 let bios_release_date = strings.add(tables.bios.release_date);
151 let t0 = SmbiosType0 {
152 typ: 0,
153 length: size_of::<SmbiosType0>() as u8,
154 handle: handle(),
155 vendor,
156 bios_version,
157 bios_segment: 0.into(),
158 bios_release_date,
159 bios_size: 0,
160 characteristics: spec::BIOS_CHARACTERISTICS_PCI_SUPPORTED.into(),
161 characteristics_ext: [
162 spec::BIOS_CHARACTERISTICS_EXT1_ACPI,
163 spec::BIOS_CHARACTERISTICS_EXT2_VM,
164 ],
165 bios_major: tables.bios.major,
166 bios_minor: tables.bios.minor,
167 ec_major: 0xff,
168 ec_minor: 0xff,
169 ext_rom_size: 0.into(),
170 };
171 structure_table.extend_from_slice(t0.as_bytes());
172 strings.write_to(&mut structure_table);
173 }
174
175 {
177 let mut strings = StringSet::default();
178 let manufacturer = strings.add(tables.system.manufacturer);
179 let product_name = strings.add(tables.system.product_name);
180 let version = strings.add(tables.system.version);
181 let serial_number = strings.add(tables.system.serial_number);
182 let sku_number = strings.add(tables.system.sku_number);
183 let family = strings.add(tables.system.family);
184 let t1 = SmbiosType1 {
185 typ: 1,
186 length: size_of::<SmbiosType1>() as u8,
187 handle: handle(),
188 manufacturer,
189 product_name,
190 version,
191 serial_number,
192 uuid: tables.system.uuid,
193 wake_up_type: spec::WAKE_UP_TYPE_POWER_SWITCH,
194 sku_number,
195 family,
196 };
197 structure_table.extend_from_slice(t1.as_bytes());
198 strings.write_to(&mut structure_table);
199 }
200
201 {
203 let t127 = SmbiosType127 {
204 typ: 127,
205 length: size_of::<SmbiosType127>() as u8,
206 handle: handle(),
207 };
208 structure_table.extend_from_slice(t127.as_bytes());
209 structure_table.extend_from_slice(&[0, 0]);
211 }
212
213 let mut entry_point = Smbios30EntryPoint {
214 anchor: *b"_SM3_",
215 checksum: 0,
216 length: size_of::<Smbios30EntryPoint>() as u8,
217 major: 3,
218 minor: 1,
219 docrev: 0,
220 revision: 0x01,
221 reserved: 0,
222 max_size: u32::try_from(structure_table.len()).unwrap().into(),
223 table_addr: table_gpa.into(),
224 };
225 let sum = entry_point
226 .as_bytes()
227 .iter()
228 .fold(0u8, |acc, b| acc.wrapping_add(*b));
229 entry_point.checksum = 0u8.wrapping_sub(sum);
230
231 BuiltSmbios {
232 entry_point: entry_point.as_bytes().to_vec(),
233 structure_table,
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 fn test_smbios_tables() -> SmbiosTables<'static> {
242 SmbiosTables {
243 system: SmbiosSystemInfo {
244 manufacturer: "Test Manufacturer",
245 product_name: "Test Product",
246 version: "Test Version",
247 serial_number: "",
248 sku_number: "Test SKU",
249 family: "Test Family",
250 uuid: [0; 16],
251 },
252 bios: SmbiosBiosInfo {
253 vendor: "Test BIOS Vendor",
254 version: "Test BIOS Version",
255 release_date: "Test BIOS Release Date",
256 major: 1,
257 minor: 2,
258 },
259 }
260 }
261
262 #[test]
263 fn entry_point_checksum_is_zero() {
264 let built = build(&test_smbios_tables(), 0xf0020);
265 let sum = built
266 .entry_point
267 .iter()
268 .fold(0u8, |acc, b| acc.wrapping_add(*b));
269 assert_eq!(sum, 0);
270 }
271
272 #[test]
273 fn entry_point_fields() {
274 let table_gpa = 0xf0020;
275 let built = build(&test_smbios_tables(), table_gpa);
276 assert_eq!(built.entry_point.len(), 0x18);
277 assert_eq!(&built.entry_point[0..5], b"_SM3_");
278 assert_eq!(built.entry_point[6], 0x18); assert_eq!(built.entry_point[7], 3); assert_eq!(built.entry_point[8], 1); let max_size = u32::from_le_bytes(built.entry_point[0x0c..0x10].try_into().unwrap());
284 assert_eq!(max_size as usize, built.structure_table.len());
285
286 let addr = u64::from_le_bytes(built.entry_point[0x10..0x18].try_into().unwrap());
288 assert_eq!(addr, table_gpa);
289 }
290
291 #[test]
292 fn structure_table_layout() {
293 let built = build(&test_smbios_tables(), 0xf0020);
294 let table = &built.structure_table;
295
296 assert_eq!(table[0], 0); assert_eq!(table[1], 0x1a); assert_eq!(table[4], 1); assert_eq!(table[5], 2); let n = table.len();
308 assert_eq!(&table[n - 6..], &[127, 4, 0x02, 0x00, 0, 0]);
309 }
310
311 #[test]
312 fn strings_resolve() {
313 let built = build(&test_smbios_tables(), 0);
314 let strings_start = 0x1a;
317 let nul = built.structure_table[strings_start..]
318 .iter()
319 .position(|&b| b == 0)
320 .unwrap();
321 let vendor = &built.structure_table[strings_start..strings_start + nul];
322 assert_eq!(vendor, "Test BIOS Vendor".as_bytes());
323 }
324
325 #[test]
326 fn empty_string_uses_index_zero() {
327 let tables = test_smbios_tables();
328 let built = build(&tables, 0);
329 let t1_off = struct_offset(&built.structure_table, 1).expect("Type 1 present");
330 assert_eq!(built.structure_table[t1_off + 7], 0);
334 }
335
336 #[test]
337 fn interior_nul_is_truncated() {
338 let mut tables = test_smbios_tables();
339 tables.system.manufacturer = "Mfg\0evil";
343 let built = build(&tables, 0);
344 let t1_off = struct_offset(&built.structure_table, 1).expect("Type 1 present");
345 assert_eq!(built.structure_table[t1_off + 4], 1);
347 assert_eq!(built.structure_table[t1_off + 5], 2);
348 let len = built.structure_table[t1_off + 1] as usize;
351 let strings = &built.structure_table[t1_off + len..];
352 let first_nul = strings.iter().position(|&b| b == 0).unwrap();
353 assert_eq!(&strings[..first_nul], b"Mfg");
354 assert!(!strings.windows(4).any(|w| w == b"evil"));
355 }
356
357 fn struct_offset(table: &[u8], want: u8) -> Option<usize> {
360 let mut off = 0;
361 while off + 2 <= table.len() {
362 let typ = table[off];
363 let formatted_len = table[off + 1] as usize;
364 if typ == want {
365 return Some(off);
366 }
367 let mut i = off + formatted_len;
370 while i + 1 < table.len() && !(table[i] == 0 && table[i + 1] == 0) {
371 i += 1;
372 }
373 off = i + 2;
374 }
375 None
376 }
377}