Skip to main content

ax_cpu/arch/aarch64/
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 = "fp-simd")]
11use super::fp::FpState;
12#[cfg(feature = "context")]
13use crate::{KernelTlsBase, TaskLocalState, context::TaskAnchor};
14
15/// Saved registers when a trap (exception) occurs.
16#[repr(C)]
17#[derive(Default, Clone, Copy)]
18pub struct TrapFrame {
19    /// General-purpose registers (X0..X30).
20    pub x: super::registers::GeneralRegisters,
21    /// Exception Link Register (ELR_EL1).
22    pub elr: u64,
23    /// Saved Process Status Register (SPSR_EL1).
24    pub spsr: u64,
25
26    /// Stack pointer at the time of the exception.
27    /// Populated by SAVE_REGS as `sp_before_sub = sp_after_sub + trapframe_size`.
28    ///
29    /// Note: This field is read-only (saved by SAVE_REGS for inspection only).
30    /// The actual SP is restored by RESTORE_REGS via `add sp, sp, #trapframe_size`,
31    /// not from this field. Modifying this value will NOT affect the actual SP
32    /// after exception return.
33    pub sp: u64,
34}
35
36impl fmt::Debug for TrapFrame {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        writeln!(f, "TrapFrame: {{")?;
39        for (i, &reg) in self.x.iter().enumerate() {
40            writeln!(f, "    x{i}: {reg:#x},")?;
41        }
42        writeln!(f, "    elr: {:#x},", self.elr)?;
43        writeln!(f, "    spsr: {:#x},", self.spsr)?;
44        writeln!(f, "    sp: {:#x},", self.sp)?;
45        write!(f, "}}")?;
46        Ok(())
47    }
48}
49
50impl TrapFrame {
51    /// Copies IRQ sampling values from this saved machine image.
52    pub fn interrupted_context(&self) -> crate::trap::InterruptedContext {
53        use crate::trap::{InterruptedContext, InterruptedPrivilege};
54        InterruptedContext {
55            pc: self.ip(),
56            sp: self.sp as usize,
57            fp: self.x[29] as usize,
58            lr: self.x[30] as usize,
59            privilege: match self.origin() {
60                crate::trap::TrapOrigin::Kernel => InterruptedPrivilege::Kernel,
61                crate::trap::TrapOrigin::User => InterruptedPrivilege::User,
62            },
63        }
64    }
65
66    /// Returns the privilege domain represented by this register image.
67    pub const fn origin(&self) -> crate::TrapOrigin {
68        if self.spsr & 0b1_1111 == 0 {
69            crate::TrapOrigin::User
70        } else {
71            crate::TrapOrigin::Kernel
72        }
73    }
74
75    /// Gets the 0th syscall argument.
76    pub const fn arg0(&self) -> usize {
77        self.x[0] as _
78    }
79
80    /// Sets the 0th syscall argument.
81    pub const fn set_arg0(&mut self, a0: usize) {
82        self.x[0] = a0 as _;
83    }
84
85    /// Gets the 1st syscall argument.
86    pub const fn arg1(&self) -> usize {
87        self.x[1] as _
88    }
89
90    /// Sets the 1st syscall argument.
91    pub const fn set_arg1(&mut self, a1: usize) {
92        self.x[1] = a1 as _;
93    }
94
95    /// Gets the 2nd syscall argument.
96    pub const fn arg2(&self) -> usize {
97        self.x[2] as _
98    }
99
100    /// Sets the 2nd syscall argument.
101    pub const fn set_arg2(&mut self, a2: usize) {
102        self.x[2] = a2 as _;
103    }
104
105    /// Gets the 3rd syscall argument.
106    pub const fn arg3(&self) -> usize {
107        self.x[3] as _
108    }
109
110    /// Sets the 3rd syscall argument.
111    pub const fn set_arg3(&mut self, a3: usize) {
112        self.x[3] = a3 as _;
113    }
114
115    /// Gets the 4th syscall argument.
116    pub const fn arg4(&self) -> usize {
117        self.x[4] as _
118    }
119
120    /// Sets the 4th syscall argument.
121    pub const fn set_arg4(&mut self, a4: usize) {
122        self.x[4] = a4 as _;
123    }
124
125    /// Gets the 5th syscall argument.
126    pub const fn arg5(&self) -> usize {
127        self.x[5] as _
128    }
129
130    /// Sets the 5th syscall argument.
131    pub const fn set_arg5(&mut self, a5: usize) {
132        self.x[5] = a5 as _;
133    }
134
135    /// Gets the instruction pointer.
136    pub const fn ip(&self) -> usize {
137        self.elr as _
138    }
139
140    /// Sets the instruction pointer.
141    pub const fn set_ip(&mut self, pc: usize) {
142        self.elr = pc as _;
143    }
144
145    /// Get the syscall number.
146    pub const fn sysno(&self) -> usize {
147        self.x[8] as usize
148    }
149
150    /// Sets the syscall number.
151    pub const fn set_sysno(&mut self, sysno: usize) {
152        self.x[8] = sysno as _;
153    }
154
155    /// Gets the return value register.
156    pub const fn retval(&self) -> usize {
157        self.x[0] as _
158    }
159
160    /// Sets the return value register.
161    pub const fn set_retval(&mut self, r0: usize) {
162        self.x[0] = r0 as _;
163    }
164
165    /// Sets the return address.
166    pub const fn set_ra(&mut self, lr: usize) {
167        self.x[30] = lr as _;
168    }
169
170    /// Copies the machine registers needed by a runtime stack unwinder.
171    pub fn backtrace_registers(&self) -> crate::trap::BacktraceRegisters {
172        crate::trap::BacktraceRegisters::new(self.x[29] as _, self.elr as _, self.x[30] as _)
173    }
174}
175
176/// Saved hardware states of a task.
177///
178/// The context usually includes:
179///
180/// - Callee-saved registers
181/// - Stack pointer register
182/// - Thread pointer register (for kernel-space thread-local storage)
183/// - FP/SIMD registers
184///
185/// On context switch, current task saves its context from CPU to memory,
186/// and the next task restores its context from memory to CPU.
187#[allow(missing_docs)]
188#[repr(C)]
189#[derive(Debug, Default)]
190#[cfg(feature = "context")]
191pub struct TaskContext {
192    sp: u64,
193    r19: u64,
194    r20: u64,
195    r21: u64,
196    r22: u64,
197    r23: u64,
198    r24: u64,
199    r25: u64,
200    r26: u64,
201    r27: u64,
202    r28: u64,
203    r29: u64,
204    lr: u64, // r30
205    /// Architecture-neutral current-header and kernel-TLS switch state.
206    task_local: TaskLocalState,
207    #[cfg(feature = "fp-simd")]
208    fp_state: FpState,
209}
210
211// `stp`/`ldp` address only the first member of each pair. Prove the paired
212// fields are adjacent and that the task TLS newtype has the register width.
213#[cfg(feature = "context")]
214const _: () = {
215    assert!(size_of::<KernelTlsBase>() == size_of::<usize>());
216    assert!(align_of::<KernelTlsBase>() == align_of::<usize>());
217    assert!(offset_of!(TaskContext, sp) == 0);
218    assert!(offset_of!(TaskContext, r20) == offset_of!(TaskContext, r19) + size_of::<u64>());
219    assert!(offset_of!(TaskContext, r22) == offset_of!(TaskContext, r21) + size_of::<u64>());
220    assert!(offset_of!(TaskContext, r24) == offset_of!(TaskContext, r23) + size_of::<u64>());
221    assert!(offset_of!(TaskContext, r26) == offset_of!(TaskContext, r25) + size_of::<u64>());
222    assert!(offset_of!(TaskContext, r28) == offset_of!(TaskContext, r27) + size_of::<u64>());
223    assert!(offset_of!(TaskContext, lr) == offset_of!(TaskContext, r29) + size_of::<u64>());
224    assert!(offset_of!(TaskContext, task_local) == offset_of!(TaskContext, lr) + size_of::<u64>());
225};
226
227#[cfg(feature = "context")]
228impl TaskContext {
229    /// Creates a dummy context for a new task.
230    ///
231    /// Note the context is not initialized, it will be filled by
232    /// [`switch_to`](Self::switch_to) (for initial tasks) and [`init`]
233    /// (for regular tasks) methods.
234    ///
235    /// [`init`]: TaskContext::init
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    /// Initializes the context for a new task, with the given entry point and
241    /// kernel stack.
242    pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, kernel_tls: KernelTlsBase) {
243        self.sp = kstack_top.as_usize() as u64;
244        self.lr = entry as u64;
245        self.task_local.set_kernel_tls(kernel_tls);
246    }
247
248    /// Sets the pinned task-owned runtime task anchor.
249    pub fn set_task_anchor(&mut self, header: TaskAnchor) {
250        self.task_local.set_task_anchor(header);
251    }
252
253    /// Returns the configured task-owned runtime task anchor.
254    pub const fn task_anchor(&self) -> Option<TaskAnchor> {
255        self.task_local.task_anchor()
256    }
257
258    /// Saves the running parent's hardware FP image into an unpublished child.
259    ///
260    /// The runtime must pin the parent CPU while calling this method. Kernel
261    /// code uses the soft-float ABI; eager task switching keeps the parent's
262    /// user register image live across kernel entry and preemption.
263    #[cfg(all(feature = "fp-simd", feature = "uspace"))]
264    pub fn clone_user_fp_state_into(&self, child: &mut Self) {
265        assert!(
266            self.task_anchor().is_some(),
267            "FP clone parent must be bound"
268        );
269        assert!(
270            child.task_anchor().is_none(),
271            "FP clone child must be unpublished"
272        );
273        child.fp_state.save();
274    }
275
276    /// Completes FP/SIMD work before current-context publication.
277    pub fn prepare_switch_to(&mut self, _next_ctx: &Self) {
278        #[cfg(feature = "fp-simd")]
279        {
280            self.fp_state.save();
281            _next_ctx.fp_state.restore();
282        }
283    }
284
285    /// Performs only the final GPR/current/TLS transfer.
286    ///
287    /// # Safety
288    ///
289    /// Scheduling must be serialized, FP state prepared, and the next current
290    /// anchor published. Both contexts and their anchors must remain pinned and
291    /// alive. IRQs must remain disabled from publication through this call,
292    /// with no intervening fallible or ownership-sensitive work.
293    #[inline(always)]
294    pub unsafe fn switch_to(&mut self, next_ctx: &Self) {
295        unsafe { context_switch_raw(self, next_ctx) }
296    }
297}
298
299#[cfg(kernel_tls)]
300#[unsafe(naked)]
301#[cfg(feature = "context")]
302unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
303    naked_asm!(
304        "
305        // save old context (callee-saved registers)
306        stp     x29, x30, [x0, {r29_offset}]
307        stp     x27, x28, [x0, {r27_offset}]
308        stp     x25, x26, [x0, {r25_offset}]
309        stp     x23, x24, [x0, {r23_offset}]
310        stp     x21, x22, [x0, {r21_offset}]
311        stp     x19, x20, [x0, {r19_offset}]
312        mov     x19, sp
313        str     x19, [x0, {sp_offset}]
314        mrs     x9, tpidr_el0
315        str     x9, [x0, {kernel_tls_offset}]
316
317        // restore new context
318        ldr     x9, [x1, {kernel_tls_offset}]
319        msr     tpidr_el0, x9
320        ldr     x19, [x1, {sp_offset}]
321        mov     sp, x19
322        ldp     x19, x20, [x1, {r19_offset}]
323        ldp     x21, x22, [x1, {r21_offset}]
324        ldp     x23, x24, [x1, {r23_offset}]
325        ldp     x25, x26, [x1, {r25_offset}]
326        ldp     x27, x28, [x1, {r27_offset}]
327        ldp     x29, x30, [x1, {r29_offset}]
328        ldr     x9, [x1, {context_header_offset}]
329        msr     sp_el0, x9
330
331        ret",
332        sp_offset = const offset_of!(TaskContext, sp),
333        r19_offset = const offset_of!(TaskContext, r19),
334        r21_offset = const offset_of!(TaskContext, r21),
335        r23_offset = const offset_of!(TaskContext, r23),
336        r25_offset = const offset_of!(TaskContext, r25),
337        r27_offset = const offset_of!(TaskContext, r27),
338        r29_offset = const offset_of!(TaskContext, r29),
339        context_header_offset = const offset_of!(TaskContext, task_local)
340            + offset_of!(TaskLocalState, context_header),
341        kernel_tls_offset = const offset_of!(TaskContext, task_local)
342            + offset_of!(TaskLocalState, kernel_tls),
343    )
344}
345
346#[cfg(not(kernel_tls))]
347#[unsafe(naked)]
348#[cfg(feature = "context")]
349unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
350    naked_asm!(
351        "
352        // save old context (callee-saved registers)
353        stp     x29, x30, [x0, {r29_offset}]
354        stp     x27, x28, [x0, {r27_offset}]
355        stp     x25, x26, [x0, {r25_offset}]
356        stp     x23, x24, [x0, {r23_offset}]
357        stp     x21, x22, [x0, {r21_offset}]
358        stp     x19, x20, [x0, {r19_offset}]
359        mov     x19, sp
360        str     x19, [x0, {sp_offset}]
361
362        // LinuxCurrent keeps task identity in SP_EL0. TPIDR_EL0 remains
363        // userspace-owned and is never part of a kernel task switch.
364        ldr     x19, [x1, {sp_offset}]
365        mov     sp, x19
366        ldp     x19, x20, [x1, {r19_offset}]
367        ldp     x21, x22, [x1, {r21_offset}]
368        ldp     x23, x24, [x1, {r23_offset}]
369        ldp     x25, x26, [x1, {r25_offset}]
370        ldp     x27, x28, [x1, {r27_offset}]
371        ldp     x29, x30, [x1, {r29_offset}]
372        ldr     x9, [x1, {context_header_offset}]
373        msr     sp_el0, x9
374        ret",
375        sp_offset = const offset_of!(TaskContext, sp),
376        r19_offset = const offset_of!(TaskContext, r19),
377        r21_offset = const offset_of!(TaskContext, r21),
378        r23_offset = const offset_of!(TaskContext, r23),
379        r25_offset = const offset_of!(TaskContext, r25),
380        r27_offset = const offset_of!(TaskContext, r27),
381        r29_offset = const offset_of!(TaskContext, r29),
382        context_header_offset = const offset_of!(TaskContext, task_local)
383            + offset_of!(TaskLocalState, context_header),
384    )
385}