Skip to main content

ax_cpu/arch/loongarch64/
context.rs

1#[cfg(feature = "context")]
2use core::arch::naked_asm;
3#[cfg(feature = "context")]
4use core::mem::{align_of, offset_of, size_of};
5
6#[cfg(feature = "context")]
7use ax_memory_addr::VirtAddr;
8
9#[cfg(feature = "fp-simd")]
10use super::fp::FpuState;
11use super::registers::GeneralRegisters;
12#[cfg(feature = "context")]
13use crate::{KernelTlsBase, TaskLocalState, context::TaskAnchor};
14
15/// Saved registers when a trap (interrupt or exception) occurs.
16#[repr(C)]
17#[derive(Debug, Default, Clone, Copy)]
18pub struct TrapFrame {
19    /// All general registers.
20    pub regs: GeneralRegisters,
21    /// Pre-exception Mode Information
22    pub prmd: usize,
23    /// Exception Return Address
24    pub era: usize,
25}
26
27impl TrapFrame {
28    /// Copies IRQ sampling values from this saved machine image.
29    pub fn interrupted_context(&self) -> crate::trap::InterruptedContext {
30        use crate::trap::{InterruptedContext, InterruptedPrivilege};
31        InterruptedContext {
32            pc: self.ip(),
33            sp: self.regs.sp,
34            fp: self.regs.fp,
35            privilege: match self.origin() {
36                crate::trap::TrapOrigin::Kernel => InterruptedPrivilege::Kernel,
37                crate::trap::TrapOrigin::User => InterruptedPrivilege::User,
38            },
39        }
40    }
41
42    /// Returns whether the saved register image belongs to kernel or user
43    /// execution.
44    ///
45    /// In particular, `regs.u0` is restorable user state only for
46    /// [`crate::TrapOrigin::User`].
47    pub const fn origin(&self) -> crate::TrapOrigin {
48        if self.prmd & 0b11 == 0 {
49            crate::TrapOrigin::Kernel
50        } else {
51            crate::TrapOrigin::User
52        }
53    }
54
55    /// Gets the 0th syscall argument.
56    pub const fn arg0(&self) -> usize {
57        self.regs.a0
58    }
59
60    /// Sets the 0th syscall argument.
61    pub const fn set_arg0(&mut self, a0: usize) {
62        self.regs.a0 = a0;
63    }
64
65    /// Gets the 1st syscall argument.
66    pub const fn arg1(&self) -> usize {
67        self.regs.a1
68    }
69
70    /// Sets the 1st syscall argument.
71    pub const fn set_arg1(&mut self, a1: usize) {
72        self.regs.a1 = a1;
73    }
74
75    /// Gets the 2nd syscall argument.
76    pub const fn arg2(&self) -> usize {
77        self.regs.a2
78    }
79
80    /// Sets the 2nd syscall argument.
81    pub const fn set_arg2(&mut self, a2: usize) {
82        self.regs.a2 = a2;
83    }
84
85    /// Gets the 3rd syscall argument.
86    pub const fn arg3(&self) -> usize {
87        self.regs.a3
88    }
89
90    /// Sets the 3rd syscall argument.
91    pub const fn set_arg3(&mut self, a3: usize) {
92        self.regs.a3 = a3;
93    }
94
95    /// Gets the 4th syscall argument.
96    pub const fn arg4(&self) -> usize {
97        self.regs.a4
98    }
99
100    /// Sets the 4th syscall argument.
101    pub const fn set_arg4(&mut self, a4: usize) {
102        self.regs.a4 = a4;
103    }
104
105    /// Gets the 5th syscall argument.
106    pub const fn arg5(&self) -> usize {
107        self.regs.a5
108    }
109
110    /// Sets the 5th syscall argument.
111    pub const fn set_arg5(&mut self, a5: usize) {
112        self.regs.a5 = a5;
113    }
114
115    /// Get the syscall number.
116    pub const fn sysno(&self) -> usize {
117        self.regs.a7
118    }
119
120    /// Sets the syscall number.
121    pub const fn set_sysno(&mut self, a7: usize) {
122        self.regs.a7 = a7;
123    }
124
125    /// Gets the instruction pointer.
126    pub const fn ip(&self) -> usize {
127        self.era
128    }
129
130    /// Sets the instruction pointer.
131    pub const fn set_ip(&mut self, pc: usize) {
132        self.era = pc;
133    }
134
135    /// Gets the stack pointer.
136    pub const fn sp(&self) -> usize {
137        self.regs.sp
138    }
139
140    /// Sets the stack pointer.
141    pub const fn set_sp(&mut self, sp: usize) {
142        self.regs.sp = sp;
143    }
144
145    /// Gets the return value register.
146    pub const fn retval(&self) -> usize {
147        self.regs.a0
148    }
149
150    /// Sets the return value register.
151    pub const fn set_retval(&mut self, a0: usize) {
152        self.regs.a0 = a0;
153    }
154
155    /// Sets the return address.
156    pub const fn set_ra(&mut self, ra: usize) {
157        self.regs.ra = ra;
158    }
159
160    /// Gets the TLS area.
161    pub const fn tls(&self) -> usize {
162        self.regs.tp
163    }
164
165    /// Sets the TLS area.
166    pub const fn set_tls(&mut self, tls_area: usize) {
167        self.regs.tp = tls_area;
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.regs.fp as _, self.era as _, self.regs.ra 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    /// Return Address
193    pub ra: usize,
194    /// Stack Pointer
195    pub sp: usize,
196    /// loongArch need to save 10 static registers from $r22 to $r31
197    pub s: [usize; 10],
198    /// Architecture-neutral current-header and kernel-TLS switch state.
199    task_local: TaskLocalState,
200    #[cfg(feature = "fp-simd")]
201    /// Floating Point Unit states
202    pub fpu: FpuState,
203}
204
205// The naked switch uses one machine-word load/store for each field. Keep the
206// array packing and TLS representation assumptions checked by the compiler.
207#[cfg(feature = "context")]
208const _: () = {
209    assert!(size_of::<KernelTlsBase>() == size_of::<usize>());
210    assert!(align_of::<KernelTlsBase>() == align_of::<usize>());
211    assert!(offset_of!(TaskContext, ra) == 0);
212    assert!(offset_of!(TaskContext, sp) == offset_of!(TaskContext, ra) + size_of::<usize>());
213    assert!(size_of::<[usize; 10]>() == 10 * size_of::<usize>());
214    assert!(
215        offset_of!(TaskContext, task_local)
216            == offset_of!(TaskContext, s) + size_of::<[usize; 10]>()
217    );
218};
219
220#[cfg(feature = "context")]
221impl TaskContext {
222    /// Creates a new default context for a new task.
223    pub fn new() -> Self {
224        Self::default()
225    }
226
227    /// Initializes a task context with its entry point, kernel stack, and
228    /// task-owned kernel TLS base.
229    pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, kernel_tls: KernelTlsBase) {
230        self.sp = kstack_top.as_usize();
231        self.ra = entry;
232        self.task_local.set_kernel_tls(kernel_tls);
233    }
234
235    /// Sets the pinned task-owned runtime task anchor.
236    pub fn set_task_anchor(&mut self, header: TaskAnchor) {
237        self.task_local.set_task_anchor(header);
238    }
239
240    /// Returns the configured task-owned runtime task anchor.
241    pub const fn task_anchor(&self) -> Option<TaskAnchor> {
242        self.task_local.task_anchor()
243    }
244
245    /// Saves the running parent's hardware FP image into an unpublished child.
246    ///
247    /// The runtime must pin the parent CPU while calling this method. Kernel
248    /// code uses the soft-float ABI; eager task switching keeps the parent's
249    /// user register image live across kernel entry and preemption.
250    #[cfg(all(feature = "fp-simd", feature = "uspace"))]
251    pub fn clone_user_fp_state_into(&self, child: &mut Self) {
252        assert!(
253            self.task_anchor().is_some(),
254            "FP clone parent must be bound"
255        );
256        assert!(
257            child.task_anchor().is_none(),
258            "FP clone child must be unpublished"
259        );
260        child.fpu.save();
261    }
262
263    /// Completes FPU work before current-context publication.
264    pub fn prepare_switch_to(&mut self, _next_ctx: &Self) {
265        #[cfg(feature = "fp-simd")]
266        {
267            self.fpu.save();
268            _next_ctx.fpu.restore();
269        }
270    }
271
272    /// Performs only the final GPR/current/TLS transfer.
273    ///
274    /// # Safety
275    ///
276    /// Scheduling must be serialized, FPU state prepared, and the next current
277    /// anchor published. Both contexts and their anchors must remain pinned and
278    /// alive. IRQs must remain disabled from publication through this call,
279    /// with no intervening fallible or ownership-sensitive work.
280    #[inline(always)]
281    pub unsafe fn switch_to(&mut self, next_ctx: &Self) {
282        unsafe { context_switch_raw(self, next_ctx) }
283    }
284}
285
286#[cfg(kernel_tls)]
287#[unsafe(naked)]
288#[cfg(feature = "context")]
289unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
290    naked_asm!(
291        include_asm_macros!(),
292        "
293        // save old context (callee-saved registers)
294        st.d    $ra, $a0, {ra_offset}
295        st.d    $sp, $a0, {sp_offset}
296        st.d    $s0, $a0, {s0_offset}
297        st.d    $s1, $a0, {s1_offset}
298        st.d    $s2, $a0, {s2_offset}
299        st.d    $s3, $a0, {s3_offset}
300        st.d    $s4, $a0, {s4_offset}
301        st.d    $s5, $a0, {s5_offset}
302        st.d    $s6, $a0, {s6_offset}
303        st.d    $s7, $a0, {s7_offset}
304        st.d    $s8, $a0, {s8_offset}
305        st.d    $fp, $a0, {frame_pointer_offset}
306        // Keep task TLS inside the final, IRQ-disabled context-switch
307        // boundary. In particular, never add the CPU-owned $r21 here.
308        st.d    $tp, $a0, {kernel_tls_offset}
309
310        // restore new context
311        ld.d    $fp, $a1, {frame_pointer_offset}
312        ld.d    $s8, $a1, {s8_offset}
313        ld.d    $s7, $a1, {s7_offset}
314        ld.d    $s6, $a1, {s6_offset}
315        ld.d    $s5, $a1, {s5_offset}
316        ld.d    $s4, $a1, {s4_offset}
317        ld.d    $s3, $a1, {s3_offset}
318        ld.d    $s2, $a1, {s2_offset}
319        ld.d    $s1, $a1, {s1_offset}
320        ld.d    $s0, $a1, {s0_offset}
321        ld.d    $sp, $a1, {sp_offset}
322        ld.d    $ra, $a1, {ra_offset}
323        ld.d    $tp, $a1, {kernel_tls_offset}
324
325        ret",
326        ra_offset = const offset_of!(TaskContext, ra),
327        sp_offset = const offset_of!(TaskContext, sp),
328        s0_offset = const offset_of!(TaskContext, s),
329        s1_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 1]>(),
330        s2_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 2]>(),
331        s3_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 3]>(),
332        s4_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 4]>(),
333        s5_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 5]>(),
334        s6_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 6]>(),
335        s7_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 7]>(),
336        s8_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 8]>(),
337        frame_pointer_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 9]>(),
338        kernel_tls_offset = const offset_of!(TaskContext, task_local)
339            + offset_of!(TaskLocalState, kernel_tls),
340    )
341}
342
343#[cfg(not(kernel_tls))]
344#[unsafe(naked)]
345#[cfg(feature = "context")]
346unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
347    naked_asm!(
348        include_asm_macros!(),
349        "
350        // Save old callee state. The CPU-owned r21/KS3 anchor and the
351        // LinuxCurrent task-owned tp value are not generic saved registers.
352        st.d    $ra, $a0, {ra_offset}
353        st.d    $sp, $a0, {sp_offset}
354        st.d    $s0, $a0, {s0_offset}
355        st.d    $s1, $a0, {s1_offset}
356        st.d    $s2, $a0, {s2_offset}
357        st.d    $s3, $a0, {s3_offset}
358        st.d    $s4, $a0, {s4_offset}
359        st.d    $s5, $a0, {s5_offset}
360        st.d    $s6, $a0, {s6_offset}
361        st.d    $s7, $a0, {s7_offset}
362        st.d    $s8, $a0, {s8_offset}
363        st.d    $fp, $a0, {frame_pointer_offset}
364
365        // Restore next state and make tp current immediately before the direct
366        // return. r21 and KS3 continue to identify the physical CPU.
367        ld.d    $fp, $a1, {frame_pointer_offset}
368        ld.d    $s8, $a1, {s8_offset}
369        ld.d    $s7, $a1, {s7_offset}
370        ld.d    $s6, $a1, {s6_offset}
371        ld.d    $s5, $a1, {s5_offset}
372        ld.d    $s4, $a1, {s4_offset}
373        ld.d    $s3, $a1, {s3_offset}
374        ld.d    $s2, $a1, {s2_offset}
375        ld.d    $s1, $a1, {s1_offset}
376        ld.d    $s0, $a1, {s0_offset}
377        ld.d    $sp, $a1, {sp_offset}
378        ld.d    $ra, $a1, {ra_offset}
379        ld.d    $tp, $a1, {context_header_offset}
380        ret",
381        ra_offset = const offset_of!(TaskContext, ra),
382        sp_offset = const offset_of!(TaskContext, sp),
383        s0_offset = const offset_of!(TaskContext, s),
384        s1_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 1]>(),
385        s2_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 2]>(),
386        s3_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 3]>(),
387        s4_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 4]>(),
388        s5_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 5]>(),
389        s6_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 6]>(),
390        s7_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 7]>(),
391        s8_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 8]>(),
392        frame_pointer_offset = const offset_of!(TaskContext, s) + size_of::<[usize; 9]>(),
393        context_header_offset = const offset_of!(TaskContext, task_local)
394            + offset_of!(TaskLocalState, context_header),
395    )
396}