1use crate::string_table::StringTable;
6use crate::structures::{Header, StructType};
7use zerocopy::byteorder::little_endian::{U16, U32, U64};
8use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
9use zx_status::Status;
10
11pub const SMBIOS2_ANCHOR: &[u8; 4] = b"_SM_";
12pub const SMBIOS2_INTERMEDIATE_ANCHOR: &[u8; 5] = b"_DMI_";
13pub const SMBIOS3_ANCHOR: &[u8; 5] = b"_SM3_";
14
15pub fn compute_checksum(data: &[u8]) -> u8 {
17 data.iter().fold(0u8, |sum, &b| sum.wrapping_add(b))
18}
19
20#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
22pub struct SpecVersion {
23 pub major_ver: u8,
24 pub minor_ver: u8,
25 pub docrev_ver: u8,
26}
27
28impl SpecVersion {
29 pub const fn new(major: u8, minor: u8, docrev: u8) -> Self {
31 Self { major_ver: major, minor_ver: minor, docrev_ver: docrev }
32 }
33
34 pub const fn new_v2(major: u8, minor: u8) -> Self {
36 Self { major_ver: major, minor_ver: minor, docrev_ver: 0 }
37 }
38
39 pub const fn includes_version(
41 &self,
42 spec_major_ver: u8,
43 spec_minor_ver: u8,
44 spec_docrev_ver: u8,
45 ) -> bool {
46 if self.major_ver > spec_major_ver {
47 return true;
48 }
49 if self.major_ver < spec_major_ver {
50 return false;
51 }
52 if self.minor_ver > spec_minor_ver {
53 return true;
54 }
55 if self.minor_ver < spec_minor_ver {
56 return false;
57 }
58 self.docrev_ver >= spec_docrev_ver
59 }
60}
61
62#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
64pub enum EntryPointVersion {
65 Unknown,
66 V2_1,
67 V3_0,
68}
69
70#[repr(C, packed)]
72#[derive(
73 Copy,
74 Clone,
75 Debug,
76 Default,
77 Eq,
78 PartialEq,
79 FromBytes,
80 IntoBytes,
81 Immutable,
82 KnownLayout,
83 Unaligned,
84)]
85pub struct EntryPoint2_1 {
86 pub anchor_string: [u8; 4], pub checksum: u8,
88 pub length: u8,
89
90 pub major_ver: u8,
92 pub minor_ver: u8,
93
94 pub max_struct_size: U16,
95
96 pub ep_rev: u8, pub formatted_area: [u8; 5], pub intermediate_anchor_string: [u8; 5], pub intermediate_checksum: u8,
101
102 pub struct_table_length: U16,
103 pub struct_table_phys: U32,
104 pub struct_count: U16,
105
106 pub bcd_rev: u8, }
108
109zr::static_assert!(core::mem::size_of::<EntryPoint2_1>() == 0x1f);
110zr::static_assert!(core::mem::align_of::<EntryPoint2_1>() == 1);
111
112impl EntryPoint2_1 {
113 pub fn is_valid(&self) -> bool {
115 if &self.anchor_string != SMBIOS2_ANCHOR {
116 return false;
117 }
118
119 let real_length = if self.length == 0x1f {
120 0x1f
121 } else if self.length == 0x1e {
122 0x1f
124 } else {
125 return false;
126 };
127
128 let bytes = self.as_bytes();
129 if compute_checksum(&bytes[..real_length as usize]) != 0 {
130 return false;
131 }
132
133 if self.ep_rev != 0 {
134 return false;
135 }
136
137 if &self.intermediate_anchor_string != SMBIOS2_INTERMEDIATE_ANCHOR {
138 return false;
139 }
140
141 const INTERMEDIATE_OFFSET: usize = 0x10;
142 if compute_checksum(&bytes[INTERMEDIATE_OFFSET..real_length as usize]) != 0 {
143 return false;
144 }
145
146 let phys = self.struct_table_phys.get();
147 let len = self.struct_table_length.get() as u32;
148 !phys.checked_add(len).is_none()
149 }
150
151 pub fn version(&self) -> SpecVersion {
153 SpecVersion::new(self.major_ver, self.minor_ver, 0)
154 }
155
156 pub fn dump<W: core::fmt::Write>(&self, writer: &mut W) -> core::fmt::Result {
158 writeln!(writer, "SMBIOS EntryPoint v2.1:")?;
159 writeln!(writer, " specification version: {}.{}", self.major_ver, self.minor_ver)?;
160 writeln!(writer, " max struct size: {}", self.max_struct_size.get())?;
161 writeln!(
162 writer,
163 " struct table: {} bytes @0x{:08x}, {} entries",
164 self.struct_table_length.get(),
165 self.struct_table_phys.get(),
166 self.struct_count.get()
167 )?;
168 Ok(())
169 }
170}
171
172#[repr(C, packed)]
174#[derive(
175 Copy,
176 Clone,
177 Debug,
178 Default,
179 Eq,
180 PartialEq,
181 FromBytes,
182 IntoBytes,
183 Immutable,
184 KnownLayout,
185 Unaligned,
186)]
187pub struct EntryPoint3_0 {
188 pub anchor_string: [u8; 5], pub checksum: u8,
190 pub length: u8,
191
192 pub major_ver: u8,
194 pub minor_ver: u8,
195 pub docrev_ver: u8,
196
197 pub ep_rev: u8, pub reserved: u8,
199
200 pub max_struct_size: U32,
201 pub struct_table_phys: U64,
202}
203
204zr::static_assert!(core::mem::size_of::<EntryPoint3_0>() == 0x18);
205zr::static_assert!(core::mem::align_of::<EntryPoint3_0>() == 1);
206
207impl EntryPoint3_0 {
208 pub fn is_valid(&self) -> bool {
210 if &self.anchor_string != SMBIOS3_ANCHOR {
211 return false;
212 }
213
214 if self.length as usize != core::mem::size_of::<Self>() {
215 return false;
216 }
217
218 if compute_checksum(self.as_bytes()) != 0 {
219 return false;
220 }
221
222 true
223 }
224
225 pub fn version(&self) -> SpecVersion {
227 SpecVersion::new(self.major_ver, self.minor_ver, self.docrev_ver)
228 }
229}
230
231#[derive(Copy, Clone, Debug, PartialEq)]
233pub enum EntryPointType<'a> {
234 V2_1(&'a EntryPoint2_1),
235 V3_0(&'a EntryPoint3_0),
236}
237
238#[derive(Copy, Clone, Debug, PartialEq)]
240pub struct EntryPoint<'a> {
241 inner: EntryPointType<'a>,
242}
243
244impl<'a> From<&'a EntryPoint2_1> for EntryPoint<'a> {
245 fn from(ep: &'a EntryPoint2_1) -> Self {
246 Self { inner: EntryPointType::V2_1(ep) }
247 }
248}
249
250impl<'a> From<&'a EntryPoint3_0> for EntryPoint<'a> {
251 fn from(ep: &'a EntryPoint3_0) -> Self {
252 Self { inner: EntryPointType::V3_0(ep) }
253 }
254}
255
256impl<'a> EntryPoint<'a> {
257 pub fn new(inner: EntryPointType<'a>) -> Self {
259 Self { inner }
260 }
261
262 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Status> {
264 if let Ok((v2, _)) = EntryPoint2_1::ref_from_prefix(bytes) {
265 if v2.is_valid() {
266 return Ok(Self { inner: EntryPointType::V2_1(v2) });
267 }
268 }
269 if let Ok((v3, _)) = EntryPoint3_0::ref_from_prefix(bytes) {
270 if v3.is_valid() {
271 return Ok(Self { inner: EntryPointType::V3_0(v3) });
272 }
273 }
274 Err(Status::IO_DATA_INTEGRITY)
275 }
276
277 pub unsafe fn create(ep_start: usize) -> Result<EntryPoint<'static>, Status> {
284 if ep_start == 0 {
285 return Err(Status::INVALID_ARGS);
286 }
287
288 let v2_ptr = core::ptr::with_exposed_provenance::<EntryPoint2_1>(ep_start);
290 let v2_ref = unsafe { &*v2_ptr };
291 if v2_ref.is_valid() {
292 return Ok(EntryPoint { inner: EntryPointType::V2_1(v2_ref) });
293 }
294
295 let v3_ptr = core::ptr::with_exposed_provenance::<EntryPoint3_0>(ep_start);
297 let v3_ref = unsafe { &*v3_ptr };
298 if v3_ref.is_valid() {
299 return Ok(EntryPoint { inner: EntryPointType::V3_0(v3_ref) });
300 }
301
302 Err(Status::IO_DATA_INTEGRITY)
303 }
304
305 pub fn struct_table_phys(&self) -> u64 {
307 match self.inner {
308 EntryPointType::V2_1(ep) => ep.struct_table_phys.get() as u64,
309 EntryPointType::V3_0(ep) => ep.struct_table_phys.get(),
310 }
311 }
312
313 pub fn struct_table_length(&self) -> u32 {
318 match self.inner {
319 EntryPointType::V2_1(ep) => ep.struct_table_length.get() as u32,
320 EntryPointType::V3_0(ep) => ep.max_struct_size.get(),
321 }
322 }
323
324 pub fn max_struct_size(&self) -> u32 {
326 match self.inner {
327 EntryPointType::V2_1(ep) => ep.max_struct_size.get() as u32,
328 EntryPointType::V3_0(ep) => ep.max_struct_size.get(),
329 }
330 }
331
332 pub fn version(&self) -> SpecVersion {
334 match self.inner {
335 EntryPointType::V2_1(ep) => ep.version(),
336 EntryPointType::V3_0(ep) => ep.version(),
337 }
338 }
339
340 pub fn has_struct_count(&self) -> bool {
342 matches!(self.inner, EntryPointType::V2_1(_))
343 }
344
345 pub fn struct_count(&self) -> u16 {
351 match self.inner {
352 EntryPointType::V2_1(ep) => ep.struct_count.get(),
353 EntryPointType::V3_0(_) => panic!("struct_count() called on SMBIOS 3.0 entry point"),
354 }
355 }
356
357 pub fn walk_structs<F>(&self, struct_table: &[u8], mut cb: F) -> Result<(), Status>
365 where
366 F: FnMut(SpecVersion, &Header, &StringTable<'_>) -> Result<(), Status>,
367 {
368 let mut idx = 0usize;
369 let mut curr = 0usize;
370 let table_len = core::cmp::min(self.struct_table_length() as usize, struct_table.len());
371
372 while curr + core::mem::size_of::<Header>() < table_len {
373 let (hdr, _) = Header::ref_from_prefix(&struct_table[curr..])
374 .map_err(|_| Status::IO_DATA_INTEGRITY)?;
375
376 let hdr_len = hdr.length as usize;
377 if hdr_len < core::mem::size_of::<Header>() || curr + hdr_len > table_len {
378 return Err(Status::IO_DATA_INTEGRITY);
379 }
380
381 if hdr.r#type == StructType::END_OF_TABLE {
382 return Ok(());
383 }
384
385 let max_struct_len = core::cmp::max(table_len - curr, self.max_struct_size() as usize);
386
387 let st = StringTable::init(hdr, max_struct_len, &struct_table[curr..])?;
388
389 let status = cb(self.version(), hdr, &st);
390 match status {
391 Ok(()) => {}
392 Err(Status::STOP) => return Ok(()),
393 Err(Status::NEXT) => {}
394 Err(err) => return Err(err),
395 }
396
397 idx += 1;
398 if self.has_struct_count() && self.struct_count() as usize == idx {
399 return Ok(());
400 }
401
402 curr += hdr_len + st.length();
404 }
405
406 Err(Status::IO_DATA_INTEGRITY)
407 }
408
409 pub unsafe fn walk_structs_raw<F>(&self, struct_table_virt: usize, cb: F) -> Result<(), Status>
416 where
417 F: FnMut(SpecVersion, &Header, &StringTable<'_>) -> Result<(), Status>,
418 {
419 let len = self.struct_table_length() as usize;
420 if struct_table_virt == 0 || len == 0 {
421 return Err(Status::INVALID_ARGS);
422 }
423
424 let slice = unsafe {
426 let ptr = core::ptr::with_exposed_provenance::<u8>(struct_table_virt);
427 core::slice::from_raw_parts(ptr, len)
428 };
429 self.walk_structs(slice, cb)
430 }
431}