From 995268b04e05a55e03a47ec3a03e557653c432d4 Mon Sep 17 00:00:00 2001 From: ED Date: Fri, 31 May 2019 07:41:28 +0100 Subject: [PATCH] Added GDT, IDT, IRQ, updated build.zig Added new build.zig Interrupts no work :( Added isr Interrupts work Added spurious irq Code review comments New name Refactor Build asm --- build.zig | 39 ++++- src/kernel/arch/x86/arch.zig | 125 ++++++++++++++- src/kernel/arch/x86/gdt.zig | 293 ++++++++++++++++++++++++++++++++++ src/kernel/arch/x86/idt.zig | 85 ++++++++++ src/kernel/arch/x86/irq.zig | 155 ++++++++++++++++++ src/kernel/arch/x86/irq_asm.s | 70 ++++++++ src/kernel/arch/x86/isr.zig | 131 +++++++++++++++ src/kernel/arch/x86/isr_asm.s | 89 +++++++++++ src/kernel/kmain.zig | 14 +- src/kernel/panic.zig | 13 ++ src/kernel/tty.zig | 1 + src/kernel/vga.zig | 5 +- 12 files changed, 1009 insertions(+), 11 deletions(-) create mode 100644 src/kernel/arch/x86/gdt.zig create mode 100644 src/kernel/arch/x86/idt.zig create mode 100644 src/kernel/arch/x86/irq.zig create mode 100644 src/kernel/arch/x86/irq_asm.s create mode 100644 src/kernel/arch/x86/isr.zig create mode 100644 src/kernel/arch/x86/isr_asm.s create mode 100644 src/kernel/panic.zig diff --git a/build.zig b/build.zig index 070487a..b0d6110 100644 --- a/build.zig +++ b/build.zig @@ -4,11 +4,13 @@ const Builder = @import("std").build.Builder; const Step = @import("std").build.Step; const builtin = @import("builtin"); const std = @import("std"); +const os = std.os; const ArrayList = std.ArrayList; const warn = std.debug.warn; const mem = std.mem; var src_files: ArrayList([]const u8) = undefined; +var src_files_asm: ArrayList([]const u8) = undefined; fn concat(allocator: *std.mem.Allocator, str: []const u8, str2: []const u8) !std.Buffer { var b = try std.Buffer.init(allocator, str); @@ -18,21 +20,38 @@ fn concat(allocator: *std.mem.Allocator, str: []const u8, str2: []const u8) !std pub fn build(b: *Builder) void { src_files = ArrayList([]const u8).init(b.allocator); + src_files_asm = ArrayList([]const u8).init(b.allocator); const debug = b.option(bool, "debug", "build with debug symbols / make qemu wait for a debug connection") orelse false; var build_path = b.option([]const u8, "build-path", "path to build to") orelse "bin"; var src_path = b.option([]const u8, "source-path", "path to source") orelse "src"; var target = b.option([]const u8, "target", "target to build/run for") orelse "x86"; const builtin_target = if (mem.eql(u8, target, "x86")) builtin.Arch.i386 else unreachable; - const iso_path = concat(b.allocator, build_path, "/pluto.iso") catch unreachable; + b.makePath(build_path) catch unreachable; + var grub_path = concat(b.allocator, build_path, "/iso/boot/grub") catch unreachable; + var kern_path = concat(b.allocator, build_path, "/kernel") catch unreachable; + var a_path = concat(b.allocator, build_path, "/kernel/arch/") catch unreachable; + a_path = concat(b.allocator, a_path.toSlice(), target) catch unreachable; + b.makePath(grub_path.toSlice()) catch unreachable; + b.makePath(kern_path.toSlice()) catch unreachable; + b.makePath(a_path.toSlice()) catch unreachable; src_files.append("kernel/kmain") catch unreachable; - // Add the architecture init file to the source files + // Add the assemblies + switch (builtin_target) { + builtin.Arch.i386 => { + src_files_asm.append("kernel/arch/x86/irq_asm") catch unreachable; + src_files_asm.append("kernel/arch/x86/isr_asm") catch unreachable; + }, + else => {}, + } + var arch_boot = concat(b.allocator, "kernel/arch/", target) catch unreachable; arch_boot.append("/boot") catch unreachable; src_files.append(arch_boot.toSlice()) catch unreachable; + const iso_path = concat(b.allocator, build_path, "/pluto.iso") catch unreachable; var objects_steps = buildObjects(b, builtin_target, build_path, src_path); var link_step = buildLink(b, builtin_target, build_path); const iso_step = buildISO(b, build_path, iso_path.toSlice()); @@ -114,6 +133,13 @@ fn buildLink(b: *Builder, target: builtin.Arch, build_path: []const u8) *Step { file_obj.append(file) catch unreachable; file_obj.append(".o") catch unreachable; exec.addObjectFile(file_obj.toSlice()); + exec.setMainPkgPath("."); + } + for (src_files_asm.toSlice()) |file| { + var file_obj = concat(b.allocator, build_path, "/") catch unreachable; + file_obj.append(file) catch unreachable; + file_obj.append(".o") catch unreachable; + exec.addObjectFile(file_obj.toSlice()); } return &exec.step; } @@ -125,6 +151,15 @@ fn buildObjects(b: *Builder, target: builtin.Arch, build_path: []const u8, src_p var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; file_src.append(".zig") catch unreachable; const obj = b.addObject(file, file_src.toSlice()); + obj.setMainPkgPath("."); + obj.setOutputDir(build_path); + obj.setTarget(target, builtin.Os.freestanding, builtin.Abi.gnu); + objects.append(&obj.step) catch unreachable; + } + for (src_files_asm.toSlice()) |file| { + var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; + file_src.append(".s") catch unreachable; + const obj = b.addAssemble(file, file_src.toSlice()); obj.setOutputDir(build_path); obj.setTarget(target, builtin.Os.freestanding, builtin.Abi.gnu); objects.append(&obj.step) catch unreachable; diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 4adbc85..32c99ff 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -1,11 +1,54 @@ // Zig version: 0.4.0 -const is_test = @import("builtin").is_test; +const builtin = @import("builtin"); +const gdt = @import("gdt.zig"); +const idt = @import("idt.zig"); +const irq = @import("irq.zig"); +const isr = @import("isr.zig"); + +pub const InterruptContext = struct { + // Extra segments + gs: u32, + fs: u32, + es: u32, + ds: u32, + + // Destination, source, base pointer + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + + // General registers + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + + // Interrupt number and error code + int_num: u32, + error_code: u32, + + // Instruction pointer, code segment and flags + eip: u32, + cs: u32, + eflags: u32, + user_esp: u32, + ss: u32, +}; /// /// Initialise the architecture /// -pub fn init() void {} +pub fn init() void { + disableInterrupts(); + + gdt.init(); + idt.init(); + + isr.init(); + irq.init(); +} /// /// Inline assembly to write to a given port with a byte of data. @@ -45,3 +88,81 @@ pub fn inb(port: u16) u8 { pub fn ioWait() void { 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 +/// registers. +/// +/// Arguments: +/// IN gdt_ptr: *gdt.GdtPtr - The address to the GDT. +/// +pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { + // Load the GDT into the CPU + asm volatile ("lgdt (%%eax)" : : [gdt_ptr] "{eax}" (gdt_ptr)); + // Load the kernel data segment, index into the GDT + asm volatile ("mov %%bx, %%ds" : : [KERNEL_DATA_OFFSET] "{bx}" (gdt.KERNEL_DATA_OFFSET)); + asm volatile ("mov %%bx, %%es"); + asm volatile ("mov %%bx, %%fs"); + asm volatile ("mov %%bx, %%gs"); + asm volatile ("mov %%bx, %%ss"); + // Load the kernel code segment into the CS register + asm volatile ( + \\ljmp $0x08, $1f + \\1: + ); +} + +/// +/// Load the TSS into the CPU. +/// +pub fn ltr() void { + asm volatile ("ltr %%ax" : : [TSS_OFFSET] "{ax}" (gdt.TSS_OFFSET)); +} + +/// Load the IDT into the CPU. +pub fn lidt(idt_ptr: *const idt.IdtPtr) void { + asm volatile ("lidt (%%eax)" : : [idt_ptr] "{eax}" (idt_ptr)); +} + +/// +/// Enable interrupts +/// +pub fn enableInterrupts() void { + asm volatile ("sti"); +} + +/// +/// Disable interrupts +/// +pub fn disableInterrupts() void { + asm volatile ("cli"); +} + +/// +/// Halt the CPU, but interrupts will still be called +/// +pub fn halt() void { + asm volatile ("hlt"); +} + +/// +/// Wait the kernel but still can handle interrupts. +/// +pub fn spinWait() noreturn { + while (true) { + enableInterrupts(); + halt(); + disableInterrupts(); + } +} + +/// +/// Halt the kernel. +/// +pub fn haltNoInterrupts() noreturn { + while (true) { + disableInterrupts(); + halt(); + } +} \ No newline at end of file diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig new file mode 100644 index 0000000..c0ed63d --- /dev/null +++ b/src/kernel/arch/x86/gdt.zig @@ -0,0 +1,293 @@ +// Zig version: 0.4.0 + +const arch = @import("arch.zig"); + +const NUMBER_OF_ENTRIES: u16 = 0x06; +const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1; + +// The indexes into the GDT where each segment resides. + +/// The index of the NULL GDT entry. +const NULL_INDEX: u16 = 0x00; + +/// The index of the kernel code GDT entry. +const KERNEL_CODE_INDEX: u16 = 0x01; + +/// The index of the kernel data GDT entry. +const KERNEL_DATA_INDEX: u16 = 0x02; + +/// The index of the user code GDT entry. +const USER_CODE_INDEX: u16 = 0x03; + +/// The index of the user data GDT entry. +const USER_DATA_INDEX: u16 = 0x04; + +/// The index of the task state segment GDT entry. +const TSS_INDEX: u16 = 0x05; + + +// The offsets into the GDT where each segment resides. + +/// The offset of the NULL GDT entry. +pub const NULL_OFFSET: u16 = 0x00; + +/// The offset of the kernel code GDT entry. +pub const KERNEL_CODE_OFFSET: u16 = 0x08; + +/// The offset of the kernel data GDT entry. +pub const KERNEL_DATA_OFFSET: u16 = 0x10; + +/// The offset of the user code GDT entry. +pub const USER_CODE_OFFSET: u16 = 0x18; + +/// The offset of the user data GDT entry. +pub const USER_DATA_OFFSET: u16 = 0x20; + +/// The offset of the TTS GDT entry. +pub const TSS_OFFSET: u16 = 0x28; + +// The access bits +const ACCESSED_BIT = 0x01; // 00000001 +const WRITABLE_BIT = 0x02; // 00000010 +const DIRECTION_CONFORMING_BIT = 0x04; // 00000100 +const EXECUTABLE_BIT = 0x08; // 00001000 +const DESCRIPTOR_BIT = 0x10; // 00010000 + +const PRIVILEGE_RING_0 = 0x00; // 00000000 +const PRIVILEGE_RING_1 = 0x20; // 00100000 +const PRIVILEGE_RING_2 = 0x40; // 01000000 +const PRIVILEGE_RING_3 = 0x60; // 01100000 + +const PRESENT_BIT = 0x80; // 10000000 + + +const KERNEL_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_0 | DESCRIPTOR_BIT; +const USER_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_3 | DESCRIPTOR_BIT; + +const CODE_SEGMENT = EXECUTABLE_BIT | WRITABLE_BIT; +const DATA_SEGMENT = WRITABLE_BIT; + +const TSS_SEGMENT = PRESENT_BIT | EXECUTABLE_BIT | ACCESSED_BIT; + + +// The flag bits +const IS_64_BIT = 0x02; // 0010 +const IS_32_BIT = 0x04; // 0100 +const IS_LIMIT_4K_BIT = 0x08; // 1000 + +/// The structure that contains all the information that each GDT entry needs. +const GdtEntry = packed struct { + /// The lower 16 bits of the limit address. Describes the size of memory that can be addressed. + limit_low: u16, + + /// The lower 24 bits of the base address. Describes the start of memory for the entry. + base_low: u24, + + /// Bit 0 : accessed - The CPU will set this when the GDT entry is accessed. + /// Bit 1 : writable - The writable bit to say if the memory region is writable. If set, then memory region is readable and writable. If not set, then the memory region is just readable. + /// Bit 2 : direction_conforming - For a code segment: if set (1), then the code segment can be executed from a lower ring level. If unset (0), then the code segment can only be executed from the same ring level in the privilege flag. For the data segment: if set (1), then the data segment grows downwards. If unset (0), then the data segment grows upwards. + /// Bit 3 : executable - The execution bit to say that the memory region is executable. + /// Bit 4 : descriptor_bit - The descriptor bit. + /// Bit 5-6 : privilege - The ring level of the memory region. + /// Bit 7 : present - The present bit to tell that this GDT entry is present. + access: u8, + + /// The upper 4 bits of the limit address. Describes the size of memory that can be addressed. + limit_high: u4, + + /// Bit 0 : reserved_zero - This must always be zero. + /// Bit 1 : is_64bits - Whether this is a 64 bit system. + /// Bit 2 : is_32bits - Whether this is a 32 bit system. + /// Bit 3 : is_limit_4K - Whether paging is turned on, and each address is addressed as if it is a page number not physical/logical linear address. + flags: u4, + + /// The upper 8 bits of the base address. Describes the start of memory for the entry. + base_high: u8, +}; + +/// The GDT pointer structure that contains the pointer to the beginning of the GDT and the number +/// of the table (minus 1). Used to load the GDT with LGDT instruction. +pub const GdtPtr = packed struct { + /// 16bit entry for the number of entries (minus 1). + limit: u16, + + /// 32bit entry for the base address for the GDT. + base: *GdtEntry, +}; + +const TtsEntry = packed struct { + /// Pointer to the previous TSS entry + prev_tss: u32, + + /// Ring 0 32 bit stack pointer. + esp0: u32, + + /// Ring 0 32 bit stack pointer. + ss0: u32, + + /// Ring 1 32 bit stack pointer. + esp1: u32, + + /// Ring 1 32 bit stack pointer. + ss1: u32, + + /// Ring 2 32 bit stack pointer. + esp2: u32, + + /// Ring 2 32 bit stack pointer. + ss2: u32, + + /// The CR3 control register 3. + cr3: u32, + + /// 32 bit instruction pointer. + eip: u32, + + /// 32 bit flags register. + eflags: u32, + + /// 32 bit accumulator register. + eax: u32, + + /// 32 bit counter register. + ecx: u32, + + /// 32 bit data register. + edx: u32, + + /// 32 bit base register. + ebx: u32, + + /// 32 bit stack pointer register. + esp: u32, + + /// 32 bit base pointer register. + ebp: u32, + + /// 32 bit source register. + esi: u32, + + /// 32 bit destination register. + edi: u32, + + /// The extra segment. + es: u32, + + /// The code segment. + cs: u32, + + /// The stack segment. + ss: u32, + + /// The data segment. + ds: u32, + + /// A extra segment FS. + fs: u32, + + /// A extra segment GS. + gs: u32, + + /// The local descriptor table register. + ldtr: u32, + + /// ? + trap: u16, + + /// A pointer to a I/O port bitmap for the current task which specifies individual ports the program should have access to. + io_permissions_base_offset: u16, +}; + +/// +/// Make a GDT entry. +/// +/// Arguments: +/// IN access: u8 - The access bits for the descriptor. +/// IN flags: u4 - The flag bits for the descriptor. +/// +/// Return: +/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and limit at 0xFFFFF. +/// +fn makeEntry(base: u32, limit: u20, access: u8, flags: u4) GdtEntry { + return GdtEntry { + .limit_low = @truncate(u16, limit), + .base_low = @truncate(u24, base), + .access = access, + .limit_high = @truncate(u4, limit >> 16), + .flags = flags, + .base_high = @truncate(u8, base >> 24), + }; +} + +/// The GDT entry table of NUMBER_OF_ENTRIES entries. +var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = []GdtEntry { + // Null descriptor + makeEntry(0, 0, 0, 0), + + // Kernel Code + makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + + // Kernel Data + makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + + // User Code + makeEntry(0, 0xFFFFF, USER_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + + // User Data + makeEntry(0, 0xFFFFF, USER_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + + // Fill in TSS at runtime + makeEntry(0, 0, 0, 0), +}; + +/// The GDT pointer that the CPU is loaded with that contains the base address of the GDT and the size. +const gdt_ptr: GdtPtr = GdtPtr { + .limit = TABLE_SIZE, + .base = &gdt_entries[0], +}; + +/// The task state segment entry. +var tss: TtsEntry = TtsEntry { + .prev_tss = 0, + .esp0 = undefined, + .ss0 = KERNEL_DATA_OFFSET, + .esp1 = 0, + .ss1 = 0, + .esp2 = 0, + .ss2 = 0, + .cr3 = 0, + .eip = 0, + .eflags = 0, + .eax = 0, + .ecx = 0, + .edx = 0, + .ebx = 0, + .esp = 0, + .ebp = 0, + .esi = 0, + .edi = 0, + .es = 0, + .cs = 0, + .ss = 0, + .ds = 0, + .fs = 0, + .gs = 0, + .ldtr = 0, + .trap = 0, + .io_permissions_base_offset = @sizeOf(TtsEntry), +}; + +pub fn setTssStack(esp0: u32) void { + tss.esp0 = esp0; +} + +pub fn init() void { + // Initiate TSS + gdt_entries[TSS_INDEX] = makeEntry(@ptrToInt(&tss), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, 0); + + // Load the GDT + arch.lgdt(&gdt_ptr); + + // Load the TSS + arch.ltr(); +} diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig new file mode 100644 index 0000000..1645238 --- /dev/null +++ b/src/kernel/arch/x86/idt.zig @@ -0,0 +1,85 @@ +// Zig version: 0.4.0 + +const gdt = @import("gdt.zig"); +const arch = @import("arch.zig"); + +const NUMBER_OF_ENTRIES: u16 = 256; +const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1; + +// The different gate types +const TASK_GATE_32BIT: u4 = 0x5; +const INTERRUPT_GATE_16BIT: u4 = 0x6; +const TRAP_GATE_16BIT: u4 = 0x7; +const INTERRUPT_GATE_32BIT: u4 = 0xE; +const TRAP_GATE_32BIT: u4 = 0xF; + +const PRIVILEGE_RING_0: u2 = 0x0; +const PRIVILEGE_RING_1: u2 = 0x1; +const PRIVILEGE_RING_2: u2 = 0x2; +const PRIVILEGE_RING_3: u2 = 0x3; + +const IdtEntry = packed struct { + /// The lower 16 bits of the base address of the interrupt handler offset. + base_low: u16, + + /// The code segment in the GDT which the handlers will be held. + selector: u16, + + /// Must be zero, unused. + zero: u8, + + /// The IDT gate type. + gate_type: u4, + + /// Must be 0 for interrupt and trap gates. + storage_segment: u1, + + /// The minimum ring level that the calling code must have to run the handler. So user code may not be able to run some interrupts. + privilege: u2, + + /// Whether the IDT entry is present. + present: u1, + + /// The upper 16 bits of the base address of the interrupt handler offset. + base_high: u16, +}; + +pub const IdtPtr = packed struct { + /// The total size of the IDT (minus 1) in bytes. + limit: u16, + + /// The base address where the IDT is located. + base: *IdtEntry, +}; + +var idt: [NUMBER_OF_ENTRIES]IdtEntry = []IdtEntry{makeEntry(0, 0, 0, 0, 0)} ** NUMBER_OF_ENTRIES; + +const idt_ptr: IdtPtr = IdtPtr { + .limit = TABLE_SIZE, + .base = &idt[0], +}; + +fn makeEntry(base: u32, selector: u16, gate_type: u4, privilege: u2, present: u1) IdtEntry { + return IdtEntry { + .base_low = @truncate(u16, base), + .selector = selector, + .zero = 0, + .gate_type = gate_type, + .storage_segment = 0, + .privilege = privilege, + .present = present, + .base_high = @truncate(u16, base >> 16), + }; +} + +pub fn openInterruptGate(index: u8, base: extern fn()void) void { + idt[index] = makeEntry(@ptrToInt(base), gdt.KERNEL_CODE_OFFSET, INTERRUPT_GATE_32BIT, PRIVILEGE_RING_0, 1); +} + +pub fn closeInterruptGate(index: u8) void { + idt[index] = makeEntry(0, 0, 0, 0, 0); +} + +pub fn init() void { + arch.lidt(&idt_ptr); +} \ No newline at end of file diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig new file mode 100644 index 0000000..3b95505 --- /dev/null +++ b/src/kernel/arch/x86/irq.zig @@ -0,0 +1,155 @@ +// Zig version: 0.4.0 + +const panic = @import("../../panic.zig"); +const tty = @import("../../tty.zig"); +const idt = @import("idt.zig"); +const arch = @import("arch.zig"); + +const NUMBER_OF_ENTRIES: u16 = 16; + +const IRQ_OFFSET: u16 = 32; + +extern fn irq0() void; +extern fn irq1() void; +extern fn irq2() void; +extern fn irq3() void; +extern fn irq4() void; +extern fn irq5() void; +extern fn irq6() void; +extern fn irq7() void; +extern fn irq8() void; +extern fn irq9() void; +extern fn irq10() void; +extern fn irq11() void; +extern fn irq12() void; +extern fn irq13() void; +extern fn irq14() void; +extern fn irq15() void; + +// Keep track of the number of spurious irq's +var spurious_irq_counter: u32 = 0; + +var irq_handlers: [NUMBER_OF_ENTRIES]fn(*arch.InterruptContext)void = []fn(*arch.InterruptContext)void {unhandled} ** NUMBER_OF_ENTRIES; + +fn unhandled(context: *arch.InterruptContext) void { + const interrupt_num: u8 = @truncate(u8, context.int_num - IRQ_OFFSET); + panic.panicFmt(null, "Unhandled IRQ number {}", interrupt_num); +} + +// TODO Move to PIC +fn sendEndOfInterrupt(irq_num: u8) void { + if (irq_num >= 8) { + arch.outb(0xA0, 0x20); + } + + arch.outb(0x20, 0x20); +} + +// TODO Move to PIC +fn spuriousIrq(irq_num: u8) bool { + // Only for IRQ 7 and 15 + if(irq_num == 7) { + // Read master ISR + arch.outb(0x20, 0x0B); + const in_service = arch.inb(0x21); + // Check the MSB is zero, if so, then is a spurious irq + if ((in_service & 0x80) == 0) { + spurious_irq_counter += 1; + return true; + } + } else if (irq_num == 15) { + // Read slave ISR + arch.outb(0xA0, 0x0B); + const in_service = arch.inb(0xA1); + // Check the MSB is zero, if so, then is a spurious irq + if ((in_service & 0x80) == 0) { + // Need to send EOI to the master + arch.outb(0x20, 0x20); + spurious_irq_counter += 1; + return true; + } + } + + return false; +} + +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 (!spuriousIrq(irq_num)) { + irq_handlers[irq_num](context); + // Send the end of interrupt command + sendEndOfInterrupt(irq_num); + } +} + +pub fn registerIrq(irq_num: u16, handler: fn(*arch.InterruptContext)void) void { + irq_handlers[irq_num] = handler; + clearMask(irq_num); +} + +pub fn unregisterIrq(irq_num: u16) void { + irq_handlers[irq_num] = unhandled; + setMask(irq_num); +} + +pub fn setMask(irq_num: u16) void { + // TODO Change to PIC constants + const port: u16 = if (irq_num < 8) 0x20 else 0xA0; + const value = arch.inb(port) | (1 << irq_num); + arch.outb(port, value); +} + +pub fn clearMask(irq_num: u16) void { + // TODO Change to PIC constants + const port: u16 = if (irq_num < 8) 0x20 else 0xA0; + const value = arch.inb(port) & ~(1 << irq_num); + arch.outb(port, value); +} + +// TODO Move this to the PIC once got one +fn remapIrq() void { + // Initiate + arch.outb(0x20, 0x11); + arch.outb(0xA0, 0x11); + + // Offsets + arch.outb(0x21, IRQ_OFFSET); + arch.outb(0xA1, IRQ_OFFSET + 8); + + // IRQ lines + arch.outb(0x21, 0x04); + arch.outb(0xA1, 0x02); + + // 80x86 mode + arch.outb(0x21, 0x01); + arch.outb(0xA1, 0x01); + + // Mask + arch.outb(0x21, 0xFF); + arch.outb(0xA1, 0xFF); + +} + +pub fn init() void { + // Remap the PIC IRQ so not to overlap with other exceptions + remapIrq(); + + // Open all the IRQ's + idt.openInterruptGate(32, irq0); + idt.openInterruptGate(33, irq1); + idt.openInterruptGate(34, irq2); + idt.openInterruptGate(35, irq3); + idt.openInterruptGate(36, irq4); + idt.openInterruptGate(37, irq5); + idt.openInterruptGate(38, irq6); + idt.openInterruptGate(39, irq7); + idt.openInterruptGate(40, irq8); + idt.openInterruptGate(41, irq9); + idt.openInterruptGate(42, irq10); + idt.openInterruptGate(43, irq11); + idt.openInterruptGate(44, irq12); + idt.openInterruptGate(45, irq13); + idt.openInterruptGate(46, irq14); + idt.openInterruptGate(47, irq15); +} \ No newline at end of file diff --git a/src/kernel/arch/x86/irq_asm.s b/src/kernel/arch/x86/irq_asm.s new file mode 100644 index 0000000..3b6db39 --- /dev/null +++ b/src/kernel/arch/x86/irq_asm.s @@ -0,0 +1,70 @@ +.macro irqGenerator n + .align 4 + .type irq\n, @function + .global irq\n + irq\n: + cli + push $0 + push $\n+32 + jmp irqCommonStub +.endmacro + +irqCommonStub: + // Push all the registers + pusha + + // Push the additional segment regiters + push %ds + push %es + push %fs + push %gs + + // Set the kernel data segment + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + // Push the stack, this is where all the registers are sported, points the interuptContect + mov %esp, %eax + push %eax + + // Call the handler + call irqHandler + + // Pop stack pointer to point to the registers pushed + pop %eax + + // Pop segment regiters inorder + pop %gs + pop %fs + pop %es + pop %ds + + // Pop all general registers + popa + + // Pop the error code and interrupt number + add $0x8, %esp + + // Pops 5 things at once: cs, eip, eflags, ss, and esp + iret +.type irqCommonStub, @function + +irqGenerator 0 +irqGenerator 1 +irqGenerator 2 +irqGenerator 3 +irqGenerator 4 +irqGenerator 5 +irqGenerator 6 +irqGenerator 7 +irqGenerator 8 +irqGenerator 9 +irqGenerator 10 +irqGenerator 11 +irqGenerator 12 +irqGenerator 13 +irqGenerator 14 +irqGenerator 15 diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig new file mode 100644 index 0000000..caf3b6f --- /dev/null +++ b/src/kernel/arch/x86/isr.zig @@ -0,0 +1,131 @@ +// Zig version: 0.4.0 + +const panic = @import("../../panic.zig"); +const tty = @import("../../tty.zig"); +const idt = @import("idt.zig"); +const arch = @import("arch.zig"); + +const NUMBER_OF_ENTRIES: u16 = 32; + +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; + +const exception_msg: [NUMBER_OF_ENTRIES][]const u8 = [NUMBER_OF_ENTRIES][]const u8 { + "Divide By Zero", + "Single Step (Debugger)", + "Non Maskable Interrupt", + "Breakpoint (Debugger)", + "Overflow", + "Bound Range Exceeded", + "Invalid Opcode", + "No Coprocessor, Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid Task State Segment (TSS)", + "Segment Not Present", + "Stack Segment Overrun", + "General Protection Fault", + "Page Fault", + "Unknown Interrupt", + "x87 FPU Floating Point Error", + "Alignment Check", + "Machine Check", + "SIMD Floating Point", + "Virtualization", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Security", + "Reserved" +}; + +var isr_handlers: [NUMBER_OF_ENTRIES]fn(*arch.InterruptContext)void = []fn(*arch.InterruptContext)void{unhandled} ** NUMBER_OF_ENTRIES; + +fn unhandled(context: *arch.InterruptContext) void { + const interrupt_num = context.int_num; + panic.panicFmt(null, "Unhandled exception: {}, number {}", exception_msg[interrupt_num], interrupt_num); +} + +export fn isrHandler(context: *arch.InterruptContext) void { + const isr_num = context.int_num; + isr_handlers[isr_num](context); +} + +pub fn registerIsr(isr_num: u16, handler: fn(*arch.InterruptContext)void) void { + isr_handlers[isr_num] = handler; +} + +pub fn unregisterIsr(isr_num: u16) void { + isr_handlers[isr_num] = unhandled; +} + +pub fn init() void { + idt.openInterruptGate(0, isr0); + idt.openInterruptGate(1, isr1); + idt.openInterruptGate(2, isr2); + idt.openInterruptGate(3, isr3); + idt.openInterruptGate(4, isr4); + idt.openInterruptGate(5, isr5); + idt.openInterruptGate(6, isr6); + idt.openInterruptGate(7, isr7); + idt.openInterruptGate(8, isr8); + idt.openInterruptGate(9, isr9); + idt.openInterruptGate(10, isr10); + idt.openInterruptGate(11, isr11); + idt.openInterruptGate(12, isr12); + idt.openInterruptGate(13, isr13); + idt.openInterruptGate(14, isr14); + idt.openInterruptGate(15, isr15); + idt.openInterruptGate(16, isr16); + idt.openInterruptGate(17, isr17); + idt.openInterruptGate(18, isr18); + idt.openInterruptGate(19, isr19); + idt.openInterruptGate(20, isr20); + idt.openInterruptGate(21, isr21); + idt.openInterruptGate(22, isr22); + idt.openInterruptGate(23, isr23); + idt.openInterruptGate(24, isr24); + idt.openInterruptGate(25, isr25); + idt.openInterruptGate(26, isr26); + idt.openInterruptGate(27, isr27); + idt.openInterruptGate(28, isr28); + idt.openInterruptGate(29, isr29); + idt.openInterruptGate(30, isr30); + idt.openInterruptGate(31, isr31); +} \ No newline at end of file diff --git a/src/kernel/arch/x86/isr_asm.s b/src/kernel/arch/x86/isr_asm.s new file mode 100644 index 0000000..74f645b --- /dev/null +++ b/src/kernel/arch/x86/isr_asm.s @@ -0,0 +1,89 @@ +.macro isrGenerator n + .align 4 + .type isr\n, @function + .global isr\n + isr\n: + cli + // Push 0 if there is no interrupt error code + .if (\n != 8 && !(\n >= 10 && \n <= 14) && \n != 17) + push $0 + .endif + push $\n + jmp isrCommonStub +.endmacro + +isrCommonStub: + // Push all the registers + pusha + + // Push the additional segment regiters + push %ds + push %es + push %fs + push %gs + + // Set the kernel data segment + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + // Push the stack, this is where all the registers are sported, points the interuptContect + mov %esp, %eax + push %eax + + // Call the handler + call isrHandler + + // Pop stack pointer to point to the registers pushed + pop %eax + + // Pop segment regiters inorder + pop %gs + pop %fs + pop %es + pop %ds + + // Pop all general registers + popa + + // Pop the error code and interrupt number + add $0x8, %esp + + // Pops 5 things at once: cs, eip, eflags, ss, and esp + iret +.type isrCommonStub, @function + +isrGenerator 0 +isrGenerator 1 +isrGenerator 2 +isrGenerator 3 +isrGenerator 4 +isrGenerator 5 +isrGenerator 6 +isrGenerator 7 +isrGenerator 8 +isrGenerator 9 +isrGenerator 10 +isrGenerator 11 +isrGenerator 12 +isrGenerator 13 +isrGenerator 14 +isrGenerator 15 +isrGenerator 16 +isrGenerator 17 +isrGenerator 18 +isrGenerator 19 +isrGenerator 20 +isrGenerator 21 +isrGenerator 22 +isrGenerator 23 +isrGenerator 24 +isrGenerator 25 +isrGenerator 26 +isrGenerator 27 +isrGenerator 28 +isrGenerator 29 +isrGenerator 30 +isrGenerator 31 diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index ffd88ab..89040c3 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -6,10 +6,15 @@ const multiboot = @import("multiboot.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); +// 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"); + +// 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); - tty.print("\nKERNEL PANIC: {}\n", msg); - while (true) {} + arch.disableInterrupts(); + panic_root.panicFmt(error_return_trace, "{}", msg); } pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { @@ -18,6 +23,9 @@ pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { arch.init(); vga.init(); tty.init(); - tty.print("\nHello Pluto from kernel :)\n"); + tty.print("Hello Pluto from kernel :)\n"); + + // Enable interrupts + arch.enableInterrupts(); } } diff --git a/src/kernel/panic.zig b/src/kernel/panic.zig new file mode 100644 index 0000000..5dcd7e9 --- /dev/null +++ b/src/kernel/panic.zig @@ -0,0 +1,13 @@ +// Zig version: 0.4.0 + +const builtin = @import("builtin"); +const tty = @import("tty.zig"); +const arch = if (builtin.is_test) @import("../../test/kernel/arch_mock.zig") else @import("arch.zig").internals; + +pub fn panicFmt(trace: ?*builtin.StackTrace, comptime format: []const u8, args: ...) noreturn { + @setCold(true); + tty.print("KERNEL PANIC\n"); + tty.print(format, args); + tty.print("\nHALTING\n"); + arch.haltNoInterrupts(); +} diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 9e879ed..0d52837 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -641,6 +641,7 @@ pub fn init() void { printLogo(); displayPageNumber(); + updateCursor(); } fn resetGlobals() void { diff --git a/src/kernel/vga.zig b/src/kernel/vga.zig index 05ba4b1..c2c9d4d 100644 --- a/src/kernel/vga.zig +++ b/src/kernel/vga.zig @@ -14,7 +14,6 @@ const PORT_DATA: u16 = 0x03D5; // The indexes that is passed to the address port to select the register for the data to be // read or written to. - const REG_HORIZONTAL_TOTAL: u8 = 0x00; const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; @@ -144,7 +143,7 @@ pub fn updateCursor(x: u16, y: u16) void { var pos_lower: u16 = undefined; // Make sure new cursor position is within the screen - if (x < HEIGHT and y < WIDTH) { + if (x < WIDTH and y < HEIGHT) { pos = y * WIDTH + x; } else { // If not within the screen, then just put the cursor at the very end @@ -240,8 +239,6 @@ pub fn init() void { // Set by default the underline cursor setCursorShape(CursorShape.UNDERLINE); - cursor_scanline_start = CURSOR_SCANLINE_MIDDLE; - cursor_scanline_end = CURSOR_SCANLINE_END; } test "entryColour" {