From c4b3d8e05c0a5e36e7a716cb3baf29f312762905 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 6 Mar 2016 11:49:30 -0500 Subject: [PATCH] PIC class which manages the PIC chips --- src/IO.hh | 25 +++++--- src/PIC.cc | 165 +++++++++++++++++++++++++++++++++++++++++++++++++ src/PIC.hh | 57 +++++++++++++++++ src/SConscript | 1 + 4 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 src/PIC.cc create mode 100644 src/PIC.hh diff --git a/src/IO.hh b/src/IO.hh index 3b64bfb..7f01e99 100644 --- a/src/IO.hh +++ b/src/IO.hh @@ -22,9 +22,7 @@ inline uint8_t inb(uint16_t port) { uint8_t ret; - asm volatile("inb %[port], %[ret]", - : [result] "=a"(ret) - : [port] "Nd"(port)); + asm volatile("inb %1, %0" : "=a"(ret) : "Nd"(port)); return ret; } @@ -33,9 +31,7 @@ inline uint16_t inw(uint16_t port) { uint16_t ret; - asm volatile("inw %[port], %[ret]", - : [result] "=a"(ret) - : [port] "Nd"(port)); + asm volatile("inw %1, %0" : "=a"(ret) : "Nd"(port)); return ret; } @@ -44,9 +40,7 @@ inline uint32_t inl(uint16_t port) { uint32_t ret; - asm volatile("inl %[port], %[ret]", - : [result] "=a"(ret) - : [port] "Nd"(port)); + asm volatile("inl %1, %0" : "=a"(ret) : "Nd"(port)); return ret; } @@ -77,6 +71,19 @@ outl(uint16_t port, asm volatile("outl %0, %1" : : "a"(value), "Nd"(port)); } +/* + * Wait + */ + +inline void +wait() +{ + asm volatile( + "jmp $1\n\t" + "1: jmp $2\n\t" + "2:"); +} + } /* namespace io */ } /* namespace kernel */ diff --git a/src/PIC.cc b/src/PIC.cc new file mode 100644 index 0000000..0b3b83e --- /dev/null +++ b/src/PIC.cc @@ -0,0 +1,165 @@ +/* PIC.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of the kernel::x86::PIC class. + */ + +#include "PIC.hh" +#include "IO.hh" + +enum ICW1 { + ICW4Required = (1 << 0), // Bit 0: 1 = ICW4 required, 0 = ICW4 not required + Single8259 = (1 << 1), // Bit 1: 1 = Single 8259, 0 = Cascading 8259s + HalfSizeVectors = (1 << 2), // Bit 2: 1 = 4-byte vector size, 0 = 8-byte vector size + LevelTriggered = (1 << 3), // Bit 3: 1 = level triggered mode, 0 = edge triggered mode + Initialize = (1 << 4), // Bit 4: 1 = initialization mode (required for ICW1) + // Remaining bits must be 0. +}; + +enum ICW4 { + X86Mode = (1 << 0), // Bit 0: 1 = 80x86 mode, 0 = MCS 80/85 mode + AutoEOI = (1 << 1), // Bit 1: 1 = Auto EOI, 0 = normal EOI + PIC2Buffered = (1 << 3), // Bit 3: 1 = PIC1 buffered mode + PIC1Buffered = (3 << 2), // Bits 2, 3: 1 = PIC2 buffered mode +}; + +enum OCW2 { + NonSpecificEOI = (1 << 5), + NOPEOI = (2 << 5), + SpecificEOI = (3 << 5), + RotateInAutomaticEOIMode = (4 << 5), + RotateOnNonSpecificEOICommand = (5 << 5), + SetPriorityCommand = (6 << 5), + RotateOnSpecificEOICommand = (7 << 5), +}; + +namespace { + const struct { + uint16_t command; + uint16_t data; + } PIC1 { 0x0020, 0x0021 }, PIC2 { 0x00A0, 0x00A1 }; +} + +namespace kernel { +namespace x86 { + +/* + * Static + */ + +PIC& +PIC::systemPIC() +{ + static PIC sPIC; + return sPIC; +} + +/* + * Public + */ + +void +PIC::remap(uint8_t pic1Offset, + uint8_t pic2Offset, + uint8_t pic2IRQ) +{ + /* + * Initialization of the PIC chips requires programming both chips together. + * Each requires a series of four bytes: the first (ICW1) on their command + * ports, and the subsequent three (ICW2 through ICW4) on their data ports. + * + * ICW1 contains the following flags: + * Bit 0: 1 = ICW4 required, 0 = ICW4 not required + * Bit 1: 1 = single 8259, 0 = cascading 8259s + * Bit 2: 1 = 4 byte interrupt vectors, 0 = 8 byte interrupt vectors + * Bit 3: 1 = level triggered mode, 0 = edge triggered mode + * Bit 4: 1 = initialization (this is required to be 1 for ICW1) + * Bits 5, 6, 7 must all be 0 + * + * ICW2 is the interrupt vector offset that this PIC's interrupt should be + * mapped to. It must be divisible by 0x8. + * + * ICW3 is different for the PIC1 and PIC2. For the PIC1, ICW3 should have + * one bit set indicating on which IRQ PIC2 (the cascaded PIC) will be. For + * PIC2, the first three bits of ICW3 indicate on which IRQ it will reside + * on the PIC1. + * + * ICW4 contains the following flags: + * Bit 0: 1 = 80x86 mode, 0 = MCS 80/85 mode + * Bit 1: 1 = auto EOI mode, 0 = normal EOI mode + * Bits 2, 3: PIC2/PIC1 buffering mode + * Bit 4: 1 = special fully nested mode (SFNM), 0 = sequential mode + * Bits 5, 6, 7 must all be 0 + * + * The buffering modes mentioned above are as follows: + * 00: not buffered + * 01: not buffered + * 10: buffered mode PIC2 (PC mode) + * 11: buffered mode PIC1 (PC mode) + */ + + // Ensure divisiblity by 8. + pic1Offset &= ~uint8_t(0x7); + pic2Offset &= ~uint8_t(0x7); + + // TODO: Implement clamping and error handling. + uint8_t pic2IRQMask = 0; + if (pic2IRQ > 0) { + pic2IRQMask = 1; + for (int i = 1; i < pic2IRQ; i--) { + pic2IRQMask <<= 1; + } + } else { + // TODO: We have a problem. + } + + // Save the current IRQ masks for each PIC + uint8_t pic1Mask = io::inb(PIC1.data); + uint8_t pic2Mask = io::inb(PIC2.data); + +#ifdef CONFIG_PIC_SHOULD_WAIT +# define waitIfRequired() io::wait() +#else +# define waitIfRequired() +#endif + +#define sendToPIC(port, byte) \ + io::outb((port), (byte)); \ + waitIfRequired() + + sendToPIC(PIC1.command, ICW1::Initialize + ICW1::ICW4Required); + sendToPIC(PIC1.data, pic1Offset); + sendToPIC(PIC1.data, pic2IRQMask); + sendToPIC(PIC1.data, ICW4::X86Mode + ICW4::AutoEOI); + + sendToPIC(PIC2.command, ICW1::Initialize + ICW1::ICW4Required); + sendToPIC(PIC2.data, pic2Offset); + sendToPIC(PIC2.data, pic2IRQ); + sendToPIC(PIC2.data, ICW4::X86Mode + ICW4::AutoEOI); + +#undef sendToPIC +#undef waitIfRequired + + // Restore the saved masks + io::outb(PIC2.data, pic1Mask); + io::outb(PIC2.data, pic2Mask); +} + +void +PIC::endOfInterrupt(uint8_t irq) +{ + /* + * Notifying the PICs that an interrupt has been completed uses OCW2 on the + * command ports. If the IRQ came from one owned by PIC2, both chips must be + * notified. Otherwise, only PIC1 needs to be notified. + */ + if (irq >= 8) { + io::outb(PIC2.command, OCW2::NOPEOI); + } + io::outb(PIC1.command, OCW2::NOPEOI); +} + +} /* namespace x86 */ +} /* namespace kernel */ diff --git a/src/PIC.hh b/src/PIC.hh new file mode 100644 index 0000000..92ab822 --- /dev/null +++ b/src/PIC.hh @@ -0,0 +1,57 @@ +/* PIC.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * The x86 Programmable Interrupt Controller. This is the simple one, not the + * newer APIC. + */ + +#ifndef __PIC_HH__ +#define __PIC_HH__ + +#include + +namespace kernel { +namespace x86 { + +/** + * On x86 systems, there are a pair of 8259 PIC chips which together control + * hardware interrupts. One is the master; the other is the slave. Each one has + * 8 interrupts. The slave is connected to the master on IRQ 2. + * + * These chips must be programmed before use. When doing so, you provide a + * vector offset for each chip that is the start of its range in the IDT. + * + * In modern CPUs, the pair of PIC chips has been replaced with the APIC and + * IO-APIC. However, interrupts via the 8259s are still supported. + */ +struct PIC +{ + static PIC& systemPIC(); + + /** + * Initialize and map the PIC chips into the interrupt vector space. The + * offsets must be divisible by 8. The `pic2IRQ` argument specifies which + * IRQ on first PIC the second PIC will be mapped to. + */ + void remap(uint8_t pic1Offset, uint8_t pic2ffset, uint8_t pic2IRQ); + + /** + * Notifies the PICs with an End Of Interrupt (EOI) command that the + * interrupt has been handled. This _must_ be done after the kernel has + * handled a hardware interrupt. + */ + void endOfInterrupt(uint8_t irq); + + /** + * Enable or disable the given IRQ. This is done by setting a mask bit in + * the PIC's IMR register. If the bit is set, the IRQ is ignored. + */ + void enableInterrupt(uint8_t irq, bool enabled); +}; + +} /* namespace x86 */ +} /* namespace kernel */ + +#endif /* __PIC_HH__ */ diff --git a/src/SConscript b/src/SConscript index a98bc52..65ec40c 100644 --- a/src/SConscript +++ b/src/SConscript @@ -12,6 +12,7 @@ files = [ 'Main.cc', 'Console.cc', 'Descriptors.cc', + 'PIC.cc', 'cxa.cc', ]