RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / acpi.rs
3016 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! 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<T: Copy>(addr: u64) -> T {
    unsafe { core::ptr::read_unaligned(phys_to_virt(PhysAddr::new(addr)).as_ptr::<T>()) }
}

fn bytes(addr: u64, len: usize) -> &'static [u8] {
    unsafe { core::slice::from_raw_parts(phys_to_virt(PhysAddr::new(addr)).as_ptr::<u8>(), 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<u64> {
    let ebda = (read::<u16>(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<Vec<u32>> {
    let rsdp = find_rsdp()?;
    let rsdt = read::<u32>(rsdp + 16) as u64; // works for ACPI 1.0 and 2.0+
    let rsdt_len = read::<u32>(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::<u32>(rsdt + 36 + i * 4) as u64;
        if bytes(table, 4) != b"APIC" {
            continue;
        }
        let len = read::<u32>(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::<u8>(entry), read::<u8>(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::<u32>(entry + 4) & 1 != 0 {
                ids.push(read::<u8>(entry + 3) as u32);
            }
            entry += entry_len;
        }
        return Some(ids);
    }
    None
}