APIC Timer: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(→‎Example code in C: Fixed some bugs produced by inaccuracy)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(15 intermediate revisions by 10 users not shown)
Line 1:
The great benefit of the Local [[APIC]] timer is that it's is hardwired to each CPU core, asunlike opposite tothe [[PIT|Programmable Interval Timer]] which is a separate circuit. Because of this, there is no need tofor any resource management, which makemakes things easier. The downside is that it's oscillating at (one of) the CPU's frequencies, which varies from machine to machine, while the PIT uses a standard frequency (11931821,193,182 Hz). To make use of it, you have to know how many interrupts/sec it's capable of.
 
== APIC Timer Modes ==
Line 18:
 
The disadvantages are that it's harder to track real-time with one-shot mode and special care needs to be taken to avoid race conditions; especially if a new count is set before the old count expires.
 
 
=== TSC-Deadline mode ===
Line 25 ⟶ 24:
 
Despite these differences, software would/could use it in the same way that one-shot mode would be used. The advantages (compared to one-shot mode) are that you get higher precision (because the CPU's time stamp counter runs at the CPU's (nominal) internal frequency rather than the CPU's external/bus frequency), and it's easier to avoid/handle race conditions.
 
 
== Enabling APIC Timer ==
 
* First you have to enable the [[APIC#Local_APIC_configuration|Local APIC]] hardware by writing it's MSR.
Before enabling the local APIC timer, you should setup the rest of the local APIC. This includes:
* After that, you have to specify a spurious interrupt and software enable the APIC (this step is necessary).
 
* Finally, you specify APIC timer interrupt number and operation mode.
* Determine the local APIC's physical address (via. ACPI tables or MultiProcessor Specification tables)
You can find more detailed information in Intel manual vol3A Chapter 9.
* Specify a spurious interrupt and software enable the APIC
* Make sure the TPR (Task Priority Register) is set (so it won't block/postpone lower priority IRQs)
 
Once that's done:
 
* Set the local APIC timer's divide configuration register
* Configure the local APIC timer's interrupt vector and unmask the timer's IRQ
* Set the local APIC timer's initial count
 
Note: It's recommended to follow the order given above (especially setting the local APIC timer's initial count last). Doing things in a different order (e.g. setting the initial count, then enabling the timer) can lead to problems on some (real or virtual) machines (e.g. everything seems right and counter is decreasing, but IRQ is never sent).
 
== Initializing ==
''Please note that this is the recommended way of determining the frequency of the APIC timer.''
There're several ways to do this, but all of them use a different, CPU bus frequency independent clock source to do that. Examples: [[RTC|Real Time Clock]], [[TSC|TimeStamp Counter]], PIT or even polling [[CMOS#Getting_Current_Date_and_Time_from_RTC|CMOS registers]]. In this tutorial we will use the good old PIT, as it's the easiest. Steps need to be done:
 
'''NOTE:''' According to Intel's documentation for IA-32 (x86) and Intel 64 (x86_64), APIC timer's frequency is equal to the bus' frequency '''OR''' the core crystal's frequency divided by the chosen frequency divider. The bus' and the core crystal's frequency can be found in the [[CPUID|CPUID]] functions [https://sandpile.org/x86/cpuid.htm#level_0000_0015h 0x15] and [https://sandpile.org/x86/cpuid.htm#level_0000_0016h 0x16], respectively. Through CPUID.0x15 can also be determined the TSC frequency. The frequency of the APIC timer depends on whether the system is using local APIC or ''discrete'' APIC ([https://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller 82489DX]). When the local APIC is built in core's crystal, the APIC timer is using core's frequency. Otherwise, it is using the bus frequency.
 
 
There are several ways to do this, but all of them use a different, CPU bus frequency independent clock source to do that. Examples: [[RTC|Real Time Clock]], [[TSC|TimeStamp Counter]], PIT or even polling [[CMOS#Getting_Current_Date_and_Time_from_RTC|CMOS registers]]. In this tutorial we will use the good old PIT, as it's the easiest. Steps that need to be done:
* Reset APIC to a well known state
* Enable APIC timer
Line 44 ⟶ 57:
* Make the APIC timer fire an interrupt at every X ticks
 
The APIC timer can be set to make a tick (decrease counter) at a given frequency, which is called "divide value". This means you have to multiply APIC timer counter ticks by this divide value to get the true CPU bus frequency. You could use a value of 1 (ticks on every bus cycle) up to 128 (ticks on every 128th cycle). See Intel manual vol3A Chapter 9.5.4 onfor details. Note that according to my tests, Bochs seems not to handle divide value of 1 properly, so I will use 16.
 
=== Prerequires ===
=== Prerequisites ===
Before we start, let's define some constant and functions.
<sourcesyntaxhighlight lang="asm">
apic = the linear address where you have mapped the APIC registers
 
Line 85 ⟶ 99:
writegate: ...
ret
</syntaxhighlight>
</source>
I will also assume that you have a working [[IDT]], and you have a function to write a gate for a specific interrupt: writegate(intnumber,israddress).
Furthermore, to make things simple, I'll assume that you did not changedchange the default interrupt mapping found in almost every tutorial:
* interrupt 0-31: exceptions
* interrupt 32: timer, IRQ0
* interrupt 39: spurious irq, IRQ7
If you've already changed this, modify accordingly.
 
 
=== Example code in ASM ===
Here's a possible way to initialize APIC timer in fasm syntax assembly:
<sourcesyntaxhighlight lang="asm">
;you should read MSR, get APIC base and map to "apic"
;you should have used lidt properly
Line 199 ⟶ 214:
;although I have found buggy hardware that required it
mov dword [apic+APIC_TMRDIV], 03h
</syntaxhighlight>
</source>
 
=== Example code in C ===
This code is an example of how to initialize the APIC timer so that it ticks every 10 milliseconds. This is done by letting the APIC timer run, waiting for 10ms using the PIT and then getting the number of ticks that were done from the APIC timer. It assumes that you have functions to "write"/"read" the APIC's registers and "pit_prepare_sleep"/"pit_perform_sleep" to perform an as accurate as possible measurement of the timer's frequency.
<source lang="c">
<syntaxhighlight lang="c">
void apic_timer_init(uint32 quantum){
void apic_start_timer() {
uint32 tmp, cpubusfreq;
// Tell APIC timer to use divider 16
 
write(APIC_REGISTER_TIMER_DIV, 0x3);
//set up isrs
writegate(32,isr_dummytmr);
// Prepare the PIT to sleep for 10ms (10000µs)
writegate(39,isr_spurious);
pit_prepare_sleep(10000);
 
//initialize LAPIC to a well known state
// Set APIC init counter to -1
(uint32*)(apic+APIC_DFR)=0xFFFFFFFF;
write(APIC_REGISTER_TIMER_INITCNT, 0xFFFFFFFF);
(uint32*)(apic+APIC_LDR)=((uint32*)(apic+APIC_LDR)&0x00FFFFFF)|1);
(uint32*)(apic+APIC_LVT_TMR)=APIC_DISABLE;
// Perform PIT-supported sleep
(uint32*)(apic+APIC_LVT_PERF)=APIC_NMI;
pit_perform_sleep();
(uint32*)(apic+APIC_LVT_LINT0)=APIC_DISABLE;
(uint32*)(apic+APIC_LVT_LINT1)=APIC_DISABLE;
// Stop the APIC timer
(uint32*)(apic+APIC_TASKPRIOR)=0;
write(APIC_REGISTER_LVT_TIMER, APIC_LVT_INT_MASKED);
 
//okay, now we can enable APIC
// Now we know how often the APIC timer has ticked in 10ms
//global enable
uint32_t ticksIn10ms = 0xFFFFFFFF - read(APIC_REGISTER_TIMER_CURRCNT);
cpuSetAPICBase(cpuGetAPICBase());
//software enable, map spurious interrupt to dummy isr
// Start timer as periodic on IRQ 0, divider 16, with the number of ticks we counted
(uint32*)(apic+APIC_SPURIOUS)=39|APIC_SW_ENABLE;
write(APIC_REGISTER_LVT_TIMER, 32 | APIC_LVT_TIMER_MODE_PERIODIC);
//map APIC timer to an interrupt, and by that enable it in one-shot mode
write(APIC_REGISTER_TIMER_DIV, 0x3);
(uint32*)(apic+APIC_LVT_TMR)=32;
write(APIC_REGISTER_TIMER_INITCNT, ticksIn10ms);
//set up divide value to 16
(uint32*)(apic+APIC_TMRDIV)=0x03;
 
//initialize PIT Ch 2 in one-shot mode
//waiting 1 sec could slow down boot time considerably,
//so we'll wait 1/100 sec, and multiply the counted ticks
outb(0x61,inb(0x61)&0xFD)|1);
outb(0x43,0xB2);
//1193180/100 Hz = 11931 = 2e9bh
outb(0x42,0x9B); //LSB
in(0x60); //short delay
outb(0x42,0x2E); //MSB
 
//reset PIT one-shot counter (start counting)
(uint8)tmp=inb(0x61)&0xFE;
outb(0x61,(uint8)tmp); //gate low
outb(0x61,(uint8)tmp|1); //gate high
//reset APIC timer (set counter to -1)
(uint32*)(apic+APIC_TMRINITCNT)=0xFFFFFFFF;
 
//now wait until PIT counter reaches zero
while(!(inb(0x61)&0x20));
 
//stop APIC timer
(uint32*)(apic+APIC_LVT_TMR)=APIC_DISABLE;
 
//now do the math...
cpubusfreq=((0xFFFFFFFF-(uint32*)(apic+APIC_TMRCURRCNT))+1)*16*100;
tmp=cpubusfreq/quantum/16;
 
//sanity check, now tmp holds appropriate number of ticks, use it as APIC timer counter initializer
(uint32*)(apic+APIC_TMRINITCNT)=(tmp<16?16:tmp);
//finally re-enable timer in periodic mode
(uint32*)(apic+APIC_LVT_TMR)=32|TMR_PERIODIC;
//setting divide value register again not needed by the manuals
//although I have found buggy hardware that required it
(uint32*)(apic+APIC_TMRDIV)=0x03;
}
</syntaxhighlight>
</source>
 
== See also ==
Line 273 ⟶ 253:
=== External Links ===
* [http://www.intel.com/products/processor/manuals/ Volume 3A:System Programming Guide, Part 1,manuals has a chapter on the APIC]
* [http://www.osdever.net/tutorials/pdfview/apic.pdfadvanced-programming-interrupt-controller Advanced Programmable Interrupt Controller by Mike Rieker]
 
[[Category:Interrupts]]
[[Category:TimeTimers]]