1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Load IDT and define gate error-code-aware handlers (rt0/x86_64)

The rt0_64 code will load a blank IDT with 256 entries (the max number
of supported interrupts in the x86_64 architecture). Each IDT entry is
set as *not present* but its handler is set to a dedicated gate entrypoint
defined in the rt0 code.

A gate entrypoint is defined for each interrupt number using a nasm
macro. Each entrypoint will then use the interrupt number to index a
list of pointers (defined and managed by the Go assembly code in
the irq pkg) to the registered interrupt handlers and push its address
on the stack before jumping to one of the two available gate dispatching
functions (some interrupts also push an error code to the stack which
must be popped before returning from the interrupt handler):
- _rt0_64_gate_dispatcher_with_code
- _rt0_64_gate_dispatcher_without_code

Both dispatchers operate in the same way:
- they save the original registers
- they invoke the interrupt handler
- they restore the original registers
- ensure that the stack pointer (rsp) points to the exception frame
  pushed by the CPU

The difference between the dispatchers is that the "with_code" variant
will invoke a handler with signature `func(code, &frame, &regs)` and
ensure that the code is popped off the stack before returning from the
interrupt while the "without_code" variant will invoke a handler with
signature `func(&frame, &regs)`
This commit is contained in:
Achilleas Anagnostopoulos 2017-06-21 17:45:42 +01:00
parent 827f1a171f
commit 5a2efb2bd3

View File

