ostd/arch/x86/kernel/acpi/
mod.rs

1// SPDX-License-Identifier: MPL-2.0
2
3pub(in crate::arch) mod dmar;
4pub(in crate::arch) mod remapping;
5
6use core::{num::NonZeroU8, ptr::NonNull};
7
8use acpi::{
9    AcpiHandler, AcpiTables,
10    address::AddressSpace,
11    fadt::{Fadt, IaPcBootArchFlags},
12    mcfg::Mcfg,
13    rsdp::Rsdp,
14};
15use log::warn;
16use spin::Once;
17
18use crate::{
19    boot::{self, BootloaderAcpiArg},
20    mm::paddr_to_vaddr,
21};
22
23#[derive(Debug, Clone)]
24pub(crate) struct AcpiMemoryHandler {}
25
26impl AcpiHandler for AcpiMemoryHandler {
27    unsafe fn map_physical_region<T>(
28        &self,
29        physical_address: usize,
30        size: usize,
31    ) -> acpi::PhysicalMapping<Self, T> {
32        let virtual_address = NonNull::new(paddr_to_vaddr(physical_address) as *mut T).unwrap();
33
34        // SAFETY: The caller should guarantee that `physical_address..physical_address + size` is
35        // part of the ACPI table. Then the memory region is mapped to `virtual_address` and is
36        // valid for read and immutable dereferencing.
37        // FIXME: The caller guarantee only holds if we trust the hardware to provide a valid ACPI
38        // table. Otherwise, if the table is corrupted, it may reference arbitrary memory regions.
39        unsafe {
40            acpi::PhysicalMapping::new(physical_address, virtual_address, size, size, self.clone())
41        }
42    }
43
44    fn unmap_physical_region<T>(_region: &acpi::PhysicalMapping<Self, T>) {}
45}
46
47struct SyncAcpiTables(Option<AcpiTables<AcpiMemoryHandler>>);
48
49// SAFETY: This relies on the current implementation of `AcpiTables`,
50// which provides thread-safe access to read-only ACPI table data,
51// so `Sync` is sound for the wrapper.
52// FIXME: It depends on implementation details of `AcpiTables`, which should be avoided.
53unsafe impl Sync for SyncAcpiTables {}
54
55static ACPI_TABLES: Once<SyncAcpiTables> = Once::new();
56
57pub(crate) fn get_acpi_tables() -> Option<&'static AcpiTables<AcpiMemoryHandler>> {
58    let acpi_tables = ACPI_TABLES.call_once(|| {
59        let acpi_tables = match boot::EARLY_INFO.get().unwrap().acpi_arg {
60            BootloaderAcpiArg::Rsdp(addr) => unsafe {
61                AcpiTables::from_rsdp(AcpiMemoryHandler {}, addr).unwrap()
62            },
63            BootloaderAcpiArg::Rsdt(addr) => unsafe {
64                AcpiTables::from_rsdt(AcpiMemoryHandler {}, 0, addr).unwrap()
65            },
66            BootloaderAcpiArg::Xsdt(addr) => unsafe {
67                AcpiTables::from_rsdt(AcpiMemoryHandler {}, 1, addr).unwrap()
68            },
69            BootloaderAcpiArg::NotProvided => {
70                // We search by ourselves if the bootloader decides not to provide a rsdp location.
71                let rsdp = unsafe { Rsdp::search_for_on_bios(AcpiMemoryHandler {}) };
72                match rsdp {
73                    Ok(map) => unsafe {
74                        AcpiTables::from_rsdp(AcpiMemoryHandler {}, map.physical_start()).unwrap()
75                    },
76                    Err(_) => {
77                        warn!("ACPI info not found!");
78                        return SyncAcpiTables(None);
79                    }
80                }
81            }
82        };
83
84        SyncAcpiTables(Some(acpi_tables))
85    });
86
87    acpi_tables.0.as_ref()
88}
89
90/// The platform information provided by the ACPI tables.
91///
92/// Currently, this structure contains only a limited set of fields, far fewer than those in all
93/// ACPI tables. However, the goal is to expand it properly to keep the simplicity of the OSTD code
94/// while enabling OSTD users to safely retrieve information from the ACPI tables.
95#[derive(Debug)]
96pub struct AcpiInfo {
97    /// The RTC CMOS RAM index to the century of data value; the "CENTURY" field in the FADT.
98    pub century_register: Option<NonZeroU8>,
99    /// IA-PC Boot Architecture Flags; the "IAPC_BOOT_ARCH" field in the FADT.
100    pub boot_flags: Option<IaPcBootArchFlags>,
101    /// An I/O port to reset the machine by writing the specified value.
102    pub reset_port_and_val: Option<(u16, u8)>,
103    /// A memory region that is stolen for PCI configuration space.
104    pub pci_ecam_region: Option<PciEcamRegion>,
105}
106
107/// A memory region that is stolen for PCI configuration space.
108#[derive(Debug)]
109pub struct PciEcamRegion {
110    /// The base address of the memory region.
111    pub base_address: u64,
112    /// The start of the bus number.
113    pub bus_start: u8,
114    /// The end of the bus number.
115    pub bus_end: u8,
116}
117
118/// The [`AcpiInfo`] singleton.
119pub static ACPI_INFO: Once<AcpiInfo> = Once::new();
120
121pub(in crate::arch) fn init() {
122    let mut acpi_info = AcpiInfo {
123        century_register: None,
124        boot_flags: None,
125        reset_port_and_val: None,
126        pci_ecam_region: None,
127    };
128
129    let Some(acpi_tables) = get_acpi_tables() else {
130        ACPI_INFO.call_once(|| acpi_info);
131        return;
132    };
133
134    if let Ok(fadt) = acpi_tables.find_table::<Fadt>() {
135        // A zero means that the century register does not exist.
136        acpi_info.century_register = NonZeroU8::new(fadt.century);
137        acpi_info.boot_flags = Some(fadt.iapc_boot_arch);
138        if let Ok(reset_reg) = fadt.reset_register()
139            && reset_reg.address_space == AddressSpace::SystemIo
140            && let Ok(reset_port) = reset_reg.address.try_into()
141        {
142            acpi_info.reset_port_and_val = Some((reset_port, fadt.reset_value));
143        }
144    };
145
146    if let Ok(mcfg) = acpi_tables.find_table::<Mcfg>()
147        // TODO: Support multiple PCIe segment groups instead of assuming only one
148        // PCIe segment group is in use.
149        && let Some(mcfg_entry) = mcfg.entries().first()
150    {
151        acpi_info.pci_ecam_region = Some(PciEcamRegion {
152            base_address: mcfg_entry.base_address,
153            bus_start: mcfg_entry.bus_number_start,
154            bus_end: mcfg_entry.bus_number_end,
155        });
156    }
157
158    log::info!("[ACPI]: Collected information {:?}", acpi_info);
159
160    ACPI_INFO.call_once(|| acpi_info);
161}