Intel 8259 Programmable Interrupt Controller

The Intel 8259 Programmable Interrupt Controller (PIC) plays an essential role in the operation of much of the hardware of the IBM PC. The PIC can be thought of as an expansion unit to the 8088’s single INTR pin, allowing 8 separate interrupt sources to be handled in a prioritized manner.

The PIC is a surprisingly challenging chip to emulate correctly, partially due to some ambiguities in its documentation.

Overview

The 8 inputs of the PIC are called Interrupt Request lines, which are often referred to as IRQs (Interrupt reQuest).

The PIC has three main 8-bit registers. Each bit in these registers corresponds to an IRQ with the least significant bit mapping to IRQ 0 and the most significant bit mapping to IRQ 7.

  • Interrupt Request Register (IRR): This register holds bits reflecting the IRQ lines that are requesting service.
  • Interrupt Mask Register (IMR): A 1 bit set in this register prevents the corresponding IRQ line from being serviced.
  • In-Service Register (ISR): A 1 bit set in this register indicates that the corresponding IRQ has been acknowledged by the CPU and is now “in-service”.

The 8 IRQs of the PIC are ordered in terms of priority, with IRQ 0 being the highest priority, and IRQ 7 being the lowest priority. This means that if IRQ 0 and IRQ 1 occur simultaneously, IRQ 0 will be serviced first.

This also means that it is possible for a higher priority interrupt to be serviced while a lower priority interrupt is already in service. Normally, the 8088 clears the I flag when executing an interrupt. If a programmer desires their interrupt service routine to be reentrant, they would need to issue an STI instruction to allow this to occur.

IBM PC PIC Configuration

IRQ Assignments

The IBM PC maps devices to the 8259’s IRQ lines as follows. Some of these connections are direct traces on the motherboard, other IRQs are connected to the ISA bus. In some cases, a peripheral card may have had a jumper to allow selection of a particular IRQ.

IRQConnectionDevice
IRQ 0DirectSystem Timer
IRQ 1DirectKeyboard Controller
IRQ 2ISA/DirectVsync Interrupt (EGA) or Secondary 8259 (AT)
IRQ 3ISASerial Port - COM2
IRQ 4ISASerial Port - COM1
IRQ 5ISAHard Disk Controller or LPT2
IRQ 6ISAFloppy Disk Controller
IRQ 7ISAParallel Port - LPT1

I/O Ports

The 8259 has a single address pin, A0, via which one of two registers can be selected. The two registers are decoded by the PC at the following addresses:

PC Port8259 PortRWDescription
0x200RWCommand/Status Register
0x211RWData/Mask Register

The Unimportant Stuff

The 8259 was designed to be compatible not only with the 8088 and 8086, but with Intel’s earlier CPUs, the 8080 and 8085. There are various mode flag bits that control whether the 8259 should expect to be paired with an 8088 or not - obviously, on the IBM PC, you can expect these bits to be set properly, and emulation of the 8080 modes is certainly not required.

The 8259 supported daisy-chaining of additional 8259 chips to enable more interrupt sources to be handled - something that Intel called cascading. This was not used on the IBM PC, which only had a single 8259. Two 8259s were employed on the IBM 5170 AT in a primary/secondary configuration. Obviously this is something you do not need to emulate either.

The PIC can be operated in edge-triggered or level-triggered mode. The IBM PC exclusively operates in edge-triggered mode, but implementing level-triggered mode is fairly trivial to do.

There are additional features like priority rotation and special mask mode which are not used by the IBM PC.

Interrupt Processing Logic

The important thing to understand about the PIC is that it is implemented as an array of 8 priority cell circuits. The IRR, IMR, and ISR “registers” are simply latches within each priority cell. This means that a good part of the interrupt evaluation logic happens continuously, since it is simply driven by immediate state of the circuit. The PIC has no clock input, so all it can do is respond to changes of its input pins. With one exception - but we’ll talk about that later.

Let’s look at an example of interrupt logic flow:

The Simple Version

  1. A device raises its IRQ line connected to the PIC.
  2. The PIC looks to see if that IRQ is masked off in the IMR, if it is already in service, or if a higher-priority interrupt is in service, in which case the interrupt cannot be serviced at the moment.
  3. If the IRQ can be serviced, the PIC raises the INTR line to the CPU.
  4. The CPU acknowledges the interrupt and the PIC sets the corresponding bit in the ISR to indicate that the interrupt is now in service. The corresponding bit in the IRR is cleared to indicate the IRQ is no longer requesting service.
  5. The CPU executes the interrupt based on the 8-bit vector the PIC provides in response to the CPU’s interrupt acknowledgement.
  6. The interrupt service routine executes. When it is done, it sends an End-of-Interrupt (EOI) command to the PIC.
  7. The PIC clears the corresponding bit in the ISR indicating that the interrupt has completed servicing.

The original IRQ line may remain high at this point, but in the PC’s standard edge-triggered mode it will not be serviced again until it transitions low and then high again.

The Detailed Version

  1. A device raises its IRQ line connected to the PIC.
  2. The low-to-high transition of the IRQ line sets the IRQ’s edge latch and sets the corresponding bit in the IRR.
    1. If the corresponding bit in the IMR is set, the signal from the IRR does not propagate further.
    2. If the corresponding bit in the IMR is clear, the signal from the IRR reaches the Priority Resolver.
  3. The priority resolver checks to see if the corresponding bit in the ISR is set, or if any bits lower than the corresponding bit are set, indicating a higher priority interrupt is already in service.
    1. If any of these bits are set, nothing further happens for the moment.
    2. If none of these bits are set, the priority resolver will instruct the control logic to raise the PIC’s INTR pin, which is connected to the CPU.
  4. The CPU finishes an instruction, at which point it samples the INTR pin.
  5. The CPU notices INTR is high.
    1. If the CPU’s I flag is cleared, it ignores INTR being high and continues execution as normal.
  6. The CPU begins to acknowledge the interrupt.
    1. The CPU issues one bus cycle with the INTA bus status encoded.
    2. The CPU bus controller decodes the INTA bus status and asserts the physical \(\overline{INTA}\) pin.
  7. The PIC detects the \(\overline{INTA}\) pin going high.
    1. The priority resolver sets the highest-priority bit in the ISR to indicate that the interrupt is entering service.
    2. In edge-triggered mode, the priority resolver clears the corresponding bit in the IRR.
    • This is the only difference between edge-triggered mode and level-triggered mode.
    1. The control logic asserts the internal \(\overline{FREEZE}\) signal. This prevents any change to any bit in the IRR while the interrupt acknowledge process is active.
  8. The \(\overline{INTA}\) pin goes low as the CPU completes the INTA bus cycle.
  9. The \(\overline{INTA}\) pin goes high again as the CPU issues a second INTA bus cycle.
  10. The PIC emits the 8-bit interrupt vector which the CPU reads during the second INTA bus cycle.
  11. The \(\overline{INTA}\) pin goes low as the CPU completes the second INTA bus cycle.
    1. The control logic de-asserts the \(\overline{FREEZE}\) signal, allowing the IRR to update again.
    2. In auto-EOI mode, the priority resolver clears the corresponding ISR bit.
  12. The CPU uses the interrupt vector received from the PIC to look up a far pointer to the correct Interrupt Service Routine from the Interrupt Vector Table.
  13. The CPU clears the I and T flags, then jumps to the interrupt service routine.
  14. At the end of the interrupt service routine, the routine sends an EOI command to the PIC.
  15. The PIC clears the appropriate bit in the ISR to indicate that the interrupt has completed servicing.