Skip to main content

smbios/
entry_point.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
15/// Computes the 8-bit checksum of a byte slice.
16pub fn compute_checksum(data: &[u8]) -> u8 {
17    data.iter().fold(0u8, |sum, &b| sum.wrapping_add(b))
18}
19
20/// Utility for comparing SMBIOS specification versions.
21#[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    /// Creates a new `SpecVersion`.
30    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    /// Creates a new `SpecVersion` for SMBIOS 2.x (docrev defaults to 0).
35    pub const fn new_v2(major: u8, minor: u8) -> Self {
36        Self { major_ver: major, minor_ver: minor, docrev_ver: 0 }
37    }
38
39    /// Returns true if this version is at least the queried version.
40    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/// SMBIOS EntryPoint version.
63#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
64pub enum EntryPointVersion {
65    Unknown,
66    V2_1,
67    V3_0,
68}
69
70/// System structure identifying where SMBIOS v2.1 structs are in memory.
71#[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], // _SM_
87    pub checksum: u8,
88    pub length: u8,
89
90    // SMBIOS specification revision
91    pub major_ver: u8,
92    pub minor_ver: u8,
93
94    pub max_struct_size: U16,
95
96    pub ep_rev: u8,              // Should be 0x00 for version SMBIOS 2.1 entry point
97    pub formatted_area: [u8; 5], // Should be all 0x00 for ver 2.1
98
99    pub intermediate_anchor_string: [u8; 5], // _DMI_
100    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, // Should be 0x21
107}
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    /// Validates the entry point structure checksums and magic strings.
114    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            // 0x1e is allowed due to errata in the SMBIOS 2.1 spec. It really means 0x1f.
123            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    /// Returns the specification version supported by this entry point.
152    pub fn version(&self) -> SpecVersion {
153        SpecVersion::new(self.major_ver, self.minor_ver, 0)
154    }
155
156    /// Formats a dump of the entry point to the provided writer.
157    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/// System structure identifying where SMBIOS v3.0 structs are in memory.
173#[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], // _SM3_
189    pub checksum: u8,
190    pub length: u8,
191
192    // SMBIOS specification revision
193    pub major_ver: u8,
194    pub minor_ver: u8,
195    pub docrev_ver: u8,
196
197    pub ep_rev: u8, // Should be 0x01 for SMBIOS 3.0 entry point.
198    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    /// Validates the entry point structure checksum and magic string.
209    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    /// Returns the specification version supported by this entry point.
226    pub fn version(&self) -> SpecVersion {
227        SpecVersion::new(self.major_ver, self.minor_ver, self.docrev_ver)
228    }
229}
230
231/// Type representing the underlying entry point version reference.
232#[derive(Copy, Clone, Debug, PartialEq)]
233pub enum EntryPointType<'a> {
234    V2_1(&'a EntryPoint2_1),
235    V3_0(&'a EntryPoint3_0),
236}
237
238/// Unified abstraction over SMBIOS 2.1 and 3.0 entry points.
239#[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    /// Creates an `EntryPoint` wrapper around an already-validated entry point type.
258    pub fn new(inner: EntryPointType<'a>) -> Self {
259        Self { inner }
260    }
261
262    /// Attempts to parse and validate an `EntryPoint` from a byte slice.
263    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    /// Creates an `EntryPoint` from a raw memory address.
278    ///
279    /// # Safety
280    ///
281    /// `ep_start` must point to valid mapped memory containing at least `size_of::<EntryPoint2_1>()`
282    /// (0x1f) bytes or `size_of::<EntryPoint3_0>()` (0x18) bytes.
283    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        // SAFETY: Caller guarantees ep_start points to valid memory.
289        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        // SAFETY: Caller guarantees ep_start points to valid memory.
296        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    /// Returns the physical address of the structure table.
306    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    /// Returns the length of the structure table in bytes.
314    ///
315    /// Note that for SMBIOS 3.0, this returns `max_struct_size`, and the structures should be
316    /// checked for the End-of-Table type (`StructType::END_OF_TABLE`).
317    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    /// Returns the maximum structure size in bytes.
325    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    /// Returns the specification version supported by this entry point.
333    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    /// Returns true if the entry point specifies a structure count (SMBIOS 2.1).
341    pub fn has_struct_count(&self) -> bool {
342        matches!(self.inner, EntryPointType::V2_1(_))
343    }
344
345    /// Returns the number of structures (SMBIOS 2.1 only).
346    ///
347    /// # Panics
348    ///
349    /// Panics if called on an SMBIOS 3.0 entry point where structure count is not present.
350    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    /// Walks the known SMBIOS structures within the provided `struct_table` slice.
358    /// The callback will be called once for each structure found.
359    ///
360    /// Return values from the callback:
361    /// - `Err(Status::STOP)`: aborted and returns `Ok(())`
362    /// - `Ok(())` or `Err(Status::NEXT)`: walk continues to the next structure
363    /// - Any other error: walk is aborted and returns the error
364    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            // Skip over the formatted portion and embedded strings
403            curr += hdr_len + st.length();
404        }
405
406        Err(Status::IO_DATA_INTEGRITY)
407    }
408
409    /// Walks the known SMBIOS structures mapped at virtual address `struct_table_virt`.
410    ///
411    /// # Safety
412    ///
413    /// `struct_table_virt` must point to valid mapped memory containing at least
414    /// `struct_table_length()` bytes.
415    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        // SAFETY: Caller guarantees struct_table_virt is mapped with at least len bytes.
425        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}