8259 PIC

From OSDev.wiki
Revision as of 16:23, 3 December 2006 by Combuster (talk | contribs) (ported PIC topics, note: still needs work)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
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 Controler" 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 perhiperals and other devices external of the CPU to tell the system than 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 begining, 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.

Notions about the PIC

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 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 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
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 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);
   }

Questions and Answers

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