What's the PIC anyway ?
- P*rogrammable *I*nterrupt *C*ontroller. See InterruptsForDummies and What is the PIC? for details ...
There are two PIC "chips" (8259A) in PC design which are organized in a master/slave scheme: everything the slave controller has to report goes through a line of the master controller (hardwired on master's channel 2 in the PC design).
What's that noise about remapping ?
PICs can be configured to use a "vector offset" that is added to their IRQ line numbers to form interrupt vectors. Master and Slave PICs have each its own offset, independent of one another.
The default (BIOS-defined) vector offsets are 8 for Master PIC and 0x70 for Slave PIC:
- Master: IRQ 0..7 -> INT 8..0xF
- Slave: IRQ 8..15 -> INT 0x70..0x77
These default values don't suit the needs of ProtectedMode programming: there's a collision between IRQs 0..7 (mapped to INT 8..0xF) and processor exceptions (INT 0..0x1F are reserved).
from Intel manual v.3 p.5-2
"Vectors in the range 0 through 31 are reserved by the IA-32 architecture for architecture-defined exceptions and interrupts. Not all of the vectors in this range have a currently defined function. The unassigned vectors in this range are reserved. Do not use the reserved vectors.
The vectors in the range 32 to 255 are designated as user-defined interrupts and are not reserved by the IA-32 architecture. These interrupts are generally assigned to external I/O devices.. "
It's thus recommended to change the PIC's offsets (also known as remapping the PIC) so that IRQs use non-reserved vectors. A common choice is to move them to the beginning of the available range (IRQs 0..0xF -> INT 0x20..0x2F). For that, we need to set Master's offset to 0x20 and Slave's to 0x28.
This can be done by calling remap_pics(0x20, 0x28); (see code below).
Note however that,
- each PIC vector offset must be divisible by 8, as the 8259A uses the lower 3 bits for the interrupt number of a particular interrupt (0..7).
- the only way to change the vector offsets used by the PIC is to re-initialize it, which explains why the code is "so long" and plenty of things that have apparently no reasons to be here.
- if you plan to return to real mode (for any purpose), you really must restore the PIC to its former configration.
Programming the PIC chips
Each chip (master and slave) has a command port and a data port (given in the table below). When no command is issued, the data port allows us to access the interrupt mask of the PIC.
PIC ports:
| Master PIC | command | 0x20 |
|---|---|---|
| data | 0x21 | |
| Slave PIC | command | 0xA0 |
| data | 0xA1 |
A common command for the PIC is the end of interrupt command (code 0x20), but there's also the "initialize" command (code 0x11), which makes the PIC wait for 3 extra "initialization words" on the data port. Those data bytes gives the PIC
- its vector offset (ICW2),
- tell it how it is wired to master/slaves (ICW3)
- gives additionnal infos about the environment (ICW4)
Code
/* reinitialize the PIC controllers, giving them specified vector offsets
rather than 8 and 70, as configured by default */
#define PIC1 0x20 /* IO base address for master PIC */
#define PIC2 0xA0 /* IO base address for slave PIC */
#define PIC1_COMMAND PIC1
#define PIC1_DATA (PIC1+1)
#define PIC2_COMMAND PIC2
#define PIC2_DATA (PIC2+1)
#define PIC_EOI 0x20 /* End - of - interrupt command code */
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
/*
arguments:
offset1 - vector offset for master PIC
vectors on the master become offset1..offset1+7
offset2 - same for slave PIC: offset2..offset2+7
*/
void remap_pics(int offset1, int offset2)
{
UCHAR a1, a2;
a1=inb(PIC1_DATA); // save masks
a2=inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT+ICW1_ICW4); // starts the initialization sequence
io_wait();
outb(PIC2_COMMAND, ICW1_INIT+ICW1_ICW4);
io_wait();
outb(PIC1_DATA, offset1); // define the PIC vectors
io_wait();
outb(PIC2_DATA, offset2);
io_wait();
outb(PIC1_DATA, 4); // continue initialization sequence
io_wait();
outb(PIC2_DATA, 2);
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
outb(PIC1_DATA, a1); // restore saved masks.
outb(PIC2_DATA, a2);
}
- Q
- What does that io_wait() function do ?
- A
- It forces the CPU to wait a little before going on, so that the PIC got the time to react. Simply jumping forward a few times or doing a small loop is usually enough. The exact timing doesn't really matter.
Note that even linux kernel is weird regarding to this feature, allowing a REAL_SLOW_IO flag make delays with 4 times more jumps or by writing to a 'dummy' port (0x80)
- Q
- Am i the only one to think ICW4_8086|ICW4_BUF_MASTER and ICW4_8086|ICW4_BUF_SLAVE should be sent to the PIC instead of raw 1 ?''
-- PypeClicker
- A
- Yes, you are. ;) Under normal circumstances, the PIC chip uses the SP/EN pin as an input pin to determine whether it is master or slave. Setting the BUF bit (3) in the PIC ICW4 causes the chip to instead use the SP/EN pin as an output pin for activating external buffers. Since PC-style computers are not wired up this way, that bit should never be set, although it probably doesn't do any harm other than requiring you to then use the M/S bit (2) to tell each PIC its function (since it's no longer using the SP/EN pin to tell). This information comes from The Indispensable PC Hardware Book by Hans-Peter Messmer.
-- DaidalosGuy
Further reading
- "The Indispensable PC Hardware Book" by Hans-Peter Messmer explains it nicely.
The datasheet from Intel - "8259A Programmable Interrupt Controller"
- Utterly fails to explain what is going on. Has pinout and electrical signal levels, in case you need them. It should have the words "Abandon hope.." written in large, burning letters on the cover..
- Cornelis Frank's "8259A Interrupt controller on the PC" - describes all PIC registers and how to program them, also lists IRQ's and devices that use them.
http://sardes.inrialpes.fr/~taton/system2/doc/Clavier/8259pic.pdf
Categories: HowTo, HardWareIrq
