From 9f53cf0b0b834c0b675325fd74b27e557691353a Mon Sep 17 00:00:00 2001 From: Austin Morris <157867780+amorrisCS@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:29:06 -0500 Subject: [PATCH 1/7] Early Implementation of IDT Modified: exodoom/docker/scripts/build.sh exodoom/src/kernel.c New: exodoom/src/idt.c exodoom/src/idt.h exodoom/src/isr.s exodoom/src/pic.c exodoom/src/pic.h exodoom/src/pit.c exodoom/src/pit.h --- docker/scripts/build.sh | 7 +++++ src/idt.c | 43 +++++++++++++++++++++++++++++ src/idt.h | 9 +++++++ src/isr.s | 14 ++++++++++ src/kernel.c | 29 +++++++++++++++++--- src/pic.c | 37 +++++++++++++++++++++++++ src/pic.h | 7 +++++ src/pit.c | 60 +++++++++++++++++++++++++++++++++++++++++ src/pit.h | 10 +++++++ 9 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 src/idt.c create mode 100644 src/idt.h create mode 100644 src/isr.s create mode 100644 src/pic.c create mode 100644 src/pic.h create mode 100644 src/pit.c create mode 100644 src/pit.h 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/idt.c b/src/idt.c new file mode 100644 index 0000000..b0000f8 --- /dev/null +++ b/src/idt.c @@ -0,0 +1,43 @@ +#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; + +extern void idt_load(uint32_t); + +void idtInit(){ + idtp.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1; + idtp.base = (uint32_t)&idt; + + for (int i = 0; i < IDT_ENTRIES; i++){ + idtSetGate(i, 0); + } + + idt_load((uint32_t)&idtp); +} + + +void idtSetGate(int n, uint32_t handler){ + idt[n].baseLow = handler & 0xFFFF; + idt[n].baseHigh = (handler >> 16) & 0xFFFF; + idt[n].sel = 0x08; + idt[n].always0 = 0; + idt[n].flags = 0x8E; +} \ No newline at end of file diff --git a/src/idt.h b/src/idt.h new file mode 100644 index 0000000..bdc9dae --- /dev/null +++ b/src/idt.h @@ -0,0 +1,9 @@ +#ifndef IDT_H +#define IDT_H + +#include + +void idtInit(); +void idtSetGate(int n, uint32_t handler); + +#endif \ No newline at end of file diff --git a/src/isr.s b/src/isr.s new file mode 100644 index 0000000..5adb2d3 --- /dev/null +++ b/src/isr.s @@ -0,0 +1,14 @@ +.global idt_load +idt_load: + mov 4(%esp), %eax + lidt (%eax) + ret + +.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..2ceed90 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -4,6 +4,14 @@ #include "memory.h" #include "mmap.h" +//IDT and Interrupt includes +#include "idt.h" +#include "pic.h" +#include "pit.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 +23,27 @@ 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(); - qemu_exit(0); + + idtInit(); + picRemap(); + + //IRQ0 = Interrupt Vector 32 + idtSetGate(32, (uint32_t)irq0_stub); + pitInit(100); //100hz + serial_print("Timer Initialized\n"); + + __asm__ volatile ("sti"); + + serial_print("Interrupts Enabled\n"); + + //Main loop + while (1){ + __asm__ volatile ("hlt"); + } } diff --git a/src/pic.c b/src/pic.c new file mode 100644 index 0000000..1c78957 --- /dev/null +++ b/src/pic.c @@ -0,0 +1,37 @@ +#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) + +void picRemap(){ + uint8_t a1 = inb(PIC1_DATA); + uint8_t a2 = inb(PIC2_DATA); + + outb(PIC1_COMMAND, 0x11); + outb(PIC2_COMMAND, 0x11); + + outb(PIC1_DATA, 0x20); + outb(PIC2_DATA, 0x28); + + outb(PIC1_DATA, 4); + outb(PIC2_DATA, 2); + + outb(PIC1_DATA, 0x01); + outb(PIC2_DATA, 0x01); + + outb(PIC1_DATA, a1); + outb(PIC2_DATA, a2); +} + +void picSendEOI(unsigned char irq){ + if (irq >= 8){ + outb(PIC2_COMMAND, 0x20); + } + + outb(PIC1_COMMAND, 0x20); +} \ No newline at end of file diff --git a/src/pic.h b/src/pic.h new file mode 100644 index 0000000..535b0ba --- /dev/null +++ b/src/pic.h @@ -0,0 +1,7 @@ +#ifndef PIC_H +#define PIC_H + +void picRemap(); +void picSendEOI(unsigned char irq); + +#endif \ No newline at end of file diff --git a/src/pit.c b/src/pit.c new file mode 100644 index 0000000..d3974f3 --- /dev/null +++ b/src/pit.c @@ -0,0 +1,60 @@ +#include "pit.h" +#include "io.h" +#include "serial.h" +#include "pic.h" + +static volatile uint64_t ticks = 0; +static uint32_t frequency = 100; + +void pitInit(uint32_t hz){ + frequency = hz; + uint32_t divisor = 1193180 / hz; + + outb(0x43, 0x36); + + outb(0x40, divisor & 0xFF); + outb(0x40, (divisor >> 8) & 0xFF); +} + +uint64_t timerTicks(){ + return ticks; +} + +uint64_t timerMS(){ + return (ticks * 1000) / frequency; +} + +void irq0_handler(){ + ticks++; + + //print once per second + if (ticks % frequency == 0){ + serial_print("tick: "); + + char buf[32]; + int i = 0; + uint64_t t = ticks; + + //int to string + char tmp[32]; + int j = 0; + + if (t == 0){ + tmp[j++] = '0'; + } else { + while (t > 0){ + tmp[j++] = '0' + (t % 10); + t /= 10; + } + } + + while (j > 0){ + buf[i++] = tmp[--j]; + } + + serial_print(buf); + serial_print("\n"); + } + + picSendEOI(0); +} \ No newline at end of file diff --git a/src/pit.h b/src/pit.h new file mode 100644 index 0000000..305b4aa --- /dev/null +++ b/src/pit.h @@ -0,0 +1,10 @@ +#ifndef PIT_H +#define PIT_H + +#include + +void pitInit(uint32_t hz); +uint64_t timerTicks(); +uint64_t timerMS(); + +#endif \ No newline at end of file From a5a3d893e8e4c975a4c3d64eba9417a2196f37ca Mon Sep 17 00:00:00 2001 From: Austin Morris <157867780+amorrisCS@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:35:23 -0500 Subject: [PATCH 2/7] Small IDT Changes Improved upon idt.c such that all flags will no longer be set to present. Improved upon pit.c such that buf is now null terminated. Function names and calls converted to relative naming scheme --- src/idt.c | 16 ++++++++++------ src/idt.h | 4 ++-- src/kernel.c | 8 ++++---- src/pic.c | 4 ++-- src/pic.h | 4 ++-- src/pit.c | 11 ++++++----- src/pit.h | 6 +++--- 7 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/idt.c b/src/idt.c index b0000f8..7d3481d 100644 --- a/src/idt.c +++ b/src/idt.c @@ -22,22 +22,26 @@ static struct idt_ptr idtp; extern void idt_load(uint32_t); -void idtInit(){ +void idt_init(){ idtp.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1; idtp.base = (uint32_t)&idt; - for (int i = 0; i < IDT_ENTRIES; i++){ - idtSetGate(i, 0); - } + //removed initialization loop, doesn't need it idt_load((uint32_t)&idtp); } -void idtSetGate(int n, uint32_t handler){ +void idt_set_gate(int n, uint32_t handler){ idt[n].baseLow = handler & 0xFFFF; idt[n].baseHigh = (handler >> 16) & 0xFFFF; idt[n].sel = 0x08; idt[n].always0 = 0; - idt[n].flags = 0x8E; + + //distinction between not present and present, since the previous method initialized all flags to present (0x8E) + if (handler == 0){ + idt[n].flags = 0; + } else { + idt[n].flags = 0x8E; + } } \ No newline at end of file diff --git a/src/idt.h b/src/idt.h index bdc9dae..8433e03 100644 --- a/src/idt.h +++ b/src/idt.h @@ -3,7 +3,7 @@ #include -void idtInit(); -void idtSetGate(int n, uint32_t handler); +void idt_init(); +void idt_set_gate(int n, uint32_t handler); #endif \ No newline at end of file diff --git a/src/kernel.c b/src/kernel.c index 2ceed90..9a394ce 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -30,12 +30,12 @@ void kernel_main(uint32_t mb_info_addr) { serial_print("\n"); serial_flush(); - idtInit(); - picRemap(); + idt_init(); + pic_remap(); //IRQ0 = Interrupt Vector 32 - idtSetGate(32, (uint32_t)irq0_stub); - pitInit(100); //100hz + idt_set_gate(32, (uint32_t)irq0_stub); + pit_init(100); //100hz serial_print("Timer Initialized\n"); __asm__ volatile ("sti"); diff --git a/src/pic.c b/src/pic.c index 1c78957..657b0d0 100644 --- a/src/pic.c +++ b/src/pic.c @@ -8,7 +8,7 @@ #define PIC2_COMMAND PIC2 #define PIC2_DATA (PIC2+1) -void picRemap(){ +void pic_remap(){ uint8_t a1 = inb(PIC1_DATA); uint8_t a2 = inb(PIC2_DATA); @@ -28,7 +28,7 @@ void picRemap(){ outb(PIC2_DATA, a2); } -void picSendEOI(unsigned char irq){ +void pic_send_EOI(unsigned char irq){ if (irq >= 8){ outb(PIC2_COMMAND, 0x20); } diff --git a/src/pic.h b/src/pic.h index 535b0ba..adbf230 100644 --- a/src/pic.h +++ b/src/pic.h @@ -1,7 +1,7 @@ #ifndef PIC_H #define PIC_H -void picRemap(); -void picSendEOI(unsigned char irq); +void pic_remap(); +void pic_send_EOI(unsigned char irq); #endif \ No newline at end of file diff --git a/src/pit.c b/src/pit.c index d3974f3..cd0a9f1 100644 --- a/src/pit.c +++ b/src/pit.c @@ -6,7 +6,7 @@ static volatile uint64_t ticks = 0; static uint32_t frequency = 100; -void pitInit(uint32_t hz){ +void pit_init(uint32_t hz){ frequency = hz; uint32_t divisor = 1193180 / hz; @@ -16,11 +16,11 @@ void pitInit(uint32_t hz){ outb(0x40, (divisor >> 8) & 0xFF); } -uint64_t timerTicks(){ +uint64_t timer_ticks(){ return ticks; } -uint64_t timerMS(){ +uint64_t timer_MS(){ return (ticks * 1000) / frequency; } @@ -51,10 +51,11 @@ void irq0_handler(){ while (j > 0){ buf[i++] = tmp[--j]; } - + + buf[i] = '\0'; serial_print(buf); serial_print("\n"); } - picSendEOI(0); + pic_send_EOI(0); } \ No newline at end of file diff --git a/src/pit.h b/src/pit.h index 305b4aa..2e72c38 100644 --- a/src/pit.h +++ b/src/pit.h @@ -3,8 +3,8 @@ #include -void pitInit(uint32_t hz); -uint64_t timerTicks(); -uint64_t timerMS(); +void pit_init(uint32_t hz); +uint64_t timer_ticks(); +uint64_t timer_MS(); #endif \ No newline at end of file From 027a88dc4d6509540da786ba0e5d58f090b3f794 Mon Sep 17 00:00:00 2001 From: Austin Morris <157867780+amorrisCS@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:05:53 -0500 Subject: [PATCH 3/7] SCRUM-9 & 10 w/ miscellaneous fixes - Configured PIT to 1000hz and implemented kernel_get_ticks_ms(). - Implemented kernel_sleep_ms() w/ busy-wait on tick counter. A test case was added to kernel.c to assess the accuracy of kernel sleep timings. - Additionally, all header files created for the IDT set up were changed to include "#pragma once", rather than ifndef -> define -> endif - Github Desktop seems to HATE when files are not ended with a newline, so I went through and added a newline at the end of all the files I created that didn't have one. --- src/idt.c | 2 +- src/idt.h | 5 +---- src/kernel.c | 8 +++++++- src/pic.c | 2 +- src/pic.h | 6 ++---- src/pit.c | 12 ++++++++---- src/pit.h | 7 +++---- src/sleep.c | 11 +++++++++++ src/sleep.h | 4 ++++ 9 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 src/sleep.c create mode 100644 src/sleep.h diff --git a/src/idt.c b/src/idt.c index 7d3481d..8b1dd75 100644 --- a/src/idt.c +++ b/src/idt.c @@ -44,4 +44,4 @@ void idt_set_gate(int n, uint32_t handler){ } else { idt[n].flags = 0x8E; } -} \ No newline at end of file +} diff --git a/src/idt.h b/src/idt.h index 8433e03..2b7122a 100644 --- a/src/idt.h +++ b/src/idt.h @@ -1,9 +1,6 @@ -#ifndef IDT_H -#define IDT_H - +#pragma once #include void idt_init(); void idt_set_gate(int n, uint32_t handler); -#endif \ No newline at end of file diff --git a/src/kernel.c b/src/kernel.c index 9a394ce..dfd49d4 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -8,6 +8,7 @@ #include "idt.h" #include "pic.h" #include "pit.h" +#include "sleep.h" //IRQ0 stub from assembly extern void irq0_stub(); @@ -35,13 +36,18 @@ void kernel_main(uint32_t mb_info_addr) { //IRQ0 = Interrupt Vector 32 idt_set_gate(32, (uint32_t)irq0_stub); - pit_init(100); //100hz + 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"); + //Main loop while (1){ __asm__ volatile ("hlt"); diff --git a/src/pic.c b/src/pic.c index 657b0d0..1da20e2 100644 --- a/src/pic.c +++ b/src/pic.c @@ -34,4 +34,4 @@ void pic_send_EOI(unsigned char irq){ } outb(PIC1_COMMAND, 0x20); -} \ No newline at end of file +} diff --git a/src/pic.h b/src/pic.h index adbf230..7a19fac 100644 --- a/src/pic.h +++ b/src/pic.h @@ -1,7 +1,5 @@ -#ifndef PIC_H -#define PIC_H +#pragma once +#include void pic_remap(); void pic_send_EOI(unsigned char irq); - -#endif \ No newline at end of file diff --git a/src/pit.c b/src/pit.c index cd0a9f1..732f69e 100644 --- a/src/pit.c +++ b/src/pit.c @@ -4,7 +4,7 @@ #include "pic.h" static volatile uint64_t ticks = 0; -static uint32_t frequency = 100; +static uint32_t frequency = 1000; void pit_init(uint32_t hz){ frequency = hz; @@ -24,16 +24,20 @@ uint64_t timer_MS(){ return (ticks * 1000) / frequency; } +uint64_t kernel_get_ticks_ms(){ + return timer_MS(); +} + void irq0_handler(){ ticks++; //print once per second if (ticks % frequency == 0){ - serial_print("tick: "); + serial_print("ms: "); char buf[32]; int i = 0; - uint64_t t = ticks; + uint64_t t = timer_MS(); //int to string char tmp[32]; @@ -58,4 +62,4 @@ void irq0_handler(){ } pic_send_EOI(0); -} \ No newline at end of file +} diff --git a/src/pit.h b/src/pit.h index 2e72c38..b43ee37 100644 --- a/src/pit.h +++ b/src/pit.h @@ -1,10 +1,9 @@ -#ifndef PIT_H -#define PIT_H - +#pragma once #include void pit_init(uint32_t hz); uint64_t timer_ticks(); uint64_t timer_MS(); -#endif \ No newline at end of file +uint64_t kernel_get_ticks_ms(); + diff --git a/src/sleep.c b/src/sleep.c new file mode 100644 index 0000000..b272144 --- /dev/null +++ b/src/sleep.c @@ -0,0 +1,11 @@ +#include +#include "pit.h" +#include "sleep.h" + +void kernel_sleep_ms(uint64_t ms) { + uint64_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..f96c739 --- /dev/null +++ b/src/sleep.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void kernel_sleep_ms(uint64_t ms); From 12449589c77b17b3444c41f75ea0663f540a4935 Mon Sep 17 00:00:00 2001 From: Austin Morris <157867780+amorrisCS@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:37:19 -0500 Subject: [PATCH 4/7] Miscellaneous Fixes Miscellaneous fixes to multiple files and refactored formatting to align with the rest of the project. --- src/idt.c | 6 +++--- src/kernel.c | 9 ++++++-- src/pic.c | 37 +++++++++++++++++++------------- src/pit.c | 59 +++++++++++++++++----------------------------------- src/pit.h | 7 +++---- src/sleep.c | 6 +++--- src/sleep.h | 2 +- 7 files changed, 59 insertions(+), 67 deletions(-) diff --git a/src/idt.c b/src/idt.c index 8b1dd75..d39ac30 100644 --- a/src/idt.c +++ b/src/idt.c @@ -22,7 +22,7 @@ static struct idt_ptr idtp; extern void idt_load(uint32_t); -void idt_init(){ +void idt_init() { idtp.limit = sizeof(struct idt_entry) * IDT_ENTRIES - 1; idtp.base = (uint32_t)&idt; @@ -32,14 +32,14 @@ void idt_init(){ } -void idt_set_gate(int n, uint32_t handler){ +void idt_set_gate(int n, uint32_t handler) { idt[n].baseLow = handler & 0xFFFF; idt[n].baseHigh = (handler >> 16) & 0xFFFF; idt[n].sel = 0x08; idt[n].always0 = 0; //distinction between not present and present, since the previous method initialized all flags to present (0x8E) - if (handler == 0){ + if (handler == 0) { idt[n].flags = 0; } else { idt[n].flags = 0x8E; diff --git a/src/kernel.c b/src/kernel.c index dfd49d4..7b1495e 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -34,7 +34,7 @@ void kernel_main(uint32_t mb_info_addr) { idt_init(); pic_remap(); - //IRQ0 = Interrupt Vector 32 + //IRQ0 vector 32 idt_set_gate(32, (uint32_t)irq0_stub); pit_init(1000); //1000hz serial_print("Timer Initialized\n"); @@ -49,7 +49,12 @@ void kernel_main(uint32_t mb_info_addr) { serial_print("Done sleeping!\n"); //Main loop - while (1){ + while (1) { + if (pit_take_print_pending()) { + serial_print("ms: "); + serial_print_u32(kernel_get_ticks_ms()); + serial_print("\n"); + } __asm__ volatile ("hlt"); } } diff --git a/src/pic.c b/src/pic.c index 1da20e2..f22c185 100644 --- a/src/pic.c +++ b/src/pic.c @@ -8,27 +8,36 @@ #define PIC2_COMMAND PIC2 #define PIC2_DATA (PIC2+1) -void pic_remap(){ - uint8_t a1 = inb(PIC1_DATA); - uint8_t a2 = inb(PIC2_DATA); +// ICW - Initialization Command Word +// Think of this as "Hey PIC, here is how we want interrupts to work". - outb(PIC1_COMMAND, 0x11); - outb(PIC2_COMMAND, 0x11); +#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 // "Where should interrupts go?" - Sets interrupt vector offsets - should help negate double faults. +#define ICW4_8086 0x01 // "Set operating mode" - puts PIC into 8086/88 mode - ensures interrupts behave correctly with CPU on x86 - outb(PIC1_DATA, 0x20); - outb(PIC2_DATA, 0x28); +void pic_remap() { + // Begin initialization + outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); - outb(PIC1_DATA, 4); - outb(PIC2_DATA, 2); + // Set vector offsets + outb(PIC1_DATA, 0x20); io_wait(); + outb(PIC2_DATA, 0x28); io_wait(); - outb(PIC1_DATA, 0x01); - outb(PIC2_DATA, 0x01); + // Tell master about slave + outb(PIC1_DATA, 4); io_wait(); + outb(PIC2_DATA, 2); io_wait(); - outb(PIC1_DATA, a1); - outb(PIC2_DATA, a2); + // 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){ +void pic_send_EOI(unsigned char irq) { if (irq >= 8){ outb(PIC2_COMMAND, 0x20); } diff --git a/src/pit.c b/src/pit.c index 732f69e..50fa2f1 100644 --- a/src/pit.c +++ b/src/pit.c @@ -3,12 +3,14 @@ #include "serial.h" #include "pic.h" -static volatile uint64_t ticks = 0; +static volatile uint32_t ticks = 0; static uint32_t frequency = 1000; +static volatile uint8_t print_pending = 0; +static uint32_t divisor = 1193; -void pit_init(uint32_t hz){ +void pit_init(uint32_t hz) { frequency = hz; - uint32_t divisor = 1193180 / hz; + uint32_t divisor = (1193180 + hz / 2) / hz; outb(0x43, 0x36); @@ -16,50 +18,27 @@ void pit_init(uint32_t hz){ outb(0x40, (divisor >> 8) & 0xFF); } -uint64_t timer_ticks(){ +uint32_t timer_ticks() { return ticks; } -uint64_t timer_MS(){ - return (ticks * 1000) / frequency; +uint32_t kernel_get_ticks_ms() { + return (ticks * divisor * 1000) / 1193180; } -uint64_t kernel_get_ticks_ms(){ - return timer_MS(); -} - -void irq0_handler(){ +void irq0_handler() { ticks++; - - //print once per second - if (ticks % frequency == 0){ - serial_print("ms: "); - - char buf[32]; - int i = 0; - uint64_t t = timer_MS(); - - //int to string - char tmp[32]; - int j = 0; - - if (t == 0){ - tmp[j++] = '0'; - } else { - while (t > 0){ - tmp[j++] = '0' + (t % 10); - t /= 10; - } - } - - while (j > 0){ - buf[i++] = tmp[--j]; - } - - buf[i] = '\0'; - serial_print(buf); - serial_print("\n"); + + 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 index b43ee37..889ea73 100644 --- a/src/pit.h +++ b/src/pit.h @@ -2,8 +2,7 @@ #include void pit_init(uint32_t hz); -uint64_t timer_ticks(); -uint64_t timer_MS(); - -uint64_t kernel_get_ticks_ms(); +uint32_t timer_ticks(); +uint32_t kernel_get_ticks_ms(); +uint8_t pit_take_print_pending(); diff --git a/src/sleep.c b/src/sleep.c index b272144..a427fa2 100644 --- a/src/sleep.c +++ b/src/sleep.c @@ -2,10 +2,10 @@ #include "pit.h" #include "sleep.h" -void kernel_sleep_ms(uint64_t ms) { - uint64_t start = kernel_get_ticks_ms(); +void kernel_sleep_ms(uint32_t ms) { + uint32_t start = kernel_get_ticks_ms(); - while ((kernel_get_ticks_ms() - start) < ms){ + while ((kernel_get_ticks_ms() - start) < ms) { __asm__ volatile ("hlt"); } } diff --git a/src/sleep.h b/src/sleep.h index f96c739..9d54610 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -1,4 +1,4 @@ #pragma once #include -void kernel_sleep_ms(uint64_t ms); +void kernel_sleep_ms(uint32_t ms); From 83b2583d5baeebd6685be80896cf01f67e1f55b0 Mon Sep 17 00:00:00 2001 From: Austin Morris <157867780+amorrisCS@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:11:56 -0500 Subject: [PATCH 5/7] Fixes for compile error, shadow variable, and unused header Compile error stemming from kernel.c:74, call to non-existent serial_printf() - now using a series of serial_print(). Shadow Variable Bug in pit.c:13 stemming from a re-declaration of divisor variable, leading to the global divisor variable becoming stale - now accurately manipulating global variable. Unused header "serial.h" within pit.c - now removed --- src/pit.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pit.c b/src/pit.c index 50fa2f1..9702083 100644 --- a/src/pit.c +++ b/src/pit.c @@ -1,6 +1,5 @@ #include "pit.h" #include "io.h" -#include "serial.h" #include "pic.h" static volatile uint32_t ticks = 0; @@ -10,7 +9,7 @@ static uint32_t divisor = 1193; void pit_init(uint32_t hz) { frequency = hz; - uint32_t divisor = (1193180 + hz / 2) / hz; + divisor = (1193180 + hz / 2) / hz; outb(0x43, 0x36); From 815861030e47e2000c6fc35eb41dbc43acf23207 Mon Sep 17 00:00:00 2001 From: charlotte whittleman Date: Wed, 1 Apr 2026 08:46:04 -0500 Subject: [PATCH 6/7] Fix IDT/PIT issues preventing stable interrupt handling - Drop AOUT_KLUDGE flag from Multiboot header; zero reserved fields so GRUB loads the ELF correctly instead of misinterpreting load addresses - Read %cs at runtime in idt_init() rather than hardcoding 0x08, fixing GP-faults on iret out of interrupt handlers - Fix uint32_t overflow in kernel_get_ticks_ms() (ticks*divisor*1000 wrapped at ~3.6s); use (uint64_t)ticks*1000/frequency instead - Move qemu_exit after the ms-printing loop so monotonic counter is visible in CI output - Add serial_print_u32(); remove dead timer_ticks() export - Fix ICW1_ICW4 comment; remove stale docker-test from .PHONY --- Makefile | 10 +--------- src/boot.s | 15 +++++++-------- src/idt.c | 22 +++++++++++++--------- src/isr.s | 7 +++++++ src/kernel.c | 10 ++++++++-- src/pic.c | 2 +- src/pit.c | 9 ++------- src/pit.h | 1 - src/serial.c | 9 +++++++++ src/serial.h | 1 + 10 files changed, 49 insertions(+), 37 deletions(-) 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/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 index d39ac30..788e81b 100644 --- a/src/idt.c +++ b/src/idt.c @@ -19,14 +19,24 @@ struct idt_ptr{ 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; - //removed initialization loop, doesn't need it + /* 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); } @@ -35,13 +45,7 @@ void idt_init() { void idt_set_gate(int n, uint32_t handler) { idt[n].baseLow = handler & 0xFFFF; idt[n].baseHigh = (handler >> 16) & 0xFFFF; - idt[n].sel = 0x08; + idt[n].sel = kernel_cs; idt[n].always0 = 0; - - //distinction between not present and present, since the previous method initialized all flags to present (0x8E) - if (handler == 0) { - idt[n].flags = 0; - } else { - idt[n].flags = 0x8E; - } + idt[n].flags = 0x8E; } diff --git a/src/isr.s b/src/isr.s index 5adb2d3..ca832aa 100644 --- a/src/isr.s +++ b/src/isr.s @@ -4,6 +4,13 @@ idt_load: 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 diff --git a/src/kernel.c b/src/kernel.c index 7b1495e..d1f1680 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -48,13 +48,19 @@ void kernel_main(uint32_t mb_info_addr) { kernel_sleep_ms(1000); serial_print("Done sleeping!\n"); - //Main loop - while (1) { + //Print monotonic ms counter for 5 seconds then exit + uint32_t prints = 0; + while (prints < 105) { 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); + + (void)mb; } diff --git a/src/pic.c b/src/pic.c index f22c185..6ddb3be 100644 --- a/src/pic.c +++ b/src/pic.c @@ -12,7 +12,7 @@ // 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 // "Where should interrupts go?" - Sets interrupt vector offsets - should help negate double faults. +#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() { diff --git a/src/pit.c b/src/pit.c index 9702083..017c482 100644 --- a/src/pit.c +++ b/src/pit.c @@ -5,11 +5,10 @@ static volatile uint32_t ticks = 0; static uint32_t frequency = 1000; static volatile uint8_t print_pending = 0; -static uint32_t divisor = 1193; void pit_init(uint32_t hz) { frequency = hz; - divisor = (1193180 + hz / 2) / hz; + uint32_t divisor = (1193180 + hz / 2) / hz; outb(0x43, 0x36); @@ -17,12 +16,8 @@ void pit_init(uint32_t hz) { outb(0x40, (divisor >> 8) & 0xFF); } -uint32_t timer_ticks() { - return ticks; -} - uint32_t kernel_get_ticks_ms() { - return (ticks * divisor * 1000) / 1193180; + return (uint64_t)ticks * 1000 / frequency; } void irq0_handler() { diff --git a/src/pit.h b/src/pit.h index 889ea73..bf92f34 100644 --- a/src/pit.h +++ b/src/pit.h @@ -2,7 +2,6 @@ #include void pit_init(uint32_t hz); -uint32_t timer_ticks(); 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); From 356617a2b966836f8456338defdf7cfca61a2b3b Mon Sep 17 00:00:00 2001 From: charlotte whittleman Date: Wed, 1 Apr 2026 08:56:05 -0500 Subject: [PATCH 7/7] Revert drift test print count to 5; restore framebuffer console demo --- src/kernel.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/kernel.c b/src/kernel.c index d1f1680..8b6f522 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -9,6 +9,8 @@ #include "pic.h" #include "pit.h" #include "sleep.h" +#include "fb.h" +#include "fb_console.h" //IRQ0 stub from assembly extern void irq0_stub(); @@ -50,7 +52,7 @@ void kernel_main(uint32_t mb_info_addr) { //Print monotonic ms counter for 5 seconds then exit uint32_t prints = 0; - while (prints < 105) { + while (prints < 5) { if (pit_take_print_pending()) { serial_print("ms: "); serial_print_u32(kernel_get_ticks_ms()); @@ -62,5 +64,24 @@ void kernel_main(uint32_t mb_info_addr) { qemu_exit(0); - (void)mb; + 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"); }