APIC standing for Advanced Programmable Interrupt Controller, is the basis of PC interrupt architecture today. An APIC can deal with more interrupts than a PIC (or Programmable Interrupt Controller also referred to as 8259) used to and can route interrupts to multiple processors effectively.
The APIC architecture has 2 components – the I/O APIC which is part of the motherboard chipset (and is connected to the interrupting devices) and the Local APIC which is on the CPU itself. In Pentium 4 systems, the I/O APIC communicates with the Local APIC through the system bus. Interrupt requests typically hit the I/O APIC which forwards it to the Local APIC which then interrupts the CPU core. Local APICs can generate an interrupt for another processor by talking to the Local APIC of the target processor. This is called an Inter Processor Interrupt (or IPI).
The undocumented windbg kdexts extension commands !apic and !ioapic let us peep into the Local APIC and I/O APIC respectively. Here is what I got when I ran !apic on my AMD64 running Windows Vista x64 [debugging over serial cable]. Since this is a dual-core CPU, it has 2 Local APICs (one per core).
The address after the @ in the output, is the virtual address of the 4K page, to which all Local APIC registers get mapped to. Local APIC uses these registers to deal with interrupt processing and these registers by default are at physical address 0xfee00000. This physical address can be modified by the OS but looks like Vista leaves it alone. The rest of the output of !apic is basically a subset of bits read from this page. Sometimes !apic may give an error “Unable to read lapic” – in which case try (re)loading kernel symbols or access the registers directly at the physical address.
ID corresponds to the Local APIC ID Register which uniquely identifies the Local APIC, so interrupts can be targetted to a Local APIC. Note that the ID of local APIC on cpu1 is 1 and the ID on local APIC on cpu0 is 0. [Mark also that I switched to cpu0 by ~0s command. If you are debugging locally, windbg switches to the other cpu automagically when you execute !apic again.]
The number in the parentheses next to ID is the content of the Local APIC Version Register. [The lowest 8 bits (0x10) store the version, I have seen 50014 on a newer desktop – you will probably see a even higher number here on your newest]
LogDesc (perhaps intended to be LogDest ?) corresponds to Logical Destination Register, which along with Destination Format Register (DestFmt in !apic output) allows APICs to be addressed in logical mode (vis-a-vis physical mode addressing via Local APIC ID). For example, 2 APICs can be given the same Logical Destination Register which would mean they have the same logical ID and therefore will respond to all messages destined to that logical ID (thus forming a logical group). We see that Vista kept them different on this host.
TPR corresponds to Task Priority Register that basically indicates what interrupts the CPU core can accept at this point. Task Priority is the second nibble (bits 7-4) and is set to 0xF in the output above. 0xF means all interrupts are disabled and the core does not wish to take any interrupts now (perhaps because of windbg breaking in before executing the !apic command). A value of zero allows all interrupts. You may get zero Task Priority when debugging locally.
TimeCnt corresponds to Initial Count Register which contains the start count value for the APIC timer. The APIC timer is a programmable 32-bit counter that starts with this value and decrements until it reaches zero which is when an APIC timer interrupt is generated.
SPurVec corresponds to the Spurious Interrupt Vector Register that defines the interrupt vector for APIC spurious interrupts. This register also has a bit to disable the local APIC altogether if system software so wishes.
FaultVec shows the interrupt vector setup in the the Error Local Vector Table Register. Errors that occur during handling of interrupts trigger an APIC error interrupt. The error gets registered in Error Status Register (shown as Error in the !apic output)
Ipi Cmd – corresponds to lower 32 bits of the 64-bit ICR or Interrupt Command Register, which is how Inter Processor Interrupts IPIs) are generated. APICs can generate IPIs for other CPUs or themselves (also referred to as a self-interrupt) by writing to this register. The local APIC can also send an SMI, NMI or forward an interrupt that was not serviced by the local APIC to another APIC through this register. That makes this register a pretty important one.
Timer shows the Timer Local Vector Table Register which among other things specifies, in the least significant byte, the APIC timer interrupt vector (which is output again as Vec later in the same row). If we look at vector 0xfd we see the following
Vista x64 seems to be using it for profiling purposes (and so do XP and Vista 32-bit)
Linti0 (Local Interrupt 0) and Linti1 (Local Interrupt 1) are registers through which local APIC can get to handle interrupts from devices directly connected to (the local APIC of) CPU. For example an old style PIC connected to some I/O device(s) may be connected to LINT0 and deliver interrupts to the CPU (more specifically local APIC of the CPU) directly, instead of going through I/O APIC and system bus.
TMR shows the Trigger Mode Register which stores the interrupt trigger mode (level or edge triggered). Edge triggered interrupts are indicated by a transition of levels (for example the interrupt line going from 0 to 1) and are one-shot interrupts where as level-triggered interrupts are on as long as the level is maintained (for example interrupt line staying at 1).
IRR or Interrupt Request Register has interrupt requests that have been accepted but have not been sent to the CPU yet. This is kind of the wait queue of interrupts on the CPU. For example, in the !apic output we see interrupt vectors 0x52, 0xA2, 0xD1 waiting for CPU time on CPU1. When the CPU wants to process the next interrupt, the highest priority interrupt in the queue is sent to the CPU core for processing.
ISR or Interrupt Service Register has interrupt requests that have been sent to the CPU and are still being processed. When the interrupt is picked up from the IRR queue, it is moved to the ISR where it stays until CPU indicates end of interrupt at which point the next highest interrupt is picked from IRR for processing. Going back to the !apic output, we know 0xD1 (clock interrupt) was being processed when we broke in to take a look at the APIC state.
I hope the epic of !apic was not too long.