APIC Timer: Difference between revisions

New APIC timer setup example code.
[unchecked revision][unchecked revision]
m (Nuke repeated newlines)
(New APIC timer setup example code.)
Line 90:
* interrupt 39: spurious irq, IRQ7
If you've already changed this, modify accordingly.
 
 
=== Example code in ASM ===
Line 198 ⟶ 199:
mov dword [apic+APIC_TMRDIV], 03h
</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 measuring of the timers frequency.
<source lang="c">
void apic_start_timer() {
void apic_timer_init(uint32_t quantum){
// Tell APIC timer to use divider 16
uint32_t tmp, cpubusfreq;
write(APIC_REGISTER_TIMER_DIV, 0x3);
 
//set up isrs
// Prepare the PIT to sleep for 10ms (10000µs)
writegate(32,isr_dummytmr);
pit_prepare_sleep(10000);
writegate(39,isr_spurious);
 
//reset APICSet timerAPIC (setinit counter to -1)
//initialize LAPIC to a well known state
write(APIC_REGISTER_TIMER_INITCNT, 0xFFFFFFFF);
(uint32_t*)(apic+APIC_DFR)=0xFFFFFFFF;
(uint32_t*)(apic+APIC_LDR)=((uint32_t*)(apic+APIC_LDR)&0x00FFFFFF)|1);
// Perform PIT-supported sleep
(uint32_t*)(apic+APIC_LVT_TMR)=APIC_DISABLE;
pit_perform_sleep();
(uint32_t*)(apic+APIC_LVT_PERF)=APIC_NMI;
(uint32_t*)(apic+APIC_LVT_LINT0)=APIC_DISABLE;
//stop Stop the APIC timer
(uint32_t*)(apic+APIC_LVT_LINT1)=APIC_DISABLE;
write(APIC_REGISTER_LVT_TIMER, APIC_LVT_INT_MASKED);
(uint32_t*)(apic+APIC_TASKPRIOR)=0;
 
// Now we know how often the APIC timer has ticked in 10ms
//okay, now we can enable APIC
uint32_t ticksIn10ms = 0xFFFFFFFF - read(APIC_REGISTER_TIMER_CURRCNT);
//global enable
cpuSetAPICBase(cpuGetAPICBase());
// Start timer as periodic on IRQ 0, divider 16, with the number of ticks we counted
//software enable, map spurious interrupt to dummy isr
write(APIC_REGISTER_LVT_TIMER, 32 | APIC_LVT_TIMER_MODE_PERIODIC);
(uint32_t*)(apic+APIC_SPURIOUS)=39|APIC_SW_ENABLE;
write(APIC_REGISTER_TIMER_DIV, 0x3);
//map APIC timer to an interrupt, and by that enable it in one-shot mode
write(APIC_REGISTER_TIMER_INITCNT, ticksIn10ms);
(uint32_t*)(apic+APIC_LVT_TMR)=32;
//set up divide value to 16
(uint32_t*)(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); // Switch to Mode 0, if you get problems!!!
//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_t)tmp=inb(0x61)&0xFE;
outb(0x61,(uint8_t)tmp); //gate low
outb(0x61,(uint8_t)tmp|1); //gate high
//reset APIC timer (set counter to -1)
(uint32_t*)(apic+APIC_TMRINITCNT)=0xFFFFFFFF;
 
//now wait until PIT counter reaches zero
while(!(inb(0x61)&0x20));
 
//stop APIC timer
(uint32_t*)(apic+APIC_LVT_TMR)=APIC_DISABLE;
 
//now do the math...
cpubusfreq=((0xFFFFFFFF-(uint32_t*)(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_t*)(apic+APIC_TMRINITCNT)=(tmp<16?16:tmp);
//finally re-enable timer in periodic mode
(uint32_t*)(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_t*)(apic+APIC_TMRDIV)=0x03;
}
</source>
89

edits