sylveos

Toy Operating System
Log | Files | Refs

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:
Mpi/mmu.zig | 38++++++++++++++++++++++++--------------
Mpi/pinned.zig | 2+-
Mpi/procmap.zig | 78++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mpi/pt.zig | 127+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mprograms/pinned-lookup.zig | 27+++++++++++++++++++++------
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", .{}); } }