From 9735279970b2d7832f1bc851a9b97c78a4db025b Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 9 Aug 2026 22:02:57 +0200 Subject: [PATCH] acpi: take the RSDP from the loader when firmware gives it to us find_rsdp() scanned the EBDA and 0xE0000-0x100000. That is a BIOS convention, and UEFI firmware is under no obligation to leave a copy there -- OVMF does not. So the kernel found no RSDP, no MADT and no IOAPIC, and nothing routed IRQ 4. Serial output is polled, so the machine booted all the way to a shell prompt and then ignored every key, which reads as a broken console rather than a missing table. The UEFI loader reads the RSDP out of the EFI configuration table, preferring ACPI 2.0 for its XSDT, and leaves it at SCHBOOT_RSDP_ADDR -- the same low-memory hand-off block the e820 map already uses, now described in include/schboot.h so the two sides cannot disagree. The kernel validates it rather than trusting it: the BIOS path never writes that slot, so there it holds whatever was in memory, and following a wild pointer faults before there is anything to report the fault with. Signature and checksum do that. It deliberately does not require 16-byte alignment -- that rule is about searching the BIOS area, and OVMF's RSDP sits at 0x3f77e014, so demanding it threw away the one address this code exists to use. UEFI now reports "acpi: 1 cpu, lapic at ..., ioapic id 0" and the ISO boots to an interactive shell: uname, ls /bin (241), writing and reading a file, ps, /proc/schfs -- all driven over serial. BIOS unchanged: bootcheck 108 [ok], same ACPI line as before. Co-Authored-By: Claude Opus 5 --- boot/uefi/efi.h | 17 +++++++++++++++ boot/uefi/main.c | 46 +++++++++++++++++++++++++++++++++++++++ include/schboot.h | 15 +++++++++++++ kernel/arch/x86_64/acpi.c | 23 ++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/boot/uefi/efi.h b/boot/uefi/efi.h index 93e8a62..c8c5850 100644 --- a/boot/uefi/efi.h +++ b/boot/uefi/efi.h @@ -171,6 +171,23 @@ typedef struct { void *ConfigurationTable; } EFI_SYSTEM_TABLE; +// --- configuration tables ------------------------------------------------- +// +// Where UEFI publishes things a BIOS left lying in low memory. The ACPI RSDP is +// the one that matters here: the kernel finds it by scanning the EBDA and +// 0xE0000-0x100000, which is a BIOS convention that firmware is under no +// obligation to honour -- and OVMF does not. + +#define EFI_ACPI_20_TABLE_GUID \ + { 0x8868E871, 0xE4F1, 0x11D3, { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 } } +#define EFI_ACPI_10_TABLE_GUID \ + { 0xEB9D2D30, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } } + +typedef struct { + EFI_GUID VendorGuid; + void *VendorTable; +} EFI_CONFIGURATION_TABLE; + // --- loaded image --------------------------------------------------------- #define EFI_LOADED_IMAGE_PROTOCOL_GUID \ diff --git a/boot/uefi/main.c b/boot/uefi/main.c index 359d250..c46726d 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -323,6 +323,45 @@ efi_type_to_e820(UINT32 t) // --------------------------------------------------------------------------- +static int +guid_eq(const EFI_GUID *a, const EFI_GUID *b) +{ + const UINT8 *p = (const UINT8 *)a, *q = (const UINT8 *)b; + for (UINTN i = 0; i < sizeof(EFI_GUID); i++) + if (p[i] != q[i]) + return 0; + return 1; +} + +// The ACPI RSDP, from the UEFI configuration table. +// +// The kernel's own search scans the EBDA and 0xE0000-0x100000. That is a BIOS +// convention, and UEFI firmware is under no obligation to leave a copy there -- +// OVMF does not. The kernel therefore found no RSDP, so no MADT, so no IOAPIC, +// so nothing routed IRQ 4: it booted to a shell prompt on polled serial output +// and then ignored every key, which reads as a broken console rather than a +// missing table. +// +// ACPI 2.0 is preferred over 1.0 because its RSDP carries the XSDT, whose +// entries are 64-bit; the 1.0 RSDT is kept only as a fallback. +static UINT64 +find_rsdp(void) +{ + EFI_GUID acpi20 = EFI_ACPI_20_TABLE_GUID; + EFI_GUID acpi10 = EFI_ACPI_10_TABLE_GUID; + EFI_CONFIGURATION_TABLE *t = + (EFI_CONFIGURATION_TABLE *)ST->ConfigurationTable; + UINT64 fallback = 0; + + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + if (guid_eq(&t[i].VendorGuid, &acpi20)) + return (UINT64)(UINTN)t[i].VendorTable; + if (guid_eq(&t[i].VendorGuid, &acpi10)) + fallback = (UINT64)(UINTN)t[i].VendorTable; + } + return fallback; +} + EFI_STATUS EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { @@ -341,6 +380,12 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) UINT32 fb = setup_framebuffer(); + // Read while boot services still exist and the system table is guaranteed + // valid; written to low memory further down, after the firmware has let go + // of it, for the same reason the e820 map is. + UINT64 rsdp = find_rsdp(); + print(L"schos: acpi rsdp "); print_hex(rsdp); print(L"\r\n"); + // The memory map, and then out. GetMemoryMap must be the last thing before // ExitBootServices: the key it returns is invalidated by any allocation, // and AllocatePool for the map buffer is itself an allocation -- hence the @@ -401,6 +446,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) copy((void *)E820_MAP_ADDR, e820, nent * sizeof e820[0]); *(UINT32 *)E820_COUNT_ADDR = nent; + *(UINT64 *)SCHBOOT_RSDP_ADDR = rsdp; // The page tables mboot_entry would have built. entry64low's first act is // a movabs to 0xffff8000...., so the high half must be mapped before the diff --git a/include/schboot.h b/include/schboot.h index 0f5dcf3..ae95195 100644 --- a/include/schboot.h +++ b/include/schboot.h @@ -23,6 +23,21 @@ #define SCH64_MAGIC0 0x36484353 /* "SCH6" */ #define SCH64_MAGIC1 0x4f4f4234 /* "4BOO" */ +// Where the loader leaves the ACPI RSDP, as a little-endian 64-bit physical +// address. Zero means "not provided, go and look for it yourself". +// +// The kernel's own search scans the EBDA and 0xE0000-0x100000, which is a BIOS +// convention. UEFI publishes the RSDP in its configuration table instead and is +// under no obligation to leave a copy anywhere in particular; OVMF does not. +// Without it the kernel finds no MADT, so no IOAPIC, so no interrupt routing -- +// the machine boots to a prompt on polled serial output and then ignores every +// key, which looks like a broken console rather than a missing table. +// +// 0x30000 is the same low-memory hand-off block the BIOS loader uses: +// +0x100 verbose flag, +0x200 e820 count, +0x208 the e820 map (32 entries of +// 24 bytes, ending at 0x508). 0x600 is clear of all of it. +#define SCHBOOT_RSDP_ADDR (0x30000 + 0x600) + #ifndef __ASSEMBLER__ struct sch64_header { diff --git a/kernel/arch/x86_64/acpi.c b/kernel/arch/x86_64/acpi.c index 7d42537..600a8dd 100644 --- a/kernel/arch/x86_64/acpi.c +++ b/kernel/arch/x86_64/acpi.c @@ -18,6 +18,7 @@ #include "types.h" #include "defs.h" +#include "schboot.h" // SCHBOOT_RSDP_ADDR: the loader/kernel hand-off #include "param.h" #include "memlayout.h" #include "mmu.h" @@ -134,6 +135,28 @@ scan_rsdp(uint64 base, uint64 len) static struct acpi_rsdp * find_rsdp(void) { + // A UEFI loader hands the address over directly, because the scan below is a + // BIOS convention that UEFI firmware need not honour and OVMF does not: the + // kernel found no RSDP, so no MADT, so no IOAPIC, and nothing routed IRQ 4 -- + // it reached a shell prompt on polled serial output and then ignored every + // key. + // + // Validated, never trusted. The BIOS path does not write this slot, so there + // it holds whatever happened to be in memory, and following a wild pointer + // faults before there is anything to report the fault with. The bound is what + // the direct map covers; the signature and checksum below do the rest. + // + // Deliberately *not* requiring 16-byte alignment. That rule applies to + // searching the BIOS area, not to a pointer handed over by firmware -- OVMF's + // sits at 0x3f77e014, and demanding alignment threw away the very address + // this code was added to use. + uint64 handed = *(uint64 *)P2V(SCHBOOT_RSDP_ADDR); + if (handed && handed < 0x100000000ULL) { + struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(handed); + if (memcmp(r->sig, "RSD PTR ", 8) == 0 && checksum_ok(r, 20)) + return r; + } + // The EBDA, whose segment address the BIOS leaves at 0x40E. uint16 ebda_seg = *(uint16 *)P2V(0x40E); if (ebda_seg) {