Optimizing MSI and IRQ stubs
Posted: Sat Jan 14, 2012 9:22 am
I'm currently redesigning my IRQ stubs to minimize latency while optimizing speed of IRQ handlers. The stubs have some basic requirements that cannot be changed, IOW, they must save segment registers, and the IRQ handlers themselves have far 48-bit addresses and are passed with DS as a parameter. The IRQ handlers should execute with interrupts enabled.
Here is the scheduler code that makes it possible to execute handlers with interrupts enabled:
The simplest handler is the MSI handler. MSIs can never be shared, and also cannot have IRQ detection, so they only contain the basic code:
The way the MSI handler is constructed is by allocating a piece of memory, and copy the above code to it. This makes it possible to construct all MSI handler with the same procedure, since the above code contain no MSI or interrupt references. The code is then mapped to a GDT code selector. This makes it possible to access the handler data at fixed addresses at the beginning without loading flat or other selectors. Then the code segment is mapped to the IDT. Then the handler address (procedure + data) is stored in the header.
Here is the scheduler code that makes it possible to execute handlers with interrupts enabled:
Code: Select all
; This locks the scheduler so it cannot switch thread. This macro is exported by the scheduler.
EnterInt MACRO
mov ax,core_data_sel ; load the unique per-core GDT selector
mov ds,ax
add ds:ps_nesting,1 ; disable scheduling
push ds:ps_sel ; save the core selector
ENDM
; This unlocks the scheduler and checks if some actions needs to be performed after the IRQ. Should be run with interrupts disabled
LeaveInt MACRO
local tucDone
local tucSwap
pop ds ; restore core selector
sub ds:ps_nesting,1 ; enable scheduler if ps_nesting becomes -1
jnc tucDone ; if we are a nested interrupt, just exit
;
mov ax,ds:ps_curr_thread ; check if there is a current thread. If not, the scheduler was running
or ax,ax
jz tucDone
;
test ds:ps_flags,PS_FLAG_TIMER OR PS_FLAG_PREEMPT ; check for pending actions
jnz tucSwap
;
mov ax,ds:ps_wakeup_list ; check for pending thread wakeups
or ax,ax
jz tucDone
tucSwap:
add ds:ps_nesting,1 ; something needs to be handled, so lock again
sti
mov ax,ds
mov fs,ax
OsGate irq_schedule_nr ; schedule
tucDone:
Code: Select all
This is the handler header, which has local variables that stores the entry DS and 48-bit handler procedure
msi_handler_struc STRUC
msi_linear DD ? ; linear address of code segment so it can easily be modified by the setup-code, but not at runtime
msi_handler_ads DD ?,? ; handler procedure
msi_handler_data DW ? ; handler data
msi_handler_struc ENDS
MsiStart:
msi_handler msi_handler_struc <> ; put the header first in the code segment
MsiEntry:
push ds
push es
push fs
pushad
;
EnterInt
sti
;
mov ds,cs:msi_handler_data
call fword ptr cs:msi_handler_ads
;
cli
mov ax,apic_mem_sel
mov ds,ax
xor eax,eax
mov ds:APIC_EOI,eax ; EOI APIC
LeaveInt
;
popad
pop fs
pop es
pop ds
iretd
MsiEnd: