use core::{mem, sync::atomic::AtomicBool}; use alloc::sync::Arc; use crate::{ gdt::{pcr, GDT_USER_FS, GDT_USER_GS}, interrupt::handler::ScratchRegisters, paging::{RmmA, RmmArch, TableKind}, percpu::PercpuBlock, pop_scratch, push_scratch, syscall::FloatRegisters, }; use core::mem::offset_of; use spin::Once; /// This must be used by the kernel to ensure that context switches are done atomically /// Compare and exchange this to true when beginning a context switch on any CPU /// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch /// This must be done, as no locks can be held on the stack during switch pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false); const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000; pub const KFX_ALIGN: usize = 16; #[derive(Clone, Debug)] #[repr(C)] pub struct Context { /// EFLAGS register eflags: usize, /// EBX register ebx: usize, /// EDI register edi: usize, /// ESI register esi: usize, /// Base pointer ebp: usize, /// Stack pointer pub(crate) esp: usize, /// FSBASE. /// /// NOTE: Same fsgsbase behavior as with gsbase. pub(crate) fsbase: usize, /// GSBASE. /// /// NOTE: Without fsgsbase, this register will strictly be equal to the register value when /// running. With fsgsbase, this is neither saved nor restored upon every syscall (there is no /// need to!), and thus it must be re-read from the register before copying this struct. pub(crate) gsbase: usize, } impl Context { pub fn new() -> Context { Context { eflags: 0, ebx: 0, edi: 0, esi: 0, ebp: 0, esp: 0, fsbase: 0, gsbase: 0, } } pub fn set_stack(&mut self, address: usize) { self.esp = address; } pub unsafe fn signal_stack(&mut self, handler: extern "C" fn(usize), sig: u8) { self.push_stack(sig as usize); self.push_stack(handler as usize); self.push_stack(signal_handler_wrapper as usize); } pub unsafe fn push_stack(&mut self, value: usize) { self.esp -= mem::size_of::(); *(self.esp as *mut usize) = value; } pub unsafe fn pop_stack(&mut self) -> usize { let value = *(self.esp as *const usize); self.esp += mem::size_of::(); value } } impl super::Context { pub fn get_fx_regs(&self) -> FloatRegisters { let mut regs = unsafe { self.kfx.as_ptr().cast::().read() }; regs._reserved = 0; let mut new_st = regs.st_space; for st in &mut new_st { // Only allow access to the 80 lowest bits *st &= !ST_RESERVED; } regs.st_space = new_st; regs } pub fn set_fx_regs(&mut self, mut new: FloatRegisters) { { let old = unsafe { &*(self.kfx.as_ptr().cast::()) }; new._reserved = old._reserved; let old_st = new.st_space; let mut new_st = new.st_space; for (new_st, old_st) in new_st.iter_mut().zip(&old_st) { *new_st &= !ST_RESERVED; *new_st |= old_st & ST_RESERVED; } new.st_space = new_st; // Make sure we don't use `old` from now on } unsafe { self.kfx.as_mut_ptr().cast::().write(new); } } } pub static EMPTY_CR3: Once = Once::new(); // SAFETY: EMPTY_CR3 must be initialized. pub unsafe fn empty_cr3() -> rmm::PhysicalAddress { debug_assert!(EMPTY_CR3.poll().is_some()); *EMPTY_CR3.get_unchecked() } /// Switch to the next context by restoring its stack and registers pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) { if let Some(ref stack) = next.kstack { crate::gdt::set_tss_stack(stack.as_ptr() as usize + stack.len()); } core::arch::asm!(" fxsave [{prev_fx}] fxrstor [{next_fx}] ", prev_fx = in(reg) prev.kfx.as_mut_ptr(), next_fx = in(reg) next.kfx.as_ptr(), ); { let gdt = &mut (&mut *pcr()).gdt; prev.arch.fsbase = gdt[GDT_USER_FS].offset() as usize; gdt[GDT_USER_FS].set_offset(next.arch.fsbase as u32); prev.arch.gsbase = gdt[GDT_USER_GS].offset() as usize; gdt[GDT_USER_GS].set_offset(next.arch.gsbase as u32); } PercpuBlock::current().new_addrsp_tmp.set(next.addr_space.clone()); core::arch::asm!( "call {inner}", inner = sym switch_to_inner, in("ecx") &mut prev.arch, in("edx") &mut next.arch, ); } // Check disassembly! #[naked] unsafe extern "cdecl" fn switch_to_inner() { use Context as Cx; core::arch::asm!( // As a quick reminder for those who are unfamiliar with the System V ABI (extern "C"): // // - the current parameters are passed in the registers `edi`, `esi`, // - we can modify scratch registers, e.g. rax // - we cannot change callee-preserved registers arbitrarily, e.g. ebx, which is why we // store them here in the first place. concat!(" // ecx is prev, edx is next // Save old registers, and load new ones mov [ecx + {off_ebx}], ebx mov ebx, [edx + {off_ebx}] mov [ecx + {off_edi}], edi mov edi, [edx + {off_edi}] mov [ecx + {off_esi}], esi mov esi, [edx + {off_esi}] mov [ecx + {off_ebp}], ebp mov ebp, [edx + {off_ebp}] mov [ecx + {off_esp}], esp mov esp, [edx + {off_esp}] // push EFLAGS (can only be modified via stack) pushfd // pop EFLAGS into `self.eflags` pop DWORD PTR [ecx + {off_eflags}] // push `next.eflags` push DWORD PTR [edx + {off_eflags}] // pop into EFLAGS popfd // When we return, we cannot even guarantee that the return address on the stack, points to // the calling function, `context::switch`. Thus, we have to execute this Rust hook by // ourselves, which will unlock the contexts before the later switch. // Note that switch_finish_hook will be responsible for executing `ret`. jmp {switch_hook} "), off_eflags = const(offset_of!(Cx, eflags)), off_ebx = const(offset_of!(Cx, ebx)), off_edi = const(offset_of!(Cx, edi)), off_esi = const(offset_of!(Cx, esi)), off_ebp = const(offset_of!(Cx, ebp)), off_esp = const(offset_of!(Cx, esp)), switch_hook = sym crate::context::switch_finish_hook, options(noreturn), ); } #[allow(dead_code)] #[repr(packed)] pub struct SignalHandlerStack { scratch: ScratchRegisters, handler: extern "C" fn(usize), sig: usize, eip: usize, } #[naked] unsafe extern "C" fn signal_handler_wrapper() { #[inline(never)] unsafe extern "C" fn inner(stack: &SignalHandlerStack) { (stack.handler)(stack.sig); } // Push scratch registers core::arch::asm!( concat!( "push eax", push_scratch!(), " push esp call {inner} pop esp ", pop_scratch!(), " add esp, 8 ret ", ), inner = sym inner, options(noreturn), ); }