add device platform module with ACPI support
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
# ACPI: finding the tables (RSDP → RSDT/XSDT → SDTs)
|
||||
|
||||
ACPI describes the hardware the kernel can't assume — the interrupt controllers, the
|
||||
PCIe config window, the timer, the power registers — in a set of **system
|
||||
description tables** (SDTs). But before it can read any of them, danos has to *find*
|
||||
them, and they aren't at a fixed address. Getting there is a short chain of pointers,
|
||||
and this note explains it — in particular the question it's easy to trip on: **how
|
||||
does the [platform / device module](arch.md) know where the RSDT is?**
|
||||
|
||||
Short answer: it doesn't receive the RSDT. The firmware hands over the **RSDP**, and
|
||||
the RSDT's address is a field *inside* the RSDP. The platform follows that pointer.
|
||||
|
||||
## The locator chain
|
||||
|
||||
```
|
||||
UEFI configuration table
|
||||
│ the loader reads the RSDP's physical address
|
||||
▼
|
||||
BootInfo.acpi_rsdp (u64, in the shared `danos` module) src/root.zig
|
||||
│ the kernel forwards the whole BootInfo
|
||||
▼
|
||||
platform.discover(boot_info, …) src/device/platform.zig
|
||||
│ reads boot_info.acpi_rsdp, hands it to the ACPI backend
|
||||
▼
|
||||
acpi.discover(rsdp_phys, …) src/device/acpi.zig
|
||||
│ dereferences the RSDP, reads the pointer it contains
|
||||
▼
|
||||
RSDP ──(a field in the struct)──► RSDT / XSDT ──► SDTs (MADT, MCFG, FADT, HPET, DSDT…)
|
||||
```
|
||||
|
||||
The **RSDP** (Root System Description Pointer) is the root of the whole ACPI tree.
|
||||
Its only job is to point at the root *table* — the **RSDT** (ACPI 1.0) or its 64-bit
|
||||
successor the **XSDT** (ACPI 2.0+) — which in turn lists every other SDT.
|
||||
|
||||
## Step 1 — the loader finds the RSDP
|
||||
|
||||
Only the firmware knows where ACPI lives, so the RSDP must be grabbed while UEFI is
|
||||
still up. `acpiRootSystemDescriptorPointer()` in `src/boot/efi.zig` walks the UEFI
|
||||
**configuration table** for the ACPI GUID and returns the vendor pointer — the same
|
||||
"grab it before `ExitBootServices`" pattern as the [framebuffer](framebuffer.md) and
|
||||
the [memory map](memory-map.md).
|
||||
|
||||
## Step 2 — the handoff: a physical address in `BootInfo`
|
||||
|
||||
The loader can't just call the device module: the bootloader binary and the kernel
|
||||
binary are compiled separately, and **the loader isn't linked against the `platform`
|
||||
module at all** (it imports only the shared `danos` module). So instead of a call, it
|
||||
deposits a value in the handoff struct:
|
||||
|
||||
```zig
|
||||
// src/boot/efi.zig — while boot services are still up
|
||||
.acpi_rsdp = if (acpiRootSystemDescriptorPointer()) |p| @intFromPtr(p) else 0,
|
||||
```
|
||||
|
||||
Two things about what crosses the boundary:
|
||||
|
||||
- **It's a *physical* address, not a Zig pointer.** The loader and kernel don't share
|
||||
an address space at the moment of the jump, so a raw `u64` physical address is the
|
||||
only thing that survives the handoff. `BootInfo.acpi_rsdp` is `0` when the firmware
|
||||
exposed no ACPI (e.g. a future device-tree machine, which would fill a different
|
||||
field instead — the kernel never learns which firmware booted it).
|
||||
- **The kernel can dereference it because it identity-maps ACPI memory.** The RSDP
|
||||
lives in ACPI-reclaim memory, which [paging.zig](paging.md) identity-maps along with
|
||||
the rest of RAM, so by the time discovery runs `@ptrFromInt(rsdp_phys)` is a valid
|
||||
pointer.
|
||||
|
||||
This is the concrete form of the "capture the description pointer" step sketched in
|
||||
[discovery.md](discovery.md) — a plain `acpi_rsdp: u64` rather than a tagged handle,
|
||||
since x86 is the only backend wired up so far.
|
||||
|
||||
## Step 3 — the platform derives the RSDT from the RSDP
|
||||
|
||||
`acpi.discover` reinterprets the physical address as the RSDP struct, validates it,
|
||||
and then reads the root-table pointer *out of it*. Which pointer depends on the ACPI
|
||||
version, because the RSDP carries **both**:
|
||||
|
||||
```zig
|
||||
const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(rsdp_phys);
|
||||
if (!std.mem.eql(u8, &rsdp.signature, "RSD PTR ")) return error.BadRsdpSignature;
|
||||
if (!checksumOk(@ptrFromInt(rsdp_phys), 20)) return error.BadRsdpChecksum;
|
||||
|
||||
if (rsdp.revision >= 2) {
|
||||
// ACPI 2.0+: use the 64-bit XSDT pointer (the 32-bit RSDT is deprecated)
|
||||
const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(rsdp_phys);
|
||||
try walkRoot(u64, xsdp.extended_system_descriptor_table_address, …);
|
||||
} else {
|
||||
// ACPI 1.0: use the 32-bit RSDT pointer
|
||||
try walkRoot(u32, rsdp.root_system_description_table_address, …);
|
||||
}
|
||||
```
|
||||
|
||||
- The **`revision`** byte selects the root table. `root_system_description_table_address`
|
||||
(32-bit, → RSDT) and `extended_system_descriptor_table_address` (64-bit, → XSDT) are
|
||||
ordinary fields of the RSDP/XSDP structs — the platform never *receives* the RSDT
|
||||
address, it *reads* it here. On QEMU q35 the RSDP is revision 2, so the XSDT path is
|
||||
taken.
|
||||
- The signature (`"RSD PTR "`) and one-byte checksum guard against a bad pointer before
|
||||
anything downstream trusts it.
|
||||
|
||||
## After the root table
|
||||
|
||||
`walkRoot` treats the RSDT/XSDT as an array of physical pointers — 32-bit entries for
|
||||
the RSDT, 64-bit for the XSDT — and hands each SDT to `handleTable`, which dispatches
|
||||
on its 4-byte signature: **MADT** (CPUs + IOAPIC), **MCFG** (PCIe ECAM), **FADT**
|
||||
(power registers, and the pointer to the DSDT), **HPET** (timer). That's where the
|
||||
firmware-agnostic [device model](discovery.md) gets populated; this note stops at the
|
||||
part that answers "where are the tables?" — everything past the RSDP is just following
|
||||
more pointers the tables themselves provide.
|
||||
|
||||
## Related
|
||||
|
||||
- [efi.md](efi.md) — the loader that captures the RSDP before `ExitBootServices`.
|
||||
- [memory-map.md](memory-map.md) — the same loader-captures / kernel-consumes seam, and
|
||||
the ACPI-reclaim memory the RSDP lives in.
|
||||
- [discovery.md](discovery.md) — the broader (still-evolving) plan for turning these
|
||||
tables into one neutral device model shared with the ARM device-tree path.
|
||||
- [arch.md](arch.md) — why the kernel reaches the device code through a `platform`
|
||||
module and never names ACPI directly.
|
||||
Reference in New Issue
Block a user