From 2a0c2e470856ba8fb5f91804a03ac2ab8ed16f57 Mon Sep 17 00:00:00 2001 From: ED Date: Tue, 1 Oct 2019 17:59:42 +0100 Subject: [PATCH] Added unit tests for PIC Removed I/O wait as it isn't needed and uses the syscall interrupt. Added runtime tests Fixed styling for TTY Fixed runtime tests Now they are fixed --- src/kernel/arch/x86/arch.zig | 13 +- src/kernel/arch/x86/gdt.zig | 5 +- src/kernel/arch/x86/idt.zig | 9 +- src/kernel/arch/x86/irq.zig | 7 +- src/kernel/arch/x86/pic.zig | 802 +++++++++++++++++++++++++++----- src/kernel/tty.zig | 188 ++++---- test/kernel/arch/x86/rt-test.py | 6 +- 7 files changed, 791 insertions(+), 239 deletions(-) diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index ac3f501..b2b51d5 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -3,13 +3,13 @@ const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const gdt = @import("gdt.zig"); const idt = @import("idt.zig"); +const pic = @import("pic.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); const pit = @import("pit.zig"); const paging = @import("paging.zig"); const syscalls = @import("syscalls.zig"); const mem = @import("../../mem.zig"); -const log = @import("../../log.zig"); const MemProfile = mem.MemProfile; /// The interrupt context that is given to a interrupt handler. It contains most of the registers @@ -76,15 +76,6 @@ pub fn inb(port: u16) u8 { ); } -/// -/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O -/// event being waited. -/// -pub fn ioWait() void { - // Port 0x80 is free to use - outb(0x80, 0); -} - /// /// Load the GDT and refreshing the code segment with the code segment offset of the kernel as we /// are still in kernel land. Also loads the kernel data segment into all the other segment @@ -230,6 +221,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *Allocator, comptime opti gdt.init(); idt.init(); + pic.init(); isr.init(); irq.init(); @@ -245,6 +237,7 @@ pub fn init(mem_profile: *const MemProfile, allocator: *Allocator, comptime opti test "" { _ = @import("gdt.zig"); _ = @import("idt.zig"); + _ = @import("pic.zig"); _ = @import("syscalls.zig"); _ = @import("paging.zig"); } diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index 01ed3f9..c0b7855 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -5,8 +5,9 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import(build_options.arch_mock_path ++ "arch_mock.zig") else @import("arch.zig"); -const log = if (is_test) @import(build_options.arch_mock_path ++ "log_mock.zig") else @import("../../log.zig"); +const mock_path = build_options.arch_mock_path; +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"); /// The access bits for a GDT entry. const AccessBits = packed struct { diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index cfd2364..03b5990 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -6,9 +6,10 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const gdt = if (is_test) @import(build_options.arch_mock_path ++ "gdt_mock.zig") else @import("gdt.zig"); -const arch = if (is_test) @import(build_options.arch_mock_path ++ "arch_mock.zig") else @import("arch.zig"); -const log = if (is_test) @import(build_options.arch_mock_path ++ "log_mock.zig") else @import("../../log.zig"); +const mock_path = build_options.arch_mock_path; +const gdt = if (is_test) @import(mock_path ++ "gdt_mock.zig") else @import("gdt.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"); /// The structure that contains all the information that each IDT entry needs. const IdtEntry = packed struct { @@ -326,6 +327,7 @@ fn rt_loadedIDTSuccess() void { const loaded_idt = arch.sidt(); expect(idt_ptr.limit == loaded_idt.limit); expect(idt_ptr.base == loaded_idt.base); + log.logInfo("IDT: Tested loading IDT\n"); } /// @@ -333,5 +335,4 @@ fn rt_loadedIDTSuccess() void { /// fn runtimeTests() void { rt_loadedIDTSuccess(); - log.logInfo("IDT: Tested loading IDT\n"); } diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig index 4e81e8e..73a7118 100644 --- a/src/kernel/arch/x86/irq.zig +++ b/src/kernel/arch/x86/irq.zig @@ -79,9 +79,9 @@ fn openIrq(index: u8, handler: idt.InterruptHandler) void { /// mask bit in the PIC so interrupts can happen. /// /// Arguments: -/// IN irq_num: u16 - The IRQ number to register. +/// IN irq_num: u8 - The IRQ number to register. /// -pub fn registerIrq(irq_num: u16, handler: fn (*arch.InterruptContext) void) void { +pub fn registerIrq(irq_num: u8, handler: fn (*arch.InterruptContext) void) void { irq_handlers[irq_num] = handler; pic.clearMask(irq_num); } @@ -103,9 +103,6 @@ pub fn unregisterIrq(irq_num: u16) void { /// the IDT interrupt gates for each IRQ. /// pub fn init() void { - // Remap the PIC IRQ so not to overlap with other exceptions - pic.remapIrq(); - // Open all the IRQ's openIrq(32, irq0); openIrq(33, irq1); diff --git a/src/kernel/arch/x86/pic.zig b/src/kernel/arch/x86/pic.zig index d174918..eac15ae 100644 --- a/src/kernel/arch/x86/pic.zig +++ b/src/kernel/arch/x86/pic.zig @@ -1,192 +1,362 @@ -// Zig version: 0.4.0 +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const build_options = @import("build_options"); +const mock_path = build_options.arch_mock_path; +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 panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic; -const arch = @import("arch.zig"); +// ---------- +// Port address for the PIC master and slave registers. +// ---------- -// Port address for the PIC master and slave registers -const MASTER_COMMAND_REG: u16 = 0x20; // (Write only). -const MASTER_STATUS_REG: u16 = 0x20; // (Read only). -const MASTER_DATA_REG: u16 = 0x21; -const MASTER_INTERRUPT_MASK_REG: u16 = 0x21; -const SLAVE_COMMAND_REG: u16 = 0xA0; // (Write only). -const SLAVE_STATUS_REG: u16 = 0xA0; // (Read only). -const SLAVE_DATA_REG: u16 = 0xA1; -const SLAVE_INTERRUPT_MASK_REG: u16 = 0xA1; +/// The port address for issuing a command to the master PIC. This is a write only operation. +const MASTER_COMMAND_REG: u16 = 0x20; -// Initialisation control word 1. Primary control word for initialising the PIC. -// If set, then the PIC expects to receive a initialisation control word 4. -const ICW1_EXPECT_ICW4: u8 = 0x01; +/// The port address for reading one of the status register of the master PIC. This can be either +/// the In-Service Register (ISR) or the Interrupt Request Register (IRR). This is a read only +/// operation. +const MASTER_STATUS_REG: u16 = 0x20; -// If set, then there is only one PIC in the system. If not set, then PIC is cascaded with slave -// PIC's and initialisation control word 3 must be sent to the controller. -const ICW1_SINGLE_CASCADE_MODE: u8 = 0x02; +/// The port address for reading or writing to the data register of the master PIC. This can be +/// used in conjunction with the command register to set up the PIC. This can also be used to mask +/// the interrupt lines so interrupts can be issued to the CPU. +const MASTER_DATA_REG: u16 = 0x21; -// If set, then the internal CALL address is 4. If not set, then is 8. Usually ignored by x86. -// So default is not set, 0. -const ICW1_CALL_ADDRESS_INTERVAL_4: u8 = 0x04; +/// The port address for issuing a command to the slave PIC. This is a write only operation. +const SLAVE_COMMAND_REG: u16 = 0xA0; -// If set, then operating in level triggered mode. If not set, then operating in edge triggered -// mode. -const ICW1_LEVEL_TRIGGER_MODE: u8 = 0x08; +/// The port address for reading one of the status register of the slave PIC. This can be either +/// the In-Service Register (ISR) or the Interrupt Request Register (IRR). This is a read only +/// operation. +const SLAVE_STATUS_REG: u16 = 0xA0; -// If set, then the PIC is to be initialised. -const ICW1_INITIALISATION: u8 = 0x10; +/// The port address for reading or writing to the data register of the status PIC. This can be +/// used in conjunction with the command register to set up the PIC. This can also be used to mask +/// the interrupt lines so interrupts can be issued to the CPU. +const SLAVE_DATA_REG: u16 = 0xA1; +// ---------- +// Initialisation control word 1. +// ---------- -// Initialisation control word 2. Map the base address of the interrupt vector table. -// The new port map for the master PIC. IRQs 0-7 mapped to use interrupts 0x20-0x27 -const ICW2_MASTER_REMAP_OFFSET: u8 = 0x20; +/// Initialisation control word 1. Primary control word for initialising the PIC. If set, then the +/// PIC expects to receive a initialisation control word 4. +const ICW1_EXPECT_ICW4: u8 = 0x01; -// The new port map for the slave PIC. IRQs 8-15 mapped to use interrupts 0x28-0x36 -const ICW2_SLAVE_REMAP_OFFSET: u8 = 0x28; +/// If set, then there is only one PIC in the system. If not set, then PIC is cascaded with slave +/// PICs and initialisation control word 3 must be sent to the controller. +const ICW1_SINGLE_CASCADE_MODE: u8 = 0x02; +/// If set, then the internal CALL address is 4. If not set, then is 8. Usually ignored by x86. So +/// default is not set, 0. +const ICW1_CALL_ADDRESS_INTERVAL_4: u8 = 0x04; -// Initialisation control word 3. For Telling the master and slave where the cascading -// interrupts are coming from. -// Tell the slave PIT to send interrupts to the master PIC on IRQ2 -const ICW3_SLAVE_IRQ_MAP_TO_MASTER: u8 = 0x02; +/// If set, then operating in level triggered mode. If not set, then operating in edge triggered +/// mode. +const ICW1_LEVEL_TRIGGER_MODE: u8 = 0x08; -// Tell the master PIT to receive interrupts from the slave PIC on IRQ2 -const ICW3_MASTER_IRQ_MAP_FROM_SLAVE: u8 = 0x04; +/// If set, then the PIC is to be initialised. +const ICW1_INITIALISATION: u8 = 0x10; +// ---------- +// Initialisation control word 2. +// ---------- -// Initialisation control word 4. Tell the master and slave what mode to operate in. -// If set, then in 80x86 mode. If not set, then in MCS-80/86 mode -const ICW4_80x86_MODE: u8 = 0x01; +/// Initialisation control word 2. Map the base address of the interrupt vector table. The new port +/// map for the master PIC. IRQs 0-7 mapped to use interrupts 0x20-0x27. +const ICW2_MASTER_REMAP_OFFSET: u8 = 0x20; -// If set, then on last interrupt acknowledge pulse the PIC automatically performs end of -// interrupt operation. -const ICW4_AUTO_END_OF_INTERRUPT: u8 = 0x02; +/// The new port map for the slave PIC. IRQs 8-15 mapped to use interrupts 0x28-0x2F. +const ICW2_SLAVE_REMAP_OFFSET: u8 = 0x28; -// Only use if ICW4_BUFFER_MODE is set. If set, then selects master's buffer. If not set then uses -// slave's buffer. -const ICW4_BUFFER_SELECT: u8 = 0x04; +// ---------- +// Initialisation control word 3. +// ---------- -// If set, then PIC operates in buffered mode. -const ICW4_BUFFER_MODE: u8 = 0x08; +/// Initialisation control word 3. For Telling the master and slave where the cascading. interrupts +/// are coming from. Tell the slave PIT to send interrupts to the master PIC on IRQ2. +const ICW3_SLAVE_IRQ_MAP_TO_MASTER: u8 = 0x02; -// If set, then the the system had many cascaded PIC's. Not supported in x86. -const ICW4_FULLY_NESTED_MODE: u8 = 0x10; +/// Tell the master PIT to receive interrupts from the slave PIC on IRQ2. +const ICW3_MASTER_IRQ_MAP_FROM_SLAVE: u8 = 0x04; +// ---------- +// Initialisation control word 4. +// ---------- -// Operation control word 1. Interrupt masks. -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; +/// Initialisation control word 4. Tell the master and slave what mode to operate in. If set, then +/// in 80x86 mode. If not set, then in MCS-80/86 mode. +const ICW4_80x86_MODE: u8 = 0x01; -// Operation control word 2. Primary commands for the PIC. -// Interrupt level 1 upon which the controller must react. Interrupt level for the current interrupt -const OCW2_INTERRUPT_LEVEL_1: u8 = 0x01; +/// If set, then on last interrupt acknowledge pulse the PIC automatically performs end of +/// interrupt operation. +const ICW4_AUTO_END_OF_INTERRUPT: u8 = 0x02; -// Interrupt level 2 upon which the controller must react. Interrupt level for the current interrupt -const OCW2_INTERRUPT_LEVEL_2: u8 = 0x02; +/// Only use if ICW4_BUFFER_MODE is set. If set, then selects master's buffer. If not set then uses +/// slave's buffer. +const ICW4_BUFFER_SELECT: u8 = 0x04; -// Interrupt level 3 upon which the controller must react. Interrupt level for the current interrupt -const OCW2_INTERRUPT_LEVEL_3: u8 = 0x04; +/// If set, then PIC operates in buffered mode. +const ICW4_BUFFER_MODE: u8 = 0x08; -// The end of interrupt command code. -const OCW2_END_OF_INTERRUPT: u8 = 0x20; +/// If set, then the the system had many cascaded PICs. Not supported in x86. +const ICW4_FULLY_NESTED_MODE: u8 = 0x10; -// Select command. -const OCW2_SELECTION: u8 = 0x40; +// ---------- +// Operation control word 1. +// ---------- -// Rotation command. -const OCW2_ROTATION: u8 = 0x80; +/// Operation control word 1. Interrupt masks for IRQ0 and IRQ8. +const OCW1_MASK_IRQ0_8: u8 = 0x01; +/// Operation control word 1. Interrupt masks for IRQ1 and IRQ9. +const OCW1_MASK_IRQ1_9: u8 = 0x02; +/// Operation control word 1. Interrupt masks for IRQ2 and IRQ10. +const OCW1_MASK_IRQ2_10: u8 = 0x04; + +/// Operation control word 1. Interrupt masks for IRQ3 and IRQ11. +const OCW1_MASK_IRQ3_11: u8 = 0x08; + +/// Operation control word 1. Interrupt masks for IRQ4 and IRQ12. +const OCW1_MASK_IRQ4_12: u8 = 0x10; + +/// Operation control word 1. Interrupt masks for IRQ5 and IRQ13. +const OCW1_MASK_IRQ5_13: u8 = 0x20; + +/// Operation control word 1. Interrupt masks for IRQ6 and IRQ14. +const OCW1_MASK_IRQ6_14: u8 = 0x40; + +/// Operation control word 1. Interrupt masks for IRQ7 and IRQ15. +const OCW1_MASK_IRQ7_15: u8 = 0x80; + +// ---------- +// Operation control word 2. +// ---------- + +/// Operation control word 2. Primary commands for the PIC. Interrupt level 1 upon which the +/// controller must react. Interrupt level for the current interrupt. +const OCW2_INTERRUPT_LEVEL_1: u8 = 0x01; + +/// Interrupt level 2 upon which the controller must react. Interrupt level for the current +/// interrupt +const OCW2_INTERRUPT_LEVEL_2: u8 = 0x02; + +/// Interrupt level 3 upon which the controller must react. Interrupt level for the current +/// interrupt +const OCW2_INTERRUPT_LEVEL_3: u8 = 0x04; + +/// The end of interrupt command code. +const OCW2_END_OF_INTERRUPT: u8 = 0x20; + +/// Select command. +const OCW2_SELECTION: u8 = 0x40; + +/// Rotation command. +const OCW2_ROTATION: u8 = 0x80; + +// ---------- // Operation control word 3. -// Read the Interrupt Request Register register -const OCW3_READ_IRR: u8 = 0x00; +// ---------- -// Read the In Service Register register. -const OCW3_READ_ISR: u8 = 0x01; +/// Operation control word 3. +/// Read the Interrupt Request Register register +const OCW3_READ_IRR: u8 = 0x00; -// If set, then bit 0 will be acted on, so read ISR or IRR. If not set, then no action taken. -const OCW3_ACT_ON_READ: u8 = 0x02; +/// Read the In Service Register register. +const OCW3_READ_ISR: u8 = 0x01; -// If set, then poll command issued. If not set, then no pool command issued. -const OCW3_POLL_COMMAND_ISSUED: u8 = 0x04; +/// If set, then bit 0 will be acted on, so read ISR or IRR. If not set, then no action taken. +const OCW3_ACT_ON_READ: u8 = 0x02; -// This must be set for all OCW 3. -const OCW3_DEFAULT: u8 = 0x08; +/// If set, then poll command issued. If not set, then no pool command issued. +const OCW3_POLL_COMMAND_ISSUED: u8 = 0x04; -// If set, then the special mask is set. If not set, then resets special mask. -const OCW3_SPECIAL_MASK: u8 = 0x20; +/// This must be set for all OCW 3. +const OCW3_DEFAULT: u8 = 0x08; -// If set, then bit 5 will be acted on, so setting the special mask. If not set, then no action it -// taken. -const OCW3_ACK_ON_SPECIAL_MASK: u8 = 0x40; +// Next bit must be zero. +/// If set, then the special mask is set. If not set, then resets special mask. +const OCW3_SPECIAL_MASK: u8 = 0x20; -// IRQ's numbers for the PIC. -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; +/// If set, then bit 5 will be acted on, so setting the special mask. If not set, then no action it +/// taken. +const OCW3_ACK_ON_SPECIAL_MASK: u8 = 0x40; -pub const IRQ_AUXILIARY_DEVICE: u8 = 0x0C; -pub const IRQ_FLOATING_POINT_UNIT: u8 = 0x0D; -pub const IRQ_HARD_DISK_CONTROLLER: u8 = 0x0E; +// Last bit must be zero. +// ---------- +// The IRQs +// ---------- -// Keep track of the number of spurious IRQ's +/// The IRQ for the PIT. +pub const IRQ_PIT: u8 = 0x00; + +/// The IRQ for the keyboard. +pub const IRQ_KEYBOARD: u8 = 0x01; + +/// The IRQ for the cascade from master to slave. +pub const IRQ_CASCADE_FOR_SLAVE: u8 = 0x02; + +/// The IRQ for the serial COM2/4. +pub const IRQ_SERIAL_PORT_2: u8 = 0x03; + +/// The IRQ for the serial COM1/3. +pub const IRQ_SERIAL_PORT_1: u8 = 0x04; + +/// The IRQ for the parallel port 2. +pub const IRQ_PARALLEL_PORT_2: u8 = 0x05; + +/// The IRQ for the floppy disk. +pub const IRQ_DISKETTE_DRIVE: u8 = 0x06; + +/// The IRQ for the parallel port 1. +pub const IRQ_PARALLEL_PORT_1: u8 = 0x07; + +/// The IRQ for the CMOS real time clock (RTC). +pub const IRQ_REAL_TIME_CLOCK: u8 = 0x08; + +/// The IRQ for the CGA vertical retrace. +pub const IRQ_CGA_VERTICAL_RETRACE: u8 = 0x09; + +/// Reserved. +pub const IRQ_RESERVED1: u8 = 0x0A; + +/// Reserved. +pub const IRQ_RESERVED2: u8 = 0x0B; + +// The IRQ for the PS/2 mouse. +pub const IRQ_PS2_MOUSE: u8 = 0x0C; + +/// The IRQ for the floating point unit/co-processor. +pub const IRQ_FLOATING_POINT_UNIT: u8 = 0x0D; + +/// The IRQ for the primary hard drive controller. +pub const IRQ_PRIMARY_HARD_DISK_CONTROLLER: u8 = 0x0E; + +/// The IRQ for the secondary hard drive controller. +pub const IRQ_SECONDARY_HARD_DISK_CONTROLLER: u8 = 0x0F; + +/// Keep track of the number of spurious IRQs. var spurious_irq_counter: u32 = 0; - +/// +/// Send a command to the master PIC. This will send it to the master command port. +/// +/// Arguments: +/// IN cmd: u8 - The command to send. +/// inline fn sendCommandMaster(cmd: u8) void { arch.outb(MASTER_COMMAND_REG, cmd); } +/// +/// Send a command to the salve PIC. This will send it to the salve command port. +/// +/// Arguments: +/// IN cmd: u8 - The command to send. +/// inline fn sendCommandSlave(cmd: u8) void { arch.outb(SLAVE_COMMAND_REG, cmd); } -inline fn sendDataMaster(cmd: u8) void { - arch.outb(MASTER_DATA_REG, cmd); +/// +/// Send data to the master PIC. This will send it to the master data port. +/// +/// Arguments: +/// IN data: u8 - The data to send. +/// +inline fn sendDataMaster(data: u8) void { + arch.outb(MASTER_DATA_REG, data); } -inline fn sendDataSlave(cmd: u8) void { - arch.outb(SLAVE_DATA_REG, cmd); +/// +/// Send data to the salve PIC. This will send it to the salve data port. +/// +/// Arguments: +/// IN data: u8 - The data to send. +/// +inline fn sendDataSlave(data: u8) void { + arch.outb(SLAVE_DATA_REG, data); } +/// +/// Read the data from the master data register. This will read from the master data port. +/// +/// Return: u8 +/// The data that is stored in the master data register. +/// inline fn readDataMaster() u8 { return arch.inb(MASTER_DATA_REG); } +/// +/// Read the data from the salve data register. This will read from the salve data port. +/// +/// Return: u8 +/// The data that is stored in the salve data register. +/// inline fn readDataSlave() u8 { return arch.inb(SLAVE_DATA_REG); } +/// +/// Read the master interrupt request register (IRR). +/// +/// Return: u8 +/// The data that is stored in the master IRR. +/// inline fn readMasterIrr() u8 { - sendCommandSlave(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_IRR); - return arch.inb(SLAVE_STATUS_REG); -} - -inline fn readSlaveIrr() u8 { sendCommandMaster(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_IRR); return arch.inb(MASTER_STATUS_REG); } -inline fn readMasterIsr() u8 { - sendCommandSlave(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_ISR); +/// +/// Read the slave interrupt request register (IRR). +/// +/// Return: u8 +/// The data that is stored in the slave IRR. +/// +inline fn readSlaveIrr() u8 { + sendCommandSlave(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_IRR); return arch.inb(SLAVE_STATUS_REG); } -inline fn readSlaveIsr() u8 { +/// +/// Read the master in-service register (ISR). +/// +/// Return: u8 +/// The data that is stored in the master ISR. +/// +inline fn readMasterIsr() u8 { sendCommandMaster(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_ISR); return arch.inb(MASTER_STATUS_REG); } +/// +/// Read the slave in-service register (ISR). +/// +/// Return: u8 +/// The data that is stored in the slave ISR. +/// +inline fn readSlaveIsr() u8 { + sendCommandSlave(OCW3_DEFAULT | OCW3_ACT_ON_READ | OCW3_READ_ISR); + return arch.inb(SLAVE_STATUS_REG); +} + +/// +/// Send the end of interrupt (EOI) signal to the PIC. If the IRQ was from the master, then will +/// send the EOI to the master only. If the IRQ came from the slave, then will send the EOI to both +/// the slave and master. +/// +/// Arguments: +/// IN irq_num: u8 - The IRQ number to sent the EOI to. +/// pub fn sendEndOfInterrupt(irq_num: u8) void { if (irq_num >= 8) { sendCommandSlave(OCW2_END_OF_INTERRUPT); @@ -195,11 +365,22 @@ pub fn sendEndOfInterrupt(irq_num: u8) void { sendCommandMaster(OCW2_END_OF_INTERRUPT); } +/// +/// Check if the interrupt was a fake interrupt. (In short, this stops a race condition between the +/// CPU and PIC. See https://wiki.osdev.org/PIC#Spurious_IRQs for more details). If this returns +/// true, then the IRQ handler must not send a EOI back. +/// +/// Arguments: +/// IN irq_num: u8 - The IRQ number to check. +/// +/// Return: bool +/// Whether the IRQ provided was spurious. +/// pub fn spuriousIrq(irq_num: u8) bool { // Only for IRQ 7 and 15 - if(irq_num == 7) { + if (irq_num == 7) { // Read master ISR - // Check the MSB is zero, if so, then is a spurious irq + // Check the MSB is zero, if so, then is a spurious IRQ // This is (1 << irq_num) or (1 << 7) to check if it is set for this IRQ if ((readMasterIsr() & 0x80) == 0) { spurious_irq_counter += 1; @@ -219,21 +400,40 @@ pub fn spuriousIrq(irq_num: u8) bool { return false; } -pub fn setMask(irq_num: u16) void { +/// +/// Set the mask bit for the provided IRQ. This will prevent interrupts from triggering for this +/// IRQ. +/// +/// Arguments: +/// IN irq_num: u8 - The IRQ number to mask. +/// +pub fn setMask(irq_num: u8) void { const port: u16 = if (irq_num < 8) MASTER_DATA_REG else SLAVE_DATA_REG; const shift = @intCast(u3, irq_num % 8); const value: u8 = arch.inb(port) | (u8(1) << shift); arch.outb(port, value); } -pub fn clearMask(irq_num: u16) void { +/// +/// Clear the mask bit for the provided IRQ. This will allow interrupts to triggering for this IRQ. +/// +/// Arguments: +/// IN irq_num: u8 - The IRQ number unmask. +/// +pub fn clearMask(irq_num: u8) void { const port: u16 = if (irq_num < 8) MASTER_DATA_REG else SLAVE_DATA_REG; const shift = @intCast(u3, irq_num % 8); const value: u8 = arch.inb(port) & ~(u8(1) << shift); arch.outb(port, value); } -pub fn remapIrq() void { +/// +/// Remap the PIC interrupt lines as initially they conflict with CPU exceptions which are reserved +/// by Intel up to 0x1F. So this will move the IRQs from 0x00-0x0F to 0x20-0x2F. +/// +pub fn init() void { + log.logInfo("Init pic\n"); + // Initiate sendCommandMaster(ICW1_INITIALISATION | ICW1_EXPECT_ICW4); sendCommandSlave(ICW1_INITIALISATION | ICW1_EXPECT_ICW4); @@ -250,7 +450,365 @@ pub fn remapIrq() void { sendDataMaster(ICW4_80x86_MODE); sendDataSlave(ICW4_80x86_MODE); - // Mask - arch.outb(0x21, 0xFF); - arch.outb(0xA1, 0xFF); + // Mask all interrupts + sendDataMaster(u8(0xFF)); + sendDataSlave(u8(0xFF)); + + log.logInfo("Done\n"); + + if (build_options.rt_test) runtimeTests(); +} + +test "sendCommandMaster" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + const cmd = u8(10); + + arch.addTestParams("outb", MASTER_COMMAND_REG, cmd); + + sendCommandMaster(cmd); +} + +test "sendCommandSlave" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + const cmd = u8(10); + + arch.addTestParams("outb", SLAVE_COMMAND_REG, cmd); + + sendCommandSlave(cmd); +} + +test "sendDataMaster" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + const data = u8(10); + + arch.addTestParams("outb", MASTER_DATA_REG, data); + + sendDataMaster(data); +} + +test "sendDataSlave" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + const data = u8(10); + + arch.addTestParams("outb", SLAVE_DATA_REG, data); + + sendDataSlave(data); +} + +test "readDataMaster" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("inb", MASTER_DATA_REG, u8(10)); + + expectEqual(u8(10), readDataMaster()); +} + +test "readDataSlave" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("inb", SLAVE_DATA_REG, u8(10)); + + expectEqual(u8(10), readDataSlave()); +} + +test "readMasterIrr" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", MASTER_COMMAND_REG, u8(0x0A)); + arch.addTestParams("inb", MASTER_STATUS_REG, u8(10)); + + expectEqual(u8(10), readMasterIrr()); +} + +test "readSlaveIrr" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", SLAVE_COMMAND_REG, u8(0x0A)); + arch.addTestParams("inb", SLAVE_STATUS_REG, u8(10)); + + expectEqual(u8(10), readSlaveIrr()); +} + +test "readMasterIsr" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", MASTER_COMMAND_REG, u8(0x0B)); + arch.addTestParams("inb", MASTER_STATUS_REG, u8(10)); + + expectEqual(u8(10), readMasterIsr()); +} + +test "readSlaveIsr" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", SLAVE_COMMAND_REG, u8(0x0B)); + arch.addTestParams("inb", SLAVE_STATUS_REG, u8(10)); + + expectEqual(u8(10), readSlaveIsr()); +} + +test "sendEndOfInterrupt master only" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + var i = u8(0); + while (i < 8) : (i += 1) { + arch.addTestParams("outb", MASTER_COMMAND_REG, OCW2_END_OF_INTERRUPT); + + sendEndOfInterrupt(i); + } +} + +test "sendEndOfInterrupt master and slave" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + var i = u8(8); + while (i < 16) : (i += 1) { + arch.addTestParams("outb", SLAVE_COMMAND_REG, OCW2_END_OF_INTERRUPT); + arch.addTestParams("outb", MASTER_COMMAND_REG, OCW2_END_OF_INTERRUPT); + + sendEndOfInterrupt(i); + } +} + +test "spuriousIrq not spurious IRQ number" { + // Pre testing + expectEqual(u32(0), spurious_irq_counter); + + var i = u8(0); + while (i < 16) : (i += 1) { + if (i != 7 and i != 15) { + expectEqual(false, spuriousIrq(i)); + } + } + + // Post testing + expectEqual(u32(0), spurious_irq_counter); + + // Clean up + spurious_irq_counter = 0; +} + +test "spuriousIrq spurious master IRQ number not spurious" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", MASTER_COMMAND_REG, u8(0x0B)); + // Return 0x80 from readMasterIsr() which will mean this was a real IRQ + arch.addTestParams("inb", MASTER_STATUS_REG, u8(0x80)); + + // Pre testing + expectEqual(u32(0), spurious_irq_counter); + + // Call function + expectEqual(false, spuriousIrq(u8(7))); + + // Post testing + expectEqual(u32(0), spurious_irq_counter); + + // Clean up + spurious_irq_counter = 0; +} + +test "spuriousIrq spurious master IRQ number spurious" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", MASTER_COMMAND_REG, u8(0x0B)); + // Return 0x0 from readMasterIsr() which will mean this was a spurious IRQ + arch.addTestParams("inb", MASTER_STATUS_REG, u8(0x0)); + + // Pre testing + expectEqual(u32(0), spurious_irq_counter); + + // Call function + expectEqual(true, spuriousIrq(u8(7))); + + // Post testing + expectEqual(u32(1), spurious_irq_counter); + + // Clean up + spurious_irq_counter = 0; +} + +test "spuriousIrq spurious slave IRQ number not spurious" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", SLAVE_COMMAND_REG, u8(0x0B)); + // Return 0x80 from readSlaveIsr() which will mean this was a real IRQ + arch.addTestParams("inb", SLAVE_STATUS_REG, u8(0x80)); + + // Pre testing + expectEqual(u32(0), spurious_irq_counter); + + // Call function + expectEqual(false, spuriousIrq(u8(15))); + + // Post testing + expectEqual(u32(0), spurious_irq_counter); + + // Clean up + spurious_irq_counter = 0; +} + +test "spuriousIrq spurious slave IRQ number spurious" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", SLAVE_COMMAND_REG, u8(0x0B)); + // Return 0x0 from readSlaveIsr() which will mean this was a spurious IRQ + arch.addTestParams("inb", SLAVE_STATUS_REG, u8(0x0)); + // A EOI will be sent for a spurious IRQ 15 + arch.addTestParams("outb", MASTER_COMMAND_REG, OCW2_END_OF_INTERRUPT); + + // Pre testing + expectEqual(u32(0), spurious_irq_counter); + + // Call function + expectEqual(true, spuriousIrq(u8(15))); + + // Post testing + expectEqual(u32(1), spurious_irq_counter); + + // Clean up + spurious_irq_counter = 0; +} + +test "setMask master IRQ masked" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + // Going to assume all bits are masked out + arch.addTestParams("inb", MASTER_DATA_REG, u8(0xFF)); + // Expect the 2nd bit to be set + arch.addTestParams("outb", MASTER_DATA_REG, u8(0xFF)); + + setMask(u8(1)); +} + +test "setMask master IRQ unmasked" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + // IRQ already unmasked + arch.addTestParams("inb", MASTER_DATA_REG, u8(0xFD)); + // Expect the 2nd bit to be set + arch.addTestParams("outb", MASTER_DATA_REG, u8(0xFF)); + + setMask(u8(1)); +} + +test "clearMask master IRQ masked" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + // Going to assume all bits are masked out + arch.addTestParams("inb", MASTER_DATA_REG, u8(0xFF)); + // Expect the 2nd bit to be clear + arch.addTestParams("outb", MASTER_DATA_REG, u8(0xFD)); + + clearMask(u8(1)); +} + +test "clearMask master IRQ unmasked" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + // IRQ already unmasked + arch.addTestParams("inb", MASTER_DATA_REG, u8(0xFD)); + // Expect the 2nd bit to still be clear + arch.addTestParams("outb", MASTER_DATA_REG, u8(0xFD)); + + clearMask(u8(1)); +} + +test "init" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + // Just a long list of OUT instructions setting up the PIC + arch.addTestParams( + "outb", + MASTER_COMMAND_REG, + ICW1_INITIALISATION | ICW1_EXPECT_ICW4, + SLAVE_COMMAND_REG, + ICW1_INITIALISATION | ICW1_EXPECT_ICW4, + MASTER_DATA_REG, + ICW2_MASTER_REMAP_OFFSET, + SLAVE_DATA_REG, + ICW2_SLAVE_REMAP_OFFSET, + MASTER_DATA_REG, + ICW3_MASTER_IRQ_MAP_FROM_SLAVE, + SLAVE_DATA_REG, + ICW3_SLAVE_IRQ_MAP_TO_MASTER, + MASTER_DATA_REG, + ICW4_80x86_MODE, + SLAVE_DATA_REG, + ICW4_80x86_MODE, + MASTER_DATA_REG, + u8(0xFF), + SLAVE_DATA_REG, + u8(0xFF), + ); + + init(); +} + +/// +/// Test that all the PIC masks are set so no interrupts can fire. +/// +fn rt_picAllMasked() void { + if (readDataMaster() != 0xFF) { + panic(@errorReturnTrace(), "Master masks are not set, found: {}\n", readDataMaster()); + } + + if (readDataSlave() != 0xFF) { + panic(@errorReturnTrace(), "Slave masks are not set, found: {}\n", readDataSlave()); + } + + log.logInfo("PIC: Tested masking\n"); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_picAllMasked(); } diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 93b46e2..5f338a6 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -633,100 +633,6 @@ pub fn init() void { if (build_options.rt_test) runtimeTests(); } -/// -/// Test the init function set up everything properly. -/// -fn rt_initialisedGlobals() void { - if (@ptrToInt(video_buffer.ptr) != @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000) { - panic(@errorReturnTrace(), "Video buffer not at correct virtual address, found: {}\n", @ptrToInt(video_buffer.ptr)); - } - - if (page_index != 0) { - panic(@errorReturnTrace(), "Page index not at zero, found: {}\n", page_index); - } - - if (colour != vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)) { - panic(@errorReturnTrace(), "Colour not set up properly, found: {}\n", colour); - } - - if (blank != vga.entry(0, colour)) { - panic(@errorReturnTrace(), "Blank not set up properly, found: {}\n", blank); - } - - // Make sure the screen isn't all blank - var all_blank = true; - for (video_buffer) |buf| { - if (buf != blank and buf != 0) { - all_blank = false; - break; - } - } - - if (all_blank) { - panic(@errorReturnTrace(), "Screen all blank, should have logo and page number\n"); - } - - log.logInfo("TTY: Tested globals\n"); -} - -/// -/// Test printing a string will output to the screen. This will check both the video memory and -/// the pages. -/// -fn rt_printString() void { - const text = "abcdefg"; - const clear_text = "\x08" ** text.len; - - print(text); - - // Check the video memory - var counter = u32(0); - for (video_buffer) |buf| { - if (counter < text.len and buf == vga.entry(text[counter], colour)) { - counter += 1; - } else if (counter == text.len) { - // Found all the text - break; - } else { - counter = 0; - } - } - - if (counter != text.len) { - panic(@errorReturnTrace(), "Didn't find the printed text in video memory\n"); - } - - // Check the pages - counter = 0; - for (pages[0]) |c| { - if (counter < text.len and c == vga.entry(text[counter], colour)) { - counter += 1; - } else if (counter == text.len) { - // Found all the text - break; - } else { - counter = 0; - } - } - - if (counter != text.len) { - panic(@errorReturnTrace(), "Didn't find the printed text in pages\n"); - } - - // Clear the text - print(clear_text); - - log.logInfo("TTY: Tested printing\n"); -} - -/// -/// Run all the runtime tests. -/// -fn runtimeTests() void { - rt_initialisedGlobals(); - rt_printString(); -} - const test_colour: u8 = vga.orig_entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK); var test_video_buffer: [VIDEO_BUFFER_SIZE]u16 = [_]u16{0} ** VIDEO_BUFFER_SIZE; @@ -2196,3 +2102,97 @@ test "init not 0,0" { // Tear down resetGlobals(); } + +/// +/// Test the init function set up everything properly. +/// +fn rt_initialisedGlobals() void { + if (@ptrToInt(video_buffer.ptr) != @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000) { + panic(@errorReturnTrace(), "Video buffer not at correct virtual address, found: {}\n", @ptrToInt(video_buffer.ptr)); + } + + if (page_index != 0) { + panic(@errorReturnTrace(), "Page index not at zero, found: {}\n", page_index); + } + + if (colour != vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)) { + panic(@errorReturnTrace(), "Colour not set up properly, found: {}\n", colour); + } + + if (blank != vga.entry(0, colour)) { + panic(@errorReturnTrace(), "Blank not set up properly, found: {}\n", blank); + } + + // Make sure the screen isn't all blank + var all_blank = true; + for (video_buffer) |buf| { + if (buf != blank and buf != 0) { + all_blank = false; + break; + } + } + + if (all_blank) { + panic(@errorReturnTrace(), "Screen all blank, should have logo and page number\n"); + } + + log.logInfo("TTY: Tested globals\n"); +} + +/// +/// Test printing a string will output to the screen. This will check both the video memory and +/// the pages. +/// +fn rt_printString() void { + const text = "abcdefg"; + const clear_text = "\x08" ** text.len; + + print(text); + + // Check the video memory + var counter = u32(0); + for (video_buffer) |buf| { + if (counter < text.len and buf == vga.entry(text[counter], colour)) { + counter += 1; + } else if (counter == text.len) { + // Found all the text + break; + } else { + counter = 0; + } + } + + if (counter != text.len) { + panic(@errorReturnTrace(), "Didn't find the printed text in video memory\n"); + } + + // Check the pages + counter = 0; + for (pages[0]) |c| { + if (counter < text.len and c == vga.entry(text[counter], colour)) { + counter += 1; + } else if (counter == text.len) { + // Found all the text + break; + } else { + counter = 0; + } + } + + if (counter != text.len) { + panic(@errorReturnTrace(), "Didn't find the printed text in pages\n"); + } + + // Clear the text + print(clear_text); + + log.logInfo("TTY: Tested printing\n"); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_initialisedGlobals(); + rt_printString(); +} diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index 37e6770..b07ded9 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -4,7 +4,9 @@ def get_test_cases(TestCase): TestCase("GDT tests", [r"GDT: Tested loading GDT"]), TestCase("IDT init", [r"Init idt", r"Done"]), TestCase("IDT tests", [r"IDT: Tested loading IDT"]), - TestCase("PIT init", [r"Init pit", r".+", "Done"]), - TestCase("Syscalls init", [r"Init syscalls", "Done"]), + TestCase("PIC init", [r"Init pic", r"Done"]), + TestCase("PIC tests", [r"PIC: Tested masking"]), + TestCase("PIT init", [r"Init pit", r".+", r"Done"]), + TestCase("Syscalls init", [r"Init syscalls", r"Done"]), TestCase("Syscall tests", [r"Syscalls: Tested no args", r"Syscalls: Tested 1 arg", r"Syscalls: Tested 2 args", r"Syscalls: Tested 3 args", r"Syscalls: Tested 4 args", r"Syscalls: Tested 5 args"]) ]