APIC timer

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
Al
Posts: 6
Joined: Wed Oct 18, 2006 9:32 am
Location: Saint-Petersburg

APIC timer

Post by Al »

Hi

I am trying to configure APIC timer.
But it doesn’t work.
My interruption handler isn't called.

After booting via GRUB I do next steps:
1. fill IDT
2. initialize APIC
- enable APIC in SVR (0x0F0)
- set flat mode in DEST (0x0E0)
3. init timer
- set timer divide (0x3E0) to (old_value | 0xb)
- set periodic mode and vector in timer LVT (0x320)
vector 0x40
- set init count (0x380) to 100
- set IDT entry 0x40 with timer_handler address

and nothing happened.

I do it in QEMU!

Do I miss something?

Al.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: APIC timer

Post by Brendan »

Hi,
Al wrote:Do I miss something?
It should go something like:

0x00000100 -> 0xFFE001FF - enable local APIC and set spurious int vector to 0xFF
0xFFFFFFFF -> 0xFEE000E0 - set destination format register to flat model
0x00000000 -> 0xFEE00080 - set the task priority register to accept all interrupts
0x0000000B -> 0xFEE003E0 - set timer to divide by 1
0x00020040 -> 0xFEE00320 - enable timer, set periodic mode, set int vector to 0x40
0x00000064 -> 0xFEE00380 - set initial count to 100 (and make the timer start counting)

If you're setting the timer counter to divide by one and using an initial count of 100, then it'd work out to a delay of between 4 uS (25 MHz bus) and 100 nS (1 GHz bus). This seems insanely fast to me...

You don't need to read the old value and then change it - it's safe to assume all reserved bits are zero (or one for the destination format register).

Also, for some Pentium CPUs (IIRC family 5, model 2, stepping 0x0B) there was a bug with the local APIC, where you need to do a dummy read before doing a write.

Of course before you do anything with the local APIC you should find out if the base address is 0xFEE00000 or something else, and all of your local APIC code should be written so that it works if the local APIC isn't at 0xFEE00000.

Apart from that, I'm out of ideas (except for obvious things, like CLI without STI, etc).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Al
Posts: 6
Joined: Wed Oct 18, 2006 9:32 am
Location: Saint-Petersburg

Re: APIC timer

Post by Al »

Thanks a lot for your response
But what do you mean?
Brendan wrote: Apart from that, I'm out of ideas (except for obvious things, like CLI without STI, etc).
We must use time as a tool, not as a crutch.
JFK.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: APIC timer

Post by Brendan »

Hi,
Al wrote:Thanks a lot for your response
But what do you mean?
Brendan wrote: Apart from that, I'm out of ideas (except for obvious things, like CLI without STI, etc).
If interrupts are disabled, the CPU won't invoke your interrupt handler (unless the interrupt is an NMI, which isn't possible for the local APIC timer).

Other problems would include forgetting to call the routine that initialises the local APIC code (I do that sometimes), trying to use the local APIC when it isn't there (literally not present for 80486, permanently disabled for later CPUs, or simply not emulated by the emulator), having some sort of bug in the IRQ handler (e.g. doing "mov byte [0xB800],0x0741" and assuming the IRQ didn't happen because nothing changed on the screen). There's plenty of mistakes that are possible...

[edit] There is another problem I should've mentioned - you must use 32-bit reads and writes when you're working with the local APIC. Something like "mov byte [0xFEE003E0],0x0B" won't get to the local APIC because it's 8-bit instead of 32-bit. In this case the CPU sends the access out to the memory controller (which should forward it to the PCI bus, which probably ignores it). [/edit]


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Al
Posts: 6
Joined: Wed Oct 18, 2006 9:32 am
Location: Saint-Petersburg

Re: APIC timer

Post by Al »

one more Q
Brendan wrote: It should go something like:
0x00000100 -> 0xFFE001FF - enable local APIC and set spurious int vector to 0xFF
0xFEE000F0H - Spurious Interrupt Vector Register
But I found that 0xFFE001FF is in the read only space
What register is situated in 0xFFE001FF?
We must use time as a tool, not as a crutch.
JFK.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: APIC timer

