diff --git a/Makefile b/Makefile index fd59a8e..c4ecc74 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: docker-build docker-run docker-run-kernel docker-test test clean +.PHONY: docker-build docker-run docker-run-kernel clean DEBUG ?= 0 @@ -40,13 +40,5 @@ docker-run-debug: docker-build run: docker-build qemu-system-i386 -m 256M -cdrom build/exodoom.iso -no-reboot -serial stdio -#TODO: Add proper testing pipeline with CUnit -test: - @mkdir -p build - gcc -std=c99 -Wall -Wextra -fno-builtin -o build/test_string tests/test_string.c src/string.c - ./build/test_string - gcc -std=c99 -Wall -Wextra -fno-builtin -o build/test_ctype tests/test_ctype.c src/ctype.c - ./build/test_ctype - clean: rm -rf build diff --git a/docker/scripts/build.sh b/docker/scripts/build.sh index b53d177..0eeb1d2 100644 --- a/docker/scripts/build.sh +++ b/docker/scripts/build.sh @@ -27,6 +27,13 @@ for c in src/*.c; do objs+=("$o") done +#assemble isr.s +if [ -f src/isr.s ]; then + echo " AS isr.s" + i686-elf-as src/isr.s -o build/isr.o + objs+=(build/isr.o) +fi + echo "[3/6] Link kernel -> build/exodoom" i686-elf-gcc "${LDFLAGS[@]}" -o build/exodoom \ "${objs[@]}" -lgcc diff --git a/src/boot.s b/src/boot.s index 3a000ac..757e7f6 100644 --- a/src/boot.s +++ b/src/boot.s @@ -1,8 +1,7 @@ .set ALIGN, 1<<0 .set MEMINFO, 1<<1 .set VIDEO, 1<<2 -.set AOUT_KLUDGE, 1<<16 -.set FLAGS, ALIGN | MEMINFO | VIDEO | AOUT_KLUDGE +.set FLAGS, ALIGN | MEMINFO | VIDEO .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) @@ -14,12 +13,12 @@ multiboot_header: .long FLAGS .long CHECKSUM -/* a.out kludge fields */ -.long multiboot_header /* header_addr */ -.long _load_start /* load_addr */ -.long _load_end /* load_end_addr */ -.long _bss_end /* bss_end_addr */ -.long _start /* entry_addr */ +/* reserved (offsets 12-28 unused without AOUT_KLUDGE, kept to preserve VIDEO field offsets) */ +.long 0 +.long 0 +.long 0 +.long 0 +.long 0 /* graphics fields */ .long 0 /* mode_type: 0=linear graphics */ diff --git a/src/idt.c b/src/idt.c new file mode 100644 index 0000000..788e81b --- /dev/null +++ b/src/idt.c @@ -0,0 +1,51 @@ +#include "idt.h" +#include "io.h" + +#define IDT_ENTRIES 256 + +struct idt_entry{ + uint16_t baseLow; + uint16_t sel; + uint8_t always0; + uint8_t flags; + uint16_t baseHigh; +} __attribute__((packed)); + +struct idt_ptr{ + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + + +static struct idt_entry idt[IDT_ENTRIES]; +static struct idt_ptr idtp; +static uint16_t kernel_cs; + +extern void idt_load(uint32_t); +extern void default_stub(void); + +void idt_init() { + /* Read the actual code segment selector GRUB gave us rather than + assuming 0x08 — a mismatch causes iret to GP-fault on the way out + of any interrupt handler. */ + __asm__ volatile ("mov %%cs, %0" : "=r"(kernel_cs)); + + idtp.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1; + idtp.base = (uint32_t)&idt; + + /* Fill every entry with a safe default so any stray vector irets + cleanly instead of cascading into a triple fault. */ + for (int i = 0; i < IDT_ENTRIES; i++) + idt_set_gate(i, (uint32_t)default_stub); + + idt_load((uint32_t)&idtp); +} + + +void idt_set_gate(int n, uint32_t handler) { + idt[n].baseLow = handler & 0xFFFF; + idt[n].baseHigh = (handler >> 16) & 0xFFFF; + idt[n].sel = kernel_cs; + idt[n].always0 = 0; + idt[n].flags = 0x8E; +} diff --git a/src/idt.h b/src/idt.h new file mode 100644 index 0000000..2b7122a --- /dev/null +++ b/src/idt.h @@ -0,0 +1,6 @@ +#pragma once +#include + +void idt_init(); +void idt_set_gate(int n, uint32_t handler); + diff --git a/src/isr.s b/src/isr.s new file mode 100644 index 0000000..ca832aa --- /dev/null +++ b/src/isr.s @@ -0,0 +1,21 @@ +.global idt_load +idt_load: + mov 4(%esp), %eax + lidt (%eax) + ret + +/* Default handler for unregistered vectors — just return silently. + No EOI is sent here; this stub covers CPU exception vectors (0-31) + where EOI is not appropriate. Hardware IRQs should have dedicated stubs. */ +.global default_stub +default_stub: + iret + +.global irq0_stub +.extern irq0_handler + +irq0_stub: + pusha + call irq0_handler + popa + iret \ No newline at end of file diff --git a/src/kernel.c b/src/kernel.c index c8415d1..8b6f522 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -4,6 +4,17 @@ #include "memory.h" #include "mmap.h" +//IDT and Interrupt includes +#include "idt.h" +#include "pic.h" +#include "pit.h" +#include "sleep.h" +#include "fb.h" +#include "fb_console.h" + +//IRQ0 stub from assembly +extern void irq0_stub(); + static inline void qemu_exit(uint32_t code) { __asm__ volatile ("outl %0, %1" : : "a"(code), "Nd"(0xF4)); } @@ -15,14 +26,62 @@ void kernel_main(uint32_t mb_info_addr) { serial_print("Kernel Booted\n"); mmap_init(mb); - memory_init(); - serial_print("Memory subsystem initialized\n"); serial_print("Allocator base: "); serial_print_hex(memory_base_address()); serial_print("\n"); - serial_flush(); + + idt_init(); + pic_remap(); + + //IRQ0 vector 32 + idt_set_gate(32, (uint32_t)irq0_stub); + pit_init(1000); //1000hz + serial_print("Timer Initialized\n"); + + __asm__ volatile ("sti"); + + serial_print("Interrupts Enabled\n"); + + //Sleep test case + serial_print("Sleeping for 1 second...\n"); + kernel_sleep_ms(1000); + serial_print("Done sleeping!\n"); + + //Print monotonic ms counter for 5 seconds then exit + uint32_t prints = 0; + while (prints < 5) { + if (pit_take_print_pending()) { + serial_print("ms: "); + serial_print_u32(kernel_get_ticks_ms()); + serial_print("\n"); + prints++; + } + __asm__ volatile ("hlt"); + } + qemu_exit(0); + + if (!(mb->flags & MULTIBOOT_INFO_FLAG_FRAMEBUFFER)) for(;;); + + framebuffer_t fb; + if (!fb_init_bgrx8888(&fb, + (uintptr_t)mb->framebuffer_addr, + mb->framebuffer_pitch, + mb->framebuffer_width, + mb->framebuffer_height, + mb->framebuffer_bpp)) { + for(;;); + } + + fb_console_t con; + if (!fbcon_init(&con, &fb)) for(;;); + + fbcon_set_color(&con, 255,255,255, 0,0,0); + fbcon_write(&con, "ExoDoom fb console online.\n"); + fbcon_write(&con, "Now printing to pixels like a proper gremlin.\n\n"); + fbcon_write(&con, "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"); + fbcon_write(&con, "abcdefghijklmnopqrstuvwxyz !@#$%^&*()[]{}\n"); } diff --git a/src/pic.c b/src/pic.c new file mode 100644 index 0000000..6ddb3be --- /dev/null +++ b/src/pic.c @@ -0,0 +1,46 @@ +#include "pic.h" +#include "io.h" + +#define PIC1 0x20 +#define PIC2 0xA0 +#define PIC1_COMMAND PIC1 +#define PIC1_DATA (PIC1+1) +#define PIC2_COMMAND PIC2 +#define PIC2_DATA (PIC2+1) + +// ICW - Initialization Command Word +// Think of this as "Hey PIC, here is how we want interrupts to work". + +#define ICW1_INIT 0x10 // "Start init" - Without this, we would likely run into unpredictable situations where PIC stays as GRUB left it leading to critical overlap. +#define ICW1_ICW4 0x01 // Tell the PIC to expect ICW4 during initialization (required for 8086 mode). +#define ICW4_8086 0x01 // "Set operating mode" - puts PIC into 8086/88 mode - ensures interrupts behave correctly with CPU on x86 + +void pic_remap() { + // Begin initialization + outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); + + // Set vector offsets + outb(PIC1_DATA, 0x20); io_wait(); + outb(PIC2_DATA, 0x28); io_wait(); + + // Tell master about slave + outb(PIC1_DATA, 4); io_wait(); + outb(PIC2_DATA, 2); io_wait(); + + // Set 8086 mode + outb(PIC1_DATA, ICW4_8086); io_wait(); + outb(PIC2_DATA, ICW4_8086); io_wait(); + + // Mask + outb(PIC1_DATA, 0xFE); io_wait(); + outb(PIC2_DATA, 0xFF); io_wait(); +} + +void pic_send_EOI(unsigned char irq) { + if (irq >= 8){ + outb(PIC2_COMMAND, 0x20); + } + + outb(PIC1_COMMAND, 0x20); +} diff --git a/src/pic.h b/src/pic.h new file mode 100644 index 0000000..7a19fac --- /dev/null +++ b/src/pic.h @@ -0,0 +1,5 @@ +#pragma once +#include + +void pic_remap(); +void pic_send_EOI(unsigned char irq); diff --git a/src/pit.c b/src/pit.c new file mode 100644 index 0000000..017c482 --- /dev/null +++ b/src/pit.c @@ -0,0 +1,38 @@ +#include "pit.h" +#include "io.h" +#include "pic.h" + +static volatile uint32_t ticks = 0; +static uint32_t frequency = 1000; +static volatile uint8_t print_pending = 0; + +void pit_init(uint32_t hz) { + frequency = hz; + uint32_t divisor = (1193180 + hz / 2) / hz; + + outb(0x43, 0x36); + + outb(0x40, divisor & 0xFF); + outb(0x40, (divisor >> 8) & 0xFF); +} + +uint32_t kernel_get_ticks_ms() { + return (uint64_t)ticks * 1000 / frequency; +} + +void irq0_handler() { + ticks++; + + if (ticks % frequency == 0) { + print_pending = 1; + } + + pic_send_EOI(0); +} + +uint8_t pit_take_print_pending() { + if (!print_pending) return 0; + + print_pending = 0; + return 1; +} diff --git a/src/pit.h b/src/pit.h new file mode 100644 index 0000000..bf92f34 --- /dev/null +++ b/src/pit.h @@ -0,0 +1,7 @@ +#pragma once +#include + +void pit_init(uint32_t hz); +uint32_t kernel_get_ticks_ms(); +uint8_t pit_take_print_pending(); + diff --git a/src/serial.c b/src/serial.c index 79ca4c2..e52e831 100644 --- a/src/serial.c +++ b/src/serial.c @@ -79,3 +79,12 @@ void serial_putc(char c) { void serial_print(const char* s) { while (*s) serial_putc(*s++); } + +void serial_print_u32(uint32_t val) { + char buf[11]; + char *p = buf + 10; + *p = '\0'; + if (val == 0) { serial_putc('0'); return; } + while (val > 0) { *--p = '0' + (val % 10); val /= 10; } + serial_print(p); +} diff --git a/src/serial.h b/src/serial.h index 08630b0..bb6744b 100644 --- a/src/serial.h +++ b/src/serial.h @@ -4,6 +4,7 @@ void serial_init(void); void serial_putc(char c); void serial_print(const char* s); +void serial_print_u32(uint32_t val); void serial_flush(void); void serial_print_hex(uint32_t num); void serial_print_hex64(uint64_t num); diff --git a/src/sleep.c b/src/sleep.c new file mode 100644 index 0000000..a427fa2 --- /dev/null +++ b/src/sleep.c @@ -0,0 +1,11 @@ +#include +#include "pit.h" +#include "sleep.h" + +void kernel_sleep_ms(uint32_t ms) { + uint32_t start = kernel_get_ticks_ms(); + + while ((kernel_get_ticks_ms() - start) < ms) { + __asm__ volatile ("hlt"); + } +} diff --git a/src/sleep.h b/src/sleep.h new file mode 100644 index 0000000..9d54610 --- /dev/null +++ b/src/sleep.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void kernel_sleep_ms(uint32_t ms);