From 60ba451aec5177e2b8db390ff22e24f3def99c52 Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Wed, 31 Jul 2019 22:41:22 +0100 Subject: [PATCH] Add x86 syscalls --- src/kernel/arch/x86/arch.zig | 3 + src/kernel/arch/x86/isr.zig | 49 +++++- src/kernel/arch/x86/isr_asm.s | 1 + src/kernel/arch/x86/paging.zig | 2 +- src/kernel/arch/x86/syscalls.zig | 287 +++++++++++++++++++++++++++++++ test/kernel/arch/x86/rt-test.py | 4 +- 6 files changed, 339 insertions(+), 7 deletions(-) create mode 100644 src/kernel/arch/x86/syscalls.zig diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index d042dc6..c0583e8 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -7,6 +7,7 @@ const irq = @import("irq.zig"); const isr = @import("isr.zig"); const log = @import("../../log.zig"); const pit = @import("pit.zig"); +const syscalls = @import("syscalls.zig"); pub const InterruptContext = struct { // Extra segments @@ -58,6 +59,8 @@ pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator, compt paging.init(mem_profile, allocator); + syscalls.init(options); + // Enable interrupts enableInterrupts(); } diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index d55f031..3d6e38c 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -3,6 +3,7 @@ const panic = @import("../../panic.zig"); const idt = @import("idt.zig"); const arch = @import("arch.zig"); +const syscalls = @import("syscalls.zig"); const NUMBER_OF_ENTRIES: u16 = 32; @@ -39,6 +40,7 @@ extern fn isr28() void; extern fn isr29() void; extern fn isr30() void; extern fn isr31() void; +extern fn isr128() void; /// The exception messaged that is printed when a exception happens const exception_msg: [NUMBER_OF_ENTRIES][]const u8 = [NUMBER_OF_ENTRIES][]const u8 { @@ -76,8 +78,18 @@ const exception_msg: [NUMBER_OF_ENTRIES][]const u8 = [NUMBER_OF_ENTRIES][]const "Reserved" }; +/// Errors that an isr function can return +pub const IsrError = error { + UnrecognisedIsr +}; + +/// An isr handler. Takes an interrupt context and returns void. +/// Should finish quickly to avoid delaying further interrupts and the previously running code +pub const IsrHandler = fn(*arch.InterruptContext)void; + // The of exception handlers initialised to unhandled. -var isr_handlers: [NUMBER_OF_ENTRIES]fn(*arch.InterruptContext)void = []fn(*arch.InterruptContext)void{unhandled} ** NUMBER_OF_ENTRIES; +var isr_handlers: [NUMBER_OF_ENTRIES]IsrHandler = []IsrHandler{unhandled} ** NUMBER_OF_ENTRIES; +var syscall_handler: IsrHandler = unhandled; /// /// A dummy handler that will make a call to panic as it is a unhandled exception. @@ -91,6 +103,17 @@ fn unhandled(context: *arch.InterruptContext) void { panic.panicFmt(null, "Unhandled exception: {}, number {}", exception_msg[interrupt_num], interrupt_num); } +/// +/// Checks if the isr is valid and returns true if it is, else false. +/// To be valid it must be greater than or equal to 0 and less than NUMBER_OF_ENTRIES. +/// +/// Arguments: +/// IN isr_num: u16 - The isr number to check +/// +pub fn isValidIsr(isr_num: u32) bool { + return isr_num >= 0 and isr_num < NUMBER_OF_ENTRIES; +} + /// /// The exception handler that each of the exceptions will call when a exception happens. /// @@ -100,7 +123,13 @@ fn unhandled(context: *arch.InterruptContext) void { /// export fn isrHandler(context: *arch.InterruptContext) void { const isr_num = context.int_num; - isr_handlers[isr_num](context); + if (isr_num == syscalls.INTERRUPT) { + syscall_handler(context); + } else if (isValidIsr(isr_num)) { + isr_handlers[isr_num](context); + } else { + panic.panicFmt(null, "Unrecognised isr: {}\n", isr_num); + } } /// @@ -109,8 +138,17 @@ export fn isrHandler(context: *arch.InterruptContext) void { /// Arguments: /// IN irq_num: u16 - The exception number to register. /// -pub fn registerIsr(isr_num: u16, handler: fn(*arch.InterruptContext)void) void { - isr_handlers[isr_num] = handler; +/// Errors: +/// IsrError.UnrecognisedIsr - If `isr_num` is invalid (see isValidIsr) +/// +pub fn registerIsr(isr_num: u16, handler: fn(*arch.InterruptContext)void) !void { + if (isr_num == syscalls.INTERRUPT) { + syscall_handler = handler; + } else if (isValidIsr(isr_num)) { + isr_handlers[isr_num] = handler; + } else { + return IsrError.UnrecognisedIsr; + } } /// @@ -160,4 +198,5 @@ pub fn init() void { idt.openInterruptGate(29, isr29); idt.openInterruptGate(30, isr30); idt.openInterruptGate(31, isr31); -} \ No newline at end of file + idt.openInterruptGate(syscalls.INTERRUPT, isr128); +} diff --git a/src/kernel/arch/x86/isr_asm.s b/src/kernel/arch/x86/isr_asm.s index 74f645b..d2eb91a 100644 --- a/src/kernel/arch/x86/isr_asm.s +++ b/src/kernel/arch/x86/isr_asm.s @@ -87,3 +87,4 @@ isrGenerator 28 isrGenerator 29 isrGenerator 30 isrGenerator 31 +isrGenerator 128 diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index f24e52f..150d972 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -142,7 +142,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator) void mapDir(kernel_directory, p_start, p_end, v_start, v_end, allocator) catch unreachable; const dir_physaddr = @ptrToInt(kernel_directory) - constants.KERNEL_ADDR_OFFSET; asm volatile ("mov %[addr], %%cr3" :: [addr] "{eax}" (dir_physaddr)); - isr.registerIsr(14, pageFault); + isr.registerIsr(14, pageFault) catch unreachable; } test "isAligned" { diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig new file mode 100644 index 0000000..429e574 --- /dev/null +++ b/src/kernel/arch/x86/syscalls.zig @@ -0,0 +1,287 @@ +const arch = @import("arch.zig"); +const testing = @import("std").testing; +const assert = @import("std").debug.assert; +const isr = @import("isr.zig"); +const log = @import("../../log.zig"); + +/// The isr number associated with syscalls +pub const INTERRUPT: u16 = 0x80; + +/// The maximum number of syscall handlers that can be registered +pub const NUM_HANDLERS: u16 = 256; + +/// A syscall handler +pub const SyscallHandler = fn (ctx: *arch.InterruptContext) u32; + +/// Errors that syscall utility functions can throw +pub const SyscallError = error { + SyscallExists, + InvalidSyscall +}; + +/// The array of registered syscalls +var handlers: [NUM_HANDLERS]?SyscallHandler = []?SyscallHandler{null} ** NUM_HANDLERS; + +/// +/// Returns true if the syscall is valid, else false. +/// A syscall is valid if it's less than NUM_HANDLERS. +/// +/// Arguments: +/// IN syscall: u32 - The syscall to check +/// +pub fn isValidSyscall(syscall: u32) bool { + return syscall < NUM_HANDLERS; +} + +/// +/// 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. +/// +/// Arguments: +/// IN ctx: *arch.InterruptContext - The cpu context when the syscall was triggered. The syscall number is +/// stored in eax. +/// +fn handle(ctx: *arch.InterruptContext) void { + // The syscall number is put in eax + const syscall = ctx.eax; + if (isValidSyscall(syscall)) { + if (handlers[syscall]) |handler| { + ctx.eax = handler(ctx); + } else { + log.logWarning("Syscall {} triggered but not registered\n", syscall); + } + } else { + log.logWarning("Syscall {} is invalid\n", syscall); + } +} + +/// +/// Register a syscall so it can be called by triggering interrupt 128. +/// +/// Arguments: +/// IN syscall: u8 - The syscall number to register the handler with. +/// IN handler: SyscallHandler - 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. +/// +pub fn registerSyscall(syscall: u8, handler: SyscallHandler) SyscallError!void { + if (!isValidSyscall(syscall)) + return SyscallError.InvalidSyscall; + if (handlers[syscall]) |_| + return SyscallError.SyscallExists; + handlers[syscall] = handler; +} + +/// +/// Trigger a syscall with no arguments. Returns the value put in eax by the syscall. +/// +/// Arguments: +/// IN syscall: u32 - The syscall to trigger, put in eax. +/// +inline fn syscall0(syscall: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + ); +} + +/// +/// Trigger a syscall with one argument. Returns the value put in eax by the syscall. +/// +/// Arguments: +/// IN syscall: u32 - The syscall to trigger, put in eax. +/// IN arg: u32 - The argument to pass. Put in ebx. +/// +inline fn syscall1(syscall: u32, arg: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + [arg1] "{ebx}" (arg), + ); +} + +/// +/// Trigger a syscall with two arguments. Returns the value put in eax by the syscall. +/// +/// 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. +/// +inline fn syscall2(syscall: u32, arg1: u32, arg2: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + ); +} + +/// +/// Trigger a syscall with three arguments. Returns the value put in eax by the syscall. +/// +/// 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. +/// +inline fn syscall3(syscall: u32, arg1: u32, arg2: u32, arg3: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + ); +} + +/// +/// Trigger a syscall with four arguments. Returns the value put in eax by the syscall. +/// +/// 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. +/// +inline fn syscall4(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + [arg4] "{esi}" (arg4), + ); +} + +/// +/// Trigger a syscall with five arguments. Returns the value put in eax by the syscall. +/// +/// 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. +/// +inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { + return asm volatile ( + \\int $0x80 + : [ret] "={eax}" (-> u32) + : [syscall] "{eax}" (syscall), + [arg1] "{ebx}" (arg1), + [arg2] "{ecx}" (arg2), + [arg3] "{edx}" (arg3), + [arg4] "{esi}" (arg4), + [arg5] "{edi}" (arg5), + ); +} + +/// +/// Gets the syscall argument according to the given index. 0 => ebx, 1 => ecx, 2 => edx, 3 => esi and 4 => edi. +/// +/// Arguments: +/// IN ctx: *arch.InterruptContext - The interrupt context from which to get the argument +/// IN arg_idx: comptime u32 - The argument index to get. Between 0 and 4. +/// +inline fn syscallArg(ctx: *arch.InterruptContext, comptime arg_idx: u32) u32 { + return switch (arg_idx) { + 0 => ctx.ebx, + 1 => ctx.ecx, + 2 => ctx.edx, + 3 => ctx.esi, + 4 => ctx.edi, + else => @compileError("Arg index must be between 0 and 4") + }; +} + +/// +/// Initialise syscalls. Registers the isr associated with INTERRUPT. +/// +pub fn init(comptime options: type) void { + log.logInfo("Init syscalls\n"); + isr.registerIsr(INTERRUPT, handle) catch unreachable; + log.logInfo("Done\n"); + if (options.rt_test) runtimeTests(); +} + +/// Tests + +var testInt: u32 = 0; + +fn testHandler0(ctx: *arch.InterruptContext) u32 { + testInt += 1; + return 0; +} + +fn testHandler1(ctx: *arch.InterruptContext) u32 { + testInt += syscallArg(ctx, 0); + return 1; +} + +fn testHandler2(ctx: *arch.InterruptContext) u32 { + testInt += syscallArg(ctx, 1); + return testHandler1(ctx) + 1; +} + +fn testHandler3(ctx: *arch.InterruptContext) u32 { + testInt += syscallArg(ctx, 2); + return testHandler2(ctx) + 1; +} + +fn testHandler4(ctx: *arch.InterruptContext) u32 { + testInt += syscallArg(ctx, 3); + return testHandler3(ctx) + 1; +} + +fn testHandler5(ctx: *arch.InterruptContext) u32 { + testInt += syscallArg(ctx, 4); + return testHandler4(ctx) + 1; +} + +test "registerSyscall returns SyscallExists" { + registerSyscall(123, testHandler) catch unreachable; + registerSyscall(123, testHandler) catch |err| { + return; + }; + assert(false); +} + +fn runtimeTests() void { + registerSyscall(123, testHandler0) catch unreachable; + registerSyscall(124, testHandler1) catch unreachable; + registerSyscall(125, testHandler2) catch unreachable; + registerSyscall(126, testHandler3) catch unreachable; + registerSyscall(127, testHandler4) catch unreachable; + registerSyscall(128, testHandler5) catch unreachable; + assert(testInt == 0); + + if (syscall0(123) == 0 and testInt == 1) + log.logInfo("Syscalls: Tested no args\n"); + + if (syscall1(124, 2) == 1 and testInt == 3) + log.logInfo("Syscalls: Tested 1 arg\n"); + + if (syscall2(125, 2, 3) == 2 and testInt == 8) + log.logInfo("Syscalls: Tested 2 args\n"); + + if (syscall3(126, 2, 3, 4) == 3 and testInt == 17) + log.logInfo("Syscalls: Tested 3 args\n"); + + if (syscall4(127, 2, 3, 4, 5) == 4 and testInt == 31) + log.logInfo("Syscalls: Tested 4 args\n"); + + if (syscall5(128, 2, 3, 4, 5, 6) == 5 and testInt == 51) + log.logInfo("Syscalls: Tested 5 args\n"); +} diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index 0cc05cd..58e26ba 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -2,5 +2,7 @@ def getTestCases(TestCase): return [ TestCase("GDT init", [r"Init gdt", r"Done"]), TestCase("IDT init", [r"Init idt", r"Done"]), - TestCase("PIT init", [r"Init pit", r".+", "Done"]) + TestCase("PIT init", [r"Init pit", r".+", "Done"]), + TestCase("Syscalls init", [r"Init syscalls", "Done"]), + TestCase("Syscall tests", [r"Syscalls: Tested no args", r"Syscalls: Tested 1 arg", r"Syscalls: Tested 2 args", r"Syscalls: Tested 3 args", r"Syscalls: Tested 4 args", r"Syscalls: Tested 5 args"]) ]