Devices
can raise an interrupt to attract CPU's attention. Raising the interrupt is
just a process of maintaining some level of voltage across the interrupt line.
As long as the voltage is maintained, line is held high and the PIC
(Programmable Interrupt Controller) knows that a particular line dedicated to a
device needs attention. The PIC will then inform the CPU, that an interrupt
occured. CPU then invokes kernel's interrupt handlers to handle
interrupt.
Intel
CPU's uses a table called IDT(Interrupt descriptor table) which has pointers to
routines which have to be executed for it’s corresponding vector number. The
table's address is stored in control registers, so when CPU wants to invoke an
interrupt handler, it jumps to appropriate routine. IDT is
an array of addresses. First 16 entries are exceptions. These are system
defined exceptions and cannot be modified by users. Ex: Divide by zero, Null
pointer, segmentation fault, etc…
Vector numbers 16-31 are
reserved for future use and 32-255 are for external device
interrupts.
Since
devices can not connect directly to CPU, there should be a intermediate
controller to pass the interrupt from devices to CPU. This
is done by interrupt controller. In x86 the interrupt
controller is 8259 chip and is also called PIC(Programmable
interrupt controller). A single 8259 PIC
contains 8 input interrupt lines and 1 output line. Since
only 8 devices can be connected on such a system, we generally use two 8259
PIC’s cascaded onto each other. That makes 16 devices to be configured. But one of the 8259 PIC is also a device and hence the output
line of one of the PIC’s is connected to one of it’s 8 input pins. Thus a total of 15 input devices can be connected on such a
system.
The
disadvantage of such a system is:
1: There
exists a controller for each unique device and they have to be connected to the
same input line always. If you change the pin, to which hardware device is connected,
that information has to be conveyed to the OS so that vectors in IDT are modified
suitably. But this is not possible on a PIC system.
2:
Limited number of devices can be connected.
3: It’s
not possible for multiple processors to talk to each other and share
information.
These
hindrances are overcome in APIC.(Advanced PIC). APIC(Advanced Programmable Interrupt Controller) is the updated Intel
standard for the older PIC(Programmable interrupt controller).
APIC consists of 2 parts: LAPIC and IOAPIC. IOAPIC is the functional equivalent of
8259 PIC. LAPIC is part of CPU core.
Devices
are connected to IOAPIC and receives interrupts from the devices and
distributes them to the LAPIC. LAPIC interrupts the processor.
Apart
from device interrupts, APIC is used for Interprocessor
Interrupt.
During
boot-up, one processor is chosen as a primary processor and it boots up. All
other secondary processors(application processors) are put into halt state.
Secondary
processors are booted by the primary processor by sending the SIPI(Start
Inter processor interrupt).
SIPI
carries a vector field which holds physical address from where the secondary
processor has to start up. Secondary processors receive the SIPI interrupt and then start in
real mode. The startup routine on these secondary processors comprise of GDT,
IDT, paging, etc….
Sequence
of steps to set up APIC:
1:
Initially only 1 processor boots up and runs in protected mode. This processor
is called primary processor.
2: The
primary processor reads the ACPI table to get information on APIC. The
following information are available from ACPI
a: how many processors are available on the system
c: Location of IO-APIC’s and LAPIC’s.
This is
done by the function: kernel./i386/arch.c: SetupAPIC().
3: After
loading the data structures with the required details to start APIC, LAPIC and
IOAPIC registers are initialized.
This is
done by InitAPIC() which is called from SetupAPIC().
4: The
main work in initializing LAPIC and IOAPIC is to initialize the base vectors.
The
specs of APIC gives the initial starting base of LAPIC and IOAPIC, which is a
physical address.
So the
OS can assign any virtual address to it and then can use the given physical
address or relocate it to any other location. In ACE, we don’t relocate it.
We
utilize MapPhysicalMemory() to allocate a VA to our given PA.
Note
that LAPIC will have common base address on all processors because that address
is local to each processor and is not put on system bus.
So we
only have to take additional care in assigning unique addresses for each of the
IOAPIC.
5: After
initializing we have to now instruct the ACPI to use our APIC.
By using
we mean, ACPI should have to now modify some things which will route interrupts
to IO-APIC instead of 8259 PIC. It also has to enable APIC by modifying the
corresponding register in LAPIC.
6: Once
this is done, APIC is now live and kicking and ready to take in any interrupts
Note
that vector table which is programmable by OS should already be setup,
so that there are routines to handle any interrupt that arrives on the
processor.
7: The
primary processor can now start other application processors.
The
specs give an universal algorithm for this:
Now query the target processor if it’s up.
This
procedure has to be followed in loop to bootup all processors.
APIC: http://jlbtc.eduunix.cn/index/html/linux/OReilly.Understanding.the.Linux.Kernel.3rd.Edition.Nov.2005.HAPPY.NEW.YEAR/0596005652/understandlk-CHP-4-SECT-2.html
Intel
volume 3: System programming
Intel
spec on Multiprocessor and IOAPIC.
IPI: http://jlbtc.eduunix.cn/index/html/linux/OReilly.Understanding.the.Linux.Kernel.3rd.Edition.Nov.2005.HAPPY.NEW.YEAR/0596005652/understandlk-CHP-4-SECT-6.html