An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
105 lines
4.1 KiB
C
105 lines
4.1 KiB
C
// 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:
|
|
// Schunix 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; Schunix 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);
|
|
}
|