section .text
global enable_sse
enable_sse:
    mov rax, cr0
    and ax, 0xFFFB		;clear coprocessor emulation CR0.EM
    or ax, 0x2			;set coprocessor monitoring  CR0.MP
    mov cr0, rax
    mov rax, cr4
    or ax, 3 << 9		;set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
    mov cr4, rax
    ret

global enable_write_protect
enable_write_protect:
    mov rax, cr0
    or eax, 0x80000 ;set write-protect CR0.WP
    mov cr0, rax
    ret

global enable_nx
enable_nx:
    mov rcx, 0xC0000080 ; IA32_EFER
    rdmsr
    or eax, 1 << 11 ; no-execute enable (NXE)
    wrmsr
    ret

global load_gdt
load_gdt:
    cli
    lgdt [rdi]
    mov   ax, 0x10
    mov   ds, ax
    mov   es, ax
    mov   fs, ax
    mov   gs, ax
    mov   ss, ax
    push 0x08                 
    lea rax, [rel .reload_CS]
    push rax
    retfq
.reload_CS:
    ret

global load_tr
load_tr:
    mov rax, rdi
    ltr ax
    ret

extern switch_task

global kernel_yield
kernel_yield:
    mov rdi, [rsp] ; return address is now in RDI
    mov rcx, rsp ; save current RSP
    add rcx, 8 ; skip over the return address

    mov eax, ss
    push rax ; SS
    push rcx ; RSP
    pushfq ; RFLAGS
    mov eax, cs
    push rax ; CS
    push rdi ; RIP

    sub rsp, 24

    push rbx ; Preserve RBX
    
    sub rsp, 32

    push rbp ; Preserve RBP
    
    sub rsp, 32
    
    push r12 ; Preserve R12
    push r13 ; Preserve R13
    push r14 ; Preserve R14
    push r15 ; Preserve R15

    mov rdi, rsp
    call switch_task
    jmp _asm_interrupt_exit

%macro ISR 1
    global _isr%1
    _isr%1:
        push byte 0
        push byte %1
        jmp _asm_interrupt_entry
%endmacro

%macro ISR_ERROR 1
    global _isr%1
    _isr%1:
        push byte %1
        jmp _asm_interrupt_entry
%endmacro

%macro IRQ 2
    global _isr%1
    _isr%1:
        push byte %2
        push byte %1
        jmp _asm_interrupt_entry
%endmacro

extern arch_interrupt_entry
extern arch_double_fault
extern arch_machine_check

_asm_interrupt_entry:
    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

    cld

    mov rdi, rsp
    call arch_interrupt_entry

_asm_interrupt_exit:
    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 rsp, 16

    iretq

ISR 0 ; divide by zero (#DE)
ISR 1 ; debug (#DB)
ISR 2 ; non-maskable interrupt
ISR 3 ; breakpoint (#BP)
ISR 4 ; overflow (#OF)
ISR 5 ; bound range exceeded (#BR)
ISR 6 ; invalid opcode (#UD)
ISR 7 ; device not available (#NM)

global _isr8
_isr8: ; double fault (#DF)
    jmp arch_double_fault
    ud2 ; we aren't even pushing a return address for arch_double_fault, this is truly UNREACHABLE

; ISR 9 obsolete
ISR_ERROR 10 ; invalid tss (#TS)
ISR_ERROR 11 ; segment not present (#NP)
ISR_ERROR 12 ; stack-segment fault (#SS)
ISR_ERROR 13 ; general protection fault (#GP)
ISR_ERROR 14 ; page fault (#PF)
; ISR 15 reserved
ISR 16 ; x87 floating-point exception (#MF)
ISR_ERROR 17 ; alignment check (#AC)

global _isr18
_isr18: ; machine check (#MC)
    jmp arch_machine_check
    ud2 ; same as above

ISR 19 ; SIMD floating-point exception (#XM)
ISR 20 ; virtualization exception (#VE)
ISR_ERROR 21 ; control-protection exception (#CP)
; ISR 22-31 reserved
IRQ 32, 0 ; timer interrupt
IRQ 33, 1 ; keyboard interrupt
IRQ 34, 2
IRQ 35, 3
IRQ 36, 4
IRQ 37, 5
IRQ 38, 6
IRQ 39, 7
IRQ 40, 8
IRQ 41, 9
IRQ 42, 10
IRQ 43, 11
IRQ 44, 12
IRQ 45, 13
IRQ 46, 14
IRQ 47, 15
ISR 66 ; system call