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.
This commit is contained in:
AI
2026-02-23 10:51:45 +00:00
parent 3909a1f581
commit f1923fdbcf
9 changed files with 475 additions and 42 deletions

View File

@@ -5,6 +5,8 @@ add_executable(kernel
gdt_flush.S gdt_flush.S
gdt.c gdt.c
idt.c idt.c
isr.c
pic.c
interrupts.S interrupts.S
kernel.c kernel.c
) )

156
src/idt.c
View File

@@ -1,46 +1,136 @@
#include "idt.h" #include "idt.h"
#include <stdint.h> #include <string.h> // For memset
#include <stddef.h> /* for memset */
idt_entry_t idt_entries[256]; // The IDT itself
idt_entry_t idt[256];
idt_ptr_t idt_ptr; idt_ptr_t idt_ptr;
extern void idt_load(); // 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) {
static void idt_set_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_entries[num].base_lo = base & 0xFFFF; idt[num].sel = sel;
idt_entries[num].base_hi = (base >> 16) & 0xFFFF; idt[num].always0 = 0;
// flags: 0x8E = 10001110 (Present, Ring0, 32-bit Interrupt Gate)
idt_entries[num].sel = sel; idt[num].flags = flags;
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 */;
} }
/* Note: memset implementation because we don't link with libc */ // Exception Handlers (ISRs)
static void *memset(void *s, int c, size_t n) extern void isr0();
{ extern void isr1();
unsigned char *p = s; extern void isr2();
while(n--) extern void isr3();
*p++ = (unsigned char)c; extern void isr4();
return s; 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.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: // 3. Set the ISRs (Exceptions 0-31)
* 0x8E = 1000 1110 // Code Selector is 0x08 usually (defined in GDT)
* P=1, DPL=00, S=0 (System), Type=1110 (32-bit Interrupt Gate) */ // 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. // 4. Set the IRQs (Remapped to 32-47)
Later we will set up ISRs. */ 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));
} }

View File

@@ -2,6 +2,119 @@
.global idt_load .global idt_load
.type idt_load, @function .type idt_load, @function
idt_load: idt_load:
mov 4(%esp), %eax /* Turn the argument into the EAX register */ mov 4(%esp), %eax
lidt (%eax) /* Load the IDT pointer */ lidt (%eax)
ret 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

75
src/isr.c Normal file
View File

@@ -0,0 +1,75 @@
#include "isr.h"
#include "pic.h"
#include <stdint.h>
/* 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 (;;) ;
}
}

18
src/isr.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef ISR_H
#define ISR_H
#include <stdint.h>
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

View File

@@ -3,11 +3,8 @@
#include <stddef.h> #include <stddef.h>
#include "gdt.h" #include "gdt.h"
#include "idt.h" #include "idt.h"
#include "pic.h"
static inline void outb(uint16_t port, uint8_t val) #include "port_io.h"
{
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
}
void offset_print(const char *str) void offset_print(const char *str)
{ {
@@ -47,5 +44,12 @@ void kernel_main(uint32_t magic, uint32_t addr) {
init_idt(); init_idt();
offset_print("IDT initialized\n"); 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"); offset_print("Hello, world\n");
} }

95
src/pic.c Normal file
View File

@@ -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);
}

11
src/pic.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef PIC_H
#define PIC_H
#include <stdint.h>
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

25
src/port_io.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef PORT_IO_H
#define PORT_IO_H
#include <stdint.h>
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