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:
parent
15331d4399
commit
0750e7cbf4
3 changed files with 147 additions and 26 deletions
|
|
@ -37,13 +37,47 @@ check_run(struct run *r, char *where)
|
|||
}
|
||||
}
|
||||
|
||||
// vstart..vend is the window the kernel is willing to give away: from the end
|
||||
// of its own image to the bottom of the DMA arena. Inside that window the
|
||||
// firmware's map decides what is real -- free each usable region clipped to the
|
||||
// window, rather than walking the whole span and handing out the holes.
|
||||
//
|
||||
// The old code walked it as one range, which is what a BIOS map looks like.
|
||||
// Under UEFI it is not: OVMF puts an ACPI NVS region at 8 MB, so the run
|
||||
// containing the kernel stopped there and everything above it was lost. The
|
||||
// window still bounds things at both ends, so nothing below the kernel image
|
||||
// and nothing inside the DMA arena is ever freed, whatever the map says.
|
||||
void
|
||||
kinit1(void *vstart, void *vend)
|
||||
{
|
||||
initlock(&kmem.lock, "kmem");
|
||||
kmem.use_lock = 0;
|
||||
kmem.freelist = 0; // empty
|
||||
freerange(vstart, vend);
|
||||
|
||||
// No map at all means e820_init() could not parse one; it leaves a single
|
||||
// synthetic region behind for exactly this reason, so this is belt and
|
||||
// braces rather than a path anything takes.
|
||||
if(nmem_usable == 0){
|
||||
freerange(vstart, vend);
|
||||
return;
|
||||
}
|
||||
|
||||
addr_t lo_limit = V2P((addr_t)vstart);
|
||||
addr_t hi_limit = V2P((addr_t)vend);
|
||||
|
||||
for(int i = 0; i < nmem_usable; i++){
|
||||
addr_t lo = mem_usable[i].base;
|
||||
addr_t hi = mem_usable[i].end;
|
||||
|
||||
if(lo < lo_limit)
|
||||
lo = lo_limit;
|
||||
if(hi > hi_limit)
|
||||
hi = hi_limit;
|
||||
if(lo >= hi)
|
||||
continue;
|
||||
|
||||
freerange(P2V(lo), P2V(hi));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
Loading…
Reference in a new issue