APIC
LAPIC and IOAPIC
The Advanced Programmable Interrupter Controller(APIC) is a interrupt controller designed to replace the 8529 Intel programmable interrupt controller(PIC) that had been standard up until this point. The reason for this replacement was two fold. Multiprocessor systems where becoming more common and the old PIC was unable to control which CPU cores would be sent interrupts, Along with the APIC being capable of sending Inter-Processor-Interrupts(IPI).
This allowed for OS's to have more fine grained control over their interrupts and allowed for better hardware delegation of interrupts allowing certain CPU cores to handle certain interrupts. The design of the APIC is simple really it's split into two pieces the Local APIC(LAPIC) and the IOAPIC. With each LAPIC and IOAPIC having an ID number on their dedicated APIC bus.
The Local APIC is a CPU core local interrupt controller, each CPU core has it's own LAPIC, these can handle their own timer interrupts and external Hardware Interrupts that are sent via the IOAPIC. The IOAPIC on the other hand is connected to external devices(PIT,HPET,PS/2 Controller, etc...) and through the use of redirection tables it can be programmed with a LAPIC ID in each Interrupt redirection table entry. This ID controls which LAPIC and thus which core will service this hardware interrupt. It is quite common for a system to have one IOAPIC however plenty of systems may two or more IOAPICs you should parse the MP tables or the ACPI MADT table to ensure all IOAPICs are found.
Please Note
The MP specification is an old spec and though it will contain the information needed for the IOAPIC and LAPIC. It may not be present on newer systems and the ACPI tables should be preferred over the MP spec wherever possible.
LAPIC
Support for the local APIC can be queried using two different methods. One would be using CPUID EDX=0x00000001
. Or you could parse the MP Tables or the ACPI MADT table(Which you would likely want to do anyway for SMP). You could also check the APIC Base MSR and assume if those exist that the LAPIC is present at the "default" physical address of 0xfee00000
. However the best way would be to check the CPUID, then parse the MP or the ACPI MADT table to determine both the if APIC is supported and get the LAPIC Physical address from the MP/MADT Table. If you wish to double check this base address you can also check the APIC Base MSR(`0x1b`).
Register Layout
TODO:
IOAPIC
The IOAPIC can be queried in multiple different ways one is to assume that if the LAPIC exists that at least one IOAPIC exists at the "default" physical address of 0xfec00000
. However again this isn't ideal and you should attempt to parse either the MP Tables or the ACPI MADT Tables. To Ensure you have the correct physical addresses and have found all IOAPICs in this system.
Register Layout
todo:
Programming LAPIC/IOAPIC
When Programming these devices their memory addresses should always be marked as non-cacheable to ensure proper operation of the devices as caching them could produce strange and undesired results due to cached values being read rather than the actual true values of the MMIO registers. In the below code we are also going to assume that everything is at it's "default" standard physical address in memory and that if paging is active it's just a simple identity map paging i.e. Virtual Address = Physical Address.
In your kernel you are going to want to parse ACPI structures and map these physical addresses in a way that makes sense for your VMM.
IOAPIC:
Below is an example of programming the IOAPIC to route GSI 2 typically ISA IRQ 0(PIT Timer) to CPU 0 with the interrupt vector set to 0x20
this is because the IOAPIC operates on register select and window model of programming meaning that base + 0
= the register to select. and base + 0x10
= the selected register.
Each Interrupt redirection entry is split into two 32bit registers. The lower bits contains data such as which IDT vector will be called for this interrupt, polarity, delivery mode, edge or level triggered, masked, etc...
While the upper 32bits contain the the LAPIC ID that this interrupt will be sent to. Depending on the setting of bit 11 of the lower 32bit. See
void ioapic_init() {
volatile uint32_t *ioapic = (uint32_t*)0xfec00000;
/*Timer Interrupt for QEMU*/
base[0] = 0x14; /*GSI Interrupt 2 Most commonly PIT/HPET timer*/
base[4] = 0x20; /*Interrupt Vector i.e. IDT vector 0x20 will be called for this*/
base[0] = 0x14; /*GSI Interrupt 2 upper 32bit*/
base[4] = 0x00;
}