Hi,
DruG5t0r3 wrote:
I'm working on some apic code right now and I'm trying to figure out how you can get the apic to fire off an interrupt every lets say...1 seconds. Based on the Intel book, says the timer is derived from the cpu bus clock speed. So how can you effectively set the apic to interrupt every second?
First (assuming the local APIC is enabled and ready), you have to find out how quick the CPU's bus speed is. Then you set the APIC timer divider and APIC timer count to values that are calculated from the measured bus speed.
To measure the CPU's bus speed you'd see how many times the APIC timer's count is decreased over a fixed time delay. After that it's fairly easy. For example:
Code:
%define LAPICapicID 0x0020+LAPIC_BASE_ADDRESS
%define LAPICversion 0x0030+LAPIC_BASE_ADDRESS
%define LAPICtaskPriority 0x0080+LAPIC_BASE_ADDRESS
%define LAPICendOfInterrupt 0x00B0+LAPIC_BASE_ADDRESS
%define LAPIClogicalDestination 0x00D0+LAPIC_BASE_ADDRESS
%define LAPICdestinationFormat 0x00E0+LAPIC_BASE_ADDRESS
%define LAPICspurious 0x00F0+LAPIC_BASE_ADDRESS
%define LAPICinterruptCommandLow 0x0300+LAPIC_BASE_ADDRESS
%define LAPICinterruptCommandHigh 0x0310+LAPIC_BASE_ADDRESS
%define LAPICLVTtimer 0x0320+LAPIC_BASE_ADDRESS
%define LAPICLVTperfCounter 0x0340+LAPIC_BASE_ADDRESS
%define LAPICLVT_LINT0 0x0350+LAPIC_BASE_ADDRESS
%define LAPICLVT_LINT1 0x0360+LAPIC_BASE_ADDRESS
%define LAPICLVTerror 0x0370+LAPIC_BASE_ADDRESS
%define LAPICTIMERinitialCount 0x0380+LAPIC_BASE_ADDRESS
%define LAPICTIMERcurrentCount 0x0390+LAPIC_BASE_ADDRESS
%define LAPICTIMERdivider 0x03E0+LAPIC_BASE_ADDRESS
%define LTIMERdivideBy1 0x0000000B
%define LTIMERdivideBy16 0x00000003
%define LTIMERdivideBy128 0x0000000A
%define LTIMERperiodic 0x00020000
get_bus_speed:
;Install the IRQ handler for the local APIC timer
mov edi,LAPIC_timer_IRQ_handler
mov al,LAPIC_int_vector
call create_IDT_descriptor
;Tell the local APIC to use the LAPIC_int_vector, set it to "one-shot" mode and enable it
mov dword [LAPICLVTtimer],LAPIC_int_vector
;Set the local APIC timer divider to divide by 128
mov dword [LAPICTIMERdivider],LTIMERdivideBy128
;Configure the RTC or PIT timer for an IRQ every 250 mS (4 Hz)
; Note: The handler for this timer's IRQ just does "inc dword [value]"
call configure_RTC_or_PIT
;Wait for the start of the next RTC or PIT IRQ
mov eax,[value]
.wait1:
cmp [value],eax
je .wait1:
;Set the initial count to 0xFFFFFFFF, which starts the local APIC timer
mov dword [LAPICTIMERinitialCount],0xFFFFFFFF
;Wait for the next RTC or PIT IRQ
mov eax,[value]
.wait2:
cmp [value],eax
je .wait2:
;Stop the local APIC timer
mov [LAPICLVTtimer],LVTdisabled
;Read the local APIC timer count
mov ebx,[LAPICTIMERcurrentCount]
;Stop the PIT or RTC timer
call stop_RTC_or_PIT
;Work out how much the local APIC timer's count decreased
mov eax,0xFFFFFFFF ;eax = initial value used
sub eax,ebx ;eax = number of times it changed
;Calculate the CPU's bus frequency (Hz) in EDX:EAX
mov ebx,(128 / 4)
mul ebx
;Calculate the local APIC count for the frequency you want
mov ebx,FREQUENCY
div ebx
;Adjust it for a different local APIC timer divider
xor edx,edx
mov ebx,16
div ebx
;Set the new local APIC timer divider
mov dword [LAPICTIMERdivider],LTIMERdivideBy16
;Set the new local APIC timer count
mov [LAPICTIMERinitialCount],eax
;And then enable the local APIC timer in "periodic" mode and return
mov dword [LAPICLVTtimer], LAPIC_int_vector | LTIMERperiodic
ret
You'll also want something like:
Code:
LAPIC_timer_IRQ_handler:
mov dword [LAPICendOfInterrupt],0 ;Send the EOI
iretd
Depending on the duration of the fixed time value and the local APIC timer frequency you actually want, you may want to adjust the values used for the local APIC timer divider. I'd select timer divider values to get the best accuracy while preventing overflows (e.g. so it works up to 1 GHz bus frequency - nothing wrong with future-proofing). You might also want to play with how long the duration of the fixed time value is - 250 mS is relatively long considering the speed of even the slowest CPU's bus.
BTW for a frequency of 1 Hz it'd probably be better to use the RTC timer instead, but you knew that
...
Cheers,
Brendan