From c0a0c164c3318ef8a7c45591169acbf86da23624 Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Sun, 22 Nov 2020 22:23:54 +0000 Subject: [PATCH] Add error handling for syscalls --- src/kernel/arch/x86/syscalls.zig | 336 ++++++++++++++++++++++--------- src/kernel/syscalls.zig | 122 +++++++++++ 2 files changed, 358 insertions(+), 100 deletions(-) create mode 100644 src/kernel/syscalls.zig diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig index b64d0c8..b26fe0a 100644 --- a/src/kernel/arch/x86/syscalls.zig +++ b/src/kernel/arch/x86/syscalls.zig @@ -9,6 +9,7 @@ const testing = std.testing; const expect = std.testing.expect; const isr = @import("isr.zig"); const panic = @import("../../panic.zig").panic; +const syscalls = @import("../../syscalls.zig"); /// The isr number associated with syscalls pub const INTERRUPT: u16 = 0x80; @@ -17,16 +18,20 @@ pub const INTERRUPT: u16 = 0x80; pub const NUM_HANDLERS: u16 = 256; /// A syscall handler -pub const SyscallHandler = fn (ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32; +pub const Handler = fn (ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize; /// Errors that syscall utility functions can throw -pub const SyscallError = error{ +pub const Error = error{ SyscallExists, InvalidSyscall, }; +comptime { + std.debug.assert(@typeInfo(syscalls.Syscall).Enum.fields.len <= NUM_HANDLERS); +} + /// The array of registered syscalls -var handlers: [NUM_HANDLERS]?SyscallHandler = [_]?SyscallHandler{null} ** NUM_HANDLERS; +var handlers: [NUM_HANDLERS]?Handler = [_]?Handler{null} ** NUM_HANDLERS; /// /// Returns true if the syscall is valid, else false. @@ -44,19 +49,29 @@ pub fn isValidSyscall(syscall: u32) bool { /// /// Handle a syscall. Gets the syscall number from eax within the context and calls the registered -/// handler. If there isn't a registered handler or the syscall is invalid (>= NUM_HANDLERS) then a -/// warning is logged. +/// handler. If an error occurs ebx will be set to its error code, or 0 otherwise. +/// The syscall result will be stored in eax. If there isn't a registered handler or the syscall is +/// invalid (>= NUM_HANDLERS) then a warning is logged. /// /// Arguments: /// IN ctx: *arch.CpuState - The cpu context when the syscall was triggered. The /// syscall number is stored in eax. /// -fn handle(ctx: *arch.CpuState) u32 { +/// Return: usize +/// The new stack pointer value +/// +fn handle(ctx: *arch.CpuState) usize { // The syscall number is put in eax const syscall = ctx.eax; if (isValidSyscall(syscall)) { if (handlers[syscall]) |handler| { - ctx.eax = handler(ctx, syscallArg(ctx, 0), syscallArg(ctx, 1), syscallArg(ctx, 2), syscallArg(ctx, 3), syscallArg(ctx, 4)); + const result = handler(ctx, syscallArg(ctx, 0), syscallArg(ctx, 1), syscallArg(ctx, 2), syscallArg(ctx, 3), syscallArg(ctx, 4)); + if (result) |res| { + ctx.eax = res; + ctx.ebx = 0; + } else |e| { + ctx.ebx = syscalls.toErrorCode(e); + } } else { log.warn("Syscall {} triggered but not registered\n", .{syscall}); } @@ -67,147 +82,201 @@ fn handle(ctx: *arch.CpuState) u32 { } /// -/// Register a syscall so it can be called by triggering interrupt 128. +/// Register a syscall so it can be called by triggering interrupt 128 and putting its number in eax. /// /// Arguments: -/// IN syscall: u8 - The syscall number to register the handler with. -/// IN handler: SyscallHandler - The handler to register the syscall with. +/// IN syscall: usize - The syscall to register the handler with. +/// IN handler: Handler - The handler to register the syscall with. /// -/// Errors: -/// SyscallError.InvalidSyscall - If the syscall is invalid (see isValidSyscall). -/// SyscallError.SyscallExists - If the syscall has already been registered. +/// Errors: Error +/// Error.SyscallExists - If the syscall has already been registered. +/// Error.InvalidSyscall - If the syscall is invalid. See isValidSyscall. /// -pub fn registerSyscall(syscall: u8, handler: SyscallHandler) SyscallError!void { +pub fn registerSyscall(syscall: usize, handler: Handler) Error!void { if (!isValidSyscall(syscall)) - return SyscallError.InvalidSyscall; + return Error.InvalidSyscall; if (handlers[syscall]) |_| - return SyscallError.SyscallExists; + return Error.SyscallExists; handlers[syscall] = handler; } /// -/// Trigger a syscall with no arguments. Returns the value put in eax by the syscall. +/// Trigger a syscall with no arguments. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. +/// IN syscall: usize - The syscall to trigger, put in eax. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall0(syscall: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall0(syscall: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall) + : "ebx" ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// -/// Trigger a syscall with one argument. Returns the value put in eax by the syscall. +/// Trigger a syscall with one argument. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. -/// IN arg: u32 - The argument to pass. Put in ebx. +/// IN syscall: usize - The syscall to trigger, put in eax. +/// IN arg: usize - The argument to pass. Put in ebx. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall1(syscall: u32, arg: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall1(syscall: usize, arg: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall), [arg1] "{ebx}" (arg) ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// -/// Trigger a syscall with two arguments. Returns the value put in eax by the syscall. +/// Trigger a syscall with two arguments. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. -/// IN arg1: u32 - The first argument to pass. Put in ebx. -/// IN arg2: u32 - The second argument to pass. Put in ecx. +/// IN syscall: usize - The syscall to trigger, put in eax. +/// IN arg1: usize - The first argument to pass. Put in ebx. +/// IN arg2: usize - The second argument to pass. Put in ecx. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall2(syscall: u32, arg1: u32, arg2: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall2(syscall: usize, arg1: usize, arg2: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall), [arg1] "{ebx}" (arg1), [arg2] "{ecx}" (arg2) ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// -/// Trigger a syscall with three arguments. Returns the value put in eax by the syscall. +/// Trigger a syscall with three arguments. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. -/// IN arg1: u32 - The first argument to pass. Put in ebx. -/// IN arg2: u32 - The second argument to pass. Put in ecx. -/// IN arg3: u32 - The third argument to pass. Put in edx. +/// IN syscall: usize - The syscall to trigger, put in eax. +/// IN arg1: usize - The first argument to pass. Put in ebx. +/// IN arg2: usize - The second argument to pass. Put in ecx. +/// IN arg3: usize - The third argument to pass. Put in edx. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall3(syscall: u32, arg1: u32, arg2: u32, arg3: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall3(syscall: usize, arg1: usize, arg2: usize, arg3: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall), [arg1] "{ebx}" (arg1), [arg2] "{ecx}" (arg2), [arg3] "{edx}" (arg3) ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// -/// Trigger a syscall with four arguments. Returns the value put in eax by the syscall. +/// Trigger a syscall with four arguments. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. -/// IN arg1: u32 - The first argument to pass. Put in ebx. -/// IN arg2: u32 - The second argument to pass. Put in ecx. -/// IN arg3: u32 - The third argument to pass. Put in edx. -/// IN arg4: u32 - The fourth argument to pass. Put in esi. +/// IN syscall: usize - The syscall to trigger, put in eax. +/// IN arg1: usize - The first argument to pass. Put in ebx. +/// IN arg2: usize - The second argument to pass. Put in ecx. +/// IN arg3: usize - The third argument to pass. Put in edx. +/// IN arg4: usize - The fourth argument to pass. Put in esi. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall4(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall4(syscall: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall), [arg1] "{ebx}" (arg1), [arg2] "{ecx}" (arg2), [arg3] "{edx}" (arg3), [arg4] "{esi}" (arg4) ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// -/// Trigger a syscall with five arguments. Returns the value put in eax by the syscall. +/// Trigger a syscall with five arguments. Returns the value put in eax by the syscall or the error returned in ebx. /// /// Arguments: -/// IN syscall: u32 - The syscall to trigger, put in eax. -/// IN arg1: u32 - The first argument to pass. Put in ebx. -/// IN arg2: u32 - The second argument to pass. Put in ecx. -/// IN arg3: u32 - The third argument to pass. Put in edx. -/// IN arg4: u32 - The fourth argument to pass. Put in esi. -/// IN arg5: u32 - The fifth argument to pass. Put in edi. +/// IN syscall: usize - The syscall to trigger, put in eax. +/// IN arg1: usize - The first argument to pass. Put in ebx. +/// IN arg2: usize - The second argument to pass. Put in ecx. +/// IN arg3: usize - The third argument to pass. Put in edx. +/// IN arg4: usize - The fourth argument to pass. Put in esi. +/// IN arg5: usize - The fifth argument to pass. Put in edi. /// -/// Return: u32 +/// Return: usize /// The return value from the syscall. /// -inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { - return asm volatile ( +/// Error: syscalls.Error +/// This function will return the error that the syscall handler returns. See the documentation for the syscall for details. +/// +inline fn syscall5(syscall: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { + const res = asm volatile ( \\int $0x80 - : [ret] "={eax}" (-> u32) + : [ret] "={eax}" (-> usize) : [syscall] "{eax}" (syscall), [arg1] "{ebx}" (arg1), [arg2] "{ecx}" (arg2), @@ -215,6 +284,13 @@ inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg [arg4] "{esi}" (arg4), [arg5] "{edi}" (arg5) ); + const err = asm ("" + : [ret] "={ebx}" (-> usize) + ); + if (err != 0) { + return syscalls.fromErrorCode(err); + } + return res; } /// @@ -225,10 +301,10 @@ inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg /// IN ctx: *arch.CpuState - The interrupt context from which to get the argument /// IN arg_idx: comptime u32 - The argument index to get. Between 0 and 4. /// -/// Return: u32 +/// Return: usize /// The syscall argument from the given index. /// -inline fn syscallArg(ctx: *arch.CpuState, comptime arg_idx: u32) u32 { +inline fn syscallArg(ctx: *arch.CpuState, comptime arg_idx: u32) usize { return switch (arg_idx) { 0 => ctx.ebx, 1 => ctx.ecx, @@ -240,13 +316,39 @@ inline fn syscallArg(ctx: *arch.CpuState, comptime arg_idx: u32) u32 { } /// -/// Initialise syscalls. Registers the isr associated with INTERRUPT. +/// Construct a handler for a syscall. +/// +/// Arguments: +/// IN comptime syscall: Syscall - The syscall to construct the handler for. +/// +/// Return: Handler +/// The handler function constructed. +/// +fn makeHandler(comptime syscall: syscalls.Syscall) Handler { + return struct { + fn func(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { + return syscalls.handle(syscall, arg1, arg2, arg3, arg4, arg5); + } + }.func; +} + +/// +/// Initialise syscalls. Registers the isr associated with INTERRUPT and sets up handlers for each syscall. /// pub fn init() void { log.info("Init\n", .{}); defer log.info("Done\n", .{}); - isr.registerIsr(INTERRUPT, handle) catch unreachable; + isr.registerIsr(INTERRUPT, handle) catch |e| { + panic(@errorReturnTrace(), "Failed to register syscall ISR: {}\n", .{e}); + }; + + inline for (std.meta.fields(syscalls.Syscall)) |field| { + const syscall = @intToEnum(syscalls.Syscall, field.value); + registerSyscall(field.value, makeHandler(syscall)) catch |e| { + panic(@errorReturnTrace(), "Failed to register syscall for '" ++ field.name ++ "': {}\n", .{e}); + }; + } switch (build_options.test_mode) { .Initialisation => runtimeTests(), @@ -257,78 +359,112 @@ pub fn init() void { /// Tests var test_int: u32 = 0; -fn testHandler0(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler0(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += 1; return 0; } -fn testHandler1(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler1(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += arg1; return 1; } -fn testHandler2(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler2(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += arg1 + arg2; return 2; } -fn testHandler3(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler3(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += arg1 + arg2 + arg3; return 3; } -fn testHandler4(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler4(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += arg1 + arg2 + arg3 + arg4; return 4; } -fn testHandler5(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { +fn testHandler5(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { test_int += arg1 + arg2 + arg3 + arg4 + arg5; return 5; } +fn testHandler6(ctx: *arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) syscalls.Error!usize { + return syscalls.Error.OutOfMemory; +} + test "registerSyscall returns SyscallExists" { - registerSyscall(123, testHandler0) catch unreachable; - registerSyscall(123, testHandler0) catch |err| { - return; - }; - expect(false); + try registerSyscall(122, testHandler0); + std.testing.expectError(Error.SyscallExists, registerSyscall(122, testHandler0)); } fn runtimeTests() void { - registerSyscall(123, testHandler0) catch panic(@errorReturnTrace(), "FAILURE registering handler 0\n", .{}); - registerSyscall(124, testHandler1) catch panic(@errorReturnTrace(), "FAILURE registering handler 1\n", .{}); - registerSyscall(125, testHandler2) catch panic(@errorReturnTrace(), "FAILURE registering handler 2\n", .{}); - registerSyscall(126, testHandler3) catch panic(@errorReturnTrace(), "FAILURE registering handler 3\n", .{}); - registerSyscall(127, testHandler4) catch panic(@errorReturnTrace(), "FAILURE registering handler 4\n", .{}); - registerSyscall(128, testHandler5) catch panic(@errorReturnTrace(), "FAILURE registering handler 5\n", .{}); + registerSyscall(121, testHandler6) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 6: {}\n", .{e}); + registerSyscall(122, testHandler0) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 0: {}\n", .{e}); + registerSyscall(123, testHandler1) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 1: {}\n", .{e}); + registerSyscall(124, testHandler2) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 2: {}\n", .{e}); + registerSyscall(125, testHandler3) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 3: {}\n", .{e}); + registerSyscall(126, testHandler4) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 4: {}\n", .{e}); + registerSyscall(127, testHandler5) catch |e| panic(@errorReturnTrace(), "FAILURE registering handler 5: {}\n", .{e}); if (test_int != 0) { panic(@errorReturnTrace(), "FAILURE initial test_int not 0: {}\n", .{test_int}); } - if (syscall0(123) != 0 or test_int != 1) { - panic(@errorReturnTrace(), "FAILURE syscall0\n", .{}); + if (syscall0(122)) |res| { + if (res != 0 or test_int != 1) { + panic(@errorReturnTrace(), "FAILURE syscall0\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall0 errored: {}\n", .{e}); } - if (syscall1(124, 2) != 1 or test_int != 3) { - panic(@errorReturnTrace(), "FAILURE syscall2\n", .{}); + if (syscall1(123, 2)) |res| { + if (res != 1 or test_int != 3) { + panic(@errorReturnTrace(), "FAILURE syscall1\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall1 errored: {}\n", .{e}); } - if (syscall2(125, 2, 3) != 2 or test_int != 8) { - panic(@errorReturnTrace(), "FAILURE syscall2\n", .{}); + if (syscall2(124, 2, 3)) |res| { + if (res != 2 or test_int != 8) { + panic(@errorReturnTrace(), "FAILURE syscall2\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall2 errored: {}\n", .{e}); } - if (syscall3(126, 2, 3, 4) != 3 or test_int != 17) { - panic(@errorReturnTrace(), "FAILURE syscall3\n", .{}); + if (syscall3(125, 2, 3, 4)) |res| { + if (res != 3 or test_int != 17) { + panic(@errorReturnTrace(), "FAILURE syscall3\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall3 errored: {}\n", .{e}); } - if (syscall4(127, 2, 3, 4, 5) != 4 or test_int != 31) { - panic(@errorReturnTrace(), "FAILURE syscall4\n", .{}); + if (syscall4(126, 2, 3, 4, 5)) |res| { + if (res != 4 or test_int != 31) { + panic(@errorReturnTrace(), "FAILURE syscall4\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall4 errored: {}\n", .{e}); } - if (syscall5(128, 2, 3, 4, 5, 6) != 5 or test_int != 51) { - panic(@errorReturnTrace(), "FAILURE syscall5\n", .{}); + if (syscall5(127, 2, 3, 4, 5, 6)) |res| { + if (res != 5 or test_int != 51) { + panic(@errorReturnTrace(), "FAILURE syscall5\n", .{}); + } + } else |e| { + panic(@errorReturnTrace(), "FAILURE syscall5 errored: {}\n", .{e}); + } + + if (syscall0(121)) |res| { + panic(@errorReturnTrace(), "FAILURE syscall6\n", .{}); + } else |e| { + if (e != syscalls.Error.OutOfMemory) { + panic(@errorReturnTrace(), "FAILURE syscall6 returned the wrong error: {}\n", .{e}); + } } log.info("Tested all args\n", .{}); diff --git a/src/kernel/syscalls.zig b/src/kernel/syscalls.zig new file mode 100644 index 0000000..6ed7961 --- /dev/null +++ b/src/kernel/syscalls.zig @@ -0,0 +1,122 @@ +const std = @import("std"); +const scheduler = @import("scheduler.zig"); +const panic = @import("panic.zig").panic; +const log = std.log.scoped(.syscalls); + +/// A compilation of all errors that syscall handlers could return. +pub const Error = error{OutOfMemory}; + +/// +/// Convert an error code to an instance of Error. The conversion must be synchronised with toErrorCode +/// +/// Arguments: +/// IN code: usize - The erorr code to convert +/// +/// Return: Error +/// The error corresponding to the error code +/// +pub fn fromErrorCode(code: usize) Error { + return switch (code) { + 1 => Error.OutOfMemory, + else => unreachable, + }; +} + +/// +/// Convert an instance of Error to an error code. The conversion must be synchronised with fromErrorCode +/// +/// Arguments: +/// IN err: Error - The erorr to convert +/// +/// Return: usize +/// The error code corresponding to the error +/// +pub fn toErrorCode(err: Error) usize { + return switch (err) { + Error.OutOfMemory => 1, + }; +} + +comptime { + // Make sure toErrorCode and fromErrorCode are synchronised, and that no errors share the same error code + inline for (@typeInfo(Error).ErrorSet.?) |err| { + const error_instance = @field(Error, err.name); + if (fromErrorCode(toErrorCode(error_instance)) != error_instance) { + @compileError("toErrorCode and fromErrorCode are not synchronised for syscall error '" ++ err.name ++ "'\n"); + } + inline for (@typeInfo(Error).ErrorSet.?) |err2| { + const error2_instance = @field(Error, err2.name); + if (error_instance != error2_instance and toErrorCode(error_instance) == toErrorCode(error2_instance)) { + @compileError("Syscall errors '" ++ err.name ++ "' and '" ++ err2.name ++ "' share the same error code\n"); + } + } + } +} + +/// All implemented syscalls +pub const Syscall = enum { + Test1, + Test2, + Test3, + + /// + /// Get the handler associated with the syscall + /// + /// Arguments: + /// IN self: Syscall - The syscall to get the handler for + /// + /// Return: Handler + /// The handler that takes care of this syscall + /// + fn getHandler(self: @This()) Handler { + return switch (self) { + .Test1 => handleTest1, + .Test2 => handleTest2, + .Test3 => handleTest3, + }; + } +}; + +/// A function that can handle a syscall and return a result or an error +pub const Handler = fn (arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize; + +/// +/// Handle a syscall and return a result or error +/// +/// Arguments: +/// IN syscall: Syscall - The syscall to handle +/// IN argX: usize - The xth argument that was passed to the syscall +/// +/// Return: usize +/// The syscall result +/// +/// Error: Error +/// The error raised by the handler +/// +pub fn handle(syscall: Syscall, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { + return try syscall.getHandler()(arg1, arg2, arg3, arg4, arg5); +} + +pub fn handleTest1(arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { + return 0; +} + +pub fn handleTest2(arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { + return arg1 + arg2 + arg3 + arg4 + arg5; +} + +pub fn handleTest3(arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { + return std.mem.Allocator.Error.OutOfMemory; +} + +test "getHandler" { + std.testing.expectEqual(Syscall.Test1.getHandler(), handleTest1); + std.testing.expectEqual(Syscall.Test2.getHandler(), handleTest2); + std.testing.expectEqual(Syscall.Test3.getHandler(), handleTest3); +} + +test "handle" { + std.testing.expectEqual(@as(usize, 0), try handle(.Test1, 0, 0, 0, 0, 0)); + std.testing.expectEqual(@as(usize, 1 + 2 + 3 + 4 + 5), try handle(.Test2, 1, 2, 3, 4, 5)); + std.testing.expectError(Error.OutOfMemory, handle(.Test3, 0, 0, 0, 0, 0)); +}