Post by Brendan »

Hi,
Al wrote:
Brendan wrote: It should go something like:
0x00000100 -> 0xFFE001FF - enable local APIC and set spurious int vector to 0xFF
0xFEE000F0H - Spurious Interrupt Vector Register
But I found that 0xFFE001FF is in the read only space
What register is situated in 0xFFE001FF?
That was a typo! It should have been:

0x000001FF -> 0xFFE000F0 - enable local APIC and set spurious int vector to 0xFF

This isn't necessarily a good idea because using int vector 0xFF makes it one of the highest priority interrupts. A better idea might be to use int vector 0x2F - the lowest priority interrupt that is possible for the spurious interrupt, considering that the first 32 interrupts are reserved for exceptions and that the lowest 3 bits are hardwired to '1' for Pentium and P6 CPUs.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Al
Posts: 6
Joined: Wed Oct 18, 2006 9:32 am
Location: Saint-Petersburg

Re: APIC timer

Post by Al »

Thx, now it works, but only once.
I've found that EOI Register (0XFEE000B0) should be updated with zero on every handling. I didn't understand complitly why, but anyway it doesn't help. :cry:


Al.
We must use time as a tool, not as a crutch.
JFK.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: APIC timer

Post by Brendan »

Hi,
Al wrote:Thx, now it works, but only once.
I've found that EOI Register (0XFEE000B0) should be updated with zero on every handling. I didn't understand complitly why, but anyway it doesn't help. :cry:
Hmm - for periodic mode, sending the EOI is all you should need.

For "one-shot" mode you'd only get one interrupt, and then you'd have to send the EOI and set the initial count register again before you get another interrupt.

I'd check the code sets the timer mode correctly, check that the EOI is being sent correctly, and then perhaps try it on a slower frequency (I'm not sure what happens when the timer generates interrupts faster than the CPU can handle). Apart from that I'm lost - it should work...


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Al
Posts: 6
Joined: Wed Oct 18, 2006 9:32 am
Location: Saint-Petersburg

Re: APIC timer

Post by Al »

Am I right that "send EIO" just
0x00000000 -> 0xFEE000B0?
We must use time as a tool, not as a crutch.
JFK.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: APIC timer

Post by Brendan »

Hi,
Al wrote:Am I right that "send EIO" just
0x00000000 -> 0xFEE000B0?
Yes. For example:

Code: Select all

    test dword [gs:CPUdata.errata],CPUERRATAbadLAPIC
    je .doWrite
    cmp dword [local_APIC_base + 0x0030],eax    ;Dummy read for dodgy Pentiums
.doWrite:
    mov dword [local_APIC_base + 0x00B0],0
    pop <stuff>
    iret
Of course the local APIC timer is often used by the scheduler for pre-empting threads. In this case the EOI must happen before the scheduler does a task switch...

Code: Select all

    test dword [gs:CPUdata.errata],CPUERRATAbadLAPIC
    je .doWrite
    cmp dword [local_APIC_base + 0x0030],eax    ;Dummy read for dodgy Pentiums
.doWrite:
    mov dword [local_APIC_base + 0x00B0],0

    call do_task_switch_thing
    pop <stuff>
    iret
For those dodgy Pentiums, I'm not sure if it's faster to test if the dummy read is necessary, or to always do a dummy read (regardless of whether it's needed or not). IMHO it's likely that it's faster to always do the dummy read.

Depending on your OS, it's possibly better to wrap all local APIC writes in a macro:

Code: Select all

%macro LAPICwrite 2
    %ifdef ??????
        cmp [local_APIC_base + 0x0030],eax   ;Dummy read
     %endif
     mov [local_APIC_base + %1], %2
%endif
That way you can skip the test and the dummy read under certain conditions - for e.g. if you have one version of the kernel for plain paging and another for PAE, then you know that the dummy read isn't necessary for the PAE version because Pentiums didn't support PAE anyway...


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply