// 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 "types.h" #include "defs.h" #include "memlayout.h" #include "mmu.h" #include "dma.h" // DMA_ARENA_BYTES: the top of PHYSTOP must be able to hold it // 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; #include "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/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; } struct e820entry { uint64 base; uint64 length; uint32 type; uint32 acpi_ext; } __attribute__((packed)); void e820_init(void) { uint32 count = *(uint32 *)P2V(SCHBOOT_E820_COUNT); struct e820entry *e = (struct e820entry *)P2V(SCHBOOT_E820_MAP); // 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 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); // Above PHYSTOP_MAX the direct map is device memory, not RAM (see below). if (end > PHYSTOP_MAX) end = PHYSTOP_MAX; if (base >= end) continue; mem_usable[nmem_usable].base = base; mem_usable[nmem_usable].end = end; nmem_usable++; // phystop comes from the largest region, not the highest. It has to be a // region that can hold the DMA arena, which is carved off the top of // PHYSTOP and must be physically contiguous; the largest is the only one // that is certain to have room. A small usable region above it is left // unfreed rather than raising the bound past a hole -- kfree() rejects // anything at or above PHYSTOP, and that check is worth more than the // stray page it costs. if (end - base > largest) { largest = end - base; top = 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; } // The PHYSTOP_MAX ceiling is applied per region above, when each one is // clipped. kvmalloc() maps physical 0-4 GB with four 1 GB pages and marks the // top one uncached because that is where PCI BARs and the framebuffer live, // so 3 GB is the ceiling for anything the kernel will treat as RAM: above it // the direct map either does not exist or is device memory that would be // catastrophically slow to allocate from and is not ours to hand out. // 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 (top < PHYSTOP_MIN) { cprintf("e820: only %d MB usable; SchOS needs at least %d MB\n", (int)(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 = 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; cprintf("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); }