Skip to main content

ax_cpu/arch/x86_64/
context.rs

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