loader: map pfndb and cleanup

This commit is contained in:
Loïc Grégoire
2026-07-18 14:06:36 -04:00
parent 3c72101228
commit 96d2bee857
12 changed files with 159 additions and 187 deletions
+2 -2
View File
@@ -14,8 +14,8 @@ pub fn virt_to_phys(vaddr: r.VAddr) usize {
}
pub const hhdm_base = 0xffff800000000000;
pub const kernel_heap_base = 0xffffe00000000000;
pub const pfndb_base = 0xffffff0000000000;
pub const kernel_heap_base = 0xffffc00000000000;
pub const pfndb_base = 0xffffd00000000000;
pub const levels = [_]mi.PMapLevel{
.{ .shift = 12, .mask = 0x1ff, .leaf = true }, // 4K
-2
View File
@@ -6,8 +6,6 @@ const config = @import("config");
pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
mi.phys.init(boot_info);
mi.kernel_space.pmap.activate();
mi.phys.init_pfndb();
mi.zone.early_init();
mi.vmem.init();
mi.heap.init();
+6 -64
View File
@@ -18,8 +18,6 @@ pub var low_memory_threshold: usize = 0;
/// Threshold at which reclamation stops.
pub var high_memory_threshold: usize = 0;
var bootstrapped = false;
var early_alloc_entry_idx: usize = 0;
var memory_map: *r.BootInfo.MemMap = undefined;
var early_allocs: usize = 0;
var list_lock: ke.SpinLock = .init();
@@ -31,27 +29,6 @@ var free_pages: usize = 0;
pub var usable_memory: std.atomic.Value(usize) = .init(0);
pub fn early_alloc() usize {
while (early_alloc_entry_idx < memory_map.entry_count) {
const entry = &memory_map.entries[early_alloc_entry_idx];
if (entry.type != .Free or entry.size < mm.page_size) {
early_alloc_entry_idx += 1;
continue;
}
const addr = entry.base;
entry.base += mm.page_size;
entry.size -= mm.page_size;
early_allocs += mm.page_size;
return addr;
}
@panic("mm/phys: Out of memory for early allocation");
}
// Wait for pages to be available.
// List lock is held.
fn wait_for_pages(old_ipl: ke.Ipl) void {
@@ -167,7 +144,7 @@ pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
free_list.init();
free_page_event.init(.Notification);
var total_usable_memory: usize = 0;
var total_usable_memory: usize = boot_info.memory_map.loader_memory_used;
log.info("physical memory map:", .{});
for (0..boot_info.memory_map.entry_count) |i| {
@@ -175,50 +152,18 @@ pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
log.info("[{x:0>16}-{x:0>16}] {s}", .{ entry.base, entry.base + entry.size, @tagName(entry.type) });
if (entry.type == .Free) {
if (entry.type == .Free or entry.type == .LoaderReclaimable) {
total_usable_memory += entry.size;
}
}
const pfndb_size_required = @sizeOf(mm.Page) * (std.math.divCeil(usize, total_usable_memory, mm.page_size) catch unreachable);
log.info("using {} KiB for pfndb ({} bytes per page)", .{ pfndb_size_required / 1024, @sizeOf(mm.Page) });
// Create the kernel pagemap from the early allocator.
// Get the kernel pagemap from the loader,
// we are already in it so no need to activate it.
mi.impl.init_kernel();
// Now map the PFN database.
for (0..boot_info.memory_map.entry_count) |i| {
const entry = boot_info.memory_map.entries[i];
if (entry.type != .Free or entry.size < mm.page_size) {
continue;
}
const npages = std.math.divCeil(usize, entry.size, mm.page_size) catch unreachable;
// 1. Calculate the exact virtual address range needed for this region's page structs.
const start_pfn: usize = mm.page_to_pfn(entry.base);
const exact_start = mi.impl.pfndb_base + (start_pfn * @sizeOf(mm.Page));
const exact_end = exact_start + (npages * @sizeOf(mm.Page));
// 2. Ensure the addresses are aligned on page boundaries.
const map_start = std.mem.alignBackward(usize, exact_start, mm.page_size);
const map_end = std.mem.alignForward(usize, exact_end, mm.page_size);
// 3. Map the virtual pages.
mi.kernel_space.pmap.map_range_allocating(
map_start,
map_end - map_start,
.{
.read = true,
.write = true,
.global = true,
},
.Boot,
);
}
// Calculate the minimum amount of free memory we accept,
// this formula is from Linux and does not grow linearly; it would be wasteful
// to take a fixed percentage of memory from a large server.
@@ -232,13 +177,12 @@ pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
low_memory_threshold = min_memory_threshold + gap;
high_memory_threshold = low_memory_threshold + gap;
}
pub fn init_pfndb() void {
// Initialize the free list from the PFNDB linkage.
for (0..memory_map.entry_count) |i| {
const entry = memory_map.entries[i];
if (entry.type != .Free or entry.size < mm.page_size) {
if ((entry.type != .Free and entry.type != .LoaderReclaimable) or entry.size < mm.page_size) {
continue;
}
@@ -253,6 +197,4 @@ pub fn init_pfndb() void {
_ = usable_memory.fetchAdd(mm.page_size, .monotonic);
}
}
bootstrapped = true;
}
+12 -90
View File
@@ -1,28 +1,4 @@
//! Generic radix-tree page map implementation.
//!
//! `RadixPmap` is a comptime-parameterized page table walker that implements
//! virtual-to-physical address mapping over a multi-level radix tree (e.g.
//! x86-64's four-level PT). The caller supplies an
//! `Impl` type that describes the tree's level structure and provides
//! platform-specific PTE constructors.
//!
//! The `Impl` type passed to `RadixPmap` must provide:
//!
//! - `levels: []const Level`: ordered from the leaf up to the root,
//! each entry carrying the VA index shift and whether that level can
//! hold a leaf (large-page) mapping.
//! - `is_leaf_level_enabled(level: usize) bool`:runtime gate that
//! controls whether large pages are used at a given level (e.g. to
//! disable 1 GiB pages on hardware that lacks them).
//! - `Pte`: the PTE type. Must be zero-initializable and expose a
//! `present: bool` field and an `address() PAddr` method.
//! - `make_table_pte(pa: PAddr) Pte`: construct a non-leaf PTE pointing
//! to a child page table at `pa`.
//! - `make_leaf_pte(pa: PAddr, flags: MapFlags, level: usize) Pte`:
//! construct a leaf PTE mapping `pa` with the given flags at the
//! chosen level.
//! - `activate(root_pa: PAddr) void`: install the root page table into
//! the CPU (e.g. write CR3 or satp).
//! Generic page map manipulation code.
const r = @import("root");
const rtl = @import("rtl");
@@ -37,11 +13,6 @@ const MapItem = struct {
flags: mm.MapFlags,
};
const Allocator = enum {
Boot,
Normal,
};
/// A mapping source that maps a single contiguous virtual-to-physical region.
/// All calls to `next` return the same physical base address and size, so this
/// is intended to be consumed exactly once by `map_from`.
@@ -60,22 +31,6 @@ const ContiguousSource = struct {
}
};
/// A mapping source that allocates physical pages on demand. Each call to `next`
/// returns a single page, so this is intended to be consumed by `map_from` in a loop until the entire range is mapped.
const AllocatingSource = struct {
flags: mm.MapFlags,
allocator: Allocator,
pub fn next(self: *const AllocatingSource, _: r.VAddr) MapItem {
const pa = if (self.allocator == .Boot) mi.phys.early_alloc() else mi.phys.alloc();
return .{
.pa = pa,
.len = mm.page_size,
.flags = self.flags,
};
}
};
fn level_shift(level: usize) u6 {
return mi.impl.levels[level].shift;
}
@@ -121,19 +76,19 @@ pub const PMap = struct {
mi.impl.activate(self.root_pa);
}
pub fn map_from(self: *Self, va: r.VAddr, size: usize, source: anytype, comptime allocator: Allocator) void {
pub fn map_from(self: *Self, va: r.VAddr, size: usize, source: anytype) void {
var c = self.cursor(va);
var remain = size;
while (remain > 0) {
const item = source.next(c.va);
c.map_range(item.pa, item.len, item.flags, allocator);
c.map_range(item.pa, item.len, item.flags);
remain -|= item.len;
}
}
/// Map a contiguous virtual address range to a contiguous physical address range.
pub fn map_contiguous_range(self: *Self, va: r.VAddr, pa: r.PAddr, size: usize, flags: mm.MapFlags, comptime allocator: Allocator) void {
pub fn map_contiguous_range(self: *Self, va: r.VAddr, pa: r.PAddr, size: usize, flags: mm.MapFlags) void {
const src = ContiguousSource{
.flags = flags,
.size = size,
@@ -141,17 +96,7 @@ pub const PMap = struct {
.base_pa = pa,
};
self.map_from(va, size, src, allocator);
}
/// Map a virtual address range to physical addresses allocated on demand by the boot allocator.
pub fn map_range_allocating(self: *Self, va: r.VAddr, size: usize, flags: mm.MapFlags, comptime allocator: Allocator) void {
const src = AllocatingSource{
.allocator = allocator,
.flags = flags,
};
self.map_from(va, size, src, allocator);
self.map_from(va, size, src);
}
pub fn query(self: *Self, va: r.VAddr) ?r.PAddr {
@@ -164,7 +109,7 @@ pub const PMap = struct {
if (!pte.present) return null;
if (level == 0 or (leaf_level_enabled(level) and pte.huge_page)) {
if (level == 0) {
return pte.address() + (va & (page_size(level) - 1));
}
@@ -232,7 +177,7 @@ pub const PMap = struct {
/// Reset upward in `advance` when the VA crosses a level boundary.
top_level: usize,
fn walk_down(self: *Cursor, target_level: usize, allocate: bool, comptime allocator: Allocator) error{PageNotMapped}!void {
fn walk_down(self: *Cursor, target_level: usize, allocate: bool) error{PageNotMapped}!void {
var current_level = self.top_level;
while (current_level > target_level) {
@@ -244,7 +189,7 @@ pub const PMap = struct {
return error.PageNotMapped;
}
const new_table_pa = if (allocator == .Boot) mi.phys.early_alloc() else mi.phys.alloc();
const new_table_pa = mi.phys.alloc();
const table_ptr: [*]mi.impl.Pte = @ptrFromInt(mm.p2v(new_table_pa));
@memset(table_ptr[0..entries_per_table], std.mem.zeroes(mi.impl.Pte));
@@ -261,41 +206,18 @@ pub const PMap = struct {
}
pub fn walk_down_resolve(self: *Cursor, target_level: usize) bool {
self.walk_down(target_level, false, .Normal) catch return false;
self.walk_down(target_level, false) catch return false;
return true;
}
/// Choose the largest page size that satisfies the alignment and
/// size constraints of the current VA, PA, and remaining byte count.
/// Tries large pages first; falls back to 4K.
fn choose_target_level(self: *Cursor, pa: usize, remain: usize) usize {
var level: usize = num_levels;
while (level > 0) {
level -= 1;
if (!leaf_level_enabled(level)) continue;
const pg_size = page_size(level);
const align_mask = pg_size - 1;
if (remain >= pg_size and
(self.va & align_mask) == 0 and
(pa & align_mask) == 0)
{
return level;
}
}
@panic("no leaf level can map current alignment/size");
}
pub fn map_range(self: *Cursor, pa: r.PAddr, size: usize, flags: mm.MapFlags, comptime allocator: Allocator) void {
pub fn map_range(self: *Cursor, pa: r.PAddr, size: usize, flags: mm.MapFlags) void {
var remain = size;
var current_pa = pa;
while (remain > 0) {
const target_level = self.choose_target_level(current_pa, remain);
const target_level = 0;
self.walk_down(target_level, true, allocator) catch @panic("walk_down failed during map_range");
self.walk_down(target_level, true) catch @panic("walk_down failed during map_range");
const table = self.tables[target_level];
table[index_for_level(self.va, target_level)] =
+12
View File
@@ -2,6 +2,7 @@ pub const private = @import("private.zig");
const std = @import("std");
const rtl = @import("rtl");
const r = @import("root");
pub const page_size = 4096;
@@ -30,6 +31,17 @@ pub const Page = extern union {
},
};
comptime {
if (@sizeOf(Page) != r.BootInfo.page_struct_size) {
@compileError(
std.fmt.comptimePrint(
"'Page' size mismatch between loader and kernel: kernel={} loader={}",
.{ @sizeOf(Page), r.BootInfo.page_struct_size },
),
);
}
}
pub const MapFlags = packed struct {
read: bool = true,
write: bool = false,