import: upstream ACPI SRAT/SLIT parsing modules (cb88ed59)

Import upstream kernel commit cb88ed59 'Parse ACPI tables for NUMA
information without allocating from the heap'. Adds new files:
- src/acpi/slit.rs (SLIT table parsing)
- src/acpi/srat/mod.rs (SRAT table framework)
- src/acpi/srat/x86.rs (x86 SRAT implementation)
- src/acpi/srat/aarch64.rs (aarch64 SRAT stub)

These modules are not yet wired into our acpi/mod.rs module tree.
Our fork uses a userspace numad daemon for NUMA discovery, while
upstream uses kernel-side parsing. The files are available for
future convergence work.

Existing files (numa.rs, startup/mod.rs, start.rs) retain our Red
Bear implementation — the upstream refactoring (numa::init() →
numa::dump_info()) does not apply to our different init path.
This commit is contained in:
2026-07-05 08:54:13 +03:00
parent f5e0b9b720
commit 2fa0860970
4 changed files with 392 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
use crate::{
acpi::{rxsdt::Rxsdt, sdt::Sdt, RXSDT_ENUM},
find_one_sdt,
memory::{round_up_pages, PAGE_SIZE},
numa::{self},
};
use core::{ops::Add, slice};
use hashbrown::HashMap;
use rmm::{Arch, BumpAllocator, FrameAllocator, FrameCount};
use spin::once::Once;
#[derive(Debug)]
pub struct Slit {
sdt: &'static Sdt,
no: u64,
address: *const u8,
}
impl Slit {
pub fn new(sdt: &'static Sdt) -> Self {
Self {
sdt,
no: unsafe { *(sdt.data_address() as *const u64) },
address: (sdt.data_address() + 8) as *const u8,
}
}
pub fn init<A: Arch>(&self, allocator: &mut BumpAllocator<A>) -> &'static mut [u8] {
unsafe { slice::from_raw_parts_mut(self.address as *mut u8, (self.no * self.no) as usize) }
}
}
pub fn init<A: Arch>(allocator: &mut BumpAllocator<A>, distances: &Once<&'static [u8]>) {
if let Some(rxsdt) = RXSDT_ENUM.get() {
for sdt_addr in rxsdt.iter() {
let sdt =
unsafe { &*(crate::memory::RmmA::phys_to_virt(sdt_addr).data() as *const Sdt) };
if &sdt.signature == b"SLIT" {
let slit = Slit::new(sdt);
distances.call_once(|| slit.init(allocator));
return;
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
use crate::acpi::srat::Srat;
pub fn init_srat(
allocator: &mut BumpAllocator<A>,
srat: &Srat,
) -> (&'static [u32], &'static [u32], &'static [NumaMemory]) {
// todo
}
+176
View File
@@ -0,0 +1,176 @@
//! See <https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#system-resource-affinity-table-srat>
use hashbrown::HashMap;
use rmm::{Arch, BumpAllocator};
use spin::once::Once;
use crate::{
acpi::{find_sdt, get_sdt_signature, rxsdt::Rxsdt, sdt::Sdt, srat, RXSDT_ENUM},
find_one_sdt, memory,
numa::NumaMemory,
};
#[cfg(target_arch = "aarch64")]
#[path = "aarch64.rs"]
mod arch;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "x86.rs"]
mod arch;
#[repr(C, packed)]
pub struct Srat {
sdt: &'static Sdt,
entries: *const u8,
}
pub fn init<A: Arch>(
allocator: &mut BumpAllocator<A>,
map: &Once<&'static [u32]>,
cpus: &Once<&'static [u32]>,
mem: &Once<&'static [NumaMemory]>,
) {
if let Some(rxsdt) = RXSDT_ENUM.get() {
for sdt_addr in rxsdt.iter() {
let sdt = unsafe { &*(memory::RmmA::phys_to_virt(sdt_addr).data() as *const Sdt) };
if &sdt.signature == b"SRAT" {
let (a, b, c) = arch::init_srat(allocator, &Srat::new(sdt));
map.call_once(|| a);
cpus.call_once(|| b);
mem.call_once(|| c);
return;
}
}
}
}
impl Srat {
pub fn new(sdt: &'static Sdt) -> Self {
Self {
sdt,
entries: (sdt.data_address() + 12) as *const u8,
}
}
}
impl<'a> IntoIterator for &'a Srat {
type Item = SratEntry;
type IntoIter = SratIter<'a>;
fn into_iter(self) -> Self::IntoIter {
SratIter { i: 0, srat: self }
}
}
pub struct SratIter<'a> {
i: u32,
srat: &'a Srat,
}
impl<'a> Iterator for SratIter<'a> {
type Item = SratEntry;
fn next(&mut self) -> Option<Self::Item> {
while self.i < self.srat.sdt.data_len() as u32 {
let entry = unsafe { self.srat.entries.add(self.i as usize) };
let entry_len = unsafe { *self.srat.entries.add(self.i as usize + 1) };
let entry = Some(match unsafe { *entry } {
0 => SratEntry::LegacyProcessorLocalAffinity(unsafe {
assert!(entry_len as usize == size_of::<LegacyProcessorLocalAffinity>() + 2);
*(entry.add(2) as *const LegacyProcessorLocalAffinity)
}),
1 => SratEntry::MemoryAffinity(unsafe {
assert!(entry_len as usize == size_of::<MemoryAffinity>() + 10);
*(entry.add(2) as *const MemoryAffinity)
}),
2 => SratEntry::ProcessorLocalAffinity(unsafe {
assert!(entry_len as usize == size_of::<ProcessorLocalAffinity>() + 8);
*(entry.add(4) as *const ProcessorLocalAffinity)
}),
3 => SratEntry::GiccAffinity(unsafe {
assert!(entry_len as usize == size_of::<GiccAffinity>() + 2);
*(entry.add(2) as *const GiccAffinity)
}),
// ignore GIC ITS Affinity and Generic Initiator Affinity
_ => {
self.i += entry_len as u32;
continue;
}
});
self.i += entry_len as u32;
return entry;
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub enum SratEntry {
LegacyProcessorLocalAffinity(LegacyProcessorLocalAffinity),
MemoryAffinity(MemoryAffinity),
ProcessorLocalAffinity(ProcessorLocalAffinity),
GiccAffinity(GiccAffinity),
// unimplemented: Gic Its Affinity and Generic Initiator Affinity
// our current focus is only on memory and cpus
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
/// For legacy xAPIC systems
struct LegacyProcessorLocalAffinity {
proximity_domain_low: u8,
apic_id: u8,
flags: u32,
sapic_id: u8,
proximity_domain_high: [u8; 3],
clock_domain: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct MemoryAffinity {
proximity_domain: u32,
_reserved0: u16,
base_address_low: u32,
base_address_high: u32,
length_low: u32,
length_high: u32,
_reserved1: u32,
flags: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
/// For x2APIC systems
struct ProcessorLocalAffinity {
proximity_domain: u32,
x2apic_id: u32,
flags: u32,
clock_domain: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct GiccAffinity {
proximity_domain: u32,
processor_uid: u32,
flags: u32,
clock_domain: u32,
}
#[inline(always)]
pub(crate) fn to_usize(low: u32, high: u32) -> usize {
#[cfg(target_pointer_width = "32")]
return low as usize;
#[cfg(target_pointer_width = "64")]
{
let mut low_and_high = [0u8; 8];
low_and_high[0..=3].copy_from_slice(low.to_le_bytes().as_slice());
low_and_high[4..=7].copy_from_slice(high.to_le_bytes().as_slice());
usize::from_le_bytes(low_and_high)
}
}
+164
View File
@@ -0,0 +1,164 @@
use core::{iter, slice};
use hashbrown::HashMap;
use rmm::{Arch, BumpAllocator, FrameAllocator};
use crate::{
acpi::srat::{to_usize, Srat, SratEntry},
cpu_set,
memory::{self, PAGE_SIZE},
numa::{self, NumaMemory},
};
#[inline(always)]
fn to_single_int(high: &[u8; 3], low: u8) -> u32 {
let mut high_and_low = [0u8; 4];
high_and_low[0] = low;
(high_and_low[1], high_and_low[2], high_and_low[3]) = (high[0], high[1], high[2]);
u32::from_le_bytes(high_and_low)
}
pub fn init_srat<A: Arch>(
allocator: &mut BumpAllocator<A>,
srat: &Srat,
) -> (&'static [u32], &'static [u32], &'static [NumaMemory]) {
let dom_node_map = allocator
.allocate(rmm::FrameCount::new(
memory::round_up_pages(numa::MAX_DOMAINS * size_of::<u32>()) / PAGE_SIZE,
))
.expect("Failed to allocate memory for storing NUMA info");
let mut mapper = unsafe { rmm::PageMapper::current(rmm::TableKind::Kernel, allocator) };
let mut flags = rmm::PageFlags::<A>::new();
let flags = flags.write(true);
let dom_node_map_ptr = unsafe {
let (va, flush) = mapper
.map_linearly(dom_node_map, flags)
.expect("Failed to map NUMA info pages");
flush.flush();
va.data() as *mut u32
};
// Occupies 512 bytes (1/8th of a page)
let dom_node_map: &'static mut [u32] =
unsafe { slice::from_raw_parts_mut(dom_node_map_ptr, numa::MAX_DOMAINS) };
dom_node_map.fill(u32::MAX);
let mut cpu_count = 0;
let mut memory_count = 0;
srat.into_iter().for_each(|e| match e {
SratEntry::LegacyProcessorLocalAffinity(legacy_processor_local_affinity) => cpu_count += 1,
SratEntry::MemoryAffinity(memory_affinity) => memory_count += 1,
SratEntry::ProcessorLocalAffinity(processor_local_affinity) => todo!(),
_ => (),
});
assert!(
memory_count <= numa::MAX_DOMAINS,
"Found {} memory blocks while only a maximum of {} are supported",
memory_count,
numa::MAX_DOMAINS
);
assert!(
cpu_count <= cpu_set::MAX_CPU_COUNT,
"Found more number of CPUs than supported"
);
// occupies 512 bytes (1/8th of a page)
let cpus: &'static mut [u32] = unsafe {
slice::from_raw_parts_mut(
dom_node_map_ptr.add(numa::MAX_DOMAINS) as *mut u32,
numa::MAX_DOMAINS,
)
};
cpus.fill(u32::MAX);
// total occupied till now: 1024 bytes, remaining 3072 bytes, can accomodate 128 memory entries
let memories: &'static mut [NumaMemory] = unsafe {
slice::from_raw_parts_mut(
cpus.as_ptr().add(numa::MAX_DOMAINS) as *mut NumaMemory,
numa::MAX_DOMAINS,
)
};
memories.fill(NumaMemory {
start: 0,
length: 0,
dom: 0,
_pad: [0; 4],
});
for affinity in srat {
match affinity {
SratEntry::LegacyProcessorLocalAffinity(legacy_processor_local_affinity) => {
if legacy_processor_local_affinity.flags & 1 == 0 {
// processor disabled
continue;
}
let dom = to_single_int(
&legacy_processor_local_affinity.proximity_domain_high,
legacy_processor_local_affinity.proximity_domain_low,
);
if dom_node_map[dom as usize] == u32::MAX {
let node_id = numa::assign_node_id(true);
dom_node_map[dom as usize] = node_id as u32;
}
cpus[legacy_processor_local_affinity.apic_id as usize] = dom_node_map[dom as usize];
}
SratEntry::MemoryAffinity(memory_affinity) => {
if memory_affinity.flags & 1 == 0 {
// memory is not enabled
continue;
}
if memory_affinity.flags & (1 << 1) != 0 {
// memory is hot-pluggable
continue;
}
let dom = memory_affinity.proximity_domain;
if memory_affinity.length_low == 0 {
continue;
}
let start = to_usize(
memory_affinity.base_address_low,
memory_affinity.base_address_high,
);
let length = to_usize(memory_affinity.length_low, memory_affinity.length_high);
if dom_node_map[dom as usize] == u32::MAX {
let node_id = numa::assign_node_id(true);
dom_node_map[dom as usize] = node_id as u32;
}
memories[dom_node_map[dom as usize] as usize] = numa::NumaMemory {
start,
length,
dom: dom_node_map[dom as usize],
_pad: [0u8; 4],
};
}
SratEntry::ProcessorLocalAffinity(processor_local_affinity) => {
if processor_local_affinity.flags & 1 == 0 {
// processor disabled
continue;
}
let dom = processor_local_affinity.proximity_domain;
if dom_node_map[dom as usize] == u32::MAX {
let node_id = numa::assign_node_id(true);
dom_node_map[dom as usize] = node_id as u32;
}
cpus[dom_node_map[dom as usize] as usize] =
processor_local_affinity.proximity_domain;
}
_ => continue,
}
}
let mut flags = rmm::PageFlags::<A>::new();
let flags = flags.write(false);
let flush = unsafe {
mapper
.remap(rmm::VirtualAddress::new(dom_node_map_ptr.addr()), flags)
.expect("Unable to make NUMA info page read-only")
};
flush.flush();
(dom_node_map, cpus, memories)
}