Skip to content

Commit f9e1bd9

Browse files
author
adrerl
committed
fixed it (FOR NOW LOL)
1 parent 197c362 commit f9e1bd9

8 files changed

Lines changed: 180 additions & 74 deletions

File tree

hwinit/src/cpu/per_cpu.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ pub struct PerCpu {
6464
pub in_tick: bool,
6565
/// True once this core has finished init and entered the scheduler.
6666
pub online: bool,
67+
/// AP's original kernel stack top (set during AP boot, never changed).
68+
/// Used to restore RSP when the AP returns to the idle loop after
69+
/// descheduling a user process. BSP doesn't use this.
70+
pub boot_kernel_rsp: u64,
6771
}
6872

6973
impl PerCpu {
@@ -81,6 +85,7 @@ impl PerCpu {
8185
tick_count: 0,
8286
in_tick: false,
8387
online: false,
88+
boot_kernel_rsp: 0,
8489
}
8590
}
8691
}
@@ -198,6 +203,12 @@ pub unsafe fn init_ap(core_idx: u32, lapic_id: u32, lapic_base: u64) {
198203
pcpu.lapic_base = lapic_base;
199204
pcpu.online = true;
200205

206+
// save the AP's kernel stack top so the scheduler can restore it
207+
// when the AP returns to idle after running a user process.
208+
let rsp: u64;
209+
core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem));
210+
pcpu.boot_kernel_rsp = (rsp + 0x1000) & !0xFFF;
211+
201212
LAPIC_TO_IDX[lapic_id as usize] = idx as u8;
202213

203214
let addr = pcpu as *const PerCpu as u64;

hwinit/src/process/scheduler.rs

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,42 @@ use crate::cpu::gdt::{KERNEL_CS, KERNEL_DS};
2828
use crate::memory::{global_registry_mut, is_registry_initialized, PAGE_SIZE};
2929
use crate::serial::{put_hex32, put_hex64, puts};
3030
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
31+
32+
use crate::cpu::per_cpu::MAX_CPUS;
33+
34+
// Per-AP idle contexts. When an AP descheduled a user process and nothing
35+
// else is runnable, we MUST NOT return the outgoing process's ring-3 context.
36+
// That would iretq into user code with current_pid=0, corrupting everything.
37+
// Instead we return a ring-0 context pointing at the AP idle HLT loop.
38+
static mut AP_IDLE_CTX: [CpuContext; MAX_CPUS] = [const { CpuContext::empty() }; MAX_CPUS];
39+
40+
/// Ring 0 HLT loop for idle APs. The LAPIC timer will fire and
41+
/// scheduler_tick will pick a real process if one becomes Ready.
42+
/// Never called directly — only entered via iretq from the ISR.
43+
#[inline(never)]
44+
unsafe fn ap_idle_hlt_loop() -> ! {
45+
loop {
46+
core::arch::asm!("sti; hlt; cli", options(nostack, nomem));
47+
}
48+
}
49+
50+
/// Build and return a kernel-mode idle context for the given AP core.
51+
/// Uses the AP's boot kernel stack and jumps to ap_idle_hlt_loop.
52+
unsafe fn ap_idle_context(core_idx: u32) -> &'static CpuContext {
53+
let ctx = &mut AP_IDLE_CTX[core_idx as usize];
54+
let pcpu = crate::cpu::per_cpu::current();
55+
ctx.rip = ap_idle_hlt_loop as u64;
56+
ctx.rsp = pcpu.boot_kernel_rsp;
57+
ctx.cs = KERNEL_CS as u64;
58+
ctx.ss = KERNEL_DS as u64;
59+
ctx.rflags = 0x202; // IF=1
60+
// zero all GPRs — clean state
61+
ctx.rax = 0; ctx.rbx = 0; ctx.rcx = 0; ctx.rdx = 0;
62+
ctx.rsi = 0; ctx.rdi = 0; ctx.rbp = 0;
63+
ctx.r8 = 0; ctx.r9 = 0; ctx.r10 = 0; ctx.r11 = 0;
64+
ctx.r12 = 0; ctx.r13 = 0; ctx.r14 = 0; ctx.r15 = 0;
65+
ctx
66+
}
3167
// GLOBAL STATE
3268

