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