commit c68ab508f8259acdb1757731aa6dcdc551b30e97
parent 5259c04eabc55e65a124f79b963d2f4e45d2f9e5
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 12 Mar 2026 19:12:03 -0700
Merge branch 'lab/page-table'
Diffstat:
| M | pi/mmu.zig | | | 112 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
| M | pi/pinned.zig | | | 2 | +- |
| M | pi/procmap.zig | | | 109 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
| A | pi/pt.zig | | | 146 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | programs/pinned-lookup.zig | | | 27 | +++++++++++++++++++++------ |
| A | programs/pt-asid.zig | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 375 insertions(+), 87 deletions(-)
diff --git a/pi/mmu.zig b/pi/mmu.zig
@@ -50,6 +50,11 @@ pub const AccessPermissions = enum(u2) {
UserReadWrite = 0b11,
};
+pub const AccessPermissionsExtended = enum(u1) {
+ SupervisorRW = 0,
+ SupervisorRO = 1,
+};
+
pub const LockdownPA = packed struct(u32) {
pub const Size = enum(u2) {
@"16MB" = 0b00,
@@ -57,10 +62,6 @@ pub const LockdownPA = packed struct(u32) {
@"64KB" = 0b10,
@"1MB" = 0b11,
};
- pub const AccessPermissionsExtended = enum(u1) {
- SupervisorRW = 0,
- SupervisorRO = 1,
- };
pub const Security = enum(u1) {
Secure = 0,
NonSecure = 1,
@@ -209,7 +210,7 @@ pub const DomainAccessControlRegister = packed struct(u32) {
};
domains.set();
- mem.barrier(.Write);
+ mem.barrier(.Instruction);
}
};
@@ -250,46 +251,10 @@ pub const InnerCacheableAttr = enum(u3) {
};
pub const TranslationTableBaseRegister0 = packed struct(u32) {
- // // This is pain
- // pub const TranslationTableBase = packed union {
- // @"16KB": packed struct {
- // translation_table_base: u15,
- // _reserved_13_5: u9,
- // },
- // @"8KB": packed struct {
- // translation_table_base: u16,
- // _reserved_12_5: u8,
- // },
- // @"4KB": packed struct {
- // translation_table_base: u17,
- // _reserved_11_5: u7,
- // },
- // @"2KB": packed struct {
- // translation_table_base: u18,
- // _reserved_10_5: u6,
- // },
- // @"1KB": packed struct {
- // translation_table_base: u19,
- // _reserved_9_5: u5,
- // },
- // @"512B": packed struct {
- // translation_table_base: u20,
- // _reserved_8_5: u4,
- // },
- // @"256B": packed struct {
- // translation_table_base: u21,
- // _reserved_7_5: u3,
- // },
- // @"128B": packed struct {
- // translation_table_base: u22,
- // _reserved_6_5: u2,
- // },
- // };
-
- inner_cacheable: bool,
- shared: bool,
- ecc: bool,
- outer_cacheable_attr: OuterCacheableAttr,
+ inner_cacheable: bool = false,
+ shared: bool = false,
+ ecc: bool = false,
+ outer_cacheable_attr: OuterCacheableAttr = .OuterNoncacheable,
translation_table_base: u27,
pub inline fn get() @This() {
@@ -390,6 +355,63 @@ pub const ContextId = packed struct(u32) {
}
};
+pub const FirstLevelDescriptor = packed struct(u32) {
+ pub const Fault = packed struct(u30) {
+ _ignore_31_2: u30 = 0,
+ };
+ pub const Coarse = packed struct(u30) {
+ _reserved_4_2: u3,
+ domain: u4,
+ _implementation_9: u1 = 0,
+ coarse_base_address: u22,
+ };
+ pub const Section = packed struct(u30) {
+ b: u1,
+ c: u1,
+ never_execute: bool,
+ domain: u4,
+ _implementation_9: u1 = 0,
+ ap: AccessPermissions,
+ tex: u3,
+ apx: AccessPermissionsExtended,
+ shared: bool,
+ not_global: bool,
+ is_supersection: bool = false,
+ _reserved_19: u1 = 0,
+ section_base_address: u12,
+ };
+ pub const SuperSection = packed struct(u30) {
+ b: u1,
+ c: u1,
+ never_execute: bool,
+ base_address_39_36: u4,
+ _implementation_9: u1,
+ ap: AccessPermissions,
+ tex: u3,
+ apx: AccessPermissionsExtended,
+ shared: bool,
+ not_global: bool,
+ is_supersection: bool = true,
+ _reserved_19: u1 = 0,
+ base_address_35_32: u4,
+ section_base_address: u8,
+ };
+
+ pub const Type = enum(u2) {
+ Fault = 0b00,
+ CoarsePageTable = 0b01,
+ Section = 0b10,
+ };
+
+ ty: Type,
+ descriptor: packed union {
+ fault: Fault,
+ coarse: Coarse,
+ section: Section,
+ supersection: SuperSection,
+ },
+};
+
pub const TranslationMode = enum {
PrivilegedRead,
PrivilegedWrite,
diff --git a/pi/pinned.zig b/pi/pinned.zig
@@ -117,7 +117,7 @@ pub const PinnedAttribute = struct {
domain: u4,
scope: mmu.LockdownVA.Scope,
permission: mmu.AccessPermissions,
- permission_x: mmu.LockdownPA.AccessPermissionsExtended,
+ permission_x: mmu.AccessPermissionsExtended,
mem_attributes: mmu.LockdownAttributesRegister.PageTableEncodingPreset,
};
diff --git a/pi/procmap.zig b/pi/procmap.zig
@@ -1,10 +1,13 @@
+// TODO; handle different page sizes
const std = @import("std");
+const uart = @import("./devices/mini-uart.zig");
const pinned = @import("./pinned.zig");
const mmu = @import("./mmu.zig");
-const pi = @import("./root.zig");
+const mem = @import("./mem.zig");
+const pt = @import("./pt.zig");
-pub const MAX_ENTRIES = 8;
+const pi = @import("./root.zig");
pub const DEVICE_ATTR: pinned.PinnedAttribute = .{
.asid = 0,
@@ -33,6 +36,12 @@ const READ_ONLY_ATTR: pinned.PinnedAttribute = .{
.mem_attributes = .OuterInnerNoncacheable,
};
+const MB = 1024 * 1024;
+
+pub const Error = error{
+ FailedTranslation,
+};
+
pub const ProcEntry = struct {
pub const ProcEntryType = enum {
Device,
@@ -40,78 +49,108 @@ pub const ProcEntry = struct {
ReadOnly,
};
- addr: usize,
- // TODO; handle other byte sizes
- n_bytes: usize,
+ virt_addr: u32,
+ phys_addr: u32,
+ n_bytes: u32,
type: ProcEntryType,
- domain: u4,
+ domain: u4 = 1,
+ asid: u8 = 0,
+
+ pub fn identity(addr: u32, n_bytes: u32, ty: ProcEntryType, dom: u4) ProcEntry {
+ return .{
+ .virt_addr = addr,
+ .phys_addr = addr,
+ .n_bytes = n_bytes,
+ .domain = dom,
+ .type = ty,
+ };
+ }
};
pub const ProcMap = struct {
- entries: [MAX_ENTRIES]ProcEntry,
- current_entry: usize,
+ pt: []mmu.FirstLevelDescriptor,
+ entries: std.ArrayList(ProcEntry),
+ alloc: std.mem.Allocator,
const Self = @This();
- pub fn init(dom: u4) Self {
- var map = std.mem.zeroes(ProcMap);
-
- const MB = 1024 * 1024;
+ pub fn init(alloc: std.mem.Allocator, dom: u4) !Self {
+ var map: ProcMap = .{
+ .pt = try pt.init(alloc, 4096),
+ .entries = try .initCapacity(alloc, 8),
+ .alloc = alloc,
+ };
// BCM2835
- map.push(.{ .addr = 0x20000000, .n_bytes = MB, .type = .Device, .domain = dom });
- map.push(.{ .addr = 0x20100000, .n_bytes = MB, .type = .Device, .domain = dom });
- map.push(.{ .addr = 0x20200000, .n_bytes = MB, .type = .Device, .domain = dom });
+ try map.push(ProcEntry.identity(0x20000000, MB * 3, .Device, dom));
// Program Code
- map.push(.{ .addr = 0x00000000, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
+ try map.push(ProcEntry.identity(0x00000000, MB, .ReadWrite, dom));
- // Heap would live here if we used one
- // map.push(.{ .addr = 0x00100000, .n_bytes = MB, .type = .ReadWrite, .dom = dom });
+ // Page table
+ try map.push(ProcEntry.identity(@intFromPtr(map.pt.ptr), MB, .ReadWrite, dom));
+ try map.push(ProcEntry.identity(0x7E00000, MB, .ReadWrite, dom));
// Stacks
- map.push(.{ .addr = pi.STACK_ADDRESS - MB, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
- map.push(.{ .addr = pi.STACK_INTERRUPT_ADDRESS - MB, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
+ try map.push(ProcEntry.identity(pi.STACK_ADDRESS - MB, MB, .ReadWrite, dom));
+ try map.push(ProcEntry.identity(pi.STACK_INTERRUPT_ADDRESS - MB, MB, .ReadWrite, dom));
return map;
}
- pub fn push(self: *Self, entry: ProcEntry) void {
- self.entries[self.current_entry] = entry;
- self.current_entry += 1;
+ pub fn push(self: *Self, entry: ProcEntry) !void {
+ try self.entries.append(self.alloc, entry);
}
fn pin(self: *const Self) !void {
- for (0..self.current_entry) |n| {
- const entry = self.entries[n];
-
+ for (self.entries.items) |entry| {
var attr = switch (entry.type) {
.Device => DEVICE_ATTR,
.ReadWrite => READ_WRITE_ATTR,
.ReadOnly => READ_ONLY_ATTR,
};
attr.domain = entry.domain;
+ attr.asid = entry.asid;
- try pinned.set(@truncate(n), entry.addr, entry.addr, attr);
+ for (0..(entry.n_bytes / MB)) |offset| {
+ const va = entry.virt_addr + offset * MB;
+ const pa = entry.phys_addr + offset * MB;
+ _ = try pt.set(self.pt, va, pa, attr);
+ }
}
}
- var null_ptr: [4096 * 4]u8 align(1 << 14) = .{0} ** (4096 * 4);
- pub fn enable(self: *const Self) !void {
+ pub fn enable(self: *const Self, asid: u8) !void {
mmu.init();
+ mmu.DomainAccessControlRegister.set_all(.Manager);
try self.pin();
- mmu.DomainAccessControlRegister.set_all(.Client);
- mmu.set_context_ttbr0(.{ .asid = 1, .pid = 128 << 8 }, @bitCast(@intFromPtr(&null_ptr)));
+ try pt.pt_switch(self.pt, 0x140E, asid);
+ mmu.sync_pte();
mmu.enable();
- pinned.lockdown_print_entries();
+ for (self.entries.items) |entry| {
+ const res = mmu.va_translation_cw(entry.virt_addr, .PrivilegedRead);
+ if (res.aborted) return Error.FailedTranslation;
- for (0..self.current_entry) |n| {
- const entry = self.entries[n];
+ const physical_base = @as(u32, res.inner.success.address) << 10;
+ const offset = entry.virt_addr & 0xFFF;
- if (!try pinned.is_pinned(entry.addr)) return pinned.Error.ExpectedPinned;
+ if ((physical_base | offset) != entry.phys_addr) return Error.FailedTranslation;
}
}
+
+ pub fn disable(self: *const Self) !void {
+ _ = self;
+ mmu.disable();
+ }
+
+ pub fn dupe(self: *const Self) !Self {
+ return .{
+ .alloc = self.alloc,
+ .entries = try self.entries.clone(self.alloc),
+ .pt = try pt.dupe(self.alloc, self.pt),
+ };
+ }
};
diff --git a/pi/pt.zig b/pi/pt.zig
@@ -0,0 +1,146 @@
+const std = @import("std");
+
+const procmap = @import("./procmap.zig");
+const pinned = @import("./pinned.zig");
+const mmu = @import("./mmu.zig");
+const mem = @import("./mem.zig");
+
+pub const Error = error{
+ InvalidPageCount,
+ MmuEnabled,
+ MmuDisabled,
+} || std.mem.Allocator.Error;
+
+pub fn init(alloc: std.mem.Allocator, count: u16) Error![]mmu.FirstLevelDescriptor {
+ if (count != 4096) return Error.InvalidPageCount;
+
+ const fault_page: mmu.FirstLevelDescriptor = .{ .ty = .Fault, .descriptor = .{ .fault = .{} } };
+
+ const page_table = try alloc.alignedAlloc(
+ mmu.FirstLevelDescriptor,
+ std.mem.Alignment.fromByteUnits(1 << 14),
+ count,
+ );
+
+ @memset(page_table, fault_page);
+ mmu.sync_pte();
+
+ return page_table;
+}
+
+pub fn dupe(alloc: std.mem.Allocator, pt: []mmu.FirstLevelDescriptor) Error![]mmu.FirstLevelDescriptor {
+ const page_table = try alloc.alignedAlloc(
+ mmu.FirstLevelDescriptor,
+ std.mem.Alignment.fromByteUnits(1 << 14),
+ pt.len,
+ );
+
+ @memcpy(page_table, pt);
+ mmu.sync_pte();
+
+ return page_table;
+}
+
+pub fn pt_switch(pt: []mmu.FirstLevelDescriptor, pid: u24, asid: u8) Error!void {
+ mmu.set_context_ttbr0(.{ .asid = asid, .pid = pid }, .{
+ .translation_table_base = @truncate(@intFromPtr(pt.ptr) >> 5),
+ });
+}
+
+pub fn set(pt: []mmu.FirstLevelDescriptor, va: u32, pa: u32, attr: pinned.PinnedAttribute) Error!*mmu.FirstLevelDescriptor {
+ const index = va >> 20;
+
+ const mem_attr: mmu.LockdownAttributesRegister.PageTableEncodingManual = @bitCast(@intFromEnum(attr.mem_attributes));
+
+ pt[index] = .{
+ .ty = .Section,
+ .descriptor = .{
+ .section = .{
+ .not_global = if (attr.scope == .Global) false else true,
+ .ap = attr.permission,
+ .apx = @enumFromInt(0),
+ .domain = attr.domain,
+ .never_execute = false,
+ .b = mem_attr.b,
+ .c = mem_attr.c,
+ .tex = mem_attr.tex,
+ .shared = false,
+ // Get top 12 bits of PA
+ .section_base_address = @truncate(pa >> 20),
+ },
+ },
+ };
+
+ mmu.sync_pte();
+
+ return &pt[index];
+}
+
+pub fn get(pt: []mmu.FirstLevelDescriptor, va: u32) !?*mmu.FirstLevelDescriptor {
+ const index = va >> 20;
+ const page = &pt[index];
+
+ if (page.ty == .Fault) return null;
+
+ return page;
+}
+
+pub fn translate(pt: []mmu.FirstLevelDescriptor, va: u32) ?u32 {
+ const index = va >> 20;
+ const page = pt[index];
+
+ // Not dealing with with coarse/fault
+ if (page.ty != .Section) return null;
+ const section = page.descriptor.section;
+ // Not dealing with super sections
+ if (section.is_supersection) return null;
+
+ // Top 12 bits of address
+ const base = @as(u32, section.section_base_address) << 20;
+
+ // Probably correct
+ return base | (va & 0x000F_FFFF);
+}
+
+pub fn check_entry(va: u32) void {
+ const index = va >> 20;
+
+ const ttbr0 = mmu.TranslationTableBaseRegister0.get();
+ const ttb: [*]mmu.FirstLevelDescriptor = @ptrFromInt(@as(u32, ttbr0.translation_table_base) << 5);
+
+ const entry = ttb[index].descriptor.section;
+ @import("devices/mini-uart.zig").print(
+ \\index = {}
+ \\va = 0x{X}
+ \\pa = 0b{b} / 0x{X}
+ \\0b{b} = nG
+ \\0b{b} = S
+ \\0b{b} = APX
+ \\0b{b} = TEX
+ \\0b{b} = AP
+ \\0b{b} = IMP
+ \\0b{b} = domain
+ \\0b{b} = XN
+ \\0b{b} = C
+ \\0b{b} = B
+ \\0b{b} = tag
+ \\
+ \\
+ , .{
+ index,
+ va,
+ entry.section_base_address,
+ entry.section_base_address,
+ if (entry.not_global) @as(u1, 1) else @as(u1, 0),
+ if (entry.shared) @as(u1, 1) else @as(u1, 0),
+ @intFromEnum(entry.apx),
+ entry.tex,
+ @intFromEnum(entry.ap),
+ entry._implementation_9,
+ entry.domain,
+ if (entry.never_execute) @as(u1, 1) else @as(u1, 0),
+ entry.c,
+ entry.b,
+ @intFromEnum(ttb[index].ty),
+ });
+}
diff --git a/programs/pinned-lookup.zig b/programs/pinned-lookup.zig
@@ -1,16 +1,29 @@
const std = @import("std");
const pi = @import("pi");
+const interrupts = pi.interrupts;
+const faults = pi.faults;
const uart = pi.devices.mini_uart;
+fn data_abort_handler(regs: interrupts.Registers) void {
+ const far = faults.FAR.get();
+
+ uart.print("got fault on 0x{X} from 0x{X}\n", .{ far, regs.pc });
+ pi.reboot();
+}
+
pub fn main() !void {
- const map: pi.procmap.ProcMap = .init(1);
- try map.enable();
+ var buffer: [1024 * 1024]u8 = undefined;
+ var fba: std.heap.FixedBufferAllocator = .init(&buffer);
+ const alloc = fba.allocator();
+
+ interrupts.set_exception_handler(.DataAbort, data_abort_handler);
- pi.pinned.lockdown_print_entries();
+ const map: pi.procmap.ProcMap = try .init(alloc, 1);
+ try map.enable(1);
- for (0..map.current_entry) |n| {
- const va = map.entries[n].addr + 0xFAA0;
+ for (map.entries.items, 0..) |entry, n| {
+ const va = entry.virt_addr;
uart.print("checking entry n={d} addr=0x{X}\n", .{ n, va });
uart.print(" translation result=0x{X}\n", .{try pi.pinned.get(va)});
@@ -31,6 +44,8 @@ pub fn main() !void {
uart.print("checking invalid\n", .{});
if (try pi.pinned.is_pinned(0xDEADFAA0)) {
- uart.print("pinned invalid VA\n", .{});
+ uart.print("mapped invalid VA\n", .{});
+ } else {
+ uart.print("did not mapped invalid VA\n", .{});
}
}
diff --git a/programs/pt-asid.zig b/programs/pt-asid.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+const pi = @import("pi");
+
+const interrupts = pi.interrupts;
+const faults = pi.faults;
+const uart = pi.devices.mini_uart;
+const mem = pi.mem;
+
+fn data_abort_handler(regs: interrupts.Registers) void {
+ pi.mmu.disable();
+ const far = faults.FAR.get();
+
+ uart.print("got fault on 0x{X} from 0x{X}\n", .{ far, regs.pc });
+ pi.reboot();
+}
+
+fn mb(n: u32) u32 {
+ return n * 1024 * 1024;
+}
+
+pub fn main() !void {
+ var buffer: [1024 * 1024]u8 = undefined;
+ var fba: std.heap.FixedBufferAllocator = .init(&buffer);
+ const alloc = fba.allocator();
+
+ interrupts.set_exception_handler(.DataAbort, data_abort_handler);
+
+ const user_addr: *u32 = @ptrFromInt(mb(16));
+ const phys_1: *u32 = @ptrFromInt(@intFromPtr(user_addr) + mb(1));
+ const phys_2: *u32 = @ptrFromInt(@intFromPtr(user_addr) + mb(2));
+
+ var map_1: pi.procmap.ProcMap = try .init(alloc, 1);
+ var map_2 = try map_1.dupe();
+
+ try map_1.push(.{
+ .asid = 1,
+ .n_bytes = mb(1),
+ .type = .ReadWrite,
+ .phys_addr = @intFromPtr(phys_1),
+ .virt_addr = @intFromPtr(user_addr),
+ });
+ try map_2.push(.{
+ .asid = 2,
+ .n_bytes = mb(1),
+ .type = .ReadWrite,
+ .phys_addr = @intFromPtr(phys_2),
+ .virt_addr = @intFromPtr(user_addr),
+ });
+
+ mem.put_u32(phys_1, 0x1111_1111);
+ mem.put_u32(phys_2, 0x2222_2222);
+
+ uart.print("enabling with ASID=1\n", .{});
+ try map_1.enable(1);
+ uart.print("ASID 1 got 0x{X}\n", .{mem.get_u32(user_addr)});
+ mem.put_u32(user_addr, 1);
+ try map_1.disable();
+ uart.print("checking write: 0x{X}\n", .{mem.get_u32(phys_1)});
+
+ uart.print("enabling with ASID=2\n", .{});
+ try map_2.enable(2);
+ uart.print("ASID 2 got 0x{X}\n", .{mem.get_u32(user_addr)});
+ mem.put_u32(user_addr, 2);
+ try map_2.disable();
+ uart.print("checking write: 0x{X}\n", .{mem.get_u32(phys_2)});
+}