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) {