972
edits
[unchecked revision] | [unchecked revision] |
m (Convert to standard ISO data types) |
m (Bot: Replace deprecated source tag with syntaxhighlight) |
||
(13 intermediate revisions by 9 users not shown) | |||
Line 1:
The great benefit of the Local [[APIC]] timer is that it
== 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 ==
Before enabling the local APIC timer, you should setup the rest of the local APIC. This includes:
* Determine the local APIC's physical address (via. ACPI tables or MultiProcessor Specification tables)
* 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.''
'''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
=== Prerequisites ===
Before we start, let's define some constant and functions.
<
apic = the linear address where you have mapped the APIC registers
Line 85 ⟶ 99:
writegate: ...
ret
</syntaxhighlight>
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
* 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:
<
;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>
=== 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.
<syntaxhighlight lang="c">
void apic_start_timer() {
// Tell APIC timer to use divider 16
write(APIC_REGISTER_TIMER_DIV, 0x3);
// Prepare the PIT to sleep for 10ms (10000µs)
pit_prepare_sleep(10000);
// Set APIC init counter to -1
write(APIC_REGISTER_TIMER_INITCNT, 0xFFFFFFFF);
// Perform PIT-supported sleep
pit_perform_sleep();
// Stop the APIC timer
write(APIC_REGISTER_LVT_TIMER, APIC_LVT_INT_MASKED);
// Now we know how often the APIC timer has ticked in 10ms
uint32_t ticksIn10ms = 0xFFFFFFFF - read(APIC_REGISTER_TIMER_CURRCNT);
// Start timer as periodic on IRQ 0, divider 16, with the number of ticks we counted
write(APIC_REGISTER_LVT_TIMER, 32 | APIC_LVT_TIMER_MODE_PERIODIC);
write(APIC_REGISTER_TIMER_DIV, 0x3);
write(APIC_REGISTER_TIMER_INITCNT, ticksIn10ms);
}
</syntaxhighlight>
== 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/
[[Category:Interrupts]]
[[Category:
|