sylveos

Toy Operating System
Log | Files | Refs

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:
Mpi/mmu.zig | 112+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mpi/pinned.zig | 2+-
Mpi/procmap.zig | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Api/pt.zig | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mprograms/pinned-lookup.zig | 27+++++++++++++++++++++------
Aprograms/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)}); +}