# AP trampoline: brings a waking application processor from the 16-bit real mode it # starts in (after INIT-SIPI-SIPI) up through protected mode into 64-bit long mode, # then jumps to the Zig AP entry (arch/x86_64/smp.zig:apEntry). # # A STARTUP IPI vectors a core to physical address `vector << 12` in real mode, so # this blob is copied to a low (<1 MiB) page and started there; at entry CS = that # page >> 4 and IP = 0. It is fully **position-independent**: it derives its own # linear base (CS << 4) into EBX and addresses every internal datum as # `(label - ap_trampoline_start)(%ebx)` — a difference of two symbols in the same # section, which the assembler folds to a constant page offset no matter where the # blob was linked or copied to. The BSP patches the parameter block (CR3, stack, # entry, per-CPU pointer) before each wake; see arch/x86_64/smp.zig. # # It lives in .rodata (not .text): it is data to be copied out and executed # elsewhere, never run at its link address, so it must not be a normal code segment. .section .rodata .balign 16 .code16 .global ap_trampoline_start ap_trampoline_start: cli cld # Linear base of this page (CS << 4) into EBX; all data is addressed off it. xorl %eax, %eax mov %cs, %ax shll $4, %eax movl %eax, %ebx mov %cs, %ax # DS = CS, so we address our data as DS:(label - start): mov %ax, %ds # the segment base (CS<<4) already supplies the page base, # so data operands use the page *offset*, not EBX. # Relocate the pointers whose absolute (linear) targets depend on where we were # copied: the GDT base and the two far-jump targets = EBX + their page offsets. # EBX supplies the base for the *value* (via leal); the store address is DS-rel. leal (gdt32 - ap_trampoline_start)(%ebx), %eax movl %eax, gdtr32_base - ap_trampoline_start leal (prot_entry - ap_trampoline_start)(%ebx), %eax movl %eax, jmp32_off - ap_trampoline_start leal (long_entry - ap_trampoline_start)(%ebx), %eax movl %eax, jmp64_off - ap_trampoline_start lgdtl gdtr32 - ap_trampoline_start movl %cr0, %eax # enter protected mode (CR0.PE) orl $1, %eax movl %eax, %cr0 ljmpl *(jmp32_ptr - ap_trampoline_start) # -> prot_entry, CS = 0x08 .code32 prot_entry: movw $0x10, %ax # flat 32-bit data segments movw %ax, %ds movw %ax, %es movw %ax, %ss movw %ax, %fs movw %ax, %gs # CR4: PAE (required for long mode) + OSFXSR/OSXMMEXCPT. The kernel is built with # SSE (part of the x86_64 baseline), and the compiler emits SSE for things as # ordinary as a struct copy — without OSFXSR those instructions #UD. The BSP got # these bits from UEFI; an AP starts fresh, so we must set them ourselves. movl %cr4, %eax orl $((1 << 5) | (1 << 9) | (1 << 10)), %eax movl %eax, %cr4 # CR0: clear EM (no x87 emulation) and set MP, so SSE/x87 don't fault. movl %cr0, %eax andl $~(1 << 2), %eax # ~EM orl $(1 << 1), %eax # MP movl %eax, %cr0 movl (param_cr3 - ap_trampoline_start)(%ebx), %eax # kernel page tables movl %eax, %cr3 movl $0xC0000080, %ecx # EFER: long mode enable (LME) + NX enable (NXE, since rdmsr # the kernel's PTEs set the NX bit) orl $((1 << 8) | (1 << 11)), %eax wrmsr movl %cr0, %eax # paging on (CR0.PG) — now in long mode (compat sub-mode) orl $(1 << 31), %eax movl %eax, %cr0 ljmpl *(jmp64_ptr - ap_trampoline_start)(%ebx) # -> long_entry, CS = 0x18 (L=1) .code64 long_entry: movw $0x10, %ax # sane flat data segments movw %ax, %ds movw %ax, %es movw %ax, %ss # RBX = EBX (zero-extended) = page base. Load our stack and per-CPU pointer, then # call the Zig entry — which runs from the kernel image and never returns. movq (param_stack - ap_trampoline_start)(%rbx), %rsp movq (param_percpu - ap_trampoline_start)(%rbx), %rdi # SysV arg 0 movq (param_entry - ap_trampoline_start)(%rbx), %rax callq *%rax 1: hlt # unreachable; guard against a stray return jmp 1b # --- data: GDT, far pointers, and the BSP-patched parameter block ----------- .balign 8 gdt32: .quad 0x0000000000000000 # 0x00 null .quad 0x00CF9A000000FFFF # 0x08 32-bit code (G, D, present, exec/read) .quad 0x00CF92000000FFFF # 0x10 data (valid in 32- and 64-bit) .quad 0x00AF9A000000FFFF # 0x18 64-bit code (L=1) gdt32_end: gdtr32: .word gdt32_end - gdt32 - 1 gdtr32_base: .long 0 # patched (16-bit code): linear base of gdt32 jmp32_ptr: # indirect far-jump operand: offset then selector jmp32_off: .long 0 # patched: linear address of prot_entry .word 0x08 # 32-bit code selector jmp64_ptr: jmp64_off: .long 0 # patched: linear address of long_entry .word 0x18 # 64-bit code selector # The parameter block, filled in by the BSP (smp.zig) before each STARTUP IPI. Global # so the Zig side can locate each field as (symbol - ap_trampoline_start). .global ap_tramp_cr3 .global ap_tramp_stack .global ap_tramp_entry .global ap_tramp_percpu param_cr3: ap_tramp_cr3: .quad 0 # kernel PML4 physical address (CR3) param_stack: ap_tramp_stack: .quad 0 # top of this AP's kernel stack param_entry: ap_tramp_entry: .quad 0 # address of apEntry (the Zig AP entry) param_percpu: ap_tramp_percpu: .quad 0 # this AP's per-CPU pointer (goes in GS base) .global ap_trampoline_end ap_trampoline_end: