bits 16

section .rodata

global smp_trampoline_start
smp_trampoline_start:
    cli
    cld

    mov ebx, cs
    shl ebx, 4

    o32 lidt [cs:(invalid_idt - smp_trampoline_start)]
    o32 lgdt [cs:(passed_info.gdtr - smp_trampoline_start)]

    lea eax, [ebx + (.mode32 - smp_trampoline_start)]
    mov [cs:(.farjmp_off - smp_trampoline_start)], eax

    mov eax, 0x00000011
    mov cr0, eax
    o32 jmp far [cs:(.farjmp - smp_trampoline_start)]

  .farjmp:
    .farjmp_off: dd 0
    .farjmp_seg: dd 0x18

    bits 32
  .mode32:
    mov ax, 0x20
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    xor eax, eax
    lldt ax

    xor eax, eax
    mov cr4, eax

    mov esi, ebx
    mov eax, 1
    xor ecx, ecx
    cpuid
    test edx, 1 << 16
    jz .no_pat
    mov ecx, 0x277
    mov eax, 0x00070406
    mov edx, 0x00000105
    wrmsr
  .no_pat:
    mov ebx, esi

    mov ecx, 0x1b
    rdmsr
    test eax, (1 << 10)
    jz .write_apic_msr

    ; Check if target also has x2APIC
    test dword [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)], (1 << 10)
    jnz .write_apic_msr

    ; AP is x2APIC but target is xAPIC: go through disabled state
    btr eax, 11
    btr eax, 10
    wrmsr

  .write_apic_msr:
    mov eax, [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)]
    mov edx, [ebx + (passed_info.bsp_apic_addr_msr_hi - smp_trampoline_start)]
    bts eax, 11
    btr eax, 8
    wrmsr
    mov esp, [ebx + (passed_info.temp_stack - smp_trampoline_start)]

    mov eax, cr4
    bts eax, 5
    mov cr4, eax

    ; Set EFER (LME + NX if available), all other bits cleared
    mov ecx, 0xc0000080
    xor edx, edx
    mov eax, 1 << 8
    test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 3)
    jz .no_nx
    or eax, 1 << 11
  .no_nx:
    wrmsr

    test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 1)
    jz .no5lv

    mov eax, cr4
    bts eax, 12
    mov cr4, eax

  .no5lv:
    mov eax, dword [ebx + (passed_info.pagemap - smp_trampoline_start)]
    mov cr3, eax

    ; Enable CR0.PG (and WP if requested)
    mov eax, cr0
    test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 4)
    jz .no_wp
    bts eax, 16
  .no_wp:
    bts eax, 31
    mov cr0, eax

%ifdef IA32_TARGET
    ; Synchronise MTRRs with BSP
    call [ebx + (passed_info.mtrr_restore - smp_trampoline_start)]

    ; Configure local APIC handoff state (if pointer is set)
    mov eax, dword [ebx + (passed_info.lapic_setup - smp_trampoline_start)]
    test eax, eax
    jz .skip_lapic_setup32
    call eax
  .skip_lapic_setup32:
%endif

    lea eax, [ebx + (.mode64 - smp_trampoline_start)]
    push 0x28
    push eax
    retf

    bits 64
  .mode64:
    mov ax, 0x30
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov ebx, ebx
    mov rax, qword [rbx + (passed_info.hhdm - smp_trampoline_start)]
    add qword [rbx + (passed_info.gdtr - smp_trampoline_start) + 2], rax
    lgdt [rbx + (passed_info.gdtr - smp_trampoline_start)]

    ; Clear TSS busy bit and load TR
    mov rcx, [rbx + (passed_info.gdtr - smp_trampoline_start) + 2]
    mov byte [rcx + 0x3d], 0x89
    mov ecx, 0x38
    ltr cx

    lea rax, [rax + rbx + (parking64 - smp_trampoline_start)]

    jmp rax

bits 64
parking64:
    mov ebx, ebx

%ifdef X86_64_TARGET
    ; Synchronise MTRRs with BSP
    call [rbx + (passed_info.mtrr_restore - smp_trampoline_start)]

    ; Configure local APIC handoff state (if pointer is set)
    mov rax, [rbx + (passed_info.lapic_setup - smp_trampoline_start)]
    test rax, rax
    jz .skip_lapic_setup64
    call rax
  .skip_lapic_setup64:
%endif

    mov edi, dword [rbx + (passed_info.smp_info_struct - smp_trampoline_start)]
    add rdi, qword [rbx + (passed_info.hhdm - smp_trampoline_start)]

    mov eax, 1
    xchg dword [rbx + (passed_info.booted_flag - smp_trampoline_start)], eax

    ; Check for MONITOR/MWAIT support
    mov eax, 1
    xor ecx, ecx
    cpuid
    test ecx, (1 << 3)
    jnz .monitor_spin

  .loop:
    mov rax, qword [rdi + 16]
    test rax, rax
    jnz .out
    pause
    jmp .loop

  .monitor_spin:
    mov rax, qword [rdi + 16]
    test rax, rax
    jnz .out
    lea rax, [rdi + 16]
    xor ecx, ecx
    xor edx, edx
    monitor
    mov rax, qword [rdi + 16]
    test rax, rax
    jnz .out
    xor eax, eax
    xor ecx, ecx
    mwait
    jmp .monitor_spin

  .out:
    ; Clear TLB
    mov rbx, cr3
    mov cr3, rbx

    ; Switch to new stack (HHDM address, safe after lower half unmap)
    mov rsp, qword [rdi + 8]

    ; Push fake return address
    push 0
    mov rsi, rsp

    ; Prepare iretq frame
    push 0x30
    push rsi
    push 0x2
    push 0x28
    push rax

    ; Zero out all GPRs (except rdi = mp_info pointer)
    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx
    xor edx, edx
    xor esi, esi
    xor ebp, ebp
    xor r8d,  r8d
    xor r9d,  r9d
    xor r10d, r10d
    xor r11d, r11d
    xor r12d, r12d
    xor r13d, r13d
    xor r14d, r14d
    xor r15d, r15d

    iretq

invalid_idt:
    times 2 dq 0

align 16
passed_info:
    .booted_flag db 0
    .target_mode db 0
    .pagemap dd 0
    .smp_info_struct dd 0
    .gdtr:
        dw 0
        dq 0
    .hhdm:
        dq 0
    .bsp_apic_addr_msr_lo:
        dd 0
    .bsp_apic_addr_msr_hi:
        dd 0
    .mtrr_restore:
        dq 0
    .temp_stack:
        dq 0
    .lapic_setup:
        dq 0

smp_trampoline_end:

global smp_trampoline_size
smp_trampoline_size dq smp_trampoline_end - smp_trampoline_start

section .note.GNU-stack noalloc noexec nowrite progbits
