From 2906d6ca139205fe68a541817820db52a12be719 Mon Sep 17 00:00:00 2001 From: ED Date: Sun, 6 Oct 2019 17:40:12 +0100 Subject: [PATCH] Added unit and runtime tests Also changed up panic call Fixed rebasing Feedback --- src/kernel/arch/x86/arch.zig | 1 + src/kernel/arch/x86/irq.zig | 257 +++++++++++++++++++++++----- src/kernel/arch/x86/isr.zig | 4 +- src/kernel/arch/x86/pit.zig | 17 +- test/kernel/arch/x86/rt-test.py | 2 + test/mock/kernel/mock_framework.zig | 15 ++ test/mock/kernel/pic_mock.zig | 92 ++++++++++ 7 files changed, 339 insertions(+), 49 deletions(-) create mode 100644 test/mock/kernel/pic_mock.zig diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 3f26002..420d982 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -239,6 +239,7 @@ test "" { _ = @import("idt.zig"); _ = @import("pic.zig"); _ = @import("isr.zig"); + _ = @import("irq.zig"); _ = @import("syscalls.zig"); _ = @import("paging.zig"); } diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig index 73a7118..43a8d43 100644 --- a/src/kernel/arch/x86/irq.zig +++ b/src/kernel/arch/x86/irq.zig @@ -1,14 +1,38 @@ -// Zig version: 0.4.0 - +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 panic = @import("../../panic.zig").panic; -const idt = @import("idt.zig"); -const arch = @import("arch.zig"); -const pic = @import("pic.zig"); +const mock_path = build_options.arch_mock_path; +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 pic = if (is_test) @import(mock_path ++ "pic_mock.zig") else @import("pic.zig"); +/// The error set for the IRQ. This will be from installing a IRQ handler. +pub const IrqError = error{ + /// The IRQ index is invalid. + InvalidIrq, + + /// A IRQ handler already exists. + IrqExists, +}; + +/// The total number of IRQ. const NUMBER_OF_ENTRIES: u16 = 16; +// The offset from the interrupt number where the IRQs are. const IRQ_OFFSET: u16 = 32; +/// The type of a IRQ handler. A function that takes a interrupt context and returns void. +const IrqHandler = fn (*arch.InterruptContext) void; + +/// The list of IRQ handlers initialised to unhandled. +var irq_handlers: [NUMBER_OF_ENTRIES]?IrqHandler = [_]?IrqHandler{null} ** NUMBER_OF_ENTRIES; + // The external assembly that is fist called to set up the interrupt handler. extern fn irq0() void; extern fn irq1() void; @@ -27,35 +51,31 @@ extern fn irq13() void; extern fn irq14() void; extern fn irq15() void; -/// The list of IRQ handlers initialised to unhandled. -var irq_handlers: [NUMBER_OF_ENTRIES]fn (*arch.InterruptContext) void = [_]fn (*arch.InterruptContext) void{unhandled} ** NUMBER_OF_ENTRIES; - /// -/// A dummy handler that will make a call to panic as it is a unhandled interrupt. +/// The IRQ handler that each of the IRQs will call when a interrupt happens. /// /// Arguments: -/// IN context: *arch.InterruptContext - Pointer to the interrupt context containing the -/// contents of the register at the time of the interrupt. +/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents +/// of the register at the time of the interrupt. /// -fn unhandled(context: *arch.InterruptContext) void { - const interrupt_num: u8 = @truncate(u8, context.int_num - IRQ_OFFSET); - panic(null, "Unhandled IRQ number {}", interrupt_num); -} - -/// -/// The IRQ handler that each of the IRQ's will call when a interrupt happens. -/// -/// Arguments: -/// IN context: *arch.InterruptContext - Pointer to the interrupt context containing the -/// contents of the register at the time of the interrupt. -/// -export fn irqHandler(context: *arch.InterruptContext) void { - const irq_num: u8 = @truncate(u8, context.int_num - IRQ_OFFSET); - // Make sure it isn't a spurious irq - if (!pic.spuriousIrq(irq_num)) { - irq_handlers[irq_num](context); - // Send the end of interrupt command - pic.sendEndOfInterrupt(irq_num); +export fn irqHandler(ctx: *arch.InterruptContext) void { + // Get the IRQ index, by getting the interrupt number and subtracting the offset. + const irq_offset = ctx.int_num - IRQ_OFFSET; + if (isValidIrq(irq_offset)) { + // IRQ index is valid so can truncate + const irq_num = @truncate(u8, irq_offset); + if (irq_handlers[irq_num]) |handler| { + // Make sure it isn't a spurious irq + if (!pic.spuriousIrq(irq_num)) { + handler(ctx); + // Send the end of interrupt command + pic.sendEndOfInterrupt(irq_num); + } + } else { + panic(@errorReturnTrace(), "IRQ not registered: {}", irq_num); + } + } else { + panic(@errorReturnTrace(), "Invalid IRQ index: {}", irq_offset); } } @@ -75,27 +95,45 @@ fn openIrq(index: u8, handler: idt.InterruptHandler) void { } /// -/// Register a IRQ by setting its interrupt handler to the given function. This will also clear the -/// mask bit in the PIC so interrupts can happen. +/// Check whether the IRQ index is valid. This will have to be less than NUMBER_OF_ENTRIES. /// /// Arguments: -/// IN irq_num: u8 - The IRQ number to register. +/// IN irq_num: u8 - The IRQ index to test. /// -pub fn registerIrq(irq_num: u8, handler: fn (*arch.InterruptContext) void) void { - irq_handlers[irq_num] = handler; - pic.clearMask(irq_num); +/// Return: bool +/// Whether the IRQ index if valid. +/// +pub fn isValidIrq(irq_num: u32) bool { + return irq_num < NUMBER_OF_ENTRIES; } /// -/// Unregister a IRQ by setting its interrupt handler to the unhandled function call to panic. This -/// will also set the mask bit in the PIC so no interrupts can happen anyway. +/// Register a IRQ by setting its interrupt handler to the given function. This will also clear the +/// mask bit in the PIC so interrupts can happen for this IRQ. /// /// Arguments: -/// IN irq_num: u16 - The IRQ number to unregister. +/// IN irq_num: u8 - The IRQ number to register. +/// IN handler: IrqHandler - The IRQ handler to register. This is what will be called when this +/// interrupt happens. /// -pub fn unregisterIrq(irq_num: u16) void { - irq_handlers[irq_num] = unhandled; - pic.setMask(irq_num); +/// Errors: IrqError +/// IrqError.InvalidIrq - If the IRQ index is invalid (see isValidIrq). +/// IrqError.IrqExists - If the IRQ handler has already been registered. +/// +pub fn registerIrq(irq_num: u8, handler: IrqHandler) IrqError!void { + // Check whether the IRQ index is valid. + if (isValidIrq(irq_num)) { + // Check if a handler has already been registered. + if (irq_handlers[irq_num]) |_| { + return IrqError.IrqExists; + } else { + // Register the handler and clear the PIC mask so interrupts can happen. + irq_handlers[irq_num] = handler; + pic.clearMask(irq_num); + } + } else { + return IrqError.InvalidIrq; + } } /// @@ -103,7 +141,9 @@ pub fn unregisterIrq(irq_num: u16) void { /// the IDT interrupt gates for each IRQ. /// pub fn init() void { - // Open all the IRQ's + log.logInfo("Init irq\n"); + + // Open all the IRQs openIrq(32, irq0); openIrq(33, irq1); openIrq(34, irq2); @@ -120,4 +160,135 @@ pub fn init() void { openIrq(45, irq13); openIrq(46, irq14); openIrq(47, irq15); + + 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 {} + +test "openIrq" { + idt.initTest(); + defer idt.freeTest(); + + const index = u8(0); + const handler = testFunction0; + const ret: idt.IdtError!void = {}; + + idt.addTestParams("openInterruptGate", index, handler, ret); + + openIrq(index, handler); +} + +test "isValidIrq" { + comptime var i = 0; + inline while (i < NUMBER_OF_ENTRIES) : (i += 1) { + expect(isValidIrq(i)); + } + + expect(!isValidIrq(200)); +} + +test "registerIrq re-register irq handler" { + // Set up + pic.initTest(); + defer pic.freeTest(); + + pic.addTestParams("clearMask", u16(0)); + + // Pre testing + for (irq_handlers) |h| { + expect(null == h); + } + + // Call function + try registerIrq(0, testFunction1); + expectError(IrqError.IrqExists, registerIrq(0, testFunction2)); + + // Post testing + for (irq_handlers) |h, i| { + if (i != 0) { + expect(null == h); + } else { + expectEqual(testFunction1, h.?); + } + } + + // Clean up + irq_handlers[0] = null; +} + +test "registerIrq register irq handler" { + // Set up + pic.initTest(); + defer pic.freeTest(); + + pic.addTestParams("clearMask", u16(0)); + + // Pre testing + for (irq_handlers) |h| { + expect(null == h); + } + + // Call function + try registerIrq(0, testFunction1); + + // Post testing + for (irq_handlers) |h, i| { + if (i != 0) { + expect(null == h); + } else { + expectEqual(testFunction1, h.?); + } + } + + // Clean up + irq_handlers[0] = null; +} + +test "registerIrq invalid irq index" { + expectError(IrqError.InvalidIrq, registerIrq(200, testFunction1)); +} + +/// +/// Test that all handers are null at initialisation. +/// +fn rt_unregisteredHandlers() void { + // Ensure all ISR are not registered yet + for (irq_handlers) |h, i| { + if (h) |_| { + panic(@errorReturnTrace(), "Handler found for IRQ: {}-{}\n", i, h); + } + } + + log.logInfo("IRQ: Tested registered handlers\n"); +} + +/// +/// Test that all IDT entries for the IRQs 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 (i >= IRQ_OFFSET and isValidIrq(i - IRQ_OFFSET)) { + if (!idt.isIdtOpen(entry)) { + panic(@errorReturnTrace(), "IDT entry for {} is not open\n", i); + } + } + } + + log.logInfo("IRQ: Tested opened IDT entries\n"); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_unregisteredHandlers(); + rt_openedIdtEntries(); } diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index 614e3b8..e03a3d1 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -334,9 +334,9 @@ test "isValidIsr" { expectEqual(true, isValidIsr(i)); } - expectEqual(true, isValidIsr(syscalls.INTERRUPT)); + expect(isValidIsr(syscalls.INTERRUPT)); - expectEqual(false, isValidIsr(200)); + expect(!isValidIsr(200)); } test "registerIsr re-register syscall handler" { diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index 673f75f..b523355 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -275,12 +275,21 @@ pub fn getFrequency() u32 { pub fn init() void { log.logInfo("Init pit\n"); // Set up counter 0 at 1000hz in a square wave mode counting in binary - const f: u32 = 10000; - setupCounter(OCW_SELECT_COUNTER_0, f, OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY); + // TODO: https://github.com/ziglang/zig/issues/557, Need type defined + const freq: u32 = 10000; + setupCounter(OCW_SELECT_COUNTER_0, freq, OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY); - log.logInfo("Set frequency at: {}Hz, real frequency: {}Hz\n", f, getFrequency()); + log.logInfo("Set frequency at: {}Hz, real frequency: {}Hz\n", freq, getFrequency()); // Installs 'pitHandler' to IRQ0 (pic.IRQ_PIT) - irq.registerIrq(pic.IRQ_PIT, pitHandler); + irq.registerIrq(pic.IRQ_PIT, pitHandler) catch |err| switch (err) { + error.IrqExists => { + panic(@errorReturnTrace(), "IRQ for PIT, IRQ number: {} exists", pic.IRQ_PIT); + }, + error.InvalidIrq => { + panic(@errorReturnTrace(), "IRQ for PIT, IRQ number: {} is invalid", pic.IRQ_PIT); + }, + }; + log.logInfo("Done\n"); } diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index 7bb9113..430ab6a 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -8,6 +8,8 @@ def get_test_cases(TestCase): 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("IRQ init", [r"Init irq", r"Done"]), + TestCase("IRQ tests", [r"IRQ: Tested registered handlers", r"IRQ: 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/mock_framework.zig b/test/mock/kernel/mock_framework.zig index 39336e2..7a3fc84 100644 --- a/test/mock/kernel/mock_framework.zig +++ b/test/mock/kernel/mock_framework.zig @@ -13,6 +13,7 @@ const idt = @import("idt_mock.zig"); /// and function types like fn () void /// const DataElementType = enum { + BOOL, U4, U8, U16, @@ -24,6 +25,8 @@ const DataElementType = enum { FN_OVOID, FN_OUSIZE, FN_OU16, + FN_IU8_OBOOL, + FN_IU8_OVOID, FN_IU16_OVOID, FN_IU16_OU8, FN_IU4_IU4_OU8, @@ -41,6 +44,7 @@ const DataElementType = enum { /// so this wraps the data into a union, (which is of one type) so can have a list of them. /// const DataElement = union(DataElementType) { + BOOL: bool, U4: u4, U8: u8, U16: u16, @@ -52,6 +56,8 @@ const DataElement = union(DataElementType) { FN_OVOID: fn () void, FN_OUSIZE: fn () usize, FN_OU16: fn () u16, + FN_IU8_OBOOL: fn (u8) bool, + FN_IU8_OVOID: fn (u8) void, FN_IU16_OVOID: fn (u16) void, FN_IU16_OU8: fn (u16) u8, FN_IU4_IU4_OU8: fn (u4, u4) u8, @@ -136,6 +142,7 @@ fn Mock() type { /// fn createDataElement(arg: var) DataElement { return switch (@typeOf(arg)) { + bool => DataElement{ .BOOL = arg }, u4 => DataElement{ .U4 = arg }, u8 => DataElement{ .U8 = arg }, u16 => DataElement{ .U16 = arg }, @@ -147,6 +154,8 @@ fn Mock() type { fn () void => DataElement{ .FN_OVOID = arg }, fn () usize => DataElement{ .FN_OUSIZE = arg }, fn () u16 => DataElement{ .FN_OU16 = arg }, + fn (u8) bool => DataElement{ .FN_IU8_OBOOL = arg }, + fn (u8) void => DataElement{ .FN_IU8_OVOID = arg }, fn (u16) void => DataElement{ .FN_IU16_OVOID = arg }, fn (u16) u8 => DataElement{ .FN_IU16_OU8 = arg }, fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg }, @@ -171,6 +180,7 @@ fn Mock() type { /// fn getDataElementType(comptime T: type) DataElementType { return switch (T) { + bool => DataElementType.BOOL, u4 => DataElementType.U4, u8 => DataElementType.U8, u16 => DataElementType.U16, @@ -181,6 +191,8 @@ fn Mock() type { extern fn () void => DataElementType.EFN_OVOID, fn () void => DataElementType.FN_OVOID, fn () u16 => DataElementType.FN_OU16, + fn (u8) bool => DataElementType.FN_IU8_OBOOL, + fn (u8) void => DataElementType.FN_IU8_OVOID, fn (u16) void => DataElementType.FN_IU16_OVOID, fn (u16) u8 => DataElementType.FN_IU16_OU8, fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8, @@ -207,6 +219,7 @@ fn Mock() type { /// fn getDataValue(comptime T: type, element: DataElement) T { return switch (T) { + bool => element.BOOL, u4 => element.U4, u8 => element.U8, u16 => element.U16, @@ -217,6 +230,8 @@ fn Mock() type { extern fn () void => element.EFN_OVOID, fn () void => element.FN_OVOID, fn () u16 => element.FN_OU16, + fn (u8) bool => element.FN_IU8_OBOOL, + fn (u8) void => element.FN_IU8_OVOID, fn (u16) void => element.FN_IU16_OVOID, fn (u16) u8 => element.FN_IU16_OU8, fn (u4, u4) u8 => element.FN_IU4_IU4_OU8, diff --git a/test/mock/kernel/pic_mock.zig b/test/mock/kernel/pic_mock.zig new file mode 100644 index 0000000..fee1341 --- /dev/null +++ b/test/mock/kernel/pic_mock.zig @@ -0,0 +1,92 @@ +const mock_framework = @import("mock_framework.zig"); +pub const initTest = mock_framework.initTest; +pub const freeTest = mock_framework.freeTest; +pub const addTestParams = mock_framework.addTestParams; +pub const addConsumeFunction = mock_framework.addConsumeFunction; +pub const addRepeatFunction = mock_framework.addRepeatFunction; + +const MASTER_COMMAND_REG: u16 = 0x20; +const MASTER_STATUS_REG: u16 = 0x20; +const MASTER_DATA_REG: u16 = 0x21; +const MASTER_INTERRUPT_MASK_REG: u16 = 0x21; +const SLAVE_COMMAND_REG: u16 = 0xA0; +const SLAVE_STATUS_REG: u16 = 0xA0; +const SLAVE_DATA_REG: u16 = 0xA1; +const SLAVE_INTERRUPT_MASK_REG: u16 = 0xA1; + +const ICW1_EXPECT_ICW4: u8 = 0x01; +const ICW1_SINGLE_CASCADE_MODE: u8 = 0x02; +const ICW1_CALL_ADDRESS_INTERVAL_4: u8 = 0x04; +const ICW1_LEVEL_TRIGGER_MODE: u8 = 0x08; +const ICW1_INITIALISATION: u8 = 0x10; + +const ICW2_MASTER_REMAP_OFFSET: u8 = 0x20; +const ICW2_SLAVE_REMAP_OFFSET: u8 = 0x28; + +const ICW3_SLAVE_IRQ_MAP_TO_MASTER: u8 = 0x02; +const ICW3_MASTER_IRQ_MAP_FROM_SLAVE: u8 = 0x04; + +const ICW4_80x86_MODE: u8 = 0x01; +const ICW4_AUTO_END_OF_INTERRUPT: u8 = 0x02; +const ICW4_BUFFER_SELECT: u8 = 0x04; +const ICW4_BUFFER_MODE: u8 = 0x08; +const ICW4_FULLY_NESTED_MODE: u8 = 0x10; + +const OCW1_MASK_IRQ0: u8 = 0x01; +const OCW1_MASK_IRQ1: u8 = 0x02; +const OCW1_MASK_IRQ2: u8 = 0x04; +const OCW1_MASK_IRQ3: u8 = 0x08; +const OCW1_MASK_IRQ4: u8 = 0x10; +const OCW1_MASK_IRQ5: u8 = 0x20; +const OCW1_MASK_IRQ6: u8 = 0x40; +const OCW1_MASK_IRQ7: u8 = 0x80; + +const OCW2_INTERRUPT_LEVEL_1: u8 = 0x01; +const OCW2_INTERRUPT_LEVEL_2: u8 = 0x02; +const OCW2_INTERRUPT_LEVEL_3: u8 = 0x04; +const OCW2_END_OF_INTERRUPT: u8 = 0x20; +const OCW2_SELECTION: u8 = 0x40; +const OCW2_ROTATION: u8 = 0x80; + +const OCW3_READ_IRR: u8 = 0x00; +const OCW3_READ_ISR: u8 = 0x01; +const OCW3_ACT_ON_READ: u8 = 0x02; +const OCW3_POLL_COMMAND_ISSUED: u8 = 0x04; +const OCW3_DEFAULT: u8 = 0x08; +const OCW3_SPECIAL_MASK: u8 = 0x20; +const OCW3_ACK_ON_SPECIAL_MASK: u8 = 0x40; + +pub const IRQ_PIT: u8 = 0x00; +pub const IRQ_KEYBOARD: u8 = 0x01; +pub const IRQ_CASCADE_FOR_SLAVE: u8 = 0x02; +pub const IRQ_SERIAL_PORT_2: u8 = 0x03; +pub const IRQ_SERIAL_PORT_1: u8 = 0x04; +pub const IRQ_PARALLEL_PORT_2: u8 = 0x05; +pub const IRQ_DISKETTE_DRIVE: u8 = 0x06; +pub const IRQ_PARALLEL_PORT_1: u8 = 0x07; +pub const IRQ_REAL_TIME_CLOCK: u8 = 0x08; +pub const IRQ_CGA_VERTICAL_RETRACE: u8 = 0x09; + +pub const IRQ_AUXILIARY_DEVICE: u8 = 0x0C; +pub const IRQ_FLOATING_POINT_UNIT: u8 = 0x0D; +pub const IRQ_HARD_DISK_CONTROLLER: u8 = 0x0E; + +pub fn sendEndOfInterrupt(irq_num: u8) void { + return mock_framework.performAction("sendEndOfInterrupt", void, irq_num); +} + +pub fn spuriousIrq(irq_num: u8) bool { + return mock_framework.performAction("spuriousIrq", bool, irq_num); +} + +pub fn setMask(irq_num: u16) void { + return mock_framework.performAction("setMask", void, irq_num); +} + +pub fn clearMask(irq_num: u16) void { + return mock_framework.performAction("clearMask", void, irq_num); +} + +pub fn remapIrq() void { + return mock_framework.performAction("remapIrq", void); +}