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