浏览代码

Add initial support for the APIC interrupt model

Isaac Woods 6 年之前
父节点
当前提交
cc6399c8d9
共有 4 个文件被更改,包括 267 次插入19 次删除
  1. 35 0
      src/interrupt.rs
  2. 85 7
      src/lib.rs
  3. 145 11
      src/madt.rs
  4. 2 1
      src/rsdp_search.rs

+ 35 - 0
src/interrupt.rs

@@ -0,0 +1,35 @@
+use alloc::vec::Vec;
+
+#[derive(Debug)]
+pub struct IoApic {
+    pub id: u8,
+    pub address: u32,
+    pub global_system_interrupt_base: u32,
+}
+
+#[derive(Debug)]
+pub enum LocalInterruptLine {
+    Lint0,
+    Lint1,
+}
+
+#[derive(Debug)]
+pub enum InterruptModel {
+    /// This model is only chosen when a newer one can not be found and the system supports the
+    /// legacy dual-8259 PIC.
+    Pic,
+
+    /// Describes an interrupt controller based around the Advanced Programmable Interrupt
+    /// Controllers. These are likely to be found on x86 and x86_64 systems and are made up of a
+    /// Local APIC for each core and one or more I/O APICs to handle external interrupts.
+    Apic {
+        local_apic_address: u64,
+        io_apics: Vec<IoApic>,
+        local_apic_nmi_line: LocalInterruptLine,
+
+        /// If this field is set, you must remap and mask all the lines of the legacy PIC, even if
+        /// you choose to use the APIC. It's recommended that you do this even if ACPI does not
+        /// require you to.
+        also_has_legacy_pics: bool,
+    },
+}

+ 85 - 7
src/lib.rs

@@ -15,6 +15,7 @@ extern crate bit_field;
 mod aml;
 mod fadt;
 mod hpet;
+pub mod interrupt;
 mod madt;
 mod rsdp;
 mod rsdp_search;
@@ -22,11 +23,12 @@ mod sdt;
 
 pub use rsdp_search::search_for_rsdp_bios;
 
-use alloc::{collections::BTreeMap, string::String};
+use alloc::{collections::BTreeMap, vec::Vec, string::String};
 use aml::{AmlError, AmlValue};
 use core::mem;
 use core::ops::Deref;
 use core::ptr::NonNull;
+use interrupt::InterruptModel;
 use rsdp::Rsdp;
 use sdt::SdtHeader;
 
@@ -44,10 +46,12 @@ pub enum AcpiError {
     SdtInvalidChecksum([u8; 4]),
 
     InvalidAmlTable([u8; 4], AmlError),
+
+    MalformedMadt(&'static str),
 }
 
 #[repr(C, packed)]
-pub struct GenericAddress {
+pub(crate) struct GenericAddress {
     address_space: u8,
     bit_width: u8,
     bit_offset: u8,
@@ -55,6 +59,50 @@ pub struct GenericAddress {
     address: u64,
 }
 
+#[derive(Clone, Copy, Debug)]
+pub enum ProcessorState {
+    /// A processor in this state is unusable, and you must not attempt to bring it up.
+    Disabled,
+
+    /// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
+    /// but may be brought up.
+    WaitingForSipi,
+
+    /// A Running processor is currently brought up and running code.
+    Running,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Processor {
+    processor_uid: u8,
+    local_apic_id: u8,
+
+    /// The state of this processor. Always check that the processor is not `Disabled` before
+    /// attempting to bring it up!
+    state: ProcessorState,
+
+    /// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
+    /// When the bootloader is entered, the BSP is the only processor running code. To run code on
+    /// more than one processor, you need to "bring up" the APs.
+    is_ap: bool,
+}
+
+impl Processor {
+    pub(crate) fn new(
+        processor_uid: u8,
+        local_apic_id: u8,
+        state: ProcessorState,
+        is_ap: bool,
+    ) -> Processor {
+        Processor {
+            processor_uid,
+            local_apic_id,
+            state,
+            is_ap,
+        }
+    }
+}
+
 /// Describes a physical mapping created by `AcpiHandler::map_physical_region` and unmapped by
 /// `AcpiHandler::unmap_physical_region`. The region mapped must be at least `size_of::<T>()`
 /// bytes, but may be bigger.
@@ -73,10 +121,9 @@ impl<T> Deref for PhysicalMapping<T> {
     }
 }
 
-/// The kernel must provide an implementation of this trait for `acpi` to interface with. It has
-/// utility methods `acpi` uses to for e.g. mapping physical memory, but also an interface for
-/// `acpi` to tell the kernel about the tables it's parsing, such as how the kernel should
-/// configure the APIC or PCI routing.
+/// An implementation of this trait must be provided to allow `acpi` to access platform-specific
+/// functionality, such as mapping regions of physical memory. You are free to implement these
+/// however you please, as long as they conform to the documentation of each function.
 pub trait AcpiHandler {
     /// Given a starting physical address and a size, map a region of physical memory that contains
     /// a `T` (but may be bigger than `size_of::<T>()`). The address doesn't have to be page-aligned,
@@ -94,10 +141,38 @@ pub trait AcpiHandler {
     fn unmap_physical_region<T>(&mut self, region: PhysicalMapping<T>);
 }
 
+#[derive(Debug)]
 pub struct Acpi
 {
     acpi_revision: u8,
     namespace: BTreeMap<String, AmlValue>,
+    boot_processor: Option<Processor>,
+    application_processors: Vec<Processor>,
+
+    /// ACPI theoretically allows for more than one interrupt model to be supported by the same
+    /// hardware. For simplicity and because hardware practically will only support one model, we
+    /// just error in cases that the tables detail more than one.
+    interrupt_model: Option<InterruptModel>,
+}
+
+impl Acpi {
+    /// A description of the boot processor. Until you bring any more up, this is the only processor
+    /// running code, even on SMP systems.
+    pub fn boot_processor<'a>(&'a self) -> &'a Option<Processor> {
+        &self.boot_processor
+    }
+
+    /// Descriptions of each of the application processors. These are not brought up until you do
+    /// so. The application processors must be brought up in the order that they appear in this
+    /// list.
+    pub fn application_processors<'a>(&'a self) -> &'a Vec<Processor> {
+        &self.application_processors
+    }
+
+    /// The interrupt model supported by this system.
+    pub fn interrupt_model<'a>(&'a self) -> &'a Option<InterruptModel> {
+        &self.interrupt_model
+    }
 }
 
 /// This is the entry point of `acpi` if you have the **physical** address of the RSDP. It maps
