Skip to main content

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

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