11; ═══════════════════════════════════════════════════════════════════════════
2- ; context_switch.s — Timer ISR + preemptive context switch
2+ ; context_switch.s — Timer ISR + preemptive context switch (SMP-safe)
33;
44; ABI: Microsoft x64 (RCX, RDX, R8, R9, xmm0-3, shadow space)
55; Format: PE/COFF (win64)
66;
77; Exports:
8- ; irq_timer_isr — installed in IDT vector 0x20 (PIT IRQ 0 ).
8+ ; irq_timer_isr — installed in IDT vector 0x20 (LAPIC timer ).
99; Saves the current CpuContext + FPU/SSE state,
1010; calls scheduler_tick() (Rust, MS x64), restores
1111; the next process's state, and resumes via iretq.
1212;
13+ ; Per-CPU data is accessed via GS segment register. The kernel sets
14+ ; IA32_GS_BASE to point at the PerCpu struct for each core. SWAPGS
15+ ; is used on transitions between ring 3 and ring 0.
16+ ;
17+ ; PerCpu field offsets (must match per_cpu.rs):
18+ ; gs:[0x00] self_ptr
19+ ; gs:[0x08] cpu_id (u32)
20+ ; gs:[0x0C] current_pid (u32)
21+ ; gs:[0x10] next_cr3 (u64)
22+ ; gs:[0x18] current_fpu_ptr (u64)
23+ ; gs:[0x20] kernel_syscall_rsp (u64)
24+ ; gs:[0x28] user_rsp_scratch (u64)
25+ ; gs:[0x30] tss_ptr (u64)
26+ ; gs:[0x38] lapic_base (u64)
27+ ; gs:[0x40] tick_count (u64)
28+ ;
1329; CpuContext field layout (must match hwinit/src/process/context.rs):
1430; 0x00 rax
1531; 0x08 rbx
3349; 0x98 ss
3450; Total: 0xA0 (160) bytes
3551;
36- ; FPU/SSE state (FpuState):
37- ; Saved/restored via FXSAVE/FXRSTOR through `current_fpu_ptr`.
38- ; 512 bytes, 16-byte aligned, stored per-process in PROCESS_TABLE.
39- ; The pointer is updated by scheduler_tick() when it picks the next process.
40- ;
4152; iretq frame layout pushed by CPU at ISR entry (all 8-byte slots):
42- ; [rsp+0x00] RIP (return address in interrupted code)
43- ; [rsp+0x08] CS (code segment selector, zero-extended)
53+ ; [rsp+0x00] RIP
54+ ; [rsp+0x08] CS
4455; [rsp+0x10] RFLAGS
45- ; [rsp+0x18] RSP (stack pointer before interrupt)
46- ; [rsp+0x20] SS (stack segment selector, zero-extended)
56+ ; [rsp+0x18] RSP
57+ ; [rsp+0x20] SS
4758; Size: 0x28 (40) bytes
4859; ═══════════════════════════════════════════════════════════════════════════
4960
5061bits 64
5162default rel
5263
53- ; ── Data ──────────────────────────────────────────────────────────────────
54- section .data
55-
56- align 8
57- global next_cr3
58- next_cr3: dq 0
59-
60- ; Pointer to the FpuState of the currently-running process.
61- ; Written by scheduler_tick() on every switch; read by this ISR for
62- ; FXSAVE (outgoing) and FXRSTOR (incoming). NULL during early boot
63- ; before the scheduler is initialized — guarded by a null check.
64- align 16
65- global current_fpu_ptr
66- current_fpu_ptr: dq 0
64+ ; LAPIC EOI register (identity-mapped)
65+ %define LAPIC_EOI_ADDR 0xFEE000B0
6766
6867section .text
6968
7069global irq_timer_isr
7170extern scheduler_tick ; Rust fn: unsafe extern "C" (MS x64 ABI)
7271
7372; ───────────────────────────────────────────────────────────────────────────
74- ; irq_timer_isr — PIT timer interrupt handler (vector 0x20)
75- ; ───────────────────────────────────────────────────────────────────────────
76- ; Stack layout at ISR entry (before any pushes):
77- ; [rsp+0x28] SS
78- ; [rsp+0x20] RSP (before IRQ)
79- ; [rsp+0x18] RFLAGS
80- ; [rsp+0x10] CS
81- ; [rsp+0x00] RIP
82- ;
83- ; After `sub rsp, 0xA0` our CpuContext struct lives at [rsp]:
84- ; [rsp+0x00 .. 0x9F] CpuContext
85- ; [rsp+0xA0 .. 0xC7] CPU iretq frame (5 × 8 bytes)
73+ ; irq_timer_isr — LAPIC timer interrupt handler (vector 0x20)
8674; ───────────────────────────────────────────────────────────────────────────
8775irq_timer_isr:
76+ ; ── SWAPGS if coming from user mode (ring 3) ─────────────────────────
77+ ; check CS RPL in the iretq frame pushed by CPU
78+ test qword [ rsp + 0x08 ], 3 ; CS is at rsp+8 (second qword)
79+ jz .no_swapgs_entry
80+ swapgs
81+ .no_swapgs_entry:
82+
8883 ; ── Allocate CpuContext on stack ──────────────────────────────────────
8984 sub rsp , 0xA0
9085
@@ -118,49 +113,42 @@ irq_timer_isr:
118113 mov [ rsp + 0x98 ], rax
119114
120115 ; ── Save outgoing process FPU/SSE state (FXSAVE) ─────────────────────
121- ; current_fpu_ptr → &proc.fpu_state of the process being preempted.
122- ; Must happen BEFORE calling Rust (scheduler_tick may use XMM regs).
123- mov rbx , [ rel current_fpu_ptr ]
116+ ; per-CPU FPU pointer: gs:[0x18]
117+ mov rbx , [ gs : 0x18 ] ; current_fpu_ptr
124118 test rbx , rbx
125119 jz .skip_fxsave
126120 fxsave [ rbx ]
127121.skip_fxsave:
128122
129- ; ── ACK PIT (send EOI to master PIC before calling Rust) ─────────────
130- mov al , 0x20
131- out 0x20 , al
123+ ; ── ACK LAPIC (write 0 to EOI register) ───────────────── ─────────────
124+ xor eax , eax
125+ mov dword [ LAPIC_EOI_ADDR ], eax
132126
133127 ; ── Call scheduler_tick(current_ctx: *const CpuContext) ──────────────
134128 ; MS x64 ABI: first arg in RCX. Need 32-byte shadow space on stack.
135- ; Current RSP = 8 mod 16 (verified in file header comment).
136- ; After sub 32 still 8 mod 16; CALL pushes 8 → callee sees 0 mod 16. ✓
137129 sub rsp , 32 ; shadow space
138130 lea rcx , [ rsp + 32 ] ; ¤t_ctx
139131 call scheduler_tick ; RAX = *const CpuContext (next proc)
140132 add rsp , 32 ; remove shadow space
141133
142- ; RAX = *const CpuContext of next process (points into PROCESS_TABLE) .
143- ; scheduler_tick has updated current_fpu_ptr to point to the incoming
144- ; process's FpuState .
134+ ; RAX = *const CpuContext of next process.
135+ ; scheduler_tick has updated gs:[0x18] ( current_fpu_ptr) and
136+ ; gs:[0x10] (next_cr3) for the incoming process .
145137
146138 ; ── Restore incoming process FPU/SSE state (FXRSTOR) ──────────────────
147- ; Must happen AFTER scheduler_tick (which updated the pointer) and
148- ; BEFORE restoring GPRs (FXRSTOR clobbers no GPRs, but we use RBX as
149- ; scratch — RBX will be properly restored from the next context below).
150- mov rbx , [ rel current_fpu_ptr ]
139+ mov rbx , [ gs : 0x18 ] ; updated current_fpu_ptr
151140 test rbx , rbx
152141 jz .skip_fxrstor
153142 fxrstor [ rbx ]
154143.skip_fxrstor:
155144
156145 ; ── Switch CR3 if process address spaces differ ───────────────────────
157- ; next_cr3 is written by scheduler_tick() before returning.
158- mov rbx , [ rel next_cr3 ]
146+ mov rbx , [ gs : 0x10 ] ; next_cr3 from PerCpu
159147 test rbx , rbx
160- jz .skip_cr3 ; zero = unset, don't switch
148+ jz .skip_cr3
161149 mov rcx , cr3
162150 cmp rbx , rcx
163- je .skip_cr3 ; same address space — avoid TLB flush
151+ je .skip_cr3
164152 mov cr3 , rbx
165153.skip_cr3:
166154
@@ -177,7 +165,6 @@ irq_timer_isr:
177165 mov [ rsp + 0xA0 + 0x20 ], rbx
178166
179167 ; ── Restore GPRs from next-process context ────────────────────────────
180- ; rbx is restored last (used as scratch above) except rax (used as ptr).
181168 mov r15 , [ rax + 0x70 ]
182169 mov r14 , [ rax + 0x68 ]
183170 mov r13 , [ rax + 0x60 ]
@@ -191,9 +178,18 @@ irq_timer_isr:
191178 mov rsi , [ rax + 0x20 ]
192179 mov rdx , [ rax + 0x18 ]
193180 mov rcx , [ rax + 0x10 ]
194- mov rbx , [ rax + 0x08 ] ; restore rbx (was used as scratch)
195- mov rax , [ rax + 0x00 ] ; restore rax last
181+ mov rbx , [ rax + 0x08 ]
182+ mov rax , [ rax + 0x00 ]
183+
184+ ; ── Remove CpuContext frame ───────────────────────────────────────────
185+ add rsp , 0xA0
186+
187+ ; ── SWAPGS if returning to user mode (ring 3) ────────────────────────
188+ ; check next CS in the patched iretq frame
189+ test qword [ rsp + 0x08 ], 3
190+ jz .no_swapgs_exit
191+ swapgs
192+ .no_swapgs_exit:
196193
197194 ; ── Return to next process ────────────────────────────────────────────
198- add rsp , 0xA0 ; remove CpuContext frame
199195 iretq
0 commit comments