SchOS/kernel/arch/x86_64/e820.c
Nexoniarz 08a1a9033a boot: separate BIOS from UEFI
boot/bios/ now holds the MBR, the El Torito stub and the real-mode stage
2; boot/uefi/ is empty and documented.

Separate directories rather than #ifdefs, because the two share almost
nothing. A BIOS loader lives in 16-bit real mode, reaches the firmware
through software interrupts, and walks itself up into long mode. A UEFI
loader is a 64-bit PE that the firmware has already placed in long mode
with paging on, calling services through a table of function pointers.
What they do share is the hand-off contract, which boot/README.md now
writes down: ELF segments placed, long mode, a 24-byte-entry memory map
at E820_MAP_ADDR, framebuffer geometry at VBE_INFO_ADDR.

That contract is the point of documenting it now. UEFI's GetMemoryMap
returns EFI_MEMORY_DESCRIPTORs, and the right move is to translate them
into the entries kernel/arch/x86_64/e820.c already parses rather than
teach the kernel a second format.

Build output paths under $(BUILD)/boot are unchanged; only sources moved.
2026-08-09 16:00:03 +02:00

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:
// 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"
// 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/bios/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);
}