reclaiming uefi memory

This commit is contained in:
2026-07-03 19:31:32 +01:00
parent 50f3610768
commit 21b9691486
6 changed files with 79 additions and 48 deletions
+17 -6
View File
@@ -269,6 +269,12 @@ fn exitBootServices(bs: *uefi.tables.BootServices) !danos.MemoryMap {
/// never sees UEFI's vocabulary — the same seam the framebuffer already uses.
fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
const regions: [*]danos.MemoryRegion = @ptrCast(@alignCast(out.ptr));
// We're about to call boot-services memory `usable`, but our own stack lives
// in it and the kernel starts out running on it. Keep the region holding the
// current stack pointer reserved so it's never handed out.
const rsp = asm volatile ("mov %%rsp, %[out]"
: [out] "=r" (-> usize),
);
var count: usize = 0;
var i: usize = 0;
while (i < map.info.len) : (i += 1) {
@@ -277,7 +283,10 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
const d: *const uefi.tables.MemoryDescriptor =
@ptrCast(@alignCast(map.ptr + i * map.info.descriptor_size));
if (d.number_of_pages == 0) continue;
const kind = classify(d);
var kind = classify(d);
// The descriptor we're executing on stays reserved (see rsp above).
const region_end = d.physical_start + d.number_of_pages * danos.page_size;
if (kind == .usable and rsp >= d.physical_start and rsp < region_end) kind = .reserved;
// Coalesce with the previous region if it's the same kind and contiguous.
if (count > 0) {
@@ -304,14 +313,16 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) danos.MemoryMap {
/// a reserved address-space window (e.g. PCIe config space) — so it's `mmio`
/// regardless of type. UEFI overloads `reserved_memory_type` for both reserved RAM
/// and such holes, and the cache attribute is what actually tells them apart.
/// Among RAM regions, anything we don't recognise is `reserved` — the safe
/// default; our own LoaderData (kernel image, these buffers) lands there too and
/// stays reserved until the kernel reclaims it.
///
/// Boot-services memory is folded straight into `usable`: we've already called
/// ExitBootServices, so it's free RAM now — the kernel never needs to know it was
/// ever the firmware's (the one live piece, our stack, is reserved by the caller).
/// Anything unrecognised is `reserved` — the safe default; our own LoaderData (the
/// kernel image and these buffers) lands there and stays reserved.
fn classify(d: *const uefi.tables.MemoryDescriptor) danos.MemoryKind {
if (!d.attribute.wb) return .mmio;
return switch (d.@"type") {
.conventional_memory => .usable,
.boot_services_code, .boot_services_data => .reclaimable,
.conventional_memory, .boot_services_code, .boot_services_data => .usable,
.acpi_reclaim_memory => .acpi_tables,
.acpi_memory_nvs => .acpi_nvs,
.memory_mapped_io, .memory_mapped_io_port_space => .mmio,
+3 -6
View File
@@ -52,25 +52,22 @@ fn kmain(boot_info: *const BootInfo) noreturn {
// own MemoryRegion, so this is a plain slice — no firmware layout in sight.
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(boot_info.memory_map.regions))[0..boot_info.memory_map.len];
var usable_pages: u64 = 0;
var reclaim_pages: u64 = 0;
var reserved_pages: u64 = 0; // reserved RAM only — MMIO is device space, not RAM
for (regions) |r| {
switch (r.kind) {
.usable => usable_pages += r.pages,
.reclaimable => reclaim_pages += r.pages,
.reserved, .acpi_tables, .acpi_nvs => reserved_pages += r.pages,
.mmio => {},
}
}
const total_pages = usable_pages + reclaim_pages + reserved_pages;
const total_pages = usable_pages + reserved_pages;
const total_bytes = total_pages * danos.page_size;
const gib = 1 << 30;
con.write("\ndanos: physical memory\n");
con.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
con.print(" usable : {d} MiB - free now; owned by the frame allocator\n", .{mib(usable_pages)});
con.print(" reclaimable: {d} MiB - UEFI boot-services memory, free after exit\n", .{mib(reclaim_pages)});
con.print(" reserved : {d} MiB - kernel image, ACPI, runtime services\n", .{mib(reserved_pages)});
con.print(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)});
con.print(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)});
con.print(" regions : {d} - entries in the firmware memory map\n", .{regions.len});
// Bring up the physical frame allocator over that map, and prove it works:
+5 -4
View File
@@ -58,12 +58,13 @@ fn regions(map: danos.MemoryMap) []const danos.MemoryRegion {
pub fn init(map: danos.MemoryMap) void {
const regs = regions(map);
// 1. Size the bitmap to cover every frame up to the highest usable address.
// Reserved/MMIO spans above that are simply outside the map and never
// allocatable.
// 1. Size the bitmap to cover every frame up to the highest RAM address —
// including reserved RAM, so those frames are trackable (e.g. to free the
// boot buffers later). Only MMIO (device address space) is excluded.
// Everything starts unallocatable; usable regions are freed below.
var highest: u64 = 0;
for (regs) |r| {
if (r.kind != .usable) continue;
if (r.kind == .mmio) continue;
const end = r.base + r.pages * page_size;
if (end > highest) highest = end;
}
+6 -5
View File
@@ -41,13 +41,14 @@ pub const page_size = 4096;
/// native memory description into these kinds, so the kernel never learns what
/// booted it. [[arch]] keeps the same discipline for CPU code.
pub const MemoryKind = enum(u32) {
/// Free RAM the kernel may allocate.
/// Free RAM the kernel may allocate. Each boot path folds its own transient
/// memory into this once it's genuinely free (e.g. the UEFI loader classifies
/// boot-services memory as usable after ExitBootServices), so the kernel never
/// has to know about boot-protocol-specific "reclaimable" states.
usable,
/// Firmware, MMIO, the kernel image, our own boot buffers — never hand out.
/// Firmware, MMIO, the kernel image, our own boot buffers, the boot stack —
/// never hand out.
reserved,
/// Usable once the kernel is done with boot-time structures (e.g. UEFI boot
/// services memory, which is free after ExitBootServices).
reclaimable,
/// ACPI tables: parse, then reclaim.
acpi_tables,
/// ACPI non-volatile storage: preserve across sleep, do not allocate.