⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/arch/x86_64/
acpi.rs

1//! ACPI table parser for x86_64.
2//!
3//! Parses ACPI tables from UEFI firmware to discover hardware topology:
4//! - MADT (Multiple APIC Description Table): CPU enumeration, I/O APIC, ISA
5//!   overrides
6//! - MCFG (PCI Express config space base addresses)
7//!
8//! Not in scope: Full AML interpreter, ACPI namespace, runtime ACPI methods.
9
10use core::sync::atomic::{AtomicBool, Ordering};
11
12use spin::Mutex;
13
14use crate::error::{KernelError, KernelResult};
15
16// ---------------------------------------------------------------------------
17// ACPI table signatures (4-byte ASCII)
18// ---------------------------------------------------------------------------
19
20const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR ";
21const RSDT_SIGNATURE: &[u8; 4] = b"RSDT";
22const XSDT_SIGNATURE: &[u8; 4] = b"XSDT";
23const MADT_SIGNATURE: &[u8; 4] = b"APIC";
24const MCFG_SIGNATURE: &[u8; 4] = b"MCFG";
25const DMAR_SIGNATURE: &[u8; 4] = b"DMAR";
26const SRAT_SIGNATURE: &[u8; 4] = b"SRAT";
27const SLIT_SIGNATURE: &[u8; 4] = b"SLIT";
28
29// ---------------------------------------------------------------------------
30// MADT entry types
31// ---------------------------------------------------------------------------
32
33const MADT_LOCAL_APIC: u8 = 0;
34const MADT_IO_APIC: u8 = 1;
35const MADT_INTERRUPT_SOURCE_OVERRIDE: u8 = 2;
36const MADT_LOCAL_APIC_NMI: u8 = 4;
37
38// ---------------------------------------------------------------------------
39// Maximum supported entries
40// ---------------------------------------------------------------------------
41
42const MAX_CPUS: usize = 16;
43const MAX_IO_APICS: usize = 4;
44const MAX_ISO: usize = 24;
45const MAX_MCFG_ENTRIES: usize = 4;
46
47// ---------------------------------------------------------------------------
48// Parsed ACPI data structures
49// ---------------------------------------------------------------------------
50
51/// Local APIC entry from the MADT.
52#[derive(Debug, Clone, Copy)]
53pub struct MadtLocalApic {
54    /// ACPI processor UID.
55    pub acpi_processor_id: u8,
56    /// Local APIC ID.
57    pub apic_id: u8,
58    /// Flags (bit 0: processor enabled, bit 1: online capable).
59    pub flags: u32,
60}
61
62impl MadtLocalApic {
63    /// Returns true if this processor is enabled or online-capable.
64    pub fn is_usable(&self) -> bool {
65        (self.flags & 0x01) != 0 || (self.flags & 0x02) != 0
66    }
67}
68
69/// I/O APIC entry from the MADT.
70#[derive(Debug, Clone, Copy)]
71pub struct MadtIoApic {
72    /// I/O APIC ID.
73    pub id: u8,
74    /// Physical base address of the I/O APIC MMIO registers.
75    pub address: u32,
76    /// Global System Interrupt base (first IRQ this I/O APIC handles).
77    pub gsi_base: u32,
78}
79
80/// Interrupt Source Override entry from the MADT.
81///
82/// Maps ISA IRQ numbers to their actual GSI (Global System Interrupt)
83/// numbers with polarity and trigger mode overrides.
84#[derive(Debug, Clone, Copy)]
85pub struct MadtIso {
86    /// Bus source (always 0 = ISA).
87    pub bus: u8,
88    /// ISA IRQ number being overridden.
89    pub irq_source: u8,
90    /// Global System Interrupt number this IRQ maps to.
91    pub gsi: u32,
92    /// Flags: bits 1:0 = polarity, bits 3:2 = trigger mode.
93    pub flags: u16,
94}
95
96impl MadtIso {
97    /// Returns true if active-low polarity.
98    pub fn is_active_low(&self) -> bool {
99        (self.flags & 0x03) == 0x03
100    }
101
102    /// Returns true if level-triggered.
103    pub fn is_level_triggered(&self) -> bool {
104        ((self.flags >> 2) & 0x03) == 0x03
105    }
106}
107
108/// PCIe Enhanced Configuration Mechanism entry from MCFG.
109#[derive(Debug, Clone, Copy)]
110pub struct McfgEntry {
111    /// Base address of the PCIe ECAM region.
112    pub base_address: u64,
113    /// PCI segment group number.
114    pub segment_group: u16,
115    /// Start PCI bus number.
116    pub start_bus: u8,
117    /// End PCI bus number.
118    pub end_bus: u8,
119}
120
121/// Parsed ACPI information, populated by `init()`.
122#[derive(Debug)]
123pub struct AcpiInfo {
124    /// OEM ID from the RSDT/XSDT (6-byte ASCII, null-padded).
125    pub oem_id: [u8; 6],
126    /// Local APIC base address from the MADT.
127    pub local_apic_address: u32,
128    /// MADT flags (bit 0: dual 8259 PIC present).
129    pub madt_flags: u32,
130    /// Local APIC entries (one per CPU).
131    pub local_apics: [Option<MadtLocalApic>; MAX_CPUS],
132    /// Number of valid local APIC entries.
133    pub local_apic_count: usize,
134    /// I/O APIC entries.
135    pub io_apics: [Option<MadtIoApic>; MAX_IO_APICS],
136    /// Number of valid I/O APIC entries.
137    pub io_apic_count: usize,
138    /// Interrupt Source Override entries.
139    pub isos: [Option<MadtIso>; MAX_ISO],
140    /// Number of valid ISO entries.
141    pub iso_count: usize,
142    /// PCIe MCFG entries.
143    pub mcfg_entries: [Option<McfgEntry>; MAX_MCFG_ENTRIES],
144    /// Number of valid MCFG entries.
145    pub mcfg_count: usize,
146    /// Whether MADT was found and parsed.
147    pub has_madt: bool,
148    /// Whether MCFG was found and parsed.
149    pub has_mcfg: bool,
150    /// Whether DMAR (DMA Remapping) table was found.
151    pub has_dmar: bool,
152    /// Physical address of the DMAR table (for IOMMU driver to parse).
153    pub dmar_address: u64,
154    /// Length of the DMAR table in bytes.
155    pub dmar_length: u32,
156    /// ACPI revision (0 = ACPI 1.0, 2 = ACPI 2.0+).
157    pub revision: u8,
158    /// Whether SRAT (System Resource Affinity Table) was found.
159    pub has_srat: bool,
160    /// Virtual address of the SRAT table.
161    pub srat_address: u64,
162    /// Length of the SRAT table in bytes.
163    pub srat_length: u32,
164    /// Whether SLIT (System Locality Information Table) was found.
165    pub has_slit: bool,
166    /// Virtual address of the SLIT table.
167    pub slit_address: u64,
168    /// Length of the SLIT table in bytes.
169    pub slit_length: u32,
170}
171
172impl AcpiInfo {
173    const fn new() -> Self {
174        Self {
175            oem_id: [0; 6],
176            local_apic_address: 0xFEE0_0000, // default
177            madt_flags: 0,
178            local_apics: [None; MAX_CPUS],
179            local_apic_count: 0,
180            io_apics: [None; MAX_IO_APICS],
181            io_apic_count: 0,
182            isos: [None; MAX_ISO],
183            iso_count: 0,
184            mcfg_entries: [None; MAX_MCFG_ENTRIES],
185            mcfg_count: 0,
186            has_madt: false,
187            has_mcfg: false,
188            has_dmar: false,
189            dmar_address: 0,
190            dmar_length: 0,
191            revision: 0,
192            has_srat: false,
193            srat_address: 0,
194            srat_length: 0,
195            has_slit: false,
196            slit_address: 0,
197            slit_length: 0,
198        }
199    }
200
201    /// Get the I/O APIC address (first one, or default 0xFEC0_0000).
202    pub fn io_apic_address(&self) -> u32 {
203        self.io_apics[0].map_or(0xFEC0_0000, |a| a.address)
204    }
205
206    /// Look up the GSI for a given ISA IRQ, applying interrupt source
207    /// overrides.
208    pub fn irq_to_gsi(&self, irq: u8) -> (u32, bool, bool) {
209        for i in 0..self.iso_count {
210            if let Some(ref iso) = self.isos[i] {
211                if iso.irq_source == irq {
212                    return (iso.gsi, iso.is_active_low(), iso.is_level_triggered());
213                }
214            }
215        }
216        // No override: identity map, edge-triggered, active-high
217        (irq as u32, false, false)
218    }
219
220    /// Count usable CPUs.
221    pub fn cpu_count(&self) -> usize {
222        let mut count = 0;
223        for i in 0..self.local_apic_count {
224            if let Some(ref lapic) = self.local_apics[i] {
225                if lapic.is_usable() {
226                    count += 1;
227                }
228            }
229        }
230        count
231    }
232}
233
234// ---------------------------------------------------------------------------
235// Global state
236// ---------------------------------------------------------------------------
237
238static ACPI_INITIALIZED: AtomicBool = AtomicBool::new(false);
239static ACPI_INFO: Mutex<Option<AcpiInfo>> = Mutex::new(None);
240
241/// Check whether ACPI tables have been parsed.
242pub fn is_initialized() -> bool {
243    ACPI_INITIALIZED.load(Ordering::Acquire)
244}
245
246/// Access the parsed ACPI info. Returns None if not initialized.
247pub fn with_acpi_info<R, F: FnOnce(&AcpiInfo) -> R>(f: F) -> Option<R> {
248    let guard = ACPI_INFO.lock();
249    guard.as_ref().map(f)
250}
251
252// ---------------------------------------------------------------------------
253// RSDP structures
254// ---------------------------------------------------------------------------
255
256/// RSDP (Root System Description Pointer) for ACPI 1.0.
257#[repr(C, packed)]
258struct Rsdp {
259    signature: [u8; 8],
260    checksum: u8,
261    oem_id: [u8; 6],
262    revision: u8,
263    rsdt_address: u32,
264}
265
266/// Extended RSDP for ACPI 2.0+.
267#[repr(C, packed)]
268struct Rsdp2 {
269    base: Rsdp,
270    length: u32,
271    xsdt_address: u64,
272    extended_checksum: u8,
273    _reserved: [u8; 3],
274}
275
276/// Standard ACPI table header (present at the start of every ACPI table).
277#[repr(C, packed)]
278struct AcpiSdtHeader {
279    signature: [u8; 4],
280    length: u32,
281    revision: u8,
282    checksum: u8,
283    oem_id: [u8; 6],
284    oem_table_id: [u8; 8],
285    oem_revision: u32,
286    creator_id: u32,
287    creator_revision: u32,
288}
289
290/// MADT header (follows standard header).
291#[repr(C, packed)]
292struct MadtHeader {
293    sdt: AcpiSdtHeader,
294    local_apic_address: u32,
295    flags: u32,
296}
297
298/// MADT entry header (2 bytes: type + length).
299#[repr(C, packed)]
300struct MadtEntryHeader {
301    entry_type: u8,
302    length: u8,
303}
304
305/// MADT Local APIC entry (type 0).
306#[repr(C, packed)]
307struct MadtLocalApicEntry {
308    header: MadtEntryHeader,
309    acpi_processor_id: u8,
310    apic_id: u8,
311    flags: u32,
312}
313
314/// MADT I/O APIC entry (type 1).
315#[repr(C, packed)]
316struct MadtIoApicEntry {
317    header: MadtEntryHeader,
318    id: u8,
319    _reserved: u8,
320    address: u32,
321    gsi_base: u32,
322}
323
324/// MADT Interrupt Source Override entry (type 2).
325#[repr(C, packed)]
326struct MadtIsoEntry {
327    header: MadtEntryHeader,
328    bus: u8,
329    source: u8,
330    gsi: u32,
331    flags: u16,
332}
333
334/// MADT Local APIC NMI entry (type 4).
335#[repr(C, packed)]
336struct MadtLocalApicNmiEntry {
337    header: MadtEntryHeader,
338    acpi_processor_id: u8,
339    flags: u16,
340    lint: u8,
341}
342
343/// MCFG entry (one per PCI segment group).
344#[repr(C, packed)]
345struct McfgAllocation {
346    base_address: u64,
347    segment_group: u16,
348    start_bus: u8,
349    end_bus: u8,
350    _reserved: u32,
351}
352
353// ---------------------------------------------------------------------------
354// Checksum validation
355// ---------------------------------------------------------------------------
356
357/// Validate ACPI table checksum. All bytes must sum to zero (mod 256).
358fn validate_checksum(addr: usize, len: usize) -> bool {
359    let mut sum: u8 = 0;
360    for i in 0..len {
361        let byte_addr = match addr.checked_add(i) {
362            Some(a) => a,
363            None => return false,
364        };
365        // SAFETY: addr..addr+len is within a valid ACPI table that was mapped
366        // by the bootloader's physical memory mapping. We read individual bytes.
367        sum = sum.wrapping_add(unsafe { *(byte_addr as *const u8) });
368    }
369    sum == 0
370}
371
372// ---------------------------------------------------------------------------
373// Table parsing
374// ---------------------------------------------------------------------------
375
376/// Parse the MADT (Multiple APIC Description Table).
377fn parse_madt(header_vaddr: usize, info: &mut AcpiInfo) {
378    // SAFETY: header_vaddr points to a valid MADT table mapped by the
379    // bootloader's physical memory offset. The packed struct layout matches
380    // the ACPI specification.
381    let madt = unsafe { &*(header_vaddr as *const MadtHeader) };
382    let table_len = { madt.sdt.length } as usize;
383
384    info.local_apic_address = madt.local_apic_address;
385    info.madt_flags = madt.flags;
386    info.has_madt = true;
387
388    println!(
389        "[ACPI] MADT: LAPIC addr={:#x}, flags={:#x}, len={}",
390        info.local_apic_address, info.madt_flags, table_len
391    );
392
393    // Walk the variable-length entries after the fixed MADT header.
394    let entries_start = header_vaddr + core::mem::size_of::<MadtHeader>();
395    let entries_end = header_vaddr + table_len;
396    let mut offset = entries_start;
397
398    while offset + 2 <= entries_end {
399        // SAFETY: offset points within the MADT table bounds. The entry
400        // header is 2 bytes (type + length).
401        let entry_header = unsafe { &*(offset as *const MadtEntryHeader) };
402        let entry_len = entry_header.length as usize;
403
404        let next_offset = match offset.checked_add(entry_len) {
405            Some(n) => n,
406            None => break,
407        };
408        if entry_len < 2 || next_offset > entries_end {
409            break;
410        }
411
412        match entry_header.entry_type {
413            MADT_LOCAL_APIC => {
414                if entry_len >= core::mem::size_of::<MadtLocalApicEntry>()
415                    && info.local_apic_count < MAX_CPUS
416                {
417                    // SAFETY: Entry type 0 has the MadtLocalApicEntry layout
418                    // and we verified the length is sufficient.
419                    let entry = unsafe { &*(offset as *const MadtLocalApicEntry) };
420                    let lapic = MadtLocalApic {
421                        acpi_processor_id: entry.acpi_processor_id,
422                        apic_id: entry.apic_id,
423                        flags: { entry.flags },
424                    };
425                    println!(
426                        "[ACPI]   CPU: proc_id={}, apic_id={}, flags={:#x}{}",
427                        lapic.acpi_processor_id,
428                        lapic.apic_id,
429                        lapic.flags,
430                        if lapic.is_usable() {
431                            " [usable]"
432                        } else {
433                            " [disabled]"
434                        }
435                    );
436                    info.local_apics[info.local_apic_count] = Some(lapic);
437                    info.local_apic_count += 1;
438                }
439            }
440            MADT_IO_APIC => {
441                if entry_len >= core::mem::size_of::<MadtIoApicEntry>()
442                    && info.io_apic_count < MAX_IO_APICS
443                {
444                    // SAFETY: Entry type 1 has the MadtIoApicEntry layout.
445                    let entry = unsafe { &*(offset as *const MadtIoApicEntry) };
446                    let ioapic = MadtIoApic {
447                        id: entry.id,
448                        address: { entry.address },
449                        gsi_base: { entry.gsi_base },
450                    };
451                    println!(
452                        "[ACPI]   I/O APIC: id={}, addr={:#x}, gsi_base={}",
453                        ioapic.id, ioapic.address, ioapic.gsi_base
454                    );
455                    info.io_apics[info.io_apic_count] = Some(ioapic);
456                    info.io_apic_count += 1;
457                }
458            }
459            MADT_INTERRUPT_SOURCE_OVERRIDE => {
460                if entry_len >= core::mem::size_of::<MadtIsoEntry>() && info.iso_count < MAX_ISO {
461                    // SAFETY: Entry type 2 has the MadtIsoEntry layout.
462                    let entry = unsafe { &*(offset as *const MadtIsoEntry) };
463                    let iso = MadtIso {
464                        bus: entry.bus,
465                        irq_source: entry.source,
466                        gsi: { entry.gsi },
467                        flags: { entry.flags },
468                    };
469                    println!(
470                        "[ACPI]   ISO: bus={}, irq={} -> gsi={}, flags={:#x}",
471                        iso.bus, iso.irq_source, iso.gsi, iso.flags
472                    );
473                    info.isos[info.iso_count] = Some(iso);
474                    info.iso_count += 1;
475                }
476            }
477            MADT_LOCAL_APIC_NMI => {
478                if entry_len >= core::mem::size_of::<MadtLocalApicNmiEntry>() {
479                    // SAFETY: Entry type 4 has the MadtLocalApicNmiEntry layout.
480                    let entry = unsafe { &*(offset as *const MadtLocalApicNmiEntry) };
481                    println!(
482                        "[ACPI]   LAPIC NMI: proc_id={}, flags={:#x}, lint={}",
483                        entry.acpi_processor_id,
484                        { entry.flags },
485                        entry.lint
486                    );
487                }
488            }
489            other => {
490                println!(
491                    "[ACPI]   Unknown MADT entry type {} (len={})",
492                    other, entry_len
493                );
494            }
495        }
496
497        offset += entry_len;
498    }
499
500    println!(
501        "[ACPI] MADT summary: {} CPUs ({} usable), {} I/O APICs, {} ISOs",
502        info.local_apic_count,
503        info.cpu_count(),
504        info.io_apic_count,
505        info.iso_count
506    );
507}
508
509/// Parse the MCFG (PCI Express Memory Mapped Configuration) table.
510fn parse_mcfg(header_vaddr: usize, info: &mut AcpiInfo) {
511    // SAFETY: header_vaddr points to a valid MCFG table.
512    let sdt = unsafe { &*(header_vaddr as *const AcpiSdtHeader) };
513    let table_len = { sdt.length } as usize;
514    let header_size = core::mem::size_of::<AcpiSdtHeader>() + 8; // 8 reserved bytes
515
516    if table_len <= header_size {
517        println!("[ACPI] MCFG: no allocation entries");
518        return;
519    }
520
521    info.has_mcfg = true;
522    let entries_start = header_vaddr + header_size;
523    let entry_size = core::mem::size_of::<McfgAllocation>();
524    let num_entries = (table_len - header_size) / entry_size;
525
526    println!("[ACPI] MCFG: {} allocation entries", num_entries);
527
528    for i in 0..num_entries {
529        if info.mcfg_count >= MAX_MCFG_ENTRIES {
530            break;
531        }
532        let entry_addr = entries_start + i * entry_size;
533        // SAFETY: entry_addr points within the MCFG table bounds.
534        let entry = unsafe { &*(entry_addr as *const McfgAllocation) };
535        let mcfg = McfgEntry {
536            base_address: { entry.base_address },
537            segment_group: { entry.segment_group },
538            start_bus: entry.start_bus,
539            end_bus: entry.end_bus,
540        };
541        println!(
542            "[ACPI]   ECAM: base={:#x}, seg={}, bus={}..{}",
543            mcfg.base_address, mcfg.segment_group, mcfg.start_bus, mcfg.end_bus
544        );
545        info.mcfg_entries[info.mcfg_count] = Some(mcfg);
546        info.mcfg_count += 1;
547    }
548}
549
550/// Parse a single ACPI table identified by its header.
551fn parse_table(header_vaddr: usize, info: &mut AcpiInfo) {
552    // SAFETY: header_vaddr points to a valid ACPI SDT header.
553    let sdt = unsafe { &*(header_vaddr as *const AcpiSdtHeader) };
554    let sig = { sdt.signature };
555    let len = { sdt.length } as usize;
556
557    // Validate checksum
558    if !validate_checksum(header_vaddr, len) {
559        println!(
560            "[ACPI] WARNING: bad checksum for table {:?}",
561            core::str::from_utf8(&sig).unwrap_or("????")
562        );
563        // Continue anyway -- some BIOS/firmware have incorrect checksums
564    }
565
566    if &sig == MADT_SIGNATURE {
567        parse_madt(header_vaddr, info);
568    } else if &sig == MCFG_SIGNATURE {
569        parse_mcfg(header_vaddr, info);
570    } else if &sig == DMAR_SIGNATURE {
571        // Record DMAR presence and location for the IOMMU driver to parse.
572        // The DMAR table has a complex structure (DRHD, RMRR, ATSR entries)
573        // that the IOMMU driver handles directly.
574        info.has_dmar = true;
575        // Store the virtual address for the IOMMU driver to read directly.
576        info.dmar_address = header_vaddr as u64;
577        info.dmar_length = len as u32;
578        println!("[ACPI]   DMAR table found (len={})", len);
579    } else if &sig == SRAT_SIGNATURE {
580        info.has_srat = true;
581        info.srat_address = header_vaddr as u64;
582        info.srat_length = len as u32;
583        println!("[ACPI]   SRAT table found (len={})", len);
584    } else if &sig == SLIT_SIGNATURE {
585        info.has_slit = true;
586        info.slit_address = header_vaddr as u64;
587        info.slit_length = len as u32;
588        println!("[ACPI]   SLIT table found (len={})", len);
589    } else {
590        // Log but skip other tables
591        let sig_str = core::str::from_utf8(&sig).unwrap_or("????");
592        println!("[ACPI]   Table '{}' (len={}) -- skipped", sig_str, len);
593    }
594}
595
596/// Parse the RSDT (Root System Description Table, 32-bit pointers).
597fn parse_rsdt(rsdt_vaddr: usize, info: &mut AcpiInfo) -> KernelResult<()> {
598    // SAFETY: rsdt_vaddr points to the RSDT mapped via phys_to_virt.
599    let sdt = unsafe { &*(rsdt_vaddr as *const AcpiSdtHeader) };
600    let len = { sdt.length } as usize;
601
602    if &{ sdt.signature } != RSDT_SIGNATURE {
603        return Err(KernelError::InvalidArgument {
604            name: "RSDT signature",
605            value: "not RSDT",
606        });
607    }
608
609    if !validate_checksum(rsdt_vaddr, len) {
610        println!("[ACPI] WARNING: RSDT checksum invalid");
611    }
612
613    // Copy OEM ID
614    info.oem_id = sdt.oem_id;
615
616    let header_size = core::mem::size_of::<AcpiSdtHeader>();
617    let num_entries = (len - header_size) / 4; // 32-bit pointers
618
619    println!(
620        "[ACPI] RSDT at {:#x}: {} child tables, OEM='{}'",
621        rsdt_vaddr,
622        num_entries,
623        core::str::from_utf8(&info.oem_id).unwrap_or("??????")
624    );
625
626    for i in 0..num_entries {
627        let ptr_addr = rsdt_vaddr + header_size + i * 4;
628        // SAFETY: ptr_addr is within the RSDT bounds, reading a 32-bit physical
629        // pointer.
630        let phys_addr = unsafe { *(ptr_addr as *const u32) } as usize;
631        if let Some(vaddr) = super::msr::phys_to_virt(phys_addr) {
632            parse_table(vaddr, info);
633        }
634    }
635
636    Ok(())
637}
638
639/// Parse the XSDT (Extended System Description Table, 64-bit pointers).
640fn parse_xsdt(xsdt_vaddr: usize, info: &mut AcpiInfo) -> KernelResult<()> {
641    // SAFETY: xsdt_vaddr points to the XSDT mapped via phys_to_virt.
642    let sdt = unsafe { &*(xsdt_vaddr as *const AcpiSdtHeader) };
643    let len = { sdt.length } as usize;
644
645    if &{ sdt.signature } != XSDT_SIGNATURE {
646        return Err(KernelError::InvalidArgument {
647            name: "XSDT signature",
648            value: "not XSDT",
649        });
650    }
651
652    if !validate_checksum(xsdt_vaddr, len) {
653        println!("[ACPI] WARNING: XSDT checksum invalid");
654    }
655
656    // Copy OEM ID
657    info.oem_id = sdt.oem_id;
658
659    let header_size = core::mem::size_of::<AcpiSdtHeader>();
660    let num_entries = (len - header_size) / 8; // 64-bit pointers
661
662    println!(
663        "[ACPI] XSDT at {:#x}: {} child tables, OEM='{}'",
664        xsdt_vaddr,
665        num_entries,
666        core::str::from_utf8(&info.oem_id).unwrap_or("??????")
667    );
668
669    for i in 0..num_entries {
670        let ptr_addr = xsdt_vaddr + header_size + i * 8;
671        // SAFETY: ptr_addr is within the XSDT bounds, reading a 64-bit physical
672        // pointer.
673        let phys_addr = unsafe { *(ptr_addr as *const u64) } as usize;
674        if let Some(vaddr) = super::msr::phys_to_virt(phys_addr) {
675            parse_table(vaddr, info);
676        }
677    }
678
679    Ok(())
680}
681
682// ---------------------------------------------------------------------------
683// Public API
684// ---------------------------------------------------------------------------
685
686/// Initialize the ACPI subsystem by parsing tables from the RSDP address
687/// provided by the UEFI bootloader.
688///
689/// Must be called after memory management is initialized (physical memory
690/// mapping available via `phys_to_virt`).
691pub fn init() -> KernelResult<()> {
692    if ACPI_INITIALIZED.load(Ordering::Acquire) {
693        return Err(KernelError::AlreadyExists {
694            resource: "ACPI",
695            id: 0,
696        });
697    }
698
699    println!("[ACPI] Initializing ACPI table parser...");
700
701    // Get RSDP physical address from bootloader BootInfo.
702    // SAFETY: BOOT_INFO is a static mut written once during early boot and
703    // read-only afterwards. We are in single-threaded kernel init context.
704    #[allow(static_mut_refs)]
705    let rsdp_phys = unsafe {
706        super::boot::BOOT_INFO
707            .as_ref()
708            .and_then(|bi| bi.rsdp_addr.into_option())
709    };
710
711    let rsdp_phys = match rsdp_phys {
712        Some(addr) => addr as usize,
713        None => {
714            println!("[ACPI] No RSDP address from bootloader, ACPI unavailable");
715            return Err(KernelError::NotInitialized {
716                subsystem: "ACPI (no RSDP)",
717            });
718        }
719    };
720
721    println!("[ACPI] RSDP physical address: {:#x}", rsdp_phys);
722
723    // Map RSDP to virtual address.
724    let rsdp_vaddr = super::msr::phys_to_virt(rsdp_phys).ok_or(KernelError::NotInitialized {
725        subsystem: "ACPI (phys_to_virt)",
726    })?;
727
728    // Validate RSDP signature.
729    // SAFETY: rsdp_vaddr points to a valid RSDP structure mapped by the
730    // bootloader's physical memory mapping.
731    let rsdp = unsafe { &*(rsdp_vaddr as *const Rsdp) };
732    if &rsdp.signature != RSDP_SIGNATURE {
733        println!("[ACPI] Invalid RSDP signature");
734        return Err(KernelError::InvalidArgument {
735            name: "RSDP signature",
736            value: "not 'RSD PTR '",
737        });
738    }
739
740    // Validate RSDP checksum (first 20 bytes for ACPI 1.0).
741    if !validate_checksum(rsdp_vaddr, 20) {
742        println!("[ACPI] WARNING: RSDP checksum invalid");
743    }
744
745    let mut info = AcpiInfo::new();
746    info.revision = rsdp.revision;
747
748    println!(
749        "[ACPI] RSDP: revision={}, OEM='{}'",
750        rsdp.revision,
751        core::str::from_utf8(&rsdp.oem_id).unwrap_or("??????")
752    );
753
754    // ACPI 2.0+ has XSDT (64-bit pointers); fall back to RSDT (32-bit).
755    if rsdp.revision >= 2 {
756        // SAFETY: ACPI 2.0 RSDP is at least 36 bytes. We read the extended
757        // XSDT address field.
758        let rsdp2 = unsafe { &*(rsdp_vaddr as *const Rsdp2) };
759        let xsdt_phys = { rsdp2.xsdt_address } as usize;
760
761        if xsdt_phys != 0 {
762            if let Some(xsdt_vaddr) = super::msr::phys_to_virt(xsdt_phys) {
763                println!("[ACPI] Using XSDT at phys={:#x}", xsdt_phys);
764                parse_xsdt(xsdt_vaddr, &mut info)?;
765            } else {
766                println!("[ACPI] Cannot map XSDT, falling back to RSDT");
767                let rsdt_phys = { rsdp.rsdt_address } as usize;
768                if let Some(rsdt_vaddr) = super::msr::phys_to_virt(rsdt_phys) {
769                    parse_rsdt(rsdt_vaddr, &mut info)?;
770                }
771            }
772        } else {
773            // XSDT address is zero -- use RSDT
774            let rsdt_phys = { rsdp.rsdt_address } as usize;
775            if let Some(rsdt_vaddr) = super::msr::phys_to_virt(rsdt_phys) {
776                println!(
777                    "[ACPI] XSDT addr is zero, using RSDT at phys={:#x}",
778                    rsdt_phys
779                );
780                parse_rsdt(rsdt_vaddr, &mut info)?;
781            }
782        }
783    } else {
784        // ACPI 1.0 -- RSDT only
785        let rsdt_phys = { rsdp.rsdt_address } as usize;
786        if let Some(rsdt_vaddr) = super::msr::phys_to_virt(rsdt_phys) {
787            println!("[ACPI] Using RSDT at phys={:#x}", rsdt_phys);
788            parse_rsdt(rsdt_vaddr, &mut info)?;
789        }
790    }
791
792    // Provide defaults if no MADT found (QEMU always provides one, but be safe).
793    if !info.has_madt {
794        println!("[ACPI] No MADT found, using defaults (1 CPU, LAPIC at 0xFEE00000)");
795        info.local_apic_address = 0xFEE0_0000;
796        info.local_apics[0] = Some(MadtLocalApic {
797            acpi_processor_id: 0,
798            apic_id: 0,
799            flags: 1,
800        });
801        info.local_apic_count = 1;
802    }
803
804    println!(
805        "[ACPI] Initialization complete: {} CPUs, {} I/O APICs, MADT={}, MCFG={}",
806        info.cpu_count(),
807        info.io_apic_count,
808        info.has_madt,
809        info.has_mcfg
810    );
811
812    *ACPI_INFO.lock() = Some(info);
813    ACPI_INITIALIZED.store(true, Ordering::Release);
814    Ok(())
815}
816
817/// Dump parsed ACPI information to the console (for the `acpi` shell command).
818pub fn dump() {
819    let guard = ACPI_INFO.lock();
820    let info = match guard.as_ref() {
821        Some(info) => info,
822        None => {
823            println!("ACPI not initialized");
824            return;
825        }
826    };
827
828    println!("=== ACPI Information ===");
829    println!(
830        "  Revision: {} (ACPI {}.0)",
831        info.revision,
832        if info.revision >= 2 { "2" } else { "1" }
833    );
834    println!(
835        "  OEM: '{}'",
836        core::str::from_utf8(&info.oem_id).unwrap_or("??????")
837    );
838    println!("  Local APIC Address: {:#x}", info.local_apic_address);
839    println!(
840        "  MADT Flags: {:#x} (dual 8259: {})",
841        info.madt_flags,
842        if info.madt_flags & 1 != 0 {
843            "yes"
844        } else {
845            "no"
846        }
847    );
848
849    println!("\n--- CPUs ({}) ---", info.local_apic_count);
850    for i in 0..info.local_apic_count {
851        if let Some(ref lapic) = info.local_apics[i] {
852            println!(
853                "  CPU {}: APIC ID={}, proc_id={}, flags={:#x} {}",
854                i,
855                lapic.apic_id,
856                lapic.acpi_processor_id,
857                lapic.flags,
858                if lapic.is_usable() {
859                    "[usable]"
860                } else {
861                    "[disabled]"
862                }
863            );
864        }
865    }
866
867    println!("\n--- I/O APICs ({}) ---", info.io_apic_count);
868    for i in 0..info.io_apic_count {
869        if let Some(ref ioapic) = info.io_apics[i] {
870            println!(
871                "  I/O APIC {}: ID={}, addr={:#x}, GSI base={}",
872                i, ioapic.id, ioapic.address, ioapic.gsi_base
873            );
874        }
875    }
876
877    if info.iso_count > 0 {
878        println!("\n--- Interrupt Source Overrides ({}) ---", info.iso_count);
879        for i in 0..info.iso_count {
880            if let Some(ref iso) = info.isos[i] {
881                println!(
882                    "  IRQ {} -> GSI {}, flags={:#x} (active_low={}, level={})",
883                    iso.irq_source,
884                    iso.gsi,
885                    iso.flags,
886                    iso.is_active_low(),
887                    iso.is_level_triggered()
888                );
889            }
890        }
891    }
892
893    if info.has_mcfg {
894        println!("\n--- PCIe ECAM ({}) ---", info.mcfg_count);
895        for i in 0..info.mcfg_count {
896            if let Some(ref mcfg) = info.mcfg_entries[i] {
897                println!(
898                    "  Segment {}: base={:#x}, bus={}..{}",
899                    mcfg.segment_group, mcfg.base_address, mcfg.start_bus, mcfg.end_bus
900                );
901            }
902        }
903    } else {
904        println!("\n--- PCIe ECAM: not available ---");
905    }
906
907    println!(
908        "\nSummary: {} usable CPUs, {} I/O APICs, {} ISOs, {} MCFG entries",
909        info.cpu_count(),
910        info.io_apic_count,
911        info.iso_count,
912        info.mcfg_count
913    );
914}
915
916/// Find SRAT table data. Returns a slice of the raw SRAT table if present.
917pub fn find_srat() -> Option<&'static [u8]> {
918    with_acpi_info(|info| {
919        if !info.has_srat || info.srat_address == 0 {
920            return None;
921        }
922        let addr = info.srat_address as usize;
923        let len = info.srat_length as usize;
924        // SAFETY: srat_address was captured from a valid ACPI table mapped by
925        // the bootloader's physical memory mapping. The table remains in memory
926        // for the kernel's lifetime.
927        Some(unsafe { core::slice::from_raw_parts(addr as *const u8, len) })
928    })
929    .flatten()
930}
931
932/// Find SLIT table data. Returns a slice of the raw SLIT table if present.
933pub fn find_slit() -> Option<&'static [u8]> {
934    with_acpi_info(|info| {
935        if !info.has_slit || info.slit_address == 0 {
936            return None;
937        }
938        let addr = info.slit_address as usize;
939        let len = info.slit_length as usize;
940        // SAFETY: slit_address was captured from a valid ACPI table mapped by
941        // the bootloader's physical memory mapping. The table remains in memory
942        // for the kernel's lifetime.
943        Some(unsafe { core::slice::from_raw_parts(addr as *const u8, len) })
944    })
945    .flatten()
946}
947
948/// Find MADT CPU topology data.
949///
950/// Returns a vector of (apic_id, acpi_processor_id, is_usable) tuples.
951pub fn find_madt_cpus() -> Option<alloc::vec::Vec<(u32, u32, bool)>> {
952    with_acpi_info(|info| {
953        if !info.has_madt {
954            return None;
955        }
956        let mut cpus = alloc::vec::Vec::new();
957        for i in 0..info.local_apic_count {
958            if let Some(ref lapic) = info.local_apics[i] {
959                cpus.push((
960                    lapic.apic_id as u32,
961                    lapic.acpi_processor_id as u32,
962                    lapic.is_usable(),
963                ));
964            }
965        }
966        Some(cpus)
967    })
968    .flatten()
969}