Skip to content

Programmable Interrupt Controller

The Programmable Interrupt Controller aka the PIC is a legacy device that is used to let multiple hardware devices give interrupts to the CPU and allow them to be remapped to different interrupts on the CPU. You will need to configure the PIC to remap the default mappings outside of the area that messes with CPU exceptions and to mask IRQs you do not want.

Setting up the PIC

The following functions give the basic tools you need to manage the PIC but make sure you have functions in place for port I/O

#define PIC1_COMMAND 0x20
#define PIC1_DATA    0x21
#define PIC2_COMMAND 0xA0
#define PIC2_DATA    0xA1

#define ICW1_INIT    0x10
#define ICW1_ICW4    0x01

#define ICW4_8086    0x01

static inline void io_wait() {
    outb(0x80, 0);
}

void unmask_irq(unsigned char irq) {
    unsigned short port;
    unsigned char value;

    if (irq < 8) {
        port = PIC1_DATA;
    } else {
        port = PIC2_DATA;
        irq -= 8;
    }

    value = inb(port);
    value &= ~(1 << irq);
    outb(port, value);
    io_wait();
}

void setup_pic(int offset1, int offset2) {
    __asm__ __volatile__("cli");

    outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4);
    outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
    io_wait();

    outb(PIC1_DATA, offset1);
    outb(PIC2_DATA, offset2);
    io_wait();

    outb(PIC1_DATA, 0x04);
    outb(PIC2_DATA, 0x02);
    io_wait();

    outb(PIC1_DATA, ICW4_8086);
    outb(PIC2_DATA, ICW4_8086);
    io_wait();

    outb(PIC1_DATA, 0xFF);
    outb(PIC2_DATA, 0xFF);
    io_wait();

    __asm__ __volatile__("sti");
}
This will setup both PICs and mask all interrupts

You can then call the setup_pic() as setup_pic(0x20, 0x28); to have it be in the most common configuration that the pages in this wiki will assume

You will need to call unmask_irq() as for example unmask_irq(0); to unmast IRQ0 and let the PIT give interrupts.

End of Interrupt Signal (EOI)

When you get an interrupt from one driven by the PIC you ned to tell it that it was handled so it can send another else it could deadlock your system if say your kernel is using preemptive multitasking based on the PIT.

At the end of these interrupts you should use the following code for the according PIC that gave you the interrupt

outb(0x20,0x20);
outb(0xA0,0x20);

Spurious IRQs

Sometimes the PICs may send an interrupt on IRQ7 or IRQ15 due to a coding issue from an improper EOI signal or a hardware issue. In most cases its ok if you are not using IRQ7 or IRQ15 for other things to just put a handler in the IDT for it and then just send an EOI to both PICs or just do nothing and it should be fine.

There are proper ways to handle them by asking the PICs if it was a real interrupt or a spurious one and then if it was from the master PIC and spurious ignore it and if it was from the slave PIC ignore it from there but send an EOI to the master PIC but in most modern kernels you wont be using the legacy PIC and things that will collide with these to where you need to do advanced checks