StuBS
|
The APIC architecture contains a high-resolution timer on each of the CPU-local APICs, which can be used to generate interrupts: The APIC timer.
A timer is implemented by a clock generator that emits rhythmic current pulses. For example, a quartz can be used for this purpose, which vibrates when a voltage is applied. The signal from the quartz is converted into a smooth square-wave signal by a specific circuit and amplified so that the logic gates can handle it.
The signal is then divided by a prescaler (or divider) so that, for example, the frequency f can be used to generate f/2. The division into powers of two is particularly easy to realize using T-flip-flips connected in series. The APIC timer offers adjustable prescaler stages that advance the signal by powers of two.
The divided signal is then used to periodically count down a 32-bit counter (CCNT
). This counter is pre-initialized with the value of the initial count register (ICNT
) and then decremented by one with each clock pulse (after the prescaling). The CCNT
thus indicates the current status of the timer. The value of the counter is compared against zero with each clock pulse; if it is zero, a timer interrupt is sent to the processor. In addition, the current count register is reset to the value of the initial count register (if the timer is running in periodic mode). The value of the ICNT
is freely adjustable.
The timer shall be used to send periodic interrupts, e.g. every 5 ms. To ensure that the timer sends interrupts at exactly this time interval, it is necessary to set the prescaler and the ICNT
appropriately. This in turn depends on the frequency of the timer.
With the LAPIC timer, the frequency is not fixed but depends on the hardware. It must therefore be determined by calibration with another time source (where the frequency is known). We use the PIT (Programmable Interval Timer) for this; the code for using the PIT is already included in the handout code. This timer must be used to determine the frequency of the APIC timer in LAPIC::Timer::ticks(). This function returns the clock ticks per millisecond.
The frequency can be used to calculate the number of ticks required per interrupt (without divider) as follows:
\[ \mathrm{ticks} = \frac{\mu s \times \text{LAPIC::Timer::ticks()}}{ 1000 } \]
Please note that CCNT
and ICNT
are 32-bit registers and must not overflow.
When multiplying \(\mu\) with the value returned by LAPIC::Timer::ticks(), for example, the 32-bit value range may already overflow. In this case, the operation should therefore be performed in the 64-bit value space (uint64_t
).
The correct divider and ICNT
value must then be calculated from the ticks. The resulting values should set the timer as precisely as possible.
Four 32-bit registers (memory-mapped) are responsible for programming the APIC timer:
Address | Register name |
---|---|
0xfee00320 | LVT Timer Register (aka Timer Control Register) |
0xfee00380 | Initial Count Register (ICNT ) |
0xfee00390 | Current Count Register (CCNT ) |
0xfee003e0 | Divide Configuration Register (TimeDIV ) |
The two count registers (ICNT
and CCNT
) are used to set the start value of the timer and to read out the current value. In addition, the timer can be stopped completely with an initial count value of 0. Subsequent writing of the ICNT
with a value not equal to 0 starts the timer.
The behavior of the timer can be influenced via the control register and the divisor. The structure of the control register is:
Bit(s) | Value | Meaning |
---|---|---|
31-19 | (reserved) | |
18-17 | Operating mode | |
0 | one-shot (stops at 0) | |
1 | periodic (starts again with ICR value) | |
2 | TSC-Deadline (compare absolute time via a special register IA32_TSC_DEADLINE, ignore ICR and CCR) | |
3 | reserved | |
16 | Interrupt mask | |
0 | Interrupts are delivered | |
1 | Interrupts are disabled | |
15-13 | (reserved) | |
12 | Interrupt status (read-only) | |
0 | idle | |
1 | Interrupt pending | |
11-8 | (reserved) | |
7-0 | Vector number of the interrupt to be triggered |
The assignment of the divisor to the corresponding register values can be found in the Intel manual, but is also already implemented by the auxiliary function LAPIC::Timer::getClockDiv() as part of the handout.