Files
RedBear-OS/src/context/arch/x86.rs
T
2024-03-05 10:26:57 +01:00

260 lines
7.4 KiB
Rust

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::<usize>();
*(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::<usize>();
value
}
}
impl super::Context {
pub fn get_fx_regs(&self) -> FloatRegisters {
let mut regs = unsafe { self.kfx.as_ptr().cast::<FloatRegisters>().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::<FloatRegisters>()) };
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::<FloatRegisters>().write(new);
}
}
}
pub static EMPTY_CR3: Once<rmm::PhysicalAddress> = 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),
);
}