SchOS/include/schboot.h
Nexoniarz c4dd76cc66 boot/uefi: the memory map the kernel gets is the one the firmware agreed to
Two bugs on the ExitBootServices path, both invisible under OVMF and both
aimed squarely at real firmware.

The retry did not re-translate. If ExitBootServices returns an error the
loader re-reads the memory map and tries again -- correct, and the
specification expects it -- but the e820 table handed to the kernel was
still the one built from the *previous* map. The retry exists precisely
because the map changed, so the kernel was being given a description of
memory the firmware had already rearranged. QEMU never takes this path,
which is why it survived. Translation is now a function called on both.

Truncation was silent. phystop comes from the largest usable region, so
dropping the tail of the map can leave the kernel believing a large
machine has a fraction of its RAM -- and nothing said so, because the
loop simply stopped at the cap. It now warns while there is still a
console to warn on; after ExitBootServices there is nowhere to report
anything.

SCHBOOT_E820_MAX goes from 32 to 40. The map sits at BASE+0x208 and the
next field is at BASE+0x600, so 1016 bytes are reserved and 42 entries of
24 bytes fit. Real firmware emits far more descriptors than QEMU's; the
headroom was already allocated and unused.

Coalescing adjacent same-type runs is what keeps any of this in budget,
and it works because boot services memory becomes usable at this point --
that is most of the fragmentation in a UEFI map.

110 [ok], no failures, and the map still reads 7 regions under OVMF.
2026-08-11 11:42:22 +02:00

102 lines
4.7 KiB
C

// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifndef SCHBOOT_H
#define SCHBOOT_H
// The loader/kernel hand-off, in one place.
//
// Included by kernel/arch/x86_64/entry.S and by boot/uefi/main.c, so the two
// cannot disagree about the magic or the layout. Defining it twice is exactly
// how a loader ends up scanning for bytes the kernel never wrote — the first
// attempt here searched for "SCH6" while the assembler had stored 0x53434836,
// which lands in the file as "6HCS".
//
// Keep this header assembler-safe: no typedefs, no structs outside the C
// guard, nothing entry.S cannot preprocess.
// Scanned for in the first 16 KB of the kernel image, 8-byte aligned, exactly
// as the BIOS loader scans for the multiboot magic. Followed by a little-endian
// 64-bit physical address: the kernel's 64-bit entry point.
//
// The values spell "SCH6" "4BOO" when stored little-endian, which is what
// makes them findable by eye in a hex dump of the image.
#define SCH64_MAGIC0 0x36484353 /* "SCH6" */
#define SCH64_MAGIC1 0x4f4f4234 /* "4BOO" */
// ---------------------------------------------------------------------------
// The hand-off block.
//
// One page of low memory that a loader fills in and the kernel reads. Every
// field below was previously #defined in whichever file happened to need it --
// five of them, across two loaders and two kernel files -- which is the same
// hazard as defining the magic twice, only quieter: nothing breaks until one
// copy moves.
//
// 0x30000 is clear of stage2 (loaded at 0x7E00 and well under 0xA000), the
// multiboot kernel-search buffer at 0x10000-0x14000, and the kernel's own load
// address at 0x100000.
//
// Both loaders write it, so a field's meaning must not depend on which one did.
// Where they genuinely differ -- the BIOS path knows what medium it booted
// from, UEFI does not -- the field is defined as "0 when unknown" rather than
// left for the reader to guess.
#define SCHBOOT_BASE 0x30000
// A VBE mode info block, 256 bytes. The UEFI loader fills the same four fields
// out of GOP rather than translating a structure it never saw, so the kernel
// cannot tell which firmware booted it.
#define SCHBOOT_VBE_INFO (SCHBOOT_BASE + 0x000)
#define SCHBOOT_VBE_STRIDE (SCHBOOT_VBE_INFO + 16) /* uint16, bytes/line */
#define SCHBOOT_VBE_WIDTH (SCHBOOT_VBE_INFO + 18) /* uint16 */
#define SCHBOOT_VBE_HEIGHT (SCHBOOT_VBE_INFO + 20) /* uint16 */
#define SCHBOOT_VBE_BPP (SCHBOOT_VBE_INFO + 25) /* uint8 */
#define SCHBOOT_VBE_LFB (SCHBOOT_VBE_INFO + 40) /* uint32, physical */
// Non-zero if the loader was asked for a verbose boot.
#define SCHBOOT_VERBOSE_FLAG (SCHBOOT_BASE + 0x100)
// What the system booted from. SCHBOOT_MEDIA_CD matters because the ISO path
// reads through ATAPI rather than ATA; 0 means "not stated", which is what the
// UEFI loader leaves, since it does not read the kernel through either.
#define SCHBOOT_MEDIA_FLAG (SCHBOOT_BASE + 0x101)
#define SCHBOOT_MEDIA_CD 1
// Real-mode spelling of the same byte, for cdboot.S which has no 32-bit
// addressing yet. Segment and offset, not a flat address.
#define SCHBOOT_MEDIA_SEG 0x3000
#define SCHBOOT_MEDIA_OFF 0x101
// The E820 memory map: a uint32 count, then that many 24-byte entries. Capped
// at 32 so a pathological firmware cannot run past the buffer; the map ends at
// 0x508, which is what leaves 0x600 free below.
#define SCHBOOT_E820_COUNT (SCHBOOT_BASE + 0x200)
#define SCHBOOT_E820_MAP (SCHBOOT_BASE + 0x208)
/* 40, not 32. The map lives at SCHBOOT_E820_MAP (BASE+0x208) and the next
* field is SCHBOOT_RSDP_ADDR at BASE+0x600, so there are 1016 bytes and each
* entry is 24 -- 42 fit. Real UEFI firmware produces far more descriptors than
* QEMU's, and the headroom costs nothing that was being used. */
#define SCHBOOT_E820_MAX 40
// 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.
//
#define SCHBOOT_RSDP_ADDR (SCHBOOT_BASE + 0x600)
#ifndef __ASSEMBLER__
struct sch64_header {
unsigned int magic0; // SCH64_MAGIC0
unsigned int magic1; // SCH64_MAGIC1
unsigned long long entry; // physical address, entered in long mode
};
#endif /* !__ASSEMBLER__ */
#endif /* SCHBOOT_H */