Skip to main content

strat9_kernel/acpi/
mod.rs

1//! ACPI (Advanced Configuration and Power Interface) support.
2//! Inspired by Theseus OS, MaestroOS, Aero, and Redox.
3//!
4//! Features:
5//! - RSDP/RSDT/XSDT parsing
6//! - MADT (interrupts, APICs)
7//! - FADT (power management, DSDT)
8//! - HPET (timers)
9//! - MCFG (PCIe MMCONFIG)
10//! - DMAR (IOMMU)
11//! - WAET (VM optimization hints)
12//! - BGRT (boot graphics)
13//! - SLIT (NUMA distances)
14//!
15//! TODO : take a look at the v6 :
16//! https://uefi.org/sites/default/files/resources/ACPI_Spec_6.6.pdf
17//! https://uefi.org/specs/ACPI/6.6/
18
19pub mod bgrt;
20pub mod dmar;
21pub mod fadt;
22pub mod hpet;
23pub mod ivrs;
24pub mod madt;
25pub mod mcfg;
26pub mod rsdt;
27pub mod sdt;
28pub mod slit;
29pub mod waet;
30
31use crate::{memory, sync::SpinLock};
32use alloc::{collections::BTreeMap, vec::Vec};
33use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
34use sdt::Sdt;
35
36/// Stored RSDP virtual address (set during init)
37static RSDP_VADDR: AtomicU64 = AtomicU64::new(0);
38
39/// RSDP revision (0 = ACPI 1.0, 2+ = ACPI 2.0+)
40static RSDP_REVISION: AtomicU64 = AtomicU64::new(0);
41
42/// RSDP (Root System Descriptor Pointer) : ACPI 1.0
43#[repr(C, packed)]
44struct Rsdp {
45    signature: [u8; 8],
46    checksum: u8,
47    oem_id: [u8; 6],
48    revision: u8,
49    rsdt_address: u32,
50}
51
52/// RSDP extended : ACPI 2.0+
53#[repr(C, packed)]
54struct Rsdp2 {
55    base: Rsdp,
56    length: u32,
57    xsdt_address: u64,
58    extended_checksum: u8,
59    _reserved: [u8; 3],
60}
61
62/// Table storage to keep track of discovered ACPI tables
63pub struct AcpiTables {
64    tables: BTreeMap<[u8; 4], Vec<*const Sdt>>,
65}
66
67unsafe impl Send for AcpiTables {}
68unsafe impl Sync for AcpiTables {}
69
70static ACPI_TABLES: SpinLock<AcpiTables> = SpinLock::new(AcpiTables {
71    tables: BTreeMap::new(),
72});
73
74/// ACPI initialization status
75static ACPI_INITIALIZED: AtomicBool = AtomicBool::new(false);
76
77/// Get ACPI revision
78pub fn revision() -> u8 {
79    RSDP_REVISION.load(Ordering::Relaxed) as u8
80}
81
82/// Check if ACPI is initialized
83pub fn is_available() -> bool {
84    ACPI_INITIALIZED.load(Ordering::Relaxed)
85}
86
87/// Get RSDP address
88pub fn rsdp_address() -> u64 {
89    RSDP_VADDR.load(Ordering::Relaxed)
90}
91
92/// Get BGRT table (boot graphics)
93pub fn get_bgrt() -> Option<&'static bgrt::Bgrt> {
94    bgrt::Bgrt::get()
95}
96
97/// Get IVRS table (AMD IOMMU)
98pub fn get_ivrs() -> Option<ivrs::Ivrs> {
99    ivrs::Ivrs::get()
100}
101
102/// Get SLIT table (NUMA distances)
103pub fn get_slit() -> Option<&'static slit::Slit> {
104    slit::Slit::get()
105}
106
107/// Get HPET table
108pub fn get_hpet() -> Option<&'static hpet::HpetAcpiTable> {
109    hpet::HpetAcpiTable::get()
110}
111
112/// Get FADT table
113pub fn get_fadt() -> Option<&'static fadt::Fadt> {
114    fadt::Fadt::get()
115}
116
117/// Get MADT table
118pub fn get_madt() -> Option<&'static madt::MadtAcpiTable> {
119    madt::MadtAcpiTable::get()
120}
121
122/// Get MCFG table
123pub fn get_mcfg() -> Option<&'static mcfg::Mcfg> {
124    mcfg::Mcfg::get()
125}
126
127/// Initialize the ACPI subsystem.
128pub fn init(rsdp_vaddr: u64) -> Result<bool, &'static str> {
129    if rsdp_vaddr == 0 {
130        log::warn!("ACPI: No RSDP provided by bootloader");
131        return Ok(false);
132    }
133
134    let rsdp = rsdp_vaddr as *const Rsdp;
135
136    // Validate signature "RSD PTR "
137    let sig = unsafe { (*rsdp).signature };
138    if &sig != b"RSD PTR " {
139        return Err("ACPI: Invalid RSDP signature");
140    }
141
142    // Validate RSDP checksum (first 20 bytes)
143    if !validate_checksum(rsdp as *const u8, 20) {
144        return Err("ACPI: RSDP checksum failed");
145    }
146
147    let revision = unsafe { (*rsdp).revision };
148
149    // For ACPI 2.0+, validate extended checksum
150    if revision >= 2 {
151        let rsdp2 = rsdp_vaddr as *const Rsdp2;
152        let length = unsafe { (*rsdp2).length } as usize;
153        if !validate_checksum(rsdp as *const u8, length) {
154            return Err("ACPI: RSDP extended checksum failed");
155        }
156    }
157
158    RSDP_VADDR.store(rsdp_vaddr, Ordering::Relaxed);
159    RSDP_REVISION.store(revision as u64, Ordering::Relaxed);
160
161    log::info!("ACPI: RSDP validated (revision {})", revision);
162
163    // Discover all tables via root RSDT/XSDT pointed by RSDP.
164    discover_tables(rsdp_vaddr, revision)?;
165
166    // Mark ACPI as initialized
167    ACPI_INITIALIZED.store(true, Ordering::SeqCst);
168
169    Ok(true)
170}
171
172/// Performs the validate checksum operation.
173fn validate_checksum(ptr: *const u8, len: usize) -> bool {
174    let mut sum: u8 = 0;
175    for i in 0..len {
176        sum = sum.wrapping_add(unsafe { *ptr.add(i) });
177    }
178    sum == 0
179}
180
181/// Performs the discover tables operation.
182fn discover_tables(rsdp_vaddr: u64, revision: u8) -> Result<(), &'static str> {
183    let rxsdt = rsdt::RsdtXsdt::from_rsdp(rsdp_vaddr, revision)
184        .ok_or("ACPI: Failed to find RSDT/XSDT from RSDP")?;
185    let root_sdt = rxsdt.sdt();
186    if root_sdt.length < core::mem::size_of::<Sdt>() as u32 {
187        return Err("ACPI: Root SDT has invalid length");
188    }
189    let root_phys = memory::virt_to_phys(root_sdt as *const Sdt as u64);
190    let root_len = root_sdt.length;
191    memory::paging::ensure_identity_map_range(root_phys, root_len as u64);
192    let root_sig = root_sdt.signature;
193    let root_sig_str = core::str::from_utf8(&root_sig).unwrap_or("????");
194    log::info!(
195        "ACPI: root table {} phys={:#x} len={}",
196        root_sig_str,
197        root_phys,
198        root_len
199    );
200
201    let mut acpi_tables = ACPI_TABLES.lock();
202    let mut discovered = 0usize;
203
204    for sdt_phys in rxsdt.addresses() {
205        if sdt_phys == 0 {
206            continue;
207        }
208
209        let (signature, sdt) = validate_sdt_at_phys(sdt_phys)?;
210
211        // Keep all tables with the same signature (e.g., multiple SSDT).
212        acpi_tables
213            .tables
214            .entry(signature)
215            .or_insert_with(Vec::new)
216            .push(sdt);
217        discovered += 1;
218
219        log::debug!(
220            "ACPI: Discovered table {:?} at phys {:#x}",
221            core::str::from_utf8(&signature).unwrap_or("????"),
222            sdt_phys
223        );
224    }
225
226    let unique = acpi_tables.tables.len();
227    log::info!(
228        "ACPI: discovered {} table entries ({} unique signatures)",
229        discovered,
230        unique
231    );
232
233    Ok(())
234}
235
236/// Performs the validate sdt at phys operation.
237fn validate_sdt_at_phys(sdt_phys: u64) -> Result<([u8; 4], *const Sdt), &'static str> {
238    // Map header first to read SDT length.
239    memory::paging::ensure_identity_map_range(sdt_phys, core::mem::size_of::<Sdt>() as u64);
240    let sdt_virt = memory::phys_to_virt(sdt_phys);
241    let sdt = sdt_virt as *const Sdt;
242    let length = unsafe { (*sdt).length as usize };
243    if length < core::mem::size_of::<Sdt>() {
244        return Err("ACPI: SDT length smaller than header");
245    }
246    memory::paging::ensure_identity_map_range(sdt_phys, length as u64);
247    if !validate_checksum(sdt as *const u8, length) {
248        return Err("ACPI: SDT checksum failed");
249    }
250    let signature = unsafe { (*sdt).signature };
251    Ok((signature, sdt))
252}
253
254/// Find the first ACPI table by its 4-byte signature.
255pub fn find_table(signature: &[u8; 4]) -> Option<*const Sdt> {
256    let acpi_tables = ACPI_TABLES.lock();
257    acpi_tables
258        .tables
259        .get(signature)
260        .and_then(|tables| tables.first().copied())
261}
262
263/// Find all ACPI tables with the given 4-byte signature.
264pub fn find_tables(signature: &[u8; 4]) -> Option<Vec<*const Sdt>> {
265    let acpi_tables = ACPI_TABLES.lock();
266    acpi_tables.tables.get(signature).cloned()
267}
268
269/// Get a typed reference to an ACPI table.
270pub fn get_table<T>(signature: &[u8; 4]) -> Option<&'static T> {
271    let ptr = find_table(signature)?;
272    let sdt = unsafe { &*ptr };
273    if (sdt.length as usize) < core::mem::size_of::<T>() {
274        return None;
275    }
276    Some(unsafe { &*(ptr as *const T) })
277}