The Intel i8259A Programmable Interrupt Controller (PIC), AKA "Legacy PIC", is the IC used as the interrupt controller for the original IBM PC and later variants. Since the PC AT (1983) there were actually two 8259's used together, one "cascaded" through the other. The functionality was moved into southbridge chipsets, and superceded by the more advanced "IOAPIC", but the 8259-pair functionality remains present in machines even today (2024).
The PIC receives interrupt requests (IRQs) from devices, via IRQ lines into the PIC. It is responsible for prioritising/masking individual requests (from devices) and passing them on to the CPU, which historically was via an interrupt input line on the CPU package (with the interrupt number being supplied to the CPU via the data bus).
In the PC AT and beyond, as mentioned above, there are two 8259A's which are "cascaded", one is the "master" and the other is the "slave". Only the master is connected to the CPU's interrupt input; the slave is connected instead to an IRQ input in the master. The master device is configured so that, when it receives an IRQ signal from the slave device, it does not put an interrupt number on the data bus but instead expects the slave to perform that duty. This setup is emulated in modern chipsets, at least until now, though of course there are no longer physical 8259A chips on your motherboard (and, there are quiet signs that hardware manufacturers will eventually remove the legacy PIC functionality, though it's hard to predict when that will happen in earnest).
Basic operation
When an 8259A receives an interrupt request, it marks the corresponding IRQ line as "in service" and raises an interrupt in the processor. In the processor, the interrupt handler routine does whatever processing is required, and then informs the PIC that processing is complete by issuing an "EOI" (end of interrupt) command at which point the PIC marks the IRQ as no longer in service.
An in-service IRQ line normally is not able to raise further interrupts until the IRQ is no longer marked as in-service. Therefore, once a device raises an interrupt, it can normally not raise an interrupt again until the first is handled (and EOI'd) by the processor. In addition, IRQs are prioritised, and a lower priority IRQ cannot raise an interrupt while a higher priority IRQ is in service.
(In the PC, the secondary 8259A cascades interrupts through the first. Thus, an EOI must be sent to both of the 8259A controllers to clear the relevant "in service" bit in both).
Programming the 8259 PICs
Note: the 8259 is way more complex than it really needs to be, for its use in the PC. Here I'll cover mainly the standard use, and brush over the rest. In any case deviation from standard register settings is less likely to work in new chipsets which (perhaps only partially) reproduce the functional interface.
Overview
The 2 PICs were/are mapped to I/O addresses:
- Master (IRQ 0-7): 0x20 - 0x3F (in practice use 0x20 and 0x21)
- Slave (IRQ 8-15): 0xA0 - 0xBF (use 0xA0 and 0xA1)
The 8259's interrupt request inputs are active high. The ISA bus architecture in early PCs actually used active low IRQ signalling, pulsing the line low briefly before it was raised high again (via a "pull up" resistor). By using the "edge triggered" mode, the 8259 would recognise the rising edge at the trailing end of the pulse as an interrupt request signal.
The 8259 can be configured to use level-triggered interrupt requests, but this was not useful on ISA-based PCs. In EISA systems (produced from 1988 onwards, largely superceded in 1993 by PCI), two additional "edge level control registers" (ELCRs) were located as follows:
- Master ELCR: 0x4D0
- Slave ELCR: 0x4D1
The slave 8259 was connected to IRQ lines 8-15, cascading via master's IRQ2.
In the initial configuration as set up by BIOS (for real mode) the base vector will be 0x8 for IRQS 0-7 (the master PIC) and 0x70 for IRQS 8-15. The interrupt number sent to the processor is the base vector plus the IR line (relative to the 8259; eg. for an interrupt in the slave, on IRQ 9, interrupt 0x71 would be selected, since this is the IR 1 input as far as the individual chip is concerned). When the processor is not running in real mode, the base vector of the master PIC must be re-programmed, to avoid conflicts with processor exceptions (interrupts 0-31).
Standard IRQ (and interrupt) assignments to (legacy) devices are as follows:
- IRQ 0 (interrupt 0x8) - 8254 Programmable Interrupt Timer
- IRQ 1 (interrupt 0x9) - 8042 (or PS/2) keyboard controller
- IRQ 2 - cascade for IRQs 8-15
- IRQ 3 (interrupt 0xB) - serial port controller for ports #2 and #4
- IRQ 4 (interrupt 0xC) - serial port controller for ports #1 and #3
- IRQ 5 (interrupt 0xD) - parallel port #3 or other peripheral (sound card etc)
- IRQ 6 (interrupt 0xE) - floppy disk controller
- IRQ 7 (interrupt 0xF) - parallel port #1 and #2
- IRQ 8 (interrupt 0x70) - CMOS Real Time Clock (RTC)
- IRQ 9 (interrupt 0x71) - no assignment (often used for ACPI System Control Interrupt)
- IRQ 10 (interrupt 0x72) - no assignment
- IRQ 11 (interrupt 0x73) - no assignment
- IRQ 12 (interrupt 0x74) - secondary device on PS/2 controller (normally mouse)
- IRQ 13 (interrupt 0x75) - math coprocessor exception
- IRQ 14 (interrupt 0x76) - primary ATA (IDE) channel
- IRQ 15 (interrupt 0x77) - secondary ATA (IDE) channel
Registers and commands
The PIC has three register tracking interrupt status:
- IRR
- Interrupt Request Register, flags which lines have seen requests that have not yet been raised to the CPU (because a higher priority request is already in service, or they are currently masked).
- ISR
- In Service Register, flags which lines have an interrupt in service (i.e. the interrupt number has been signalled to the CPU and acknowledged, but no EOI has been received yet). If an interrupt is in service it will not be raised again, and no lower-priorty IRQ will be raised, until an appropriate EOI is received.
- IMR
- Interrupt Mask Register. A masked interrupt line will not trigger an interrupt on the CPU.
An 8259A Accepts "ICW"s (Initialisation Command Words) and "OCW"s (Operation Command Words).
ICWs are sent in a sequence and used to set the base interrupt vector (for 'x86 mode) among other things. For example, the base vector can be set to 0x20 and then the interrupts that will be generated to the CPU are 0x20-0x27. The first ICW is sent to the low port (0x20 or 0xA0) and the rest are sent to the high port. Technically ICW3 & ICW4 are optional but they are necessary to correctly configure for x86 PC systems.
ICW bitmasks. Where values are given they are taken from the original PC AT BIOS routines. These must be sent in appropriate sequence:
ICW1 (sent to low port):
Bit(s) 0 ------------------- 1 = ICW4 will be sent (to select x86 mode) 1 ------------------- 0 = cascade mode (PC: there are 2 PICs) 2 ------------------- X Doesn't matter for x86 ("call address interval") 3 ------------------- 0 = edge triggered mode (1 = level triggered) 4 ------------------- 1 (identifies ICW1) 7-5 ----------------- X Don't matter for x86 mode
ICW2 (sent to high port):
Bit(s) 0-2 ----------------- Don't matter for x86 mode 3-7 ----------------- base vector address bits 3-7
ICW3, master (sent to high port; only sent if cascade mode):
Bit(s) 0-1 ----------------- 00 2 ------------------- 1 (IRQ2 has slave attached) 3-7 ----------------- 00000
ICW3, slave (sent to high port; only sent if cascade mode):
Bit(s) 0-2 ----------------- 010 (i.e. value 2, attached via IRQ2 to master) 3-7 ----------------- 00000
ICW4 (sent to high port):
Bit(s) 0 ------------------- 1 (select x86 mode) 1 ------------------- 0 (disable automatic EOI) 2-3 ----------------- 0X (non-buffered mode; 10=buffered slave, 11=buffered master) 4 ------------------- 0 (not "special fully nested mode") 5-7 ----------------- 0
Note that after initialisation, all interrupts are unmasked (although interrupts that were pending at initialisation time might not be recognised, due to edge-triggering, until the device raising them is appropriately serviced).
Note also that it is not necessary to programmatically inform an individual 8259A chip whether it is master or slave: that is determined by an external input pin which is hard-wired. This is why ICW3 can have a different format for the master and slave and each will interpret it correctly.
Operation Command Words (OCWs) can be used to set the mask register (mask particular interrupt request lines), signal an interrupt handler has completed (end-of-interrupt, EOI), or specify which register should be read via the low I/O port. Unlike ICWs these do not need to be sent in a sequence.
OCW1 (sent to high port)
Bit(s) 0-7 ----------------- set (1)/clear (0) mask bit for corresponding IRQ (i.e. write IMR)
OCW2 (sent to low port)
Bit(s) 7-5 ----------------- select operation: 000 - disable priority rotation when in automatic EOI mode 001 - non specific EOI 010 - no operation 011 - specific EOI (IRQ specified in bits 2-0) 100 - enable priority rotation when in automatic EOI mode 101 - non-specific EOI with priority rotation 110 - set lowest priority IRQ (specified in bits 2-0) 111 - specific EOI with priority rotation 4 ------------------- 0 3 ------------------- 0 2-0 ----------------- specify IRQ for operations 011/110/111
OCW3 (sent to low port)
Bit(s) 0-1 ----------------- specifies register to be read via low port reads: 10 = IRR, 11 = ISR, 0X = no action 2 ------------------- 0 (don't issue "poll" command) 3 ------------------- 1 (identifies OCW3) 4 ------------------- 0 5-6 ----------------- 00 = no special mask mode action 7 ------------------- 0
Reading IMR, ISR, IRR
The IMR can be read via the odd-numbered port address at any time (it can be written via the same port, as OCW1). The ISR and IRR are multiplexed (for reading) over the even-numbered port and OCW3 (sent to the same port) selects which will be read.
EOI signalling
Once an interrupt has been received by the CPU it is marked as "in service" in the ISR. Higher priority interrupts will not be sent to the processor until the "in service" bit is cleared, by means of an "End-of-interrupt" (EOI) command sent by the processor (OCW2 above). The typically-used "non-specific" EOI (value of 20h sent as OCW2 to low port) will mark the highest-priority currently-in-service interrupt as no longer in service (and allow that interrupt, and lower-priority interrupts, to be issued to the CPU again).
If any form of priority rotation is used, it may be necessary to instead send a "specific" EOI to ensure that the correct ISR bit is cleared.
An "automatic EOI" mode exists and effectively dispenses with the ISR. It could perhaps be used for slightly more efficient interrupt handling, but in this case it would be advisable to ensure that the processor keeps interrupts disabled for the entire duration of the interrupt handler, to avoid a flurry of interrupts coming in from a single device (particularly if level-triggered) and potentially overflowing the stack.
An interrupt on the slave PIC will require an EOI to be issued both to the slave and the master (for the cascade line). Note that in the usual mode, a higher-priority slave interrupt cannot interrupt the service routine of a lower-priority interrupt, since the ISR bit in the master is already set for the cascade line (and is not cleared until the EOI is sent to the master). The "special fully nested mode" overcomes this issue by allowing the slave to generate further interrupts while it is already marked as in-service, but note that this complicates the EOI logic (software needs to only send EOI to the master once no interrupts from the slave are in-service).
Interrupt priorities
After initialisation, the highest priority interrupt is #0 and each interrupt through to 7 decreases priority compared to the previous so that #7 has the lowest priority. Priority can be rotated by the use of EOI-with-rotation commands (these shift priority so that highest priority interrupt becomes the lowest priority, and every other interrupt increases in priority by one). The lowest priority can also be given to a specific interrupt via operation 110b in OCW2 (the general priority pattern remains fixed, i.e. setting IRQ N as the lowest priority results in IRQ N-1 being the next lowest priority and so on). Priority rotation has not (as far as I know) generally been employed in PC OSes.
Spurious interrupts
Due to the signal handshake between PIC(s) and CPU it is possible for an interrupt to be acknowledged by the CPU but de-asserted by the device before the PIC has transmitted the interrupt vector; if this happens the PIC produces a "spurious" interrupt #7 (i.e. the interrupt corresponding to IRQ 7), which will not set the ISR #7 bit but may occur even if interrupt #7 is already in service. This requires some care in handling and in the sending of EOI (and in the handling of IRQ 7 in general). Note also that a spurious interrupt generated in the slave PIC will require an EOI in the master (the master is not aware that the interrupt is spurious).
To detect a spurious interrupt, the handler for the IRQ 7 interrupt can check the ISR and see if bit #7 is set; if not, the interrupt is spurious. The handler may also need to keep track of whether a legitimate IRQ 7 is currently being handled, since if a spurious interrupt arrives while that is the case, bit #7 in the ISR will be set and not clear. This is normally only necessary (unless automatic EOIs or special fully-nested mode are active) for the master, since the slave cannot normally issue another interrupt while it has one in service.
Since automatic-EOI mode will clear the ISR when an interrupt is generated, using an automatic-EOI will make it difficult or impossible to differentiate spurious interrupts from legitimate interrupts.
Interrupt sharing
In theory it is possible, assuming correct electronic design, for ISA devices to share an interrupt line, although interrupt sharing was not part of the ISA standard. When two or more devices are sharing an interrupt, the interrupt handler must check each device in turn, since a simultaneous interrupt from two devices will register as only a single interrupt request. (A level-triggered scheme such as is used in EISA or PCI, in contrast, only requires one device to be serviced per interrupt, since devices that still require servicing will be holding the interrupt request active and so will re-trigger the interrupt if an EOI is issued before they are serviced).
Disabling the PIC
A PIC can be effectively disabled by masking all interrupts, i.e. by writing an all-1's mask to the odd-numbered port. This should in theory also prevent spurious interrupts (but it might not be a chance worth taking, i.e. it may be worth setting up an IRQ 7 handler).
ELCR (edge-level control registers)
In the EISA system variant of the 8259, IR lines can be set as edge- or level- triggered on an individual basis. This is done via the 8-bit ELCR registers at ports 0x4D0 (master) and 0x4D1 (slave). Each bit corresponds to an IR line and selects edge-sensitive (0) or level-sensitive (1) operation for the line. Note that certain system board devices associated with certain interrupt request lines require edge-sensitive operation: IRQ0 (system timer), IRQ1 (keyboard controller), IRQ2 (cascade), IRQ8 (Real-time Clock alarm), IRQ13 (FPU error signal). The global edge-vs-level select bit in ICW1 is ignored in these systems.
The ELCR registers from EISA were carried through into at least some (and possibly most or all) chipsets designed around PCI, such as the Intel ICH5 (~2003) and Intel 9-series PCH (~2015). Handling PCI interrupts in the PIC may require programming the corresponding interrupt request line as level-triggered. Documentation for the Intel 9-series PCH indicates that PCI interrupt signals are internally inverted before being routed to the legacy PICs, so they are effectively active high as the 8259 requires; it's likely that other (post-)PCI-era chipsets do the same. (IRQ lines can therefore be shared between PCI devices, but they can not be shared between PCI and ISA devices including legacy system devices).
Note that firmware will (via the ACPI namespace) typically advertise the 8259 PIC pair as a "PNP0000" class device, i.e. AT interrupt controller. If the ELCR is present (which in modern machines it generally should be) this is not really correct; it should be PNP0001 (EISA interrupt controller). If PNP0000 is used then the ELCR register IO ports (4D0h, 4D1h) may be marked as resources of a generic system board device (PNP0C02).
General notes
Since reinitialising the 8259 unmasks all IRQ lines, it is usually best done with interrupts disabled at the CPU level.
It's advisable to mask IRQs for any devices (any IRQ lines) that are not specifically handled. Unmasking them can only result in useless interrupts. In the case of level-triggered IRQs this can even result in an endless series of interrupts with no time in between (assuming that the handler sends an EOI).
Unserviced edge-triggered interrupts raised by devices prior to initialisation of the 8259 may be lost. A device that requires servicing before it will generate interrupts may therefore not do so unless it is explicitly serviced. For example, the keyboard controller buffer typically should be polled and cleared to ensure that the controller will generate further interrupts.
Micro-channel (MCA) based systems (80's-90's, rare even then) use level-triggered interrupts even for system board devices, and require the 8259s are initialised to use level-triggering. They do not support the ELCR registers.
Please use comments for suggestions or corrections only.
Comments may be deleted once actioned.
Requests for help will be deleted immediately, use an appropriate forum instead.