Code: Select all
%macro INTERRUPT_HANDLER 2
global x64_interrupt_handler_%2
x64_interrupt_handler_%2:
cld ;Safe, IRET restores flags
%if %1 == 0
push 0xDEADBEEF ;Dummy error code
%endif
;Stack frame
push rbp
mov rbp, rsp
SAVE_VOLATILE_REGISTERS
;Per CPU information
call swap_gs_ifpriv
call save_fpu_interrupt
;Now we pass the stack interrupt stack and vector
mov rcx, %2
mov rdx, rbp
sub rsp, 32
call x64_interrupt_dispatcher
add rsp, 32
call restore_fpu_interrupt
call swap_gs_ifpriv
RESTORE_VOLATILE_REGISTERS
pop rbp
add rsp, 8 ;Get rid of error code
iretq
%endmacro
%macro SAVE_VOLATILE_REGISTERS 0
push rax
call save_interrupt_registers
%endmacro
save_interrupt_registers:
pop rax
push rcx
push rdx
push r8
push r9
push r10
push r11
jmp rax
That's my interrupt handling code.
Firstly, you have to remember that interrupts are, well, interrupts, so the ABI doesn't count for squat. Furthermore, you don't want to make your kernel dependent on a particular user mode ABI, beyond what you specify for syscalls. So you can say "kernel scratches these registers, these registers are preserved, these are parameters. User mode has to do the relevant translations (it likely will have a syscall wrapper anyway, so that's pretty easy).
However, once my interrupt handler calls into the main, C++, kernel (x64_interrupt_dispatcher), then we have to be concerned about the ABI.
My kernel is compiled in Visual C++, so uses the Microsoft x64 ABI. It's worth noting that the MS ABI is supported by GCC, so this actually means that the kernel is more portable to different compilers! We also avoid that annoying thing called the
red zone.
Anyway, what is the ABI?
Integer arguments are passed in RCX, RDX, R8, and R9.
Floating point arguments are really irrelevant here, but XMM0, XMM1, XMM2, and XMM3.
Now, volatile, or scratch registers, are both these, and:
RAX, R10, R11
XMM4, XMM5
Upper portions of YMM and ZMM registers, as well as ZMM16-31, are also volatile. This is only relevant if you use AVX(-512) instructions in the kernel.
It's worth noting that RAX is the return value.
Now, how does this correlate with my interrupt handler? Notice that save_interrupt_registers saves all integer volatile registers, except for RAX, which is used as a scratch register (because the function modifies the stack frame of the caller, which is in a way, a custom ABI).
Hence the helper macro SAVE_VOLATILE_REGISTERS, which preserves rax before calling the function.
Finally, the sub rsp,32 before calling into the main kernel?
That's the parameter shadow space. The x64 ABI specifies that this area of the stack is reserved for the function's register parameters.