288 lines
8.8 KiB
Zig
288 lines
8.8 KiB
Zig
|
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");
|
||
|
}
|