//! Just enough ACPI to find the CPUs. //! //! The firmware describes the machine in ACPI tables. We follow the chain: //! //! RSDP ("RSD PTR ", found by scanning the BIOS area) //! -> RSDT (Root System Description Table: a list of table addresses) //! -> MADT (signature "APIC": one entry per interrupt controller) //! //! Each MADT entry of type 0 is a CPU's local APIC; we collect the APIC IDs //! of the enabled ones. That's the list of CPUs smp.rs will start. use crate::memory::phys_to_virt; use alloc::vec::Vec; use x86_64::PhysAddr; /// Read a value of type T at physical address `addr`. fn read(addr: u64) -> T { unsafe { core::ptr::read_unaligned(phys_to_virt(PhysAddr::new(addr)).as_ptr::()) } } fn bytes(addr: u64, len: usize) -> &'static [u8] { unsafe { core::slice::from_raw_parts(phys_to_virt(PhysAddr::new(addr)).as_ptr::(), len) } } /// ACPI tables are valid if all their bytes add up to 0 (mod 256). fn checksum_ok(addr: u64, len: usize) -> bool { bytes(addr, len).iter().fold(0u8, |sum, b| sum.wrapping_add(*b)) == 0 } /// Find the RSDP: in the first KiB of the EBDA (Extended BIOS Data Area) /// or in the BIOS ROM area 0xE0000-0xFFFFF, on a 16-byte boundary. fn find_rsdp() -> Option { let ebda = (read::(0x40E) as u64) << 4; let areas = [(ebda, ebda + 1024), (0xE0000, 0x100000)]; for (start, end) in areas { if start == 0 { continue; } for addr in (start..end).step_by(16) { if bytes(addr, 8) == b"RSD PTR " && checksum_ok(addr, 20) { return Some(addr); } } } None } /// The APIC IDs of all enabled CPUs, or None if there's no usable MADT. pub fn cpu_apic_ids() -> Option> { let rsdp = find_rsdp()?; let rsdt = read::(rsdp + 16) as u64; // works for ACPI 1.0 and 2.0+ let rsdt_len = read::(rsdt + 4) as u64; // Table header is 36 bytes, then 4-byte physical addresses. for i in 0..(rsdt_len - 36) / 4 { let table = read::(rsdt + 36 + i * 4) as u64; if bytes(table, 4) != b"APIC" { continue; } let len = read::(table + 4) as u64; let mut ids = Vec::new(); // MADT: header (36) + local APIC address (4) + flags (4), then // variable-length entries: [type, length, ...]. let mut entry = table + 44; while entry + 2 <= table + len { let (kind, entry_len) = (read::(entry), read::(entry + 1) as u64); if entry_len < 2 { break; // broken table: stop rather than loop forever } // Type 0 = processor local APIC: [0, 8, acpi id, apic id, flags u32] // flags bit 0 = enabled, bit 1 = "can be enabled later". if kind == 0 && read::(entry + 4) & 1 != 0 { ids.push(read::(entry + 3) as u32); } entry += entry_len; } return Some(ids); } None }