strat9_kernel/arch/x86_64/
smp.rs1use core::{
15 arch::global_asm,
16 sync::atomic::{AtomicBool, AtomicUsize, Ordering},
17};
18
19use alloc::{vec, vec::Vec};
20use x86_64::{
21 structures::paging::{Page, PageTableFlags, PhysFrame, Size4KiB},
22 PhysAddr, VirtAddr,
23};
24
25use crate::{
26 acpi::madt,
27 arch::x86_64::{apic, idt, io::io_wait, percpu, timer},
28 memory,
29};
30
31pub const TRAMPOLINE_PHYS_ADDR: u64 = 0x8000;
33
34static BOOTED_CORES: AtomicUsize = AtomicUsize::new(1);
36static SYNC_BARRIER: AtomicUsize = AtomicUsize::new(0);
38static BARRIER_TARGET: AtomicUsize = AtomicUsize::new(0);
40static AP_SCHED_GATE_OPEN: AtomicBool = AtomicBool::new(false);
42
43#[cfg(target_arch = "x86_64")]
69global_asm!(
70 r#"
71.section .text
72.code16
73
74.global smp_trampoline
75.global smp_trampoline_end
76
77.set SMP_VAR_ADDR, 0x8000 + (smp_trampoline_end - smp_trampoline)
78
79smp_trampoline:
80 cli
81 cld
82 # Jump over the GDT data : code continues at 0x8040.
83 ljmp 0, 0x8040
84
85# -------------------------------------------------------------------
86# GDT : must be at physical offset 0x10 so that:
87# _gdt_table starts at 0x8010, _gdt (GDTR) is at 0x8030.
88# -------------------------------------------------------------------
89.align 16
90_gdt_table:
91 .long 0, 0 # null (selector 0)
92 .long 0x0000ffff, 0x00af9a00 # code64 (selector 8): 64-bit ring-0
93 .long 0x0000ffff, 0x00cf9200 # data (selector 16): ring-0 rw
94 .long 0x0000ffff, 0x00cf9a00 # code32 (selector 24): 32-bit ring-0
95_gdt:
96 .word _gdt - _gdt_table - 1 # limit = 31 (4 entries × 8 - 1)
97 .long 0x8010 # base = 0x8010
98 .long 0, 0 # padding
99.align 64
100
101# -------------------------------------------------------------------
102# Real-mode setup continues at 0x8040.
103# -------------------------------------------------------------------
104 xor ax, ax
105 mov ds, ax
106 lgdt [0x8030] # loads GDTR from 0x8030
107
108 # Enter protected mode
109 mov eax, cr0
110 or eax, 1
111 mov cr0, eax
112 ljmp 24, 0x8060 # → code32 segment
113
114.align 32
115.code32
116 mov ax, 16
117 mov ds, ax
118 mov ss, ax
119
120 # Enable PAE + PSE + OSFXSR + OSXMMEXCPT
121 # OSXSAVE is NOT set here : enabled conditionally in Rust
122 mov eax, cr4
123 or eax, 0x630
124 mov cr4, eax
125
126 # Enable Long Mode (EFER.LME)
127 mov ecx, 0xc0000080
128 xor edx, edx
129 rdmsr
130 or eax, 0x901 # LME + SCE
131 wrmsr
132
133 # Load kernel PML4 from data area
134 mov eax, [SMP_VAR_ADDR]
135 mov cr3, eax
136
137 # Enable paging (activates long mode)
138 mov eax, cr0
139 and eax, 0xFFFFFFFB # Clear EM
140 or eax, 0x80010002 # PG + WP + MP
141 mov cr0, eax
142
143 ljmp 8, 0x80c0 # → code64 segment
144
145.align 32
146.code64
147 # Load kernel stack pointer
148 mov rsp, [SMP_VAR_ADDR + 8]
149
150 # Clear RFLAGS
151 push 0
152 popfq
153
154 # Jump to smp_main (Rust)
155 # NOTE: must use movabs (64-bit absolute), NOT RIP-relative lea.
156 # The trampoline is copied to 0x8000, so RIP-relative would compute
157 # an offset based on the wrong base address (the linker's original
158 # placement in the kernel .text section, not 0x8000).
159 movabs rax, offset smp_main
160 jmp rax
161
162.align 8
163smp_trampoline_end:
164"#
165);
166
167unsafe extern "C" {
168 fn smp_trampoline();
169 fn smp_trampoline_end();
170}
171
172fn udelay(us: u32) {
174 for _ in 0..us {
175 io_wait();
176 }
177}
178
179fn ensure_identity_mapping(phys_start: u64, length: usize) {
182 let start = phys_start & !0xFFFu64;
183 let end = (phys_start + length as u64 + 0xFFF) & !0xFFFu64;
184 let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
185
186 let mut addr = start;
187 while addr < end {
188 let virt = VirtAddr::new(addr);
189 if let Some(mapped) = crate::memory::paging::translate(virt) {
190 if mapped.as_u64() != addr {
191 log::warn!(
192 "SMP: identity map collision at {:#x} -> {:#x}",
193 addr,
194 mapped.as_u64()
195 );
196 }
197 } else {
198 let page = Page::<Size4KiB>::containing_address(virt);
199 let frame = PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(addr));
200 if let Err(e) = crate::memory::paging::map_page(page, frame, flags) {
201 log::error!("SMP: failed to identity map {:#x}: {}", addr, e);
202 }
203 }
204 addr += 0x1000;
205 }
206}
207
208fn copy_trampoline(cr3_phys: u64, stack_top_virt: u64) {
221 let tramp_len = (smp_trampoline_end as *const u8 as usize)
222 .saturating_sub(smp_trampoline as *const u8 as usize);
223
224 ensure_identity_mapping(TRAMPOLINE_PHYS_ADDR, tramp_len + 16);
225
226 let tramp_virt = memory::phys_to_virt(TRAMPOLINE_PHYS_ADDR) as *mut u8;
227
228 unsafe {
230 core::ptr::copy_nonoverlapping(smp_trampoline as *const u8, tramp_virt, tramp_len);
231 let data = tramp_virt.add(tramp_len) as *mut u64;
232 core::ptr::write_volatile(data, cr3_phys);
234 core::ptr::write_volatile(data.add(1), stack_top_virt);
236
237 core::arch::asm!("sfence");
239 core::arch::asm!("wbinvd");
240 }
241}
242
243fn wait_delivery() {
249 if apic::is_x2apic_enabled() {
251 return;
252 }
253
254 const DELIVERY_PENDING: u32 = 1 << 12;
255 for i in 0..1_000_000 {
256 let val = unsafe { apic::read_reg(apic::REG_ICR_LOW) };
257 if val & DELIVERY_PENDING == 0 {
258 return;
259 }
260 if i > 0 && i % 200_000 == 0 {
261 crate::serial_println!(
262 "[smp] wait_delivery: still pending (iter={}, icr={:#x})",
263 i,
264 val,
265 );
266 }
267 core::hint::spin_loop();
268 }
269 let final_val = unsafe { apic::read_reg(apic::REG_ICR_LOW) };
270 crate::serial_println!("[smp] wait_delivery: TIMEOUT, final icr={:#x}", final_val,);
271 log::warn!("SMP: IPI delivery timeout");
272}
273
274fn send_ipi(apic_id: u32, value: u32) {
276 apic::send_ipi_raw(apic_id, value);
277 wait_delivery();
278}
279
280fn send_init_sipi(apic_id: u32) {
297 crate::serial_println!("[smp] send_init_sipi: apic_id={}", apic_id);
298
299 crate::serial_println!("[smp] INIT assert -> {}", apic_id);
301 send_ipi(apic_id, 0xC500);
302 udelay(10_000);
303
304 crate::serial_println!("[smp] INIT de-assert -> {}", apic_id);
306 send_ipi(apic_id, 0x8500);
307 udelay(200);
308
309 crate::serial_println!("[smp] SIPI (1/2) -> {} (vector=0x8)", apic_id);
311 send_ipi(apic_id, 0x0608);
312 udelay(200);
313
314 crate::serial_println!("[smp] SIPI (2/2) -> {} (vector=0x8)", apic_id);
316 send_ipi(apic_id, 0x0608);
317 udelay(200);
318
319 crate::serial_println!("[smp] INIT+SIPI complete for {}", apic_id);
320}
321
322pub fn broadcast_panic_halt() {
326 if !apic::is_initialized() {
327 return;
328 }
329 let icr_low = (0b11 << 18) | (0b100 << 8) | (1 << 14);
330 apic::send_ipi_raw(0, icr_low);
331}
332
333fn rendezvous_barrier() {
335 let expected = BARRIER_TARGET.load(Ordering::Acquire);
336 SYNC_BARRIER.fetch_add(1, Ordering::AcqRel);
337 while SYNC_BARRIER.load(Ordering::Acquire) < expected {
338 core::hint::spin_loop();
339 }
340}
341
342pub fn init() -> Result<usize, &'static str> {
353 crate::serial_println!("[smp] init: entering SMP initialization");
354 if !apic::is_initialized() {
355 crate::serial_println!("[smp] init: ERROR - APIC not initialized");
356 return Err("APIC not initialized");
357 }
358
359 BOOTED_CORES.store(1, Ordering::Release);
360 SYNC_BARRIER.store(0, Ordering::Release);
361 BARRIER_TARGET.store(0, Ordering::Release);
362
363 let madt_info = madt::parse_madt().ok_or("MADT not available")?;
364 let bsp_apic_id = apic::lapic_id();
365
366 crate::serial_println!(
367 "[smp] init: MADT parsed, {} local APICs, BSP apic_id={}",
368 madt_info.local_apic_count,
369 bsp_apic_id,
370 );
371
372 if madt_info.local_apic_count <= 1 {
373 crate::serial_println!("[smp] init: single CPU system, returning");
374 log::info!("SMP: single CPU system");
375 return Ok(1);
376 }
377
378 let mut max_apic_id: usize = 0;
379 for i in 0..madt_info.local_apic_count {
380 if let Some(ref entry) = madt_info.local_apics[i] {
381 crate::serial_println!(
382 "[smp] APIC[{}]: id={} proc={} flags={:#x} {}",
383 i,
384 entry.apic_id,
385 entry.processor,
386 entry.flags,
387 if entry.flags & 1 == 0 {
388 "(DISABLED)"
389 } else {
390 ""
391 },
392 );
393 max_apic_id = max_apic_id.max(entry.apic_id as usize);
394 }
395 }
396
397 let mut stack_tops: Vec<u64> = vec![0; max_apic_id + 1];
398 let cr3_phys = crate::memory::paging::kernel_l4_phys().as_u64();
399 let mut targets: Vec<u32> = Vec::new();
400 let mut expected: usize = 1;
401
402 for i in 0..madt_info.local_apic_count {
403 let Some(ref entry) = madt_info.local_apics[i] else {
404 continue;
405 };
406
407 let apic_id = entry.apic_id as u32;
408 if apic_id == bsp_apic_id {
409 continue;
410 }
411
412 let stack_size = crate::process::task::Task::DEFAULT_STACK_SIZE;
414 let pages = (stack_size + 4095) / 4096;
415 let order = pages.next_power_of_two().trailing_zeros() as u8;
416 let frame = crate::sync::with_irqs_disabled(|token| {
417 crate::memory::allocate_phys_contiguous(token, order)
418 })
419 .map_err(|_| "SMP: failed to allocate AP stack from buddy")?;
420 let stack_phys = frame.start_address.as_u64();
421 let stack_virt = crate::memory::phys_to_virt(stack_phys);
422
423 unsafe { core::ptr::write_bytes(stack_virt as *mut u8, 0, stack_size) };
425
426 let stack_top = stack_virt.saturating_add(stack_size as u64);
428
429 if apic_id as usize >= stack_tops.len() {
430 log::warn!("SMP: APIC id {} out of stack array range", apic_id);
431 continue;
432 }
433
434 stack_tops[apic_id as usize] = stack_top;
435
436 let cpu_index =
437 percpu::register_cpu(apic_id).ok_or("SMP: exceeded MAX_CPUS for per-CPU data")?;
438 percpu::set_kernel_stack_top(cpu_index, stack_top);
439
440 targets.push(apic_id);
441 expected += 1;
442 }
443
444 let first_stack_top = targets
449 .first()
450 .and_then(|id| stack_tops.get(*id as usize))
451 .copied()
452 .unwrap_or(0);
453 copy_trampoline(cr3_phys, first_stack_top);
454 crate::serial_println!(
455 "[smp] init: trampoline at {:#x}, cr3={:#x}, stack={:#x}",
456 TRAMPOLINE_PHYS_ADDR,
457 cr3_phys,
458 first_stack_top,
459 );
460
461 crate::serial_println!("[smp] init: sending INIT+SIPI to {} APs", targets.len(),);
463 for apic_id in &targets {
464 if let Some(stack_top) = stack_tops.get(*apic_id as usize) {
467 let tramp_len = (smp_trampoline_end as *const u8 as usize)
468 .saturating_sub(smp_trampoline as *const u8 as usize);
469 let data = memory::phys_to_virt(TRAMPOLINE_PHYS_ADDR) as *mut u64;
470 unsafe {
471 core::ptr::write_volatile(data.add(tramp_len / 8 + 1), *stack_top);
473 }
474 }
475 send_init_sipi(*apic_id);
476 }
477
478 let mut spins: u64 = 0;
480 const MAX_SPINS: u64 = 200_000_000;
481 crate::serial_println!("[smp] init: waiting for APs (expected={})...", expected,);
482 while BOOTED_CORES.load(Ordering::Acquire) < expected && spins < MAX_SPINS {
483 if spins > 0 && spins % 50_000_000 == 0 {
484 crate::serial_println!(
485 "[smp] init: waiting... online={} expected={} spins={}",
486 BOOTED_CORES.load(Ordering::Acquire),
487 expected,
488 spins,
489 );
490 }
491 core::hint::spin_loop();
492 spins = spins.saturating_add(1);
493 }
494 let online = BOOTED_CORES.load(Ordering::Acquire);
495 crate::serial_println!(
496 "[smp] init: AP wait done: online={} expected={} spins={}",
497 online,
498 expected,
499 spins,
500 );
501 if online < expected {
502 crate::serial_println!(
503 "[smp] init: WARNING only {}/{} APs online",
504 online,
505 expected,
506 );
507 log::warn!(
508 "SMP: timeout waiting APs (online={} expected={}), continuing",
509 online,
510 expected
511 );
512 }
513
514 log::info!("SMP: {} cores online (expected {})", online, expected);
515
516 BARRIER_TARGET.store(online, Ordering::Release);
518 rendezvous_barrier();
519
520 Ok(online)
521}
522
523#[unsafe(no_mangle)]
528pub extern "C" fn smp_main() -> ! {
529 {
532 use core::fmt::Write;
533 let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
534 port.init();
535 let _ = port.write_fmt(format_args!("[smp][ap] entered smp_main\n"));
536 }
537
538 idt::load();
539
540 apic::init_ap();
542
543 let apic_id = apic::lapic_id();
544 {
545 use core::fmt::Write;
546 let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
547 let _ = port.write_fmt(format_args!("[smp][ap] apic_id={}\n", apic_id));
548 }
549
550 let cpu_index = match percpu::cpu_index_by_apic(apic_id) {
551 Some(idx) => idx,
552 None => {
553 {
554 use core::fmt::Write;
555 let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
556 let _ = port.write_fmt(format_args!(
557 "[smp][ap] ERROR: apic_id={} not registered, halting\n",
558 apic_id
559 ));
560 }
561 loop {
562 core::hint::spin_loop();
563 }
564 }
565 };
566
567 crate::arch::x86_64::percpu::init_gs_base(cpu_index);
569
570 crate::arch::x86_64::tss::init_cpu(cpu_index);
572 crate::arch::x86_64::gdt::init_cpu(cpu_index);
573
574 crate::arch::x86_64::syscall::init();
575 crate::arch::x86_64::init_cpu_extensions();
576
577 if let Some(stack_top) = percpu::kernel_stack_top(cpu_index) {
578 crate::arch::x86_64::tss::set_kernel_stack_for(cpu_index, x86_64::VirtAddr::new(stack_top));
579 }
580
581 let _ = percpu::mark_online_by_apic(apic_id);
582 BOOTED_CORES.fetch_add(1, Ordering::Release);
583
584 {
585 use core::fmt::Write;
586 let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
587 let _ = port.write_fmt(format_args!(
588 "[smp][ap] cpu_index={} online, waiting for barrier\n",
589 cpu_index
590 ));
591 }
592
593 while BARRIER_TARGET.load(Ordering::Acquire) == 0 {
595 core::hint::spin_loop();
596 }
597 rendezvous_barrier();
598
599 crate::serial_println!("[trace][ap] cpu_index={} entering scheduler", cpu_index);
600
601 while !AP_SCHED_GATE_OPEN.load(Ordering::Acquire) {
603 core::hint::spin_loop();
604 }
605
606 timer::start_apic_timer_cached();
608
609 crate::process::scheduler::schedule_on_cpu(cpu_index)
611}
612
613pub fn cpu_count() -> usize {
615 BOOTED_CORES.load(Ordering::Acquire)
616}
617
618pub fn open_ap_scheduler_gate() {
620 AP_SCHED_GATE_OPEN.store(true, Ordering::Release);
621}