3369
/// The flat process table. Index == PID.
@@ -379,13 +415,27 @@ impl Scheduler {
379415
// TODO: Clean up after dead process :)
380416
match sig {
381417
Signal::SIGKILL => {
382-
puts("[SCHED] SIGKILL → PID ");
383-
put_hex32(pid);
384-
puts("\n");
385-
terminate_process_inner(slot, -9);
418+
// if the target is running a syscall on another core, it holds
419+
// &mut Process via current_process_mut() WITHOUT the lock.
420+
// calling terminate_process_inner here would race on the same
421+
// Process entry. defer: set pending signal, let the target
422+
// core's scheduler_tick deliver it after saving context.
423+
if slot.running_on != u32::MAX {
424+
slot.pending_signals.raise(Signal::SIGKILL);
425+
} else {
426+
puts("[SCHED] SIGKILL → PID ");
427+
put_hex32(pid);
428+
puts("\n");
429+
terminate_process_inner(slot, -9);
430+
}
386431
}
387432
Signal::SIGSTOP => {
388-
slot.state = ProcessState::Blocked(BlockReason::Io);
433+
// same race concern as SIGKILL — defer if running on another core.
434+
if slot.running_on != u32::MAX {
435+
slot.pending_signals.raise(Signal::SIGSTOP);
436+
} else {
437+
slot.state = ProcessState::Blocked(BlockReason::Io);
438+
}
389439
}
390440
Signal::SIGCONT => {
391441
if let ProcessState::Blocked(_) = slot.state {
@@ -655,14 +705,15 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
655705

656706
let next_pid = pick_next(0, true, core_idx);
657707
if next_pid == 0 {
658-
// no user process available — keep idling. null fpu ptr so ISR
659-
// skips FXSAVE/FXRSTOR (AP idle loop doesn't use FPU).
708+
// no user process available — return to ring 0 idle loop.
660709
set_percpu_fpu_ptr(0);
661-
set_percpu_next_cr3(0);
710+
set_percpu_next_cr3(KERNEL_CR3);
711+
// restore AP's own kernel stack in TSS for future ring transitions
712+
let pcpu = crate::cpu::per_cpu::current();
713+
crate::cpu::gdt::set_kernel_stack_for_core(core_idx, pcpu.boot_kernel_rsp);
714+
pcpu.kernel_syscall_rsp = pcpu.boot_kernel_rsp;
662715
PROCESS_TABLE_LOCK.unlock();
663-
// return the ISR's own stack-saved context. AP wakes up exactly
664-
// where it was (hlt loop). PID 0's context is untouched.
665-
return core::mem::transmute::<&CpuContext, &'static CpuContext>(current_ctx);
716+
return ap_idle_context(core_idx);
666717
}
667718

668719
// found a real process — switch AP to it
@@ -684,11 +735,15 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
684735
set_percpu_fpu_ptr(fpu_ptr);
685736
&next.context
686737
} else {
738+
// pick_next lied — PID doesn't exist. reset to idle.
739+
set_this_core_pid(0);
687740
set_percpu_fpu_ptr(0);
688-
set_percpu_next_cr3(0);
689-
// transmute lifetime — pointer is on ISR stack, used immediately
741+
set_percpu_next_cr3(KERNEL_CR3);
742+
let pcpu = crate::cpu::per_cpu::current();
743+
crate::cpu::gdt::set_kernel_stack_for_core(core_idx, pcpu.boot_kernel_rsp);
744+
pcpu.kernel_syscall_rsp = pcpu.boot_kernel_rsp;
690745
PROCESS_TABLE_LOCK.unlock();
691-
return core::mem::transmute::<&CpuContext, &'static CpuContext>(current_ctx);
746+
return ap_idle_context(core_idx);
692747
};
693748

694749
PROCESS_TABLE_LOCK.unlock();
@@ -751,13 +806,19 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
751806
let next_pid = pick_next(cur_pid, skip_kernel, core_idx);
752807

753808
// AP ran a user process but nothing else is runnable — return to idle.
754-
// don't let pick_next's fallback put the AP on PID 0.
809+
// CRITICAL: must NOT return the outgoing process's ring-3 context.
810+
// That would iretq into user code while AP thinks it's PID 0 — instant UB.
811+
// Instead, return a kernel-mode idle context on the AP's own stack.
755812
if next_pid == 0 && core_idx != 0 {
756813
set_this_core_pid(0);
757814
set_percpu_fpu_ptr(0);
758-
set_percpu_next_cr3(0);
815+
set_percpu_next_cr3(KERNEL_CR3);
816+
// restore AP's own kernel stack in TSS
817+
let pcpu = crate::cpu::per_cpu::current();
818+
crate::cpu::gdt::set_kernel_stack_for_core(core_idx, pcpu.boot_kernel_rsp);
819+
pcpu.kernel_syscall_rsp = pcpu.boot_kernel_rsp;
759820
PROCESS_TABLE_LOCK.unlock();
760-
return core::mem::transmute::<&CpuContext, &'static CpuContext>(current_ctx);
821+
return ap_idle_context(core_idx);
761822
}
762823

763824
set_this_core_pid(next_pid as u32);
@@ -788,11 +849,27 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
788849

789850
&next.context
790851
} else {
791-
// fallback: restore current
852+
// pick_next returned a PID that no longer exists. recover gracefully.
853+
if core_idx != 0 {
854+
// AP: go back to idle
855+
set_this_core_pid(0);
856+
set_percpu_fpu_ptr(0);
857+
set_percpu_next_cr3(KERNEL_CR3);
858+
let pcpu = crate::cpu::per_cpu::current();
859+
crate::cpu::gdt::set_kernel_stack_for_core(core_idx, pcpu.boot_kernel_rsp);
860+
pcpu.kernel_syscall_rsp = pcpu.boot_kernel_rsp;
861+
PROCESS_TABLE_LOCK.unlock();
862+
return ap_idle_context(core_idx);
863+
}
864+
// BSP: reset core PID and return kernel context
865+
set_this_core_pid(0);
866+
set_percpu_fpu_ptr(0);
867+
set_percpu_next_cr3(KERNEL_CR3);
792868
if let Some(Some(cur)) = PROCESS_TABLE.get(cur_pid) {
793869
&cur.context
794870
} else {
795-
panic!("scheduler: no runnable process")
871+
// nothing — return ISR stack frame unchanged
872+
core::mem::transmute::<&CpuContext, &'static CpuContext>(current_ctx)
796873
}
797874
};
798875

hwinit/src/stdout.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ static mut BUF: [u8; BUF_SIZE] = [0; BUF_SIZE];
1616

1717
static HEAD: AtomicUsize = AtomicUsize::new(0);
1818
static TAIL: AtomicUsize = AtomicUsize::new(0);
19+
static PUSH_LOCK: crate::sync::IsrSafeRawSpinLock = crate::sync::IsrSafeRawSpinLock::new();
1920

2021
/// Master enable flag — the desktop sets this once the WM is ready
2122
/// to receive process output. Before that, stdout goes to serial only.
@@ -32,17 +33,20 @@ pub fn push(data: &[u8]) {
3233
if !ENABLED.load(Ordering::Acquire) {
3334
return;
3435
}
36+
PUSH_LOCK.lock();
3537
for &b in data {
3638
let head = HEAD.load(Ordering::Relaxed);
3739
let next = (head + 1) & BUF_MASK;
3840
if next == TAIL.load(Ordering::Acquire) {
41+
PUSH_LOCK.unlock();
3942
return; // full — drop remainder
4043
}
4144
unsafe {
4245
BUF[head] = b;
4346
}
4447
HEAD.store(next, Ordering::Release);
4548
}
49+
PUSH_LOCK.unlock();
4650
}
4751

4852
/// Drain all available bytes into `out`, appending up to `limit` bytes.

hwinit/src/syscall/handler/compositor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub unsafe fn sys_win_surface_list(buf_ptr: u64, max_count: u64) -> u64 {
6868
return 0;
6969
}
7070

71-
let fb_info = match FB_REGISTERED {
71+
let fb_info = match fb_registered() {
7272
Some(i) => i,
7373
None => return 0,
7474
};

hwinit/src/syscall/handler/fb.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub unsafe fn sys_fb_info(buf_ptr: u64) -> u64 {
1010
if !validate_user_buf(buf_ptr, size) {
1111
return EFAULT;
1212
}
13-
match FB_REGISTERED {
13+
match fb_registered() {
1414
Some(info) => {
1515
core::ptr::write(buf_ptr as *mut FbInfo, info);
1616
0
@@ -126,7 +126,7 @@ pub fn fb_lock_holder() -> u32 {
126126
/// these surfaces to composite windows.
127127
pub unsafe fn sys_fb_map() -> u64 {
128128
use crate::serial::{puts, put_hex32};
129-
let info = match FB_REGISTERED {
129+
let info = match fb_registered() {
130130
Some(i) => i,
131131
None => {
132132
puts("[FB_MAP] ENODEV: no framebuffer registered\n");
@@ -282,7 +282,7 @@ pub unsafe fn fb_present_tick() {
282282
return;
283283
}
284284

285-
let info = match FB_REGISTERED {
285+
let info = match fb_registered() {
286286
Some(i) => i,
287287
None => return,
288288
};
@@ -320,7 +320,7 @@ pub unsafe fn sys_fb_present() -> u64 {
320320
if back == 0 || shadow == 0 {
321321
return ENODEV;
322322
}
323-
let info = match FB_REGISTERED {
323+
let info = match fb_registered() {
324324
Some(i) => i,
325325
None => return ENODEV,
326326
};
@@ -358,7 +358,7 @@ pub unsafe fn sys_fb_blit() -> u64 {
358358
if back == 0 {
359359
return ENODEV;
360360
}
361-
let info = match FB_REGISTERED {
361+
let info = match fb_registered() {
362362
Some(i) => i,
363363
None => return ENODEV,
364364
};

hwinit/src/syscall/handler/nic_fb.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,25 @@ pub struct FbInfo {
120120
pub format: u32,
121121
}
122122

123-
static mut FB_REGISTERED: Option<FbInfo> = None;
123+
// write-once, read-many from any core. atomic flag + raw pointer avoids static mut UB.
124+
use core::sync::atomic::{AtomicBool, Ordering as FbOrd};
125+
static mut FB_REGISTERED_STORAGE: Option<FbInfo> = None;
126+
static FB_REGISTERED_READY: AtomicBool = AtomicBool::new(false);
127+
128+
/// Read the registered framebuffer info. Returns None before register_framebuffer.
129+
#[inline]
130+
pub unsafe fn fb_registered() -> Option<FbInfo> {
131+
if FB_REGISTERED_READY.load(FbOrd::Acquire) {
132+
FB_REGISTERED_STORAGE
133+
} else {
134+
None
135+
}
136+
}
124137

125138
/// Register framebuffer info. Called by bootloader before entering desktop.
126139
pub unsafe fn register_framebuffer(info: FbInfo) {
127-
FB_REGISTERED = Some(info);
140+
FB_REGISTERED_STORAGE = Some(info);
141+
FB_REGISTERED_READY.store(true, FbOrd::Release);
128142
}
129143

130144
// DOUBLE BUFFER — kernel-owned back buffer + shadow for delta presentation

hwinit/src/syscall/handler/nic_io.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ pub unsafe fn sys_ioctl(fd: u64, cmd: u64, arg: u64) -> u64 {
172172
// Terminal window size: derive from framebuffer if available, else 80×25.
173173
(0..=2, IOCTL_TIOCGWINSZ) => {
174174
if arg != 0 && validate_user_buf(arg, 8) {
175-
let (rows, cols, xpix, ypix) = match FB_REGISTERED {
175+
let (rows, cols, xpix, ypix) = match fb_registered() {
176176
Some(fb) => {
177177
let c = fb.width / 8; // 8px font width
178178
let r = fb.height / 16; // 16px font height

0 commit comments

Comments
 (0)