186 lines
4.6 KiB
ArmAsm
186 lines
4.6 KiB
ArmAsm
# x86_64 low-level entry code: the CPU-exception stubs, plus the GDT/IDT load
|
|
# helpers. Kept in a dedicated assembly file rather than inline asm because these
|
|
# need real labels and cross-symbol jumps/calls (isr_common, exceptionHandler),
|
|
# and because `lgdt`/`lidt` memory operands aren't expressible in Zig inline asm.
|
|
#
|
|
# Each exception vector normalises the stack to a uniform trap frame — a dummy
|
|
# error code where the CPU pushes none, then the vector number — and jumps to the
|
|
# shared tail, which saves the general registers and calls the Zig handler with a
|
|
# pointer to the frame (matching src/arch/x86_64/idt.zig's CpuState).
|
|
|
|
.text
|
|
|
|
# gdt_flush(rdi = *GDT descriptor): load the GDT, reload the data segment
|
|
# registers to the data selector, and reload CS to the code selector. CS can't be
|
|
# set with mov, so we far-return through the caller's own return address.
|
|
.global gdt_flush
|
|
gdt_flush:
|
|
lgdt (%rdi)
|
|
mov $0x10, %ax # kernel data selector
|
|
mov %ax, %ds
|
|
mov %ax, %es
|
|
mov %ax, %ss
|
|
mov %ax, %fs
|
|
mov %ax, %gs
|
|
pop %rax # caller's return address
|
|
push $0x08 # kernel code selector (new CS)
|
|
push %rax # return address (new RIP)
|
|
lretq
|
|
|
|
# idt_flush(rdi = *IDT descriptor): load the IDT.
|
|
.global idt_flush
|
|
idt_flush:
|
|
lidt (%rdi)
|
|
ret
|
|
|
|
# load_tr(di = TSS selector): load the task register.
|
|
.global load_tr
|
|
load_tr:
|
|
ltr %di
|
|
ret
|
|
|
|
# switch_context(rdi = &old_task.rsp, rsi = new_task.rsp)
|
|
# Cooperative context switch: save the callee-saved registers on the current
|
|
# stack, stash the stack pointer in the old task, load the new task's stack
|
|
# pointer, restore its callee-saved registers, and return into it. Caller-saved
|
|
# registers are the compiler's responsibility (this looks like a normal call).
|
|
.global switch_context
|
|
switch_context:
|
|
push %rbx
|
|
push %rbp
|
|
push %r12
|
|
push %r13
|
|
push %r14
|
|
push %r15
|
|
mov %rsp, (%rdi) # save old stack pointer into old_task.rsp
|
|
mov %rsi, %rsp # switch to the new task's stack
|
|
pop %r15
|
|
pop %r14
|
|
pop %r13
|
|
pop %r12
|
|
pop %rbp
|
|
pop %rbx
|
|
ret # return into the new task's saved instruction pointer
|
|
|
|
# task_trampoline: the first thing a freshly-spawned task runs. init_task_stack
|
|
# leaves its entry function in r15. A fresh task is switched to with the big kernel
|
|
# lock held (the hand-off rule in sync.zig) but has no enter/leave frame of its own,
|
|
# so it releases the lock here before running its body. r15 survives the call (it's
|
|
# callee-saved). New tasks then start with interrupts enabled.
|
|
.extern releaseForFreshTask
|
|
.global task_trampoline
|
|
task_trampoline:
|
|
call releaseForFreshTask # drop the kernel lock we inherited across the switch
|
|
sti
|
|
call *%r15 # call the task entry (fn() void)
|
|
1: hlt # if the entry returns, idle (still preemptible)
|
|
jmp 1b
|
|
|
|
# Stub for a vector the CPU does NOT push an error code for: push a dummy 0.
|
|
.macro STUB_NOERR vec
|
|
.global isr\vec
|
|
isr\vec:
|
|
pushq $0
|
|
pushq $\vec
|
|
jmp isr_common
|
|
.endm
|
|
|
|
# Stub for a vector the CPU DOES push an error code for: leave it in place.
|
|
.macro STUB_ERR vec
|
|
.global isr\vec
|
|
isr\vec:
|
|
pushq $\vec
|
|
jmp isr_common
|
|
.endm
|
|
|
|
STUB_NOERR 0
|
|
STUB_NOERR 1
|
|
STUB_NOERR 2
|
|
STUB_NOERR 3
|
|
STUB_NOERR 4
|
|
STUB_NOERR 5
|
|
STUB_NOERR 6
|
|
STUB_NOERR 7
|
|
STUB_ERR 8
|
|
STUB_NOERR 9
|
|
STUB_ERR 10
|
|
STUB_ERR 11
|
|
STUB_ERR 12
|
|
STUB_ERR 13
|
|
STUB_ERR 14
|
|
STUB_NOERR 15
|
|
STUB_NOERR 16
|
|
STUB_ERR 17
|
|
STUB_NOERR 18
|
|
STUB_NOERR 19
|
|
STUB_NOERR 20
|
|
STUB_ERR 21
|
|
STUB_NOERR 22
|
|
STUB_NOERR 23
|
|
STUB_NOERR 24
|
|
STUB_NOERR 25
|
|
STUB_NOERR 26
|
|
STUB_NOERR 27
|
|
STUB_NOERR 28
|
|
STUB_NOERR 29
|
|
STUB_NOERR 30
|
|
STUB_NOERR 31
|
|
|
|
# Device-interrupt vectors (timer, spurious, room for more). None push an error
|
|
# code, so they all use the dummy-zero form.
|
|
STUB_NOERR 32
|
|
STUB_NOERR 33
|
|
STUB_NOERR 34
|
|
STUB_NOERR 35
|
|
STUB_NOERR 36
|
|
STUB_NOERR 37
|
|
STUB_NOERR 38
|
|
STUB_NOERR 39
|
|
STUB_NOERR 40
|
|
STUB_NOERR 41
|
|
STUB_NOERR 42
|
|
STUB_NOERR 43
|
|
STUB_NOERR 44
|
|
STUB_NOERR 45
|
|
STUB_NOERR 46
|
|
STUB_NOERR 47
|
|
|
|
.extern interruptDispatch
|
|
|
|
# Shared tail. Register push order here defines the CpuState field order.
|
|
isr_common:
|
|
push %rax
|
|
push %rbx
|
|
push %rcx
|
|
push %rdx
|
|
push %rsi
|
|
push %rdi
|
|
push %rbp
|
|
push %r8
|
|
push %r9
|
|
push %r10
|
|
push %r11
|
|
push %r12
|
|
push %r13
|
|
push %r14
|
|
push %r15
|
|
mov %rsp, %rdi # first argument: pointer to the trap frame
|
|
call interruptDispatch
|
|
pop %r15
|
|
pop %r14
|
|
pop %r13
|
|
pop %r12
|
|
pop %r11
|
|
pop %r10
|
|
pop %r9
|
|
pop %r8
|
|
pop %rbp
|
|
pop %rdi
|
|
pop %rsi
|
|
pop %rdx
|
|
pop %rcx
|
|
pop %rbx
|
|
pop %rax
|
|
add $16, %rsp # drop the vector and error code
|
|
iretq
|