8259 PIC: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
No edit summary
m (Spelling..)
Line 1: Line 1:
{{In Progress}}
{{In Progress}}


The PIC stands for "Programmable Interrupt Controler" and is one of THE important chips in the PC. Without it, x86 would not be an interrupt driven architecture.
The PIC stands for "Programmable Interrupt Controller" and is one of THE important chips in the PC. Without it, x86 would not be an interrupt driven architecture.


== What does the PIC do ==
== What does the PIC do ==
Line 39: Line 39:
* 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).
* 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.
* 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.
* if you plan to return to real mode (for any purpose), you really must restore the PIC to its former configuration.


== Programming the PIC chips ==
== Programming the PIC chips ==

Revision as of 20:03, 19 February 2007

This page is a work in progress.
This page may thus be incomplete. Its content may be changed in the near future.

The PIC stands for "Programmable Interrupt Controller" and is one of THE important chips in the PC. Without it, x86 would not be an interrupt driven architecture.

What does the PIC do

There needs to be a way for peripherals and other devices external of the CPU to tell the system that an event has happened or needs to happen. Examples of this: hard disk IO, modem/serial ports, keyboard.

The PIC controls the CPU's interrupt mechanism, by accepting several interrupt requests and feeding them to the processor in order.

Without the PIC interface, you would have to poll all the devices in the system to see if they want to do anything (signal an event), but with the PIC, your system can run along nicely until such time that a device wants to signal an event, which means you don't waste time going to the devices, you let the devices come to you when they are ready.

In the beginning, the age of the IBM XT, we had only 1 PIC chip giving us 8 hardware interrupt lines, but the 8259A PIC chip has a neat ability, it can cascade!

Cascading means you can daisy chain PIC chips together. This is what happened with the introduction of the IBM AT, we had a second PIC chip cascaded onto the first, giving us a total of 15 hardware lines... Why 15 and not 16? That's because when you cascade chips, the PIC needs to use one of the int lines to signal to the other chip.

Thus, in an AT, IRQ line 2 is used to signal the second chip... But to confuse things more, IRQ 9 is redirected to IRQ 2. So when you get an IRQ 9, the signal is redirected to IRQ 2.

How does the PIC work

Each of the two PICs in modern systems have 8 inputs. When any of the inputs is raised, the PIC sets a bit internally telling one of the inputs needs servicing. It then checks whether that channel is masked or not, and whether theres an interrupt already pending. If the channel is unmasked and theres no interrupt pending, the PIC will raise the interrupt line. On the slave, this feeds IRQ 2 to the master, and the master is connected to the processor interrupt line.

When the processor accepts the interrupt, the master checks which of the two PICs is responsible for answering, then either supplies the interrupt number to the processor, or asks the slave to do so. The PIC that answers looks up the "vector offset" variable stored internally and adds the input line to form the requested interrupt number. After that the processor will look up the interrupt address and act accordingly.

The default (BIOS-defined) vector offsets are 8 for Master PIC and 0x70 for Slave PIC:

  • Master: IRQ 0..7 -> INT 8..0xF (vector offset = 0x08)
  • Slave: IRQ 8..15 -> INT 0x70..0x77 (vector offset = 0x70)

However, 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). Consequently you wouldn't be able to tell the difference between an IRQ or an software error.

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 sample 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 configuration.

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
Master PIC data 0x21
Slave PIC command 0xA0
Slave PIC 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 additional 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);
}

Note the presence of io_wait() calls, on older machines its necessary to give the PIC some time to react to commands as they might not be processed quickly