1mod spec;
15
16use spec::Smbios30EntryPoint;
17use spec::SmbiosType0;
18use spec::SmbiosType1;
19use spec::SmbiosType127;
20use zerocopy::IntoBytes;
21use zerocopy::LE;
22use zerocopy::U16;
23
24#[derive(Debug, Copy, Clone)]
26pub struct SmbiosBiosInfo<'a> {
27 pub vendor: &'a str,
29 pub version: &'a str,
31 pub release_date: &'a str,
33 pub major: u8,
35 pub minor: u8,
37}
38
39#[derive(Debug, Copy, Clone)]
41pub struct SmbiosSystemInfo<'a> {
42 pub manufacturer: &'a str,
44 pub product_name: &'a str,
46 pub version: &'a str,
48 pub serial_number: &'a str,
50 pub sku_number: &'a str,
52 pub family: &'a str,
54 pub uuid: [u8; 16],
57}
58
59#[derive(Debug, Copy, Clone)]
62pub struct SmbiosTables<'a> {
63 pub bios: SmbiosBiosInfo<'a>,
65 pub system: SmbiosSystemInfo<'a>,
67}
68
69pub const ENTRY_POINT_SIZE: usize = size_of::<Smbios30EntryPoint>();
74
75#[derive(Debug, Clone)]
77pub struct BuiltSmbios {
78 pub entry_point: Vec<u8>,
80 pub structure_table: Vec<u8>,
82}
83
84#[derive(Default)]
87struct StringSet {
88 strings: Vec<String>,
89}
90
91impl StringSet {
92 fn add(&mut self, s: &str) -> u8 {
101 let s = s.split('\0').next().unwrap_or("");
102 if s.is_empty() {
103 return 0;
104 }
105 self.strings.push(s.to_string());
106 self.strings.len().try_into().unwrap()
107 }
108
109 fn write_to(&self, out: &mut Vec<u8>) {
113 if self.strings.is_empty() {
114 out.extend_from_slice(&[0, 0]);
115 return;
116 }
117 for s in &self.strings {
118 out.extend_from_slice(s.as_bytes());
119 out.push(0);
120 }
121 out.push(0);
122 }
123}
124
125pub fn build(tables: &SmbiosTables<'_>, table_gpa: u64) -> BuiltSmbios {
131 let mut structure_table = Vec::new();
132
133 let mut next_handle = 0u16;
136 let mut handle = || {
137 let h = next_handle;
138 next_handle += 1;
139 U16::<LE>::new(h)
140 };
141
142 {
144 let mut strings = StringSet::default();
145 let vendor = strings.add(tables.bios.vendor);
146 let bios_version = strings.add(tables.bios.version);
147 let bios_release_date = strings.add(tables.bios.release_date);
148 let t0 = SmbiosType0 {
149 typ: 0,
150 length: size_of::<SmbiosType0>() as u8,
151 handle: handle(),
152 vendor,
153 bios_version,
154 bios_segment: 0.into(),
155 bios_release_date,
156 bios_size: 0,
157 characteristics: spec::BIOS_CHARACTERISTICS_PCI_SUPPORTED.into(),
158 characteristics_ext: [
159 spec::BIOS_CHARACTERISTICS_EXT1_ACPI,
160 spec::BIOS_CHARACTERISTICS_EXT2_VM,
161 ],
162 bios_major: tables.bios.major,
163 bios_minor: tables.bios.minor,
164 ec_major: 0xff,
165 ec_minor: 0xff,
166 ext_rom_size: 0.into(),
167 };
168 structure_table.extend_from_slice(t0.as_bytes());
169 strings.write_to(&mut structure_table);
170 }
171
172 {
174 let mut strings = StringSet::default();
175 let manufacturer = strings.add(tables.system.manufacturer);
176 let product_name = strings.add(tables.system.product_name);
177 let version = strings.add(tables.system.version);
178 let serial_number = strings.add(tables.system.serial_number);
179 let sku_number = strings.add(tables.system.sku_number);
180 let family = strings.add(tables.system.family);
181 let t1 = SmbiosType1 {
182 typ: 1,
183 length: size_of::<SmbiosType1>() as u8,
184 handle: handle(),
185 manufacturer,
186 product_name,
187 version,
188 serial_number,
189 uuid: tables.system.uuid,
190 wake_up_type: spec::WAKE_UP_TYPE_POWER_SWITCH,
191 sku_number,
192 family,
193 };
194 structure_table.extend_from_slice(t1.as_bytes());
195 strings.write_to(&mut structure_table);
196 }
197
198 {
200 let t127 = SmbiosType127 {
201 typ: 127,
202 length: size_of::<SmbiosType127>() as u8,
203 handle: handle(),
204 };
205 structure_table.extend_from_slice(t127.as_bytes());
206 structure_table.extend_from_slice(&[0, 0]);
208 }
209
210 let mut entry_point = Smbios30EntryPoint {
211 anchor: *b"_SM3_",
212 checksum: 0,
213 length: size_of::<Smbios30EntryPoint>() as u8,
214 major: 3,
215 minor: 1,
216 docrev: 0,
217 revision: 0x01,
218 reserved: 0,
219 max_size: u32::try_from(structure_table.len()).unwrap().into(),
220 table_addr: table_gpa.into(),
221 };
222 let sum = entry_point
223 .as_bytes()
224 .iter()
225 .fold(0u8, |acc, b| acc.wrapping_add(*b));
226 entry_point.checksum = 0u8.wrapping_sub(sum);
227
228 BuiltSmbios {
229 entry_point: entry_point.as_bytes().to_vec(),
230 structure_table,
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn test_smbios_tables() -> SmbiosTables<'static> {
239 SmbiosTables {
240 system: SmbiosSystemInfo {
241 manufacturer: "Test Manufacturer",
242 product_name: "Test Product",
243 version: "Test Version",
244 serial_number: "",
245 sku_number: "Test SKU",
246 family: "Test Family",
247 uuid: [0; 16],
248 },
249 bios: SmbiosBiosInfo {
250 vendor: "Test BIOS Vendor",
251 version: "Test BIOS Version",
252 release_date: "Test BIOS Release Date",
253 major: 1,
254 minor: 2,
255 },
256 }
257 }
258
259 #[test]
260 fn entry_point_checksum_is_zero() {
261 let built = build(&test_smbios_tables(), 0xf0020);
262 let sum = built
263 .entry_point
264 .iter()
265 .fold(0u8, |acc, b| acc.wrapping_add(*b));
266 assert_eq!(sum, 0);
267 }
268
269 #[test]
270 fn entry_point_fields() {
271 let table_gpa = 0xf0020;
272 let built = build(&test_smbios_tables(), table_gpa);
273 assert_eq!(built.entry_point.len(), 0x18);
274 assert_eq!(&built.entry_point[0..5], b"_SM3_");
275 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());
281 assert_eq!(max_size as usize, built.structure_table.len());
282
283 let addr = u64::from_le_bytes(built.entry_point[0x10..0x18].try_into().unwrap());
285 assert_eq!(addr, table_gpa);
286 }
287
288 #[test]
289 fn structure_table_layout() {
290 let built = build(&test_smbios_tables(), 0xf0020);
291 let table = &built.structure_table;
292
293 assert_eq!(table[0], 0); assert_eq!(table[1], 0x1a); assert_eq!(table[4], 1); assert_eq!(table[5], 2); let n = table.len();
305 assert_eq!(&table[n - 6..], &[127, 4, 0x02, 0x00, 0, 0]);
306 }
307
308 #[test]
309 fn strings_resolve() {
310 let built = build(&test_smbios_tables(), 0);
311 let strings_start = 0x1a;
314 let nul = built.structure_table[strings_start..]
315 .iter()
316 .position(|&b| b == 0)
317 .unwrap();
318 let vendor = &built.structure_table[strings_start..strings_start + nul];
319 assert_eq!(vendor, "Test BIOS Vendor".as_bytes());
320 }
321
322 #[test]
323 fn empty_string_uses_index_zero() {
324 let tables = test_smbios_tables();
325 let built = build(&tables, 0);
326 let t1_off = struct_offset(&built.structure_table, 1).expect("Type 1 present");
327 assert_eq!(built.structure_table[t1_off + 7], 0);
331 }
332
333 #[test]
334 fn interior_nul_is_truncated() {
335 let mut tables = test_smbios_tables();
336 tables.system.manufacturer = "Mfg\0evil";
340 let built = build(&tables, 0);
341 let t1_off = struct_offset(&built.structure_table, 1).expect("Type 1 present");
342 assert_eq!(built.structure_table[t1_off + 4], 1);
344 assert_eq!(built.structure_table[t1_off + 5], 2);
345 let len = built.structure_table[t1_off + 1] as usize;
348 let strings = &built.structure_table[t1_off + len..];
349 let first_nul = strings.iter().position(|&b| b == 0).unwrap();
350 assert_eq!(&strings[..first_nul], b"Mfg");
351 assert!(!strings.windows(4).any(|w| w == b"evil"));
352 }
353
354 fn struct_offset(table: &[u8], want: u8) -> Option<usize> {
357 let mut off = 0;
358 while off + 2 <= table.len() {
359 let typ = table[off];
360 let formatted_len = table[off + 1] as usize;
361 if typ == want {
362 return Some(off);
363 }
364 let mut i = off + formatted_len;
367 while i + 1 < table.len() && !(table[i] == 0 && table[i + 1] == 0) {
368 i += 1;
369 }
370 off = i + 2;
371 }
372 None
373 }
374}