Files
RedBear-OS/src/context/arch/x86_64.rs
T

302 lines
9.1 KiB
Rust

use core::{
ptr::{addr_of, addr_of_mut},
sync::atomic::AtomicBool,
};
use crate::syscall::FloatRegisters;
use core::mem::offset_of;
use spin::Once;
use x86::msr;
/// 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;
#[cfg(cpu_feature_never = "xsave")]
pub const KFX_ALIGN: usize = 16;
#[cfg(not(cpu_feature_never = "xsave"))]
pub const KFX_ALIGN: usize = 64;
pub const KSTACK_SIZE: usize = 65536;
// Why not, helps with guarding etc.
// TODO: Unmap the 63rd page, for stack guarding! Then re-map it onto the kernel heap when freeing.
// Use a RAII guard.
pub const KSTACK_ALIGN: usize = 4096;
#[derive(Clone, Debug)]
#[repr(C)]
pub struct Context {
/// RFLAGS register
rflags: usize,
/// RBX register
rbx: usize,
/// R12 register
r12: usize,
/// R13 register
r13: usize,
/// R14 register
r14: usize,
/// R15 register
r15: usize,
/// Base pointer
rbp: usize,
/// Stack pointer
pub(crate) rsp: 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,
userspace_io_allowed: bool,
}
impl Context {
pub fn new() -> Context {
Context {
rflags: 0,
rbx: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
rbp: 0,
rsp: 0,
fsbase: 0,
gsbase: 0,
userspace_io_allowed: false,
}
}
pub fn set_stack(&mut self, address: usize) {
self.rsp = address;
}
}
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 fn set_userspace_io_allowed(&mut self, allowed: bool) {
self.arch.userspace_io_allowed = allowed;
if self.id == super::context_id() {
unsafe {
crate::gdt::set_userspace_io_allowed(crate::gdt::pcr(), allowed);
}
}
}
pub fn current_syscall(&self) -> Option<[usize; 6]> {
if !self.inside_syscall {
return None;
}
let regs = self.regs()?;
let scratch = &regs.scratch;
Some([scratch.rax, scratch.rdi, scratch.rsi, scratch.rdx, scratch.r10, scratch.r8])
}
}
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) {
let pcr = crate::gdt::pcr();
if let Some(ref stack) = next.kstack {
crate::gdt::set_tss_stack(pcr, stack.initial_top() as usize);
}
crate::gdt::set_userspace_io_allowed(pcr, next.arch.userspace_io_allowed);
core::arch::asm!(
alternative2!(
feature1: "xsaveopt",
then1: ["
mov eax, 0xffffffff
mov edx, eax
xsaveopt [{prev_fx}]
xrstor [{next_fx}]
"],
feature2: "xsave",
then2: ["
mov eax, 0xffffffff
mov edx, eax
xsave [{prev_fx}]
xrstor [{next_fx}]
"],
default: ["
fxsave64 [{prev_fx}]
fxrstor64 [{next_fx}]
"]
),
prev_fx = in(reg) prev.kfx.as_mut_ptr(),
next_fx = in(reg) next.kfx.as_ptr(),
out("eax") _,
out("edx") _,
);
{
core::arch::asm!(
alternative!(
feature: "fsgsbase",
then: ["
mov rax, [{next}+{fsbase_off}]
mov rcx, [{next}+{gsbase_off}]
rdfsbase rdx
wrfsbase rax
swapgs
rdgsbase rax
wrgsbase rcx
swapgs
mov [{prev}+{fsbase_off}], rdx
mov [{prev}+{gsbase_off}], rax
"],
// TODO: Most applications will set FSBASE, but won't touch GSBASE. Maybe avoid
// wrmsr or even the swapgs+rdgsbase+wrgsbase+swapgs sequence if they are already
// equal?
default: ["
mov ecx, {MSR_FSBASE}
mov rdx, [{next}+{fsbase_off}]
mov eax, edx
shr rdx, 32
wrmsr
mov ecx, {MSR_KERNEL_GSBASE}
mov rdx, [{next}+{gsbase_off}]
mov eax, edx
shr rdx, 32
wrmsr
// {prev}
"]
),
out("rax") _,
out("rdx") _,
out("ecx") _, prev = in(reg) addr_of_mut!(prev.arch), next = in(reg) addr_of!(next.arch),
MSR_FSBASE = const msr::IA32_FS_BASE,
MSR_KERNEL_GSBASE = const msr::IA32_KERNEL_GSBASE,
gsbase_off = const offset_of!(Context, gsbase),
fsbase_off = const offset_of!(Context, fsbase),
);
}
(*pcr).percpu.new_addrsp_tmp.set(next.addr_space.clone());
switch_to_inner(&mut prev.arch, &mut next.arch)
}
// Check disassembly!
#[naked]
unsafe extern "sysv64" fn switch_to_inner(_prev: &mut Context, _next: &mut Context) {
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 `rdi`, `rsi`,
// - we can modify scratch registers, e.g. rax
// - we cannot change callee-preserved registers arbitrarily, e.g. rbx, which is why we
// store them here in the first place.
concat!("
// Save old registers, and load new ones
mov [rdi + {off_rbx}], rbx
mov rbx, [rsi + {off_rbx}]
mov [rdi + {off_r12}], r12
mov r12, [rsi + {off_r12}]
mov [rdi + {off_r13}], r13
mov r13, [rsi + {off_r13}]
mov [rdi + {off_r14}], r14
mov r14, [rsi + {off_r14}]
mov [rdi + {off_r15}], r15
mov r15, [rsi + {off_r15}]
mov [rdi + {off_rbp}], rbp
mov rbp, [rsi + {off_rbp}]
mov [rdi + {off_rsp}], rsp
mov rsp, [rsi + {off_rsp}]
// push RFLAGS (can only be modified via stack)
pushfq
// pop RFLAGS into `self.rflags`
pop QWORD PTR [rdi + {off_rflags}]
// push `next.rflags`
push QWORD PTR [rsi + {off_rflags}]
// pop into RFLAGS
popfq
// 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_rflags = const(offset_of!(Cx, rflags)),
off_rbx = const(offset_of!(Cx, rbx)),
off_r12 = const(offset_of!(Cx, r12)),
off_r13 = const(offset_of!(Cx, r13)),
off_r14 = const(offset_of!(Cx, r14)),
off_r15 = const(offset_of!(Cx, r15)),
off_rbp = const(offset_of!(Cx, rbp)),
off_rsp = const(offset_of!(Cx, rsp)),
switch_hook = sym crate::context::switch_finish_hook,
options(noreturn),
);
}