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); +// }