@@ -116,7 +191,7 @@ where
 fn parse_validated_rsdp<H>(
     handler: &mut H,
     rsdp_mapping: PhysicalMapping<Rsdp>,
-) -> Result<(), AcpiError>
+) -> Result<Acpi, AcpiError>
 where
     H: AcpiHandler,
 {
@@ -157,6 +232,9 @@ where
     let mut acpi = Acpi {
         acpi_revision: revision,
         namespace: BTreeMap::new(),
+        boot_processor: None,
+        application_processors: Vec::new(),
+        interrupt_model: None,
     };
 
     let header = sdt::peek_at_sdt_header(handler, physical_address);

+ 145 - 11
src/madt.rs

@@ -1,10 +1,19 @@
+use alloc::vec::Vec;
+use bit_field::BitField;
 use core::marker::PhantomData;
 use core::mem;
+use interrupt::{InterruptModel, IoApic};
 use sdt::SdtHeader;
-use {Acpi, AcpiError, AcpiHandler, PhysicalMapping};
+use {Acpi, AcpiError, AcpiHandler, PhysicalMapping, Processor, ProcessorState};
 
 /// 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
+///     * The Generic Interrupt Controller (GIC) model (ARM systems only)
 #[repr(C, packed)]
 pub(crate) struct Madt {
     header: SdtHeader,
@@ -22,6 +31,10 @@ impl Madt {
             _phantom: PhantomData,
         }
     }
+
+    fn supports_8259(&self) -> bool {
+        unsafe { self.flags.get_bit(0) }
+    }
 }
 
 struct MadtEntryIter<'a> {
