From 307d87d6d66ed20be5f3df5cca540e8e866349ef Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Tue, 25 Mar 2025 17:01:11 +0200 Subject: [PATCH 01/10] WIP: Add a userspace entry to riscv64 --- src/arch/riscv64/boot.zig | 2 +- src/arch/riscv64/context.S | 24 +++++++ src/arch/riscv64/context.zig | 64 +++++++++++++++++ src/arch/riscv64/regs.zig | 2 + src/arch/riscv64/vmm.zig | 132 +++++++++++++++++++++++++++++++---- src/kernel.zig | 18 +++-- src/mem.zig | 2 +- src/mem/vmalloc.zig | 21 +++--- src/mem/vmm.zig | 51 +++++++++++++- src/thread.zig | 38 +++++++++- 10 files changed, 320 insertions(+), 34 deletions(-) diff --git a/src/arch/riscv64/boot.zig b/src/arch/riscv64/boot.zig index 81d3ed1..e466c19 100644 --- a/src/arch/riscv64/boot.zig +++ b/src/arch/riscv64/boot.zig @@ -37,7 +37,7 @@ fn bsp_upper_entry(real_address: usize, unused: usize) callconv(.C) noreturn { exception.init(); debug.log.set_write_fn(&sbi.debug_print_byte); - kernel.mem.PhysicalAddress.g_virtualize_base = 0; + kernel.mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE; kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range(); // Setup physical memory management diff --git a/src/arch/riscv64/context.S b/src/arch/riscv64/context.S index 2596b12..08d954b 100644 --- a/src/arch/riscv64/context.S +++ b/src/arch/riscv64/context.S @@ -4,6 +4,7 @@ .global __rv64_enter_task .global __rv64_switch_task +.global __rv64_task_enter_user .global __rv64_task_enter_kernel .macro LOAD_TASK_STATE @@ -44,6 +45,29 @@ sd s0, 13 * 8(sp) .endm +.set SSTATUS_SPP, (1 << 8) +.set SSTATUS_SPIE, (1 << 5) + +__rv64_task_enter_user: + // TODO setup user thread pointer + + ld a0, (sp) // argument + ld ra, 16(sp) // entry + ld sp, 8(sp) // stack + + // Clear SPP to zero to indicate a return to U-mode + li t1, SSTATUS_SPP + not t1, t1 + + csrr t0, sstatus + // TODO enable interrupts via SPIE + // ori t0, t0, SSTATUS_SPIE + and t0, t0, t1 + csrw sstatus, t0 + csrw sepc, ra + + sret + __rv64_task_enter_kernel: ld a0, (sp) // argument ld ra, 8(sp) // entry diff --git a/src/arch/riscv64/context.zig b/src/arch/riscv64/context.zig index 34f90f7..df33329 100644 --- a/src/arch/riscv64/context.zig +++ b/src/arch/riscv64/context.zig @@ -1,4 +1,11 @@ const thread = @import("../../thread.zig"); +const mem = @import("../../mem.zig"); +const kernel = @import("../../kernel.zig"); +const regs = @import("regs.zig"); +const vmm = @import("vmm.zig"); + +const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; +const log = kernel.log; fn idle_function() callconv(.naked) noreturn { asm volatile ("j ."); @@ -7,6 +14,7 @@ fn idle_function() callconv(.naked) noreturn { extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn; extern fn __rv64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void; extern fn __rv64_task_enter_kernel() callconv(.C) noreturn; +extern fn __rv64_task_enter_user() callconv(.C) noreturn; pub const Context = extern struct { const STACK_SIZE: usize = 8192; @@ -14,12 +22,52 @@ pub const Context = extern struct { // Has to be exactly at offset 0x00, used in assembly. kstack: thread.KStack(STACK_SIZE), + satp: u64 = 0, + /// Constructs an idle context struct. pub fn idle() @This() { const entry = @intFromPtr(&idle_function); return Context.kernel(entry, 0); } + pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() { + const space_physical = address_space.physical_address(); + const space_asid = address_space.asid(); + + const satp = regs.SATP.Bits { + .PPN = @truncate(space_physical.raw >> 12), + .ASID = @truncate(space_asid), + .MODE = .sv39 + }; + + var ks = thread.KStack(STACK_SIZE).create(); + const entry = @intFromPtr(&__rv64_task_enter_user); + + ks.push(pc); + ks.push(sp); + ks.push(arg); + + ks.push(0); // x8/s0/fp + ks.push(0); // x9/s1 + ks.push(0); // x18/s2 + ks.push(0); // x19/s3 + ks.push(0); // x20/s4 + ks.push(0); // x21/s5 + ks.push(0); // x22/s6 + ks.push(0); // x23/s7 + ks.push(0); // x24/s8 + ks.push(0); // x25/s9 + ks.push(0); // x26/s10 + ks.push(0); // x27/s11 + ks.push(0); // x4/gp + ks.push(entry); // x1/ra return address + + return .{ + .kstack = ks, + .satp = @bitCast(satp) + }; + } + /// Constructs a kernel task context with entry point in `pc` and an `arg`ument. pub fn kernel(pc: usize, arg: usize) @This() { var ks = thread.KStack(STACK_SIZE).create(); @@ -48,13 +96,29 @@ pub const Context = extern struct { /// Low-level task context entry function. pub fn enter(self: *@This()) noreturn { + self.load_state(); __rv64_enter_task(self); } /// Low-level task context switch function. pub fn switch_from(self: *@This(), from: *@This()) void { + from.store_state(); + self.load_state(); __rv64_switch_task(self, from); } + + fn load_state(self: *@This()) void { + if (self.satp != 0) { + log.info("Load SATP = 0x{x}", .{self.satp}); + regs.SATP.set(self.satp); + } else { + vmm.load_kernel_table(); + } + } + + fn store_state(self: *@This()) void { + _ = self; + } }; comptime { diff --git a/src/arch/riscv64/regs.zig b/src/arch/riscv64/regs.zig index 4af215e..48749bd 100644 --- a/src/arch/riscv64/regs.zig +++ b/src/arch/riscv64/regs.zig @@ -4,6 +4,8 @@ fn Register(comptime name: []const u8, comptime bits: type) type { else => bits, }; return enum(repr) { + pub const Bits = bits; + pub fn set(value: repr) void { asm volatile ("csrw " ++ name ++ ", %[value]" : diff --git a/src/arch/riscv64/vmm.zig b/src/arch/riscv64/vmm.zig index 43f4ed8..4efc59c 100644 --- a/src/arch/riscv64/vmm.zig +++ b/src/arch/riscv64/vmm.zig @@ -1,17 +1,23 @@ +const std = @import("std"); const sync = @import("../../sync.zig"); const regs = @import("regs.zig"); const mem = @import("../../mem.zig"); -const arch = @import("../../kernel.zig").arch; +const kernel = @import("../../kernel.zig"); +const log = kernel.log; +const arch = kernel.arch; const PhysicalAddress = mem.PhysicalAddress; +const AtomicU8 = std.atomic.Value(u8); pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000; pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511; +pub const VIRTUALIZE_BASE: usize = KERNEL_VIRTUAL_BASE + L1.SIZE; +pub const VIRTUALIZE_BASE_L1I: usize = L1.index(VIRTUALIZE_BASE); // 16 GiB const EARLY_MAPPING_SIZE: usize = 16; -pub const L1 = mem.TranslationLevel(30); -pub const L2 = mem.TranslationLevel(21); +pub const L1 = mem.TranslationLevel(30, L2); +pub const L2 = mem.TranslationLevel(21, L3); pub const L3 = mem.vmm.L3; pub const RawEntry = packed struct(u64) { @@ -45,7 +51,7 @@ pub const RawEntry = packed struct(u64) { } pub fn clear(self: *@This(), mask: @This()) void { - const lhs = @as(*u64, @bitCast(self)); + const lhs = @as(*u64, @ptrCast(self)); const rhs = @as(u64, @bitCast(mask)); lhs.* &= ~rhs; } @@ -83,12 +89,14 @@ pub fn TableEntry(comptime Level: type) type { } pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() { - flags.clear(.{ .r = true, .w = true, .x = true }); - return .{ .raw = flags.make_union(.{ + var f = flags; + f.clear(.{ .r = true, .w = true, .x = true }); + return .{ .raw = f.make_union(.{ .address = @as(u39, @intCast(addr.raw >> 12)), .v = true, }) }; } + }; } @@ -98,16 +106,108 @@ pub fn Table(comptime Level: type) type { entries: [512]Entry align(4096), + pub const Error = mem.vmm.AddressSpaceError; + pub fn empty() @This() { return .{ .entries = [_]Entry{.INVALID} ** 512 }; } + pub fn allocate_empty() Error!*@This() { + const page = mem.phys.alloc_page() orelse return error.out_of_pages; + const table = @as(*@This(), @ptrFromInt(page.virtualize())); + for (0..512) |i| { + table.entry(i).* = .INVALID; + } + return table; + } + + pub fn physical_address(self: *const @This()) PhysicalAddress { + return PhysicalAddress.from_virtualized(@intFromPtr(self)); + } + pub inline fn entry(self: *@This(), index: usize) *Entry { return &self.entries[index]; } + + pub usingnamespace if (Level.NextLevel) |NextLevel| struct { + pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) { + _ = self; + _ = index; + @panic("TODO"); + } + + pub fn get_or_create_next_level(self: *Table(Level), index: usize) Error!*Table(NextLevel) { + const ent = self.entry(index); + + if (ent.raw.v) { + // TODO handle mixed hugepages + tables + if (ent.raw.r or ent.raw.w or ent.raw.x) { + @panic("TODO: handle mixed hugepages and tables"); + } + // It is a table + @panic("OOO"); + } else { + // Allocate a new entry + const table = try Table(NextLevel).allocate_empty(); + const physical = table.physical_address(); + ent.* = TableEntry(Level).table(physical, .{}); + return table; + } + } + } else struct {}; }; } +pub const ProcessAddressSpace = struct { + l1: *Table(L1), + asid: u8, + + pub const Error = mem.vmm.AddressSpaceError; + + var g_asid: AtomicU8 = .{ .raw = 1 }; + + pub fn init() Error!ProcessAddressSpace { + const table = try Table(L1).allocate_empty(); + // Copy kernel's mappings + for (KERNEL_VIRTUAL_L1I..512) |i| { + table.entry(i).* = g_fixed.entry(i).*; + } + const asid = g_asid.fetchAdd(1, .seq_cst); + return .{ .l1 = table, .asid = asid }; + } + + pub fn physical_address(self: *const @This()) PhysicalAddress { + return self.l1.physical_address(); + } + + pub fn map_page(self: *@This(), virtual: usize, physical: PhysicalAddress) Error!void { + // TODO align check on both virtual and physical + + const l1i = L1.index(virtual); + const l2i = L2.index(virtual); + const l3i = L3.index(virtual); + + const l2 = try self.l1.get_or_create_next_level(l1i); + const l3 = try l2.get_or_create_next_level(l2i); + + const entry = l3.entry(l3i); + + if (entry.raw.v) { + @panic("TODO: handle already present"); + } + + entry.* = TableEntry(L3).page(physical, .{ + .r = true, + .w = true, + .x = true, + .u = true, + }); + flush_vma_asid(virtual, self.asid); + + log.debug("Map 0x{x} -> page 0x{x}", .{ virtual, physical.raw }); + } +}; + var g_fixed = Table(L1).empty(); var g_fixed_lock: sync.Spinlock = .{}; @@ -120,13 +220,15 @@ pub fn unmap_early() void { const guard = g_fixed_lock.lock_irqsave(); defer guard.release(); for (0..EARLY_MAPPING_SIZE) |i| { - g_fixed.entry(i).* = .page( - .{ .raw = L1.address(i) }, - .{ .r = true, .w = true }, - ); + g_fixed.entry(i).* = .INVALID; } } +pub fn load_kernel_table() void { + const address = @as(usize, @intFromPtr(&g_fixed)); + regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 }); +} + pub fn map_early(real_address: usize) void { const real_l1 = L1.index(real_address); @@ -138,14 +240,20 @@ pub fn map_early(real_address: usize) void { ); } + for (0..EARLY_MAPPING_SIZE) |i| { + g_fixed.entry(i + VIRTUALIZE_BASE_L1I).* = .page( + .{ .raw = L1.address(i) }, + .{ .r = true, .w = true }, + ); + } + // Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded g_fixed.entry(KERNEL_VIRTUAL_L1I).* = .page( .{ .raw = L1.address(real_l1) }, .{ .r = true, .w = true, .x = true }, ); - const address = @as(usize, @intFromPtr(&g_fixed)); - regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 }); + load_kernel_table(); } pub inline fn flush_vma(page: usize) void { diff --git a/src/kernel.zig b/src/kernel.zig index 58e5375..ef9f42c 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -38,15 +38,21 @@ noinline fn f1(arg: usize, c: usize) void { /// * Physical memory must be initialized. /// * (optional) Logging should be set up. pub export fn kernel_main() callconv(.C) noreturn { - log.write("\x1B[2J", .{}); var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena"); thread.Queue.init_this_cpu(&a); - const pc = @intFromPtr(&f0); - for (0..4) |i| { - const t = thread.Thread.create(&a, pc, i); - thread.enqueue(t); - } + const t = thread.test_create_user_from_code(&a, &[_]u8 { + 0x6F, 0x00, 0x00, 0x00 + }); + thread.enqueue(t); + + log.info("Test", .{}); + // log.write("\x1B[2J", .{}); + // const pc = @intFromPtr(&f0); + // for (0..4) |i| { + // const t = thread.Thread.create_kernel(&a, pc, i); + // thread.enqueue(t); + // } thread.enter(); } diff --git a/src/mem.zig b/src/mem.zig index 822ff23..7eb2de8 100644 --- a/src/mem.zig +++ b/src/mem.zig @@ -46,7 +46,7 @@ pub const PhysicalAddress = packed struct(u64) { /// /// Panics if the virtual address provided is outside of virtualizable memory range. pub fn from_virtualized(virt: usize) @This() { - if ((virt < g_virtualize_base) || (virt - g_virtualize_base > g_virtualize_size)) { + if (virt < g_virtualize_base or virt - g_virtualize_base > g_virtualize_size) { @panic("Invalid virtualized physical address"); } diff --git a/src/mem/vmalloc.zig b/src/mem/vmalloc.zig index ed04d20..8afc857 100644 --- a/src/mem/vmalloc.zig +++ b/src/mem/vmalloc.zig @@ -1,9 +1,8 @@ const std = @import("std"); +const Arena = @import("../arena.zig").Arena; const Range = @import("../util/range.zig").Range; -const Allocator = std.mem.Allocator; - /// Describes a single virtual memory range. /// /// Used by `VirtualMemoryAllocator` to track allocated/used regions. @@ -16,12 +15,12 @@ pub const VirtualMemoryRange = struct { /// Virtual memory allocator implementation. pub const VirtualMemoryAllocator = struct { - gpa: Allocator, + arena: *Arena, head: ?*VirtualMemoryRange = null, outer_range: Range(u64), /// One of errors returned by the allocation logic + underlying allocator error. - pub const Error = error{ already_exists, invalid_region, cannot_fit } || Allocator.Error; + pub const Error = error{ already_exists, invalid_region, cannot_fit }; /// An iterator over VM regions being freed. pub const FreeIterator = struct { @@ -46,7 +45,7 @@ pub const VirtualMemoryAllocator = struct { } // Free it self.current = n.next; - self.vma.gpa.destroy(n); + // self.vma.arena.destroy(n); return xs; } @@ -64,7 +63,7 @@ pub const VirtualMemoryAllocator = struct { return xs; } else { // Insert a new node after the current one - const new_node = try self.vma.gpa.create(VirtualMemoryRange); + const new_node = self.vma.arena.create(VirtualMemoryRange); new_node.* = VirtualMemoryRange { .range = .{ .start = xs.end(), .len = n.range.end() - xs.end() }, .prev = n, @@ -90,10 +89,10 @@ pub const VirtualMemoryAllocator = struct { }; /// Creates a new instance of a virtual memory allocator. - pub fn init(gpa: Allocator, outer_range: Range(u64)) @This() { + pub fn init(arena: *Arena, outer_range: Range(u64)) @This() { return .{ .outer_range = outer_range, - .gpa = gpa, + .arena = arena, }; } @@ -108,7 +107,7 @@ pub const VirtualMemoryAllocator = struct { const gap_before_first = if (self.head) |n| (n.range.start - self.outer_range.start) else self.outer_range.len; if (gap_before_first >= pfn_count) { - var new_node = try self.gpa.create(VirtualMemoryRange); + var new_node = self.arena.create(VirtualMemoryRange); new_node.range = .{ .start = self.outer_range.start, .len = pfn_count }; new_node.next = self.head; @@ -137,7 +136,7 @@ pub const VirtualMemoryAllocator = struct { if (gap >= pfn_count) { // Insert after this const result = n.range.end(); - var new_node = try self.gpa.create(VirtualMemoryRange); + var new_node = self.arena.create(VirtualMemoryRange); new_node.prev = n; new_node.next = n.next; new_node.range = .{ .start = result, .len = pfn_count }; @@ -181,7 +180,7 @@ pub const VirtualMemoryAllocator = struct { node = n.next; } - var new_node = try self.gpa.create(VirtualMemoryRange); + var new_node = self.arena.create(VirtualMemoryRange); new_node.range = region; diff --git a/src/mem/vmm.zig b/src/mem/vmm.zig index 1f7f483..a59e946 100644 --- a/src/mem/vmm.zig +++ b/src/mem/vmm.zig @@ -1,18 +1,30 @@ //! Platform-independent virtual memory management definitions. const mem = @import("../mem.zig"); +const arena = @import("../arena.zig"); +const vmalloc = @import("vmalloc.zig"); +const kernel = @import("../kernel.zig"); +const sync = @import("../sync.zig"); + +const arch = kernel.arch; +const Arena = arena.Arena; /// Last virtual memory translation level. Always 4KiB on all platforms. -pub const L3 = mem.TranslationLevel(12); +pub const L3 = mem.TranslationLevel(12, null); /// Page size is 4KiB on all platforms. pub const PAGE_SIZE: usize = L3.SIZE; +pub const AddressSpaceError = error{ + out_of_pages, +}; + /// Helper function to construct a "Translation Level" struct type from a bit shift. -pub fn TranslationLevel(comptime shift: usize) type { +pub fn TranslationLevel(comptime shift: usize, comptime Next: ?type) type { return struct { pub const SHIFT: usize = shift; pub const SIZE: usize = 1 << shift; + pub const NextLevel = Next; pub inline fn index(addr: usize) usize { return (addr >> shift) & 511; @@ -43,3 +55,38 @@ pub fn TranslationLevel(comptime shift: usize) type { } }; } + +pub const ProcessAddressSpace = struct { + inner: arch.vmm.ProcessAddressSpace, + allocator: vmalloc.VirtualMemoryAllocator, + lock: sync.Spinlock, + + pub fn init(a: *Arena) AddressSpaceError!ProcessAddressSpace { + // 0x200000..0x600000 + const inner = try arch.vmm.ProcessAddressSpace.init(); + const allocator = vmalloc.VirtualMemoryAllocator.init(a, .{ .start = 512, .len = 1024 }); + + return .{ .inner = inner, .allocator = allocator, .lock = .{} }; + } + + pub fn map_single_page( + self: *@This(), + virtual: usize, + physical: mem.PhysicalAddress, + ) AddressSpaceError!void { + self.lock.lock(); + defer self.lock.release(); + + // TODO If allocation succeeds, but mapping fails, rollback + self.allocator.insert(.{ .start = L3.page_number(virtual), .len = 1 }) catch @panic("TODO error"); + try self.inner.map_page(virtual, physical); + } + + pub fn physical_address(self: *const @This()) mem.PhysicalAddress { + return self.inner.physical_address(); + } + + pub fn asid(self: *const @This()) u64 { + return self.inner.asid; + } +}; diff --git a/src/thread.zig b/src/thread.zig index 20ad958..8a60d3b 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -7,6 +7,8 @@ const arch = @import("kernel.zig").arch; const log = @import("debug.zig").log; const mem = @import("mem.zig"); +const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; + /// Per-CPU thread queue structure. pub const Queue = struct { /// Idle task context. Used when there are no other tasks running. @@ -92,8 +94,11 @@ pub const Thread = struct { /// Previous thread in the queue. prev: ?*Thread = null, + // TODO move to process + address_space: ?ProcessAddressSpace = null, + /// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument. - pub fn create(a: *arena.Arena, pc: usize, arg: usize) *Thread { + pub fn create_kernel(a: *arena.Arena, pc: usize, arg: usize) *Thread { const thread = a.create(Thread); thread.* = .{ .allocator = a, @@ -102,6 +107,22 @@ pub const Thread = struct { return thread; } + pub fn create_user( + a: *arena.Arena, + address_space: ProcessAddressSpace, + pc: usize, + sp: usize, + arg: usize, + ) *Thread { + const thread = a.create(Thread); + thread.* = .{ + .allocator = a, + .address_space = address_space, + .arch_context = arch.Context.user(&address_space, pc, sp, arg), + }; + return thread; + } + /// Enters the thread, does not return. pub fn enter(self: *@This()) noreturn { self.arch_context.enter(); @@ -170,3 +191,18 @@ pub fn enter() noreturn { pub fn yield() void { Queue.t_this_cpu.?.yield(); } + +pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) *Thread { + var address_space = ProcessAddressSpace.init(a) catch @panic("TODO"); + + // Map 0x200000 + const page = mem.phys.alloc_page() orelse @panic("TODO error"); + address_space.map_single_page(0x200000, page) catch @panic("TODO error map"); + + const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..code.len]; + @memcpy(page_data, code); + + const thread = Thread.create_user(a, address_space, 0x200000, 0, 0); + + return thread; +} -- 2.53.0 From 000b434c960d1fe5f06670b22eb27dd5383cc0a2 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 14:09:17 +0200 Subject: [PATCH 02/10] WIP: Userspace entry code for both platforms --- src/arch.zig | 12 ++- src/arch/aarch64/boot.zig | 13 ++- src/arch/aarch64/context.S | 30 +++++- src/arch/aarch64/context.zig | 79 ++++++++++++---- src/arch/aarch64/exception.zig | 1 + src/arch/aarch64/regs.zig | 13 ++- src/arch/aarch64/vmm.zig | 166 +++++++++++++++++++++++++++++---- src/arch/riscv64/boot.zig | 2 + src/arch/riscv64/context.S | 7 +- src/arch/riscv64/context.zig | 84 ++++++----------- src/arch/riscv64/vmm.zig | 39 +++++--- src/kernel.zig | 26 +++--- src/mem/vmalloc.zig | 26 +++++- src/mem/vmm.zig | 21 ++++- src/thread.zig | 39 ++++++-- src/util/rangemap.zig | 42 ++++----- 16 files changed, 435 insertions(+), 165 deletions(-) diff --git a/src/arch.zig b/src/arch.zig index c0618c5..928e1ba 100644 --- a/src/arch.zig +++ b/src/arch.zig @@ -4,10 +4,18 @@ const std = @import("std"); const builtin = @import("builtin"); -pub const impl = switch (builtin.cpu.arch) { +pub const cpu: enum { + riscv64, + aarch64, +} = switch (builtin.cpu.arch) { + .riscv64 => .riscv64, + .aarch64 => .aarch64, + else => @compileError("Unsupported architecture"), +}; + +pub const impl = switch (cpu) { .riscv64 => @import("arch/riscv64.zig"), .aarch64 => @import("arch/aarch64.zig"), - else => @compileError("Unsupported architecture"), }; pub const vmm = impl.vmm; diff --git a/src/arch/aarch64/boot.zig b/src/arch/aarch64/boot.zig index 34b774e..a8f3410 100644 --- a/src/arch/aarch64/boot.zig +++ b/src/arch/aarch64/boot.zig @@ -13,8 +13,10 @@ extern const __aa64_bsp_stack_top: u8; var g_dtb_address: u64 = undefined; -fn early_debug_print(byte: u8) void { - const address = 0x9000000; +fn early_debug_print_high(byte: u8) void { + // TODO this is incorrect: writes should come to a memory region marked as device memory, + // "virtualize" range is normal memory. + const address = 0x9000000 + vmm.VIRTUALIZE_BASE; @as(*volatile u32, @ptrFromInt(address)).* = byte; } @@ -44,14 +46,15 @@ fn aa64_bsp_upper_entry(real_address: u64) callconv(.C) noreturn { arch.barrier(.acq_rel); aa64_relocate_kernel(rel_offset, rela_start, rela_end); + vmm.unmap_early(); arch.barrier(.acq_rel); - log.set_write_fn(&early_debug_print); + log.set_write_fn(&early_debug_print_high); exception.init(); - mem.PhysicalAddress.g_virtualize_base = 0; - mem.PhysicalAddress.g_virtualize_size = 16 << 30; + mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE; + mem.PhysicalAddress.g_virtualize_size = 16 << vmm.L1.SHIFT; setup_memory_from_fdt(real_address); diff --git a/src/arch/aarch64/context.S b/src/arch/aarch64/context.S index 7a2cbc7..b70d10e 100644 --- a/src/arch/aarch64/context.S +++ b/src/arch/aarch64/context.S @@ -3,6 +3,7 @@ .global __aa64_enter_task .global __aa64_switch_task .global __aa64_task_enter_kernel +.global __aa64_task_enter_user .set CONTEXT_SIZE, (12 * 8) @@ -28,14 +29,37 @@ .pushsection .text +__aa64_task_enter_user: + // x0 == sp, ... + ldr x0, [sp, #16 * 0] + msr sp_el0, x0 + + // x0 == arg, x1 == entry + ldp x0, x1, [sp, #16 * 1] + add sp, sp, #32 + + msr elr_el1, x1 + + mov x1, #(1 << 9) + msr spsr_el1, x1 + + mov lr, xzr + + dsb ish + isb sy + + eret + __aa64_task_enter_kernel: // arg, entry - ldp x0, lr, [sp] - add sp, sp, #16 + ldp x0, x1, [sp] + // return address + ldr lr, [sp, #16] + add sp, sp, #24 // TODO enter task via eret to EL1t - ret + br x1 __aa64_switch_task: // x0 -- "dst" context diff --git a/src/arch/aarch64/context.zig b/src/arch/aarch64/context.zig index 4ab95b4..d58598e 100644 --- a/src/arch/aarch64/context.zig +++ b/src/arch/aarch64/context.zig @@ -1,30 +1,83 @@ const thread = @import("../../thread.zig"); +const vmm = @import("vmm.zig"); +const mem = @import("../../mem.zig"); +const regs = @import("regs.zig"); +const kernel = @import("../../kernel.zig"); -fn idle_function() callconv(.naked) noreturn { - asm volatile ("b ."); -} +const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; +const arch = kernel.arch; extern fn __aa64_enter_task(cx: *Context) callconv(.C) noreturn; extern fn __aa64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void; extern fn __aa64_task_enter_kernel() callconv(.C) noreturn; +extern fn __aa64_task_enter_user() callconv(.C) noreturn; pub const Context = extern struct { const STACK_SIZE: usize = 16384; kstack: thread.KStack(STACK_SIZE), + ttbr0: u64 = 0, + pub fn idle() Context { - const entry = @intFromPtr(&idle_function); - return Context.kernel(entry, 0); + return Context.kernel(&thread.idle_function, 0); } - pub fn kernel(pc: usize, arg: usize) Context { - var ks = thread.KStack(STACK_SIZE).create(); - const entry = @intFromPtr(&__aa64_task_enter_kernel); + pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() { + const space_physical = address_space.physical_address(); + const space_asid = address_space.asid(); + var ks = thread.KStack(STACK_SIZE).create(); + + const ttbr0 = @as(u64, @bitCast(regs.TTBR0_EL1.Bits{ + .BADDR = @truncate(space_physical.raw), + .ASID = @truncate(space_asid), + })); + + // Arguments to __aa64_task_enter_user ks.push(pc); ks.push(arg); + ks.push(0); // Padding + ks.push(sp); + setup_stack_common(&ks, @intFromPtr(&__aa64_task_enter_user)); + + return .{ .kstack = ks, .ttbr0 = ttbr0 }; + } + + pub fn kernel(function: *const thread.KernelThreadFn, arg: usize) Context { + var ks = thread.KStack(STACK_SIZE).create(); + + // Arguments to __aa64_task_enter_kernel + ks.push(@intFromPtr(&thread.kernel_return)); + ks.push(@intFromPtr(function)); + ks.push(arg); + + setup_stack_common(&ks, @intFromPtr(&__aa64_task_enter_kernel)); + + return Context{ .kstack = ks }; + } + + pub fn enter(self: *Context) noreturn { + self.load_state(); + __aa64_enter_task(self); + } + + pub fn switch_from(self: *Context, from: *Context) void { + from.store_state(); + self.load_state(); + __aa64_switch_task(self, from); + } + + pub fn load_state(self: *Context) void { + regs.TTBR0_EL1.set(self.ttbr0); + } + + pub fn store_state(self: *Context) void { + _ = self; + } + + fn setup_stack_common(ks: *thread.KStack(STACK_SIZE), entry: usize) void { ks.push(entry); // x30/lr ks.push(0); // x29 ks.push(0); // x28 @@ -37,16 +90,6 @@ pub const Context = extern struct { ks.push(0); // x21 ks.push(0); // x20 ks.push(0); // x19 - - return Context{ .kstack = ks }; - } - - pub fn enter(self: *Context) noreturn { - __aa64_enter_task(self); - } - - pub fn switch_from(self: *Context, from: *Context) void { - __aa64_switch_task(self, from); } }; diff --git a/src/arch/aarch64/exception.zig b/src/arch/aarch64/exception.zig index 1d036c5..813be4b 100644 --- a/src/arch/aarch64/exception.zig +++ b/src/arch/aarch64/exception.zig @@ -41,6 +41,7 @@ export fn __aa64_el1_sync_handler(frame: *ExceptionFrame) callconv(.C) void { log.err("Exception in EL1:", .{}); log.err(" EC = {s} (0b{b:06}) ISS = 0x{x}", .{ esr.EC.as_str(), @intFromEnum(esr.EC), esr.ISS }); + log.err(" ESR = 0x{x:016}", .{@as(u64, @bitCast(esr))}); log.err(" ELR = 0x{x:016}", .{elr}); switch (esr.as_enum()) { diff --git a/src/arch/aarch64/regs.zig b/src/arch/aarch64/regs.zig index 0cc22cd..21fb4c0 100644 --- a/src/arch/aarch64/regs.zig +++ b/src/arch/aarch64/regs.zig @@ -6,6 +6,8 @@ fn Register(comptime name: []const u8, comptime bits: type) type { else => bits, }; return enum(repr) { + pub const Bits = bits; + pub fn set(value: repr) void { asm volatile ("msr " ++ name ++ ", %[value]" : @@ -34,8 +36,15 @@ fn Register(comptime name: []const u8, comptime bits: type) type { }; } -pub const TTBR0_EL1 = Register("ttbr0_el1", u64); -pub const TTBR1_EL1 = Register("ttbr1_el1", u64); +pub const TTBR = packed struct(u64) { + // 0..48 + BADDR: u48 = 0, + // 48..64 + ASID: u16 = 0, +}; + +pub const TTBR0_EL1 = Register("ttbr0_el1", TTBR); +pub const TTBR1_EL1 = Register("ttbr1_el1", TTBR); // NOTE: tpidr_el0 is used until codegen can emit TLS instructions against tpidr_el1 pub const TPIDR_EL0 = Register("tpidr_el0", u64); diff --git a/src/arch/aarch64/vmm.zig b/src/arch/aarch64/vmm.zig index 0cb93c1..def4634 100644 --- a/src/arch/aarch64/vmm.zig +++ b/src/arch/aarch64/vmm.zig @@ -1,13 +1,20 @@ +const std = @import("std"); const mem = @import("../../mem.zig"); const regs = @import("regs.zig"); +const kernel = @import("../../kernel.zig"); const PhysicalAddress = mem.PhysicalAddress; +const AtomicU8 = std.atomic.Value(u8); +const log = kernel.log; pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFF8000000000; pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE); +pub const KERNEL_VIRTUAL_SIZE: usize = 16 * L1.SIZE; +pub const VIRTUALIZE_BASE: usize = KERNEL_VIRTUAL_BASE + KERNEL_VIRTUAL_SIZE; +pub const VIRTUALIZE_BASE_L1I: usize = L1.index(VIRTUALIZE_BASE); -pub const L1 = mem.TranslationLevel(30); -pub const L2 = mem.TranslationLevel(21); +pub const L1 = mem.TranslationLevel(30, L2); +pub const L2 = mem.TranslationLevel(21, L3); pub const L3 = mem.vmm.L3; pub const RawEntry = packed struct(u64) { @@ -135,31 +142,149 @@ pub fn Table(comptime Level: type) type { return struct { pub const Entry = TableEntry(Level); + pub const Error = mem.vmm.AddressSpaceError; + entries: [512]Entry align(4096) = [_]Entry{.INVALID} ** 512, + pub fn allocate_empty() Error!*@This() { + const page = mem.phys.alloc_page() orelse return error.out_of_pages; + const table = @as(*@This(), @ptrFromInt(page.virtualize())); + for (0..512) |i| { + table.entry(i).* = .INVALID; + } + return table; + } + pub inline fn entry(self: *@This(), index: usize) *Entry { return &self.entries[index]; } + + pub fn physical_address(self: *const @This()) PhysicalAddress { + return PhysicalAddress.from_virtualized(@intFromPtr(self)); + } + + pub usingnamespace if (Level.NextLevel) |NextLevel| struct { + pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) { + const ent = self.entry(index); + if (ent.raw.V and !ent.raw.P) { + @panic("TODO: translate existing table"); + } + return null; + } + + pub fn get_or_create_next_level(self: *Table(Level), index: usize) Error!*Table(NextLevel) { + const ent = self.entry(index); + if (ent.raw.V) { + if (!ent.raw.P) { + @panic("TODO: mixed hugepages and tables"); + } + + // Entry is a table + @panic("TODO: translate existing table"); + } else { + const table = try Table(NextLevel).allocate_empty(); + const physical = table.physical_address(); + ent.* = TableEntry(Level).table(physical, .{}); + return table; + } + } + } else struct {}; }; } -// 0x0000_0000_0000_0000 .. 0x0000_0080_0000_0000 -var g_fixed_low = Table(L1){}; +pub const ProcessAddressSpace = struct { + l1: *Table(L1), + asid: u8, + + pub const Error = mem.vmm.AddressSpaceError; + + var g_asid: AtomicU8 = .{ .raw = 1 }; + + pub fn init() Error!ProcessAddressSpace { + const table = try Table(L1).allocate_empty(); + const asid = g_asid.fetchAdd(1, .seq_cst); + return .{ .l1 = table, .asid = asid }; + } + + pub fn physical_address(self: *const @This()) PhysicalAddress { + return self.l1.physical_address(); + } + + pub fn map_page(self: *@This(), virtual: usize, physical: PhysicalAddress) Error!void { + // TODO align check on both virtual and physical + + const l1i = L1.index(virtual); + const l2i = L2.index(virtual); + const l3i = L3.index(virtual); + + const l2 = try self.l1.get_or_create_next_level(l1i); + const l3 = try l2.get_or_create_next_level(l2i); + + const entry = l3.entry(l3i); + + if (entry.raw.V) { + @panic("TODO: handle already present"); + } + + entry.* = TableEntry(L3).normal_page(physical, RawEntry{ .AP = .both_readwrite, .NG = true }); + tlb_flush_vma_asid(virtual, self.asid); + + log.debug("Map 0x{x} -> page 0x{x}", .{ virtual, physical.raw }); + } +}; + +pub inline fn tlb_flush_vma(vma: usize) void { + const xt = vma >> 12; + asm volatile ( + \\ dsb ishst + \\ tlbi vaae1, %[xt] + \\ dsb ish + \\ isb sy + : + : [xt] "r" (xt), + : "memory" + ); +} + +pub inline fn tlb_flush_vma_asid(vma: usize, asid: usize) void { + const xt = (vma >> 12) | (asid << 48); + asm volatile ( + \\ dsb ishst + \\ tlbi vae1, %[xt] + \\ dsb ish + \\ isb sy + : + : [xt] "r" (xt), + : "memory" + ); +} + +pub inline fn tlb_flush_asid(asid: usize) void { + const xt = asid << 48; + asm volatile ( + \\ dsb ishst + \\ tlbi aside1, %[xt] + \\ dsb ish + \\ isb sy + : + : [xt] "r" (xt), + : "memory" + ); +} + // 0xFFFF_FF80_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF var g_fixed_high = Table(L1){}; +pub fn unmap_early() void { + // Flush whole ASID 0 + tlb_flush_asid(0); + regs.TTBR0_EL1.set(0); +} + pub fn map_early(real_address: usize) void { _ = real_address; - for (0..16) |i| { - // Identity - g_fixed_low.entry(i).* = TableEntry(L1).normal_block( - .{ .raw = i << L1.SHIFT }, - .{}, - ); - } - - for (0..16) |i| { + for (0..L1.page_count(KERNEL_VIRTUAL_SIZE)) |i| { // Identity + KERNEL_VIRTUAL_BASE g_fixed_high.entry(i).* = TableEntry(L1).normal_block( .{ .raw = i << L1.SHIFT }, @@ -167,11 +292,18 @@ pub fn map_early(real_address: usize) void { ); } - const ttbr0 = @intFromPtr(&g_fixed_low); - const ttbr1 = @intFromPtr(&g_fixed_high); + for (0..16) |i| { + // Identity + VIRTUALIZE_BASE for "Whole RAM mapping" + g_fixed_high.entry(VIRTUALIZE_BASE_L1I + i).* = TableEntry(L1).normal_block( + .{ .raw = i << L1.SHIFT }, + .{}, + ); + } - regs.TTBR0_EL1.set(ttbr0); - regs.TTBR1_EL1.set(ttbr1); + const ttbr = @intFromPtr(&g_fixed_high); + + regs.TTBR0_EL1.write(.{ .BADDR = @truncate(ttbr) }); + regs.TTBR1_EL1.write(.{ .BADDR = @truncate(ttbr) }); regs.TCR_EL1.write(.{ .AS = .asid_8bit, diff --git a/src/arch/riscv64/boot.zig b/src/arch/riscv64/boot.zig index e466c19..0503833 100644 --- a/src/arch/riscv64/boot.zig +++ b/src/arch/riscv64/boot.zig @@ -55,6 +55,8 @@ pub export fn rv64_bsp_lower_entry(real_address: usize, bsp_hart_id: usize, dtb_ g_dtb_address = dtb_address; g_bsp_hart_id = @truncate(bsp_hart_id); + vmm.g_kernel_real_base = real_address; + vmm.map_early(real_address); // &bspUpperEntry will yield a pointer like: X + P, where diff --git a/src/arch/riscv64/context.S b/src/arch/riscv64/context.S index 08d954b..e743774 100644 --- a/src/arch/riscv64/context.S +++ b/src/arch/riscv64/context.S @@ -70,11 +70,12 @@ __rv64_task_enter_user: __rv64_task_enter_kernel: ld a0, (sp) // argument - ld ra, 8(sp) // entry - addi sp, sp, 16 + ld t0, 8(sp) // entry + ld ra, 16(sp) // return address + addi sp, sp, 24 // TODO S-mode -> S-mode return via sret - ret + jr t0 __rv64_switch_task: // a0 - new context diff --git a/src/arch/riscv64/context.zig b/src/arch/riscv64/context.zig index df33329..8d8ef63 100644 --- a/src/arch/riscv64/context.zig +++ b/src/arch/riscv64/context.zig @@ -7,10 +7,6 @@ const vmm = @import("vmm.zig"); const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; const log = kernel.log; -fn idle_function() callconv(.naked) noreturn { - asm volatile ("j ."); -} - extern fn __rv64_enter_task(cx: *Context) callconv(.C) noreturn; extern fn __rv64_switch_task(dcx: *Context, scx: *Context) callconv(.C) void; extern fn __rv64_task_enter_kernel() callconv(.C) noreturn; @@ -26,72 +22,40 @@ pub const Context = extern struct { /// Constructs an idle context struct. pub fn idle() @This() { - const entry = @intFromPtr(&idle_function); - return Context.kernel(entry, 0); + return Context.kernel(&thread.idle_function, 0); } pub fn user(address_space: *const ProcessAddressSpace, pc: usize, sp: usize, arg: usize) @This() { const space_physical = address_space.physical_address(); const space_asid = address_space.asid(); - const satp = regs.SATP.Bits { - .PPN = @truncate(space_physical.raw >> 12), - .ASID = @truncate(space_asid), - .MODE = .sv39 - }; + const satp = regs.SATP.Bits{ .PPN = @truncate(space_physical.raw >> 12), .ASID = @truncate(space_asid), .MODE = .sv39 }; var ks = thread.KStack(STACK_SIZE).create(); - const entry = @intFromPtr(&__rv64_task_enter_user); ks.push(pc); ks.push(sp); ks.push(arg); - ks.push(0); // x8/s0/fp - ks.push(0); // x9/s1 - ks.push(0); // x18/s2 - ks.push(0); // x19/s3 - ks.push(0); // x20/s4 - ks.push(0); // x21/s5 - ks.push(0); // x22/s6 - ks.push(0); // x23/s7 - ks.push(0); // x24/s8 - ks.push(0); // x25/s9 - ks.push(0); // x26/s10 - ks.push(0); // x27/s11 - ks.push(0); // x4/gp - ks.push(entry); // x1/ra return address + setup_stack_common(&ks, @intFromPtr(&__rv64_task_enter_user)); - return .{ - .kstack = ks, - .satp = @bitCast(satp) - }; + return .{ .kstack = ks, .satp = @bitCast(satp) }; } /// Constructs a kernel task context with entry point in `pc` and an `arg`ument. - pub fn kernel(pc: usize, arg: usize) @This() { + pub fn kernel(function: *const thread.KernelThreadFn, arg: usize) @This() { var ks = thread.KStack(STACK_SIZE).create(); - const entry = @intFromPtr(&__rv64_task_enter_kernel); - ks.push(pc); + const table_physical = vmm.kernel_table_physical(); + const satp = regs.SATP.Bits{ .PPN = @truncate(table_physical >> 12), .MODE = .sv39 }; + + ks.push(@intFromPtr(&thread.kernel_return)); + ks.push(@intFromPtr(function)); ks.push(arg); - ks.push(0); // x8/s0/fp - ks.push(0); // x9/s1 - ks.push(0); // x18/s2 - ks.push(0); // x19/s3 - ks.push(0); // x20/s4 - ks.push(0); // x21/s5 - ks.push(0); // x22/s6 - ks.push(0); // x23/s7 - ks.push(0); // x24/s8 - ks.push(0); // x25/s9 - ks.push(0); // x26/s10 - ks.push(0); // x27/s11 - ks.push(0); // x4/gp - ks.push(entry); // x1/ra return address + setup_stack_common(&ks, @intFromPtr(&__rv64_task_enter_kernel)); - return .{ .kstack = ks }; + return .{ .kstack = ks, .satp = @bitCast(satp) }; } /// Low-level task context entry function. @@ -108,17 +72,29 @@ pub const Context = extern struct { } fn load_state(self: *@This()) void { - if (self.satp != 0) { - log.info("Load SATP = 0x{x}", .{self.satp}); - regs.SATP.set(self.satp); - } else { - vmm.load_kernel_table(); - } + regs.SATP.set(self.satp); } fn store_state(self: *@This()) void { _ = self; } + + fn setup_stack_common(ks: *thread.KStack(STACK_SIZE), entry: usize) void { + ks.push(0); // x8/s0/fp + ks.push(0); // x9/s1 + ks.push(0); // x18/s2 + ks.push(0); // x19/s3 + ks.push(0); // x20/s4 + ks.push(0); // x21/s5 + ks.push(0); // x22/s6 + ks.push(0); // x23/s7 + ks.push(0); // x24/s8 + ks.push(0); // x25/s9 + ks.push(0); // x26/s10 + ks.push(0); // x27/s11 + ks.push(0); // x4/gp + ks.push(entry); // x1/ra return address + } }; comptime { diff --git a/src/arch/riscv64/vmm.zig b/src/arch/riscv64/vmm.zig index 4efc59c..3799c01 100644 --- a/src/arch/riscv64/vmm.zig +++ b/src/arch/riscv64/vmm.zig @@ -55,11 +55,15 @@ pub const RawEntry = packed struct(u64) { const rhs = @as(u64, @bitCast(mask)); lhs.* &= ~rhs; } + + pub fn is_table(self: @This()) bool { + return !self.r and !self.w and !self.x; + } }; pub fn TableEntry(comptime Level: type) type { _ = Level; - return struct { + return packed struct(u64) { raw: RawEntry, pub const INVALID: @This() = .{ .raw = .{} }; @@ -96,12 +100,11 @@ pub fn TableEntry(comptime Level: type) type { .v = true, }) }; } - }; } pub fn Table(comptime Level: type) type { - return struct { + return extern struct { pub const Entry = TableEntry(Level); entries: [512]Entry align(4096), @@ -131,9 +134,11 @@ pub fn Table(comptime Level: type) type { pub usingnamespace if (Level.NextLevel) |NextLevel| struct { pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) { - _ = self; - _ = index; - @panic("TODO"); + const ent = self.entry(index); + if (ent.raw.v and ent.raw.is_table()) { + @panic("TODO: translate existing table"); + } + return null; } pub fn get_or_create_next_level(self: *Table(Level), index: usize) Error!*Table(NextLevel) { @@ -141,11 +146,11 @@ pub fn Table(comptime Level: type) type { if (ent.raw.v) { // TODO handle mixed hugepages + tables - if (ent.raw.r or ent.raw.w or ent.raw.x) { + if (!ent.raw.is_table()) { @panic("TODO: handle mixed hugepages and tables"); } // It is a table - @panic("OOO"); + @panic("TODO: translate existing table"); } else { // Allocate a new entry const table = try Table(NextLevel).allocate_empty(); @@ -210,13 +215,21 @@ pub const ProcessAddressSpace = struct { var g_fixed = Table(L1).empty(); var g_fixed_lock: sync.Spinlock = .{}; +pub var g_kernel_real_base: u64 = undefined; +extern var __kernel_start: u8; pub fn virtualize_range() usize { return EARLY_MAPPING_SIZE * L1.SIZE; } +pub fn kernel_table_physical() u64 { + const address = @as(usize, @intFromPtr(&g_fixed)); + const kernel_start = @intFromPtr(&__kernel_start); + return address - kernel_start + g_kernel_real_base; +} + pub fn unmap_early() void { - // Make lower half mappings non-executable + // Unmap lower half const guard = g_fixed_lock.lock_irqsave(); defer guard.release(); for (0..EARLY_MAPPING_SIZE) |i| { @@ -224,11 +237,6 @@ pub fn unmap_early() void { } } -pub fn load_kernel_table() void { - const address = @as(usize, @intFromPtr(&g_fixed)); - regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 }); -} - pub fn map_early(real_address: usize) void { const real_l1 = L1.index(real_address); @@ -253,7 +261,8 @@ pub fn map_early(real_address: usize) void { .{ .r = true, .w = true, .x = true }, ); - load_kernel_table(); + const address = @intFromPtr(&g_fixed); + regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 }); } pub inline fn flush_vma(page: usize) void { diff --git a/src/kernel.zig b/src/kernel.zig index ef9f42c..d7e8b64 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -16,13 +16,9 @@ pub const TRACE_PHYSICAL_ALLOCATOR: bool = false; const std = @import("std"); -fn f0(arg: usize) callconv(.C) noreturn { - var c: usize = 0; - while (true) { - f1(arg, c); - c += 1; - thread.yield(); - } +fn f0(arg: usize) callconv(.C) void { + log.info("Argument is {}", .{arg}); + thread.yield(); } noinline fn f1(arg: usize, c: usize) void { @@ -41,10 +37,18 @@ pub export fn kernel_main() callconv(.C) noreturn { var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena"); thread.Queue.init_this_cpu(&a); - const t = thread.test_create_user_from_code(&a, &[_]u8 { - 0x6F, 0x00, 0x00, 0x00 - }); - thread.enqueue(t); + const t0 = thread.Thread.create_kernel(&a, &f0, 1234); + + const code = switch (comptime arch.cpu) { + .riscv64 => &[_]u8{ 0x6F, 0x00, 0x00, 0x00 }, + .aarch64 => &[_]u8{ + 0x00, 0x00, 0x00, 0x14, + }, + }; + const t1 = thread.test_create_user_from_code(&a, code) catch @panic("Could not create test thread"); + + thread.enqueue(t0); + thread.enqueue(t1); log.info("Test", .{}); // log.write("\x1B[2J", .{}); diff --git a/src/mem/vmalloc.zig b/src/mem/vmalloc.zig index 8afc857..80032ff 100644 --- a/src/mem/vmalloc.zig +++ b/src/mem/vmalloc.zig @@ -22,13 +22,27 @@ pub const VirtualMemoryAllocator = struct { /// One of errors returned by the allocation logic + underlying allocator error. pub const Error = error{ already_exists, invalid_region, cannot_fit }; + pub const DrainIterator = struct { + vma: *VirtualMemoryAllocator, + + pub fn next(self: *@This()) ?Range(u64) { + while (self.vma.head) |head| { + self.vma.head = head.next; + const range = head.range; + // TODO free the range + return range; + } + return null; + } + }; + /// An iterator over VM regions being freed. pub const FreeIterator = struct { range: Range(u64), vma: *VirtualMemoryAllocator, current: ?*VirtualMemoryRange, - fn next(self: *@This()) Error!?Range(u64) { + pub fn next(self: *@This()) Error!?Range(u64) { while (self.current) |n| { if (n.range.intersect(&self.range)) |xs| { if (xs.start == n.range.start) { @@ -64,7 +78,7 @@ pub const VirtualMemoryAllocator = struct { } else { // Insert a new node after the current one const new_node = self.vma.arena.create(VirtualMemoryRange); - new_node.* = VirtualMemoryRange { + new_node.* = VirtualMemoryRange{ .range = .{ .start = xs.end(), .len = n.range.end() - xs.end() }, .prev = n, .next = n.next, @@ -202,13 +216,17 @@ pub const VirtualMemoryAllocator = struct { /// Deallocates (shrinks/truncates) regions intersecting the requested range. pub fn free(self: *@This(), start_pfn: u64, pfn_count: u64) FreeIterator { - const range = Range(u64) { .start = start_pfn, .len = pfn_count }; - return FreeIterator { + const range = Range(u64){ .start = start_pfn, .len = pfn_count }; + return FreeIterator{ .current = self.head, .vma = self, .range = range, }; } + + pub fn drain(self: *@This()) DrainIterator { + return DrainIterator{ .vma = self }; + } }; test "Inserted entries in vmalloc are properly ordered" { diff --git a/src/mem/vmm.zig b/src/mem/vmm.zig index a59e946..cee0e46 100644 --- a/src/mem/vmm.zig +++ b/src/mem/vmm.zig @@ -7,6 +7,7 @@ const kernel = @import("../kernel.zig"); const sync = @import("../sync.zig"); const arch = kernel.arch; +const log = kernel.log; const Arena = arena.Arena; /// Last virtual memory translation level. Always 4KiB on all platforms. @@ -17,7 +18,7 @@ pub const PAGE_SIZE: usize = L3.SIZE; pub const AddressSpaceError = error{ out_of_pages, -}; +} || vmalloc.VirtualMemoryAllocator.Error; /// Helper function to construct a "Translation Level" struct type from a bit shift. pub fn TranslationLevel(comptime shift: usize, comptime Next: ?type) type { @@ -69,6 +70,14 @@ pub const ProcessAddressSpace = struct { return .{ .inner = inner, .allocator = allocator, .lock = .{} }; } + pub fn clear(self: *@This()) void { + var drain = self.allocator.drain(); + while (drain.next()) |range| { + log.info("Free range: 0x{x}..0x{x}", .{ range.start * L3.SIZE, range.end() * L3.SIZE }); + // TODO unmap/free pages + } + } + pub fn map_single_page( self: *@This(), virtual: usize, @@ -77,8 +86,14 @@ pub const ProcessAddressSpace = struct { self.lock.lock(); defer self.lock.release(); - // TODO If allocation succeeds, but mapping fails, rollback - self.allocator.insert(.{ .start = L3.page_number(virtual), .len = 1 }) catch @panic("TODO error"); + try self.allocator.insert(.{ .start = L3.page_number(virtual), .len = 1 }); + errdefer { + var it = self.allocator.free(L3.page_number(virtual), 1); + while (it.next() catch unreachable) |n| { + // TODO: inner.unmap_page() + _ = n; + } + } try self.inner.map_page(virtual, physical); } diff --git a/src/thread.zig b/src/thread.zig index 8a60d3b..9f21473 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -9,6 +9,23 @@ const mem = @import("mem.zig"); const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; +// TODO: are kernel threads needed at all if we're doing a microkernel? + +/// Signature for kernel thread entry +pub const KernelThreadFn = fn (usize) callconv(.C) void; + +pub fn kernel_return() callconv(.C) noreturn { + @panic("TODO: kernel thread exit"); +} + +/// Task to run when there are no real threads in the queue +pub fn idle_function(arg: usize) callconv(.C) noreturn { + _ = arg; + while (true) { + arch.wait_for_interrupt(); + } +} + /// Per-CPU thread queue structure. pub const Queue = struct { /// Idle task context. Used when there are no other tasks running. @@ -97,12 +114,14 @@ pub const Thread = struct { // TODO move to process address_space: ?ProcessAddressSpace = null, - /// Creates a new (kernel) thread with given `pc` (entry point) and `arg`ument. - pub fn create_kernel(a: *arena.Arena, pc: usize, arg: usize) *Thread { + pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError; + + /// Creates a new (kernel) thread with given `function` and `arg`ument. + pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread { const thread = a.create(Thread); thread.* = .{ .allocator = a, - .arch_context = arch.Context.kernel(pc, arg), + .arch_context = arch.Context.kernel(function, arg), }; return thread; } @@ -192,12 +211,18 @@ pub fn yield() void { Queue.t_this_cpu.?.yield(); } -pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) *Thread { - var address_space = ProcessAddressSpace.init(a) catch @panic("TODO"); +pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Error!*Thread { + var address_space = try ProcessAddressSpace.init(a); + errdefer { + address_space.clear(); + } // Map 0x200000 - const page = mem.phys.alloc_page() orelse @panic("TODO error"); - address_space.map_single_page(0x200000, page) catch @panic("TODO error map"); + const page = mem.phys.alloc_page() orelse return error.out_of_memory; + errdefer { + mem.phys.free_page(page); + } + try address_space.map_single_page(0x200000, page); const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..code.len]; @memcpy(page_data, code); diff --git a/src/util/rangemap.zig b/src/util/rangemap.zig index b661db8..6c11af1 100644 --- a/src/util/rangemap.zig +++ b/src/util/rangemap.zig @@ -11,7 +11,7 @@ pub fn RangeMap( comptime K: type, comptime V: type, comptime ops: struct { - deinit_fn: ?fn(*V) void = null, + deinit_fn: ?fn (*V) void = null, merge_fn: ?fn (*const V, *const V) bool = null, }, ) type { @@ -244,11 +244,11 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); try std.testing.expectEqual(null, it.next()); @@ -261,19 +261,19 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 10 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 10 }, n3.key); try std.testing.expectEqual(false, n3.value); try std.testing.expectEqual(null, it.next()); @@ -286,23 +286,23 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 20 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 20 }, n3.key); try std.testing.expectEqual(false, n3.value); const n4 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 71, .len = 9 }, n4.key); + try std.testing.expectEqual(Range(u32){ .start = 71, .len = 9 }, n4.key); try std.testing.expectEqual(false, n4.value); try std.testing.expectEqual(null, it.next()); @@ -314,19 +314,19 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key); try std.testing.expectEqual(false, n3.value); try std.testing.expectEqual(null, it.next()); @@ -339,23 +339,23 @@ test "Range map merging insertion" { { var it = map.iterator(); const n0 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key); + try std.testing.expectEqual(Range(u32){ .start = 0, .len = 10 }, n0.key); try std.testing.expectEqual(true, n0.value); const n1 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key); + try std.testing.expectEqual(Range(u32){ .start = 10, .len = 30 }, n1.key); try std.testing.expectEqual(false, n1.value); const n2 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key); + try std.testing.expectEqual(Range(u32){ .start = 40, .len = 10 }, n2.key); try std.testing.expectEqual(true, n2.value); const n3 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key); + try std.testing.expectEqual(Range(u32){ .start = 50, .len = 30 }, n3.key); try std.testing.expectEqual(false, n3.value); const n4 = it.next().?; - try std.testing.expectEqual(Range(u32) { .start = 100, .len = 20 }, n4.key); + try std.testing.expectEqual(Range(u32){ .start = 100, .len = 20 }, n4.key); try std.testing.expectEqual(false, n4.value); try std.testing.expectEqual(null, it.next()); -- 2.53.0 From f57fd485c5637ac4bc6bf5fdb9e0e43b5ba7fa50 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 14:22:33 +0200 Subject: [PATCH 03/10] WIP: Allow userspace to use stack --- src/arch/aarch64/vmm.zig | 8 ++++++-- src/arch/riscv64/vmm.zig | 8 ++++++-- src/kernel.zig | 12 ++++++++++-- src/thread.zig | 19 +++++++++++++------ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/arch/aarch64/vmm.zig b/src/arch/aarch64/vmm.zig index def4634..dea1441 100644 --- a/src/arch/aarch64/vmm.zig +++ b/src/arch/aarch64/vmm.zig @@ -155,6 +155,10 @@ pub fn Table(comptime Level: type) type { return table; } + pub fn from_physical_address(physical: PhysicalAddress) *@This() { + return @ptrFromInt(physical.virtualize()); + } + pub inline fn entry(self: *@This(), index: usize) *Entry { return &self.entries[index]; } @@ -167,7 +171,7 @@ pub fn Table(comptime Level: type) type { pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) { const ent = self.entry(index); if (ent.raw.V and !ent.raw.P) { - @panic("TODO: translate existing table"); + return Table(NextLevel).from_physical_address(ent.address()); } return null; } @@ -180,7 +184,7 @@ pub fn Table(comptime Level: type) type { } // Entry is a table - @panic("TODO: translate existing table"); + return Table(NextLevel).from_physical_address(ent.address()); } else { const table = try Table(NextLevel).allocate_empty(); const physical = table.physical_address(); diff --git a/src/arch/riscv64/vmm.zig b/src/arch/riscv64/vmm.zig index 3799c01..f9eb6ca 100644 --- a/src/arch/riscv64/vmm.zig +++ b/src/arch/riscv64/vmm.zig @@ -115,6 +115,10 @@ pub fn Table(comptime Level: type) type { return .{ .entries = [_]Entry{.INVALID} ** 512 }; } + pub fn from_physical_address(physical: PhysicalAddress) *@This() { + return @ptrFromInt(physical.virtualize()); + } + pub fn allocate_empty() Error!*@This() { const page = mem.phys.alloc_page() orelse return error.out_of_pages; const table = @as(*@This(), @ptrFromInt(page.virtualize())); @@ -136,7 +140,7 @@ pub fn Table(comptime Level: type) type { pub fn get_next_level(self: *Table(Level), index: usize) ?*Table(NextLevel) { const ent = self.entry(index); if (ent.raw.v and ent.raw.is_table()) { - @panic("TODO: translate existing table"); + return Table(NextLevel).from_physical_address(ent.address()); } return null; } @@ -150,7 +154,7 @@ pub fn Table(comptime Level: type) type { @panic("TODO: handle mixed hugepages and tables"); } // It is a table - @panic("TODO: translate existing table"); + return Table(NextLevel).from_physical_address(ent.address()); } else { // Allocate a new entry const table = try Table(NextLevel).allocate_empty(); diff --git a/src/kernel.zig b/src/kernel.zig index d7e8b64..df5d777 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -40,9 +40,17 @@ pub export fn kernel_main() callconv(.C) noreturn { const t0 = thread.Thread.create_kernel(&a, &f0, 1234); const code = switch (comptime arch.cpu) { - .riscv64 => &[_]u8{ 0x6F, 0x00, 0x00, 0x00 }, + .riscv64 => &[_]u8{ + 0x93, 0x02, 0xB0, 0x07, // li t0, 123 + 0x13, 0x01, 0x81, 0xFF, // addi sp, sp, -8 + 0x23, 0x30, 0x51, 0x00, // sd t0, (sp) + 0x6F, 0x00, 0x00, 0x00, // j . + }, .aarch64 => &[_]u8{ - 0x00, 0x00, 0x00, 0x14, + 0x60, 0x0F, 0x80, 0xD2, // mov x0, #123 + 0xFF, 0x23, 0x00, 0xD1, // sub sp, sp, #8 + 0xE0, 0x03, 0x00, 0xF9, // str x0, [sp] + 0x00, 0x00, 0x00, 0x14, // b . }, }; const t1 = thread.test_create_user_from_code(&a, code) catch @panic("Could not create test thread"); diff --git a/src/thread.zig b/src/thread.zig index 9f21473..a906cdf 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -217,17 +217,24 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro address_space.clear(); } - // Map 0x200000 - const page = mem.phys.alloc_page() orelse return error.out_of_memory; + // @ 0x200000 + const code_page = mem.phys.alloc_page() orelse return error.out_of_memory; errdefer { - mem.phys.free_page(page); + mem.phys.free_page(code_page); + } + // @ 0x201000 + const stack_page = mem.phys.alloc_page() orelse return error.out_of_memory; + errdefer { + mem.phys.free_page(stack_page); } - try address_space.map_single_page(0x200000, page); - const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..code.len]; + try address_space.map_single_page(0x200000, code_page); + try address_space.map_single_page(0x201000, stack_page); + + const page_data = @as([*]u8, @ptrFromInt(code_page.virtualize()))[0..code.len]; @memcpy(page_data, code); - const thread = Thread.create_user(a, address_space, 0x200000, 0, 0); + const thread = Thread.create_user(a, address_space, 0x200000, 0x201000, 0); return thread; } -- 2.53.0 From e1bd496b8faf26081cb008dd308f906ca46d8275 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 14:36:04 +0200 Subject: [PATCH 04/10] WIP: Enter kernel tasks via exception return --- src/arch/aarch64/context.S | 20 +++++++++++++++++--- src/arch/riscv64/context.S | 11 +++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/arch/aarch64/context.S b/src/arch/aarch64/context.S index b70d10e..10aafb8 100644 --- a/src/arch/aarch64/context.S +++ b/src/arch/aarch64/context.S @@ -29,6 +29,9 @@ .pushsection .text +.set SPSR_ELx_I, (1 << 9) +.set SPSR_ELx_EL1h, (0b0101) + __aa64_task_enter_user: // x0 == sp, ... ldr x0, [sp, #16 * 0] @@ -40,7 +43,8 @@ __aa64_task_enter_user: msr elr_el1, x1 - mov x1, #(1 << 9) + // SPSR_ELx_M[4:0] = 0, a return to EL0, AArch64 mode + mov x1, #SPSR_ELx_I msr spsr_el1, x1 mov lr, xzr @@ -57,9 +61,19 @@ __aa64_task_enter_kernel: ldr lr, [sp, #16] add sp, sp, #24 - // TODO enter task via eret to EL1t + msr elr_el1, x1 - br x1 + // SPSR_ELx_M[4:0] = 0b100, a return to EL1t, AArch64 mode + mov x1, #SPSR_ELx_EL1h + orr x1, x1, #SPSR_ELx_I + msr spsr_el1, x1 + + mov x1, xzr + + dsb ish + isb sy + + eret __aa64_switch_task: // x0 -- "dst" context diff --git a/src/arch/riscv64/context.S b/src/arch/riscv64/context.S index e743774..ae04e7b 100644 --- a/src/arch/riscv64/context.S +++ b/src/arch/riscv64/context.S @@ -74,8 +74,15 @@ __rv64_task_enter_kernel: ld ra, 16(sp) // return address addi sp, sp, 24 - // TODO S-mode -> S-mode return via sret - jr t0 + // Set SPP to indicate a return to S-mode + csrr t1, sstatus + // TODO enable interrupts via SPIE + // ori t0, t0, SSTATUS_SPIE + ori t1, t1, SSTATUS_SPP + csrw sstatus, t1 + csrw sepc, t0 + + sret __rv64_switch_task: // a0 - new context -- 2.53.0 From b1a59dd42bb153fa58b978a2ea4a4477eb9ee38b Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 15:33:41 +0200 Subject: [PATCH 05/10] WIP: Implement thread exit --- src/arch.zig | 13 ++++++++ src/kernel.zig | 6 ++-- src/thread.zig | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/arch.zig b/src/arch.zig index 928e1ba..9c830e7 100644 --- a/src/arch.zig +++ b/src/arch.zig @@ -20,6 +20,19 @@ pub const impl = switch (cpu) { pub const vmm = impl.vmm; +pub const IrqGuard = struct { + state: bool, + + pub fn acquire() @This() { + const state = set_interrupt_mask(true); + return .{ .state = state }; + } + + pub fn release(self: @This()) void { + set_interrupt_mask(self.state); + } +}; + /// Halts the CPU execution indefinitely, without ever returning. pub inline fn halt() noreturn { impl.halt(); diff --git a/src/kernel.zig b/src/kernel.zig index df5d777..7d105cc 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -18,7 +18,6 @@ const std = @import("std"); fn f0(arg: usize) callconv(.C) void { log.info("Argument is {}", .{arg}); - thread.yield(); } noinline fn f1(arg: usize, c: usize) void { @@ -37,8 +36,6 @@ pub export fn kernel_main() callconv(.C) noreturn { var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena"); thread.Queue.init_this_cpu(&a); - const t0 = thread.Thread.create_kernel(&a, &f0, 1234); - const code = switch (comptime arch.cpu) { .riscv64 => &[_]u8{ 0x93, 0x02, 0xB0, 0x07, // li t0, 123 @@ -55,7 +52,8 @@ pub export fn kernel_main() callconv(.C) noreturn { }; const t1 = thread.test_create_user_from_code(&a, code) catch @panic("Could not create test thread"); - thread.enqueue(t0); + const t = thread.Thread.create_kernel(&a, &f0, 1234); + thread.enqueue(t); thread.enqueue(t1); log.info("Test", .{}); diff --git a/src/thread.zig b/src/thread.zig index a906cdf..2cecc79 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -6,6 +6,7 @@ const arena = @import("arena.zig"); const arch = @import("kernel.zig").arch; const log = @import("debug.zig").log; const mem = @import("mem.zig"); +const sync = @import("sync.zig"); const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; @@ -15,7 +16,7 @@ const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; pub const KernelThreadFn = fn (usize) callconv(.C) void; pub fn kernel_return() callconv(.C) noreturn { - @panic("TODO: kernel thread exit"); + Thread.exit_current(); } /// Task to run when there are no real threads in the queue @@ -34,6 +35,8 @@ pub const Queue = struct { current: ?*Thread = null, /// Thread queue head pointer. head: ?*Thread = null, + /// Queue's lock + lock: sync.Spinlock = .{}, /// Pointer to this CPU's thread queue. pub threadlocal var t_this_cpu: ?*Queue = null; @@ -59,6 +62,7 @@ pub const Queue = struct { /// Yields CPU to the next available task. pub fn yield(self: *@This()) void { + // TODO locking here if (self.current) |curr| { // Switching from thread if (curr.next) |next| { @@ -67,6 +71,12 @@ pub const Queue = struct { self.current = next; next.switch_from(curr); } + } else if (self.head) |h| { + // ... to thread (head) + if (h != curr) { + self.current = h; + h.switch_from(curr); + } } else { // ... to idle self.current = null; @@ -86,6 +96,11 @@ pub const Queue = struct { /// Adds an available task to this queue. pub fn enqueue(self: *@This(), t: *Thread) void { + var guard = self.lock.lock_irqsave(); + defer guard.release(); + + t.queue = self; + if (self.head) |gt| { t.next = gt; t.prev = gt.prev; @@ -97,6 +112,44 @@ pub const Queue = struct { t.prev = t; } } + + /// # Invariants + /// + /// `t` must be a thread within the `self` queue. + fn dequeue(self: *@This(), t: *Thread) void { + var guard = self.lock.lock_irqsave(); + defer guard.release(); + + t.queue = null; + + if (t == self.head) { + if (t.next == t) { + self.head = null; + t.next = null; + t.prev = null; + return; + } + + if (t.next) |tn| { + tn.prev = t.prev; + } + if (t.prev) |tp| { + tp.next = t.next; + } + + self.head = t.next; + } else { + if (t.next) |tn| { + tn.prev = t.prev; + } + if (t.prev) |tp| { + tp.next = t.next; + } + } + + t.next = null; + t.prev = null; + } }; /// Represents a single execution thread. @@ -106,6 +159,8 @@ pub const Thread = struct { /// Architecture-specific task context. arch_context: arch.Context, + /// Queue to which this thread belongs + queue: ?*Queue = null, /// Next thread in the queue. next: ?*Thread = null, /// Previous thread in the queue. @@ -151,6 +206,30 @@ pub const Thread = struct { pub fn switch_from(self: *@This(), from: *@This()) void { self.arch_context.switch_from(&from.arch_context); } + + pub fn dequeue(self: *@This()) void { + // TODO queueing information should be put under a lock for SMP to work properly + if (self.queue) |q| { + q.dequeue(self); + } + } + + pub fn current() *@This() { + return Queue.t_this_cpu.?.current.?; + } + + pub fn exit_current() noreturn { + // Mask IRQs so they don't break current thread's state + const mask = arch.IrqGuard.acquire(); + defer mask.release(); + + const curr = Thread.current(); + curr.dequeue(); + + yield(); + + @panic("This code should not be reachable"); + } }; /// Helper data structure to represent kernel stacks in task contexts. -- 2.53.0 From 47dbe64814052b95f7a02d0e9ea862170d00d9a1 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 16:55:01 +0200 Subject: [PATCH 06/10] WIP: Implement system call interface --- etc/riscv64-unknown-none.ld | 2 + src/arch/aarch64/exception.zig | 31 +++++++-- src/arch/aarch64/regs.zig | 11 +-- src/arch/aarch64/vectors.S | 59 +++++++++++++++-- src/arch/riscv64.zig | 6 ++ src/arch/riscv64/context.S | 4 ++ src/arch/riscv64/context.zig | 2 + src/arch/riscv64/exception.zig | 38 +++++++++-- src/arch/riscv64/vectors.S | 118 ++++++++++++++++++++++++++++++--- src/kernel.zig | 8 ++- 10 files changed, 249 insertions(+), 30 deletions(-) diff --git a/etc/riscv64-unknown-none.ld b/etc/riscv64-unknown-none.ld index 446b96b..b6a1ae5 100644 --- a/etc/riscv64-unknown-none.ld +++ b/etc/riscv64-unknown-none.ld @@ -23,6 +23,8 @@ SECTIONS { .tdata : ALIGN(4K) { PROVIDE(__tdata_start = .); + /* Storage for thread-locals used in assembly */ + KEEP(*(.tdata.assembly*)); *(.tdata*) PROVIDE(__tdata_end = .); } diff --git a/src/arch/aarch64/exception.zig b/src/arch/aarch64/exception.zig index 813be4b..43e1849 100644 --- a/src/arch/aarch64/exception.zig +++ b/src/arch/aarch64/exception.zig @@ -8,6 +8,10 @@ extern const __aa64_exception_vectors: u8; pub const ExceptionFrame = extern struct { xN: [32]usize, + spsr_el1: usize, + elr_el1: usize, + sp_el0: usize, + _0: usize, pub fn dump(self: *const ExceptionFrame, comptime level: log.Level) void { for (0..16) |i| { @@ -83,8 +87,25 @@ export fn __aa64_el1_serror_handler(frame: *ExceptionFrame) callconv(.C) void { // EL0 export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void { - // TODO EL0 - _ = frame; + const esr = regs.ESR_EL1.read(); + + switch (esr.as_enum()) { + .svc => { + log.info("Syscall executed!", .{}); + log.info("args:", .{}); + for (0..6) |i| { + log.info(" x{} = 0x{x:016} ({})", .{ i, frame.xN[i], frame.xN[i] }); + } + return; + }, + else => {}, + } + + log.err("Unhandled exception in EL0:", .{}); + log.err(" EC = {s} (0b{b:06}) ISS = 0x{x}", .{ esr.EC.as_str(), @intFromEnum(esr.EC), esr.ISS }); + log.err(" ESR = 0x{x:016}", .{@as(u64, @bitCast(esr))}); + log.err(" ELR = 0x{x:016}", .{frame.elr_el1}); + frame.dump(log.Level.err); arch.halt(); } @@ -94,14 +115,12 @@ export fn __aa64_el0_irq_handler(frame: *ExceptionFrame) callconv(.C) void { export fn __aa64_el0_fiq_handler(frame: *ExceptionFrame) callconv(.C) void { _ = frame; - // TODO I've never used FIQ - arch.halt(); + @panic("__aa64_el0_fiq_handler"); } export fn __aa64_el0_serror_handler(frame: *ExceptionFrame) callconv(.C) void { _ = frame; - // TODO - arch.halt(); + @panic("__aa64_el0_serror_handler"); } comptime { diff --git a/src/arch/aarch64/regs.zig b/src/arch/aarch64/regs.zig index 21fb4c0..257e7a4 100644 --- a/src/arch/aarch64/regs.zig +++ b/src/arch/aarch64/regs.zig @@ -76,6 +76,7 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) { // 26..32 EC: enum(u6) { unknown = 0b000000, + svc = 0b010101, data_abort_lower_el = 0b100100, data_abort_same_el = 0b100101, sp_align = 0b100110, @@ -145,14 +146,16 @@ pub const ESR_EL1 = Register("esr_el1", packed struct(u64) { pub const AsEnum = union(enum) { data_abort: DataAbort, + svc, other, }; pub fn as_enum(self: @This()) AsEnum { - switch (self.EC) { - .data_abort_lower_el, .data_abort_same_el => return .{ .data_abort = @bitCast(self.ISS) }, - else => return .other, - } + return switch (self.EC) { + .data_abort_lower_el, .data_abort_same_el => .{ .data_abort = @bitCast(self.ISS) }, + .svc => .svc, + else => .other, + }; } }); diff --git a/src/arch/aarch64/vectors.S b/src/arch/aarch64/vectors.S index f500b45..1161ad1 100644 --- a/src/arch/aarch64/vectors.S +++ b/src/arch/aarch64/vectors.S @@ -2,11 +2,14 @@ // 32 general-purpose registers .set EXC_GP_SIZE, (32 * 8) -.set EXC_STATE_SIZE, (EXC_GP_SIZE) +// 4 special-purpose registers +.set EXC_SP_SIZE, (4 * 8) +.set EXC_STATE_SIZE, (EXC_GP_SIZE + EXC_SP_SIZE) .macro EXC_SAVE_STATE sub sp, sp, #EXC_STATE_SIZE + // General-purpose block stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] @@ -23,6 +26,45 @@ stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] stp x30, x31, [sp, #16 * 15] + + // Special-purpose block + mrs x0, spsr_el1 + mrs x1, elr_el1 + mrs x2, sp_el0 + mov x3, xzr // Padding + + stp x0, x1, [sp, #EXC_GP_SIZE + 16 * 0] + stp x2, x3, [sp, #EXC_GP_SIZE + 16 * 1] +.endm + +.macro EXC_RESTORE_STATE + // Special-purpose block + ldp x0, x1, [sp, #EXC_GP_SIZE + 16 * 0] + ldp x2, x3, [sp, #EXC_GP_SIZE + 16 * 1] + + msr spsr_el1, x0 + msr elr_el1, x1 + msr sp_el0, x2 + + // General-purpose block + ldp x0, x1, [sp, #16 * 0] + ldp x2, x3, [sp, #16 * 1] + ldp x4, x5, [sp, #16 * 2] + ldp x6, x7, [sp, #16 * 3] + ldp x8, x9, [sp, #16 * 4] + ldp x10, x11, [sp, #16 * 5] + ldp x12, x13, [sp, #16 * 6] + ldp x14, x15, [sp, #16 * 7] + ldp x16, x17, [sp, #16 * 8] + ldp x18, x19, [sp, #16 * 9] + ldp x20, x21, [sp, #16 * 10] + ldp x22, x23, [sp, #16 * 11] + ldp x24, x25, [sp, #16 * 12] + ldp x26, x27, [sp, #16 * 13] + ldp x28, x29, [sp, #16 * 14] + ldp x30, x31, [sp, #16 * 15] + + add sp, sp, #EXC_STATE_SIZE .endm // Exception vector size is 0x80 @@ -38,13 +80,22 @@ __aa\bits\()_el\el\ht\()_\kind: // TODO taking exceptions from EL0t 32-bit b . .endif - EXC_SAVE_STATE + + dsb ish + isb sy + mov x0, sp mov lr, xzr bl __aa64_el\el\()_\kind\()_handler - // TODO exception return - b . + + EXC_RESTORE_STATE + + ic iallu + dsb ishst + isb sy + + eret .size __aa\bits\()_el\el\ht\()_\kind, . - __aa\bits\()_el\el\ht\()_\kind .endm diff --git a/src/arch/riscv64.zig b/src/arch/riscv64.zig index deef238..eb4e996 100644 --- a/src/arch/riscv64.zig +++ b/src/arch/riscv64.zig @@ -12,6 +12,12 @@ export const _ = boot.rv64_bsp_lower_entry; /// This CPU's HART (HARdware Thread) ID. pub threadlocal var t_hart_id: u32 = 0; +// Linked as .tdata.assembly to ensure `tp == &this` +pub export threadlocal var t_tdata_assembly: extern struct { + kernel_stack_pointer: usize, // tp + 0x00 + user_stack_pointer: usize, // tp + 0x08 +} linksection(".tdata.assembly") = undefined; + /// RISC-V task context pub const Context = @import("riscv64/context.zig").Context; diff --git a/src/arch/riscv64/context.S b/src/arch/riscv64/context.S index ae04e7b..449b62b 100644 --- a/src/arch/riscv64/context.S +++ b/src/arch/riscv64/context.S @@ -49,12 +49,15 @@ .set SSTATUS_SPIE, (1 << 5) __rv64_task_enter_user: + csrw sscratch, tp // TODO setup user thread pointer ld a0, (sp) // argument ld ra, 16(sp) // entry ld sp, 8(sp) // stack + mv tp, zero + // Clear SPP to zero to indicate a return to U-mode li t1, SSTATUS_SPP not t1, t1 @@ -81,6 +84,7 @@ __rv64_task_enter_kernel: ori t1, t1, SSTATUS_SPP csrw sstatus, t1 csrw sepc, t0 + csrw sscratch, zero sret diff --git a/src/arch/riscv64/context.zig b/src/arch/riscv64/context.zig index 8d8ef63..6c57008 100644 --- a/src/arch/riscv64/context.zig +++ b/src/arch/riscv64/context.zig @@ -3,6 +3,7 @@ const mem = @import("../../mem.zig"); const kernel = @import("../../kernel.zig"); const regs = @import("regs.zig"); const vmm = @import("vmm.zig"); +const riscv64 = @import("../riscv64.zig"); const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; const log = kernel.log; @@ -72,6 +73,7 @@ pub const Context = extern struct { } fn load_state(self: *@This()) void { + riscv64.t_tdata_assembly.kernel_stack_pointer = @intFromPtr(self.kstack.sp); regs.SATP.set(self.satp); } diff --git a/src/arch/riscv64/exception.zig b/src/arch/riscv64/exception.zig index cfcca37..d5b3718 100644 --- a/src/arch/riscv64/exception.zig +++ b/src/arch/riscv64/exception.zig @@ -51,6 +51,13 @@ pub const ExceptionFrame = extern struct { sN: [12]usize, aN: [8]usize, + umode_sp: usize, + sstatus: usize, + sepc: usize, + stval: usize, + scause: usize, + sscratch: usize, + pub fn dump(self: *const @This(), comptime level: log.Level) void { log.writeln(level, " ra = 0x{x:016} gp = 0x{x:016}", .{ self.ra, self.gp }); log.writeln(level, " t0 = 0x{x:016} t1 = 0x{x:016}", .{ self.tN[0], self.tN[1] }); @@ -78,17 +85,38 @@ pub fn init() void { } export fn rv64SmodeTrapGeneral(frame: *ExceptionFrame) callconv(.C) void { - const scause = regs.SCAUSE.read(); + // const scause = regs.SCAUSE.read(); + const scause = @as(regs.SCAUSE.Bits, @bitCast(frame.scause)); if (scause.INTERRUPT) { return rv64SmodeTrapInterrupt(frame); } + const sstatus = @as(regs.SSTATUS.Bits, @bitCast(frame.sstatus)); const cause = @as(ExceptionCause, @enumFromInt(scause.CODE)); - const epc = regs.SEPC.get(); - const tval = regs.STVAL.get(); - log.err("S-mode exception:", .{}); + const is_umode = !sstatus.SPP; + + switch (cause) { + .ecall_umode => { + log.info("ecall from U-mode!!!", .{}); + log.info("args:", .{}); + for (0..6) |i| { + log.info(" a{} = 0x{x:016} ({})", .{ i, frame.aN[i], frame.aN[i] }); + } + + // Add size of `ecall` instruction to return epc to execute the next instruction + // instead of falling back to the same `ecall` instruction that caused this + // interrupt. + frame.sepc += 4; + return; + }, + else => {}, + } + + const mode_str = if (is_umode) "U-mode" else "S-mode"; + + log.err("{s} exception:", .{mode_str}); log.err(" Cause: {s} (0x{x})", .{ cause.name(), scause.CODE }); - log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ tval, epc }); + log.err(" stval = 0x{x:016} sepc = 0x{x:016}", .{ frame.stval, frame.sepc }); frame.dump(.err); @panic("Unhandled exception in S-mode"); diff --git a/src/arch/riscv64/vectors.S b/src/arch/riscv64/vectors.S index 2ca96d1..9d07409 100644 --- a/src/arch/riscv64/vectors.S +++ b/src/arch/riscv64/vectors.S @@ -3,8 +3,17 @@ .extern rv64SmodeTrapInterrupt // ra, gp, 7×tN, 12×sN, 8×aN -.set GP_REGS_SIZE, (2 + 7 + 12 + 8) * 8 -.set TRAP_CONTEXT_SIZE, (GP_REGS_SIZE) +.set GP_REGS_SIZE, ((2 + 7 + 12 + 8) * 8) +// U-mode sp, sstatus, sepc, stval, scause, sscratch +.set SP_REGS_SIZE, (6 * 8) +.set TRAP_CONTEXT_SIZE, (GP_REGS_SIZE + SP_REGS_SIZE) + +.set REG_UMODE_SP, (GP_REGS_SIZE + 0 * 8) +.set REG_SSTATUS, (GP_REGS_SIZE + 1 * 8) +.set REG_SEPC, (GP_REGS_SIZE + 2 * 8) +.set REG_STVAL, (GP_REGS_SIZE + 3 * 8) +.set REG_SCAUSE, (GP_REGS_SIZE + 4 * 8) +.set REG_SSCRATCH, (GP_REGS_SIZE + 5 * 8) .macro SAVE_GP_REGS sd ra, 0 * 8(sp) @@ -41,19 +50,110 @@ sd a7, 28 * 8(sp) .endm +.macro RESTORE_GP_REGS + ld ra, 0 * 8(sp) + ld gp, 1 * 8(sp) + + ld t0, 2 * 8(sp) + ld t1, 3 * 8(sp) + ld t2, 4 * 8(sp) + ld t3, 5 * 8(sp) + ld t4, 6 * 8(sp) + ld t5, 7 * 8(sp) + ld t6, 8 * 8(sp) + + ld s0, 9 * 8(sp) + ld s1, 10 * 8(sp) + ld s2, 11 * 8(sp) + ld s3, 12 * 8(sp) + ld s4, 13 * 8(sp) + ld s5, 14 * 8(sp) + ld s6, 15 * 8(sp) + ld s7, 16 * 8(sp) + ld s8, 17 * 8(sp) + ld s9, 18 * 8(sp) + ld s10, 19 * 8(sp) + ld s11, 20 * 8(sp) + + ld a0, 21 * 8(sp) + ld a1, 22 * 8(sp) + ld a2, 23 * 8(sp) + ld a3, 24 * 8(sp) + ld a4, 25 * 8(sp) + ld a5, 26 * 8(sp) + ld a6, 27 * 8(sp) + ld a7, 28 * 8(sp) +.endm + +.set TPREL_KERNEL_STACK, (0 * 8) +.set TPREL_USER_STACK, (1 * 8) + +.set SSTATUS_SPP, (1 << 8) + .macro SMODE_TRAP n, handler .type __rv64_smode_trap_\n, @function __rv64_smode_trap_\n: - // TODO properly handle traps coming from U-mode - // TODO save CSRs - addi sp, sp, -(TRAP_CONTEXT_SIZE) - SAVE_GP_REGS - mv a0, sp + // If coming from U-mode, sscratch = kernel-mode tp + // If coming from S-mode, sscratch = 0 + csrrw tp, sscratch, tp + bnez tp, 1f + // Coming from S-mode + sd sp, TPREL_KERNEL_STACK(tp) // tdata_assembly.kernel_stack = sp + // [fallthrough] +1: + // Coming from U-mode + sd sp, TPREL_USER_STACK(tp) // tdata_assembly.user_stack = sp + ld sp, TPREL_KERNEL_STACK(tp) // sp = tdata_assembly.kernel_stack + // Store pre-trap context + addi sp, sp, -(TRAP_CONTEXT_SIZE) + + SAVE_GP_REGS + // Save special-purpose registers + ld t0, TPREL_USER_STACK(tp) + csrr t1, sstatus + csrr t2, sepc + csrr t3, stval + csrr t4, scause + csrr t5, sscratch + + sd t0, REG_UMODE_SP (sp) + sd t1, REG_SSTATUS (sp) + sd t2, REG_SEPC (sp) + sd t3, REG_STVAL (sp) + sd t4, REG_SCAUSE (sp) + sd t5, REG_SSCRATCH (sp) + + // Reset sscratch to zero to make sure a nested S-mode -> S-mode exception + // happens properly + csrw sscratch, zero + + mv a0, sp call \handler - // TODO return from exception - j . + // Return from exception + ld t0, REG_SSTATUS (sp) + andi t0, t0, SSTATUS_SPP + bnez t0, 2f + + // sstatus.SPP == 0, return to U-mode + // Restore sscratch to its proper value + csrw sscratch, tp + // [fallthrough] +2: + // sstatus.SPP == 1, return to S-mode + ld t0, REG_SSTATUS (sp) + ld t1, REG_SEPC (sp) + csrw sstatus, t0 + csrw sepc, t1 + + // Restore general-purpose registers + RESTORE_GP_REGS + + ld tp, REG_SSCRATCH (sp) + ld sp, REG_UMODE_SP (sp) + + sret .size __rv64_smode_trap_\n, . - __rv64_smode_trap_\n .endm diff --git a/src/kernel.zig b/src/kernel.zig index 7d105cc..76f3510 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -38,14 +38,18 @@ pub export fn kernel_main() callconv(.C) noreturn { const code = switch (comptime arch.cpu) { .riscv64 => &[_]u8{ - 0x93, 0x02, 0xB0, 0x07, // li t0, 123 0x13, 0x01, 0x81, 0xFF, // addi sp, sp, -8 + 0x73, 0x00, 0x00, 0x00, // ecall + 0x73, 0x00, 0x00, 0x00, // ecall + 0x93, 0x02, 0xB0, 0x07, // li t0, 123 0x23, 0x30, 0x51, 0x00, // sd t0, (sp) 0x6F, 0x00, 0x00, 0x00, // j . }, .aarch64 => &[_]u8{ - 0x60, 0x0F, 0x80, 0xD2, // mov x0, #123 0xFF, 0x23, 0x00, 0xD1, // sub sp, sp, #8 + 0x01, 0x00, 0x00, 0xD4, // svc #0 + 0x01, 0x00, 0x00, 0xD4, // svc #0 + 0x60, 0x0F, 0x80, 0xD2, // mov x0, #123 0xE0, 0x03, 0x00, 0xF9, // str x0, [sp] 0x00, 0x00, 0x00, 0x14, // b . }, -- 2.53.0 From 3c27839bffa62491539025a385e3d7e3e8b27af3 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 18:10:46 +0200 Subject: [PATCH 07/10] WIP: Add userspace zig program --- build.zig | 82 ++++++++++++++++++++++++++++------ src/arch/aarch64/exception.zig | 10 ++--- src/arch/riscv64/boot.zig | 3 ++ src/arch/riscv64/exception.zig | 17 +++---- src/debug.zig | 2 +- src/kernel.zig | 30 +++---------- src/syscall.zig | 27 +++++++++++ src/thread.zig | 40 +++++++++++------ src/util/dtb.zig | 22 ++++----- user/aarch64.ld | 20 +++++++++ user/main.zig | 59 ++++++++++++++++++++++++ user/riscv64.ld | 20 +++++++++ 12 files changed, 256 insertions(+), 76 deletions(-) create mode 100644 src/syscall.zig create mode 100644 user/aarch64.ld create mode 100644 user/main.zig create mode 100644 user/riscv64.ld diff --git a/build.zig b/build.zig index 209c2b9..ee10aff 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const SupportedArch = enum { aarch64, riscv64, - fn make_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget { + fn make_kernel_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget { switch (self) { .riscv64 => { return b.resolveTargetQuery(.{ @@ -38,7 +38,47 @@ const SupportedArch = enum { } } - fn add_target_specific(self: SupportedArch, b: *std.Build, kernel: *std.Build.Step.Compile) anyerror!*std.Build.Step { + fn make_userspace_target(self: SupportedArch, b: *std.Build) std.Build.ResolvedTarget { + // TODO: it's the same for now, until userspace support for CPU extensions like floating + // point is added. + return self.make_kernel_target(b); + } + + fn configure_user( + self: SupportedArch, + b: *std.Build, + user: *std.Build.Step.Compile, + ) anyerror!*std.Build.Step { + switch (self) { + .riscv64 => { + user.setLinkerScript(b.path("user/riscv64.ld")); + }, + .aarch64 => { + user.setLinkerScript(b.path("user/aarch64.ld")); + }, + } + + b.installArtifact(user); + + const elf2bin = b.addSystemCommand(&.{ + "llvm-objcopy", + "-O", + "binary", + "zig-out/bin/userspace", + "zig-out/bin/userspace.bin", + }); + + elf2bin.step.dependOn(&user.step); + + return &elf2bin.step; + } + + fn configure_kernel( + self: SupportedArch, + b: *std.Build, + kernel: *std.Build.Step.Compile, + user: *std.Build.Step, + ) anyerror!*std.Build.Step { switch (self) { .riscv64 => { kernel.entry = .{ .symbol_name = "__rv64_entry" }; @@ -61,6 +101,7 @@ const SupportedArch = enum { }, } + kernel.step.dependOn(user); b.installArtifact(kernel); if (self == .riscv64 or self == .aarch64) { @@ -136,29 +177,37 @@ fn insert_fake_linux_image_header(step: *std.Build.Step, opts: std.Build.Step.Ma _ = opts; } -fn build_riscv64(b: *std.Build) anyerror!void { - _ = b; -} - pub fn build(b: *std.Build) anyerror!void { const maybe_arch_option = b.option(SupportedArch, "arch", "Architecture to use"); const arch = maybe_arch_option orelse DEFAULT_ARCH; - const target = arch.make_target(b); + const kernel_target = arch.make_kernel_target(b); + const user_target = arch.make_userspace_target(b); const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); - const code_model: std.builtin.CodeModel = switch (arch) { + const kernel_code_model: std.builtin.CodeModel = switch (arch) { .riscv64 => .medium, .aarch64 => .small, }; + const user_code_model = kernel_code_model; + const kernel_module = b.addModule("kernel", .{ .optimize = optimize, - .target = target, + .target = kernel_target, .pic = true, .red_zone = false, - .code_model = code_model, + .code_model = kernel_code_model, .root_source_file = b.path("src/kernel.zig"), }); + kernel_module.addAnonymousImport("userspace", .{ .root_source_file = b.path("zig-out/bin/userspace.bin") }); + + const user_module = b.addModule("userspace", .{ + .optimize = optimize, + .target = user_target, + .red_zone = false, + .code_model = user_code_model, + .root_source_file = b.path("user/main.zig"), + }); const kernel = b.addExecutable(.{ .name = "kernel", .root_module = kernel_module, @@ -167,6 +216,12 @@ pub fn build(b: *std.Build) anyerror!void { }); kernel.pie = true; + const user = b.addExecutable(.{ + .name = "userspace", + .root_module = user_module, + .use_lld = true, + }); + const install_docs = b.addInstallDirectory(.{ .source_dir = kernel.getEmittedDocs(), .install_dir = .prefix, @@ -176,10 +231,11 @@ pub fn build(b: *std.Build) anyerror!void { const docs_step = b.step("docs", "Install documentation"); docs_step.dependOn(&install_docs.step); - const kernel_step = try arch.add_target_specific(b, kernel); + const user_step = try arch.configure_user(b, user); + const kernel_step = try arch.configure_kernel(b, kernel, user_step); // TODO QEMU binary override - const qemu_info = switch (target.result.cpu.arch) { + const qemu_info = switch (kernel_target.result.cpu.arch) { .riscv64 => .{ "qemu-system-riscv64", "rv64" }, .aarch64 => .{ "qemu-system-aarch64", "cortex-a72" }, else => unreachable, @@ -201,7 +257,7 @@ pub fn build(b: *std.Build) anyerror!void { "none", }); - if (target.result.cpu.arch == .riscv64) { + if (kernel_target.result.cpu.arch == .riscv64) { qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" }); } diff --git a/src/arch/aarch64/exception.zig b/src/arch/aarch64/exception.zig index 43e1849..5a33ef6 100644 --- a/src/arch/aarch64/exception.zig +++ b/src/arch/aarch64/exception.zig @@ -3,6 +3,7 @@ const kernel = @import("../../kernel.zig"); const arch = kernel.arch; const log = kernel.debug.log; +const syscall = kernel.syscall; extern const __aa64_exception_vectors: u8; @@ -91,11 +92,10 @@ export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void { switch (esr.as_enum()) { .svc => { - log.info("Syscall executed!", .{}); - log.info("args:", .{}); - for (0..6) |i| { - log.info(" x{} = 0x{x:016} ({})", .{ i, frame.xN[i], frame.xN[i] }); - } + const func = frame.xN[8]; + const args = frame.xN[0..6]; + const result = syscall.syscall_handler(func, args); + frame.xN[0] = result; return; }, else => {}, diff --git a/src/arch/riscv64/boot.zig b/src/arch/riscv64/boot.zig index 0503833..8dd72f4 100644 --- a/src/arch/riscv64/boot.zig +++ b/src/arch/riscv64/boot.zig @@ -40,6 +40,9 @@ fn bsp_upper_entry(real_address: usize, unused: usize) callconv(.C) noreturn { kernel.mem.PhysicalAddress.g_virtualize_base = vmm.VIRTUALIZE_BASE; kernel.mem.PhysicalAddress.g_virtualize_size = vmm.virtualize_range(); + // Enable supervisor access to user memory + regs.SSTATUS.modify(.{ .SUM = true }, .{}); + // Setup physical memory management setup_memory_from_fdt(real_address); diff --git a/src/arch/riscv64/exception.zig b/src/arch/riscv64/exception.zig index d5b3718..470a1b4 100644 --- a/src/arch/riscv64/exception.zig +++ b/src/arch/riscv64/exception.zig @@ -1,8 +1,9 @@ const regs = @import("regs.zig"); -const debug = @import("../../debug.zig"); -const arch = @import("../../kernel.zig").arch; +const kernel = @import("../../kernel.zig"); -const log = debug.log; +const syscall = kernel.syscall; +const arch = kernel.arch; +const log = kernel.log; extern fn __rv64_exception_vectors() void; @@ -97,11 +98,11 @@ export fn rv64SmodeTrapGeneral(frame: *ExceptionFrame) callconv(.C) void { switch (cause) { .ecall_umode => { - log.info("ecall from U-mode!!!", .{}); - log.info("args:", .{}); - for (0..6) |i| { - log.info(" a{} = 0x{x:016} ({})", .{ i, frame.aN[i], frame.aN[i] }); - } + // TODO assert that `is_umode` + const func = frame.aN[0]; + const args = frame.aN[1..7]; + const result = syscall.syscall_handler(func, args); + frame.aN[0] = result; // Add size of `ecall` instruction to return epc to execute the next instruction // instead of falling back to the same `ecall` instruction that caused this diff --git a/src/debug.zig b/src/debug.zig index 952f9a3..6f0b5b9 100644 --- a/src/debug.zig +++ b/src/debug.zig @@ -55,7 +55,7 @@ pub const log = struct { } /// Write raw byte data into the debugging output. - pub fn write_waw(data: []const u8) void { + pub fn write_raw(data: []const u8) void { _ = write_wrapper_fn(0, data) catch return; } diff --git a/src/kernel.zig b/src/kernel.zig index 76f3510..18c672f 100644 --- a/src/kernel.zig +++ b/src/kernel.zig @@ -7,6 +7,7 @@ pub const arena = @import("arena.zig"); pub const thread = @import("thread.zig"); pub const util = @import("util.zig"); pub const sync = @import("sync.zig"); +pub const syscall = @import("syscall.zig"); pub const log = debug.log; pub const vmm = mem.vmm; @@ -16,6 +17,8 @@ pub const TRACE_PHYSICAL_ALLOCATOR: bool = false; const std = @import("std"); +const userspace_code = @embedFile("userspace"); + fn f0(arg: usize) callconv(.C) void { log.info("Argument is {}", .{arg}); } @@ -36,37 +39,14 @@ pub export fn kernel_main() callconv(.C) noreturn { var a = arena.Arena.init(256 * 0x1000) orelse @panic("Could not setup kernel arena"); thread.Queue.init_this_cpu(&a); - const code = switch (comptime arch.cpu) { - .riscv64 => &[_]u8{ - 0x13, 0x01, 0x81, 0xFF, // addi sp, sp, -8 - 0x73, 0x00, 0x00, 0x00, // ecall - 0x73, 0x00, 0x00, 0x00, // ecall - 0x93, 0x02, 0xB0, 0x07, // li t0, 123 - 0x23, 0x30, 0x51, 0x00, // sd t0, (sp) - 0x6F, 0x00, 0x00, 0x00, // j . - }, - .aarch64 => &[_]u8{ - 0xFF, 0x23, 0x00, 0xD1, // sub sp, sp, #8 - 0x01, 0x00, 0x00, 0xD4, // svc #0 - 0x01, 0x00, 0x00, 0xD4, // svc #0 - 0x60, 0x0F, 0x80, 0xD2, // mov x0, #123 - 0xE0, 0x03, 0x00, 0xF9, // str x0, [sp] - 0x00, 0x00, 0x00, 0x14, // b . - }, - }; - const t1 = thread.test_create_user_from_code(&a, code) catch @panic("Could not create test thread"); + log.info("Userspace code size: {} bytes", .{userspace_code.len}); + const t1 = thread.test_create_user_from_code(&a, userspace_code) catch @panic("Could not create test thread"); const t = thread.Thread.create_kernel(&a, &f0, 1234); thread.enqueue(t); thread.enqueue(t1); log.info("Test", .{}); - // log.write("\x1B[2J", .{}); - // const pc = @intFromPtr(&f0); - // for (0..4) |i| { - // const t = thread.Thread.create_kernel(&a, pc, i); - // thread.enqueue(t); - // } thread.enter(); } diff --git a/src/syscall.zig b/src/syscall.zig new file mode 100644 index 0000000..4a74a42 --- /dev/null +++ b/src/syscall.zig @@ -0,0 +1,27 @@ +const kernel = @import("kernel.zig"); + +const thread = kernel.thread; +const log = kernel.log; + +pub fn syscall_handler(func: usize, args: []usize) usize { + // TODO validation, etc + const cthread = thread.Thread.current(); + switch (func) { + // debug_print(text_data: [*]const u8, text_len: usize) + 1 => { + if (args[0] != 0 and args[1] != 0) { + const text = @as([*]u8, @ptrFromInt(args[0]))[0..args[1]]; + log.debug("{*}: {s}", .{ cthread, text }); + } + return 0; + }, + // exit(code: u32) + 2 => { + log.info("{*} exits with code {}", .{ cthread, args[0] }); + thread.Thread.exit_current(); + }, + else => { + @panic("Undefined syscall"); + }, + } +} diff --git a/src/thread.zig b/src/thread.zig index 2cecc79..319a7d5 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -291,29 +291,43 @@ pub fn yield() void { } pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Error!*Thread { + const L3 = mem.vmm.L3; + var address_space = try ProcessAddressSpace.init(a); errdefer { address_space.clear(); } // @ 0x200000 - const code_page = mem.phys.alloc_page() orelse return error.out_of_memory; - errdefer { - mem.phys.free_page(code_page); - } - // @ 0x201000 - const stack_page = mem.phys.alloc_page() orelse return error.out_of_memory; - errdefer { - mem.phys.free_page(stack_page); + const code_page_count = L3.page_count(code.len); + log.info("Code is {} pages", .{code_page_count}); + var offset: usize = 0; + var address: usize = 0x200000; + while (offset < code.len) { + const page_offset = address % L3.SIZE; + const amount = @min(L3.SIZE - page_offset, code.len - offset); + + const page = mem.phys.alloc_page() orelse return error.out_of_memory; + try address_space.map_single_page(address, page); + + const page_data = @as([*]u8, @ptrFromInt(page.virtualize()))[0..amount]; + @memcpy(page_data, code[offset .. offset + amount]); + + address += amount; + offset += amount; } - try address_space.map_single_page(0x200000, code_page); - try address_space.map_single_page(0x201000, stack_page); + // @ 0x400000 + const sp_base = 0x400000; + for (0..8) |i| { + const page = mem.phys.alloc_page() orelse return error.out_of_memory; + try address_space.map_single_page(sp_base + i * L3.SIZE, page); + } + const sp = sp_base + 8 * L3.SIZE; - const page_data = @as([*]u8, @ptrFromInt(code_page.virtualize()))[0..code.len]; - @memcpy(page_data, code); + log.info("Enter with sp = 0x{x}", .{sp}); - const thread = Thread.create_user(a, address_space, 0x200000, 0x201000, 0); + const thread = Thread.create_user(a, address_space, 0x200000, sp, 0); return thread; } diff --git a/src/util/dtb.zig b/src/util/dtb.zig index 52daf5b..01c4719 100644 --- a/src/util/dtb.zig +++ b/src/util/dtb.zig @@ -487,7 +487,7 @@ pub const Fdt = struct { fn dump_property(property: *const FdtNodeProp, depth: usize, all_strings: bool) void { for (0..depth) |_| { - log.write_waw(" "); + log.write_raw(" "); } log.write("{s}", .{property.name}); @@ -510,9 +510,9 @@ pub const Fdt = struct { var f = true; while (v.next()) |s| { if (f) { - log.write_waw(" = "); + log.write_raw(" = "); } else { - log.write_waw(", "); + log.write_raw(", "); } f = false; log.write("\"{s}\"", .{s}); @@ -520,17 +520,17 @@ pub const Fdt = struct { } else { // Dump the rest as a cell array const len = property.len_cells(); - log.write_waw(" = <"); + log.write_raw(" = <"); for (0..len) |i| { if (i != 0) { - log.write_waw(", "); + log.write_raw(", "); } log.write("0x{x}", .{property.get_cell_unchecked(i)}); } - log.write_waw(">"); + log.write_raw(">"); } - log.write_waw(";\r\n"); + log.write_raw(";\r\n"); } fn dump_node(node: *const FdtNode, depth: usize) void { @@ -538,12 +538,12 @@ pub const Fdt = struct { var first_child = true; for (0..depth) |_| { - log.write_waw(" "); + log.write_raw(" "); } if (node.name.len != 0) { log.write("{s} ", .{node.name}); } - log.write_waw("{\r\n"); + log.write_raw("{\r\n"); var properties = node.prop_iterator(); const all_strings = std.mem.eql(u8, node.name, "aliases"); while (properties.next()) |property| { @@ -553,13 +553,13 @@ pub const Fdt = struct { var children = node.children(); while (children.next()) |child| { if (any_properties and first_child) { - log.write_waw("\r\n"); + log.write_raw("\r\n"); } first_child = false; dump_node(&child, depth + 1); } for (0..depth) |_| { - log.write_waw(" "); + log.write_raw(" "); } log.write("}},\r\n", .{}); } diff --git a/user/aarch64.ld b/user/aarch64.ld new file mode 100644 index 0000000..2936fdc --- /dev/null +++ b/user/aarch64.ld @@ -0,0 +1,20 @@ +SECTIONS { + . = 0x200000; + + .text : { + *(.text.entry*); + *(.text*) + } + + .rodata : ALIGN(4K) { + *(.rodata*) + } + + .data : ALIGN(4K) { + *(.data*) + } + + .bss : ALIGN(4K) { + *(.bss*) + } +} diff --git a/user/main.zig b/user/main.zig new file mode 100644 index 0000000..9fd9b64 --- /dev/null +++ b/user/main.zig @@ -0,0 +1,59 @@ +const builtin = @import("builtin"); + +const syscall = switch (builtin.cpu.arch) { + .aarch64 => struct { + pub fn syscall1(func: usize, arg0: usize) usize { + return asm volatile ("svc #0" + : [result] "={x0}" (-> usize), + : [arg0] "{x0}" (arg0), + [func] "{x8}" (func), + : "memory" + ); + } + + pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + return asm volatile ("svc #0" + : [result] "={x0}" (-> usize), + : [arg0] "{x0}" (arg0), + [arg1] "{x1}" (arg1), + [func] "{x8}" (func), + : "memory" + ); + } + }, + .riscv64 => struct { + pub fn syscall1(func: usize, arg0: usize) usize { + return asm volatile ("ecall" + : [result] "={a0}" (-> usize), + : [func] "{a0}" (func), + [arg0] "{a1}" (arg0), + : "memory" + ); + } + + pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + return asm volatile ("ecall" + : [result] "={a0}" (-> usize), + : [func] "{a0}" (func), + [arg0] "{a1}" (arg0), + [arg1] "{a2}" (arg1), + : "memory" + ); + } + }, + else => @compileError("Unsupported architecture"), +}; + +fn exit(code: u32) noreturn { + _ = syscall.syscall1(2, code); + unreachable; +} + +fn debug_print(text: []const u8) void { + _ = syscall.syscall2(1, @intFromPtr(text.ptr), text.len); +} + +export fn _start() linksection(".text.entry") callconv(.C) noreturn { + debug_print("Hello!"); + exit(0); +} diff --git a/user/riscv64.ld b/user/riscv64.ld new file mode 100644 index 0000000..2936fdc --- /dev/null +++ b/user/riscv64.ld @@ -0,0 +1,20 @@ +SECTIONS { + . = 0x200000; + + .text : { + *(.text.entry*); + *(.text*) + } + + .rodata : ALIGN(4K) { + *(.rodata*) + } + + .data : ALIGN(4K) { + *(.data*) + } + + .bss : ALIGN(4K) { + *(.bss*) + } +} -- 2.53.0 From 0f157caf5cd1886f57752ec629e52f8238e28636 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 26 Mar 2025 21:57:18 +0200 Subject: [PATCH 08/10] WIP: Make userspace a bit more useful --- src/thread.zig | 2 +- user/arch.zig | 9 +++++++ user/arch/aarch64.zig | 20 ++++++++++++++ user/arch/riscv64.zig | 20 ++++++++++++++ user/log.zig | 28 +++++++++++++++++++ user/main.zig | 63 +++++-------------------------------------- user/syscall.zig | 12 +++++++++ 7 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 user/arch.zig create mode 100644 user/arch/aarch64.zig create mode 100644 user/arch/riscv64.zig create mode 100644 user/log.zig create mode 100644 user/syscall.zig diff --git a/src/thread.zig b/src/thread.zig index 319a7d5..424b01a 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -327,7 +327,7 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro log.info("Enter with sp = 0x{x}", .{sp}); - const thread = Thread.create_user(a, address_space, 0x200000, sp, 0); + const thread = Thread.create_user(a, address_space, 0x200000, sp, 1234); return thread; } diff --git a/user/arch.zig b/user/arch.zig new file mode 100644 index 0000000..0b726e9 --- /dev/null +++ b/user/arch.zig @@ -0,0 +1,9 @@ +const builtin = @import("builtin"); + +const impl = switch (builtin.cpu.arch) { + .riscv64 => @import("arch/riscv64.zig"), + .aarch64 => @import("arch/aarch64.zig"), + else => @compileError("Unsupported architecture"), +}; + +pub const syscall = impl.syscall; diff --git a/user/arch/aarch64.zig b/user/arch/aarch64.zig new file mode 100644 index 0000000..69b0c24 --- /dev/null +++ b/user/arch/aarch64.zig @@ -0,0 +1,20 @@ +pub const syscall = struct { + pub fn syscall1(func: usize, arg0: usize) usize { + return asm volatile ("svc #0" + : [result] "={x0}" (-> usize), + : [arg0] "{x0}" (arg0), + [func] "{x8}" (func), + : "memory" + ); + } + + pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + return asm volatile ("svc #0" + : [result] "={x0}" (-> usize), + : [arg0] "{x0}" (arg0), + [arg1] "{x1}" (arg1), + [func] "{x8}" (func), + : "memory" + ); + } +}; diff --git a/user/arch/riscv64.zig b/user/arch/riscv64.zig new file mode 100644 index 0000000..dcfc0a2 --- /dev/null +++ b/user/arch/riscv64.zig @@ -0,0 +1,20 @@ +pub const syscall = struct { + pub fn syscall1(func: usize, arg0: usize) usize { + return asm volatile ("ecall" + : [result] "={a0}" (-> usize), + : [func] "{a0}" (func), + [arg0] "{a1}" (arg0), + : "memory" + ); + } + + pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + return asm volatile ("ecall" + : [result] "={a0}" (-> usize), + : [func] "{a0}" (func), + [arg0] "{a1}" (arg0), + [arg1] "{a2}" (arg1), + : "memory" + ); + } +}; diff --git a/user/log.zig b/user/log.zig new file mode 100644 index 0000000..69209fe --- /dev/null +++ b/user/log.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const syscall = @import("syscall.zig"); + +pub const BUFFER_SIZE: usize = 512; + +pub const Writer = struct { + buffer: [BUFFER_SIZE]u8 = undefined, + position: usize = 0, + + fn write(self: *Writer, bytes: []const u8) error{}!usize { + const amount = @min(BUFFER_SIZE - self.position, bytes.len); + if (amount != 0) { + @memcpy(self.buffer[self.position .. self.position + amount], bytes[0..amount]); + self.position += amount; + } + return amount; + } +}; + +pub fn println(comptime format: []const u8, args: anytype) void { + const W = std.io.GenericWriter(*Writer, error{}, Writer.write); + var context = Writer{}; + var w = W{ .context = &context }; + w.print(format, args) catch return; + if (context.position != 0) { + syscall.debug_print(context.buffer[0..context.position]); + } +} diff --git a/user/main.zig b/user/main.zig index 9fd9b64..4d05b1a 100644 --- a/user/main.zig +++ b/user/main.zig @@ -1,59 +1,8 @@ -const builtin = @import("builtin"); +pub const arch = @import("arch.zig"); +pub const syscall = @import("syscall.zig"); +pub const log = @import("log.zig"); -const syscall = switch (builtin.cpu.arch) { - .aarch64 => struct { - pub fn syscall1(func: usize, arg0: usize) usize { - return asm volatile ("svc #0" - : [result] "={x0}" (-> usize), - : [arg0] "{x0}" (arg0), - [func] "{x8}" (func), - : "memory" - ); - } - - pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { - return asm volatile ("svc #0" - : [result] "={x0}" (-> usize), - : [arg0] "{x0}" (arg0), - [arg1] "{x1}" (arg1), - [func] "{x8}" (func), - : "memory" - ); - } - }, - .riscv64 => struct { - pub fn syscall1(func: usize, arg0: usize) usize { - return asm volatile ("ecall" - : [result] "={a0}" (-> usize), - : [func] "{a0}" (func), - [arg0] "{a1}" (arg0), - : "memory" - ); - } - - pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { - return asm volatile ("ecall" - : [result] "={a0}" (-> usize), - : [func] "{a0}" (func), - [arg0] "{a1}" (arg0), - [arg1] "{a2}" (arg1), - : "memory" - ); - } - }, - else => @compileError("Unsupported architecture"), -}; - -fn exit(code: u32) noreturn { - _ = syscall.syscall1(2, code); - unreachable; -} - -fn debug_print(text: []const u8) void { - _ = syscall.syscall2(1, @intFromPtr(text.ptr), text.len); -} - -export fn _start() linksection(".text.entry") callconv(.C) noreturn { - debug_print("Hello!"); - exit(0); +export fn _start(arg: usize) linksection(".text.entry") callconv(.C) noreturn { + log.println("arg=0x{x} ({})", .{ arg, arg }); + syscall.exit(0); } diff --git a/user/syscall.zig b/user/syscall.zig new file mode 100644 index 0000000..7ac13d3 --- /dev/null +++ b/user/syscall.zig @@ -0,0 +1,12 @@ +const arch = @import("arch.zig"); + +const sc = arch.syscall; + +pub fn exit(code: u32) noreturn { + _ = sc.syscall1(2, code); + unreachable; +} + +pub fn debug_print(text: []const u8) void { + _ = sc.syscall2(1, @intFromPtr(text.ptr), text.len); +} -- 2.53.0 From d25e1c034638a7e2881a963d62d0d3e9e45d2076 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 27 Mar 2025 11:35:03 +0200 Subject: [PATCH 09/10] sys: add abi module to bridge kernel<->userspace --- abi/abi.zig | 3 +++ build.zig | 10 ++++++++ src/syscall.zig | 58 +++++++++++++++++++++++++++---------------- user/arch/aarch64.zig | 10 +++++--- user/arch/riscv64.zig | 10 +++++--- user/log.zig | 2 +- user/syscall.zig | 8 +++--- 7 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 abi/abi.zig diff --git a/abi/abi.zig b/abi/abi.zig new file mode 100644 index 0000000..3b9e8f9 --- /dev/null +++ b/abi/abi.zig @@ -0,0 +1,3 @@ +pub const SyscallNumber = enum(usize) { SYS_debug_write = 1, SYS_exit = 2, _ }; + +pub const MAX_SYSCALL: usize = 32; diff --git a/build.zig b/build.zig index ee10aff..2fd002e 100644 --- a/build.zig +++ b/build.zig @@ -201,6 +201,13 @@ pub fn build(b: *std.Build) anyerror!void { }); kernel_module.addAnonymousImport("userspace", .{ .root_source_file = b.path("zig-out/bin/userspace.bin") }); + const abi_module = b.addModule("abi", .{ + .optimize = optimize, + .red_zone = false, + .target = kernel_target, + .code_model = kernel_code_model, + .root_source_file = b.path("abi/abi.zig"), + }); const user_module = b.addModule("userspace", .{ .optimize = optimize, .target = user_target, @@ -216,6 +223,9 @@ pub fn build(b: *std.Build) anyerror!void { }); kernel.pie = true; + user_module.addImport("abi", abi_module); + kernel_module.addImport("abi", abi_module); + const user = b.addExecutable(.{ .name = "userspace", .root_module = user_module, diff --git a/src/syscall.zig b/src/syscall.zig index 4a74a42..f58cc31 100644 --- a/src/syscall.zig +++ b/src/syscall.zig @@ -1,27 +1,43 @@ const kernel = @import("kernel.zig"); +const abi = @import("abi"); const thread = kernel.thread; +const Thread = thread.Thread; const log = kernel.log; -pub fn syscall_handler(func: usize, args: []usize) usize { - // TODO validation, etc - const cthread = thread.Thread.current(); - switch (func) { - // debug_print(text_data: [*]const u8, text_len: usize) - 1 => { - if (args[0] != 0 and args[1] != 0) { - const text = @as([*]u8, @ptrFromInt(args[0]))[0..args[1]]; - log.debug("{*}: {s}", .{ cthread, text }); - } - return 0; - }, - // exit(code: u32) - 2 => { - log.info("{*} exits with code {}", .{ cthread, args[0] }); - thread.Thread.exit_current(); - }, - else => { - @panic("Undefined syscall"); - }, - } +pub const SyscallFn = *const fn (*Thread, []usize) usize; + +pub const syscall_table: [abi.MAX_SYSCALL]?SyscallFn = make_syscall_table(); + +fn make_syscall_table() [abi.MAX_SYSCALL]?SyscallFn { + const SC = abi.SyscallNumber; + var array = [_]?SyscallFn{undefined} ** abi.MAX_SYSCALL; + array[@intFromEnum(SC.SYS_debug_write)] = sys_debug_write; + array[@intFromEnum(SC.SYS_exit)] = sys_exit; + return array; +} + +fn sys_exit(cthread: *Thread, args: []usize) usize { + log.info("{*} exits with code {}", .{ cthread, args[0] }); + Thread.exit_current(); +} + +fn sys_debug_write(cthread: *Thread, args: []usize) usize { + const message = @as([*]const u8, @ptrFromInt(args[0]))[0..args[1]]; + log.debug("{*}: {s}", .{ cthread, message }); + return 0; +} + +fn sys_undefined_syscall(cthread: *Thread, args: []usize) usize { + _ = args; + log.warn("{*} invoked an undefined syscall", .{cthread}); + Thread.exit_current(); +} + +pub fn syscall_handler(func: usize, args: []usize) usize { + const cthread = thread.Thread.current(); + const handler = if (func >= abi.MAX_SYSCALL) sys_undefined_syscall // + else syscall_table[func] // + orelse sys_undefined_syscall; + return handler(cthread, args); } diff --git a/user/arch/aarch64.zig b/user/arch/aarch64.zig index 69b0c24..c9a30c1 100644 --- a/user/arch/aarch64.zig +++ b/user/arch/aarch64.zig @@ -1,19 +1,21 @@ +const abi = @import("abi"); + pub const syscall = struct { - pub fn syscall1(func: usize, arg0: usize) usize { + pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize { return asm volatile ("svc #0" : [result] "={x0}" (-> usize), : [arg0] "{x0}" (arg0), - [func] "{x8}" (func), + [func] "{x8}" (@intFromEnum(func)), : "memory" ); } - pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize { return asm volatile ("svc #0" : [result] "={x0}" (-> usize), : [arg0] "{x0}" (arg0), [arg1] "{x1}" (arg1), - [func] "{x8}" (func), + [func] "{x8}" (@intFromEnum(func)), : "memory" ); } diff --git a/user/arch/riscv64.zig b/user/arch/riscv64.zig index dcfc0a2..c9c9dfb 100644 --- a/user/arch/riscv64.zig +++ b/user/arch/riscv64.zig @@ -1,17 +1,19 @@ +const abi = @import("abi"); + pub const syscall = struct { - pub fn syscall1(func: usize, arg0: usize) usize { + pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize { return asm volatile ("ecall" : [result] "={a0}" (-> usize), - : [func] "{a0}" (func), + : [func] "{a0}" (@intFromEnum(func)), [arg0] "{a1}" (arg0), : "memory" ); } - pub fn syscall2(func: usize, arg0: usize, arg1: usize) usize { + pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize { return asm volatile ("ecall" : [result] "={a0}" (-> usize), - : [func] "{a0}" (func), + : [func] "{a0}" (@intFromEnum(func)), [arg0] "{a1}" (arg0), [arg1] "{a2}" (arg1), : "memory" diff --git a/user/log.zig b/user/log.zig index 69209fe..b72de6b 100644 --- a/user/log.zig +++ b/user/log.zig @@ -23,6 +23,6 @@ pub fn println(comptime format: []const u8, args: anytype) void { var w = W{ .context = &context }; w.print(format, args) catch return; if (context.position != 0) { - syscall.debug_print(context.buffer[0..context.position]); + syscall.debug_write(context.buffer[0..context.position]); } } diff --git a/user/syscall.zig b/user/syscall.zig index 7ac13d3..87d16de 100644 --- a/user/syscall.zig +++ b/user/syscall.zig @@ -1,12 +1,14 @@ const arch = @import("arch.zig"); +const abi = @import("abi"); const sc = arch.syscall; +const SC = abi.SyscallNumber; pub fn exit(code: u32) noreturn { - _ = sc.syscall1(2, code); + _ = sc.syscall1(SC.SYS_exit, code); unreachable; } -pub fn debug_print(text: []const u8) void { - _ = sc.syscall2(1, @intFromPtr(text.ptr), text.len); +pub fn debug_write(text: []const u8) void { + _ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len); } -- 2.53.0 From ff2932d088fa889095ec3e18fb91afe65a4c2139 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 27 Mar 2025 23:37:11 +0200 Subject: [PATCH 10/10] WIP: test objects --- abi/abi.zig | 20 +++- src/arch/aarch64/exception.zig | 5 +- src/object.zig | 103 +++++++++++++++++ src/syscall.zig | 40 ++++--- src/thread.zig | 199 +++++++++++++++++++-------------- user/aarch64.ld | 7 ++ user/arch/aarch64.zig | 49 ++++++-- user/main.zig | 54 ++++++++- user/syscall.zig | 23 +++- 9 files changed, 383 insertions(+), 117 deletions(-) create mode 100644 src/object.zig diff --git a/abi/abi.zig b/abi/abi.zig index 3b9e8f9..944646f 100644 --- a/abi/abi.zig +++ b/abi/abi.zig @@ -1,3 +1,21 @@ -pub const SyscallNumber = enum(usize) { SYS_debug_write = 1, SYS_exit = 2, _ }; +pub const SyscallNumber = enum(usize) { + SYS_send = 1, + SYS_recv = 2, + SYS_sendrecv = 3, + _, +}; + +pub const ProcessObjectAction = enum(usize) { + ZO_process_exit = 1, + _, +}; + +pub const PhysicalMemoryObjectAction = enum(usize) { + ZO_physical_memory_allocate = 1, + ZO_physical_memory_free = 2, + _, +}; pub const MAX_SYSCALL: usize = 32; + +pub const Handle = enum(u32) { _ }; diff --git a/src/arch/aarch64/exception.zig b/src/arch/aarch64/exception.zig index 5a33ef6..53e97ad 100644 --- a/src/arch/aarch64/exception.zig +++ b/src/arch/aarch64/exception.zig @@ -92,10 +92,7 @@ export fn __aa64_el0_sync_handler(frame: *ExceptionFrame) callconv(.C) void { switch (esr.as_enum()) { .svc => { - const func = frame.xN[8]; - const args = frame.xN[0..6]; - const result = syscall.syscall_handler(func, args); - frame.xN[0] = result; + syscall.syscall_handler(frame.xN[8], frame.xN[0..6]); return; }, else => {}, diff --git a/src/object.zig b/src/object.zig new file mode 100644 index 0000000..c14673e --- /dev/null +++ b/src/object.zig @@ -0,0 +1,103 @@ +const mem = @import("mem.zig"); +const kernel = @import("kernel.zig"); +const thread = @import("thread.zig"); +const abi = @import("abi"); + +const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; +const log = kernel.log; +const Thread = thread.Thread; + +pub const PhysicalMemoryObject = struct { + fn send(self: *const PhysicalMemoryObject, caller: *Thread, message: *const [5]usize) usize { + _ = self; + _ = caller; + _ = message; + @panic("TODO: physical memory object messaging"); + } + + fn sendrecv(self: *const PhysicalMemoryObject, caller: *Thread, message: *[5]usize) usize { + switch (@as(abi.PhysicalMemoryObjectAction, @enumFromInt(message[0]))) { + .ZO_physical_memory_allocate => { + // TODO somehow track ownership of the memory by the process + const pages = mem.phys.alloc_pages(message[1]) orelse { + return 1; + }; + log.info("{*}: allocated {} pages: 0x{x}", .{caller, message[1], pages.raw}); + message[0] = pages.raw; + return 0; + }, + .ZO_physical_memory_free => { + @panic("TODO: ZO_physical_memory_free"); + }, + else => { + @panic("TODO: invalid message to Physical Memory Object"); + } + } + _ = self; + } +}; + +pub const ProcessObject = struct { + inner: *Thread, + + fn send(self: *const ProcessObject, caller: *Thread, message: *const [5]usize) usize { + _ = self; + // TODO define this in IDL/ABI + switch (@as(abi.ProcessObjectAction, @enumFromInt(message[0]))) { + .ZO_process_exit => { + log.info("{*} exited with code 0x{x} ({})", .{caller, message[1], message[1]}); + Thread.exit_current(); + }, + else => { + @panic("TODO: invalid message to Process Object"); + } + } + } + + fn sendrecv(self: *const ProcessObject, caller: *Thread, message: *[5]usize) usize { + _ = self; + _ = caller; + _ = message; + @panic("TODO: ProcessObject sendrecv()"); + } +}; + +pub const DebugObject = struct { + fn send(self: *const DebugObject, caller: *Thread, message: *const [5]usize) usize { + _ = self; + // Debug has only one message + const text = @as([*]u8, @ptrFromInt(message[0]))[0..message[1]]; + log.info("{*}: {s}", .{ caller, text }); + return 0; + } + + fn sendrecv(self: *const DebugObject, caller: *Thread, message: *[5]usize) usize { + _ = self; + _ = caller; + _ = message; + @panic("TODO: DebugObject sendrecv()"); + } +}; + +pub const Object = union(enum) { + physical_memory: PhysicalMemoryObject, + process: ProcessObject, + debug: DebugObject, + // TODO userspace "object" can be placed here + + pub fn send(self: Object, caller: *Thread, message: *const [5]usize) usize { + return switch (self) { + .physical_memory => |physical_memory| physical_memory.send(caller, message), + .process => |process| process.send(caller, message), + .debug => |debug| debug.send(caller, message), + }; + } + + pub fn sendrecv(self: Object, caller: *Thread, message: *[5]usize) usize { + return switch (self) { + .physical_memory => |physical_memory| physical_memory.sendrecv(caller, message), + .process => |process| process.sendrecv(caller, message), + .debug => |debug| debug.sendrecv(caller, message), + }; + } +}; diff --git a/src/syscall.zig b/src/syscall.zig index f58cc31..5b7088c 100644 --- a/src/syscall.zig +++ b/src/syscall.zig @@ -5,39 +5,51 @@ const thread = kernel.thread; const Thread = thread.Thread; const log = kernel.log; -pub const SyscallFn = *const fn (*Thread, []usize) usize; +pub const SyscallFn = *const fn (*Thread, *[6]usize) void; pub const syscall_table: [abi.MAX_SYSCALL]?SyscallFn = make_syscall_table(); fn make_syscall_table() [abi.MAX_SYSCALL]?SyscallFn { const SC = abi.SyscallNumber; var array = [_]?SyscallFn{undefined} ** abi.MAX_SYSCALL; - array[@intFromEnum(SC.SYS_debug_write)] = sys_debug_write; - array[@intFromEnum(SC.SYS_exit)] = sys_exit; + array[@intFromEnum(SC.SYS_send)] = sys_send; + array[@intFromEnum(SC.SYS_recv)] = sys_recv; + array[@intFromEnum(SC.SYS_sendrecv)] = sys_sendrecv; return array; } -fn sys_exit(cthread: *Thread, args: []usize) usize { - log.info("{*} exits with code {}", .{ cthread, args[0] }); - Thread.exit_current(); +fn sys_send(cthread: *Thread, frame: *[6]usize) void { + const handle: abi.Handle = @enumFromInt(frame[0]); + const object = cthread.handle(handle) orelse { + @panic("TODO: userspace invoked non-existent handle"); + }; + frame[0] = object.send(cthread, frame[1..6]); } -fn sys_debug_write(cthread: *Thread, args: []usize) usize { - const message = @as([*]const u8, @ptrFromInt(args[0]))[0..args[1]]; - log.debug("{*}: {s}", .{ cthread, message }); - return 0; +fn sys_recv(cthread: *Thread, frame: *[6]usize) void { + _ = cthread; + _ = frame; + @panic("TODO: SYS_recv()"); } -fn sys_undefined_syscall(cthread: *Thread, args: []usize) usize { - _ = args; +fn sys_sendrecv(cthread: *Thread, frame: *[6]usize) void { + const handle: abi.Handle = @enumFromInt(frame[0]); + const object = cthread.handle(handle) orelse { + @panic("TODO: userspace invoked non-existent handle"); + }; + frame[0] = object.sendrecv(cthread, frame[1..6]); +} + +fn sys_undefined_syscall(cthread: *Thread, frame: *[6]usize) void { + _ = frame; log.warn("{*} invoked an undefined syscall", .{cthread}); Thread.exit_current(); } -pub fn syscall_handler(func: usize, args: []usize) usize { +pub fn syscall_handler(func: usize, frame: *[6]usize) void { const cthread = thread.Thread.current(); const handler = if (func >= abi.MAX_SYSCALL) sys_undefined_syscall // else syscall_table[func] // orelse sys_undefined_syscall; - return handler(cthread, args); + handler(cthread, frame); } diff --git a/src/thread.zig b/src/thread.zig index 424b01a..a41fa6f 100644 --- a/src/thread.zig +++ b/src/thread.zig @@ -7,8 +7,12 @@ const arch = @import("kernel.zig").arch; const log = @import("debug.zig").log; const mem = @import("mem.zig"); const sync = @import("sync.zig"); +const object = @import("object.zig"); +const abi = @import("abi"); const ProcessAddressSpace = mem.vmm.ProcessAddressSpace; +const Object = object.Object; +const Handle = abi.Handle; // TODO: are kernel threads needed at all if we're doing a microkernel? @@ -27,6 +31,113 @@ pub fn idle_function(arg: usize) callconv(.C) noreturn { } } +/// Represents a single execution thread. +pub const Thread = struct { + const MAX_HANDLES: usize = 64; + + /// Arena. + allocator: *arena.Arena, + /// Architecture-specific task context. + arch_context: arch.Context, + + /// Queue to which this thread belongs + queue: ?*Queue = null, + /// Next thread in the queue. + next: ?*Thread = null, + /// Previous thread in the queue. + prev: ?*Thread = null, + + // TODO move to process + address_space: ?ProcessAddressSpace = null, + // TODO move to process + handle_table: [MAX_HANDLES]?Object = [_]?Object{null} ** MAX_HANDLES, + + pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError; + + /// Creates a new (kernel) thread with given `function` and `arg`ument. + pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread { + const thread = a.create(Thread); + thread.* = .{ + .allocator = a, + .arch_context = arch.Context.kernel(function, arg), + }; + return thread; + } + + pub fn create_user( + a: *arena.Arena, + address_space: ProcessAddressSpace, + pc: usize, + sp: usize, + arg: usize, + ) *Thread { + const thread = a.create(Thread); + thread.* = .{ + .allocator = a, + .address_space = address_space, + .arch_context = arch.Context.user(&address_space, pc, sp, arg), + }; + // "self" process object is granted to all processes + const process_object = Object{ .process = object.ProcessObject { + .inner = thread, + } }; + _ = thread.grant(process_object); + return thread; + } + + /// Enters the thread, does not return. + pub fn enter(self: *@This()) noreturn { + self.arch_context.enter(); + } + + /// Switches from `from` to `self` thread. + pub fn switch_from(self: *@This(), from: *@This()) void { + self.arch_context.switch_from(&from.arch_context); + } + + pub fn grant(self: *@This(), obj: Object) Handle { + for (0..MAX_HANDLES) |i| { + if (self.handle_table[i] == null) { + self.handle_table[i] = obj; + return @enumFromInt(i + 1); + } + } + @panic("TODO: ran out of objects"); + } + + pub fn handle(self: *@This(), h: Handle) ?Object { + const index = @as(usize, @intFromEnum(h)); + if (index > MAX_HANDLES or index == 0) { + return null; + } + return self.handle_table[index - 1]; + } + + pub fn dequeue(self: *@This()) void { + // TODO queueing information should be put under a lock for SMP to work properly + if (self.queue) |q| { + q.dequeue(self); + } + } + + pub fn current() *@This() { + return Queue.t_this_cpu.?.current.?; + } + + pub fn exit_current() noreturn { + // Mask IRQs so they don't break current thread's state + const mask = arch.IrqGuard.acquire(); + defer mask.release(); + + const curr = Thread.current(); + curr.dequeue(); + + yield(); + + @panic("This code should not be reachable"); + } +}; + /// Per-CPU thread queue structure. pub const Queue = struct { /// Idle task context. Used when there are no other tasks running. @@ -152,86 +263,6 @@ pub const Queue = struct { } }; -/// Represents a single execution thread. -pub const Thread = struct { - /// Arena. - allocator: *arena.Arena, - /// Architecture-specific task context. - arch_context: arch.Context, - - /// Queue to which this thread belongs - queue: ?*Queue = null, - /// Next thread in the queue. - next: ?*Thread = null, - /// Previous thread in the queue. - prev: ?*Thread = null, - - // TODO move to process - address_space: ?ProcessAddressSpace = null, - - pub const Error = error{out_of_memory} || mem.vmm.AddressSpaceError; - - /// Creates a new (kernel) thread with given `function` and `arg`ument. - pub fn create_kernel(a: *arena.Arena, function: *const KernelThreadFn, arg: usize) *Thread { - const thread = a.create(Thread); - thread.* = .{ - .allocator = a, - .arch_context = arch.Context.kernel(function, arg), - }; - return thread; - } - - pub fn create_user( - a: *arena.Arena, - address_space: ProcessAddressSpace, - pc: usize, - sp: usize, - arg: usize, - ) *Thread { - const thread = a.create(Thread); - thread.* = .{ - .allocator = a, - .address_space = address_space, - .arch_context = arch.Context.user(&address_space, pc, sp, arg), - }; - return thread; - } - - /// Enters the thread, does not return. - pub fn enter(self: *@This()) noreturn { - self.arch_context.enter(); - } - - /// Switches from `from` to `self` thread. - pub fn switch_from(self: *@This(), from: *@This()) void { - self.arch_context.switch_from(&from.arch_context); - } - - pub fn dequeue(self: *@This()) void { - // TODO queueing information should be put under a lock for SMP to work properly - if (self.queue) |q| { - q.dequeue(self); - } - } - - pub fn current() *@This() { - return Queue.t_this_cpu.?.current.?; - } - - pub fn exit_current() noreturn { - // Mask IRQs so they don't break current thread's state - const mask = arch.IrqGuard.acquire(); - defer mask.release(); - - const curr = Thread.current(); - curr.dequeue(); - - yield(); - - @panic("This code should not be reachable"); - } -}; - /// Helper data structure to represent kernel stacks in task contexts. pub fn KStack(comptime SIZE: usize) type { return extern struct { @@ -292,6 +323,7 @@ pub fn yield() void { pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Error!*Thread { const L3 = mem.vmm.L3; + const CODE_BASE: usize = 0x200000; var address_space = try ProcessAddressSpace.init(a); errdefer { @@ -302,7 +334,7 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro const code_page_count = L3.page_count(code.len); log.info("Code is {} pages", .{code_page_count}); var offset: usize = 0; - var address: usize = 0x200000; + var address: usize = CODE_BASE; while (offset < code.len) { const page_offset = address % L3.SIZE; const amount = @min(L3.SIZE - page_offset, code.len - offset); @@ -327,7 +359,10 @@ pub fn test_create_user_from_code(a: *arena.Arena, code: []const u8) Thread.Erro log.info("Enter with sp = 0x{x}", .{sp}); - const thread = Thread.create_user(a, address_space, 0x200000, sp, 1234); + const thread = Thread.create_user(a, address_space, CODE_BASE, sp, 1234); + + _ = thread.grant(Object { .physical_memory = object.PhysicalMemoryObject {} }); + _ = thread.grant(Object { .debug = object.DebugObject {} }); return thread; } diff --git a/user/aarch64.ld b/user/aarch64.ld index 2936fdc..16d94af 100644 --- a/user/aarch64.ld +++ b/user/aarch64.ld @@ -10,6 +10,13 @@ SECTIONS { *(.rodata*) } + .eh_frame_hdr : { + *(.eh_frame_hdr*) + } + .eh_frame : { + *(.eh_frame*) + } + .data : ALIGN(4K) { *(.data*) } diff --git a/user/arch/aarch64.zig b/user/arch/aarch64.zig index c9a30c1..e2cfbe1 100644 --- a/user/arch/aarch64.zig +++ b/user/arch/aarch64.zig @@ -1,22 +1,55 @@ const abi = @import("abi"); pub const syscall = struct { - pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize { + pub fn send(handle: u32, msg: *const [5]usize) usize { return asm volatile ("svc #0" : [result] "={x0}" (-> usize), - : [arg0] "{x0}" (arg0), - [func] "{x8}" (@intFromEnum(func)), + : [a0] "{x0}" (handle), + [a1] "{x1}" (msg[0]), + [a2] "{x2}" (msg[1]), + [a3] "{x3}" (msg[2]), + [a4] "{x4}" (msg[3]), + [a5] "{x5}" (msg[4]), + [func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_send)), : "memory" ); } - pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize { - return asm volatile ("svc #0" + pub fn sendrecv(handle: u32, msg: *const[5]usize, buffer: *[5]usize) usize { + return asm volatile ( + \\ svc #0 + \\ stp x1, x2, [%[buf], #16 * 0] + \\ stp x3, x4, [%[buf], #16 * 1] + \\ str x5, [%[buf], #16 * 2] : [result] "={x0}" (-> usize), - : [arg0] "{x0}" (arg0), - [arg1] "{x1}" (arg1), - [func] "{x8}" (@intFromEnum(func)), + : [a0] "{x0}" (handle), + [a1] "{x1}" (msg[0]), + [a2] "{x2}" (msg[1]), + [a3] "{x3}" (msg[2]), + [a4] "{x4}" (msg[3]), + [a5] "{x5}" (msg[4]), + [func] "{x8}" (@intFromEnum(abi.SyscallNumber.SYS_sendrecv)), + [buf] "r" (buffer), : "memory" ); } + + // pub fn syscall1(func: abi.SyscallNumber, arg0: usize) usize { + // return asm volatile ("svc #0" + // : [result] "={x0}" (-> usize), + // : [arg0] "{x0}" (arg0), + // [func] "{x8}" (@intFromEnum(func)), + // : "memory" + // ); + // } + + // pub fn syscall2(func: abi.SyscallNumber, arg0: usize, arg1: usize) usize { + // return asm volatile ("svc #0" + // : [result] "={x0}" (-> usize), + // : [arg0] "{x0}" (arg0), + // [arg1] "{x1}" (arg1), + // [func] "{x8}" (@intFromEnum(func)), + // : "memory" + // ); + // } }; diff --git a/user/main.zig b/user/main.zig index 4d05b1a..3fc4319 100644 --- a/user/main.zig +++ b/user/main.zig @@ -1,8 +1,58 @@ pub const arch = @import("arch.zig"); pub const syscall = @import("syscall.zig"); pub const log = @import("log.zig"); +const abi = @import("abi"); + +const Handle = abi.Handle; + +fn debug_write(handle: Handle, message: []const u8) void { + _ = syscall.send(handle, &.{ + @intFromPtr(message.ptr), + message.len, + 0, + 0, + 0, + }); +} + +fn exit_process(handle: Handle, code: u32) noreturn { + _ = syscall.send(handle, &.{ + @intFromEnum(abi.ProcessObjectAction.ZO_process_exit), + code, + 0, + 0, + 0, + }); + unreachable; +} + +fn allocate_memory(handle: Handle, count: usize) u64 { + var output: [5]usize = undefined; + // TODO error code + _ = syscall.sendrecv( + handle, + &.{ + @intFromEnum(abi.PhysicalMemoryObjectAction.ZO_physical_memory_allocate), + count, + 0, + 0, + 0, + }, + &output, + ); + return output[0]; +} export fn _start(arg: usize) linksection(".text.entry") callconv(.C) noreturn { - log.println("arg=0x{x} ({})", .{ arg, arg }); - syscall.exit(0); + // TODO make the kernel provide those dynamically somehow + const SELF_PROCESS_HANDLE: Handle = @enumFromInt(1); + const PHYSICAL_MEMORY_HANDLE: Handle = @enumFromInt(2); + const DEBUG_HANDLE: Handle = @enumFromInt(3); + + _ = arg; + const mem = allocate_memory(PHYSICAL_MEMORY_HANDLE, 8); + _ = mem; + + debug_write(DEBUG_HANDLE, "Hello!!!"); + exit_process(SELF_PROCESS_HANDLE, 4321); } diff --git a/user/syscall.zig b/user/syscall.zig index 87d16de..ffd7f24 100644 --- a/user/syscall.zig +++ b/user/syscall.zig @@ -2,13 +2,24 @@ const arch = @import("arch.zig"); const abi = @import("abi"); const sc = arch.syscall; -const SC = abi.SyscallNumber; -pub fn exit(code: u32) noreturn { - _ = sc.syscall1(SC.SYS_exit, code); - unreachable; +pub inline fn send(object: abi.Handle, msg: *const [5]usize) usize { + return sc.send(@intFromEnum(object), msg); } -pub fn debug_write(text: []const u8) void { - _ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len); +pub inline fn recv(object: abi.Handle, buffer: *[5]usize) usize { + return sc.recv(@intFromEnum(object), buffer); } + +pub inline fn sendrecv(object: abi.Handle, msg: *const [5]usize, buffer: *[5]usize) usize { + return sc.sendrecv(@intFromEnum(object), msg, buffer); +} + +// pub fn exit(code: u32) noreturn { +// _ = sc.syscall1(SC.SYS_exit, code); +// unreachable; +// } +// +// pub fn debug_write(text: []const u8) void { +// _ = sc.syscall2(SC.SYS_debug_write, @intFromPtr(text.ptr), text.len); +// } -- 2.53.0