StuBS
APIC-Architecture: External Interrupts on modern PCs

Modern PCs no longer use the PIC8259A to connect external interrupts to the CPU. They use the modern Advanced Programmable Interrupt Controller (APIC) instead. The APIC-Architecture consists of several chips:

Schema der APIC Architektur auf Intel IA-32/AMD64
  • Each CPU has a Local APIC which handles interrupt requests from external sources and forwards them to its CPU core. On multiprocessor systems it also sends inter-processor-interrupts to other CPUs, allowing communication between individual CPUs.
  • External interrupt sources such as keyboard, mouse, timer etc. are connected to the so-called IO-APIC. It has 24 input pins and each of them has the same priority, meaning the IO-APIC reacts in a FIFO order to serveral external interrupts.

All of a system's APICs (Local as well as IO) communicate either via the system bus or a dedicated APIC bus. However, this does not matter to the system programmers (that is you 😉), since both implementations have exactly the same interface.

The assigment of external interrupts to the CPU's interrupt vectors is programmable via the IO-APIC. The IO-APIC has a table (Redirection Table) for that purpose. Each input pin has an entry in this table, allowing you to assign which of the CPU's interrupt vector should be triggered. Furthermore you can mask an interrupt and configure its type (edge- or level-triggered). Additionally, it allows you to decide to which subset of processors an interrupt should be delivered.

If the IO-APIC detects that one of its interrupt sources sent an interrupt request, it will decide to which CPU the interrupt vector is going to be sent based on its configuration. The respective CPU's Local APIC will then receive the interrupt and forward it to its processor core. The core will then start the its interrupt service routine. After the processor has executed its interrupt service routine, the Local APIC must be informed that the procedure has completed. Once the latter has been done, you will be able to receive new interrupt requests of the respective type.

In contrast to the IO-APIC the Local APIC actually prioritizes interrupts based on their interrupt vector number:

priority = vector / 16

Since vectors 0 to 31 are reserved for CPU-local exceptions (e.g. Invalid Opcode or Division by Zero), priorities 2 to 15 are available to the application.

Software

Ignoring Interrupts

It is possible for the CPU to ignore incoming interrupt requests during programme execution by clearing the EFLAG register's interrupt flag via the cli instruction. After execution of this instruction, the CPU will no longer react to interrupts delivered via the Local APIC. The command sti is used to activate processing of interrupts again.

External interrupts can be suppressed selectively by programming the IO-APIC. As the APIC subsystem is relatively new, its components are not programmed via the in and out commands, but via memory-mapped registers.

Interrupt Service Routines

When an external device's interrupt request arrives at the processor, it automatically branches to the corresponding interrupt service routine.

The routine's address is taken from the Interrupt Descriptor Table (IDT). The interrupt vector number configured in the IO-APIC serves as an index into this table. The vector number is transmitted from the IO-APIC via the APIC or system bus to the Local APIC, which then ensures that the CPU is interrupted. While the IDT was located at a fixed address on 8086 processors, the 80386 processor family stores its location and length in a special IDT register.

The Interrupt Descriptor Table can store up to 256 interrupt gate descriptors. These descriptors are divided into three different categories:

Task-Gate

The task gate descriptor points to a task. You can think of tasks as processes in an operating system, but directly supported by hardware. The processor automatically performs a context switch to this hardware task, when the descriptor's respective interrupt occurs.

Interrupt Gate

The interrupt gate descriptor points to a procedure that serves as interrupt service routine. The processor does not performing a task switch. The CPU automatically disables interrupts before calling the function.

Trap Gate

The trap gate descriptor points to a procedure that serves as a trap service routine. The processor does not perform a task switch. In contrast to the interrupt gate descriptor, however, interrupts remain enabled during the execution of the trap service routine.

Before calling an interrrupt's service routine, the processor pushes the current contents of the EFLAGS register onto the stack. Afterwards, it may safely clear the interrupt enable flag, thus preventing nested handling of additional interrupts. Similar to a regular function call, the return address (including the current code segment and the instruction pointer) is also stored on the stack before the service routine is called. For some exceptions, the processor stores an additional error code on the stack.

One task of the interrupt servie routine is to acknowledge successful servicing of the interrupt to the Local APIC. Otherwise, the APIC will stop forwarding interrupts of the same device to the processor. Acknowledging interrupts is done by writing to a special register of the Local APIC.

The iret instruction completes the interrupt handling. The processor fetches the return address from the stack, restores the contents of the EFLAGS register and returns to the interrupt function. Restoring the EFLAGS register implicitly enables interrupts, if they have not been enabled by the service routine before.

Accessing Local APIC and I/O APIC Registers

Both components, the Local APIC as well as the I/O APIC, are accessed via memory-mapped I/O. However, the ways which their internal registers are addressed are fundamentally different. The Local APIC internal registers are all mapped into the processor's memory address space, while the I/O APIC's registers are not directly accessible. They are addressed via two registers (IOREGSEL and IOWIN) instead. This is done by first writing the address of the desired internal regeister into the IOREGSEL register. The IOWIN register can then be used to read or write the value of the respective internal register.

You can find the definitions and descriptions of the I/O APIC's registers in the file machine/ioapic_registers.h, while those of the Local APIC are located in machine/lapic_registers.h.

While we have already done the low-level implementation of the Local APIC for you, you still have to implement the I/O APIC's access functions yourself by completing the class IOAPIC. Should you require the Local APIC's functionality, you may simply use the interface provided by the class LAPIC.

Literatur