@@ -59,10 +72,6 @@ impl<'a> Iterator for MadtEntryIter<'a> {
 
             self.pointer = unsafe { self.pointer.offset(header.length as isize) };
             self.remaining_length -= header.length as u32;
-            info!(
-                "Found MADT entry of {} bytes (id={}), {} bytes remaining",
-                header.length, header.entry_type, self.remaining_length
-            );
 
             match header.entry_type {
                 0x0 => {
@@ -174,6 +183,7 @@ impl<'a> Iterator for MadtEntryIter<'a> {
                 0x80..0xff => {}
 
                 // TODO: remove when support for exhaustive integer patterns is merged
+                // (rust-lang/rust#50912)
                 _ => unreachable!(),
             }
         }
@@ -363,20 +373,144 @@ struct GicInterruptTranslationServiceEntry {
     _reserved2: u32,
 }
 
-pub(crate) fn parse_madt<'a, 'h, H>(
-    acpi: &'a mut Acpi<'h, H>,
+pub(crate) fn parse_madt<H>(
+    acpi: &mut Acpi,
+    handler: &mut H,
     mapping: &PhysicalMapping<Madt>,
 ) -> Result<(), AcpiError>
 where
-    'h: 'a,
-    H: AcpiHandler + 'a,
+    H: AcpiHandler,
 {
     (*mapping).header.validate(b"APIC")?;
 
+    /*
+     * If the MADT doesn't contain another supported interrupt model (either APIC, SAPIC, X2APIC or
+     * GIC), and the system supports the legacy i8259 PIC, recommend that.
+     * TODO: It's not clear how trustworthy this field is - should we be relying on it in any way?
+     */
+    if (*mapping).supports_8259() {
+        acpi.interrupt_model = Some(InterruptModel::Pic);
+    }
+
+    /*
+     * We first do a pass through the MADT to determine which interrupt model is being used.
+     */
     for entry in (*mapping).entries() {
-        info!("Found MADT entry");
-        // TODO: parse entries
+        match entry {
+            MadtEntry::LocalApic(_) |
+            MadtEntry::IoApic(_) |
+            MadtEntry::InterruptSourceOverride(_) |
+            MadtEntry::NmiSource(_) |   // TODO: is this one used by more than one model?
+            MadtEntry::LocalApicNmi(_) |
+            MadtEntry::LocalApicAddressOverride(_) => {
+                acpi.interrupt_model = Some(parse_apic_model(acpi, mapping)?);
+                break;
+            }
+
+            MadtEntry::IoSapic(_) |
+            MadtEntry::LocalSapic(_) |
+            MadtEntry::PlatformInterruptSource(_) => {
+                unimplemented!();
+            }
+
+            MadtEntry::LocalX2Apic(_) |
+            MadtEntry::X2ApicNmi(_) => {
+                unimplemented!();
+            }
+
+            MadtEntry::Gicc(_) |
+            MadtEntry::Gicd(_) |
+            MadtEntry::GicMsiFrame(_) |
+            MadtEntry::GicRedistributor(_) |
+            MadtEntry::GicInterruptTranslationService(_) => {
+                unimplemented!();
+            }
+        }
     }
 
     Ok(())
 }
+
+/// This parses the MADT and gathers information about a APIC interrupt model. We error if we
+/// encounter an entry that doesn't configure the APIC.
+fn parse_apic_model(
+    acpi: &mut Acpi,
+    mapping: &PhysicalMapping<Madt>,
+) -> Result<InterruptModel, AcpiError> {
+    use interrupt::LocalInterruptLine;
+
+    let mut local_apic_address = (*mapping).local_apic_address as u64;
+    let mut io_apics = Vec::new();
+    let mut local_apic_nmi_line = None;
+
+    for entry in (*mapping).entries() {
+        match entry {
+            MadtEntry::LocalApic(ref 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 = acpi.boot_processor.is_some();
+                let is_disabled = !unsafe { 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::new(
+                    entry.processor_id,
+                    entry.apic_id,
+                    state,
+                    is_ap,
+                );
+
+                if is_ap {
+                    acpi.application_processors.push(processor);
+                } else {
+                    acpi.boot_processor = Some(processor);
+                }
+            }
+
+            MadtEntry::IoApic(ref entry) => {
+                io_apics.push(IoApic {
+                    id: entry.io_apic_id,
+                    address: entry.io_apic_address,
+                    global_system_interrupt_base: entry.global_system_interrupt_base,
+                });
+            }
+
+            MadtEntry::InterruptSourceOverride(ref entry) => {
+                // TODO
+            }
+
+            MadtEntry::NmiSource(ref entry) => {
+                // TODO
+            }
+
+            MadtEntry::LocalApicNmi(ref entry) => {
+                local_apic_nmi_line = Some(match entry.nmi_line {
+                    0 => LocalInterruptLine::Lint0,
+                    1 => LocalInterruptLine::Lint1,
+                    _ => return Err(AcpiError::MalformedMadt("APIC: invalid local NMI line")),
+                })
+            }
+
+            MadtEntry::LocalApicAddressOverride(ref entry) => {
+                local_apic_address = entry.local_apic_address;
+            }
+
+            _ => {
+                return Err(AcpiError::MalformedMadt("APIC: unexpected entry"));
+            }
+        }
+    }
+
+    Ok(InterruptModel::Apic {
+        local_apic_address,
+        io_apics,
+        local_apic_nmi_line: local_apic_nmi_line.ok_or(AcpiError::MalformedMadt("APIC: no local NMI line specified"))?,
+        also_has_legacy_pics: (*mapping).supports_8259(),
+    })
+}

+ 2 - 1
src/rsdp_search.rs

@@ -1,4 +1,5 @@
 use core::{mem, ops::RangeInclusive};
+use Acpi;
 use rsdp::Rsdp;
 use {parse_validated_rsdp, AcpiError, AcpiHandler};
 
@@ -55,7 +56,7 @@ where
 ///
 /// This function is unsafe because it may read from protected memory if the computer is using UEFI.
 /// Only use this function if you are sure the computer is using BIOS.
-pub unsafe fn search_for_rsdp_bios<H>(handler: &mut H) -> Result<(), AcpiError>
+pub unsafe fn search_for_rsdp_bios<H>(handler: &mut H) -> Result<Acpi, AcpiError>
 where
     H: AcpiHandler,
 {