Free every usable memory region, not one contiguous run

e820_init() took the usable run *containing the kernel* and treated its
end as the top of RAM, and kinit1() walked a single range from the end
of the kernel image to there. That is the shape of a BIOS map. It is
not the shape of a UEFI one: OVMF places an ACPI NVS region at 8 MB,
so the kernel's run ended there, the map failed the PHYSTOP_MIN check,
and the kernel fell back to asserting 224 MB it had not measured --
then freed straight through the reserved hole anyway.

Keep the usable regions as a list as well as a top address. kinit1()
now frees each one, clipped to the window main() passes in, so the
holes are skipped rather than either freed or fatal. PHYSTOP still
means what it always did -- the top of what kalloc may hand out, and
the bound kfree() rejects pages above -- so no call site changes.

phystop comes from the largest region rather than the highest. The DMA
arena is carved off the top of PHYSTOP and must be physically
contiguous, and the largest region is the only one certain to have
room; a smaller region above it is left unfreed rather than raising
the bound past a hole, because kfree()'s check is worth more than the
pages it costs. Report the usable total separately from the top
address, since with a fragmented map they are different numbers and
printing one as the other is how 8 MB went unnoticed.

Verified on both paths. BIOS: unchanged at 1023 MB in 2 regions,
bootcheck 27/27. UEFI under OVMF: 1018 MB in 7 regions, up from the
8 MB that triggered the fallback, booting through vga, e1000, the RTC
and on to cpu0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-09 20:03:51 +02:00
commit 0750e7cbf4
3 changed files with 147 additions and 26 deletions

View file

@ -17,6 +17,28 @@ extern uint64 phystop;
#define PHYSTOP phystop
#endif
// PHYSTOP alone describes memory as one run ending at a top address, which is
// what a BIOS map usually looks like and what UEFI never does: OVMF drops an
// ACPI NVS region at 0x800000, so the run containing the kernel ends at 8 MB
// and everything above the hole was invisible. The usable regions are
// therefore kept as a list as well, and kinit1() frees each one rather than
// walking a single range from the kernel's end to the top.
//
// PHYSTOP still means what it always did -- the top of what kalloc may hand
// out, and the bound kfree() rejects pages above -- so every existing call
// site is unaffected.
#define MEMREGION_MAX 32
#ifndef __ASSEMBLER__
struct memregion {
uint64 base; // first byte, page aligned
uint64 end; // one past the last byte, page aligned
};
extern struct memregion mem_usable[MEMREGION_MAX];
extern int nmem_usable;
#endif
// What to assume when the firmware gives us nothing usable: the old constant,
// so that path behaves exactly as the kernel always did.
#define PHYSTOP_FALLBACK 0xE000000ULL