summaryrefslogtreecommitdiff
path: root/src/madt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/madt.rs')
-rw-r--r--src/madt.rs646
1 files changed, 646 insertions, 0 deletions
diff --git a/src/madt.rs b/src/madt.rs
new file mode 100644
index 0000000..7debcf6
--- /dev/null
+++ b/src/madt.rs
@@ -0,0 +1,646 @@
+use crate::{
+ sdt::{ExtendedField, SdtHeader, Signature},
+ AcpiTable,
+};
+use bit_field::BitField;
+use core::{marker::PhantomData, mem};
+
+#[cfg(feature = "allocator_api")]
+use crate::{
+ platform::{
+ interrupt::{InterruptModel, Polarity, TriggerMode},
+ ProcessorInfo,
+ },
+ AcpiResult,
+};
+
+#[derive(Debug)]
+pub enum MadtError {
+ UnexpectedEntry,
+ InterruptOverrideEntryHasInvalidBus,
+ InvalidLocalNmiLine,
+ MpsIntiInvalidPolarity,
+ MpsIntiInvalidTriggerMode,
+}
+
+/// Represents the MADT - this contains the MADT header fields. You can then iterate over a `Madt`
+/// to read each entry from it.
+///
+/// In modern versions of ACPI, the MADT can detail one of four interrupt models:
+/// * The ancient dual-i8259 legacy PIC model
+/// * The Advanced Programmable Interrupt Controller (APIC) model
+/// * The Streamlined Advanced Programmable Interrupt Controller (SAPIC) model (for Itanium systems)
+/// * The Generic Interrupt Controller (GIC) model (for ARM systems)
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy)]
+pub struct Madt {
+ pub header: SdtHeader,
+ pub local_apic_address: u32,
+ pub flags: u32,
+}
+
+/// ### Safety: Implementation properly represents a valid MADT.
+unsafe impl AcpiTable for Madt {
+ const SIGNATURE: Signature = Signature::MADT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
+
+impl Madt {
+ #[cfg(feature = "allocator_api")]
+ pub fn parse_interrupt_model_in<'a, A>(
+ &self,
+ allocator: A,
+ ) -> AcpiResult<(InterruptModel<'a, A>, Option<ProcessorInfo<'a, A>>)>
+ where
+ A: core::alloc::Allocator + Clone,
+ {
+ /*
+ * We first do a pass through the MADT to determine which interrupt model is being used.
+ */
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::LocalApic(_) |
+ MadtEntry::LocalX2Apic(_) |
+ MadtEntry::IoApic(_) |
+ MadtEntry::InterruptSourceOverride(_) |
+ MadtEntry::NmiSource(_) | // TODO: is this one used by more than one model?
+ MadtEntry::LocalApicNmi(_) |
+ MadtEntry::X2ApicNmi(_) |
+ MadtEntry::LocalApicAddressOverride(_) => {
+ return self.parse_apic_model_in(allocator);
+ }
+
+ MadtEntry::IoSapic(_) |
+ MadtEntry::LocalSapic(_) |
+ MadtEntry::PlatformInterruptSource(_) => {
+ unimplemented!();
+ }
+
+ MadtEntry::Gicc(_) |
+ MadtEntry::Gicd(_) |
+ MadtEntry::GicMsiFrame(_) |
+ MadtEntry::GicRedistributor(_) |
+ MadtEntry::GicInterruptTranslationService(_) => {
+ unimplemented!();
+ }
+
+ MadtEntry::MultiprocessorWakeup(_) => ()
+ }
+ }
+
+ Ok((InterruptModel::Unknown, None))
+ }
+
+ #[cfg(feature = "allocator_api")]
+ fn parse_apic_model_in<'a, A>(
+ &self,
+ allocator: A,
+ ) -> AcpiResult<(InterruptModel<'a, A>, Option<ProcessorInfo<'a, A>>)>
+ where
+ A: core::alloc::Allocator + Clone,
+ {
+ use crate::{
+ platform::{
+ interrupt::{
+ Apic,
+ InterruptSourceOverride,
+ IoApic,
+ LocalInterruptLine,
+ NmiLine,
+ NmiProcessor,
+ NmiSource,
+ },
+ Processor,
+ ProcessorState,
+ },
+ AcpiError,
+ };
+
+ let mut local_apic_address = self.local_apic_address as u64;
+ let mut io_apic_count = 0;
+ let mut iso_count = 0;
+ let mut nmi_source_count = 0;
+ let mut local_nmi_line_count = 0;
+ let mut processor_count = 0usize;
+
+ // Do a pass over the entries so we know how much space we should reserve in the vectors
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::IoApic(_) => io_apic_count += 1,
+ MadtEntry::InterruptSourceOverride(_) => iso_count += 1,
+ MadtEntry::NmiSource(_) => nmi_source_count += 1,
+ MadtEntry::LocalApicNmi(_) => local_nmi_line_count += 1,
+ MadtEntry::LocalApic(_) => processor_count += 1,
+ _ => (),
+ }
+ }
+
+ let mut io_apics = crate::ManagedSlice::new_in(io_apic_count, allocator.clone())?;
+ let mut interrupt_source_overrides = crate::ManagedSlice::new_in(iso_count, allocator.clone())?;
+ let mut nmi_sources = crate::ManagedSlice::new_in(nmi_source_count, allocator.clone())?;
+ let mut local_apic_nmi_lines = crate::ManagedSlice::new_in(local_nmi_line_count, allocator.clone())?;
+ let mut application_processors =
+ crate::ManagedSlice::new_in(processor_count.saturating_sub(1), allocator)?; // Subtract one for the BSP
+ let mut boot_processor = None;
+
+ io_apic_count = 0;
+ iso_count = 0;
+ nmi_source_count = 0;
+ local_nmi_line_count = 0;
+ processor_count = 0;
+
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::LocalApic(entry) => {
+ /*
+ * The first processor is the BSP. Subsequent ones are APs. If we haven't found
+ * the BSP yet, this must be it.
+ */
+ let is_ap = boot_processor.is_some();
+ let is_disabled = !{ entry.flags }.get_bit(0);
+
+ let state = match (is_ap, is_disabled) {
+ (_, true) => ProcessorState::Disabled,
+ (true, false) => ProcessorState::WaitingForSipi,
+ (false, false) => ProcessorState::Running,
+ };
+
+ let processor = Processor {
+ processor_uid: entry.processor_id as u32,
+ local_apic_id: entry.apic_id as u32,
+ state,
+ is_ap,
+ };
+
+ if is_ap {
+ application_processors[processor_count] = processor;
+ processor_count += 1;
+ } else {
+ boot_processor = Some(processor);
+ }
+ }
+
+ MadtEntry::LocalX2Apic(entry) => {
+ let is_ap = boot_processor.is_some();
+ let is_disabled = !{ entry.flags }.get_bit(0);
+
+ let state = match (is_ap, is_disabled) {
+ (_, true) => ProcessorState::Disabled,
+ (true, false) => ProcessorState::WaitingForSipi,
+ (false, false) => ProcessorState::Running,
+ };
+
+ let processor = Processor {
+ processor_uid: entry.processor_uid,
+ local_apic_id: entry.x2apic_id,
+ state,
+ is_ap,
+ };
+
+ if is_ap {
+ application_processors[processor_count] = processor;
+ processor_count += 1;
+ } else {
+ boot_processor = Some(processor);
+ }
+ }
+
+ MadtEntry::IoApic(entry) => {
+ io_apics[io_apic_count] = IoApic {
+ id: entry.io_apic_id,
+ address: entry.io_apic_address,
+ global_system_interrupt_base: entry.global_system_interrupt_base,
+ };
+ io_apic_count += 1;
+ }
+
+ MadtEntry::InterruptSourceOverride(entry) => {
+ if entry.bus != 0 {
+ return Err(AcpiError::InvalidMadt(MadtError::InterruptOverrideEntryHasInvalidBus));
+ }
+
+ let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
+
+ interrupt_source_overrides[iso_count] = InterruptSourceOverride {
+ isa_source: entry.irq,
+ global_system_interrupt: entry.global_system_interrupt,
+ polarity,
+ trigger_mode,
+ };
+ iso_count += 1;
+ }
+
+ MadtEntry::NmiSource(entry) => {
+ let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
+
+ nmi_sources[nmi_source_count] = NmiSource {
+ global_system_interrupt: entry.global_system_interrupt,
+ polarity,
+ trigger_mode,
+ };
+ nmi_source_count += 1;
+ }
+
+ MadtEntry::LocalApicNmi(entry) => {
+ local_apic_nmi_lines[local_nmi_line_count] = NmiLine {
+ processor: if entry.processor_id == 0xff {
+ NmiProcessor::All
+ } else {
+ NmiProcessor::ProcessorUid(entry.processor_id as u32)
+ },
+ line: match entry.nmi_line {
+ 0 => LocalInterruptLine::Lint0,
+ 1 => LocalInterruptLine::Lint1,
+ _ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
+ },
+ };
+ local_nmi_line_count += 1;
+ }
+
+ MadtEntry::X2ApicNmi(entry) => {
+ local_apic_nmi_lines[local_nmi_line_count] = NmiLine {
+ processor: if entry.processor_uid == 0xffffffff {
+ NmiProcessor::All
+ } else {
+ NmiProcessor::ProcessorUid(entry.processor_uid)
+ },
+ line: match entry.nmi_line {
+ 0 => LocalInterruptLine::Lint0,
+ 1 => LocalInterruptLine::Lint1,
+ _ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
+ },
+ };
+ local_nmi_line_count += 1;
+ }
+
+ MadtEntry::LocalApicAddressOverride(entry) => {
+ local_apic_address = entry.local_apic_address;
+ }
+
+ _ => {
+ return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
+ }
+ }
+ }
+
+ Ok((
+ InterruptModel::Apic(Apic::new(
+ local_apic_address,
+ io_apics,
+ local_apic_nmi_lines,
+ interrupt_source_overrides,
+ nmi_sources,
+ self.supports_8259(),
+ )),
+ Some(ProcessorInfo::new(boot_processor.unwrap(), application_processors)),
+ ))
+ }
+
+ pub fn entries(&self) -> MadtEntryIter {
+ MadtEntryIter {
+ pointer: unsafe { (self as *const Madt as *const u8).add(mem::size_of::<Madt>()) },
+ remaining_length: self.header.length - mem::size_of::<Madt>() as u32,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn supports_8259(&self) -> bool {
+ { self.flags }.get_bit(0)
+ }
+}
+
+#[derive(Debug)]
+pub struct MadtEntryIter<'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 MadtEntry<'a> {
+ LocalApic(&'a LocalApicEntry),
+ IoApic(&'a IoApicEntry),
+ InterruptSourceOverride(&'a InterruptSourceOverrideEntry),
+ NmiSource(&'a NmiSourceEntry),
+ LocalApicNmi(&'a LocalApicNmiEntry),
+ LocalApicAddressOverride(&'a LocalApicAddressOverrideEntry),
+ IoSapic(&'a IoSapicEntry),
+ LocalSapic(&'a LocalSapicEntry),
+ PlatformInterruptSource(&'a PlatformInterruptSourceEntry),
+ LocalX2Apic(&'a LocalX2ApicEntry),
+ X2ApicNmi(&'a X2ApicNmiEntry),
+ Gicc(&'a GiccEntry),
+ Gicd(&'a GicdEntry),
+ GicMsiFrame(&'a GicMsiFrameEntry),
+ GicRedistributor(&'a GicRedistributorEntry),
+ GicInterruptTranslationService(&'a GicInterruptTranslationServiceEntry),
+ MultiprocessorWakeup(&'a MultiprocessorWakeupEntry),
+}
+
+impl<'a> Iterator for MadtEntryIter<'a> {
+ type Item = MadtEntry<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while self.remaining_length > 0 {
+ 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;
+
+ macro_rules! construct_entry {
+ ($entry_type:expr,
+ $entry_pointer:expr,
+ $(($value:expr => $variant:path as $type:ty)),*
+ ) => {
+ match $entry_type {
+ $(
+ $value => {
+ return Some($variant(unsafe {
+ &*($entry_pointer as *const $type)
+ }))
+ }
+ )*
+
+ /*
+ * These entry types are reserved by the ACPI standard. We should skip them
+ * if they appear in a real MADT.
+ */
+ 0x11..=0x7f => {}
+
+ /*
+ * These entry types are reserved for OEM use. Atm, we just skip them too.
+ * TODO: work out if we should ever do anything else here
+ */
+ 0x80..=0xff => {}
+ }
+ }
+ }
+
+ #[rustfmt::skip]
+ construct_entry!(
+ header.entry_type,
+ entry_pointer,
+ (0x0 => MadtEntry::LocalApic as LocalApicEntry),
+ (0x1 => MadtEntry::IoApic as IoApicEntry),
+ (0x2 => MadtEntry::InterruptSourceOverride as InterruptSourceOverrideEntry),
+ (0x3 => MadtEntry::NmiSource as NmiSourceEntry),
+ (0x4 => MadtEntry::LocalApicNmi as LocalApicNmiEntry),
+ (0x5 => MadtEntry::LocalApicAddressOverride as LocalApicAddressOverrideEntry),
+ (0x6 => MadtEntry::IoSapic as IoSapicEntry),
+ (0x7 => MadtEntry::LocalSapic as LocalSapicEntry),
+ (0x8 => MadtEntry::PlatformInterruptSource as PlatformInterruptSourceEntry),
+ (0x9 => MadtEntry::LocalX2Apic as LocalX2ApicEntry),
+ (0xa => MadtEntry::X2ApicNmi as X2ApicNmiEntry),
+ (0xb => MadtEntry::Gicc as GiccEntry),
+ (0xc => MadtEntry::Gicd as GicdEntry),
+ (0xd => MadtEntry::GicMsiFrame as GicMsiFrameEntry),
+ (0xe => MadtEntry::GicRedistributor as GicRedistributorEntry),
+ (0xf => MadtEntry::GicInterruptTranslationService as GicInterruptTranslationServiceEntry),
+ (0x10 => MadtEntry::MultiprocessorWakeup as MultiprocessorWakeupEntry)
+ );
+ }
+
+ None
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct EntryHeader {
+ pub entry_type: u8,
+ pub length: u8,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub apic_id: u8,
+ pub flags: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct IoApicEntry {
+ pub header: EntryHeader,
+ pub io_apic_id: u8,
+ _reserved: u8,
+ pub io_apic_address: u32,
+ pub global_system_interrupt_base: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct InterruptSourceOverrideEntry {
+ pub header: EntryHeader,
+ pub bus: u8, // 0 - ISA bus
+ pub irq: u8, // This is bus-relative
+ pub global_system_interrupt: u32,
+ pub flags: u16,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct NmiSourceEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub global_system_interrupt: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicNmiEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub flags: u16,
+ pub nmi_line: u8, // Describes which LINTn is the NMI connected to
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicAddressOverrideEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub local_apic_address: u64,
+}
+
+/// If this entry is present, the system has an I/O SAPIC, which must be used instead of the I/O
+/// APIC.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct IoSapicEntry {
+ pub header: EntryHeader,
+ pub io_apic_id: u8,
+ _reserved: u8,
+ pub global_system_interrupt_base: u32,
+ pub io_sapic_address: u64,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalSapicEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub local_sapic_id: u8,
+ pub local_sapic_eid: u8,
+ _reserved: [u8; 3],
+ pub flags: u32,
+ pub processor_uid: u32,
+
+ /// This string can be used to associate this local SAPIC to a processor defined in the
+ /// namespace when the `_UID` object is a string. It is a null-terminated ASCII string, and so
+ /// this field will be `'\0'` if the string is not present, otherwise it extends from the
+ /// address of this field.
+ processor_uid_string: u8,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct PlatformInterruptSourceEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub interrupt_type: u8,
+ pub processor_id: u8,
+ pub processor_eid: u8,
+ pub io_sapic_vector: u8,
+ pub global_system_interrupt: u32,
+ pub platform_interrupt_source_flags: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalX2ApicEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub x2apic_id: u32,
+ pub flags: u32,
+ pub processor_uid: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct X2ApicNmiEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub processor_uid: u32,
+ pub nmi_line: u8,
+ _reserved: [u8; 3],
+}
+
+/// This field will appear for ARM processors that support ACPI and use the Generic Interrupt
+/// Controller. In the GICC interrupt model, each logical process has a Processor Device object in
+/// the namespace, and uses this structure to convey its GIC information.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GiccEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub cpu_interface_number: u32,
+ pub processor_uid: u32,
+ pub flags: u32,
+ pub parking_protocol_version: u32,
+ pub performance_interrupt_gsiv: u32,
+ pub parked_address: u64,
+ pub gic_registers_address: u64,
+ pub gic_virtual_registers_address: u64,
+ pub gic_hypervisor_registers_address: u64,
+ pub vgic_maintenance_interrupt: u32,
+ pub gicr_base_address: u64,
+ pub mpidr: u64,
+ pub processor_power_efficiency_class: u8,
+ _reserved2: u8,
+ /// SPE overflow Interrupt.
+ ///
+ /// ACPI 6.3 defined this field. It is zero in prior versions or
+ /// if this processor does not support SPE.
+ pub spe_overflow_interrupt: u16,
+ pub trbe_interrupt: ExtendedField<u16, 6>,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicdEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub gic_id: u32,
+ pub physical_base_address: u64,
+ pub system_vector_base: u32,
+
+ /// The GIC version
+ /// 0x00: Fall back to hardware discovery
+ /// 0x01: GICv1
+ /// 0x02: GICv2
+ /// 0x03: GICv3
+ /// 0x04: GICv4
+ /// 0x05-0xff: Reserved for future use
+ pub gic_version: u8,
+ _reserved2: [u8; 3],
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicMsiFrameEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub frame_id: u32,
+ pub physical_base_address: u64,
+ pub flags: u32,
+ pub spi_count: u16,
+ pub spi_base: u16,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicRedistributorEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub discovery_range_base_address: u64,
+ pub discovery_range_length: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicInterruptTranslationServiceEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub id: u32,
+ pub physical_base_address: u64,
+ _reserved2: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MultiprocessorWakeupEntry {
+ pub header: EntryHeader,
+ pub mailbox_version: u16,
+ _reserved: u32,
+ pub mailbox_address: u64,
+}
+
+#[cfg(feature = "allocator_api")]
+fn parse_mps_inti_flags(flags: u16) -> crate::AcpiResult<(Polarity, TriggerMode)> {
+ let polarity = match flags.get_bits(0..2) {
+ 0b00 => Polarity::SameAsBus,
+ 0b01 => Polarity::ActiveHigh,
+ 0b11 => Polarity::ActiveLow,
+ _ => return Err(crate::AcpiError::InvalidMadt(MadtError::MpsIntiInvalidPolarity)),
+ };
+
+ let trigger_mode = match flags.get_bits(2..4) {
+ 0b00 => TriggerMode::SameAsBus,
+ 0b01 => TriggerMode::Edge,
+ 0b11 => TriggerMode::Level,
+ _ => return Err(crate::AcpiError::InvalidMadt(MadtError::MpsIntiInvalidTriggerMode)),
+ };
+
+ Ok((polarity, trigger_mode))
+}