blob: 719ad9449df77b197e9c4d2bbfbb6308eb87038a [file] [edit]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! SMBIOS support for PC platform.
#[cfg(console_enabled)]
use crate::console::{CMD_AVAIL_ALWAYS, CmdArgs, static_command};
use crate::vm::arch_vm_aspace::{ARCH_MMU_FLAG_CACHED, ARCH_MMU_FLAG_PERM_READ};
use crate::vm::vm_aspace::VmAspace;
use core::ptr::NonNull;
use handoff::PhysHandoff;
use lazy_init::LazyInit;
use smbios::{
BiosInformationStruct2_0, BiosInformationStruct2_4, EntryPoint, EntryPoint2_1,
EntryPointVersion, Header, SMBIOS2_ANCHOR, SMBIOS3_ANCHOR, SpecVersion, StringTable,
StructType, SystemInformationStruct2_0, SystemInformationStruct2_1, SystemInformationStruct2_4,
};
use zerocopy::FromBytes;
use zx_status::Status;
use zx_types::zx_status_t;
unsafe extern "C" {
fn printf(format: *const core::ffi::c_char, ...) -> core::ffi::c_int;
}
/// Callback type for walking SMBIOS structures across the FFI boundary.
pub type RustSmbiosWalkCallback = unsafe extern "C" fn(
major: u8,
minor: u8,
docrev: u8,
hdr: *const Header,
str_table_start: *const core::ffi::c_char,
str_table_len: usize,
ctx: *mut core::ffi::c_void,
) -> zx_status_t;
/// Holds the global SMBIOS state.
#[derive(Debug, Default)]
struct SmbiosState {
version: Option<EntryPointVersion>,
ep2_1: Option<EntryPoint2_1>,
struct_base: usize,
struct_len: usize,
}
static SMBIOS_STATE: LazyInit<SmbiosState> = LazyInit::uninit();
/// Maximum entry point size to map when searching for the anchor.
const MAX_EP_SIZE: usize = 31;
/// Maps a physical memory range into the kernel address space.
///
/// Returns a tuple of `(virt_ptr, mapping_base_addr)`.
fn map_range(paddr: u64, len: usize) -> Result<(NonNull<u8>, usize), Status> {
if paddr == 0 || len == 0 {
return Err(Status::INVALID_ARGS);
}
let paddr_usize = usize::try_from(paddr).map_err(|_| Status::INVALID_ARGS)?;
let paddr_end = paddr_usize.checked_add(len).ok_or(Status::INVALID_ARGS)?;
let paddr_base = page::round_down(paddr_usize);
let paddr_top = page::round_up(paddr_end);
let map_size = paddr_top.checked_sub(paddr_base).ok_or(Status::INVALID_ARGS)?;
let mut base_ptr: *mut core::ffi::c_void = core::ptr::null_mut();
// SAFETY: `alloc_physical` maps the specified physical address range into the kernel aspace.
let status = unsafe {
VmAspace::kernel_aspace().alloc_physical(
b"smbios",
map_size,
&mut base_ptr,
page::SHIFT as u8,
crate::kernel::types::PAddr(paddr_base),
0,
ARCH_MMU_FLAG_CACHED | ARCH_MMU_FLAG_PERM_READ,
)
};
status?;
let offset = paddr_usize - paddr_base;
let virt_addr = (base_ptr as usize) + offset;
let virt_ptr = core::ptr::with_exposed_provenance_mut::<u8>(virt_addr);
let non_null = NonNull::new(virt_ptr).ok_or(Status::NO_MEMORY)?;
Ok((non_null, base_ptr as usize))
}
/// Searches for and maps the SMBIOS entry point table, returning the mapped pointer and version.
fn find_entry_point() -> Result<(NonNull<u8>, EntryPointVersion), Status> {
let smbios_phys = Option::from(PhysHandoff::get().smbios_phys).ok_or(Status::NOT_FOUND)?;
let (ep_virt_ptr, ep_mapping_base) = map_range(smbios_phys, MAX_EP_SIZE)?;
// SAFETY: `ep_virt_ptr` points to at least `MAX_EP_SIZE` (31) valid mapped bytes.
let ep_bytes = unsafe { core::slice::from_raw_parts(ep_virt_ptr.as_ptr(), MAX_EP_SIZE) };
if ep_bytes.starts_with(SMBIOS2_ANCHOR) {
Ok((ep_virt_ptr, EntryPointVersion::V2_1))
} else if ep_bytes.starts_with(SMBIOS3_ANCHOR) {
Ok((ep_virt_ptr, EntryPointVersion::V3_0))
} else {
// SAFETY: Anchor mismatch; free the allocated entry point mapping before returning error.
unsafe {
let _ = VmAspace::kernel_aspace().free_region(ep_mapping_base);
}
Err(Status::NOT_FOUND)
}
}
/// Initializes the SMBIOS subsystem.
#[unsafe(no_mangle)]
pub extern "C" fn pc_init_smbios() {
// SAFETY: Initialization is serialized with respect to any other access.
unsafe {
SMBIOS_STATE.init(probe_smbios());
}
}
fn probe_smbios() -> SmbiosState {
let mut state = SmbiosState::default();
let (ep_ptr, version) = match find_entry_point() {
Ok(res) => res,
Err(_) => {
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(c"smbios: Failed to locate entry point\n".as_ptr());
}
return state;
}
};
match version {
EntryPointVersion::V2_1 => {
// SAFETY: `ep_ptr` points to at least `MAX_EP_SIZE` (31) valid mapped bytes.
let ep_bytes = unsafe { core::slice::from_raw_parts(ep_ptr.as_ptr(), MAX_EP_SIZE) };
let Ok((ep, _)) = EntryPoint2_1::ref_from_prefix(ep_bytes) else {
// SAFETY: Free the entry point mapping on parse error.
unsafe {
let _ = VmAspace::kernel_aspace()
.free_region(page::round_down(ep_ptr.as_ptr() as usize));
}
return state;
};
if !ep.is_valid() {
// SAFETY: Free the entry point mapping on invalid entry point.
unsafe {
let _ = VmAspace::kernel_aspace()
.free_region(page::round_down(ep_ptr.as_ptr() as usize));
}
return state;
}
let ep_copy = *ep;
let struct_table_phys = ep.struct_table_phys.get() as u64;
let struct_table_length = ep.struct_table_length.get() as usize;
// SAFETY: Free the entry point mapping now that ep has been copied.
unsafe {
let _ = VmAspace::kernel_aspace()
.free_region(page::round_down(ep_ptr.as_ptr() as usize));
}
match map_range(struct_table_phys, struct_table_length) {
Ok((struct_virt_ptr, _struct_mapping_base)) => {
state.version = Some(EntryPointVersion::V2_1);
state.ep2_1 = Some(ep_copy);
state.struct_base = struct_virt_ptr.as_ptr() as usize;
state.struct_len = struct_table_length;
}
Err(status) => {
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(c"smbios: failed to map structs: %d\n".as_ptr(), status.into_raw());
}
}
}
}
EntryPointVersion::V3_0 => {
// SAFETY: Free the entry point mapping and print version 3 not yet implemented message.
unsafe {
let _ = VmAspace::kernel_aspace()
.free_region(page::round_down(ep_ptr.as_ptr() as usize));
printf(c"smbios: version 3 not yet implemented\n".as_ptr());
}
}
_ => {
// SAFETY: Free the entry point mapping on unknown version.
unsafe {
let _ = VmAspace::kernel_aspace()
.free_region(page::round_down(ep_ptr.as_ptr() as usize));
}
}
}
state
}
/// Walks the known SMBIOS structures. `cb` will be called once for each structure found.
pub fn walk_structs<F>(cb: F) -> Result<(), Status>
where
F: FnMut(SpecVersion, &Header, &StringTable<'_>) -> Result<(), Status>,
{
let state = SMBIOS_STATE.get();
match state.version {
Some(EntryPointVersion::V2_1) => {
let Some(ep2_1) = state.ep2_1.as_ref() else {
return Err(Status::NOT_SUPPORTED);
};
let entry = EntryPoint::from(ep2_1);
// SAFETY: `struct_base` points to mapped structure table of `struct_len` bytes.
unsafe { entry.walk_structs_raw(state.struct_base, cb) }
}
Some(EntryPointVersion::V3_0) | Some(EntryPointVersion::Unknown) | None => {
Err(Status::NOT_SUPPORTED)
}
}
}
/// Walks the SMBIOS structures across the FFI boundary.
///
/// # Safety
///
/// `cb` must be a valid function pointer with the expected C ABI.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_smbios_walk_structs(
cb: RustSmbiosWalkCallback,
ctx: *mut core::ffi::c_void,
) -> zx_status_t {
let res = walk_structs(|ver, hdr, st| {
let (str_table_start, str_table_len) = if st.length() > 0 {
// String table in memory starts immediately after the formatted struct header:
// SAFETY: `hdr` points to the structure within mapped struct table; string table follows `hdr + hdr.length`.
let ptr = unsafe {
(hdr as *const Header as *const core::ffi::c_char).add(hdr.length as usize)
};
(ptr, st.length())
} else {
(core::ptr::null(), 0)
};
// SAFETY: `cb` is called with valid structure and string table pointers.
let status_raw = unsafe {
cb(
ver.major_ver,
ver.minor_ver,
ver.docrev_ver,
hdr as *const Header,
str_table_start,
str_table_len,
ctx,
)
};
Status::ok(status_raw)
});
Status::result_into_raw(res)
}
/// Callback for debug dumping SMBIOS structures.
fn debug_struct_walk(ver: SpecVersion, hdr: &Header, st: &StringTable<'_>) -> Result<(), Status> {
// SAFETY: `hdr` points to a structure within the valid mapped structure table.
let raw_bytes = unsafe {
core::slice::from_raw_parts(hdr as *const Header as *const u8, hdr.length as usize)
};
let mut writer = debug::dprintf::KernelConsoleWriter;
match hdr.r#type {
StructType::BIOS_INFO => {
if ver.includes_version(2, 4, 0)
&& let Ok((entry, _)) = BiosInformationStruct2_4::ref_from_prefix(raw_bytes)
{
let _ = entry.dump(&mut writer, st);
return Ok(());
} else if ver.includes_version(2, 0, 0)
&& let Ok((entry, _)) = BiosInformationStruct2_0::ref_from_prefix(raw_bytes)
{
let _ = entry.dump(&mut writer, st, raw_bytes);
return Ok(());
}
}
StructType::SYSTEM_INFO => {
if ver.includes_version(2, 4, 0)
&& let Ok((entry, _)) = SystemInformationStruct2_4::ref_from_prefix(raw_bytes)
{
let _ = entry.dump(&mut writer, st);
return Ok(());
} else if ver.includes_version(2, 1, 0)
&& let Ok((entry, _)) = SystemInformationStruct2_1::ref_from_prefix(raw_bytes)
{
let _ = entry.dump(&mut writer, st);
return Ok(());
} else if ver.includes_version(2, 0, 0)
&& let Ok((entry, _)) = SystemInformationStruct2_0::ref_from_prefix(raw_bytes)
{
let _ = entry.dump(&mut writer, st);
return Ok(());
}
}
_ => {}
}
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(
c"smbios: found struct@%p: typ=%u len=%u st_len=%zu\n".as_ptr(),
hdr as *const Header,
hdr.r#type.0 as core::ffi::c_uint,
hdr.length as core::ffi::c_uint,
st.length(),
);
}
let _ = st.dump(&mut writer);
Ok(())
}
#[cfg(console_enabled)]
unsafe extern "C" fn cmd_smbios(argc: i32, argv: *const CmdArgs, _flags: u32) -> i32 {
// SAFETY: The console framework guarantees `argv` is valid and contains `argc` elements.
let args = unsafe { core::slice::from_raw_parts(argv, argc as usize) };
let usage = || -> i32 {
// SAFETY: `printf` format strings are valid null-terminated C strings.
unsafe {
printf(c"usage:\n".as_ptr());
printf(c"%s dump\n".as_ptr(), args[0].arg_str);
}
Status::INTERNAL.into_raw()
};
if argc < 2 {
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(c"not enough arguments\n".as_ptr());
}
return usage();
}
// SAFETY: The console framework guarantees `args[1].arg_str` is a valid null-terminated C string.
let cmd = unsafe { core::ffi::CStr::from_ptr(args[1].arg_str) };
if cmd.to_bytes() == b"dump" {
if let Err(status) = walk_structs(debug_struct_walk) {
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(c"smbios: failed to walk structs: %d\n".as_ptr(), status.into_raw());
}
}
zx_status::sys::ZX_OK
} else {
// SAFETY: `printf` format string is a valid null-terminated C string.
unsafe {
printf(c"unknown command\n".as_ptr());
}
usage()
}
}
#[cfg(console_enabled)]
static_command!(CMD_SMBIOS, c"smbios".as_ptr(), c"smbios".as_ptr(), cmd_smbios, CMD_AVAIL_ALWAYS);