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>
71 lines
2.7 KiB
C
71 lines
2.7 KiB
C
#pragma once
|
|
|
|
// Memory layout
|
|
|
|
#define EXTMEM 0x100000 // Start of extended memory
|
|
// Top of usable physical memory. A *variable*, read from the firmware's E820
|
|
// map at boot -- see kernel/arch/x86_64/e820.c. It used to be the constant
|
|
// below, which was not a limit on what the kernel used but an assertion that
|
|
// exactly 224 MB existed: less and the allocator walked off the end of RAM,
|
|
// more and it was invisible.
|
|
//
|
|
// The macro spelling is kept so that the twenty-odd call sites read the same as
|
|
// they always did. It must not be used before e820_init(), which runs first
|
|
// thing in main() for that reason.
|
|
#ifndef __ASSEMBLER__
|
|
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
|
|
|
|
// kvmalloc() maps physical 0-4 GB as four 1 GB pages and marks the top one
|
|
// uncached, because that is where PCI BARs and the framebuffer sit. 3 GB is
|
|
// therefore the most that can be treated as RAM.
|
|
#define PHYSTOP_MAX 0xC0000000ULL
|
|
|
|
// Below this the kernel image, its page tables and the DMA arena do not fit.
|
|
#define PHYSTOP_MIN (24ULL * 1024 * 1024)
|
|
|
|
// Key addresses for address space layout
|
|
#define KERNBASE 0xFFFF800000000000 // First kernel virtual address
|
|
#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked
|
|
|
|
#ifndef __ASSEMBLER__
|
|
static inline addr_t v2p(void *a) {
|
|
return ((addr_t)(a)) - ((addr_t)KERNBASE);
|
|
}
|
|
static inline void *p2v(addr_t a) {
|
|
return (void *)((a) + ((addr_t)KERNBASE));
|
|
}
|
|
#endif
|
|
|
|
#define V2P(a) (((addr_t) (a)) - KERNBASE)
|
|
#define P2V(a) (((void *) (a)) + KERNBASE)
|
|
|
|
#define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts
|
|
#define P2V_WO(x) ((x) + KERNBASE) // same as P2V, but without casts
|