diff --git a/README.md b/README.md index 1f61df2..5cd09a4 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,8 @@ But this tool is an unified opensource approach to manipulate UEFI settings on a ### For OCP hardware -* Extract HiiDB from `/dev/mem` after getting offsets from `efivarfs`. +* Extract HiiDB. Linux exposes no file that holds it, and `/sys/firmware/efi/systab` does not list its address, so the OCP way is to read the physical address and length from the `HiiDB-1b838190-4625-4ead-abc9-cd5e6af18fe0` efivar and then read that memory from `/dev/mem`. +* If the firmware publishes a copy of the HiiDB as the vendor ACPI table `HIID`, it is read from `/sys/firmware/acpi/tables/HIID` instead (skip the 36-byte ACPI header), without `/dev/mem`. This is the way where `/dev/mem` cannot reach the database (e.g. aarch64, whose EFI runtime memory is `nomap`) or the database is too large for one efivarfs read. When the table is missing or unreadable, the reason goes to stderr and the OCP way is used. * Library (`hii`) in Rust partially implements the UEFI Hii specification. * It parses the HiiDB op-codes into a DOM tree like representation which can be read by machines and humans. * It can ask questions about UEFI settings from HiiDB and get their answers. @@ -213,7 +214,7 @@ uefisettings hii dump-efivars -f hiidb.bin efivars uefisettings hii list-questions --efivars efivars ``` -`extract-db` reads the HiiDB from /dev/mem, which needs CAP_SYS_RAWIO. `dump-efivars -f` reads +`extract-db` reads the HiiDB from the `HIID` ACPI table or, failing that, from /dev/mem; either needs root (the table is root-only, /dev/mem needs CAP_SYS_RAWIO). `dump-efivars -f` reads only efivarfs, whose files every user can read, so run it unprivileged: as root, it would leave a directory that only root can read. diff --git a/src/lib/hii/extract.rs b/src/lib/hii/extract.rs index 4db7045..4ffba40 100644 --- a/src/lib/hii/extract.rs +++ b/src/lib/hii/extract.rs @@ -32,6 +32,18 @@ use log::debug; pub const OCP_HIIDB_PATH: &str = "/sys/firmware/efi/efivars/HiiDB-1b838190-4625-4ead-abc9-cd5e6af18fe0"; +// Vendor ACPI table ("HIID") published by the EDK2 PlatformHiiDatabase driver. +// Its body (after the 36-byte ACPI header) is a copy of the HII database +// itself, so it is read straight from sysfs with no /dev/mem - a workaround for +// hosts where /dev/mem cannot reach the database (e.g. aarch64, whose EFI +// runtime memory is nomap). Unlike a UEFI variable, an ACPI table has no +// efivarfs single-read size limit, so a multi-MB database reads fine. +pub const HIIDB_ACPI_PATH: &str = "/sys/firmware/acpi/tables/HIID"; + +// Size of the standard ACPI table header (EFI_ACPI_DESCRIPTION_HEADER) that +// prefixes the HII database bytes in the HIID table. +const ACPI_HEADER_LEN: usize = 36; + const DEV_MEM_PATH: &str = "/dev/mem"; /// Below this address (1 MiB), x86 read() of /dev/mem may return zeros. @@ -102,6 +114,19 @@ impl HiiDBEFIVar { } pub fn extract_db() -> Result> { + // Prefer the vendor ACPI table that carries the HII database content + // directly (read from sysfs, no /dev/mem); fall back to the OCP variable + + // /dev/mem when it is absent (e.g. genuine OCP hardware). The fallback + // reason is printed to stderr so a failure here is not masked by the OCP + // error below. + match extract_db_acpi() { + Ok(bytes) => return Ok(bytes), + Err(e) => eprintln!( + "[uefisettings] could not use the HII ACPI table ({HIIDB_ACPI_PATH}): {e:#}; \ + falling back to the OCP HiiDB variable + /dev/mem" + ), + } + // I haven't seen any documentation on extracting HiiDB anywhere on the internet // So this is directly based on what hiitool does. @@ -118,6 +143,27 @@ pub fn extract_db() -> Result> { read_physical_memory(db_info.address, db_info.length.try_into()?) } +/// Reads the HII database from the vendor ACPI table (HIIDB_ACPI_PATH) whose +/// body IS the database content: a 36-byte ACPI header followed by the +/// database bytes, so the header is dropped and the rest returned. +fn extract_db_acpi() -> Result> { + let mut acpi_file = + File::open(HIIDB_ACPI_PATH).context(format!("Failed to open {HIIDB_ACPI_PATH}"))?; + + let mut contents = Vec::new(); + acpi_file + .read_to_end(&mut contents) + .context(format!("Failed to read ACPI table file, {HIIDB_ACPI_PATH}"))?; + + if contents.len() <= ACPI_HEADER_LEN { + bail!( + "{HIIDB_ACPI_PATH} is too short ({} bytes) to contain a HII database", + contents.len() + ); + } + Ok(contents.split_off(ACPI_HEADER_LEN)) +} + /// The bytes of the HiiDB efivar `file`; one longer than HiiDBEFIVar64::SIZE, which /// HiiDBEFIVar::parse would refuse, is refused without being read whole. fn read_hiidb_efivar(file: impl Read) -> Result> {