From f1923fdbcfcbee53dc0aea6991ca6b7cc8ebec77 Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 10:51:45 +0000 Subject: [PATCH] Implement ISR stubs and PIC driver for hardware interrupt handling (AI) - Reworked IDT initialization to register all 32 CPU exception handlers (ISR 0-31) and 16 hardware interrupt handlers (IRQ 0-15, mapped to IDT entries 32-47). - Created assembly stubs in interrupts.S using macros for ISRs with and without error codes, plus IRQ stubs. All route through a common stub that saves registers, loads kernel data segment, and calls the C handler. - Added isr.c with a unified interrupt dispatcher that handles both exceptions (halts on fault) and hardware IRQs (sends EOI via PIC). - Implemented PIC (8259) driver in pic.c with full initialization sequence that remaps IRQ 0-7 to IDT 32-39 and IRQ 8-15 to IDT 40-47. Includes mask/unmask and EOI support. - Extracted port I/O primitives (inb, outb, io_wait) into port_io.h header for reuse across drivers. - Kernel now initializes PIC after IDT and enables interrupts with STI. --- src/CMakeLists.txt | 2 + src/idt.c | 158 +++++++++++++++++++++++++++++++++++---------- src/interrupts.S | 117 ++++++++++++++++++++++++++++++++- src/isr.c | 75 +++++++++++++++++++++ src/isr.h | 18 ++++++ src/kernel.c | 16 +++-- src/pic.c | 95 +++++++++++++++++++++++++++ src/pic.h | 11 ++++ src/port_io.h | 25 +++++++ 9 files changed, 475 insertions(+), 42 deletions(-) create mode 100644 src/isr.c create mode 100644 src/isr.h create mode 100644 src/pic.c create mode 100644 src/pic.h create mode 100644 src/port_io.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3dade13..7ee6304 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ add_executable(kernel gdt_flush.S gdt.c idt.c + isr.c + pic.c interrupts.S kernel.c ) diff --git a/src/idt.c b/src/idt.c index 300a144..4b5b981 100644 --- a/src/idt.c +++ b/src/idt.c @@ -1,46 +1,136 @@ #include "idt.h" -#include -#include /* for memset */ +#include // For memset -idt_entry_t idt_entries[256]; -idt_ptr_t idt_ptr; +// The IDT itself +idt_entry_t idt[256]; +idt_ptr_t idt_ptr; -extern void idt_load(); - -static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) -{ - idt_entries[num].base_lo = base & 0xFFFF; - idt_entries[num].base_hi = (base >> 16) & 0xFFFF; - - idt_entries[num].sel = sel; - idt_entries[num].always0 = 0; - // We must uncomment the OR below when we get to using user-mode. - // It sets the interrupt gate's privilege level to 3. - idt_entries[num].flags = flags /* | 0x60 */; +// Helper to set a gate in the IDT +static void set_idt_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { + idt[num].base_lo = base & 0xFFFF; + idt[num].base_hi = (base >> 16) & 0xFFFF; + idt[num].sel = sel; + idt[num].always0 = 0; + // flags: 0x8E = 10001110 (Present, Ring0, 32-bit Interrupt Gate) + idt[num].flags = flags; } -/* Note: memset implementation because we don't link with libc */ -static void *memset(void *s, int c, size_t n) -{ - unsigned char *p = s; - while(n--) - *p++ = (unsigned char)c; - return s; -} +// Exception Handlers (ISRs) +extern void isr0(); +extern void isr1(); +extern void isr2(); +extern void isr3(); +extern void isr4(); +extern void isr5(); +extern void isr6(); +extern void isr7(); +extern void isr8(); +extern void isr9(); +extern void isr10(); +extern void isr11(); +extern void isr12(); +extern void isr13(); +extern void isr14(); +extern void isr15(); +extern void isr16(); +extern void isr17(); +extern void isr18(); +extern void isr19(); +extern void isr20(); +extern void isr21(); +extern void isr22(); +extern void isr23(); +extern void isr24(); +extern void isr25(); +extern void isr26(); +extern void isr27(); +extern void isr28(); +extern void isr29(); +extern void isr30(); +extern void isr31(); -void init_idt() -{ +// Hardware Interrupt Handlers (IRQs) +extern void irq0(); +extern void irq1(); +extern void irq2(); +extern void irq3(); +extern void irq4(); +extern void irq5(); +extern void irq6(); +extern void irq7(); +extern void irq8(); +extern void irq9(); +extern void irq10(); +extern void irq11(); +extern void irq12(); +extern void irq13(); +extern void irq14(); +extern void irq15(); + +void init_idt() { + // 1. Set up the IDT pointer idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1; - idt_ptr.base = (uint32_t)&idt_entries; + idt_ptr.base = (uint32_t)&idt; - memset(&idt_entries, 0, sizeof(idt_entry_t) * 256); + // 2. Clear the IDT + memset(&idt, 0, sizeof(idt_entry_t) * 256); - /* Determine valid flags: - * 0x8E = 1000 1110 - * P=1, DPL=00, S=0 (System), Type=1110 (32-bit Interrupt Gate) */ + // 3. Set the ISRs (Exceptions 0-31) + // Code Selector is 0x08 usually (defined in GDT) + // Flags: 0x8E = Present(1), DPL(00), Storage(0), GateType(1110 = 32-bit Int) + set_idt_gate( 0, (uint32_t)isr0, 0x08, 0x8E); + set_idt_gate( 1, (uint32_t)isr1, 0x08, 0x8E); + set_idt_gate( 2, (uint32_t)isr2, 0x08, 0x8E); + set_idt_gate( 3, (uint32_t)isr3, 0x08, 0x8E); + set_idt_gate( 4, (uint32_t)isr4, 0x08, 0x8E); + set_idt_gate( 5, (uint32_t)isr5, 0x08, 0x8E); + set_idt_gate( 6, (uint32_t)isr6, 0x08, 0x8E); + set_idt_gate( 7, (uint32_t)isr7, 0x08, 0x8E); + set_idt_gate( 8, (uint32_t)isr8, 0x08, 0x8E); + set_idt_gate( 9, (uint32_t)isr9, 0x08, 0x8E); + set_idt_gate(10, (uint32_t)isr10, 0x08, 0x8E); + set_idt_gate(11, (uint32_t)isr11, 0x08, 0x8E); + set_idt_gate(12, (uint32_t)isr12, 0x08, 0x8E); + set_idt_gate(13, (uint32_t)isr13, 0x08, 0x8E); + set_idt_gate(14, (uint32_t)isr14, 0x08, 0x8E); + set_idt_gate(15, (uint32_t)isr15, 0x08, 0x8E); + set_idt_gate(16, (uint32_t)isr16, 0x08, 0x8E); + set_idt_gate(17, (uint32_t)isr17, 0x08, 0x8E); + set_idt_gate(18, (uint32_t)isr18, 0x08, 0x8E); + set_idt_gate(19, (uint32_t)isr19, 0x08, 0x8E); + set_idt_gate(20, (uint32_t)isr20, 0x08, 0x8E); + set_idt_gate(21, (uint32_t)isr21, 0x08, 0x8E); + set_idt_gate(22, (uint32_t)isr22, 0x08, 0x8E); + set_idt_gate(23, (uint32_t)isr23, 0x08, 0x8E); + set_idt_gate(24, (uint32_t)isr24, 0x08, 0x8E); + set_idt_gate(25, (uint32_t)isr25, 0x08, 0x8E); + set_idt_gate(26, (uint32_t)isr26, 0x08, 0x8E); + set_idt_gate(27, (uint32_t)isr27, 0x08, 0x8E); + set_idt_gate(28, (uint32_t)isr28, 0x08, 0x8E); + set_idt_gate(29, (uint32_t)isr29, 0x08, 0x8E); + set_idt_gate(30, (uint32_t)isr30, 0x08, 0x8E); + set_idt_gate(31, (uint32_t)isr31, 0x08, 0x8E); - /* For now, just load the IDT without any entries to verify loading works without crashing. - Later we will set up ISRs. */ + // 4. Set the IRQs (Remapped to 32-47) + set_idt_gate(32, (uint32_t)irq0, 0x08, 0x8E); + set_idt_gate(33, (uint32_t)irq1, 0x08, 0x8E); + set_idt_gate(34, (uint32_t)irq2, 0x08, 0x8E); + set_idt_gate(35, (uint32_t)irq3, 0x08, 0x8E); + set_idt_gate(36, (uint32_t)irq4, 0x08, 0x8E); + set_idt_gate(37, (uint32_t)irq5, 0x08, 0x8E); + set_idt_gate(38, (uint32_t)irq6, 0x08, 0x8E); + set_idt_gate(39, (uint32_t)irq7, 0x08, 0x8E); + set_idt_gate(40, (uint32_t)irq8, 0x08, 0x8E); + set_idt_gate(41, (uint32_t)irq9, 0x08, 0x8E); + set_idt_gate(42, (uint32_t)irq10, 0x08, 0x8E); + set_idt_gate(43, (uint32_t)irq11, 0x08, 0x8E); + set_idt_gate(44, (uint32_t)irq12, 0x08, 0x8E); + set_idt_gate(45, (uint32_t)irq13, 0x08, 0x8E); + set_idt_gate(46, (uint32_t)irq14, 0x08, 0x8E); + set_idt_gate(47, (uint32_t)irq15, 0x08, 0x8E); - idt_load((uint32_t)&idt_ptr); + // 5. Load the IDT using assembly instruction 'lidt' + // We can use inline assembly or a helper function. + // Assuming lid is available via inline asm or similar to gdt_flush + __asm__ volatile("lidt (%0)" : : "r" (&idt_ptr)); } diff --git a/src/interrupts.S b/src/interrupts.S index 3021029..6285f0a 100644 --- a/src/interrupts.S +++ b/src/interrupts.S @@ -2,6 +2,119 @@ .global idt_load .type idt_load, @function idt_load: - mov 4(%esp), %eax /* Turn the argument into the EAX register */ - lidt (%eax) /* Load the IDT pointer */ + mov 4(%esp), %eax + lidt (%eax) ret + +/* Common ISR stub */ +isr_common_stub: + pusha + + /* Save segment registers */ + mov %ds, %ax + push %eax + + /* Load kernel data segment */ + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + /* Push pointer to stack structure as argument for C handler */ + push %esp + call isr_handler + add $4, %esp /* Clean up pushed pointer */ + + /* Restore segment registers */ + pop %eax + mov %eax, %ds + mov %eax, %es + mov %eax, %fs + mov %eax, %gs + + popa + add $8, %esp /* Cleans up error code and ISR number */ + iret + +/* Macro for exceptions with NO Error Code */ +.macro ISR_NOERRCODE num +.global isr\num +.type isr\num, @function +isr\num: + cli + push $0 + push $\num + jmp isr_common_stub +.endm + +/* Macro for exceptions WITH Error Code */ +.macro ISR_ERRCODE num +.global isr\num +.type isr\num, @function +isr\num: + cli + push $\num + jmp isr_common_stub +.endm + +ISR_NOERRCODE 0 +ISR_NOERRCODE 1 +ISR_NOERRCODE 2 +ISR_NOERRCODE 3 +ISR_NOERRCODE 4 +ISR_NOERRCODE 5 +ISR_NOERRCODE 6 +ISR_NOERRCODE 7 +ISR_ERRCODE 8 +ISR_NOERRCODE 9 +ISR_ERRCODE 10 +ISR_ERRCODE 11 +ISR_ERRCODE 12 +ISR_ERRCODE 13 +ISR_ERRCODE 14 +ISR_NOERRCODE 15 +ISR_NOERRCODE 16 +ISR_ERRCODE 17 +ISR_NOERRCODE 18 +ISR_NOERRCODE 19 +ISR_NOERRCODE 20 +ISR_NOERRCODE 21 +ISR_NOERRCODE 22 +ISR_NOERRCODE 23 +ISR_NOERRCODE 24 +ISR_NOERRCODE 25 +ISR_NOERRCODE 26 +ISR_NOERRCODE 27 +ISR_NOERRCODE 28 +ISR_NOERRCODE 29 +ISR_ERRCODE 30 +ISR_NOERRCODE 31 +/* Macro for IRQs (Hardware Interrupts) */ +.macro ISR_IRQ num, idt_index +.global irq\num +.type irq\num, @function +irq\num: + cli + push $0 + push $\idt_index + jmp isr_common_stub +.endm + +/* Hardware Interrupts */ +ISR_IRQ 0, 32 +ISR_IRQ 1, 33 +ISR_IRQ 2, 34 +ISR_IRQ 3, 35 +ISR_IRQ 4, 36 +ISR_IRQ 5, 37 +ISR_IRQ 6, 38 +ISR_IRQ 7, 39 +ISR_IRQ 8, 40 +ISR_IRQ 9, 41 +ISR_IRQ 10, 42 +ISR_IRQ 11, 43 +ISR_IRQ 12, 44 +ISR_IRQ 13, 45 +ISR_IRQ 14, 46 +ISR_IRQ 15, 47 diff --git a/src/isr.c b/src/isr.c new file mode 100644 index 0000000..8786387 --- /dev/null +++ b/src/isr.c @@ -0,0 +1,75 @@ +#include "isr.h" +#include "pic.h" +#include + +/* Forward declaration for kernel panic or similar */ +void offset_print(const char *str); +void print_hex(uint32_t val); + +/* Exception messages */ +char *exception_messages[] = { + "Division By Zero", + "Debug", + "Non Maskable Interrupt", + "Breakpoint", + "Into Detected Overflow", + "Out of Bounds", + "Invalid Opcode", + "No Coprocessor", + "Double Fault", + "Coprocessor Segment Overrun", + "Bad TSS", + "Segment Not Present", + "Stack Fault", + "General Protection Fault", + "Page Fault", + "Unknown Interrupt", + "Coprocessor Fault", + "Alignment Check", + "Machine Check", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved" +}; + +void isr_handler(registers_t *regs) +{ + // If it's a hardware interrupt (IRQ), we must acknowledge it + if (regs->int_no >= 32 && regs->int_no < 48) { + // Send EOI to PIC (IRQ number 0-15) + pic_send_eoi(regs->int_no - 32); + + // Here we would call the registered handler for this IRQ + // For now, just print something for the timer tick so we know it works, + // but limit it to avoid flooding the log. + if (regs->int_no == 32) { + // Timer tick - do nothing verbose + // offset_print("."); + } else if (regs->int_no == 33) { + // Keyboard + offset_print("Keyboard IRQ!\n"); + } + return; + } + + offset_print("received interrupt: "); + print_hex(regs->int_no); + offset_print("\n"); + + if (regs->int_no < 32) + { + offset_print(exception_messages[regs->int_no]); + offset_print(" Exception. System Halted!\n"); + for (;;) ; + } +} diff --git a/src/isr.h b/src/isr.h new file mode 100644 index 0000000..0ecc96f --- /dev/null +++ b/src/isr.h @@ -0,0 +1,18 @@ +#ifndef ISR_H +#define ISR_H + +#include + +typedef struct registers +{ + uint32_t ds; // Data segment selector + uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha + uint32_t int_no, err_code; // Interrupt number and error code (if applicable) + uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically +} registers_t; + +typedef void (*isr_t)(registers_t*); + +void isr_handler(registers_t* regs); + +#endif diff --git a/src/kernel.c b/src/kernel.c index 4c79e46..08e5405 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -3,11 +3,8 @@ #include #include "gdt.h" #include "idt.h" - -static inline void outb(uint16_t port, uint8_t val) -{ - asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) ); -} +#include "pic.h" +#include "port_io.h" void offset_print(const char *str) { @@ -46,6 +43,13 @@ void kernel_main(uint32_t magic, uint32_t addr) { init_idt(); offset_print("IDT initialized\n"); - + + init_pic(); + offset_print("PIC initialized\n"); + + /* Enable interrupts */ + asm volatile("sti"); + offset_print("Interrupts enabled\n"); + offset_print("Hello, world\n"); } diff --git a/src/pic.c b/src/pic.c new file mode 100644 index 0000000..8e26239 --- /dev/null +++ b/src/pic.c @@ -0,0 +1,95 @@ +#include "pic.h" +#include "port_io.h" + +#define PIC1_COMMAND 0x20 +#define PIC1_DATA 0x21 +#define PIC2_COMMAND 0xA0 +#define PIC2_DATA 0xA1 + +#define ICW1_ICW4 0x01 /* ICW4 (not) needed */ +#define ICW1_SINGLE 0x02 /* Single (cascade) mode */ +#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */ +#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */ +#define ICW1_INIT 0x10 /* Initialization - required! */ + +#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */ +#define ICW4_AUTO 0x02 /* Auto (normal) EOI */ +#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */ +#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */ +#define ICW4_SFNM 0x10 /* Special fully nested (not) */ + +#define PIC_EOI 0x20 + +void pic_send_eoi(uint8_t irq) +{ + if(irq >= 8) + outb(PIC2_COMMAND, PIC_EOI); + + outb(PIC1_COMMAND, PIC_EOI); +} + +/* + reinitialize the PIC controllers, giving them specified vector offsets + rather than 8h and 70h, as configured by default +*/ +#define PIC1_OFFSET 0x20 +#define PIC2_OFFSET 0x28 + +void init_pic(void) +{ + unsigned char a1, a2; + + a1 = inb(PIC1_DATA); // save masks + a2 = inb(PIC2_DATA); + + outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode) + io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + + outb(PIC1_DATA, PIC1_OFFSET); // ICW2: Master PIC vector offset + io_wait(); + outb(PIC2_DATA, PIC2_OFFSET); // ICW2: Slave PIC vector offset + io_wait(); + + outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100) + io_wait(); + outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010) + io_wait(); + + outb(PIC1_DATA, ICW4_8086); + io_wait(); + outb(PIC2_DATA, ICW4_8086); + io_wait(); + + outb(PIC1_DATA, a1); // restore saved masks. + outb(PIC2_DATA, a2); +} + +void pic_clear_mask(uint8_t irq) { + uint16_t port; + uint8_t value; + + if(irq < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + irq -= 8; + } + value = inb(port) & ~(1 << irq); + outb(port, value); +} + +void pic_set_mask(uint8_t irq) { + uint16_t port; + uint8_t value; + + if(irq < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + irq -= 8; + } + value = inb(port) | (1 << irq); + outb(port, value); +} diff --git a/src/pic.h b/src/pic.h new file mode 100644 index 0000000..2343c31 --- /dev/null +++ b/src/pic.h @@ -0,0 +1,11 @@ +#ifndef PIC_H +#define PIC_H + +#include + +void init_pic(void); +void pic_send_eoi(uint8_t irq); +void pic_clear_mask(uint8_t irq); +void pic_set_mask(uint8_t irq); + +#endif diff --git a/src/port_io.h b/src/port_io.h new file mode 100644 index 0000000..b353f9e --- /dev/null +++ b/src/port_io.h @@ -0,0 +1,25 @@ +#ifndef PORT_IO_H +#define PORT_IO_H + +#include + +static inline void outb(uint16_t port, uint8_t val) +{ + asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) ); +} + +static inline uint8_t inb(uint16_t port) +{ + uint8_t ret; + asm volatile ( "inb %w1, %b0" : "=a"(ret) : "Nd"(port) ); + return ret; +} + +static inline void io_wait(void) +{ + /* Port 0x80 is used for 'checkpoints' during POST. */ + /* The Linux kernel seems to think it is free for use :-/ */ + asm volatile ( "outb %%al, $0x80" : : "a"(0) ); +} + +#endif