diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 1a61b050..d0b42a24 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,4 +1,5 @@ pub mod interrupt; +pub mod numa; pub mod pci; pub use interrupt::InterruptModel; diff --git a/src/platform/numa.rs b/src/platform/numa.rs new file mode 100644 index 00000000..1ed15774 --- /dev/null +++ b/src/platform/numa.rs @@ -0,0 +1,87 @@ +use crate::{ + AcpiTables, + Handler, + sdt::{ + slit::{DistanceMatrix, Slit}, + srat::{LocalApicAffinityFlags, MemoryAffinityFlags, Srat, SratEntry}, + }, +}; +use alloc::{alloc::Global, vec::Vec}; +use core::alloc::Allocator; + +/// Information about the setup of NUMA (Non-Uniform Memory Architecture) resources within the +/// sytem. +pub struct NumaInfo { + pub processor_affinity: Vec, + pub memory_affinity: Vec, + pub num_proximity_domains: usize, + pub distance_matrix: Vec, +} + +impl NumaInfo { + pub fn new(tables: AcpiTables) -> NumaInfo { + Self::new_in(tables, Global) + } +} + +impl NumaInfo { + pub fn new_in(tables: AcpiTables, allocator: A) -> NumaInfo { + let mut processor_affinity = Vec::new_in(allocator.clone()); + let mut memory_affinity = Vec::new_in(allocator.clone()); + + if let Some(srat) = tables.find_table::() { + for entry in srat.get().entries() { + match entry { + SratEntry::LocalApicAffinity(entry) => processor_affinity.push(ProcessorAffinity { + local_apic_id: entry.apic_id as u32, + proximity_domain: entry.proximity_domain(), + is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED), + }), + SratEntry::LocalApicX2Affinity(entry) => processor_affinity.push(ProcessorAffinity { + local_apic_id: entry.x2apic_id, + proximity_domain: entry.proximity_domain, + is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED), + }), + SratEntry::MemoryAffinity(entry) => memory_affinity.push(MemoryAffinity { + base_address: entry.base_address(), + length: entry.length(), + proximity_domain: entry.proximity_domain, + is_enabled: { entry.flags }.contains(MemoryAffinityFlags::ENABLED), + is_hot_pluggable: { entry.flags }.contains(MemoryAffinityFlags::HOT_PLUGGABLE), + is_non_volatile: { entry.flags }.contains(MemoryAffinityFlags::NON_VOLATILE), + }), + _ => (), + } + } + } + + let (num_proximity_domains, distance_matrix) = if let Some(slit) = tables.find_table::() { + (slit.get().num_proximity_domains as usize, slit.get().matrix_raw().to_vec_in(allocator.clone())) + } else { + (0, Vec::new_in(allocator.clone())) + }; + + NumaInfo { processor_affinity, memory_affinity, num_proximity_domains, distance_matrix } + } + + pub fn distance_matrix(&self) -> DistanceMatrix<'_> { + DistanceMatrix { num_proximity_domains: self.num_proximity_domains as u64, matrix: &self.distance_matrix } + } +} + +#[derive(Clone, Debug)] +pub struct ProcessorAffinity { + pub local_apic_id: u32, + pub proximity_domain: u32, + pub is_enabled: bool, +} + +#[derive(Clone, Debug)] +pub struct MemoryAffinity { + pub base_address: u64, + pub length: u64, + pub proximity_domain: u32, + pub is_enabled: bool, + pub is_hot_pluggable: bool, + pub is_non_volatile: bool, +} diff --git a/src/sdt/madt.rs b/src/sdt/madt.rs index eab7371d..805e428b 100644 --- a/src/sdt/madt.rs +++ b/src/sdt/madt.rs @@ -9,6 +9,7 @@ use core::{ mem, pin::Pin, }; +use log::warn; #[derive(Clone, Copy, Debug)] pub enum MadtError { @@ -114,8 +115,16 @@ impl<'a> Iterator for MadtEntryIter<'a> { let entry_pointer = self.pointer; let header = unsafe { *(self.pointer as *const EntryHeader) }; - self.pointer = unsafe { self.pointer.offset(header.length as isize) }; - self.remaining_length -= header.length as u32; + if header.length as u32 > self.remaining_length { + warn!( + "Invalid entry of type {} in MADT - extending past length of table. Ignoring", + header.entry_type + ); + return None; + } + + self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) }; + self.remaining_length = self.remaining_length.saturating_sub(header.length as u32); macro_rules! construct_entry { ($entry_type:expr, diff --git a/src/sdt/mod.rs b/src/sdt/mod.rs index af55afac..fe2d0c5f 100644 --- a/src/sdt/mod.rs +++ b/src/sdt/mod.rs @@ -4,7 +4,9 @@ pub mod fadt; pub mod hpet; pub mod madt; pub mod mcfg; +pub mod slit; pub mod spcr; +pub mod srat; use crate::AcpiError; use core::{fmt, mem::MaybeUninit, str}; diff --git a/src/sdt/slit.rs b/src/sdt/slit.rs new file mode 100644 index 00000000..565db7e5 --- /dev/null +++ b/src/sdt/slit.rs @@ -0,0 +1,62 @@ +use crate::{ + AcpiTable, + sdt::{SdtHeader, Signature}, +}; +use core::{marker::PhantomPinned, mem, pin::Pin, slice}; +use log::warn; + +/// The SLIT (System Locality Information Table) provides a matrix of relative distances between +/// all proximity domains. It encodes a list of N*N entries, where each entry is `u8` and the +/// relative distance between proximity domains `(i, j)` is the entry at `i*N+j`. The distance +/// between a proximity domain and itself is normalised to a value of `10`. +#[derive(Debug)] +#[repr(C, packed)] +pub struct Slit { + pub header: SdtHeader, + pub num_proximity_domains: u64, + _pinned: PhantomPinned, +} + +unsafe impl AcpiTable for Slit { + const SIGNATURE: Signature = Signature::SLIT; + + fn header(&self) -> &SdtHeader { + &self.header + } +} + +impl Slit { + pub fn matrix(self: Pin<&Self>) -> DistanceMatrix<'_> { + DistanceMatrix { num_proximity_domains: self.num_proximity_domains, matrix: self.matrix_raw() } + } + + pub fn matrix_raw(self: Pin<&Self>) -> &[u8] { + let mut num_entries = self.num_proximity_domains * self.num_proximity_domains; + if (mem::size_of::() + num_entries as usize * num_entries as usize * mem::size_of::()) + > self.header.length as usize + { + warn!("SLIT too short for given number of proximity domains! Returning empty matrix"); + num_entries = 0; + } + + unsafe { + let ptr = Pin::into_inner_unchecked(self) as *const Slit as *const u8; + slice::from_raw_parts(ptr.byte_add(mem::size_of::()), num_entries as usize) + } + } +} + +pub struct DistanceMatrix<'a> { + pub num_proximity_domains: u64, + pub matrix: &'a [u8], +} + +impl DistanceMatrix<'_> { + pub fn distance(&self, i: u32, j: u32) -> Option { + if i as u64 <= self.num_proximity_domains && j as u64 <= self.num_proximity_domains { + Some(self.matrix[i as usize + j as usize * self.num_proximity_domains as usize]) + } else { + None + } + } +} diff --git a/src/sdt/srat.rs b/src/sdt/srat.rs new file mode 100644 index 00000000..2a40b536 --- /dev/null +++ b/src/sdt/srat.rs @@ -0,0 +1,228 @@ +use crate::{ + AcpiTable, + sdt::{SdtHeader, Signature}, +}; +use bit_field::BitField; +use core::{ + marker::{PhantomData, PhantomPinned}, + mem, + pin::Pin, +}; +use log::warn; + +/// Represents the SRAT (System Resource Affinity Table). This is a variable length table that +/// allows devices (processors, memory ranges, and generic 'initiators') to be associated with +/// system locality / proximity domains and clock domains. +#[derive(Debug)] +#[repr(C, packed)] +pub struct Srat { + pub header: SdtHeader, + _reserved0: u32, + _reserved1: u64, + _pinned: PhantomPinned, +} + +unsafe impl AcpiTable for Srat { + const SIGNATURE: Signature = Signature::SRAT; + + fn header(&self) -> &SdtHeader { + &self.header + } +} + +impl Srat { + pub fn entries(self: Pin<&Self>) -> SratEntryIter<'_> { + let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Srat as *const u8 }; + SratEntryIter { + pointer: unsafe { ptr.add(mem::size_of::()) }, + remaining_length: self.header.length - mem::size_of::() as u32, + _phantom: PhantomData, + } + } +} + +#[derive(Debug)] +pub struct SratEntryIter<'a> { + pointer: *const u8, + /* + * The iterator can only have at most `u32::MAX` remaining bytes, because the length of the + * whole SDT can only be at most `u32::MAX`. + */ + remaining_length: u32, + _phantom: PhantomData<&'a ()>, +} + +#[derive(Debug)] +pub enum SratEntry<'a> { + LocalApicAffinity(&'a LocalApicAffinity), + MemoryAffinity(&'a MemoryAffinity), + LocalApicX2Affinity(&'a LocalApicX2Affinity), + GiccAffinity(&'a GiccAffinity), + GicItsAffinity(&'a GicItsAffinity), + GicInitiatorAffinity(&'a GicInitiatorAffinity), +} + +impl<'a> Iterator for SratEntryIter<'a> { + type Item = SratEntry<'a>; + + fn next(&mut self) -> Option { + while self.remaining_length > 0 { + let entry_pointer = self.pointer; + let header = unsafe { *(self.pointer as *const EntryHeader) }; + + if header.length as u32 > self.remaining_length { + warn!("Invalid entry of type {} in SRAT - extending past length of table. Ignoring", header.typ); + return None; + } + + self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) }; + self.remaining_length = self.remaining_length.saturating_sub(header.length as u32); + + match header.typ { + 0 => { + return Some(SratEntry::LocalApicAffinity(unsafe { + &*(entry_pointer as *const LocalApicAffinity) + })); + } + 1 => { + return Some(SratEntry::MemoryAffinity(unsafe { &*(entry_pointer as *const MemoryAffinity) })); + } + 2 => { + return Some(SratEntry::LocalApicX2Affinity(unsafe { + &*(entry_pointer as *const LocalApicX2Affinity) + })); + } + 3 => return Some(SratEntry::GiccAffinity(unsafe { &*(entry_pointer as *const GiccAffinity) })), + 4 => { + return Some(SratEntry::GicItsAffinity(unsafe { &*(entry_pointer as *const GicItsAffinity) })); + } + 5 => { + return Some(SratEntry::GicInitiatorAffinity(unsafe { + &*(entry_pointer as *const GicInitiatorAffinity) + })); + } + other => warn!("Unrecognised entry in SRAT of type {}", other), + } + } + + None + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct EntryHeader { + pub typ: u8, + pub length: u16, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct LocalApicAffinity { + pub header: EntryHeader, + pub proximity_domain_low: u8, + pub apic_id: u8, + pub flags: LocalApicAffinityFlags, + pub local_sapic_eid: u8, + pub proximity_domain_high: [u8; 3], + pub clock_domain: u32, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug)] + pub struct LocalApicAffinityFlags: u32 { + const ENABLED = 1; + } +} + +impl LocalApicAffinity { + pub fn proximity_domain(&self) -> u32 { + u32::from_le_bytes([ + self.proximity_domain_low, + self.proximity_domain_high[0], + self.proximity_domain_high[1], + self.proximity_domain_high[2], + ]) + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct MemoryAffinity { + pub header: EntryHeader, + pub proximity_domain: u32, + _reserved0: u16, + pub base_address_low: u32, + pub base_address_high: u32, + pub length_low: u32, + pub length_high: u32, + _reserved1: u32, + pub flags: MemoryAffinityFlags, + _reserved2: u64, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug)] + pub struct MemoryAffinityFlags: u32 { + const ENABLED = 1; + const HOT_PLUGGABLE = 1 << 1; + const NON_VOLATILE = 1 << 2; + } +} + +impl MemoryAffinity { + pub fn base_address(&self) -> u64 { + let mut address = self.base_address_low as u64; + address.set_bits(32..64, self.base_address_high as u64); + address + } + + pub fn length(&self) -> u64 { + let mut length = self.length_low as u64; + length.set_bits(32..64, self.base_address_high as u64); + length + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct LocalApicX2Affinity { + pub header: EntryHeader, + _reserved0: u16, + pub proximity_domain: u32, + pub x2apic_id: u32, + pub flags: LocalApicAffinityFlags, + pub clock_domain: u32, + _reserved1: u32, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct GiccAffinity { + pub header: EntryHeader, + pub proximity_domain: u32, + pub acpi_processor_uid: u32, + pub flags: u32, + pub clock_domain: u32, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct GicItsAffinity { + pub header: EntryHeader, + pub proximity_domain: u32, + _reserved0: u16, + pub its_id: u32, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct GicInitiatorAffinity { + pub header: EntryHeader, + _reserved0: u8, + pub device_handle_type: u8, + pub proximity_domain: u32, + pub device_handle: [u8; 16], + pub flags: u32, + _reserved1: u32, +}