// Find out how much memory this machine actually has. // // PHYSTOP used to be a constant: 0xE000000, 224 MB, inherited from xv6 and // never revisited. That is not a limit on how much memory the kernel *uses* -- // it is an assertion that exactly that much exists. Boot a machine with less // and kinit1() walks off the end of RAM into nothing; boot one with more and // every byte above 224 MB is invisible. Measured before this file existed: // SchOS booted at 224 MB and triple faulted at 192 MB, for no reason at all. // // The firmware has known the answer the whole time. boot/bios/stage2.S asks it with // INT 0x15 / E820 in real mode -- the same interface Linux's boot stub and GRUB // use -- and leaves the result in low memory. stage2main.c already parses it to // print detected RAM on the splash. The kernel simply never read it. #include "core/types.h" #include "core/defs.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/dma.h" // DMA_ARENA_BYTES: the top of PHYSTOP must be able to hold it #include "core/hwinfo.h" // Set before kinit1() and never again. Starts at the old constant so that a // crash between entry and e820_init() behaves the way it always did rather // than in some new way. uint64 phystop = PHYSTOP_FALLBACK; // The top of the low region. See memlayout.h: the DMA arena and everything // kinit1() frees live below this, because that is all the loader's page tables // map and all a 32-bit device can reach. uint64 lowtop = PHYSTOP_FALLBACK; #include "arch/schboot.h" /* the hand-off block */ #define E820_USABLE 1 #define E820_MAXENTRIES SCHBOOT_E820_MAX // Every usable region, page aligned inwards and clipped to PHYSTOP_MAX. // kinit1() frees these; see include/arch/memlayout.h for why a list and not just a // top address. struct memregion mem_usable[MEMREGION_MAX]; int nmem_usable; // The fallback path must still hand kalloc something, or the kernel comes up // with an empty free list and dies somewhere far less obvious than here. One // region spanning what the old constant asserted reproduces the previous // behaviour exactly. static void assume_fallback(void) { mem_usable[0].base = EXTMEM; mem_usable[0].end = PHYSTOP_FALLBACK; nmem_usable = 1; phystop = PHYSTOP_FALLBACK; lowtop = PHYSTOP_FALLBACK; } struct e820entry { uint64 base; uint64 length; uint32 type; uint32 acpi_ext; } __attribute__((packed)); // The map exactly as handed over, for /sys/firmware/memmap. The low page it // came in is not the kernel's to keep. static struct e820_rec map_copy[E820_MAXENTRIES]; static int nmap_copy; int e820_nth(int i, struct e820_rec *out) { if (i < 0 || i >= nmap_copy) return -1; *out = map_copy[i]; return 0; } uint64 ram_total_bytes(void) { uint64 t = 0; for (int i = 0; i < nmem_usable; i++) t += mem_usable[i].end - mem_usable[i].base; return t; } void e820_init(void) { uint32 count = *(uint32 *)P2V(SCHBOOT_E820_COUNT); struct e820entry *e = (struct e820entry *)P2V(SCHBOOT_E820_MAP); if (count <= E820_MAXENTRIES) for (uint32 i = 0; i < count; i++) { map_copy[i].base = e[i].base; map_copy[i].len = e[i].length; map_copy[i].type = e[i].type; nmap_copy = (int)i + 1; } // A count of zero means the firmware declined; anything absurd means we are // reading something that is not a memory map. Either way the constant is the // only honest answer left. if (count == 0 || count > E820_MAXENTRIES) { cprintf("e820: no usable map (count %d), assuming %d MB\n", count, (int)(PHYSTOP_FALLBACK / (1024 * 1024))); assume_fallback(); return; } // Collect every usable region rather than just the one containing the // kernel. // // A memory map is not one run of RAM: there are reserved holes in it -- ACPI // tables, firmware scratch, the legacy hole below 1 MB. The old code took the // run containing the kernel and stopped, because kalloc walked a single range // and taking the global maximum would have stepped over a reserved region and // put firmware tables on the free list. That was the right answer for the // wrong shape: under OVMF the kernel's run ends at the ACPI NVS region at // 8 MB, and every byte above it was lost. kalloc now frees region by region, // so the holes are skipped instead of being either freed or fatal. uint64 largest = 0; uint64 phys_top = 0, low_top = 0; nmem_usable = 0; for (uint32 i = 0; i < count; i++) { if (e[i].type != E820_USABLE) continue; // Align inwards: a region is only usable where a whole page fits. uint64 base = (e[i].base + PGSIZE - 1) & ~(uint64)(PGSIZE - 1); uint64 end = (e[i].base + e[i].length) & ~(uint64)(PGSIZE - 1); // More than the direct map will ever cover. Clipped rather than refused: // a machine with more RAM than this should use what it can. if (end > PHYSTOP_MAX) end = PHYSTOP_MAX; if (base >= end) continue; // Full: the smallest region gives way to a larger one. A map that // fragments RAM into more than MEMREGION_MAX pieces loses its crumbs // rather than whatever happens to be listed last, which is the RAM above // 4 GB. if (nmem_usable == MEMREGION_MAX) { int s = 0; for (int k = 1; k < nmem_usable; k++) if (mem_usable[k].end - mem_usable[k].base < mem_usable[s].end - mem_usable[s].base) s = k; if (mem_usable[s].end - mem_usable[s].base >= end - base) continue; mem_usable[s] = mem_usable[--nmem_usable]; } mem_usable[nmem_usable].base = base; mem_usable[nmem_usable].end = end; nmem_usable++; // Two different questions, which used to be one and should not have been. // // `phystop` is how far RAM goes, and it is simply the highest end: kfree() // uses it as a bound and kvmalloc() maps up to it. if (end > phys_top) phys_top = end; // `lowtop` is where the DMA arena goes, and it has to be the largest // region *below the boot direct map* -- contiguous, reachable by a 32-bit // device, and mapped before kvmalloc has run. The largest such region is // the only one certain to have room. { uint64 lo_end = end > DIRECTMAP_BOOT ? DIRECTMAP_BOOT : end; if (base < lo_end && lo_end - base > largest) { largest = lo_end - base; low_top = lo_end; } } } if (nmem_usable == 0) { cprintf("e820: no usable regions in %d entries, assuming %d MB\n", count, (int)(PHYSTOP_FALLBACK / (1024 * 1024))); assume_fallback(); return; } // Below this the kernel image, its page tables and the DMA arena do not fit, // and continuing produces a failure much harder to read than this line. if (low_top < PHYSTOP_MIN) { cprintf("e820: only %d MB usable below 4 GB; SchOS needs at least %d MB " "there\n", (int)(low_top / (1024 * 1024)), (int)(PHYSTOP_MIN / (1024 * 1024))); // Left at the fallback deliberately rather than lowered: a machine this // small is going to fail, and it should fail having said why. assume_fallback(); return; } // dma_init() carves [PHYSTOP - DMA_ARENA_BYTES, PHYSTOP) and needs it // physically contiguous, so the region that set `top` must be able to hold // it. Picking the largest region makes this true on any machine worth // booting; checking says so out loud if it ever is not. // // The kernel log is *not* up here -- it sits just above the kernel image, // where the firmware does not allocate. See klog_init()'s caller. if (largest < DMA_ARENA_BYTES) { cprintf("e820: largest usable region is %d MB, too small for the %d MB " "DMA arena\n", (int)(largest / (1024 * 1024)), (int)(DMA_ARENA_BYTES / (1024 * 1024))); assume_fallback(); return; } phystop = phys_top; lowtop = low_top; // Report what is actually usable, not phystop: with a fragmented map those // are different numbers, and printing the top address as though it were the // amount of RAM is how the 8 MB under OVMF went unnoticed for so long. uint64 usable = 0; for (int i = 0; i < nmem_usable; i++) usable += mem_usable[i].end - mem_usable[i].base; kinfo("e820: %d MB usable in %d region%s, top %d MB (%d entries)\n", (int)(usable / (1024 * 1024)), nmem_usable, nmem_usable == 1 ? "" : "s", (int)(phystop / (1024 * 1024)), count); }