feat(kernel): lazy user FPU: off until first FP insn (illegal-insn trap -> zeroed regs, retry), save on dirty trap, restore on switch, off for threads without + kernel; spawntest per-thread FP check

This commit is contained in:
2026-10-28 03:20:41 +01:00
parent 34a44b5928
commit c256f4ecf3
7 changed files with 302 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
mod fast;
mod fp;
mod sched;
mod vector;
@@ -17,6 +18,7 @@ const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
const CAUSE_S_SOFTWARE: usize = 1;
const CAUSE_S_TIMER: usize = 5;
const CAUSE_S_EXTERNAL: usize = 9;
const CAUSE_ILLEGAL_INSTRUCTION: usize = 2;
const CAUSE_ECALL_U: usize = 8;
/// Set in `sstatus` when the trap came from supervisor mode.
const SSTATUS_SPP: usize = 1 << 8;
@@ -29,6 +31,8 @@ pub fn init() {
let addr = trap_vector as *const () as usize;
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
core::arch::asm!("csrw sscratch, zero");
// The kernel never uses the FPU; user threads get it lazily.
core::arch::asm!("csrc sstatus, {0}", in(reg) 3usize << 13);
// SIE stays clear; interrupts are enabled only by sret into userspace.
let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1);
core::arch::asm!("csrs sie, {0}", in(reg) sie_bits);
@@ -90,6 +94,14 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) {
}
crate::smp::service_ipi();
crate::smp::lock();
// The thread's FP registers are saved before anything can switch away.
if let Some(ctx) = kern()
.current_thread()
.and_then(|t| kern().thread_context_mut(t))
{
fp::save_if_dirty(frame, ctx);
}
fp::kernel_off();
if scause & CAUSE_INTERRUPT_BIT != 0 {
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
} else {
@@ -163,6 +175,11 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
}
}
}
CAUSE_ILLEGAL_INSTRUCTION
if kern()
.current_thread()
.and_then(|t| kern().thread_context_mut(t))
.is_some_and(|ctx| fp::first_use(frame, ctx)) => {}
_ => {
let uart = crate::hw::uart::Uart::new();
uart.write_str("FAULT: scause=");