Added unit and runtime tests

Also fixed some typos
Also changed the panic a bit
Removed changed to unnecessary files


Fixed merge


Feedback


Fixed tests
This commit is contained in:
ED 2019-10-05 20:46:31 +01:00
parent d306078c62
commit 42bdbf6b7f
10 changed files with 438 additions and 159 deletions

View file

@ -238,6 +238,7 @@ test "" {
_ = @import("gdt.zig");
_ = @import("idt.zig");
_ = @import("pic.zig");
_ = @import("isr.zig");
_ = @import("syscalls.zig");
_ = @import("paging.zig");
}

View file

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

View file

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

View file

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

View file

@ -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;

View file

@ -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,

View file

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

View file

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

View file

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

View file

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