What's the PIC anyway ?

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:

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,

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

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


Categories: HowTo, HardWareIrq

Notice: "Optimizing database"