@@ -30,10 +30,12 @@ ap_start:
3030 mov ss , ax
3131 xor sp , sp ; stack at top of segment (wraps to 0xFFFF)
3232
33- ; ── load the GDT pointer from the data area ──────────────────────────
34- ; data area is at this_page + 0xF00. Our segment base = 0x8000.
35- ; so offset within segment = 0xF00 + 0x20 (TD_GDT_PTR within data area)
36- lgdt [ 0xF20 ] ; 0x8000 + 0xF20 in linear addressing = offset 0xF20 from segment base
33+ ; ── load a TEMPORARY GDT that lives inside this trampoline page ──────
34+ ; The BSP's GDT is above 4 GB (PE BSS at 0x140xxxxxx). In 16-bit
35+ ; mode, lgdt only reads a 4-byte base → truncated → wrong GDT → #GP.
36+ ; We use a temp GDT at a known sub-1MB address for the mode transition,
37+ ; then reload the real 64-bit GDT once we're in long mode.
38+ lgdt [ 0xE38 ] ; segment offset → physical 0x8E38 (temp_gdt_ptr_16)
3739
3840 ; ── enter protected mode ──────────────────────────────────────────────
3941 mov eax , cr0
@@ -77,8 +79,10 @@ ap_pm32:
7779 or eax , ( 1 << 31 ) ; CR0.PG
7880 mov cr0 , eax
7981
80- ; far jump to 64-bit long mode code
81- jmp dword 0x08 :ap_lm64
82+ ; far jump to 64-bit long mode code.
83+ ; selector 0x18 = temp GDT's 64-bit code descriptor (L=1, D=0).
84+ ; NOT 0x08 — that's the 32-bit code descriptor we used to get here.
85+ jmp dword 0x18 :ap_lm64
8286
8387; ───────────────────────────────────────────────────────────────────────────
8488; 64-bit long mode
@@ -93,6 +97,25 @@ ap_lm64:
9397 mov ss , ax
9498 ; intentionally skip gs — Rust will set GS base via MSR
9599
100+ ; ── reload the BSP's ACTUAL 64-bit GDT from the data area ────────────
101+ ; In 64-bit mode lgdt reads the full 10-byte descriptor (2+8 base).
102+ ; The BSP's GDT is in PE BSS above 4 GB — now we can load it properly.
103+ lgdt [ 0x8F20 ] ; TD_GDT_PTR (flat 64-bit address)
104+
105+ ; reload data segments again with the real GDT's selectors
106+ mov ax , 0x10
107+ mov ds , ax
108+ mov es , ax
109+ mov fs , ax
110+ mov ss , ax
111+
112+ ; reload CS via a far return trick — push selector:offset, retfq
113+ lea rax , [ rel .cs_reloaded ]
114+ push 0x08 ; kernel code selector from BSP's GDT
115+ push rax
116+ retfq
117+ .cs_reloaded:
118+
96119 ; ── load the per-AP stack from data area ──────────────────────────────
97120 mov rsp , qword [ 0x8F10 ] ; TD_STACK
98121
@@ -105,8 +128,68 @@ ap_lm64:
105128 jmp rax ; ap_rust_entry(core_idx, lapic_id) — never returns
106129
107130; ───────────────────────────────────────────────────────────────────────────
108- ; Pad to keep total code well under 0xF00 (data area starts there)
131+ ; TEMP GDT (offset 0xE00) — used for the real→prot→long mode transition.
132+ ;
133+ ; The BSP's GDT lives in PE BSS above 4 GB. A 16-bit lgdt can only read
134+ ; a 4-byte base, truncating 0x140xxxxxx → garbage. This temp GDT at
135+ ; physical 0x8E00 is safely below 1 MB.
136+ ;
137+ ; We need TWO code descriptors:
138+ ; 0x08 — 32-bit code (D=1, L=0): for protected mode transition.
139+ ; D=1 gives 32-bit default operand size so `bits 32` instructions
140+ ; decode correctly. If D=0 the CPU interprets `or eax, imm32`
141+ ; as `or ax, imm16` and the instruction stream desynchronizes.
142+ ; yes this is what was causing the triple fault. yes it was that dumb.
143+ ; 0x18 — 64-bit code (L=1, D=0): for the far jump into long mode.
144+ ; Intel requires L=1 D=0 for 64-bit mode.
145+ ;
146+ ; After reaching 64-bit mode we reload the BSP's real GDT (which has its
147+ ; own code64 at selector 0x08) and retfq to it.
109148; ───────────────────────────────────────────────────────────────────────────
149+ times ( 0xE00 - ($ - $$)) db 0
150+
151+ ; 0xE00: temp GDT entries
152+ temp_gdt:
153+ ; 0x00: null descriptor
154+ dq 0
155+
156+ ; 0x08: 32-bit code (D=1, L=0, P=1, DPL=0, type=exec+read)
157+ ; used ONLY for the real→protected mode transition.
158+ dw 0xFFFF ; limit low (4GB with G=1)
159+ dw 0x0000 ; base low
160+ db 0x00 ; base mid
161+ db 0x9A ; P=1, DPL=0, S=1, type=0xA (exec/read)
162+ db 0xCF ; G=1, D=1, L=0, AVL=0, limit_hi=0xF
163+ db 0x00 ; base high
164+
165+ ; 0x10: flat data (P=1, DPL=0, writable, G=1, D=1, 4GB limit)
166+ dw 0xFFFF ; limit low
167+ dw 0x0000 ; base low
168+ db 0x00 ; base mid
169+ db 0x92 ; P=1, DPL=0, S=1, type=0x2 (data/write)
170+ db 0xCF ; G=1, D=1, L=0, AVL=0, limit_hi=0xF
171+ db 0x00 ; base high
172+
173+ ; 0x18: 64-bit code (L=1, D=0, P=1, DPL=0, type=exec+read)
174+ ; used for the far jump into long mode after LME+PG are set.
175+ dw 0x0000 ; limit low (ignored in 64-bit mode)
176+ dw 0x0000 ; base low
177+ db 0x00 ; base mid
178+ db 0x9A ; P=1, DPL=0, S=1, type=0xA (exec/read)
179+ db 0x20 ; G=0, L=1, D=0, AVL=0, limit_hi=0x0
180+ db 0x00 ; base high
181+ temp_gdt_end:
182+
183+ ; padding to 0xE38
184+ times ( 0xE38 - ($ - $$)) db 0
185+
186+ ; 0xE38: temp GDT pointer for 16-bit lgdt (6 bytes: limit + 32-bit base)
187+ ; segment offset 0xE38 → physical 0x8E38. lgdt reads 6 bytes in 16-bit mode.
188+ temp_gdt_ptr_16:
189+ dw (temp_gdt_end - temp_gdt - 1 ) ; limit = 31 (4 entries × 8 - 1)
190+ dd 0x00008E00 ; base = physical address of temp_gdt
191+
192+ ; pad to 0xF00
110193times ( 0xF00 - ($ - $$)) db 0
111194
112195; ───────────────────────────────────────────────────────────────────────────
0 commit comments