Skip to main content

ax_cpu/x86_64/
context.rs

1use core::{
2    arch::naked_asm,
3    fmt,
4    mem::{align_of, offset_of, size_of},
5    ptr::NonNull,
6};
7
8use ax_memory_addr::VirtAddr;
9use cpu_local::{ExecutionContextHeader, PreparedContextSwitch};
10
11use crate::{KernelTlsBase, TaskLocalState};
12
13/// Saved registers when a trap (interrupt or exception) occurs.
14#[allow(missing_docs)]
15#[repr(C)]
16#[derive(Debug, Default, Clone, Copy)]
17pub struct TrapFrame {
18    pub rax: u64,
19    pub rcx: u64,
20    pub rdx: u64,
21    pub rbx: u64,
22    pub rbp: u64,
23    pub rsi: u64,
24    pub rdi: u64,
25    pub r8: u64,
26    pub r9: u64,
27    pub r10: u64,
28    pub r11: u64,
29    pub r12: u64,
30    pub r13: u64,
31    pub r14: u64,
32    pub r15: u64,
33
34    // Pushed by `trap.S`
35    pub vector: u64,
36    pub error_code: u64,
37
38    // Pushed by CPU
39    pub rip: u64,
40    pub cs: u64,
41    pub rflags: u64,
42    pub rsp: u64,
43    pub ss: u64,
44}
45
46impl TrapFrame {
47    /// Returns the privilege domain represented by this register image.
48    pub const fn origin(&self) -> crate::TrapOrigin {
49        if self.cs & 0b11 == 0 {
50            crate::TrapOrigin::Kernel
51        } else {
52            crate::TrapOrigin::User
53        }
54    }
55
56    /// Gets the 0th syscall argument.
57    pub const fn arg0(&self) -> usize {
58        self.rdi as _
59    }
60
61    /// Sets the 0th syscall argument.
62    pub const fn set_arg0(&mut self, rdi: usize) {
63        self.rdi = rdi as _;
64    }
65
66    /// Gets the 1st syscall argument.
67    pub const fn arg1(&self) -> usize {
68        self.rsi as _
69    }
70
71    /// Sets the 1st syscall argument.
72    pub const fn set_arg1(&mut self, rsi: usize) {
73        self.rsi = rsi as _;
74    }
75
76    /// Gets the 2nd syscall argument.
77    pub const fn arg2(&self) -> usize {
78        self.rdx as _
79    }
80
81    /// Sets the 2nd syscall argument.
82    pub const fn set_arg2(&mut self, rdx: usize) {
83        self.rdx = rdx as _;
84    }
85
86    /// Gets the 3rd syscall argument.
87    pub const fn arg3(&self) -> usize {
88        self.r10 as _
89    }
90
91    /// Sets the 3rd syscall argument.
92    pub const fn set_arg3(&mut self, r10: usize) {
93        self.r10 = r10 as _;
94    }
95
96    /// Gets the 4th syscall argument.
97    pub const fn arg4(&self) -> usize {
98        self.r8 as _
99    }
100
101    /// Sets the 4th syscall argument.
102    pub const fn set_arg4(&mut self, r8: usize) {
103        self.r8 = r8 as _;
104    }
105
106    /// Gets the 5th syscall argument.
107    pub const fn arg5(&self) -> usize {
108        self.r9 as _
109    }
110
111    /// Sets the 5th syscall argument.
112    pub const fn set_arg5(&mut self, r9: usize) {
113        self.r9 = r9 as _;
114    }
115
116    /// Gets the instruction pointer.
117    pub const fn ip(&self) -> usize {
118        self.rip as _
119    }
120
121    /// Sets the instruction pointer.
122    pub const fn set_ip(&mut self, rip: usize) {
123        self.rip = rip as _;
124    }
125
126    /// Gets the stack pointer.
127    pub const fn sp(&self) -> usize {
128        self.rsp as _
129    }
130
131    /// Sets the stack pointer.
132    pub const fn set_sp(&mut self, rsp: usize) {
133        self.rsp = rsp as _;
134    }
135
136    /// Gets the syscall number.
137    pub const fn sysno(&self) -> usize {
138        self.rax as usize
139    }
140
141    /// Sets the syscall number.
142    pub const fn set_sysno(&mut self, rax: usize) {
143        self.rax = rax as _;
144    }
145
146    /// Gets the return value register.
147    pub const fn retval(&self) -> usize {
148        self.rax as _
149    }
150
151    /// Sets the return value register.
152    pub const fn set_retval(&mut self, rax: usize) {
153        self.rax = rax as _;
154    }
155
156    /// Unwind the stack and get the backtrace.
157    pub fn backtrace(&self) -> axbacktrace::Backtrace {
158        axbacktrace::Backtrace::capture_trap(self.rbp as _, self.rip as _, 0)
159    }
160}
161
162#[repr(C)]
163#[derive(Debug, Default)]
164struct ContextSwitchFrame {
165    r15: u64,
166    r14: u64,
167    r13: u64,
168    r12: u64,
169    rbx: u64,
170    rbp: u64,
171    rip: u64,
172}
173
174/// A 512-byte memory region for the FXSAVE/FXRSTOR instruction to save and
175/// restore the x87 FPU, MMX, XMM, and MXCSR registers.
176///
177/// This is also the legacy region (offset 0..512) at the head of the
178/// XSAVE/XRSTOR area, so it doubles as the start of [`UserXstate`].
179///
180/// See <https://www.felixcloutier.com/x86/fxsave> for more details.
181#[allow(missing_docs)]
182#[repr(C, align(16))]
183#[derive(Clone, Copy, Debug)]
184pub struct FxsaveArea {
185    pub fcw: u16,
186    pub fsw: u16,
187    pub ftw: u16,
188    pub fop: u16,
189    pub fip: u64,
190    pub fdp: u64,
191    pub mxcsr: u32,
192    pub mxcsr_mask: u32,
193    pub st: [u64; 16],
194    pub xmm: [u64; 32],
195    _padding: [u64; 12],
196}
197
198const _: () = assert!(core::mem::size_of::<FxsaveArea>() == 512);
199
200/// Size of the per-task XSAVE/XRSTOR area, in bytes.
201///
202/// The boot path ([`enable_xsave_features`]) only ever enables the x87, SSE,
203/// and AVX components in `XCR0` (it never enables AVX-512, MPX, or PKRU), so the
204/// largest XSAVE layout we must hold is the 512-byte legacy region, the 64-byte
205/// XSAVE header, and the 256-byte AVX (`YMM_Hi128`) component. 1024 bytes covers
206/// that with headroom and is a multiple of the required 64-byte alignment.
207///
208/// [`enable_xsave_features`]: ../../../../platforms/someboot/src/arch/x86_64/trap.rs
209const XSAVE_AREA_SIZE: usize = 1024;
210#[cfg(feature = "fp-simd")]
211const XSAVE_HEADER_OFFSET: usize = 512;
212#[cfg(feature = "fp-simd")]
213const XSAVE_HEADER_SIZE: usize = 64;
214#[cfg(feature = "fp-simd")]
215const XSAVE_XCOMP_BV_OFFSET: usize = XSAVE_HEADER_OFFSET + size_of::<u64>();
216#[cfg(feature = "fp-simd")]
217const XSAVE_HEADER_RESERVED_OFFSET: usize = XSAVE_XCOMP_BV_OFFSET + size_of::<u64>();
218#[cfg(feature = "fp-simd")]
219const XFEATURE_MASK_FPSSE: u64 = (1 << 0) | (1 << 1);
220#[cfg(feature = "fp-simd")]
221const MXCSR_FALLBACK_MASK: u32 = 0x0000_ffbf;
222
223/// A 64-byte-aligned memory region for the XSAVE/XRSTOR instructions, which save
224/// and restore the full `XCR0`-enabled extended state (x87, SSE/XMM, and the
225/// upper 128 bits of the AVX `YMM` registers that FXSAVE/FXRSTOR drop).
226///
227/// The first 512 bytes share the legacy [`FxsaveArea`] layout, so the FXSAVE
228/// fallback path (CPUs/VMs without XSAVE, e.g. the default `qemu64` model) reads
229/// and writes the same region.
230///
231/// See <https://www.felixcloutier.com/x86/xsave> for more details.
232#[repr(C, align(64))]
233#[derive(Clone, Copy)]
234pub struct UserXstate {
235    /// Legacy region, identical in layout to the FXSAVE/FXRSTOR area.
236    legacy: FxsaveArea,
237    /// XSAVE header (`XSTATE_BV`, `XCOMP_BV`, reserved) plus the extended
238    /// component area. A zeroed header marks every component as being in its
239    /// initial state, which is the correct starting point for a fresh task.
240    rest: [u8; XSAVE_AREA_SIZE - 512],
241}
242
243const _: () = assert!(core::mem::size_of::<UserXstate>() == XSAVE_AREA_SIZE);
244
245#[cfg(feature = "fp-simd")]
246impl UserXstate {
247    /// Returns the architecture's initial user FPU state image.
248    pub const fn initial() -> Self {
249        ExtendedState::default().area
250    }
251
252    /// Returns the standard, non-compacted x86 user xstate size enabled by XCR0.
253    ///
254    /// `None` means this CPU uses the FXSAVE fallback and therefore does not
255    /// provide Linux's `NT_X86_XSTATE` regset.
256    pub fn user_size() -> Option<usize> {
257        if !ExtendedState::xsave_enabled() {
258            return None;
259        }
260        let size = core::arch::x86_64::__cpuid_count(0x0d, 0).ebx as usize;
261        assert!(
262            (XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=XSAVE_AREA_SIZE).contains(&size),
263            "enabled x86 user xstate exceeds the task-owned XSAVE area",
264        );
265        Some(size)
266    }
267
268    /// Returns the user xfeatures enabled by the boot-time XCR0 policy.
269    pub fn user_feature_mask() -> u64 {
270        if ExtendedState::xsave_enabled() {
271            ExtendedState::xsave_mask()
272        } else {
273            XFEATURE_MASK_FPSSE
274        }
275    }
276
277    /// Returns the legacy 512-byte FXSAVE region.
278    pub const fn fxsave_area(&self) -> &FxsaveArea {
279        &self.legacy
280    }
281
282    /// Returns the legacy 512-byte FXSAVE region as bytes.
283    pub fn fxsave_bytes(&self) -> &[u8] {
284        // SAFETY: `FxsaveArea` is a contiguous initialized 512-byte region.
285        unsafe {
286            core::slice::from_raw_parts(
287                (&self.legacy as *const FxsaveArea).cast::<u8>(),
288                size_of::<FxsaveArea>(),
289            )
290        }
291    }
292
293    /// Returns the enabled standard-format user xstate bytes.
294    pub fn user_bytes(&self) -> Option<&[u8]> {
295        let size = Self::user_size()?;
296        // SAFETY: `UserXstate` is a contiguous XSAVE area and `size` was
297        // validated against its capacity above.
298        Some(unsafe { core::slice::from_raw_parts((self as *const Self).cast::<u8>(), size) })
299    }
300
301    /// Replaces the Linux FXSAVE-compatible portion while retaining all other
302    /// xfeatures, as `PTRACE_SETFPREGS`/`NT_PRFPREG` require.
303    pub fn replace_fxsave_area(&mut self, area: FxsaveArea) -> bool {
304        if !Self::mxcsr_is_valid(area.mxcsr) {
305            return false;
306        }
307        self.legacy = area;
308        if ExtendedState::xsave_enabled() {
309            let features = self.xstate_bv() | XFEATURE_MASK_FPSSE;
310            self.write_xstate_bv(features);
311        }
312        true
313    }
314
315    /// Replaces the Linux FXSAVE-compatible portion from its byte UABI.
316    pub fn replace_fxsave_bytes(&mut self, bytes: &[u8]) -> bool {
317        if bytes.len() != size_of::<FxsaveArea>() {
318            return false;
319        }
320        let mut area = core::mem::MaybeUninit::<FxsaveArea>::zeroed();
321        // SAFETY: the destination is a valid aligned `FxsaveArea`, both slices
322        // have exactly its size, and `u8` has no invalid bit patterns.
323        unsafe {
324            core::ptr::copy_nonoverlapping(
325                bytes.as_ptr(),
326                area.as_mut_ptr().cast::<u8>(),
327                bytes.len(),
328            );
329            self.replace_fxsave_area(area.assume_init())
330        }
331    }
332
333    /// Replaces the complete standard-format user xstate after validating the
334    /// Linux UABI header, enabled feature mask, and MXCSR reserved bits.
335    pub fn replace_user_bytes(&mut self, bytes: &[u8]) -> bool {
336        let Some(user_size) = Self::user_size() else {
337            return false;
338        };
339        if bytes.len() != user_size {
340            return false;
341        }
342        let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
343        let xcomp_bv = read_u64(bytes, XSAVE_XCOMP_BV_OFFSET);
344        if xstate_bv & !ExtendedState::xsave_mask() != 0
345            || xcomp_bv != 0
346            || bytes[XSAVE_HEADER_RESERVED_OFFSET..XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE]
347                .iter()
348                .any(|byte| *byte != 0)
349        {
350            return false;
351        }
352        let mxcsr = u32::from_ne_bytes(
353            bytes[24..28]
354                .try_into()
355                .expect("the FXSAVE MXCSR field has a fixed width"),
356        );
357        if !Self::mxcsr_is_valid(mxcsr) {
358            return false;
359        }
360
361        // SAFETY: `UserXstate` is a contiguous writable XSAVE area, and the
362        // destination length is its compile-time capacity.
363        let destination = unsafe {
364            core::slice::from_raw_parts_mut((self as *mut Self).cast::<u8>(), XSAVE_AREA_SIZE)
365        };
366        destination.fill(0);
367        destination[..user_size].copy_from_slice(bytes);
368        true
369    }
370
371    /// Replaces a standard-format user xstate prefix from an older signal ABI.
372    ///
373    /// Components absent from the supplied prefix enter their architectural
374    /// initial state. Every feature named in `XSTATE_BV` must fit completely in
375    /// the supplied prefix.
376    pub fn replace_user_bytes_prefix(&mut self, bytes: &[u8]) -> bool {
377        let Some(user_size) = Self::user_size() else {
378            return false;
379        };
380        if !(XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=user_size).contains(&bytes.len()) {
381            return false;
382        }
383        let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
384        if xstate_bv & !ExtendedState::xsave_mask() != 0
385            || !xstate_components_fit(xstate_bv, bytes.len())
386        {
387            return false;
388        }
389
390        let mut complete = [0; XSAVE_AREA_SIZE];
391        complete[..bytes.len()].copy_from_slice(bytes);
392        self.replace_user_bytes(&complete[..user_size])
393    }
394
395    fn xstate_bv(&self) -> u64 {
396        read_u64(
397            self.user_bytes()
398                .expect("xstate header requires XSAVE support"),
399            XSAVE_HEADER_OFFSET,
400        )
401    }
402
403    fn write_xstate_bv(&mut self, value: u64) {
404        let bytes = value.to_ne_bytes();
405        // SAFETY: the fixed XSAVE header lies inside `UserXstate`.
406        unsafe {
407            core::ptr::copy_nonoverlapping(
408                bytes.as_ptr(),
409                (self as *mut Self).cast::<u8>().add(XSAVE_HEADER_OFFSET),
410                bytes.len(),
411            )
412        };
413    }
414
415    fn mxcsr_is_valid(mxcsr: u32) -> bool {
416        let mut feature_image =
417            unsafe { core::mem::MaybeUninit::<FxsaveArea>::zeroed().assume_init() };
418        // SAFETY: `feature_image` is a writable 16-byte-aligned FXSAVE area.
419        // FXSAVE preserves the current hardware registers and reports the CPU
420        // feature mask independently of any user-provided xstate payload.
421        unsafe {
422            core::arch::x86_64::_fxsave64((&mut feature_image as *mut FxsaveArea).cast::<u8>())
423        };
424        let mask = if feature_image.mxcsr_mask == 0 {
425            MXCSR_FALLBACK_MASK
426        } else {
427            feature_image.mxcsr_mask
428        };
429        mxcsr & !mask == 0
430    }
431}
432
433#[cfg(feature = "fp-simd")]
434fn xstate_components_fit(xstate_bv: u64, supplied_size: usize) -> bool {
435    for feature in 2..u64::BITS {
436        if xstate_bv & (1 << feature) == 0 {
437            continue;
438        }
439        let component = core::arch::x86_64::__cpuid_count(0x0d, feature);
440        let offset = component.ebx as usize;
441        let size = component.eax as usize;
442        if size == 0
443            || offset
444                .checked_add(size)
445                .is_none_or(|end| end > supplied_size)
446        {
447            return false;
448        }
449    }
450    true
451}
452
453#[cfg(feature = "fp-simd")]
454fn read_u64(bytes: &[u8], offset: usize) -> u64 {
455    u64::from_ne_bytes(
456        bytes[offset..offset + size_of::<u64>()]
457            .try_into()
458            .expect("the XSAVE header field has a fixed width"),
459    )
460}
461
462/// Extended state of a task, such as FP/SIMD states.
463///
464/// On context switch the state is saved/restored with XSAVE/XRSTOR when the boot
465/// path enabled `CR4.OSXSAVE` (so that the AVX `YMM` upper halves are preserved),
466/// and falls back to FXSAVE/FXRSTOR otherwise.
467pub struct ExtendedState {
468    area: UserXstate,
469}
470
471#[cfg(feature = "fp-simd")]
472impl ExtendedState {
473    /// Provides access to the legacy FXSAVE region for compatibility with code
474    /// that inspects the x87/SSE state directly.
475    #[inline]
476    pub fn fxsave_area(&self) -> &FxsaveArea {
477        &self.area.legacy
478    }
479
480    /// Returns `true` when the boot path enabled XSAVE state management
481    /// (`CR4.OSXSAVE`), which is the single source of truth for whether
482    /// XSAVE/XRSTOR (and reading `XCR0` via `XGETBV`) are safe to use.
483    #[inline]
484    #[cfg(not(feature = "host-test"))]
485    fn xsave_enabled() -> bool {
486        // SAFETY: reading CR4 from ring 0 is always well-defined.
487        let cr4 = unsafe { x86::controlregs::cr4() };
488        cr4.contains(x86::controlregs::Cr4::CR4_ENABLE_OS_XSAVE)
489    }
490
491    /// Host scheduler tests execute at ring 3 and therefore cannot inspect
492    /// CR4. FXSAVE/FXRSTOR remain available and cover the state exercised by
493    /// the test fixture without changing the kernel's XSAVE policy.
494    #[inline]
495    #[cfg(feature = "host-test")]
496    fn xsave_enabled() -> bool {
497        false
498    }
499
500    /// The set of state components to save/restore, i.e. the `XCR0` mask the
501    /// boot path programmed. Only valid to call when [`Self::xsave_enabled`].
502    #[inline]
503    fn xsave_mask() -> u64 {
504        // SAFETY: `CR4.OSXSAVE` is set (checked by the caller), so XGETBV is
505        // well-defined and will not #UD.
506        unsafe { x86::controlregs::xcr0().bits() }
507    }
508
509    /// Saves the current extended states from CPU to this structure.
510    #[inline]
511    pub fn save(&mut self) {
512        let ptr = &mut self.area as *mut _ as *mut u8;
513        #[cfg(feature = "uspace")]
514        if let Some((mask, xsaveopt_enabled)) = super::local_state::current_cpu_user_xsave_config()
515        {
516            // SAFETY: the CPU-local mask is the XCR0 value installed during
517            // this CPU's userspace initialization, and the task area is a
518            // standard 64-byte-aligned XSAVE image. Linux likewise selects
519            // XSAVEOPT once from boot CPU capabilities and otherwise uses
520            // the architectural XSAVE fallback.
521            unsafe {
522                if xsaveopt_enabled {
523                    core::arch::x86_64::_xsaveopt64(ptr, mask)
524                } else {
525                    core::arch::x86_64::_xsave64(ptr, mask)
526                }
527            }
528            return;
529        }
530        if Self::xsave_enabled() {
531            // SAFETY: `area` is 64-byte aligned and large enough for the
532            // XCR0-enabled state (x87/SSE/AVX); the mask matches XCR0.
533            unsafe { core::arch::x86_64::_xsave64(ptr, Self::xsave_mask()) }
534        } else {
535            // SAFETY: `area` starts with the 16-byte-aligned legacy FXSAVE region.
536            unsafe { core::arch::x86_64::_fxsave64(ptr) }
537        }
538    }
539
540    /// Restores the extended states from this structure to CPU.
541    #[inline]
542    pub fn restore(&self) {
543        let ptr = &self.area as *const _ as *const u8;
544        #[cfg(feature = "uspace")]
545        if let Some((mask, _)) = super::local_state::current_cpu_user_xsave_config() {
546            // SAFETY: the image and per-CPU mask obey the same contract as
547            // save(), and XRSTOR consumes the standard non-compacted format
548            // produced by XSAVE or XSAVEOPT.
549            unsafe { core::arch::x86_64::_xrstor64(ptr, mask) }
550            return;
551        }
552        if Self::xsave_enabled() {
553            // SAFETY: `area` was populated by `_xsave64` (or zero-initialized,
554            // which XRSTOR reads as the components' initial state) with a header
555            // consistent with the XCR0 mask used here.
556            unsafe { core::arch::x86_64::_xrstor64(ptr, Self::xsave_mask()) }
557        } else {
558            // SAFETY: `area` starts with the 16-byte-aligned legacy FXSAVE region.
559            unsafe { core::arch::x86_64::_fxrstor64(ptr) }
560        }
561    }
562
563    /// Returns the extended state with initialized values.
564    pub const fn default() -> Self {
565        // Zeroing the whole area gives XRSTOR an all-initial XSAVE header
566        // (XSTATE_BV = 0) so the first restore loads each component's default
567        // state; the legacy fields below seed the FXSAVE fallback path too.
568        let mut area: UserXstate = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
569        area.legacy.fcw = 0x37f;
570        // In the 512-byte FXSAVE/FXRSTOR area the x87 tag word is *abridged*: the
571        // low byte of this field is one bit per x87 register, where 0 = empty and
572        // 1 = occupied (FXRSTOR then derives the full tag from the register data).
573        // A freshly-initialized FPU (FNINIT) has an EMPTY x87 stack, i.e. abridged
574        // tag 0x00 — NOT the legacy full-tag-word value 0xFFFF (which encodes "all
575        // empty" only in the 2-bits-per-register FSAVE/FRSTOR format). Seeding
576        // 0xFFFF here set the abridged byte to 0xFF, so on the FXSAVE-fallback path
577        // (CPUs/VMs without XSAVE, e.g. the default `qemu64` model, where
578        // `ExtendedState::restore` uses FXRSTOR rather than XRSTOR) every new task
579        // resumed with all eight x87 registers tagged occupied — a "full" stack.
580        // The first `fld`/`fild` then overflowed it, yielding the x87 indefinite
581        // value, which is exactly how musl's x87 long-double `fmt_fp` loop got a
582        // wild operand, over-ran its on-stack digit array into the thread's `%fs:0`
583        // TLS self-pointer, and triggered the recursive-SIGSEGV storm that broke
584        // the x86 java workload. (On real XSAVE hardware XRSTOR re-inits x87 from
585        // the zeroed XSTATE_BV header, which is why the bug was qemu64-only.)
586        area.legacy.ftw = 0x0000;
587        area.legacy.mxcsr = 0x1f80;
588        Self { area }
589    }
590}
591
592impl fmt::Debug for ExtendedState {
593    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
594        f.debug_struct("ExtendedState")
595            .field("fxsave_area", &self.area.legacy)
596            .finish()
597    }
598}
599
600/// Saved hardware states of a task.
601///
602/// The context usually includes:
603///
604/// - Callee-saved registers
605/// - Stack pointer register
606/// - Thread pointer register (for kernel-space thread-local storage)
607/// - FP/SIMD registers
608///
609/// On context switch, current task saves its context from CPU to memory,
610/// and the next task restores its context from memory to CPU.
611///
612/// On x86_64, callee-saved registers are saved to the kernel stack by the
613/// `PUSH` instruction. So that [`rsp`] is the `RSP` after callee-saved
614/// registers are pushed, and [`kstack_top`] is the top of the kernel stack
615/// (`RSP` before any push).
616///
617/// [`rsp`]: TaskContext::rsp
618/// [`kstack_top`]: TaskContext::kstack_top
619#[repr(C)]
620#[derive(Debug)]
621pub struct TaskContext {
622    /// The kernel stack top of the task.
623    kstack_top: VirtAddr,
624    /// `RSP` after all callee-saved registers are pushed.
625    rsp: u64,
626    /// Architecture-neutral current-header and kernel-TLS switch state.
627    task_local: TaskLocalState,
628    /// Extended states, i.e., FP/SIMD states.
629    #[cfg(feature = "fp-simd")]
630    ext_state: ExtendedState,
631}
632
633// The naked switch loads these fields with machine-word instructions. Keep the
634// representation and adjacency assumptions executable as compile-time checks.
635const _: () = {
636    assert!(size_of::<KernelTlsBase>() == size_of::<usize>());
637    assert!(align_of::<KernelTlsBase>() == align_of::<usize>());
638    assert!(offset_of!(TaskContext, kstack_top) == 0);
639    assert!(offset_of!(TaskContext, rsp) == size_of::<VirtAddr>());
640    assert!(offset_of!(TaskContext, task_local) == offset_of!(TaskContext, rsp) + size_of::<u64>());
641};
642
643impl TaskContext {
644    /// Creates a dummy context for a new task.
645    ///
646    /// Note the context is not initialized, it will be filled by
647    /// [`switch_to_prepared`](Self::switch_to_prepared) (for initial tasks) and [`init`]
648    /// (for regular tasks) methods.
649    ///
650    /// [`init`]: TaskContext::init
651    pub fn new() -> Self {
652        Self {
653            kstack_top: va!(0),
654            rsp: 0,
655            task_local: TaskLocalState::new(),
656            #[cfg(feature = "fp-simd")]
657            ext_state: ExtendedState::default(),
658        }
659    }
660
661    /// Initializes the context for a new task, with the given entry point and
662    /// kernel stack.
663    pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, kernel_tls: KernelTlsBase) {
664        unsafe {
665            // x86_64 calling convention: the stack must be 16-byte aligned before
666            // calling a function. That means when entering a new task (`ret` in `context_switch`
667            // is executed), (stack pointer + 8) should be 16-byte aligned.
668            let frame_ptr = (kstack_top.as_mut_ptr() as *mut u64).sub(1);
669            let frame_ptr = (frame_ptr as *mut ContextSwitchFrame).sub(1);
670            core::ptr::write(
671                frame_ptr,
672                ContextSwitchFrame {
673                    rip: entry as _,
674                    ..Default::default()
675                },
676            );
677            self.rsp = frame_ptr as u64;
678        }
679        self.kstack_top = kstack_top;
680        self.task_local.set_kernel_tls(kernel_tls);
681    }
682
683    /// Sets the pinned task-owned execution-context header restored by the raw
684    /// switch tail in LinuxCurrent images.
685    pub fn set_context_header(&mut self, header: NonNull<ExecutionContextHeader>) {
686        self.task_local.set_context_header(header);
687    }
688
689    /// Returns the configured task-owned execution-context header.
690    pub const fn context_header(&self) -> Option<NonNull<ExecutionContextHeader>> {
691        self.task_local.context_header()
692    }
693
694    /// Completes every helper operation that must precede current publication.
695    pub fn prepare_switch_to(&mut self, _next_ctx: &Self) {
696        #[cfg(all(feature = "fp-simd", feature = "uspace"))]
697        {
698            let Some(current) = self.context_header() else {
699                super::local_state::assert_current_user_fp_unowned();
700                return;
701            };
702            let current = current.as_ptr().expose_provenance();
703            if super::local_state::current_user_fp_is_owner(current) {
704                self.ext_state.save();
705                super::local_state::clear_current_user_fp_owner_after_save(current);
706            }
707        }
708        #[cfg(all(feature = "fp-simd", not(feature = "uspace")))]
709        {
710            self.ext_state.save();
711            _next_ctx.ext_state.restore();
712        }
713    }
714
715    /// Restores this task's userspace FPU image at the final IRQ-off return boundary.
716    pub fn prepare_user_return_fp(&self) {
717        #[cfg(all(feature = "fp-simd", feature = "uspace"))]
718        {
719            let current = self
720                .context_header()
721                .expect("a userspace FPU owner requires a bound execution context")
722                .as_ptr()
723                .expose_provenance();
724            if super::local_state::current_user_fp_needs_restore(current) {
725                self.ext_state.restore();
726                super::local_state::publish_current_user_fp_owner(current);
727            }
728        }
729    }
730
731    /// Saves the current task's user FPU image directly into an unpublished clone.
732    #[cfg(all(feature = "fp-simd", feature = "uspace"))]
733    pub fn clone_user_fp_state_into(&self, child: &mut Self) {
734        assert!(
735            !core::ptr::eq(self, child),
736            "a cloned user FPU image requires a distinct task context",
737        );
738        assert!(
739            child.context_header().is_none(),
740            "a cloned user FPU image must be installed before context binding",
741        );
742        let current = self
743            .context_header()
744            .expect("a userspace FPU clone requires a bound execution context")
745            .as_ptr()
746            .expose_provenance();
747        if super::local_state::current_user_fp_needs_restore(current) {
748            self.ext_state.restore();
749            super::local_state::publish_current_user_fp_owner(current);
750        }
751        child.ext_state.save();
752    }
753
754    /// Captures the current task's complete hardware user xstate.
755    #[cfg(all(feature = "fp-simd", feature = "uspace"))]
756    pub fn capture_user_fp_state(&self) -> UserXstate {
757        let current = self
758            .context_header()
759            .expect("a userspace FPU snapshot requires a bound execution context")
760            .as_ptr()
761            .expose_provenance();
762        if super::local_state::current_user_fp_needs_restore(current) {
763            self.ext_state.restore();
764            super::local_state::publish_current_user_fp_owner(current);
765        }
766        let mut snapshot = ExtendedState::default();
767        snapshot.save();
768        snapshot.area
769    }
770
771    /// Installs a complete user xstate into the current task and hardware owner.
772    #[cfg(all(feature = "fp-simd", feature = "uspace"))]
773    pub fn replace_user_fp_state(&mut self, state: UserXstate) {
774        let current = self
775            .context_header()
776            .expect("a userspace FPU replacement requires a bound execution context")
777            .as_ptr()
778            .expose_provenance();
779        super::local_state::assert_current_user_fp_resettable(current);
780        self.ext_state.area = state;
781        self.ext_state.restore();
782        super::local_state::publish_current_user_fp_owner(current);
783    }
784
785    /// Replaces this task's user FPU state with the architecture initial image.
786    pub fn reset_user_fp_state(&mut self) {
787        #[cfg(all(feature = "fp-simd", feature = "uspace"))]
788        {
789            let current = self
790                .context_header()
791                .expect("a userspace FPU reset requires a bound execution context")
792                .as_ptr()
793                .expose_provenance();
794            super::local_state::assert_current_user_fp_resettable(current);
795            self.ext_state = ExtendedState::default();
796            self.ext_state.restore();
797            super::local_state::publish_current_user_fp_owner(current);
798        }
799    }
800
801    /// Commits current-context publication and performs the raw transfer.
802    ///
803    /// # Safety
804    ///
805    /// The caller must have serialized scheduling, prepared FP/SIMD state, and
806    /// `prepared` must belong to `next_ctx`. No fallible Rust work may be
807    /// placed between its commit and the naked switch tail.
808    #[inline(always)]
809    pub unsafe fn switch_to_prepared(
810        &mut self,
811        next_ctx: &Self,
812        prepared: PreparedContextSwitch<'_>,
813    ) {
814        unsafe { prepared.commit() };
815        unsafe { context_switch_raw(self, next_ctx) }
816    }
817}
818
819#[cfg(kernel_tls)]
820#[unsafe(naked)]
821unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
822    naked_asm!(
823        "
824        .code64
825        push    rbp
826        push    rbx
827        push    r12
828        push    r13
829        push    r14
830        push    r15
831        mov     [rdi + {rsp_offset}], rsp
832
833        // Save and restore task TLS only after all Rust helpers have finished.
834        mov     ecx, {fs_base_msr}
835        rdmsr
836        shl     rdx, 32
837        or      rax, rdx
838        mov     [rdi + {kernel_tls_offset}], rax
839        mov     rax, [rsi + {kernel_tls_offset}]
840        mov     rdx, rax
841        shr     rdx, 32
842        mov     ecx, {fs_base_msr}
843        wrmsr
844
845        mov     rsp, [rsi + {rsp_offset}]
846        pop     r15
847        pop     r14
848        pop     r13
849        pop     r12
850        pop     rbx
851        pop     rbp
852        ret",
853        rsp_offset = const offset_of!(TaskContext, rsp),
854        kernel_tls_offset = const offset_of!(TaskContext, task_local)
855            + offset_of!(TaskLocalState, kernel_tls),
856        fs_base_msr = const 0xc000_0100_u32,
857    )
858}
859
860#[cfg(all(test, feature = "host-test", feature = "uspace"))]
861mod tests {
862    use super::*;
863
864    #[test]
865    fn context_prepare_does_not_override_the_runtime_address_space_commit() {
866        // SAFETY: the host-test backend models CR3 with an unprivileged atomic.
867        unsafe { crate::asm::write_user_page_table(0x1000.into()) };
868        let mut previous = TaskContext::new();
869        unsafe { crate::asm::write_user_page_table(0x2000.into()) };
870        let next = TaskContext::new();
871
872        // The runtime address-space transaction commits a third root before
873        // the architecture register context is prepared.
874        unsafe { crate::asm::write_user_page_table(0x3000.into()) };
875        previous.prepare_switch_to(&next);
876
877        assert_eq!(crate::asm::read_user_page_table().as_usize(), 0x3000);
878    }
879}
880
881#[cfg(not(kernel_tls))]
882#[unsafe(naked)]
883unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
884    naked_asm!(
885        "
886        .code64
887        push    rbp
888        push    rbx
889        push    r12
890        push    r13
891        push    r14
892        push    r15
893        mov     [rdi + {rsp_offset}], rsp
894
895        // LinuxCurrent uses the already-published kernel GS slot. FS remains
896        // userspace-owned and must not be touched by a kernel task switch.
897        mov     rsp, [rsi + {rsp_offset}]
898        pop     r15
899        pop     r14
900        pop     r13
901        pop     r12
902        pop     rbx
903        pop     rbp
904        ret",
905        rsp_offset = const offset_of!(TaskContext, rsp),
906    )
907}