Add x86 syscalls

This commit is contained in:
Sam Tebbs 2019-07-31 22:41:22 +01:00
parent 190f21f907
commit 60ba451aec
6 changed files with 339 additions and 7 deletions

View file

@ -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();
}

View file

@ -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;
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 {
/// 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);
idt.openInterruptGate(syscalls.INTERRUPT, isr128);
}

View file

@ -87,3 +87,4 @@ isrGenerator 28
isrGenerator 29
isrGenerator 30
isrGenerator 31
isrGenerator 128

View file

@ -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" {

View file

@ -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");
}

View file

@ -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"])
]