// 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/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" // 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; // Where stage2 left the map. Must match E820_COUNT_ADDR / E820_MAP_ADDR in // boot/stage2main.c, which are themselves relative to VBE_INFO_ADDR. #define E820_COUNT_ADDR (0x30000 + 0x200) #define E820_MAP_ADDR (0x30000 + 0x208) #define E820_USABLE 1 #define E820_MAXENTRIES 32 struct e820entry { uint64 base; uint64 length; uint32 type; uint32 acpi_ext; } __attribute__((packed)); void e820_init(void) { uint32 count = *(uint32 *)P2V(E820_COUNT_ADDR); struct e820entry *e = (struct e820entry *)P2V(E820_MAP_ADDR); // 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))); return; } // The end of the usable region that *contains the kernel*, not the highest // usable address anywhere. // // This distinction matters and is easy to get wrong. 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 -- and kalloc's freerange() walks // straight from the end of the kernel image to phystop handing out every page // between. Taking the global maximum would step over a reserved region and // put firmware tables on the free list, which fails later, elsewhere, and // looks like anything but a memory map problem. uint64 top = 0; for (uint32 i = 0; i < count; i++) { if (e[i].type != E820_USABLE) continue; uint64 base = e[i].base, end = base + e[i].length; if (base <= EXTMEM && EXTMEM < end) { top = end; break; } } if (top == 0) { cprintf("e820: no usable region covers 0x%x, assuming %d MB\n", EXTMEM, (int)(PHYSTOP_FALLBACK / (1024 * 1024))); return; } // 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. if (top > PHYSTOP_MAX) top = PHYSTOP_MAX; top &= ~(uint64)(PGSIZE - 1); // 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. return; } phystop = top; cprintf("e820: %d MB usable (%d entries)\n", (int)(phystop / (1024 * 1024)), count); }