commit 997b21c4f4c97481bc190ce3108c9ea7acd02886
parent 8e1e9d0353d992a7f7cb738e999e59c4d20585f0
Author: Sylvia Ivory <git@sivory.net>
Date: Wed, 11 Mar 2026 09:26:40 -0700
Update procmap to use page tables
Diffstat:
5 files changed, 170 insertions(+), 102 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,10 +251,10 @@ pub const InnerCacheableAttr = enum(u3) {
};
pub const TranslationTableBaseRegister0 = packed struct(u32) {
- 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() {
@@ -356,7 +357,13 @@ pub const ContextId = packed struct(u32) {
pub const FirstLevelDescriptor = packed struct(u32) {
pub const Fault = packed struct(u30) {
- _ignore_31_2: 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,
@@ -366,11 +373,12 @@ pub const FirstLevelDescriptor = packed struct(u32) {
_implementation_9: u1 = 0,
ap: AccessPermissions,
tex: u3,
- apx: AccessPermissions,
+ apx: AccessPermissionsExtended,
shared: bool,
not_global: bool,
is_supersection: bool = false,
- section_base_address: u22,
+ _reserved_19: u1 = 0,
+ section_base_address: u12,
};
pub const SuperSection = packed struct(u30) {
b: u1,
@@ -380,12 +388,13 @@ pub const FirstLevelDescriptor = packed struct(u32) {
_implementation_9: u1,
ap: AccessPermissions,
tex: u3,
- apx: AccessPermissions,
+ apx: AccessPermissionsExtended,
shared: bool,
not_global: bool,
is_supersection: bool = true,
+ _reserved_19: u1 = 0,
base_address_35_32: u4,
- section_base_address: u18,
+ section_base_address: u8,
};
pub const Type = enum(u2) {
@@ -397,6 +406,7 @@ pub const FirstLevelDescriptor = packed struct(u32) {
ty: Type,
descriptor: packed union {
fault: Fault,
+ coarse: Coarse,
section: Section,
supersection: SuperSection,
},
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
@@ -2,9 +2,11 @@ const std = @import("std");
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");
+const uart = @import("./devices/mini-uart.zig");
-pub const MAX_ENTRIES = 8;
+const pi = @import("./root.zig");
pub const DEVICE_ATTR: pinned.PinnedAttribute = .{
.asid = 0,
@@ -40,51 +42,54 @@ pub const ProcEntry = struct {
ReadOnly,
};
- addr: usize,
+ addr: u32,
// TODO; handle other byte sizes
- n_bytes: usize,
+ n_bytes: u32,
type: ProcEntryType,
domain: u4,
};
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);
+ 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,
+ };
const MB = 1024 * 1024;
// 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(.{ .addr = 0x20000000, .n_bytes = MB, .type = .Device, .domain = dom });
+ try map.push(.{ .addr = 0x20100000, .n_bytes = MB, .type = .Device, .domain = dom });
+ try map.push(.{ .addr = 0x20200000, .n_bytes = MB, .type = .Device, .domain = dom });
// Program Code
- map.push(.{ .addr = 0x00000000, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
+ try map.push(.{ .addr = 0x00000000, .n_bytes = MB, .type = .ReadWrite, .domain = 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(.{ .addr = 0x7E00000, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
+ try map.push(.{ .addr = @intFromPtr(map.pt.ptr), .n_bytes = MB, .type = .ReadWrite, .domain = 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(.{ .addr = pi.STACK_ADDRESS - MB, .n_bytes = MB, .type = .ReadWrite, .domain = dom });
+ try map.push(.{ .addr = pi.STACK_INTERRUPT_ADDRESS - MB, .n_bytes = MB, .type = .ReadWrite, .domain = 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,
@@ -92,25 +97,38 @@ pub const ProcMap = struct {
};
attr.domain = entry.domain;
- try pinned.set(@truncate(n), entry.addr, entry.addr, attr);
+ uart.print("pinning 0x{X} -> 0x{X}\n", .{ entry.addr, entry.addr });
+ _ = try pt.set(self.pt, entry.addr, entry.addr, attr);
+ uart.print(" check 0x{X} -> 0x{X}\n", .{ entry.addr, pt.translate(self.pt, entry.addr) orelse 0 });
}
}
- var null_ptr: [4096 * 4]u8 align(1 << 14) = .{0} ** (4096 * 4);
pub fn enable(self: *const Self) !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)));
- mmu.enable();
+ try pt.pt_switch(self.pt, 0x140E, 1);
+
+ const ttbc = mmu.TranslationTableBaseControl.get();
+ uart.print("N={}\n", .{@intFromEnum(ttbc.boundary_size)});
- pinned.lockdown_print_entries();
+ for (self.entries.items) |entry| {
+ pt.check_entry(entry.addr);
+ }
+
+ const ctx = mmu.ContextId.get();
+ uart.print("asid={} pid={X}\n", .{ ctx.asid, ctx.pid });
+
+ mmu.sync_pte();
- for (0..self.current_entry) |n| {
- const entry = self.entries[n];
+ uart.print(" check 0x{X} -> 0x{X}\n", .{ 0x7EFFE24, pt.translate(self.pt, 0x7EFFE24) orelse 0 });
+
+ mmu.enable();
+ uart.print("checking entries\n", .{});
+ for (self.entries.items) |entry| {
if (!try pinned.is_pinned(entry.addr)) return pinned.Error.ExpectedPinned;
}
}
diff --git a/pi/pt.zig b/pi/pt.zig
@@ -9,24 +9,21 @@ pub const Error = error{
InvalidPageCount,
MmuEnabled,
MmuDisabled,
-} || std.mem.Allocator;
+} || std.mem.Allocator.Error;
pub fn init(alloc: std.mem.Allocator, count: u16) Error![]mmu.FirstLevelDescriptor {
if (count != 4096) return Error.InvalidPageCount;
- const invalid: mmu.FirstLevelDescriptor = .{
- .ty = .Fault,
- .descriptor = .{ .fault = .{ ._ignore_31_2 = 0 } },
- };
+ const fault_page: mmu.FirstLevelDescriptor = .{ .ty = .Fault, .descriptor = .{ .fault = .{} } };
- var page_table = try alloc.allocWithOptions(
+ const page_table = try alloc.alignedAlloc(
mmu.FirstLevelDescriptor,
+ std.mem.Alignment.fromByteUnits(1 << 14),
count,
- std.mem.Alignment.fromByteUnits(14),
- null,
);
- @memset(&page_table, invalid);
+ @memset(page_table, fault_page);
+ mmu.sync_pte();
return page_table;
}
@@ -35,60 +32,44 @@ pub fn dupe(alloc: std.mem.Allocator, pt: []mmu.FirstLevelDescriptor) Error![]mm
return try alloc.dupe(mmu.FirstLevelDescriptor, pt);
}
-pub fn enable() Error!void {
- if (mmu.is_enabled()) return Error.MmuEnabled;
- mmu.enable();
- if (!mmu.is_enabled()) return Error.MmuDisabled;
-}
-
-pub fn disable() Error!void {
- if (!mmu.is_enabled()) return Error.MmuDisabled;
- mmu.disable();
- if (mmu.is_enabled()) return Error.MmuEnabled;
-}
-
-pub fn pt_switch(pt: []mmu.FirstLevelDescriptor, pid: u32, asid: u8) Error!void {
+pub fn pt_switch(pt: []mmu.FirstLevelDescriptor, pid: u24, asid: u8) Error!void {
mmu.set_context_ttbr0(.{ .asid = asid, .pid = pid }, .{
- // just guess wcgw
- .ecc = false,
- .shared = true,
- .inner_cacheable = false,
- .outer_cacheable_attr = .OuterNoncacheable,
- .translation_table_base = @truncate(@intFromPtr(pt) >> 5),
+ .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 cs = mem.enter_critical_section();
- defer cs.exit();
-
const index = va >> 20;
- const page = pt[index];
- const mem_attr: mmu.LockdownAttributesRegister.PageTableEncodingManual = @bitCast(attr.mem_attributes);
-
- page.ty = .Section;
- page.descriptor = .{ .section = .{
- .not_global = if (attr.scope == .Global) false else true,
- .ap = attr.permission,
- .apx = attr.permission_x,
- .domain = attr.domain,
- .never_execute = false,
- .b = mem_attr.b,
- .c = mem_attr.c,
- .tex = mem_attr.tex,
- .shared = true,
- .section_base_address = @truncate(pa >> 10),
- } };
+ 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 &page;
+ return &pt[index];
}
pub fn get(pt: []mmu.FirstLevelDescriptor, va: u32) !?*mmu.FirstLevelDescriptor {
const index = va >> 20;
- const page = pt[index];
+ const page = &pt[index];
if (page.ty == .Fault) return null;
@@ -105,8 +86,52 @@ pub fn translate(pt: []mmu.FirstLevelDescriptor, va: u32) ?u32 {
// Not dealing with super sections
if (section.is_supersection) return null;
- const base = @as(u32, section.section_base_address) << 10;
+ // Top 12 bits of address
+ const base = @as(u32, section.section_base_address) << 20;
// Probably correct
- return base | (index << 22);
+ 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();
- for (0..map.current_entry) |n| {
- const va = map.entries[n].addr + 0xFAA0;
+ for (map.entries.items, 0..) |entry, n| {
+ const va = entry.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", .{});
}
}