User:Johnburger/PIC

From OSDev Wiki
Jump to navigation Jump to search

This page is under construction! This page or section is a work in progress and may thus be incomplete. Its content may be changed in the near future.

Priority Interrupt Controller (PIC)

Introduction

The 8086/8088 CPU used in the original IBM PC had a single External Interrupt input called INTR. If an attached device signalled this line, the CPU would stop what it was doing, remember where it was up to (saving certain registers on the stack), and start executing the interrupt handler. At the end, the interrupt handler would execute an IRET instruction and the CPU would return to what it was doing before it was interrupted. However, if a number of attached devices wanted to produce interrupts, they would have to share the single INTR pin and let the single interrupt handler sort out which device(s) were interrupting.

The Intel 8259 PIC was originally designed as a support chip for the 8086/8088 (and 8080/8085 - never mind!), to extend the CPU's single interrupt pin and allow a number of different devices to interrupt it. The PIC had 8 inputs, and it would prioritise these for the CPU to ensure that the more important interrupts were serviced before - or even during - less important ones. Of course, this complicated interrupt handling for all hardware interrupt handling code, but this complexity was seen as necessary for a true computer like the IBM PC.

System design

Main article: User:Johnburger/PIC/Background

Initialisation

The PIC by default provides access to only two of its registers: the Command register and the Interrupt Mask register. You can Command it to read its Interrupt Request Register or In-Service Register, but that's a two-step operation. The Master PIC uses I/O port addresses 020h and 021h respectively; the Slave uses I/O port addresses 0A0h and 0A1h respectively.

To initialise a PIC from scratch is a four-step operation: an explicit sequence of writes needs to be performed, and this sequence is different between the Master and the Slave:

    MOV    AL, 00010001b    ; b4=1: Init; b3=0: Edge; b1=0: Cascade; b0=1: Need 4th init step
    OUT    020h, AL         ; Tell Master
    OUT    0A0h, AL         ; Tell Slave

    MOV    AL, 08h          ; Master's IRQ0 shoud be on INT 08h
    OUT    021h, AL         ; Tell Master
    MOV    AL, 70h          ; Slave's IRQ8 should be on INT 70h
    OUT    0A1h, AL         ; Tell Slave

    MOV    AL, 00000100b    ; Master has a Slave on input #2
    OUT    021h, AL         ; Tell Master
    MOV    AL, 02h          ; Slave is on Master's input #2
    OUT    0A1h, AL         ; Tell Slave

    MOV    AL, 00000001b    ; b4=0: FNM; b3-2=00: Master/Slave set by hardware; b1=0: Not AEOI; b0=1: x86 mode
    MOV    021h, AL         ; Tell Master
    MOV    0A1h, AL         ; Tell Slave

Note that at the end of the above initialisation sequence, all interrupts are masked off. You can read 021h or 0A1h and observe the bits which are set (this should be all of them!). Clear the appropriate bit (b0=IRQ0 or IRQ8) to enable the corresponding interrupt through to the CPU.

Servicing an Interrupt

CPU Actions

When an external interrupt occurs, and the CPU's Interrupt flag is set, the CPU will start the Interrupt handling mechanism:

  • If it is in Real Mode, it will:
    • Push a minimal state onto the stack;
    • Disable interrupts;
    • Load the appropriate address from the Interrupt Vector Table (IVT at 0000h:0000h);
    • Start executing at the new address.
  • If it is in 32-bit Protected Mode, it will:
    • Reference the appropriate entry in the Interrupt Descriptor Table;
    • If necessary switch stacks or tasks to make sure it will be running in Ring 0 / Supervisor Mode;
    • Push a minimal state onto the stack;
    • If defined in the IDT, disable interrupts;
    • Start executing at the new address.

Interrupt Handler actions

At this point the CPU is the programmer's responsibility. There is a fundamental minimum that the programmer must do: if they don't happen, the whole system will crash:

  • Any registers used by the interrupt handler must be saved before being changed, and of course restored to their original values before the end of the interrupt handler. This amounts to all of them: the "minimal state" saved above is essentially only the CPU flags and the address of the interrupted instruction;
  • Before the end of the interrupt, the appropriate PIC(s) must be sent an EOI command. Failure to do so will prevent this interrupt - and all lower-priority ones - from ever happening again.
  • At the end of the interrupt, the interrupt handler must end with some form of the IRET command. This will fully restore the CPU's state before the interrupt occurred.

Apart from the above, there's something else that the programmer can consider doing: re-enable the CPU's interrupt flag. Doing so will allow the PICs' priority mechanisms to work again, allowing interrupts with a higher priority through.

Interrupt Handler considerations

Given these two independent actions, what (and when!) is the correct order to do them in?

  • If interrupts are re-enabled too early, more important interrupts may interrupt this one before critical information can be retrieved from the device. This runs the risk of important information being lost;
  • If the EOI is commanded before the device has been told that its interrupt has been serviced:
    • The device may immediately re-signal the interrupt;
    • The interrupt handler would immediately restart;
    • It would again re-command the EOI;
    • Which would re-signal the interrupt;
    • Which would start the interrupt handler again...

In short, the CPU will be locked up until the stack runs out of room, crashing the system.

  • If both the Master and the Slave PICs need an EOI sent to them, what is the correct order?
    • If the Master is commanded first, it may immediately signal a low-priority interrupt that has been waiting for a while. This would leave the Slave PIC unacknowledged and unable to forward other interrupts until it was finally acknowledged when the CPU resumed the previous interrupt handler;
    • If the Slave is commanded first, it may immediately signal a new low-priority interrupt of its own - but the Master PIC will ignore it until it has had its acknowledgement. At least in this scenario the Master will behave correctly.

It is obvious in the final example that it is critical that a Slave interrupt handler acknowledge the PICs in the order of Slave first, then Master.

Interrupt Handler EOI Command

To actually acknowledge that an interrupt has been handled, the interrupt handler has to send an EOI command to the appropriate PIC(s)' Command registers. As described above, this is likely to be a Non-specific EOI, which has the value 20h:

Known Master-only EOI command:

    MOV    AL, 20h    ; EOI
    OUT    020h, AL   ; Tell Master

Known Slave-only EOI command:

    MOV    AL, 20h    ; EOI Command
    OUT    0A0h, AL   ; Note Slave first!
    OUT    020h, AL   ; Then Master

If the same interrupt handler is used for both a potential Master and Slave interrupt, it is very important that only the relevant PIC(s) get commanded - failure to do so may crash the system.

The new APIC - Advanced Priority Interrupt Controller

Legacy Mode

See Also

Articles