diff --git a/include/pic.h b/include/pic.h new file mode 100644 index 0000000..af41903 --- /dev/null +++ b/include/pic.h @@ -0,0 +1,58 @@ +/* Nadir 8259 PIC driver: remap, EOI, IRQ masking. + * + * Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT. + * Privileged instructions: `outb`/`inb` (via io.h) trap with #GP(0) + * when CPL > IOPL, so every entry point below is Ring 0 only. + * Fault behavior: no CPU fault is possible beyond a programming bug; + * a wrong sequence masks the wrong IRQ or loses an EOI (hung line), + * never a triple fault by itself. IF stays cleared by the caller; + * this driver never executes `sti`/`cli`. + */ + +#ifndef NADIR_PIC_H +#define NADIR_PIC_H + +#include + +/* Command/data ports for the master (PIC1) and slave (PIC2). */ +#define PIC1_CMD 0x20U +#define PIC1_DATA 0x21U +#define PIC2_CMD 0xA0U +#define PIC2_DATA 0xA1U + +/* End-of-interrupt command byte. */ +#define PIC_EOI 0x20U + +/* Remapped vector bases: IRQ 0-7 -> vectors 32-39, IRQ 8-15 -> 40-47. */ +#define PIC_IRQ0_VECTOR 0x20U +#define PIC_IRQ8_VECTOR 0x28U + +/* Number of IRQ lines behind the cascaded 8259 pair. */ +#define PIC_IRQ_COUNT 16U + +/* Remap the cascaded 8259 pair to vectors 32-47 and mask everything. + * After reset IRQs overlap CPU exceptions 0-15, so without a + * remap a timer tick looks like a fault. Mask-all leaves each device + * opt-in via pic_unmask(). + * Preconditions: Ring 0, called once before idt_install_irqs(). + * Failure modes: none. */ +void pic_remap(void); + +/* Signal end-of-interrupt for IRQ line `irq` (0-15). + * WHY cascade order: the slave must be acked before the master or the + * master keeps the cascade line busy and IRQ 2 stops firing. + * Preconditions: Ring 0, `irq` < 16. + * Failure modes: out-of-range `irq` is ignored. */ +void pic_eoi(uint8_t irq); + +/* Mask (disable) IRQ line `irq` (0-15). + * Preconditions: Ring 0, `irq` < 16. + * Failure modes: out-of-range `irq` is ignored. */ +void pic_mask(uint8_t irq); + +/* Unmask (enable) IRQ line `irq` (0-15). + * Preconditions: Ring 0, `irq` < 16, PIC already remapped. + * Failure modes: out-of-range `irq` is ignored. */ +void pic_unmask(uint8_t irq); + +#endif /* NADIR_PIC_H */ diff --git a/kernel/arch/x86_64/pic.c b/kernel/arch/x86_64/pic.c new file mode 100644 index 0000000..d729a0f --- /dev/null +++ b/kernel/arch/x86_64/pic.c @@ -0,0 +1,90 @@ +/* Nadir 8259 PIC driver: cascaded master/slave remap + EOI + masking. + * + * Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT. + * Privileged instructions: `outb`/`inb` via io.h (trap #GP if CPL > + * IOPL); no `cli`/`sti`/`lidt`. + * Fault behavior: no CPU fault possible a wrong ICW order only + * misroutes IRQs. + */ + +#include + +#include "io.h" +#include "pic.h" + +/* Init control words: ICW1 starts init, ICW2 sets the vector base, + * ICW3 wires the cascade (master IRQ2 <-> slave identity 2), ICW4 + * selects 8086 mode. io_wait() after each byte gives the slow 8259 + * time to latch it. */ +#define PIC_ICW1_INIT 0x11U +#define PIC_ICW3_MASTER_SLAVE_LINE 0x04U +#define PIC_ICW3_SLAVE_ID 0x02U +#define PIC_ICW4_8086 0x01U + +void pic_remap(void) +{ + /* WHY this exact order: the 8259 latches ICW2-4 only while in init + * mode, and the cascade identities must match or slave IRQs never + * reach the CPU. Mask-all at the end keeps devices quiet until + * their driver calls pic_unmask(). */ + outb(PIC1_CMD, PIC_ICW1_INIT); + io_wait(); + outb(PIC2_CMD, PIC_ICW1_INIT); + io_wait(); + outb(PIC1_DATA, PIC_IRQ0_VECTOR); + io_wait(); + outb(PIC2_DATA, PIC_IRQ8_VECTOR); + io_wait(); + outb(PIC1_DATA, PIC_ICW3_MASTER_SLAVE_LINE); + io_wait(); + outb(PIC2_DATA, PIC_ICW3_SLAVE_ID); + io_wait(); + outb(PIC1_DATA, PIC_ICW4_8086); + io_wait(); + outb(PIC2_DATA, PIC_ICW4_8086); + io_wait(); + outb(PIC1_DATA, 0xFFU); + io_wait(); + outb(PIC2_DATA, 0xFFU); + io_wait(); +} + +void pic_eoi(uint8_t irq) +{ + if (irq >= PIC_IRQ_COUNT) { + return; + } + + if (irq >= 8U) { + outb(PIC2_CMD, PIC_EOI); + } + outb(PIC1_CMD, PIC_EOI); +} + +void pic_mask(uint8_t irq) +{ + uint16_t port; + uint8_t mask; + + if (irq >= PIC_IRQ_COUNT) { + return; + } + + port = (irq < 8U) ? PIC1_DATA : PIC2_DATA; + mask = inb(port) | (uint8_t)(1U << (irq & 7U)); + outb(port, mask); +} + +void pic_unmask(uint8_t irq) +{ + uint16_t port; + uint8_t mask; + + if (irq >= PIC_IRQ_COUNT) { + return; + } + + port = (irq < 8U) ? PIC1_DATA : PIC2_DATA; + mask = inb(port) & (uint8_t) ~(1U << (irq & 7U)); + outb(port, mask); +}