@ -1,9 +1,25 @@
; vim: set ft=nasm : ; vim: set ft=nasm :
%include "constants.inc" %include "constants.inc"
bits 64
section .bss section .bss
align 8 align 8
; Allocate space for the interrupt descriptor table (IDT).
; This arch supports up to 256 interrupt handlers
%define IDT_ENTRIES 0xff
_rt0_idt_start:
resq 2 * IDT_ENTRIES ; each 64-bit IDT entry is 16 bytes
_rt0_idt_end:
_rt0_idt_desc:
resw 1
resq 1
; Allocates space for the IRQ handlers pointers registered by the IRQ package
_rt0_irq_handlers resq IDT_ENTRIES
r0_g_ptr: resq 1 ; fs:0x00 is a pointer to the current g struct r0_g_ptr: resq 1 ; fs:0x00 is a pointer to the current g struct
r0_g: r0_g:
r0_g_stack_lo: resq 1 r0_g_stack_lo: resq 1
@ -12,7 +28,6 @@ r0_g_stackguard0: resq 1 ; rsp compared to this value in go stack growth prolog
r0_g_stackguard1: resq 1 ; rsp compared to this value in C stack growth prologue r0_g_stackguard1: resq 1 ; rsp compared to this value in C stack growth prologue
section .text section .text
bits 64
;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------
; Kernel 64-bit entry point ; Kernel 64-bit entry point
@ -25,6 +40,8 @@ bits 64
;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------
global _rt0_64_entry global _rt0_64_entry
_rt0_64_entry: _rt0_64_entry:
call _rt0_64_load_idt
; According to the x86_64 ABI, the fs:0 should point to the address of ; According to the x86_64 ABI, the fs:0 should point to the address of
; the user-space thread structure. The actual TLS structure is located ; the user-space thread structure. The actual TLS structure is located
; just before that (aligned). Go code tries to fetch the address to the ; just before that (aligned). Go code tries to fetch the address to the
@ -75,6 +92,196 @@ _rt0_64_entry:
cli cli
hlt hlt
;------------------------------------------------------------------------------
; Setup and load IDT. We preload each IDT entry with a pointer to a gate handler
; but set it as inactive. The code in irq_amd64 is responsible for enabling
; individual IDT entries when handlers are installed.
;------------------------------------------------------------------------------
_rt0_64_load_idt:
mov rax, _rt0_idt_start
%assign gate_num 0
%rep IDT_ENTRIES
mov rbx, _rt0_64_gate_entry_%+ gate_num
mov word [rax], bx ; gate entry bits 0-15
mov word [rax+2], 0x8 ; GDT descriptor
mov byte [rax+5], 0x0 ; Mark the entry as NOT present
shr rbx, 16
mov word [rax+6], bx ; gate entry bits 16-31
shr rbx, 16
mov dword [rax+8], ebx ; gate entry bits 32-63
add rax, 16 ; size of IDT entry
%assign gate_num gate_num+1
%endrep
mov rax, _rt0_idt_desc
mov word [rax], _rt0_idt_end - _rt0_idt_start - 1 ; similar to GDT this must be len(IDT) - 1
mov rbx, _rt0_idt_start
mov qword [rax+2], rbx
lidt [rax]
ret
;------------------------------------------------------------------------------
; Generate gate entries. Each gate handler pushes the address of the registered
; handler to the stack before jumping to a dispatcher function.
;
; Some exceptions push an error code to the stack after the stack frame. This
; code must be popped off the stack before calling iretq. The generated handlers
; are aware whether they need to deal with the code or not and jump to the
; appropriate get dispatcher.
;------------------------------------------------------------------------------
%assign gate_num 0
%rep IDT_ENTRIES
extern _rt0_interrupt_handlers
_rt0_64_gate_entry_%+ gate_num:
push rax
mov rax, _rt0_interrupt_handlers
add rax, 8*gate_num
mov rax, [rax]
xchg rax, [rsp] ; store handler address and restore original rax
; For a list of gate numbers that push an error code see:
; http://wiki.osdev.org/Exceptions
%if (gate_num == 8) || (gate_num >= 10 && gate_num <= 14) || (gate_num == 17) || (gate_num == 30)
jmp _rt0_64_gate_dispatcher_with_code
%else
jmp _rt0_64_gate_dispatcher_without_code
%endif
%assign gate_num gate_num+1
%endrep
%macro save_regs 0
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push rbp
push rdi
push rsi
push rdx
push rcx
push rbx
push rax
%endmacro
%macro restore_regs 0
pop rax
pop rbx
pop rcx
pop rdx
pop rsi
pop rdi
pop rbp
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
%endmacro
;------------------------------------------------------------------------------
; This dispatcher is invoked by gate entries that expect a code to be pushed
; by the CPU to the stack. It performs the following functions:
; - save registers
; - push pointer to saved regs
; - push pointer to stack frame
; - read and push exception code
; - invoke handler(code, &frame, &regs)
; - restore registers
; - pop exception code from stack so rsp points to the stack frame
;------------------------------------------------------------------------------
_rt0_64_gate_dispatcher_with_code:
; This is how the stack looks like when entering this function:
; (each item is 8-bytes wide)
;
;------------------
; handler address | <- pushed by gate_entry_xxx (RSP points here)
;-----------------|
; Exception code | <- needs to be removed from stack before calling iretq
;-----------------|
; RIP | <- exception frame
; CS |
; RFLAGS |
; RSP |
; SS |
;-----------------
cld
; save regs and push a pointer to them
save_regs
mov rax, rsp ; rax points to saved rax
push rax ; push pointer to saved regs
; push pointer to exception stack frame (we have used 15 qwords for the
; saved registers plus one qword for the data pushed by the gate entry
; plus one extra qword to jump over the exception code)
add rax, 17*8
push rax
; push exception code (located between the stack frame and the saved regs)
sub rax, 8
push qword [rax]
call [rsp + 18*8] ; call registered irq handler
add rsp, 3 * 8 ; unshift the pushed arguments so rsp points to the saved regs
restore_regs
add rsp, 16 ; pop handler address and exception code off the stack before returning
iretq
;------------------------------------------------------------------------------
; This dispatcher is invoked by gate entries that do not use exception codes.
; It performs the following functions:
; - save registers
; - push pointer to saved regs
; - push pointer to stack frame
; - invoke handler(&frame, &regs)
; - restore registers
;------------------------------------------------------------------------------
_rt0_64_gate_dispatcher_without_code:
; This is how the stack looks like when entering this function:
; (each item is 8-bytes wide)
;
;------------------
; handler address | <- pushed by gate_entry_xxx (RSP points here)
;-----------------|
; RIP | <- exception frame
; CS |
; RFLAGS |
; RSP |
; SS |
;-----------------
cld
; save regs and push a pointer to them
save_regs
mov rax, rsp ; rax points to saved rax
push rax ; push pointer to saved regs
; push pointer to exception stack frame (we have used 15 qwords for the
; saved registers plus one qword for the data pushed by the gate entry)
add rax, 16*8
push rax
call [rsp + 17*8] ; call registered irq handler
add rsp, 2 * 8 ; unshift the pushed arguments so rsp points to the saved regs
restore_regs
add rsp, 8 ; pop handler address off the stack before returning
iretq
;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------
; Error messages ; Error messages
;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------