Refactor FPU context using pre_schedule_handler
This commit is contained in:
parent
ab5e1999fc
commit
6cd53fbb8a
|
@ -237,15 +237,18 @@ fn clone_child_task(
|
|||
..
|
||||
} = ctx;
|
||||
|
||||
// clone system V semaphore
|
||||
// Clone system V semaphore
|
||||
clone_sysvsem(clone_flags)?;
|
||||
|
||||
// clone file table
|
||||
// Clone file table
|
||||
let child_file_table = clone_files(thread_local.borrow_file_table().unwrap(), clone_flags);
|
||||
|
||||
// clone fs
|
||||
// Clone fs
|
||||
let child_fs = clone_fs(posix_thread.fs(), clone_flags);
|
||||
|
||||
// Clone FPU context
|
||||
let child_fpu_context = thread_local.fpu().clone_context();
|
||||
|
||||
let child_user_ctx = Arc::new(clone_user_ctx(
|
||||
parent_context,
|
||||
clone_args.stack,
|
||||
|
@ -272,7 +275,8 @@ fn clone_child_task(
|
|||
.thread_name(thread_name)
|
||||
.sig_mask(sig_mask)
|
||||
.file_table(child_file_table)
|
||||
.fs(child_fs);
|
||||
.fs(child_fs)
|
||||
.fpu_context(child_fpu_context);
|
||||
|
||||
// Deal with SETTID/CLEARTID flags
|
||||
clone_parent_settid(child_tid, clone_args.parent_tid, clone_flags)?;
|
||||
|
@ -332,6 +336,9 @@ fn clone_child_process(
|
|||
// Clone System V semaphore
|
||||
clone_sysvsem(clone_flags)?;
|
||||
|
||||
// Clone FPU context
|
||||
let child_fpu_context = thread_local.fpu().clone_context();
|
||||
|
||||
// Inherit the parent's signal mask
|
||||
let child_sig_mask = posix_thread.sig_mask().load(Ordering::Relaxed).into();
|
||||
|
||||
|
@ -358,6 +365,7 @@ fn clone_child_process(
|
|||
.sig_mask(child_sig_mask)
|
||||
.file_table(child_file_table)
|
||||
.fs(child_fs)
|
||||
.fpu_context(child_fpu_context)
|
||||
};
|
||||
|
||||
// Deal with SETTID/CLEARTID flags
|
||||
|
@ -474,10 +482,6 @@ fn clone_user_ctx(
|
|||
child_context.set_tls_pointer(tls as usize);
|
||||
}
|
||||
|
||||
// New threads inherit the FPU state of the parent thread and
|
||||
// the state is private to the thread thereafter.
|
||||
child_context.fpu_state().save();
|
||||
|
||||
child_context
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use ostd::{
|
||||
cpu::{context::UserContext, CpuSet},
|
||||
cpu::{
|
||||
context::{FpuContext, UserContext},
|
||||
CpuSet,
|
||||
},
|
||||
sync::RwArc,
|
||||
task::Task,
|
||||
};
|
||||
|
@ -37,6 +40,7 @@ pub struct PosixThreadBuilder {
|
|||
sig_mask: AtomicSigMask,
|
||||
sig_queues: SigQueues,
|
||||
sched_policy: SchedPolicy,
|
||||
fpu_context: FpuContext,
|
||||
}
|
||||
|
||||
impl PosixThreadBuilder {
|
||||
|
@ -54,6 +58,7 @@ impl PosixThreadBuilder {
|
|||
sig_mask: AtomicSigMask::new_empty(),
|
||||
sig_queues: SigQueues::new(),
|
||||
sched_policy: SchedPolicy::Fair(Nice::default()),
|
||||
fpu_context: FpuContext::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +97,11 @@ impl PosixThreadBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn fpu_context(mut self, fpu_context: FpuContext) -> Self {
|
||||
self.fpu_context = fpu_context;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Arc<Task> {
|
||||
let Self {
|
||||
tid,
|
||||
|
@ -106,6 +116,7 @@ impl PosixThreadBuilder {
|
|||
sig_mask,
|
||||
sig_queues,
|
||||
sched_policy,
|
||||
fpu_context,
|
||||
} = self;
|
||||
|
||||
let file_table = file_table.unwrap_or_else(|| RwArc::new(FileTable::new_with_stdio()));
|
||||
|
@ -150,8 +161,13 @@ impl PosixThreadBuilder {
|
|||
sched_policy,
|
||||
));
|
||||
|
||||
let thread_local =
|
||||
ThreadLocal::new(set_child_tid, clear_child_tid, root_vmar, file_table);
|
||||
let thread_local = ThreadLocal::new(
|
||||
set_child_tid,
|
||||
clear_child_tid,
|
||||
root_vmar,
|
||||
file_table,
|
||||
fpu_context,
|
||||
);
|
||||
|
||||
thread_table::add_thread(tid, thread.clone());
|
||||
task::create_new_user_task(user_ctx, thread, thread_local)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use core::cell::{Cell, Ref, RefCell, RefMut};
|
||||
|
||||
use aster_rights::Full;
|
||||
use ostd::{mm::Vaddr, sync::RwArc, task::CurrentTask};
|
||||
use ostd::{cpu::context::FpuContext, mm::Vaddr, sync::RwArc, task::CurrentTask};
|
||||
|
||||
use super::RobustListHead;
|
||||
use crate::{fs::file_table::FileTable, process::signal::SigStack, vm::vmar::Vmar};
|
||||
|
@ -25,6 +25,10 @@ pub struct ThreadLocal {
|
|||
// Files.
|
||||
file_table: RefCell<Option<RwArc<FileTable>>>,
|
||||
|
||||
// User FPU context.
|
||||
fpu_context: RefCell<FpuContext>,
|
||||
fpu_state: Cell<FpuState>,
|
||||
|
||||
// Signal.
|
||||
/// `ucontext` address for the signal handler.
|
||||
// FIXME: This field may be removed. For glibc applications with RESTORER flag set, the
|
||||
|
@ -40,6 +44,7 @@ impl ThreadLocal {
|
|||
clear_child_tid: Vaddr,
|
||||
root_vmar: Vmar<Full>,
|
||||
file_table: RwArc<FileTable>,
|
||||
fpu_context: FpuContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
set_child_tid: Cell::new(set_child_tid),
|
||||
|
@ -49,6 +54,8 @@ impl ThreadLocal {
|
|||
file_table: RefCell::new(Some(file_table)),
|
||||
sig_context: Cell::new(None),
|
||||
sig_stack: RefCell::new(None),
|
||||
fpu_context: RefCell::new(fpu_context),
|
||||
fpu_state: Cell::new(FpuState::Unloaded),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +90,96 @@ impl ThreadLocal {
|
|||
pub fn sig_stack(&self) -> &RefCell<Option<SigStack>> {
|
||||
&self.sig_stack
|
||||
}
|
||||
|
||||
pub fn fpu(&self) -> ThreadFpu<'_> {
|
||||
ThreadFpu(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of `ThreadFpu`.
|
||||
///
|
||||
/// - `Activated`: The FPU context is currently loaded onto the CPU and it must be loaded
|
||||
/// while the associated task is running. If preemption occurs in between, the context switch
|
||||
/// must load FPU context again.
|
||||
/// - `Loaded`: The FPU context is currently loaded onto the CPU. It may or may not still
|
||||
/// be loaded in CPU after a context switch.
|
||||
/// - `Unloaded`: The FPU context is not currently loaded onto the CPU.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum FpuState {
|
||||
Activated,
|
||||
Loaded,
|
||||
Unloaded,
|
||||
}
|
||||
|
||||
/// The FPU information for the _current_ thread.
|
||||
///
|
||||
/// # Notes about kernel preemption
|
||||
///
|
||||
/// All the methods of `ThreadFpu` assume that preemption will not occur.
|
||||
/// This means that the FPU state will not change unexpectedly
|
||||
/// (e.g., changing from `Loaded` to `Unloaded`).
|
||||
///
|
||||
/// In the current architecture, this is always true because kernel
|
||||
/// preemption was never implemented. More importantly, we cannot implement
|
||||
/// kernel preemption without refactoring the `ThreadLocal` mechanism
|
||||
/// because `ThreadLocal` cannot be accessed in interrupt handlers for
|
||||
/// soundness reasons. But such access is necessary for the preempted
|
||||
/// schedule.
|
||||
///
|
||||
/// Therefore, we omit the preemption guards for better performance and
|
||||
/// defer preemption considerations to future work.
|
||||
pub struct ThreadFpu<'a>(&'a ThreadLocal);
|
||||
|
||||
impl ThreadFpu<'_> {
|
||||
pub fn activate(&self) {
|
||||
match self.0.fpu_state.get() {
|
||||
FpuState::Activated => return,
|
||||
FpuState::Loaded => (),
|
||||
FpuState::Unloaded => self.0.fpu_context.borrow_mut().load(),
|
||||
}
|
||||
self.0.fpu_state.set(FpuState::Activated);
|
||||
}
|
||||
|
||||
pub fn deactivate(&self) {
|
||||
if self.0.fpu_state.get() == FpuState::Activated {
|
||||
self.0.fpu_state.set(FpuState::Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_context(&self) -> FpuContext {
|
||||
match self.0.fpu_state.get() {
|
||||
FpuState::Activated | FpuState::Loaded => {
|
||||
let mut fpu_context = self.0.fpu_context.borrow_mut();
|
||||
fpu_context.save();
|
||||
fpu_context.clone()
|
||||
}
|
||||
FpuState::Unloaded => self.0.fpu_context.borrow().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_context(&self, context: FpuContext) {
|
||||
let _ = self.0.fpu_context.replace(context);
|
||||
self.0.fpu_state.set(FpuState::Unloaded);
|
||||
}
|
||||
|
||||
pub fn before_schedule(&self) {
|
||||
match self.0.fpu_state.get() {
|
||||
FpuState::Activated => {
|
||||
self.0.fpu_context.borrow_mut().save();
|
||||
}
|
||||
FpuState::Loaded => {
|
||||
self.0.fpu_context.borrow_mut().save();
|
||||
self.0.fpu_state.set(FpuState::Unloaded);
|
||||
}
|
||||
FpuState::Unloaded => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn after_schedule(&self) {
|
||||
if self.0.fpu_state.get() == FpuState::Activated {
|
||||
self.0.fpu_context.borrow_mut().load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An immutable, shared reference to the file table in [`ThreadLocal`].
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use aster_rights::WriteOp;
|
||||
use ostd::{
|
||||
cpu::context::{FpuState, GeneralRegs, UserContext},
|
||||
cpu::context::{FpuContext, GeneralRegs, UserContext},
|
||||
user::UserContextApi,
|
||||
};
|
||||
|
||||
|
@ -144,6 +144,9 @@ fn do_execve(
|
|||
*thread_local.robust_list().borrow_mut() = None;
|
||||
debug!("load elf in execve succeeds");
|
||||
|
||||
// Reset FPU context
|
||||
thread_local.fpu().set_context(FpuContext::new());
|
||||
|
||||
let credentials = posix_thread.credentials_mut();
|
||||
set_uid_from_elf(process, &credentials, &elf_file)?;
|
||||
set_gid_from_elf(process, &credentials, &elf_file)?;
|
||||
|
@ -156,12 +159,6 @@ fn do_execve(
|
|||
// set cpu context to default
|
||||
*user_context.general_regs_mut() = GeneralRegs::default();
|
||||
user_context.set_tls_pointer(0);
|
||||
*user_context.fpu_state_mut() = FpuState::default();
|
||||
// FIXME: how to reset the FPU state correctly? Before returning to the user space,
|
||||
// the kernel will call `handle_pending_signal`, which may update the CPU states so that
|
||||
// when the kernel switches to the user mode, the control of the CPU will be handed over
|
||||
// to the user-registered signal handlers.
|
||||
user_context.fpu_state().restore();
|
||||
// set new entry point
|
||||
user_context.set_instruction_pointer(elf_load_info.entry_point as _);
|
||||
debug!("entry_point: 0x{:x}", elf_load_info.entry_point);
|
||||
|
|
|
@ -22,6 +22,17 @@ pub mod work_queue;
|
|||
|
||||
pub type Tid = u32;
|
||||
|
||||
fn pre_schedule_handler() {
|
||||
let Some(task) = Task::current() else {
|
||||
return;
|
||||
};
|
||||
let Some(thread_local) = task.as_thread_local() else {
|
||||
return;
|
||||
};
|
||||
|
||||
thread_local.fpu().before_schedule();
|
||||
}
|
||||
|
||||
fn post_schedule_handler() {
|
||||
let task = Task::current().unwrap();
|
||||
let Some(thread_local) = task.as_thread_local() else {
|
||||
|
@ -32,9 +43,12 @@ fn post_schedule_handler() {
|
|||
if let Some(vmar) = root_vmar.as_ref() {
|
||||
vmar.vm_space().activate()
|
||||
}
|
||||
|
||||
thread_local.fpu().after_schedule();
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
ostd::task::inject_pre_schedule_handler(pre_schedule_handler);
|
||||
ostd::task::inject_post_schedule_handler(post_schedule_handler);
|
||||
ostd::arch::trap::inject_user_page_fault_handler(exception::page_fault_handler);
|
||||
}
|
||||
|
|
|
@ -75,7 +75,9 @@ pub fn create_new_user_task(
|
|||
|
||||
while !current_thread.is_exited() {
|
||||
// Execute the user code
|
||||
ctx.thread_local.fpu().activate();
|
||||
let return_reason = user_mode.execute(has_kernel_event_fn);
|
||||
ctx.thread_local.fpu().deactivate();
|
||||
|
||||
// Handle user events
|
||||
let user_ctx = user_mode.context_mut();
|
||||
|
|
|
@ -14,13 +14,12 @@ use crate::{
|
|||
user::{ReturnReason, UserContextApi, UserContextApiInternal},
|
||||
};
|
||||
|
||||
/// Userspace CPU context, including both general-purpose registers and FPU state.
|
||||
/// Userspace CPU context, including general-purpose registers and exception information.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UserContext {
|
||||
user_context: RawUserContext,
|
||||
trap: Trap,
|
||||
fpu_state: FpuState,
|
||||
cpu_exception_info: Option<CpuExceptionInfo>,
|
||||
}
|
||||
|
||||
|
@ -81,7 +80,6 @@ impl Default for UserContext {
|
|||
UserContext {
|
||||
user_context: RawUserContext::default(),
|
||||
trap: Trap::Exception(Exception::Unknown),
|
||||
fpu_state: FpuState,
|
||||
cpu_exception_info: None,
|
||||
}
|
||||
}
|
||||
|
@ -120,16 +118,6 @@ impl UserContext {
|
|||
self.cpu_exception_info.take()
|
||||
}
|
||||
|
||||
/// Returns a reference to the FPU state.
|
||||
pub fn fpu_state(&self) -> &FpuState {
|
||||
&self.fpu_state
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the FPU state.
|
||||
pub fn fpu_state_mut(&mut self) -> &mut FpuState {
|
||||
&mut self.fpu_state
|
||||
}
|
||||
|
||||
/// Sets thread-local storage pointer.
|
||||
pub fn set_tls_pointer(&mut self, tls: usize) {
|
||||
self.set_tp(tls)
|
||||
|
@ -275,27 +263,32 @@ cpu_context_impl_getter_setter!(
|
|||
/// CPU exception.
|
||||
pub type CpuException = Exception;
|
||||
|
||||
/// The FPU state of user task.
|
||||
/// The FPU context of user task.
|
||||
///
|
||||
/// This could be used for saving both legacy and modern state format.
|
||||
// FIXME: Implement FPU state on RISC-V platforms.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FpuState;
|
||||
// FIXME: Implement FPU context on RISC-V platforms.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct FpuContext;
|
||||
|
||||
impl FpuState {
|
||||
/// Saves CPU's current FPU state into this instance.
|
||||
pub fn save(&self) {
|
||||
todo!()
|
||||
impl FpuContext {
|
||||
/// Creates a new FPU context.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Restores CPU's FPU state from this instance.
|
||||
pub fn restore(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FpuState {
|
||||
fn default() -> Self {
|
||||
FpuState
|
||||
/// Saves CPU's current FPU context to this instance, if needed.
|
||||
pub fn save(&mut self) {}
|
||||
|
||||
/// Loads CPU's FPU context from this instance, if needed.
|
||||
pub fn load(&mut self) {}
|
||||
|
||||
/// Returns the FPU context as a byte slice.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Returns the FPU context as a mutable byte slice.
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||
&mut []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,12 @@
|
|||
//! CPU execution context control.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use core::{
|
||||
arch::x86_64::{_fxrstor64, _fxsave64, _xrstor64, _xsave64},
|
||||
fmt::Debug,
|
||||
sync::atomic::{AtomicBool, Ordering::Relaxed},
|
||||
};
|
||||
use core::arch::x86_64::{_fxrstor64, _fxsave64, _xrstor64, _xsave64};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use cfg_if::cfg_if;
|
||||
use log::debug;
|
||||
use ostd_pod::Pod;
|
||||
use spin::Once;
|
||||
use x86::bits64::segmentation::wrfsbase;
|
||||
use x86_64::registers::{
|
||||
|
@ -41,12 +38,11 @@ cfg_if! {
|
|||
|
||||
pub use x86::cpuid;
|
||||
|
||||
/// Userspace CPU context, including both general-purpose registers and FPU state.
|
||||
/// Userspace CPU context, including general-purpose registers and exception information.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UserContext {
|
||||
user_context: RawUserContext,
|
||||
fpu_state: FpuState,
|
||||
exception: Option<CpuException>,
|
||||
}
|
||||
|
||||
|
@ -242,16 +238,6 @@ impl UserContext {
|
|||
self.exception.take()
|
||||
}
|
||||
|
||||
/// Returns a reference to the FPU state.
|
||||
pub fn fpu_state(&self) -> &FpuState {
|
||||
&self.fpu_state
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the FPU state.
|
||||
pub fn fpu_state_mut(&mut self) -> &mut FpuState {
|
||||
&mut self.fpu_state
|
||||
}
|
||||
|
||||
/// Sets thread-local storage pointer.
|
||||
pub fn set_tls_pointer(&mut self, tls: usize) {
|
||||
self.set_fsbase(tls)
|
||||
|
@ -506,19 +492,129 @@ cpu_context_impl_getter_setter!(
|
|||
[gsbase, set_gsbase]
|
||||
);
|
||||
|
||||
/// The FPU state of user task.
|
||||
/// The FPU context of user task.
|
||||
///
|
||||
/// This could be used for saving both legacy and modern state format.
|
||||
#[derive(Debug)]
|
||||
pub struct FpuState {
|
||||
state_area: Box<XSaveArea>,
|
||||
pub struct FpuContext {
|
||||
xsave_area: Box<XSaveArea>,
|
||||
area_size: usize,
|
||||
is_valid: AtomicBool,
|
||||
}
|
||||
|
||||
// The legacy SSE/MMX FPU state format (as saved by `FXSAVE` and restored by the `FXRSTOR` instructions).
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
impl FpuContext {
|
||||
/// Creates a new FPU context.
|
||||
pub fn new() -> Self {
|
||||
let mut area_size = size_of::<FxSaveArea>();
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
area_size = area_size.max(*XSAVE_AREA_SIZE.get().unwrap());
|
||||
}
|
||||
|
||||
Self {
|
||||
xsave_area: Box::new(XSaveArea::new()),
|
||||
area_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves CPU's current FPU context to this instance.
|
||||
pub fn save(&mut self) {
|
||||
let mem_addr = self.as_bytes_mut().as_mut_ptr();
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
unsafe { _xsave64(mem_addr, XFEATURE_MASK_USER_RESTORE) };
|
||||
} else {
|
||||
unsafe { _fxsave64(mem_addr) };
|
||||
}
|
||||
|
||||
debug!("Save FPU context");
|
||||
}
|
||||
|
||||
/// Loads CPU's FPU context from this instance.
|
||||
pub fn load(&mut self) {
|
||||
let mem_addr = self.as_bytes().as_ptr();
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
let rs_mask = XFEATURE_MASK_USER_RESTORE & XSTATE_MAX_FEATURES.get().unwrap();
|
||||
|
||||
unsafe { _xrstor64(mem_addr, rs_mask) };
|
||||
} else {
|
||||
unsafe { _fxrstor64(mem_addr) };
|
||||
}
|
||||
|
||||
debug!("Load FPU context");
|
||||
}
|
||||
|
||||
/// Returns the FPU context as a byte slice.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.xsave_area.as_bytes()[..self.area_size]
|
||||
}
|
||||
|
||||
/// Returns the FPU context as a mutable byte slice.
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.xsave_area.as_bytes_mut()[..self.area_size]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FpuContext {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FpuContext {
|
||||
fn clone(&self) -> Self {
|
||||
let mut xsave_area = Box::new(XSaveArea::new());
|
||||
xsave_area.fxsave_area = self.xsave_area.fxsave_area;
|
||||
xsave_area.features = self.xsave_area.features;
|
||||
xsave_area.compaction = self.xsave_area.compaction;
|
||||
if self.area_size > size_of::<FxSaveArea>() {
|
||||
let len = self.area_size - size_of::<FxSaveArea>() - 64;
|
||||
xsave_area.extended_state_area[..len]
|
||||
.copy_from_slice(&self.xsave_area.extended_state_area[..len]);
|
||||
}
|
||||
|
||||
Self {
|
||||
xsave_area,
|
||||
area_size: self.area_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The modern FPU context format (as saved and restored by the `XSAVE` and `XRSTOR` instructions).
|
||||
#[repr(C)]
|
||||
#[repr(align(64))]
|
||||
#[derive(Clone, Copy, Debug, Pod)]
|
||||
struct XSaveArea {
|
||||
fxsave_area: FxSaveArea,
|
||||
features: u64,
|
||||
compaction: u64,
|
||||
reserved: [u64; 6],
|
||||
extended_state_area: [u8; MAX_XSAVE_AREA_SIZE - size_of::<FxSaveArea>() - 64],
|
||||
}
|
||||
|
||||
impl XSaveArea {
|
||||
fn new() -> Self {
|
||||
let features = if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
XCr0::read().bits() & XSTATE_MAX_FEATURES.get().unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let mut xsave_area = Self::new_zeroed();
|
||||
// Set the initial values for the FPU context. Refer to Intel SDM, Table 11-1:
|
||||
// "IA-32 and Intel® 64 Processor States Following Power-up, Reset, or INIT (Contd.)".
|
||||
xsave_area.fxsave_area.control = 0x037F;
|
||||
xsave_area.fxsave_area.tag = 0xFFFF;
|
||||
xsave_area.fxsave_area.mxcsr = 0x1F80;
|
||||
xsave_area.features = features;
|
||||
|
||||
xsave_area
|
||||
}
|
||||
}
|
||||
|
||||
/// The legacy SSE/MMX FPU context format (as saved and restored by the `FXSAVE` and `FXRSTOR` instructions).
|
||||
#[repr(C)]
|
||||
#[repr(align(16))]
|
||||
#[derive(Clone, Copy, Debug, Pod)]
|
||||
struct FxSaveArea {
|
||||
control: u16, // x87 FPU Control Word
|
||||
status: u16, // x87 FPU Status Word
|
||||
|
@ -536,129 +632,6 @@ struct FxSaveArea {
|
|||
reserved: [u32; 12], // Software reserved
|
||||
}
|
||||
|
||||
/// The modern FPU state format (as saved by the `XSAVE`` and restored by the `XRSTOR` instructions).
|
||||
#[repr(C, align(64))]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct XSaveArea {
|
||||
fxsave_area: FxSaveArea,
|
||||
features: u64,
|
||||
compaction: u64,
|
||||
reserved: [u64; 6],
|
||||
extended_state_area: [u8; MAX_XSAVE_AREA_SIZE - size_of::<FxSaveArea>() - 64],
|
||||
}
|
||||
|
||||
impl XSaveArea {
|
||||
fn init() -> Box<Self> {
|
||||
let features = if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
XCr0::read().bits() & XSTATE_MAX_FEATURES.get().unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let mut xsave_area = Box::<Self>::new_uninit();
|
||||
let ptr = xsave_area.as_mut_ptr();
|
||||
// SAFETY: it's safe to initialize the XSaveArea field then return the instance.
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, 1);
|
||||
(*ptr).fxsave_area.control = 0x37F;
|
||||
(*ptr).fxsave_area.mxcsr = 0x1F80;
|
||||
(*ptr).features = features;
|
||||
xsave_area.assume_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FpuState {
|
||||
/// Initializes a new instance.
|
||||
pub fn init() -> Self {
|
||||
let mut area_size = size_of::<FxSaveArea>();
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
area_size = area_size.max(*XSAVE_AREA_SIZE.get().unwrap());
|
||||
}
|
||||
|
||||
Self {
|
||||
state_area: XSaveArea::init(),
|
||||
area_size,
|
||||
is_valid: AtomicBool::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the instance can contains valid state.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.is_valid.load(Relaxed)
|
||||
}
|
||||
|
||||
/// Save CPU's current FPU state into this instance.
|
||||
pub fn save(&self) {
|
||||
let mem_addr = &*self.state_area as *const _ as *mut u8;
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
unsafe { _xsave64(mem_addr, XFEATURE_MASK_USER_RESTORE) };
|
||||
} else {
|
||||
unsafe { _fxsave64(mem_addr) };
|
||||
}
|
||||
|
||||
self.is_valid.store(true, Relaxed);
|
||||
|
||||
debug!("Save FPU state");
|
||||
}
|
||||
|
||||
/// Restores CPU's FPU state from this instance.
|
||||
pub fn restore(&self) {
|
||||
if !self.is_valid() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mem_addr = &*self.state_area as *const _ as *const u8;
|
||||
|
||||
if CPU_FEATURES.get().unwrap().has_xsave() {
|
||||
let rs_mask = XFEATURE_MASK_USER_RESTORE & XSTATE_MAX_FEATURES.get().unwrap();
|
||||
|
||||
unsafe { _xrstor64(mem_addr, rs_mask) };
|
||||
} else {
|
||||
unsafe { _fxrstor64(mem_addr) };
|
||||
}
|
||||
|
||||
self.is_valid.store(false, Relaxed);
|
||||
|
||||
debug!("Restore FPU state");
|
||||
}
|
||||
|
||||
/// Clears the state of the instance.
|
||||
///
|
||||
/// This method does not reset the underlying buffer that contains the
|
||||
/// FPU state; it only marks the buffer __invalid__.
|
||||
pub fn clear(&self) {
|
||||
self.is_valid.store(false, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FpuState {
|
||||
fn clone(&self) -> Self {
|
||||
let mut state_area = XSaveArea::init();
|
||||
state_area.fxsave_area = self.state_area.fxsave_area;
|
||||
state_area.features = self.state_area.features;
|
||||
state_area.compaction = self.state_area.compaction;
|
||||
if self.area_size > size_of::<FxSaveArea>() {
|
||||
let len = self.area_size - size_of::<FxSaveArea>() - 64;
|
||||
state_area.extended_state_area[..len]
|
||||
.copy_from_slice(&self.state_area.extended_state_area[..len]);
|
||||
}
|
||||
|
||||
Self {
|
||||
state_area,
|
||||
area_size: self.area_size,
|
||||
is_valid: AtomicBool::new(self.is_valid()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FpuState {
|
||||
fn default() -> Self {
|
||||
Self::init()
|
||||
}
|
||||
}
|
||||
|
||||
/// The XSTATE features (user & supervisor) supported by the processor.
|
||||
static XSTATE_MAX_FEATURES: Once<u64> = Once::new();
|
||||
|
||||
|
@ -690,7 +663,10 @@ pub(in crate::arch) fn enable_essential_features() {
|
|||
|
||||
XSAVE_AREA_SIZE.call_once(|| {
|
||||
let cpuid = cpuid::CpuId::new();
|
||||
let size = cpuid.get_extended_state_info().unwrap().xsave_size() as usize;
|
||||
let size = cpuid
|
||||
.get_extended_state_info()
|
||||
.unwrap()
|
||||
.xsave_area_size_supported_features() as usize;
|
||||
debug_assert!(size <= MAX_XSAVE_AREA_SIZE);
|
||||
size
|
||||
});
|
||||
|
|
|
@ -30,8 +30,15 @@ pub use self::{
|
|||
pub(crate) use crate::arch::task::{context_switch, TaskContext};
|
||||
use crate::{cpu::context::UserContext, prelude::*, trap::in_interrupt_context};
|
||||
|
||||
static PRE_SCHEDULE_HANDLER: Once<fn()> = Once::new();
|
||||
|
||||
static POST_SCHEDULE_HANDLER: Once<fn()> = Once::new();
|
||||
|
||||
/// Injects a handler to be executed before scheduling.
|
||||
pub fn inject_pre_schedule_handler(handler: fn()) {
|
||||
PRE_SCHEDULE_HANDLER.call_once(|| handler);
|
||||
}
|
||||
|
||||
/// Injects a handler to be executed after scheduling.
|
||||
pub fn inject_post_schedule_handler(handler: fn()) {
|
||||
POST_SCHEDULE_HANDLER.call_once(|| handler);
|
||||
|
@ -130,22 +137,6 @@ impl Task {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the FPU state for user task.
|
||||
pub fn save_fpu_state(&self) {
|
||||
let Some(user_ctx) = self.user_ctx.as_ref() else {
|
||||
return;
|
||||
};
|
||||
user_ctx.fpu_state().save();
|
||||
}
|
||||
|
||||
/// Restores the FPU state for user task.
|
||||
pub fn restore_fpu_state(&self) {
|
||||
let Some(user_ctx) = self.user_ctx.as_ref() else {
|
||||
return;
|
||||
};
|
||||
user_ctx.fpu_state().restore();
|
||||
}
|
||||
}
|
||||
|
||||
/// Options to create or spawn a new task.
|
||||
|
@ -215,8 +206,6 @@ impl TaskOptions {
|
|||
let current_task = Task::current()
|
||||
.expect("no current task, it should have current task in kernel task entry");
|
||||
|
||||
current_task.restore_fpu_state();
|
||||
|
||||
// SAFETY: The `func` field will only be accessed by the current task in the task
|
||||
// context, so the data won't be accessed concurrently.
|
||||
let task_func = unsafe { current_task.func.get() };
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use alloc::sync::Arc;
|
||||
use core::{ptr::NonNull, sync::atomic::Ordering};
|
||||
|
||||
use super::{context_switch, Task, TaskContext, POST_SCHEDULE_HANDLER};
|
||||
use super::{context_switch, Task, TaskContext, POST_SCHEDULE_HANDLER, PRE_SCHEDULE_HANDLER};
|
||||
use crate::{cpu_local_cell, trap::irq::DisabledLocalIrqGuard};
|
||||
|
||||
cpu_local_cell! {
|
||||
|
@ -52,8 +52,6 @@ pub(super) fn switch_to_task(next_task: Arc<Task>) {
|
|||
// so its reference will be valid until `after_switching_to`.
|
||||
let current_task = unsafe { &*current_task_ptr };
|
||||
|
||||
current_task.save_fpu_state();
|
||||
|
||||
// Until `after_switching_to`, the task's context is alive and can be exclusively used.
|
||||
current_task.ctx.get()
|
||||
} else {
|
||||
|
@ -86,13 +84,13 @@ pub(super) fn switch_to_task(next_task: Arc<Task>) {
|
|||
|
||||
// SAFETY: The task is just switched back, `after_switching_to` hasn't been called yet.
|
||||
unsafe { after_switching_to() };
|
||||
|
||||
if let Some(current) = Task::current() {
|
||||
current.restore_fpu_state();
|
||||
}
|
||||
}
|
||||
|
||||
fn before_switching_to(next_task: &Task, irq_guard: &DisabledLocalIrqGuard) {
|
||||
if let Some(handler) = PRE_SCHEDULE_HANDLER.get() {
|
||||
handler();
|
||||
}
|
||||
|
||||
// Ensure that the mapping to the kernel stack is valid.
|
||||
next_task.kstack.flush_tlb(irq_guard);
|
||||
|
||||
|
|
Loading…
Reference in New Issue