From 14ff4db74a92a30b96c129d3122f0b37755a6a4d Mon Sep 17 00:00:00 2001 From: Jack Halford Date: Sat, 11 May 2019 01:11:34 +0200 Subject: [PATCH] first commit boot ok vga ok console soon interrupt mapping ok isr triggers ok but doesn't iret still lots to port from rust --- .gitignore | 1 + README.md | 25 ++++ build.zig | 18 +++ run.sh | 38 ++++++ src/arch/x86/gdt.s | 23 ++++ src/arch/x86/gdt.zig | 133 ++++++++++++++++++++ src/arch/x86/idt.zig | 58 +++++++++ src/arch/x86/interrupt.zig | 188 +++++++++++++++++++++++++++ src/arch/x86/isr.s | 102 +++++++++++++++ src/arch/x86/isr.zig | 158 +++++++++++++++++++++++ src/arch/x86/lib/index.zig | 2 + src/arch/x86/lib/instructions.zig | 39 ++++++ src/arch/x86/lib/io.zig | 30 +++++ src/arch/x86/linker.ld | 80 ++++++++++++ src/arch/x86/main.zig | 13 ++ src/console.zig | 15 +++ src/index.zig | 2 + src/main.zig | 9 ++ src/multiboot.zig | 128 +++++++++++++++++++ src/vga.zig | 203 ++++++++++++++++++++++++++++++ 20 files changed, 1265 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100755 run.sh create mode 100644 src/arch/x86/gdt.s create mode 100644 src/arch/x86/gdt.zig create mode 100644 src/arch/x86/idt.zig create mode 100644 src/arch/x86/interrupt.zig create mode 100644 src/arch/x86/isr.s create mode 100644 src/arch/x86/isr.zig create mode 100644 src/arch/x86/lib/index.zig create mode 100644 src/arch/x86/lib/instructions.zig create mode 100644 src/arch/x86/lib/io.zig create mode 100644 src/arch/x86/linker.ld create mode 100644 src/arch/x86/main.zig create mode 100644 src/console.zig create mode 100644 src/index.zig create mode 100644 src/main.zig create mode 100644 src/multiboot.zig create mode 100644 src/vga.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2040c29 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +zig-cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..956768a --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## hobby kernel in zig + +slowly porting from rust. + +### features + + - vga frame buffer + - interrupts + - todo: terminal console + - todo: memory mapping + - todo: cfs scheduler + +### dependencies + +`ziglang` compiler + +### compile + +`zig build` compile and links a multiboot kernel, without a bootloader. + +### test + +`./run.sh qemu start` +`./run.sh qemu monitor` +`./run.sh gdb` diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..20d0a33 --- /dev/null +++ b/build.zig @@ -0,0 +1,18 @@ +const Builder = @import("std").build.Builder; +const builtin = @import("builtin"); + +pub fn build(b: *Builder) void { + const kernel = b.addExecutable("bzImage", "src/arch/x86/main.zig"); + kernel.addPackagePath("kernel", "src/index.zig"); + kernel.addPackagePath("arch", "src/arch/x86/lib/index.zig"); + kernel.setOutputDir("build"); + + kernel.addAssemblyFile("src/arch/x86/gdt.s"); + kernel.addAssemblyFile("src/arch/x86/isr.s"); + + kernel.setBuildMode(b.standardReleaseOptions()); + kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none); + kernel.setLinkerScriptPath("src/arch/x86/linker.ld"); + + b.default_step.dependOn(&kernel.step); +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..d7e3ed2 --- /dev/null +++ b/run.sh @@ -0,0 +1,38 @@ +QEMU_SOCKET=/tmp/qemu.sock +QEMU_MONITOR="socat - unix-connect:${QEMU_SOCKET}" +QEMU_GDB_PORT=4242 +KERNEL=build/bzImage + +qemu() { + start() { + sudo qemu-system-i386\ + -kernel ${KERNEL}\ + -gdb tcp::${QEMU_GDB_PORT}\ + -monitor unix:${QEMU_SOCKET},server,nowait\ + -display curses\ + -enable-kvm\ + $* + } + + monitor() { + sudo ${QEMU_MONITOR} + } + + reload() { + echo "stop" | ${QEMU_MONITOR} &>/dev/null + echo "change ide1-cd0 ${KERNEL}" | ${QEMU_MONITOR} &>/dev/null + echo "system_reset" | ${QEMU_MONITOR} &>/dev/null + } + + "$@" +} + +gdb() { + gdb\ + -q\ + -symbols "${KERNEL}" \ + -ex "target remote :${QEMU_GDB_PORT}"\ + -ex "set arch i386" +} + +"$@" diff --git a/src/arch/x86/gdt.s b/src/arch/x86/gdt.s new file mode 100644 index 0000000..c51fc73 --- /dev/null +++ b/src/arch/x86/gdt.s @@ -0,0 +1,23 @@ +.type loadGDT, @function + +// +// Load the GDT into the system registers. +// +// Arguments: +// gdtr: Pointer to the GDTR. +// +loadGDT: + mov +4(%esp), %eax // Fetch the gdtr parameter. + lgdt (%eax) // Load the new GDT. + + // Reload data segments (GDT entry 2: kernel data). + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + + // Reload code segment (GDT entry 1: kernel code). + ljmp $0x08, $1f +1: ret diff --git a/src/arch/x86/gdt.zig b/src/arch/x86/gdt.zig new file mode 100644 index 0000000..ec1c08a --- /dev/null +++ b/src/arch/x86/gdt.zig @@ -0,0 +1,133 @@ +// const tty = @import("tty.zig"); +const x86 = @import("lib/index.zig"); + +// GDT segment selectors. +pub const KERNEL_CODE = 0x08; +pub const KERNEL_DATA = 0x10; +pub const USER_CODE = 0x18; +pub const USER_DATA = 0x20; +pub const TSS_DESC = 0x28; + +// Privilege level of segment selector. +pub const KERNEL_RPL = 0b00; +pub const USER_RPL = 0b11; + +// Access byte values. +const KERNEL = 0x90; +const USER = 0xF0; +const CODE = 0x0A; +const DATA = 0x02; +const TSS_ACCESS = 0x89; + +// Segment flags. +const PROTECTED = (1 << 2); +const BLOCKS_4K = (1 << 3); + +// Structure representing an entry in the GDT. +const GDTEntry = packed struct { + limit_low: u16, + base_low: u16, + base_mid: u8, + access: u8, + limit_high: u4, + flags: u4, + base_high: u8, +}; + +// GDT descriptor register. +const GDTRegister = packed struct { + limit: u16, + base: *const GDTEntry, +}; + +// Task State Segment. +const TSS = packed struct { + unused1: u32, + esp0: u32, // Stack to use when coming to ring 0 from ring > 0. + ss0: u32, // Segment to use when coming to ring 0 from ring > 0. + unused2: [22]u32, + unused3: u16, + iomap_base: u16, // Base of the IO bitmap. +}; + +//// +// Generate a GDT entry structure. +// +// Arguments: +// base: Beginning of the segment. +// limit: Size of the segment. +// access: Access byte. +// flags: Segment flags. +// +fn makeEntry(base: usize, limit: usize, access: u8, flags: u4) GDTEntry { + return GDTEntry{ + .limit_low = @truncate(u16, limit), + .base_low = @truncate(u16, base), + .base_mid = @truncate(u8, base >> 16), + .access = @truncate(u8, access), + .limit_high = @truncate(u4, limit >> 16), + .flags = @truncate(u4, flags), + .base_high = @truncate(u8, base >> 24), + }; +} + +// Fill in the GDT. +var gdt align(4) = []GDTEntry{ + makeEntry(0, 0, 0, 0), + makeEntry(0, 0xFFFFF, KERNEL | CODE, PROTECTED | BLOCKS_4K), + makeEntry(0, 0xFFFFF, KERNEL | DATA, PROTECTED | BLOCKS_4K), + makeEntry(0, 0xFFFFF, USER | CODE, PROTECTED | BLOCKS_4K), + makeEntry(0, 0xFFFFF, USER | DATA, PROTECTED | BLOCKS_4K), + makeEntry(0, 0, 0, 0), // TSS (fill in at runtime). +}; + +// GDT descriptor register pointing at the GDT. +var gdtr = GDTRegister{ + .limit = u16(@sizeOf(@typeOf(gdt))), + .base = &gdt[0], +}; + +// Instance of the Task State Segment. +var tss = TSS{ + .unused1 = 0, + .esp0 = undefined, + .ss0 = KERNEL_DATA, + .unused2 = []u32{0} ** 22, + .unused3 = 0, + .iomap_base = @sizeOf(TSS), +}; + +//// +// Set the kernel stack to use when interrupting user mode. +// +// Arguments: +// esp0: Stack for Ring 0. +// +pub fn setKernelStack(esp0: usize) void { + tss.esp0 = esp0; +} + +//// +// Load the GDT into the system registers (defined in assembly). +// +// Arguments: +// gdtr: Pointer to the GDTR. +// +extern fn loadGDT(gdtr: *const GDTRegister) void; + +//// +// Initialize the Global Descriptor Table. +// +pub fn initialize() void { + // tty.step("Setting up the Global Descriptor Table"); + + // Initialize GDT. + loadGDT(&gdtr); + + // Initialize TSS. + const tss_entry = makeEntry(@ptrToInt(&tss), @sizeOf(TSS) - 1, TSS_ACCESS, PROTECTED); + gdt[TSS_DESC / @sizeOf(GDTEntry)] = tss_entry; + x86.ltr(TSS_DESC); + + // tty.stepOK(); +} diff --git a/src/arch/x86/idt.zig b/src/arch/x86/idt.zig new file mode 100644 index 0000000..ecd2bd5 --- /dev/null +++ b/src/arch/x86/idt.zig @@ -0,0 +1,58 @@ +// https://wiki.osdev.org/IDT +const x86 = @import("lib/index.zig"); +const interrupt = @import("interrupt.zig"); +const gdt = @import("gdt.zig"); + +// Types of gates. +pub const INTERRUPT_GATE = 0x8E; +pub const SYSCALL_GATE = 0xEE; + +// Structure representing an entry in the IDT. +const IDTEntry = packed struct { + offset_low: u16, + selector: u16, + zero: u8, + flags: u8, + offset_high: u16, +}; + +// IDT descriptor register. +const IDTRegister = packed struct { + limit: u16, + base: *[256]IDTEntry, +}; + +// Interrupt Descriptor Table. +var idt: [256]IDTEntry = undefined; + +// IDT descriptor register pointing at the IDT. +const idtr = IDTRegister{ + .limit = u16(@sizeOf(@typeOf(idt))), + .base = &idt, +}; + +//// +// Setup an IDT entry. +// +// Arguments: +// n: Index of the gate. +// flags: Type and attributes. +// offset: Address of the ISR. +// +pub fn setGate(n: u8, flags: u8, offset: extern fn () void) void { + const intOffset = @ptrToInt(offset); + + idt[n].offset_low = @truncate(u16, intOffset); + idt[n].offset_high = @truncate(u16, intOffset >> 16); + idt[n].flags = flags; + idt[n].zero = 0; + idt[n].selector = gdt.KERNEL_CODE; +} + +//// +// Initialize the Interrupt Descriptor Table. +// +pub fn initialize() void { + interrupt.initialize(); + x86.lidt(@ptrToInt(&idtr)); +} diff --git a/src/arch/x86/interrupt.zig b/src/arch/x86/interrupt.zig new file mode 100644 index 0000000..5ecb6d0 --- /dev/null +++ b/src/arch/x86/interrupt.zig @@ -0,0 +1,188 @@ +const x86 = @import("lib/index.zig"); +const isr = @import("isr.zig"); + +// PIC ports. +const PIC1_CMD = 0x20; +const PIC1_DATA = 0x21; +const PIC2_CMD = 0xA0; +const PIC2_DATA = 0xA1; +// PIC commands: +const ISR_READ = 0x0B; // Read the In-Service Register. +const ACK = 0x20; // Acknowledge interrupt. +// Initialization Control Words commands. +const ICW1_INIT = 0x10; +const ICW1_ICW4 = 0x01; +const ICW4_8086 = 0x01; +// write 0 to wait +const WAIT_PORT = 0x80; +// Interrupt Vector offsets of exceptions. +const EXCEPTION_0 = 0; +const EXCEPTION_31 = EXCEPTION_0 + 31; +// Interrupt Vector offsets of IRQs. +const IRQ_0 = EXCEPTION_31 + 1; +const IRQ_15 = IRQ_0 + 15; +// Interrupt Vector offsets of syscalls. +const SYSCALL = 128; + +// Registered interrupt handlers. (see isr.s) +var handlers = []fn () void{unhandled} ** 48; +// Registered IRQ subscribers. (see isr.s) +var irq_subscribers = []MailboxId{MailboxId.Kernel} ** 16; + +fn unhandled() noreturn { + const n = isr.context.interrupt_n; + // if (n >= IRQ_0) { + // tty.panic("unhandled IRQ number {d}", n - IRQ_0); + // } else { + // tty.panic("unhandled exception number {d}", n); + // } + x86.hang(); +} + +inline fn picwait() void { + x86.outb(WAIT_PORT, 0); +} + +//// +// Call the correct handler based on the interrupt number. +// +export fn interruptDispatch() void { + const n = @intCast(u8, isr.context.interrupt_n); + + switch (n) { + // Exceptions. + EXCEPTION_0...EXCEPTION_31 => { + handlers[n](); + }, + + // IRQs. + IRQ_0...IRQ_15 => { + const irq = n - IRQ_0; + // if (spuriousIRQ(irq)) return; + + startOfInterrupt(irq); + handlers[n](); + endOfInterrupt(irq); + }, + + // Syscalls. + // SYSCALL => { + // const syscall_n = isr.context.registers.eax; + // if (syscall_n < syscall.handlers.len) { + // syscall.handlers[syscall_n](); + // } else { + // syscall.invalid(); + // } + // }, + + else => unreachable, + } + + // If no user thread is ready to run, halt here and wait for interrupts. + // if (scheduler.current() == null) { + // x86.sti(); + // x86.hlt(); + // } +} + +inline fn spuriousIRQ(irq: u8) bool { + // Only IRQ 7 and IRQ 15 can be spurious. + if (irq != 7) return false; + // TODO: handle spurious IRQ15. + + // Read the value of the In-Service Register. + x86.outb(PIC1_CMD, ISR_READ); + const in_service = x86.inb(PIC1_CMD); + + // Verify whether IRQ7 is set in the ISR. + return (in_service & (1 << 7)) == 0; +} + +inline fn startOfInterrupt(irq: u8) void { + // mask the irq and then ACK + if (irq >= 8) { + maskIRQ(irq, true); + x86.outb(PIC1_CMD, ACK); + x86.outb(PIC2_CMD, ACK); + } else { + maskIRQ(irq, true); + x86.outb(PIC1_CMD, ACK); + } +} + +inline fn endOfInterrupt(irq: u8) void { + // unmask the irq and then ACK + if (irq >= 8) { + maskIRQ(irq, false); + x86.outb(PIC2_CMD, ACK); + } else { + maskIRQ(irq, false); + x86.outb(PIC1_CMD, ACK); + } +} + +pub fn register(n: u8, handler: fn () void) void { + handlers[n] = handler; +} + +pub fn registerIRQ(irq: u8, handler: fn () void) void { + register(IRQ_0 + irq, handler); + maskIRQ(irq, false); // Unmask the IRQ. +} + +fn remapPIC() void { + // ICW1: start initialization sequence. + x86.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4); + picwait(); + x86.outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4); + picwait(); + + // ICW2: Interrupt Vector offsets of IRQs. + x86.outb(PIC1_DATA, IRQ_0); // IRQ 0..7 -> Interrupt 32..39 + picwait(); + x86.outb(PIC2_DATA, IRQ_0 + 8); // IRQ 8..15 -> Interrupt 40..47 + picwait(); + + // ICW3: IRQ line 2 to connect master to slave PIC. + x86.outb(PIC1_DATA, 1 << 2); + picwait(); + x86.outb(PIC2_DATA, 2); + picwait(); + + // ICW4: 80x86 mode. + x86.outb(PIC1_DATA, ICW4_8086); + picwait(); + x86.outb(PIC2_DATA, ICW4_8086); + picwait(); + + // Mask all IRQs. + x86.outb(PIC1_DATA, 0xFF); + picwait(); + x86.outb(PIC2_DATA, 0xFF); + picwait(); +} + +pub fn maskIRQ(irq: u8, mask: bool) void { + if (irq > 15) { + return; + } + // Figure out if master or slave PIC owns the IRQ. + const port = if (irq < 8) u16(PIC1_DATA) else u16(PIC2_DATA); + const old = x86.inb(port); // Retrieve the current mask. + + // Mask or unmask the interrupt. + const shift = @intCast(u3, irq % 8); // TODO: waiting for Andy to fix this. + if (mask) { + x86.outb(port, old | (u8(1) << shift)); + } else { + x86.outb(port, old & ~(u8(1) << shift)); + } +} + +//// +// Initialize interrupts. +// +pub fn initialize() void { + remapPIC(); + isr.install(); +} diff --git a/src/arch/x86/isr.s b/src/arch/x86/isr.s new file mode 100644 index 0000000..a815e1b --- /dev/null +++ b/src/arch/x86/isr.s @@ -0,0 +1,102 @@ +// Kernel stack for interrupt handling. +KERNEL_STACK = 0x80000 +// GDT selectors. +KERNEL_DS = 0x10 +USER_DS = 0x23 + +// Template for the Interrupt Service Routines. +.macro isrGenerate n + .align 4 + .type isr\n, @function + + isr\n: + // Push a dummy error code for interrupts that don't have one. + .if (\n != 8 && !(\n >= 10 && \n <= 14) && \n != 17) + push $0 + .endif + push $\n // Push the interrupt number. + jmp isrCommon // Jump to the common handler. +.endmacro + +// Common code for all Interrupt Service Routines. +isrCommon: + pusha // Save the registers state. + + // Setup kernel data segment. + mov $KERNEL_DS, %ax + mov %ax, %ds + mov %ax, %es + + // Save the pointer to the current context and switch to the kernel stack. + mov %esp, context + mov $KERNEL_STACK, %esp + + call interruptDispatch // Handle the interrupt event. + + // Restore the pointer to the context (of a different thread, potentially). + mov context, %esp + + // Setup user data segment. + mov $USER_DS, %ax + mov %ax, %ds + mov %ax, %es + + popa // Restore the registers state. + add $8, %esp // Remove interrupt number and error code from stack. + iret +.type isrCommon, @function + +// Exceptions. +isrGenerate 0 +isrGenerate 1 +isrGenerate 2 +isrGenerate 3 +isrGenerate 4 +isrGenerate 5 +isrGenerate 6 +isrGenerate 7 +isrGenerate 8 +isrGenerate 9 +isrGenerate 10 +isrGenerate 11 +isrGenerate 12 +isrGenerate 13 +isrGenerate 14 +isrGenerate 15 +isrGenerate 16 +isrGenerate 17 +isrGenerate 18 +isrGenerate 19 +isrGenerate 20 +isrGenerate 21 +isrGenerate 22 +isrGenerate 23 +isrGenerate 24 +isrGenerate 25 +isrGenerate 26 +isrGenerate 27 +isrGenerate 28 +isrGenerate 29 +isrGenerate 30 +isrGenerate 31 + +// IRQs. +isrGenerate 32 +isrGenerate 33 +isrGenerate 34 +isrGenerate 35 +isrGenerate 36 +isrGenerate 37 +isrGenerate 38 +isrGenerate 39 +isrGenerate 40 +isrGenerate 41 +isrGenerate 42 +isrGenerate 43 +isrGenerate 44 +isrGenerate 45 +isrGenerate 46 +isrGenerate 47 + +// Syscalls. +isrGenerate 128 diff --git a/src/arch/x86/isr.zig b/src/arch/x86/isr.zig new file mode 100644 index 0000000..90abf13 --- /dev/null +++ b/src/arch/x86/isr.zig @@ -0,0 +1,158 @@ +const idt = @import("idt.zig"); + +// Interrupt Service Routines defined externally in assembly. +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 isr32() void; +extern fn isr33() void; +extern fn isr34() void; +extern fn isr35() void; +extern fn isr36() void; +extern fn isr37() void; +extern fn isr38() void; +extern fn isr39() void; +extern fn isr40() void; +extern fn isr41() void; +extern fn isr42() void; +extern fn isr43() void; +extern fn isr44() void; +extern fn isr45() void; +extern fn isr46() void; +extern fn isr47() void; +extern fn isr128() void; + +// Context saved by Interrupt Service Routines. +pub const Context = packed struct { + registers: Registers, // General purpose registers. + + interrupt_n: u32, // Number of the interrupt. + error_code: u32, // Associated error code (or 0). + + // CPU status: + eip: u32, + cs: u32, + eflags: u32, + esp: u32, + ss: u32, + + pub inline fn setReturnValue(self: *volatile Context, value: var) void { + self.registers.eax = if (@typeOf(value) == bool) @boolToInt(value) else @intCast(u32, value); + } +}; + +// Structure holding general purpose registers as saved by PUSHA. +pub const Registers = packed struct { + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + + pub fn init() Registers { + return Registers{ + .edi = 0, + .esi = 0, + .ebp = 0, + .esp = 0, + .ebx = 0, + .edx = 0, + .ecx = 0, + .eax = 0, + }; + } +}; + +// Pointer to the current saved context. +pub export var context: *volatile Context = undefined; +//// +// Install the Interrupt Service Routines in the IDT. +// +pub fn install() void { + // Exceptions. + idt.setGate(0, idt.INTERRUPT_GATE, isr0); + idt.setGate(1, idt.INTERRUPT_GATE, isr1); + idt.setGate(2, idt.INTERRUPT_GATE, isr2); + idt.setGate(3, idt.INTERRUPT_GATE, isr3); + idt.setGate(4, idt.INTERRUPT_GATE, isr4); + idt.setGate(5, idt.INTERRUPT_GATE, isr5); + idt.setGate(6, idt.INTERRUPT_GATE, isr6); + idt.setGate(7, idt.INTERRUPT_GATE, isr7); + idt.setGate(8, idt.INTERRUPT_GATE, isr8); + idt.setGate(9, idt.INTERRUPT_GATE, isr9); + idt.setGate(10, idt.INTERRUPT_GATE, isr10); + idt.setGate(11, idt.INTERRUPT_GATE, isr11); + idt.setGate(12, idt.INTERRUPT_GATE, isr12); + idt.setGate(13, idt.INTERRUPT_GATE, isr13); + idt.setGate(14, idt.INTERRUPT_GATE, isr14); + idt.setGate(15, idt.INTERRUPT_GATE, isr15); + idt.setGate(16, idt.INTERRUPT_GATE, isr16); + idt.setGate(17, idt.INTERRUPT_GATE, isr17); + idt.setGate(18, idt.INTERRUPT_GATE, isr18); + idt.setGate(19, idt.INTERRUPT_GATE, isr19); + idt.setGate(20, idt.INTERRUPT_GATE, isr20); + idt.setGate(21, idt.INTERRUPT_GATE, isr21); + idt.setGate(22, idt.INTERRUPT_GATE, isr22); + idt.setGate(23, idt.INTERRUPT_GATE, isr23); + idt.setGate(24, idt.INTERRUPT_GATE, isr24); + idt.setGate(25, idt.INTERRUPT_GATE, isr25); + idt.setGate(26, idt.INTERRUPT_GATE, isr26); + idt.setGate(27, idt.INTERRUPT_GATE, isr27); + idt.setGate(28, idt.INTERRUPT_GATE, isr28); + idt.setGate(29, idt.INTERRUPT_GATE, isr29); + idt.setGate(30, idt.INTERRUPT_GATE, isr30); + idt.setGate(31, idt.INTERRUPT_GATE, isr31); + + // IRQs. + idt.setGate(32, idt.INTERRUPT_GATE, isr32); + idt.setGate(33, idt.INTERRUPT_GATE, isr33); + idt.setGate(34, idt.INTERRUPT_GATE, isr34); + idt.setGate(35, idt.INTERRUPT_GATE, isr35); + idt.setGate(36, idt.INTERRUPT_GATE, isr36); + idt.setGate(37, idt.INTERRUPT_GATE, isr37); + idt.setGate(38, idt.INTERRUPT_GATE, isr38); + idt.setGate(39, idt.INTERRUPT_GATE, isr39); + idt.setGate(40, idt.INTERRUPT_GATE, isr40); + idt.setGate(41, idt.INTERRUPT_GATE, isr41); + idt.setGate(42, idt.INTERRUPT_GATE, isr42); + idt.setGate(43, idt.INTERRUPT_GATE, isr43); + idt.setGate(44, idt.INTERRUPT_GATE, isr44); + idt.setGate(45, idt.INTERRUPT_GATE, isr45); + idt.setGate(46, idt.INTERRUPT_GATE, isr46); + idt.setGate(47, idt.INTERRUPT_GATE, isr47); + + // Syscalls. + idt.setGate(128, idt.SYSCALL_GATE, isr128); +} diff --git a/src/arch/x86/lib/index.zig b/src/arch/x86/lib/index.zig new file mode 100644 index 0000000..808dc73 --- /dev/null +++ b/src/arch/x86/lib/index.zig @@ -0,0 +1,2 @@ +use @import("io.zig"); +use @import("instructions.zig"); diff --git a/src/arch/x86/lib/instructions.zig b/src/arch/x86/lib/instructions.zig new file mode 100644 index 0000000..97787d4 --- /dev/null +++ b/src/arch/x86/lib/instructions.zig @@ -0,0 +1,39 @@ +//// +// Load a new Task Register. +// +// Arguments: +// desc: Segment selector of the TSS. +// +pub inline fn ltr(desc: u16) void { + asm volatile ("ltr %[desc]" + : + : [desc] "r" (desc) + ); +} + +//// +// Completely stop the computer. +// +pub inline fn hang() noreturn { + asm volatile ("cli"); + while (true) { + asm volatile ("hlt"); + } +} + +pub inline fn sti() void { + asm volatile ("sti"); +} + +//// +// Load a new Interrupt Descriptor Table. +// +// Arguments: +// idtr: Address of the IDTR register. +// +pub inline fn lidt(idtr: usize) void { + asm volatile ("lidt (%[idtr])" + : + : [idtr] "r" (idtr) + ); +} diff --git a/src/arch/x86/lib/io.zig b/src/arch/x86/lib/io.zig new file mode 100644 index 0000000..e6f92c9 --- /dev/null +++ b/src/arch/x86/lib/io.zig @@ -0,0 +1,30 @@ +//// +// Read a byte from a port. +// +// Arguments: +// port: Port from where to read. +// +// Returns: +// The read byte. +// +pub inline fn inb(port: u16) u8 { + return asm volatile ("inb %[port], %[result]" + : [result] "={al}" (-> u8) + : [port] "N{dx}" (port) + ); +} + +//// +// Write a byte on a port. +// +// Arguments: +// port: Port where to write the value. +// value: Value to be written. +// +pub inline fn outb(port: u16, value: u8) void { + asm volatile ("outb %[value], %[port]" + : + : [value] "{al}" (value), + [port] "N{dx}" (port) + ); +} diff --git a/src/arch/x86/linker.ld b/src/arch/x86/linker.ld new file mode 100644 index 0000000..37e3c08 --- /dev/null +++ b/src/arch/x86/linker.ld @@ -0,0 +1,80 @@ +ENTRY(_start) +/* OUTPUT_FORMAT(elf32-i386) */ + +SECTIONS { + /* VGA, cannot use section for this */ + VGA_PTR = 0xb8000; + . = 0xb8000; + . += 80 * 25 * 2; + + . = 1M; + /* ensure that the multiboot header is at the beginning */ + .multiboot : + { + /* KEEP otherwise it gets garbage collected by linker */ + KEEP(*(.multiboot)) + . = ALIGN(4K); + } + + .text : + { + *(.text .text.*) + . = ALIGN(4K); + } + + .rodata : + { + *(.rodata .rodata.*) + . = ALIGN(4K); + } + + .data : + { + *(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*) *(.data.*) + . = ALIGN(4K); + } + + /* NOT A GOOD IDEA TO GROUP debug_* SYMBOLS ! */ + /* .debug : */ + /* { */ + /* /1* KEEP(*(.debug_*)) *1/ */ + /* *(.debug_*) */ + /* . = ALIGN(4K); */ + /* } */ + + .gdt : + { + *(.gdt) + . = ALIGN(4K); + } + + .got : + { + *(.got) + . = ALIGN(4K); + } + + .got.plt : + { + *(.got.plt) + . = ALIGN(4K); + } + + .bss : + { + *(.bss .bss.*) + . = ALIGN(4K); + } + + /* .stab : */ + /* { */ + /* KEEP(*(.stab)) */ + /* . = ALIGN(4K); */ + /* } */ + + /* .stabstr : */ + /* { */ + /* KEEP(*(.stabstr)) */ + /* . = ALIGN(4K); */ + /* } */ +} diff --git a/src/arch/x86/main.zig b/src/arch/x86/main.zig new file mode 100644 index 0000000..74548d4 --- /dev/null +++ b/src/arch/x86/main.zig @@ -0,0 +1,13 @@ +use @import("kernel").main; +const idt = @import("idt.zig"); +const gdt = @import("gdt.zig"); +const x86 = @import("lib/index.zig"); + +/// x86 specific intialization +/// first entry point (see linker.ld) +export nakedcc fn _start() noreturn { + gdt.initialize(); + idt.initialize(); + x86.sti(); + kmain(); +} diff --git a/src/console.zig b/src/console.zig new file mode 100644 index 0000000..4488c04 --- /dev/null +++ b/src/console.zig @@ -0,0 +1,15 @@ +const interrupt = @import("arch/x86/interrupt.zig"); +use @import("vga.zig"); + +var vga = VGA.init(VRAM_ADDR); + +pub fn keyboard_handler() void { + vga.writeString("hello"); +} + +pub fn initialize() void { + vga.clear(); + vga.writeString("zzzzzzzzzzzzzzzz"); + interrupt.registerIRQ(1, keyboard_handler); + vga.writeString("aaaa"); +} diff --git a/src/index.zig b/src/index.zig new file mode 100644 index 0000000..cbc573a --- /dev/null +++ b/src/index.zig @@ -0,0 +1,2 @@ +pub const vga = @import("vga.zig"); +pub const main = @import("main.zig"); diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..c4c4e8e --- /dev/null +++ b/src/main.zig @@ -0,0 +1,9 @@ +use @import("multiboot.zig"); +const console = @import("console.zig"); +const arch = @import("arch/x86/lib/index.zig"); + +// platform independant initialization +pub fn kmain() noreturn { + console.initialize(); + while (true) {} +} diff --git a/src/multiboot.zig b/src/multiboot.zig new file mode 100644 index 0000000..1ab9739 --- /dev/null +++ b/src/multiboot.zig @@ -0,0 +1,128 @@ +// zig fmt: off +const tty = @import("tty.zig"); +const cstr = @import("std").cstr; +const Process = @import("process.zig").Process; + +// This should be in EAX. +pub const MULTIBOOT_BOOTLOADER_MAGIC = 0x2BADB002; + +// Is there basic lower/upper memory information? +pub const MULTIBOOT_INFO_MEMORY = 0x00000001; +// Is there a full memory map? +pub const MULTIBOOT_INFO_MEM_MAP = 0x00000040; + +// System information structure passed by the bootloader. +pub const MultibootInfo = packed struct { + // Multiboot info version number. + flags: u32, + + // Available memory from BIOS. + mem_lower: u32, + mem_upper: u32, + + // "root" partition. + boot_device: u32, + + // Kernel command line. + cmdline: u32, + + // Boot-Module list. + mods_count: u32, + mods_addr: u32, + + // TODO: use the real types here. + u: u128, + + // Memory Mapping buffer. + mmap_length: u32, + mmap_addr: u32, + + // Drive Info buffer. + drives_length: u32, + drives_addr: u32, + + // ROM configuration table. + config_table: u32, + + // Boot Loader Name. + boot_loader_name: u32, + + // APM table. + apm_table: u32, + + // Video. + vbe_control_info: u32, + vbe_mode_info: u32, + vbe_mode: u16, + vbe_interface_seg: u16, + vbe_interface_off: u16, + vbe_interface_len: u16, + + //// + // Return the ending address of the last module. + // + pub fn lastModuleEnd(self: *const MultibootInfo) usize { + const mods = @intToPtr([*]MultibootModule, self.mods_addr); + return mods[self.mods_count - 1].mod_end; + } + + //// + // Load all the modules passed by the bootloader. + // + pub fn loadModules(self: *const MultibootInfo) void { + const mods = @intToPtr([*]MultibootModule, self.mods_addr)[0..self.mods_count]; + + for (mods) |mod| { + const cmdline = cstr.toSlice(@intToPtr([*]u8, mod.cmdline)); + tty.step("Loading \"{}\"", cmdline); + + _ = Process.create(mod.mod_start, null); + // TODO: deallocate the original memory. + + tty.stepOK(); + } + } +}; + +// Types of memory map entries. +pub const MULTIBOOT_MEMORY_AVAILABLE = 1; +pub const MULTIBOOT_MEMORY_RESERVED = 2; + +// Entries in the memory map. +pub const MultibootMMapEntry = packed struct { + size: u32, + addr: u64, + len: u64, + type: u32, +}; + +pub const MultibootModule = packed struct { + // The memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive. + mod_start: u32, + mod_end: u32, + + cmdline: u32, // Module command line. + pad: u32, // Padding to take it to 16 bytes (must be zero). +}; + +// Multiboot structure to be read by the bootloader. +const MultibootHeader = packed struct { + magic: u32, // Must be equal to header magic number. + flags: u32, // Feature flags. + checksum: u32, // Above fields plus this one must equal 0 mod 2^32. +}; +// NOTE: this structure is incomplete. + +// Place the header at the very beginning of the binary. +export const multiboot_header align(4) linksection(".multiboot") = multiboot: { + const MAGIC = u32(0x1BADB002); // Magic number for validation. + const ALIGN = u32(1 << 0); // Align loaded modules. + const MEMINFO = u32(1 << 1); // Receive a memory map from the bootloader. + const FLAGS = ALIGN | MEMINFO; // Combine the flags. + + break :multiboot MultibootHeader { + .magic = MAGIC, + .flags = FLAGS, + .checksum = ~(MAGIC +% FLAGS) +% 1, + }; +}; diff --git a/src/vga.zig b/src/vga.zig new file mode 100644 index 0000000..017a08a --- /dev/null +++ b/src/vga.zig @@ -0,0 +1,203 @@ +const builtin = @import("builtin"); +const mem = @import("std").mem; +const arch = @import("arch/x86/lib/index.zig"); + +// VRAM buffer address in physical memory. +pub const VRAM_ADDR = 0xB8000; +pub const VRAM_SIZE = 0x8000; +// Screen size. +pub const VGA_WIDTH = 80; +pub const VGA_HEIGHT = 25; +pub const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT; + +// Color codes. +pub const Color = enum(u4) { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGrey = 7, + DarkGrey = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + LightMagenta = 13, + LightBrown = 14, + White = 15, +}; + +// Character with attributes. +pub const VGAEntry = packed struct { + char: u8, + foreground: Color, + background: Color, +}; + +//// +// Enable hardware cursor. +// +pub fn enableCursor() void { + outb(0x3D4, 0x0A); + outb(0x3D5, 0x00); +} + +//// +// Disable hardware cursor. +// +pub fn disableCursor() void { + outb(0x3D4, 0x0A); + outb(0x3D5, 1 << 5); +} + +// VGA status. +pub const VGA = struct { + vram: []VGAEntry, + cursor: usize, + foreground: Color, + background: Color, + + //// + // Initialize the VGA status. + // + // Arguments: + // vram: The address of the VRAM buffer. + // + // Returns: + // A structure holding the VGA status. + // + pub fn init(vram: usize) VGA { + return VGA{ + .vram = @intToPtr([*]VGAEntry, vram)[0..0x4000], + .cursor = 0, + .foreground = Color.Black, + .background = Color.Brown, + }; + } + + //// + // Clear the screen. + // + pub fn clear(self: *VGA) void { + mem.set(VGAEntry, self.vram[0..VGA_SIZE], self.entry(' ')); + + self.cursor = 0; + self.updateCursor(); + } + + //// + // Print a character to the screen. + // + // Arguments: + // char: Character to be printed. + // + fn writeChar(self: *VGA, char: u8) void { + if (self.cursor == VGA_WIDTH * VGA_HEIGHT - 1) { + self.scrollDown(); + } + + switch (char) { + // Newline. + '\n' => { + self.writeChar(' '); + while (self.cursor % VGA_WIDTH != 0) + self.writeChar(' '); + }, + // Tab. + '\t' => { + self.writeChar(' '); + while (self.cursor % 4 != 0) + self.writeChar(' '); + }, + // Backspace. + // FIXME: hardcoded 8 here is horrible. + 8 => { + self.cursor -= 1; + self.writeChar(' '); + self.cursor -= 1; + }, + // Any other character. + else => { + self.vram[self.cursor] = self.entry(char); + self.cursor += 1; + }, + } + } + + //// + // Print a string to the screen. + // + // Arguments: + // string: String to be printed. + // + pub fn writeString(self: *VGA, string: []const u8) void { + for (string) |char| { + self.writeChar(char); + } + + self.updateCursor(); + } + + //// + // Scroll the screen one line down. + // + fn scrollDown(self: *VGA) void { + const first = VGA_WIDTH; // Index of first line. + const last = VGA_SIZE - VGA_WIDTH; // Index of last line. + + // Copy all the screen (apart from the first line) up one line. + mem.copy(VGAEntry, self.vram[0..last], self.vram[first..VGA_SIZE]); + // Clean the last line. + mem.set(VGAEntry, self.vram[last..VGA_SIZE], self.entry(' ')); + + // Bring the cursor back to the beginning of the last line. + self.cursor -= VGA_WIDTH; + } + + //// + // Update the position of the hardware cursor. + // Use the software cursor as the source of truth. + // + fn updateCursor(self: *const VGA) void { + arch.outb(0x3D4, 0x0F); + arch.outb(0x3D5, @truncate(u8, self.cursor)); + arch.outb(0x3D4, 0x0E); + arch.outb(0x3D5, @truncate(u8, self.cursor >> 8)); + } + + //// + // Update the position of the software cursor. + // Use the hardware cursor as the source of truth. + // + pub fn fetchCursor(self: *VGA) void { + var cursor: usize = 0; + + arch.outb(0x3D4, 0x0E); + cursor |= usize(arch.inb(0x3D5)) << 8; + + arch.outb(0x3D4, 0x0F); + cursor |= arch.inb(0x3D5); + + self.cursor = cursor; + } + + //// + // Build a VGAEntry with current foreground and background. + // + // Arguments: + // char: The character of the entry. + // + // Returns: + // The requested VGAEntry. + // + fn entry(self: *VGA, char: u8) VGAEntry { + return VGAEntry{ + .char = char, + .foreground = self.foreground, + .background = self.background, + }; + } +};