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" model (1983) there were actually two 8259's used together, one "cascaded" through the other. The functionality was later moved into southbridge chipsets, and superceded by the more advanced "IOAPIC", but the 8259-pair programming interface and 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). As part of the interrupt handshake, the PIC also informs the processor of the interrupt number corresponding to the interrupt request by outputting the number to the data bus (the processor looks up the handler vector in a table, using the interrupt number as an index; the specifics depend on the current processor operating mode).
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 (and alternative values, which may not be supported in the PC AT, are listed in parentheses). The ICWs must be sent in 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, for 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, for 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).
An astute reader may notice that ICW4 selects "non-buffered mode" and does not inform the 8259A whether it should act as master or slave. In this non-buffered mode, the master/slave selection is made by the signal on an external pin which is hardwired appropriately for each 8259A in the PC AT.
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 ------------------- poll action 0 - don't issue "poll" command 1 - issue "poll" command 3 ------------------- 1 (identifies OCW3) 4 ------------------- 0 5-6 ----------------- set/reset special mask mode 00/01 - no special mask mode action 10 - reset (disable) special mask mode 11 - set special mask mode 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. Lower 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 so 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. I do not recommend its use; particularly with level-triggered IRQs it seems likely to result in spurious interrupts.
An interrupt on the slave PIC will require an EOI to be issued both to the slave and the master (for the cascade line, IRQ2). Note that in the usual mode, the slave can have only interrupt in service at a time, and 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, or IRQ 15 on the slave), 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) because an EOI should not be sent to acknowledge a spurious interrupt. Note however that a spurious interrupt generated in the slave PIC will generally require an EOI in the master (the master is not aware that the interrupt is spurious).
(It might seem impossible for edge-triggered interrupts to be "de-asserted" but in fact the 8259 requires that the level after the rising edge is sustained for a certain period).
To detect a spurious interrupt, the handler for the IRQ 7/15 interrupt can check the ISR in the appropriate 8259A and see if bit #7 is set; if not, the interrupt is spurious. The handler may also need to separately keep track of whether a legitimate IRQ 7 is currently being handled, since if a spurious interrupt arrives while that is the case (and before an appropriate EOI has been issued), bit #7 in the ISR will be set and not clear; This is normally only necessary (unless special fully-nested mode is active) for the master PIC, since the slave cannot normally issue another interrupt while it has one in service (because IRQ 2 in the master will be marked as in-service). It would also be unnecessary to separately track in-service state of IRQ 7 If the handler keeps interrupts disabled at least up until it sends EOI.
Since automatic-EOI mode will clear the ISR when an interrupt is generated, using it may make it difficult or impossible to differentiate spurious interrupts from legitimate interrupts.
Special mask mode
There is a "special mask mode" which can be enabled via OCW3, to allow better software control over prioritising different IRQs. The original Intel datasheet confusingly states only that:
In the special Mask Mode, when a mask bit is set in OCW1, it inhibits further interrupts at that level and enables interrupts from all other levels (lower as well as higher) that are not masked.
What it actually means is that an IRQ that is in-service (as marked in the ISR) and also masked (via the IMR) will not prevent a lower priority IRQ from generating an interrupt. That is, when an IRQ is generated, the normal check for higher-priority IRQs already in service (which would prevent an interrupt) ignores those higher-priority IRQs which are masked.
The datasheet is not completely correct, in that setting a mask bit via OCW1 does not "enable interrupts" from all lower-priority levels; rather, it will allow generating interrupts via all IRQs that do not have a higher-priority IRQ that is both unmasked and currently in-service.
The idea is that on entry to the interrupt handler for some IRQ, the handler can immediately send an OCW1 to set the IMR, masking its own IRQ as well as any other IRQs that the software considers to be lower priority (as opposed to IRQs that are lower priority by the PIC's own priority scheme). If it then re-enables interrupts in the processor, it will be possible for unmasked IRQs (i.e. those that are higher-priority, software-wise) to interrupt the current handler, even though an IRQ which may be higher-priority (according to the PIC) is in-service.
Polling
A "poll command" can be issued via OCW3. This can be used to check for, and acknowledge, a pending interrupt; the next read from either of the PIC's IO ports will yield the highest priority IRQ currently pending, if any, with bit 7 set to indicate a pending IRQ (which is not masked and has no higher-priority IRQ in service) or clear if there is no pending IRQ. If an IRQ is returned, the corresponding bit in the ISR is set (and presumably the bit in the IRR is cleared, though Intel documentation fails to mention this).
Polling is intended to be used with interrupts disabled in the CPU.
Since "polling" an IRQ sets its ISR bit, it will no longer generate an interrupt if interrupts are later enabled. An EOI must be issued as usual to clear the ISR bit.
Once a value is read, whether an IRQ was pending or not, the poll command is complete.
Interrupt sharing
In theory it is possible, assuming correct electronic design, for ISA devices (using edge-triggered interrupt requests) to share an interrupt line, although interrupt sharing was not part of the ISA standard. When two or more such devices are sharing an interrupt, the interrupt handler must check each device in turn and service it if necessary, since a simultaneous interrupt request from two devices will register as only a single interrupt request in th 8259A. This in turn requires that it be possible to query each device using the shared IRQ and determine if it does in fact require service, which is not supported by all devices.
(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 also prevent spurious interrupts.
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 systems with ELCRs.
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 (single) "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 the PIC, or 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 delay between them (assuming that the handler sends an EOI).
IRQ 2 (cascade) must be enabled in the master 8259 for any interrupts generated in the slave 8259 to be serviced.
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. This would not be a problem with level-triggered interrupts.
Although Intel documentation is vague on the topic, a device can request an interrupt (causing the corresponding bit in the IRR to be set) even when it currently has an interrupt in service (correponding bit in the ISR is set). This will cause another interrupt to be generated once an EOI signals that the IRQ is no longer in service (the usual priority rules apply); if this were not the case, it would be necessary to send EOI before servicing a device, or risk losing any interrupt request from the device that came between servicing it and sending EOI.
Being masked (via the IMR) does not prevent an interrupt request from setting the corresponding bit in the IRR. It is safe to mask interrupts temporarily without fear of losing an interrupt.
Micro-channel (MCA) based systems (80's-90's, rare even then) use level-triggered interrupts even for system board devices, and require that the 8259s are initialised to use level-triggering. They do not support the ELCR registers. These interrupt controllers have an ACPI ID of PNP0002 (but I do not know if such machines ever supported ACPI).
Modern systems do not have physical 8259 chips but implement their functional interface as part of a chipset. Although I am not aware of specific cases, it is possible that some of less widely-used features (such as special mask mode, and the poll command) may not be implemented correctly or at all. It may be wise to avoid relying on these features.
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.