diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index b2b51d5..3f26002 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -238,6 +238,7 @@ test "" { _ = @import("gdt.zig"); _ = @import("idt.zig"); _ = @import("pic.zig"); + _ = @import("isr.zig"); _ = @import("syscalls.zig"); _ = @import("paging.zig"); } diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index 03b5990..ecc5aff 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -12,7 +12,7 @@ const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("ar const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("../../log.zig"); /// The structure that contains all the information that each IDT entry needs. -const IdtEntry = packed struct { +pub const IdtEntry = packed struct { /// The lower 16 bits of the base address of the interrupt handler offset. base_low: u16, @@ -88,12 +88,12 @@ const PRIVILEGE_RING_2: u2 = 0x2; /// Privilege level 3. User land. The privilege level the calling descriptor minimum will have. const PRIVILEGE_RING_3: u2 = 0x3; -/// The total number of entries the IDT can have (2^8). -const NUMBER_OF_ENTRIES: u16 = 256; - /// The total size of all the IDT entries (minus 1). const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1; +/// The total number of entries the IDT can have (2^8). +pub const NUMBER_OF_ENTRIES: u16 = 256; + /// The IDT pointer that the CPU is loaded with that contains the base address of the IDT and the /// size. var idt_ptr: IdtPtr = IdtPtr{ @@ -150,7 +150,7 @@ fn makeEntry(base: u32, selector: u16, gate_type: u4, privilege: u2) IdtEntry { /// Return: bool /// Whether the provided IDT entry is open or not. /// -fn isIdtOpen(entry: IdtEntry) bool { +pub fn isIdtOpen(entry: IdtEntry) bool { return entry.present == 1; } diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index 82ce01b..614e3b8 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -1,46 +1,31 @@ -// Zig version: 0.4.0 - -const panic = @import("../../panic.zig").panic; -const idt = @import("idt.zig"); -const arch = @import("arch.zig"); +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; +const build_options = @import("build_options"); +const mock_path = build_options.arch_mock_path; const syscalls = @import("syscalls.zig"); +const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic; +const idt = if (is_test) @import(mock_path ++ "idt_mock.zig") else @import("idt.zig"); +const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig"); +const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("../../log.zig"); -const NUMBER_OF_ENTRIES: u16 = 32; +/// The error set for the ISR. This will be from installing a ISR handler. +pub const IsrError = error{ + /// The ISR index is invalid. + InvalidIsr, -// The external assembly that is fist called to set up the exception handler. -extern fn isr0() void; -extern fn isr1() void; -extern fn isr2() void; -extern fn isr3() void; -extern fn isr4() void; -extern fn isr5() void; -extern fn isr6() void; -extern fn isr7() void; -extern fn isr8() void; -extern fn isr9() void; -extern fn isr10() void; -extern fn isr11() void; -extern fn isr12() void; -extern fn isr13() void; -extern fn isr14() void; -extern fn isr15() void; -extern fn isr16() void; -extern fn isr17() void; -extern fn isr18() void; -extern fn isr19() void; -extern fn isr20() void; -extern fn isr21() void; -extern fn isr22() void; -extern fn isr23() void; -extern fn isr24() void; -extern fn isr25() void; -extern fn isr26() void; -extern fn isr27() void; -extern fn isr28() void; -extern fn isr29() void; -extern fn isr30() void; -extern fn isr31() void; -extern fn isr128() void; + /// A ISR handler already exists. + IsrExists, +}; + +/// The type of a ISR handler. A function that takes a interrupt context and returns void. +const IsrHandler = fn (*arch.InterruptContext) void; + +/// The number of ISR entries. +const NUMBER_OF_ENTRIES: u8 = 32; /// The exception messaged that is printed when a exception happens const exception_msg: [NUMBER_OF_ENTRIES][]const u8 = [NUMBER_OF_ENTRIES][]const u8{ @@ -78,55 +63,139 @@ 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}; +/// Divide By Zero exception. +pub const DIVIDE_BY_ZERO: u8 = 0; -/// 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; +/// Single Step (Debugger) exception. +pub const SINGLE_STEP_DEBUG: u8 = 1; -// The of exception handlers initialised to unhandled. -var isr_handlers: [NUMBER_OF_ENTRIES]IsrHandler = [_]IsrHandler{unhandled} ** NUMBER_OF_ENTRIES; -var syscall_handler: IsrHandler = unhandled; +/// Non Maskable Interrupt exception. +pub const NON_MASKABLE_INTERRUPT: u8 = 2; -/// -/// A dummy handler that will make a call to panic as it is a unhandled exception. -/// -/// Arguments: -/// IN context: *arch.InterruptContext - Pointer to the exception context containing the -/// contents of the register at the time of the exception. -/// -fn unhandled(context: *arch.InterruptContext) void { - const interrupt_num = context.int_num; - panic(null, "Unhandled exception: {}, number {}", exception_msg[interrupt_num], interrupt_num); -} +/// Breakpoint (Debugger) exception. +pub const BREAKPOINT_DEBUG: u8 = 3; -/// -/// 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; -} +/// Overflow exception. +pub const OVERFLOW: u8 = 4; + +/// Bound Range Exceeded exception. +pub const BOUND_RANGE_EXCEEDED: u8 = 5; + +/// Invalid Opcode exception. +pub const INVALID_OPCODE: u8 = 6; + +/// No Coprocessor, Device Not Available exception. +pub const DEVICE_NOT_AVAILABLE: u8 = 7; + +/// Double Fault exception. +pub const DOUBLE_FAULT: u8 = 8; + +/// Coprocessor Segment Overrun exception. +pub const COPROCESSOR_SEGMENT_OVERRUN: u8 = 9; + +/// Invalid Task State Segment (TSS) exception. +pub const INVALID_TASK_STATE_SEGMENT: u8 = 10; + +/// Segment Not Present exception. +pub const SEGMENT_NOT_PRESENT: u8 = 11; + +/// Stack Segment Overrun exception. +pub const STACK_SEGMENT_FAULT: u8 = 12; + +/// General Protection Fault exception. +pub const GENERAL_PROTECTION_FAULT: u8 = 13; + +/// Page Fault exception. +pub const PAGE_FAULT: u8 = 14; + +/// x87 FPU Floating Point Error exception. +pub const X87_FLOAT_POINT: u8 = 16; + +/// Alignment Check exception. +pub const ALIGNMENT_CHECK: u8 = 17; + +/// Machine Check exception. +pub const MACHINE_CHECK: u8 = 18; + +/// SIMD Floating Point exception. +pub const SIMD_FLOAT_POINT: u8 = 19; + +/// Virtualisation exception. +pub const VIRTUALISATION: u8 = 20; + +/// Security exception. +pub const SECURITY: u8 = 30; + +/// The of exception handlers initialised to null. Need to open a ISR for these to be valid. +var isr_handlers: [NUMBER_OF_ENTRIES]?IsrHandler = [_]?IsrHandler{null} ** NUMBER_OF_ENTRIES; + +/// The syscall hander. +var syscall_handler: ?IsrHandler = null; + +// The external assembly that is fist called to set up the exception handler. +extern fn isr0() void; +extern fn isr1() void; +extern fn isr2() void; +extern fn isr3() void; +extern fn isr4() void; +extern fn isr5() void; +extern fn isr6() void; +extern fn isr7() void; +extern fn isr8() void; +extern fn isr9() void; +extern fn isr10() void; +extern fn isr11() void; +extern fn isr12() void; +extern fn isr13() void; +extern fn isr14() void; +extern fn isr15() void; +extern fn isr16() void; +extern fn isr17() void; +extern fn isr18() void; +extern fn isr19() void; +extern fn isr20() void; +extern fn isr21() void; +extern fn isr22() void; +extern fn isr23() void; +extern fn isr24() void; +extern fn isr25() void; +extern fn isr26() void; +extern fn isr27() void; +extern fn isr28() void; +extern fn isr29() void; +extern fn isr30() void; +extern fn isr31() void; +extern fn isr128() void; /// /// The exception handler that each of the exceptions will call when a exception happens. /// /// Arguments: -/// IN context: *arch.InterruptContext - Pointer to the exception context containing the -/// contents of the register at the time of the exception. +/// IN ctx: *arch.InterruptContext - Pointer to the exception context containing the contents +/// of the register at the time of the exception. /// -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); +export fn isrHandler(ctx: *arch.InterruptContext) void { + // Get the interrupt number + const isr_num = ctx.int_num; + + if (isValidIsr(isr_num)) { + if (isr_num == syscalls.INTERRUPT) { + // A syscall, so use the syscall handler + if (syscall_handler) |handler| { + handler(ctx); + } else { + panic(@errorReturnTrace(), "Syscall handler not registered\n"); + } + } else { + if (isr_handlers[isr_num]) |handler| { + // Regular ISR exception, if there is one registered. + handler(ctx); + } else { + panic(@errorReturnTrace(), "ISR not registered to: {}-{}\n", isr_num, exception_msg[isr_num]); + } + } } else { - panic(null, "Unrecognised isr: {}\n", isr_num); + panic(@errorReturnTrace(), "Invalid ISR index: {}\n", isr_num); } } @@ -140,45 +209,67 @@ export fn isrHandler(context: *arch.InterruptContext) void { fn openIsr(index: u8, handler: idt.InterruptHandler) void { idt.openInterruptGate(index, handler) catch |err| switch (err) { error.IdtEntryExists => { - panic(@errorReturnTrace(), "Error opening ISR number: {} exists", index); + panic(@errorReturnTrace(), "Error opening ISR number: {} exists\n", index); }, }; } +/// +/// 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 +/// +/// Return: bool +/// Whether a ISR hander index if valid. +/// +pub fn isValidIsr(isr_num: u32) bool { + return isr_num < NUMBER_OF_ENTRIES or isr_num == syscalls.INTERRUPT; +} + /// /// Register an exception by setting its exception handler to the given function. /// /// Arguments: /// IN irq_num: u16 - The exception number to register. /// -/// Errors: -/// IsrError.UnrecognisedIsr - If `isr_num` is invalid (see isValidIsr) +/// Errors: IsrError +/// IsrError.InvalidIsr - If the ISR index is invalid (see isValidIsr). +/// IsrError.IsrExists - If the ISR handler has already been registered. /// -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; +pub fn registerIsr(isr_num: u16, handler: IsrHandler) IsrError!void { + // Check if a valid ISR index + if (isValidIsr(isr_num)) { + if (isr_num == syscalls.INTERRUPT) { + // Syscall handler + if (syscall_handler) |_| { + // One already registered + return IsrError.IsrExists; + } else { + // Register a handler + syscall_handler = handler; + } + } else { + if (isr_handlers[isr_num]) |_| { + // One already registered + return IsrError.IsrExists; + } else { + // Register a handler + isr_handlers[isr_num] = handler; + } + } } else { - return IsrError.UnrecognisedIsr; + return IsrError.InvalidIsr; } } -/// -/// Unregister an exception by setting its exception handler to the unhandled function call to -/// panic. -/// -/// Arguments: -/// IN irq_num: u16 - The exception number to unregister. -/// -pub fn unregisterIsr(isr_num: u16) void { - isr_handlers[isr_num] = unhandled; -} - /// /// Initialise the exception and opening up all the IDT interrupt gates for each exception. /// pub fn init() void { + log.logInfo("Init isr\n"); + openIsr(0, isr0); openIsr(1, isr1); openIsr(2, isr2); @@ -212,4 +303,160 @@ pub fn init() void { openIsr(30, isr30); openIsr(31, isr31); openIsr(syscalls.INTERRUPT, isr128); + + log.logInfo("Done\n"); + + if (build_options.rt_test) runtimeTests(); +} + +extern fn testFunction0() void {} +fn testFunction1(ctx: *arch.InterruptContext) void {} +fn testFunction2(ctx: *arch.InterruptContext) void {} +fn testFunction3(ctx: *arch.InterruptContext) void {} +fn testFunction4(ctx: *arch.InterruptContext) void {} + +test "openIsr" { + idt.initTest(); + defer idt.freeTest(); + + const index = u8(0); + const handler = testFunction0; + const ret: idt.IdtError!void = {}; + + idt.addTestParams("openInterruptGate", index, handler, ret); + + openIsr(index, handler); +} + +test "isValidIsr" { + comptime var i = 0; + inline while (i < NUMBER_OF_ENTRIES) : (i += 1) { + expectEqual(true, isValidIsr(i)); + } + + expectEqual(true, isValidIsr(syscalls.INTERRUPT)); + + expectEqual(false, isValidIsr(200)); +} + +test "registerIsr re-register syscall handler" { + // Pre testing + expect(null == syscall_handler); + + // Call function + try registerIsr(syscalls.INTERRUPT, testFunction3); + expectError(IsrError.IsrExists, registerIsr(syscalls.INTERRUPT, testFunction4)); + + // Post testing + expectEqual(testFunction3, syscall_handler.?); + + // Clean up + syscall_handler = null; +} + +test "registerIsr register syscall handler" { + // Pre testing + expect(null == syscall_handler); + + // Call function + try registerIsr(syscalls.INTERRUPT, testFunction3); + + // Post testing + expectEqual(testFunction3, syscall_handler.?); + + // Clean up + syscall_handler = null; +} + +test "registerIsr re-register isr handler" { + // Pre testing + for (isr_handlers) |h| { + expect(null == h); + } + + // Call function + try registerIsr(0, testFunction1); + expectError(IsrError.IsrExists, registerIsr(0, testFunction2)); + + // Post testing + for (isr_handlers) |h, i| { + if (i != 0) { + expect(null == h); + } else { + expectEqual(testFunction1, h.?); + } + } + + // Clean up + isr_handlers[0] = null; +} + +test "registerIsr register isr handler" { + // Pre testing + for (isr_handlers) |h| { + expect(null == h); + } + + // Call function + try registerIsr(0, testFunction1); + + // Post testing + for (isr_handlers) |h, i| { + if (i != 0) { + expect(null == h); + } else { + expectEqual(testFunction1, h.?); + } + } + + // Clean up + isr_handlers[0] = null; +} + +test "registerIsr invalid isr index" { + expectError(IsrError.InvalidIsr, registerIsr(200, testFunction1)); +} + +/// +/// Test that all handers are null at initialisation. +/// +fn rt_unregisteredHandlers() void { + // Ensure all ISR are not registered yet + for (isr_handlers) |h, i| { + if (h) |_| { + panic(@errorReturnTrace(), "Handler found for ISR: {}-{}\n", i, h); + } + } + + if (syscall_handler) |h| { + panic(@errorReturnTrace(), "Pre-testing failed for syscall: {}\n", h); + } + + log.logInfo("ISR: Tested registered handlers\n"); +} + +/// +/// Test that all IDT entries for the ISRs are open. +/// +fn rt_openedIdtEntries() void { + const loaded_idt = arch.sidt(); + const idt_entries = @intToPtr([*]idt.IdtEntry, loaded_idt.base)[0..idt.NUMBER_OF_ENTRIES]; + + for (idt_entries) |entry, i| { + if (isValidIsr(i)) { + if (!idt.isIdtOpen(entry)) { + panic(@errorReturnTrace(), "IDT entry for {} is not open\n", i); + } + } + } + + log.logInfo("ISR: Tested opened IDT entries\n"); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_unregisteredHandlers(); + rt_openedIdtEntries(); } diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index e256d72..030c287 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -37,10 +37,10 @@ const PagingError = error{ /// Physical and virtual addresses don't cover spaces of the same size. PhysicalVirtualMismatch, - /// Physical addressses aren't aligned by page size. + /// Physical addresses aren't aligned by page size. UnalignedPhysAddresses, - /// Virtual addressses aren't aligned by page size. + /// Virtual addresses aren't aligned by page size. UnalignedVirtAddresses, }; @@ -301,7 +301,7 @@ fn mapDir(dir: *Directory, virt_start: usize, virt_end: usize, phys_start: usize /// Called when a page fault occurs. /// /// Arguments: -/// IN state: *arch.InterruptContext - The CPU's state when the fault occured. +/// IN state: *arch.InterruptContext - The CPU's state when the fault occurred. /// fn pageFault(state: *arch.InterruptContext) void { @panic("Page fault"); @@ -334,7 +334,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator) void panic(@errorReturnTrace(), "Failed to map kernel directory: {}\n", e); }; const tty_addr = tty.getVideoBufferAddress(); - // If the previous mappping space didn't cover the tty buffer, do so now + // If the previous mapping space didn't cover the tty buffer, do so now if (v_start > tty_addr or v_end <= tty_addr) { const tty_phys = virtToPhys(tty_addr); const tty_buff_size = 32 * 1024; @@ -348,7 +348,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator) void : : [addr] "{eax}" (dir_physaddr) ); - isr.registerIsr(14, if (options.rt_test) rt_pageFault else pageFault) catch |e| { + isr.registerIsr(isr.PAGE_FAULT, if (options.rt_test) rt_pageFault else pageFault) catch |e| { panic(@errorReturnTrace(), "Failed to register page fault ISR: {}\n", e); }; log.logInfo("Done\n"); diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index 65abbe4..673f75f 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -1,27 +1,27 @@ -// Zig version: 0.4.0 - const arch = @import("arch.zig"); const assert = @import("std").debug.assert; const irq = @import("irq.zig"); const pic = @import("pic.zig"); const log = @import("../../log.zig"); +const panic = @import("../../panic.zig").panic; // The port addresses of the PIT registers + /// The port address for the PIT data register for counter 0. This is going to be used as the /// system clock. -const COUNTER_0_REGISTER: u16 = 0x40; +const COUNTER_0_REGISTER: u16 = 0x40; /// The port address for the PIT data register for counter 1. This was used for refreshing the DRAM /// chips. But now is unused and unknown use, so won't use. -const COUNTER_1_REGISTER: u16 = 0x41; +const COUNTER_1_REGISTER: u16 = 0x41; /// The port address for the PIT data register for counter 2. Connected to the PC speakers, we'll /// use this for the speakers. -const COUNTER_2_REGISTER: u16 = 0x42; +const COUNTER_2_REGISTER: u16 = 0x42; /// The port address for the PIT control word register. Used to tell the PIT controller what is /// about to happen. Tell what data is going where, lower or upper part of it's registers. -const COMMAND_REGISTER: u16 = 0x43; +const COMMAND_REGISTER: u16 = 0x43; // The operational command word marks for the different modes. // @@ -49,62 +49,62 @@ const COMMAND_REGISTER: u16 = 0x43; // 11: Illegal value. /// Have the counter count in binary (internally?). -const OCW_BINARY_COUNT_BINARY: u8 = 0x00; // xxxxxxx0 +const OCW_BINARY_COUNT_BINARY: u8 = 0x00; // xxxxxxx0 /// Have the counter count in BCD (internally?). -const OCW_BINARY_COUNT_BCD: u8 = 0x01; // xxxxxxx1 +const OCW_BINARY_COUNT_BCD: u8 = 0x01; // xxxxxxx1 /// The PIT counter will be programmed with an initial COUNT value that counts down at a rate of /// the input clock frequency. When the COUNT reaches 0, and after the control word is written, /// then its OUT pit is set high (1). Count down starts then the COUNT is set. The OUT pin remains /// high until the counter is reloaded with a new COUNT value or a new control work is written. -const OCW_MODE_TERMINAL_COUNT: u8 = 0x00; // xxxx000x +const OCW_MODE_TERMINAL_COUNT: u8 = 0x00; // xxxx000x /// The counter is programmed to output a pulse every curtain number of clock pulses. The OUT pin /// remains high as soon as a control word is written. When COUNT is written, the counter waits /// until the rising edge of the GATE pin to start. One clock pulse after the GATE pin, the OUT /// pin will remain low until COUNT reaches 0. -const OCW_MODE_ONE_SHOT: u8 = 0x02; // xxxx001x +const OCW_MODE_ONE_SHOT: u8 = 0x02; // xxxx001x /// The counter is initiated with a COUNT value. Counting starts next clock pulse. OUT pin remains /// high until COUNT reaches 1, then is set low for one clock pulse. Then COUNT is reset back to /// initial value and OUT pin is set high again. -const OCW_MODE_RATE_GENERATOR: u8 = 0x04; // xxxx010x +const OCW_MODE_RATE_GENERATOR: u8 = 0x04; // xxxx010x /// Similar to PIT_OCW_MODE_RATE_GENERATOR, but OUT pin will be high for half the time and low for /// half the time. Good for the speaker when setting a tone. -const OCW_MODE_SQUARE_WAVE_GENERATOR: u8 = 0x06; // xxxx011x +const OCW_MODE_SQUARE_WAVE_GENERATOR: u8 = 0x06; // xxxx011x /// The counter is initiated with a COUNT value. Counting starts on next clock pulse. OUT pin remains /// high until count is 0. Then OUT pin is low for one clock pulse. Then resets to high again. -const OCW_MODE_SOFTWARE_TRIGGER: u8 = 0x08; // xxxx100x +const OCW_MODE_SOFTWARE_TRIGGER: u8 = 0x08; // xxxx100x /// The counter is initiated with a COUNT value. OUT pin remains high until the rising edge of the /// GATE pin. Then the counting begins. When COUNT reaches 0, OUT pin goes low for one clock pulse. /// Then COUNT is reset and OUT pin goes high. This cycles for each rising edge of the GATE pin. -const OCW_MODE_HARDWARE_TRIGGER: u8 = 0x0A; // xxxx101x +const OCW_MODE_HARDWARE_TRIGGER: u8 = 0x0A; // xxxx101x /// The counter value is latched into an internal control register at the time of the I/O write /// operations. -const OCW_READ_LOAD_LATCH: u8 = 0x00; // xx00xxxx +const OCW_READ_LOAD_LATCH: u8 = 0x00; // xx00xxxx /// Read or load the most significant bit only. -const OCW_READ_LOAD_LSB_ONLY: u8 = 0x10; // xx01xxxx +const OCW_READ_LOAD_LSB_ONLY: u8 = 0x10; // xx01xxxx /// Read or load the least significant bit only. -const OCW_READ_LOAD_MSB_ONLY: u8 = 0x20; // xx10xxxx +const OCW_READ_LOAD_MSB_ONLY: u8 = 0x20; // xx10xxxx /// Read or load the least significant bit first then the most significant bit. -const OCW_READ_LOAD_DATA: u8 = 0x30; // xx11xxxx +const OCW_READ_LOAD_DATA: u8 = 0x30; // xx11xxxx /// The OCW bits for selecting counter 0. Used for the system clock. -const OCW_SELECT_COUNTER_0: u8 = 0x00; // 00xxxxxx +const OCW_SELECT_COUNTER_0: u8 = 0x00; // 00xxxxxx /// The OCW bits for selecting counter 1. Was for the memory refreshing. -const OCW_SELECT_COUNTER_1: u8 = 0x40; // 01xxxxxx +const OCW_SELECT_COUNTER_1: u8 = 0x40; // 01xxxxxx /// The OCW bits for selecting counter 2. Channel for the speaker. -const OCW_SELECT_COUNTER_2: u8 = 0x80; // 10xxxxxx +const OCW_SELECT_COUNTER_2: u8 = 0x80; // 10xxxxxx // The divisor constant const MAX_FREQUENCY: u32 = 1193180; diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig index 00bc4c1..d8531e6 100644 --- a/src/kernel/arch/x86/syscalls.zig +++ b/src/kernel/arch/x86/syscalls.zig @@ -29,17 +29,21 @@ var handlers: [NUM_HANDLERS]?SyscallHandler = [_]?SyscallHandler{null} ** NUM_HA /// Arguments: /// IN syscall: u32 - The syscall to check /// +/// Return: bool +/// Whether the syscall number is valid. +/// 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. +/// 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. +/// 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 @@ -80,6 +84,9 @@ pub fn registerSyscall(syscall: u8, handler: SyscallHandler) SyscallError!void { /// Arguments: /// IN syscall: u32 - The syscall to trigger, put in eax. /// +/// Return: u32 +/// The return value from the syscall. +/// inline fn syscall0(syscall: u32) u32 { return asm volatile ( \\int $0x80 @@ -95,6 +102,9 @@ inline fn syscall0(syscall: u32) u32 { /// IN syscall: u32 - The syscall to trigger, put in eax. /// IN arg: u32 - The argument to pass. Put in ebx. /// +/// Return: u32 +/// The return value from the syscall. +/// inline fn syscall1(syscall: u32, arg: u32) u32 { return asm volatile ( \\int $0x80 @@ -112,6 +122,9 @@ inline fn syscall1(syscall: u32, arg: u32) u32 { /// IN arg1: u32 - The first argument to pass. Put in ebx. /// IN arg2: u32 - The second argument to pass. Put in ecx. /// +/// Return: u32 +/// The return value from the syscall. +/// inline fn syscall2(syscall: u32, arg1: u32, arg2: u32) u32 { return asm volatile ( \\int $0x80 @@ -131,6 +144,9 @@ inline fn syscall2(syscall: u32, arg1: u32, arg2: u32) u32 { /// IN arg2: u32 - The second argument to pass. Put in ecx. /// IN arg3: u32 - The third argument to pass. Put in edx. /// +/// Return: u32 +/// The return value from the syscall. +/// inline fn syscall3(syscall: u32, arg1: u32, arg2: u32, arg3: u32) u32 { return asm volatile ( \\int $0x80 @@ -152,6 +168,9 @@ inline fn syscall3(syscall: u32, arg1: u32, arg2: u32, arg3: u32) u32 { /// IN arg3: u32 - The third argument to pass. Put in edx. /// IN arg4: u32 - The fourth argument to pass. Put in esi. /// +/// Return: u32 +/// The return value from the syscall. +/// inline fn syscall4(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32) u32 { return asm volatile ( \\int $0x80 @@ -175,6 +194,9 @@ inline fn syscall4(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32) u32 /// IN arg4: u32 - The fourth argument to pass. Put in esi. /// IN arg5: u32 - The fifth argument to pass. Put in edi. /// +/// Return: u32 +/// 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 ( \\int $0x80 @@ -189,12 +211,16 @@ inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg } /// -/// Gets the syscall argument according to the given index. 0 => ebx, 1 => ecx, 2 => edx, 3 => esi and 4 => edi. +/// 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. /// +/// Return: u32 +/// The syscall argument from the given index. +/// inline fn syscallArg(ctx: *arch.InterruptContext, comptime arg_idx: u32) u32 { return switch (arg_idx) { 0 => ctx.ebx, diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 85432d4..bd469fa 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -1,13 +1,16 @@ const std = @import("std"); const builtin = @import("builtin"); +const is_test = builtin.is_test; const build_options = @import("build_options"); +const mock_path = build_options.mock_path; const arch = @import("arch.zig").internals; const multiboot = @import("multiboot.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); const log = @import("log.zig"); const serial = @import("serial.zig"); -const mem = if (builtin.is_test) @import(build_options.mock_path ++ "mem_mock.zig") else @import("mem.zig"); +const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig"); +const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic; comptime { switch (builtin.arch) { @@ -20,10 +23,6 @@ comptime { // from the linker script export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefined; -// Need to import this as we need the panic to be in the root source file, or zig will just use the -// builtin panic and just loop, which is what we don't want -const panic_root = @import("panic.zig").panic; - // Just call the panic function, as this need to be in the root source file pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index d075808..7bb9113 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -6,6 +6,8 @@ def get_test_cases(TestCase): TestCase("IDT tests", [r"IDT: Tested loading IDT"]), TestCase("PIC init", [r"Init pic", r"Done"]), TestCase("PIC tests", [r"PIC: Tested masking"]), + TestCase("ISR init", [r"Init isr", r"Done"]), + TestCase("ISR tests", [r"ISR: Tested registered handlers", r"ISR: Tested opened IDT entries"]), TestCase("PIT init", [r"Init pit", r".+", r"Done"]), TestCase("Paging init", [r"Init paging", r"Done"]), TestCase("Paging tests", [r"Paging: Tested accessing unmapped memory", r"Paging: Tested accessing mapped memory"]), diff --git a/test/mock/kernel/idt_mock.zig b/test/mock/kernel/idt_mock.zig index 6c1fca4..41f44e1 100644 --- a/test/mock/kernel/idt_mock.zig +++ b/test/mock/kernel/idt_mock.zig @@ -21,9 +21,9 @@ const IdtEntry = packed struct { // Need to use the type from the source file so that types match pub const IdtPtr = src_idt.IdtPtr; -pub const InterruptHandler = extern fn () void; +pub const InterruptHandler = src_idt.InterruptHandler; -pub const IdtError = error{IdtEntryExists}; +pub const IdtError = src_idt.IdtError; const TASK_GATE: u4 = 0x5; const INTERRUPT_GATE: u4 = 0xE; @@ -39,7 +39,7 @@ const NUMBER_OF_ENTRIES: u16 = 256; const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1; pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void { - return mock_framework.performAction("openInterruptGate", IdtError!void, port); + return mock_framework.performAction("openInterruptGate", IdtError!void, index, handler); } pub fn init() void { diff --git a/test/mock/kernel/mock_framework.zig b/test/mock/kernel/mock_framework.zig index d80a831..39336e2 100644 --- a/test/mock/kernel/mock_framework.zig +++ b/test/mock/kernel/mock_framework.zig @@ -19,6 +19,8 @@ const DataElementType = enum { U32, PTR_CONST_GdtPtr, PTR_CONST_IdtPtr, + ERROR_IDTERROR_VOID, + EFN_OVOID, FN_OVOID, FN_OUSIZE, FN_OU16, @@ -28,6 +30,7 @@ const DataElementType = enum { FN_IU8_IU8_OU16, FN_IU16_IU8_OVOID, FN_IU16_IU16_OVOID, + FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, FN_IPTRCONSTGDTPTR_OVOID, FN_IPTRCONSTIDTPTR_OVOID, }; @@ -44,6 +47,8 @@ const DataElement = union(DataElementType) { U32: u32, PTR_CONST_GdtPtr: *const gdt.GdtPtr, PTR_CONST_IdtPtr: *const idt.IdtPtr, + ERROR_IDTERROR_VOID: idt.IdtError!void, + EFN_OVOID: extern fn () void, FN_OVOID: fn () void, FN_OUSIZE: fn () usize, FN_OU16: fn () u16, @@ -53,6 +58,7 @@ const DataElement = union(DataElementType) { FN_IU8_IU8_OU16: fn (u8, u8) u16, FN_IU16_IU8_OVOID: fn (u16, u8) void, FN_IU16_IU16_OVOID: fn (u16, u16) void, + FN_IU8_IEFNOVOID_OERRORIDTERRORVOID: fn (u8, extern fn () void) idt.IdtError!void, FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void, FN_IPTRCONSTIDTPTR_OVOID: fn (*const idt.IdtPtr) void, }; @@ -136,6 +142,8 @@ fn Mock() type { u32 => DataElement{ .U32 = arg }, *const gdt.GdtPtr => DataElement{ .PTR_CONST_GdtPtr = arg }, *const idt.IdtPtr => DataElement{ .PTR_CONST_IdtPtr = arg }, + idt.IdtError!void => DataElement{ .ERROR_IDTERROR_VOID = arg }, + extern fn () void => DataElement{ .EFN_OVOID = arg }, fn () void => DataElement{ .FN_OVOID = arg }, fn () usize => DataElement{ .FN_OUSIZE = arg }, fn () u16 => DataElement{ .FN_OU16 = arg }, @@ -147,6 +155,7 @@ fn Mock() type { fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg }, fn (*const idt.IdtPtr) void => DataElement{ .FN_IPTRCONSTIDTPTR_OVOID = arg }, + fn (u8, extern fn () void) idt.IdtError!void => DataElement{ .FN_IU8_IEFNOVOID_OERRORIDTERRORVOID = arg }, else => @compileError("Type not supported: " ++ @typeName(@typeOf(arg))), }; } @@ -168,6 +177,8 @@ fn Mock() type { u32 => DataElementType.U32, *const gdt.GdtPtr => DataElement.PTR_CONST_GdtPtr, *const idt.IdtPtr => DataElement.PTR_CONST_IdtPtr, + idt.IdtError!void => DataElement.ERROR_IDTERROR_VOID, + extern fn () void => DataElementType.EFN_OVOID, fn () void => DataElementType.FN_OVOID, fn () u16 => DataElementType.FN_OU16, fn (u16) void => DataElementType.FN_IU16_OVOID, @@ -178,6 +189,7 @@ fn Mock() type { fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID, fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID, + fn (u8, extern fn () void) idt.IdtError!void => DataElementType.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, else => @compileError("Type not supported: " ++ @typeName(T)), }; } @@ -201,6 +213,8 @@ fn Mock() type { u32 => element.U32, *const gdt.GdtPtr => element.PTR_CONST_GdtPtr, *const idt.IdtPtr => element.PTR_CONST_IdtPtr, + idt.IdtError!void => element.ERROR_IDTERROR_VOID, + extern fn () void => element.EFN_OVOID, fn () void => element.FN_OVOID, fn () u16 => element.FN_OU16, fn (u16) void => element.FN_IU16_OVOID, @@ -211,6 +225,7 @@ fn Mock() type { fn (u16, u16) void => element.FN_IU16_IU16_OVOID, fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID, fn (*const idt.IdtPtr) void => element.FN_IPTRCONSTIDTPTR_OVOID, + fn (u8, extern fn () void) idt.IdtError!void => element.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, else => @compileError("Type not supported: " ++ @typeName(T)), }; } @@ -248,8 +263,7 @@ fn Mock() type { fn expectTest(comptime ExpectedType: type, expected_value: ExpectedType, elem: DataElement) void { if (ExpectedType == void) { // Can't test void as it has no value - warn("Can not test a value for void\n"); - expect(false); + std.debug.panic("Can not test a value for void\n"); } // Test that the types match @@ -291,9 +305,7 @@ fn Mock() type { return ret; } else { - warn("No more test values for the return of function: " ++ fun_name ++ "\n"); - expect(false); - unreachable; + std.debug.panic("No more test values for the return of function: " ++ fun_name ++ "\n"); } } @@ -328,9 +340,7 @@ fn Mock() type { } else { // Shouldn't get here as we would have just added a new mapping // But just in case ;) - warn("No function name: " ++ fun_name ++ "\n"); - expect(false); - unreachable; + std.debug.panic("No function name: " ++ fun_name ++ "\n"); } } @@ -448,14 +458,10 @@ fn Mock() type { kv_actions_list.value = action_list; return ret; } else { - warn("No action list elements for function: " ++ fun_name ++ "\n"); - expect(false); - unreachable; + std.debug.panic("No action list elements for function: " ++ fun_name ++ "\n"); } } else { - warn("No function name: " ++ fun_name ++ "\n"); - expect(false); - unreachable; + std.debug.panic("No function name: " ++ fun_name ++ "\n"); } } @@ -489,9 +495,7 @@ fn Mock() type { switch (action.action) { ActionType.TestValue, ActionType.ConsumeFunctionCall => { // These need to be all consumed - warn("Unused testing value: Type: {}, value: {} for function '{}'\n", action.action, DataElementType(action.data), next.key); - expect(false); - unreachable; + std.debug.panic("Unused testing value: Type: {}, value: {} for function '{}'\n", action.action, DataElementType(action.data), next.key); }, ActionType.RepeatFunctionCall => { // As this is a repeat action, the function will still be here