mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-30 14:58:21 +00:00
loader: initial loader code
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
const r = @import("root");
|
||||
const mem = r.mem;
|
||||
|
||||
pub const PMapLevel = struct {
|
||||
shift: u6,
|
||||
mask: usize,
|
||||
leaf: bool,
|
||||
};
|
||||
|
||||
const MapItem = struct {
|
||||
pa: usize,
|
||||
len: usize,
|
||||
flags: mem.MapFlags,
|
||||
};
|
||||
|
||||
/// 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`.
|
||||
const ContiguousSource = struct {
|
||||
flags: mem.MapFlags,
|
||||
size: usize,
|
||||
base_va: usize,
|
||||
base_pa: usize,
|
||||
|
||||
pub fn next(self: *const ContiguousSource, _: usize) MapItem {
|
||||
return .{
|
||||
.pa = self.base_pa,
|
||||
.len = self.size,
|
||||
.flags = self.flags,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// 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: mem.MapFlags,
|
||||
|
||||
pub fn next(self: *const AllocatingSource, _: usize) MapItem {
|
||||
const pa = mem.alloc_page();
|
||||
return .{
|
||||
.pa = pa,
|
||||
.len = r.page_size,
|
||||
.flags = self.flags,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn level_shift(level: usize) u6 {
|
||||
return r.arch.levels[level].shift;
|
||||
}
|
||||
|
||||
fn page_size(level: usize) usize {
|
||||
return @as(usize, 1) << level_shift(level);
|
||||
}
|
||||
|
||||
fn index_for_level(va: usize, level: usize) usize {
|
||||
return (va >> level_shift(level)) & r.arch.levels[level].mask;
|
||||
}
|
||||
|
||||
pub const PMap = struct {
|
||||
const Self = @This();
|
||||
|
||||
const num_levels = r.arch.levels.len;
|
||||
const entries_per_table = 512;
|
||||
const index_mask = entries_per_table - 1;
|
||||
|
||||
root_pa: usize,
|
||||
|
||||
fn is_leaf_level(level: usize) bool {
|
||||
return r.arch.levels[level].leaf;
|
||||
}
|
||||
|
||||
fn leaf_level_enabled(level: usize) bool {
|
||||
return is_leaf_level(level) and r.arch.is_leaf_level_enabled(level);
|
||||
}
|
||||
|
||||
fn cursor(self: *Self, va: usize) Cursor {
|
||||
var c = Cursor{
|
||||
.pmap = self,
|
||||
.va = va,
|
||||
.tables = undefined,
|
||||
.top_level = num_levels - 1,
|
||||
};
|
||||
|
||||
c.tables[num_levels - 1] = @ptrFromInt(r.mem.p2v(self.root_pa));
|
||||
return c;
|
||||
}
|
||||
|
||||
pub fn activate(self: *Self) void {
|
||||
r.arch.activate(self.root_pa);
|
||||
}
|
||||
|
||||
pub fn map_from(self: *Self, va: usize, 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);
|
||||
remain -|= item.len;
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a contiguous virtual address range to a contiguous physical address range.
|
||||
pub fn map_contiguous_range(self: *Self, va: usize, pa: usize, size: usize, flags: r.mem.MapFlags) void {
|
||||
const src = ContiguousSource{
|
||||
.flags = flags,
|
||||
.size = size,
|
||||
.base_va = va,
|
||||
.base_pa = pa,
|
||||
};
|
||||
|
||||
self.map_from(va, size, src);
|
||||
}
|
||||
|
||||
/// Map a single virtual page to a physical page.
|
||||
pub fn map_page(self: *Self, va: usize, pa: usize, flags: r.mem.MapFlags) void {
|
||||
self.map_contiguous_range(va, pa, r.page_size, flags);
|
||||
}
|
||||
|
||||
/// Map a virtual address range to physical addresses
|
||||
pub fn map_range_allocating(self: *Self, va: usize, size: usize, flags: mem.MapFlags) void {
|
||||
const src = AllocatingSource{
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
self.map_from(va, size, src);
|
||||
}
|
||||
|
||||
pub const Cursor = struct {
|
||||
pmap: *Self,
|
||||
va: usize,
|
||||
/// Cached pointers to each level's page table, filled as the cursor
|
||||
/// descends. Only indices in `[0, top_level]` are valid.
|
||||
tables: [num_levels][*]r.arch.Pte,
|
||||
/// Index of the highest level for which `tables` currently holds a
|
||||
/// valid pointer. Descends toward 0 (the leaf) as `walk_down` runs.
|
||||
/// Reset upward in `advance` when the VA crosses a level boundary.
|
||||
top_level: usize,
|
||||
|
||||
fn walk_down(self: *Cursor, target_level: usize, allocate: bool) error{PageNotMapped}!void {
|
||||
var current_level = self.top_level;
|
||||
|
||||
while (current_level > target_level) {
|
||||
const idx = index_for_level(self.va, current_level);
|
||||
var pte = r.arch.Pte.load(&self.tables[current_level][idx]);
|
||||
|
||||
if (!pte.present) {
|
||||
if (!allocate) {
|
||||
return error.PageNotMapped;
|
||||
}
|
||||
|
||||
const new_table_pa = mem.alloc_page();
|
||||
|
||||
const table_ptr: [*]r.arch.Pte = @ptrFromInt(r.mem.p2v(new_table_pa));
|
||||
@memset(table_ptr[0..entries_per_table], r.arch.Pte.zero());
|
||||
|
||||
pte = r.arch.make_table_pte(new_table_pa);
|
||||
self.tables[current_level][idx] = pte;
|
||||
}
|
||||
|
||||
const child_va = r.mem.p2v(pte.address());
|
||||
self.tables[current_level - 1] = @ptrFromInt(child_va);
|
||||
self.top_level -= 1;
|
||||
current_level -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_down_resolve(self: *Cursor, target_level: usize) bool {
|
||||
self.walk_down(target_level, false, 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: usize, size: usize, flags: mem.MapFlags) void {
|
||||
var remain = size;
|
||||
var current_pa = pa;
|
||||
|
||||
while (remain > 0) {
|
||||
const target_level = self.choose_target_level(current_pa, remain);
|
||||
|
||||
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)] =
|
||||
r.arch.make_leaf_pte(current_pa, flags, target_level);
|
||||
|
||||
const step = page_size(target_level);
|
||||
current_pa += step;
|
||||
remain -= step;
|
||||
|
||||
self.advance(step);
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the cursor's virtual address by `offset` bytes and
|
||||
/// invalidate any cached table pointers that no longer apply.
|
||||
/// The cursor tracks the highest level whose index is still valid
|
||||
/// for the new VA, `walk_down` will re-descend from there on the
|
||||
/// next mapping operation.
|
||||
pub fn advance(self: *Cursor, offset: usize) void {
|
||||
const old_va = self.va;
|
||||
self.va +%= offset;
|
||||
|
||||
var level: usize = num_levels - 1;
|
||||
while (level > 0) : (level -= 1) {
|
||||
const shift = level_shift(level);
|
||||
if ((old_va >> shift) != (self.va >> shift)) {
|
||||
self.top_level = level;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user