Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5118,6 +5118,7 @@ dependencies = [
name = "opentmk"
version = "0.0.0"
dependencies = [
"acpi_spec",
"bitfield-struct 0.11.0",
"cfg-if",
"hvdef",
Expand Down
1 change: 1 addition & 0 deletions opentmk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition.workspace = true
rust-version.workspace = true

[dependencies]
acpi_spec.workspace = true
bitfield-struct.workspace = true
cfg-if.workspace = true
hvdef.workspace = true
Expand Down
10 changes: 8 additions & 2 deletions opentmk/src/platform/hyperv/arch/x86_64/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,14 @@ impl VirtualProcessorPlatformTrait<HvTestCtx> for HvTestCtx {

/// Return the number of logical processors present in the machine
fn get_vp_count(&self) -> TmkResult<u32> {
// TODO: use ACPI to get the actual count
Ok(4)
#[cfg(target_os = "uefi")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When are we not uefi?

{
crate::uefi::acpi_wrap::get_apic_count_from_madt().map(|r| r as u32)
}
#[cfg(not(target_os = "uefi"))]
{
Err(TmkError::NotImplemented)
}
}

/// Push a command onto the per-VP linked-list so it will be executed
Expand Down
3 changes: 3 additions & 0 deletions opentmk/src/tmkdefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use thiserror::Error;
/// Primary error type produced by TMK operations.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
pub enum TmkError {
/// Returned when an error occurs in ACPI parsing or handling.
#[error("Error occurred in ACPI handling")]
AcpiError,
/// Returned when a memory allocation attempt fails.
#[error("allocation failed")]
AllocationFailed,
Expand Down
142 changes: 142 additions & 0 deletions opentmk/src/uefi/acpi_wrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! ACPI table handling for UEFI environment.
use core::ffi::c_void;
use core::mem::size_of;
use core::mem::transmute;
use core::ptr::NonNull;

use acpi_spec::Header;
use acpi_spec::Rsdp;
use acpi_spec::madt::MadtEntry;
use acpi_spec::madt::MadtParser;
use alloc::vec::Vec;
use uefi::table::cfg::ACPI2_GUID;

use crate::tmkdefs::TmkError;
use crate::tmkdefs::TmkResult;

fn get_rsdp_ptr() -> TmkResult<NonNull<Rsdp>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this function return a &'static Rsdp instead? Is that safe to do? It'd be more useful to its callers that way at least.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same applies to other functions in here.

let system_table = uefi::table::system_table_raw();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be more worthwhile to move some of this code into acpi_spec as a SystemTableParser, RsdpParser, etc, even if they don't expose everything those tables have just yet?


if system_table.is_none() {
return Err(TmkError::AcpiError);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These errors should carry more detail so we can tell which of these conditions we've hit.

}

let mut system_table = system_table.unwrap();
Copy link
Contributor

@smalis-msft smalis-msft Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let Ok(system_table) = system_table else { return Err(TmkError::AcpiError) };


// SAFETY: system_table is valid as ensured by uefi::table::system_table_raw
let system_table_address = unsafe { system_table.as_mut() };

let config_count = system_table_address.number_of_configuration_table_entries;
let config_table_ptr = system_table_address.configuration_table;

// SAFETY: UEFI guarantees that the configuration table pointer is valid for the number of entries
let config_slice = unsafe { core::slice::from_raw_parts(config_table_ptr, config_count) };

let find = |guid| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just inline this if it's only used once?

config_slice
.iter()
.find(|entry| entry.vendor_guid == guid)
.map(|entry| entry.vendor_table)
};

if let Some(rsdp) = find(ACPI2_GUID) {
Ok(NonNull::new(rsdp as *mut Rsdp).ok_or(TmkError::AcpiError)?)
} else {
log::error!("ACPI2 RSDP not found");
Err(TmkError::AcpiError)
}
}

fn get_xsdt_ptr() -> TmkResult<NonNull<Header>> {
let rsdp_ptr = get_rsdp_ptr()?;

// SAFETY: rsdp_ptr is valid as ensured by get_rsdp_ptr
let rsdp = unsafe { rsdp_ptr.as_ref() };

let xsdt_address = rsdp.xsdt as usize;

Ok(NonNull::new(xsdt_address as *mut Header).ok_or(TmkError::AcpiError)?)
}

fn get_madt_ptr() -> TmkResult<NonNull<Header>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have some sort of AcpiContext that gets carried around and saved so that we don't have to repeat this parsing if we call get_apic_count_from_madt multiple times?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like we could provide platform contexts with an AcpiContext that finds all the tables ahead of time and holds on to an XsdtParser, MadtParser, RsdpParser, etc, and then we don't have to repeat any of the parsing ever.

// From XSDT get SDT Header
let sdt_ptr = get_xsdt_ptr()?;
let sdt_address = sdt_ptr.as_ptr() as usize;

// SAFETY: sdt_ptr is valid as ensured by get_xsdt_ptr
let sdt_length = unsafe { sdt_ptr.as_ref().length.get() };
let sdt_header_size = size_of::<Header>();

// get number of entries pointing to other SDTs
let entries_count = (sdt_length as usize - sdt_header_size) / size_of::<u64>();

// pointer to pointer table of other SDTs
let entries_ptr = sdt_address + sdt_header_size;

// SAFETY: madt size is valid as ensured by UEFI specification
let entries_ptr_bytes = unsafe {
core::slice::from_raw_parts(entries_ptr as *const u8, entries_count * size_of::<u64>())
};

// create slice of u64 pointers
let entries_slice = entries_ptr_bytes
.chunks_exact(8)
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
.collect::<Vec<u64>>();

// finding MADT table
let madt = entries_slice
.iter()
.find(|addr| {
let sdt_header =
unsafe { transmute::<*const c_void, &Header>(**addr as *const c_void) };
log::info!("Found SDT Table: {:x?}", sdt_header.signature);
if sdt_header.signature == *b"APIC" {
log::info!("Found MADT Table at address: {:x?}", addr);
}
sdt_header.signature == *b"APIC"
})
.map(|u64| {
// SAFETY: the address is valid as it was found in the ACPI tables, UEFI guarantees their validity
unsafe { transmute::<*const c_void, &Header>(*u64 as *const c_void) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid using transmute whenever possible, use the pointer methods for casting and as_ref instead.

});
if let Some(madt) = madt {
let madt_table_ptr = madt as *const Header;
return Ok(NonNull::new(madt_table_ptr as *mut Header).ok_or(TmkError::AcpiError)?);
}

log::error!("MADT Table not found");
Err(TmkError::AcpiError)
}

/// Returns the number of APIC entries found in the MADT table.
pub fn get_apic_count_from_madt() -> TmkResult<usize> {
let madt_ptr = get_madt_ptr()?;
// SAFETY: madt_ptr is valid as ensured by get_madt_ptr
let madt_table_size: usize = unsafe { madt_ptr.as_ref().length.get() } as usize;
// SAFETY: madt_ptr is valid as ensured by get_madt_ptr
let madt_table_bytes =
unsafe { core::slice::from_raw_parts(madt_ptr.as_ptr() as *const u8, madt_table_size) };
let madt_parser = MadtParser::new(madt_table_bytes).map_err(|e| {
log::error!("Failed to parse MADT table: {:?}", e);
TmkError::AcpiError
})?;
let mut processor_count = 0;
madt_parser.entries().for_each(|e| {
if let Ok(entry) = e {
log::trace!("MADT Entry: {:?}", entry);
match entry {
MadtEntry::Apic(_) | MadtEntry::X2Apic(_) => {
processor_count += 1;
}
}
} else {
log::error!("Failed to parse MADT entry: {:?}", e);
}
});

Ok(processor_count)
}
5 changes: 2 additions & 3 deletions opentmk/src/uefi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

pub mod acpi_wrap;
mod alloc;
pub mod init;
mod rt;

use init::init;
use uefi::Status;
use uefi::entry;

use crate::tmk_assert;

#[entry]
#[uefi::entry]
fn uefi_main() -> Status {
let r = init();
tmk_assert!(r.is_ok(), "init should succeed");

log::warn!("TEST_START");
crate::tests::run_test();
log::warn!("TEST_END");
Expand Down
Loading