Skip to main content

ax_cpu/loongarch64/
context.rs

1use core::arch::naked_asm;
2#[cfg(feature = "fp-simd")]
3use core::mem::offset_of;
4
5use ax_memory_addr::VirtAddr;
6
7/// General registers of Loongarch64.
8#[allow(missing_docs)]
9#[repr(C)]
10#[derive(Debug, Default, Clone, Copy)]
11pub struct GeneralRegisters {
12    pub zero: usize,
13    pub ra: usize,
14    pub tp: usize,
15    pub sp: usize,
16    pub a0: usize,
17    pub a1: usize,
18    pub a2: usize,
19    pub a3: usize,
20    pub a4: usize,
21    pub a5: usize,
22    pub a6: usize,
23    pub a7: usize,
24    pub t0: usize,
25    pub t1: usize,
26    pub t2: usize,
27    pub t3: usize,
28    pub t4: usize,
29    pub t5: usize,
30    pub t6: usize,
31    pub t7: usize,
32    pub t8: usize,
33    pub u0: usize,
34    pub fp: usize,
35    pub s0: usize,
36    pub s1: usize,
37    pub s2: usize,
38    pub s3: usize,
39    pub s4: usize,
40    pub s5: usize,
41    pub s6: usize,
42    pub s7: usize,
43    pub s8: usize,
44}
45
46/// Floating-point registers of LoongArch64
47#[repr(C)]
48#[derive(Debug, Default, Clone, Copy)]
49pub struct FpuState {
50    /// Floating-point registers (f0-f31)
51    pub fp: [u64; 32],
52    /// Floating-point Condition Code register
53    pub fcc: [u8; 8],
54    /// Floating-point Control and Status register
55    pub fcsr: u32,
56}
57
58#[cfg(feature = "fp-simd")]
59impl FpuState {
60    /// Save the current FPU states from CPU to this structure.
61    #[inline]
62    pub fn save(&mut self) {
63        unsafe { save_fp_registers(self) }
64    }
65
66    /// Restore FPU states from this structure to CPU.
67    #[inline]
68    pub fn restore(&self) {
69        unsafe { restore_fp_registers(self) }
70    }
71}
72
73/// Saved registers when a trap (interrupt or exception) occurs.
74#[repr(C)]
75#[derive(Debug, Default, Clone, Copy)]
76pub struct TrapFrame {
77    /// All general registers.
78    pub regs: GeneralRegisters,
79    /// Pre-exception Mode Information
80    pub prmd: usize,
81    /// Exception Return Address
82    pub era: usize,
83}
84
85impl TrapFrame {
86    /// Gets the 0th syscall argument.
87    pub const fn arg0(&self) -> usize {
88        self.regs.a0
89    }
90
91    /// Sets the 0th syscall argument.
92    pub const fn set_arg0(&mut self, a0: usize) {
93        self.regs.a0 = a0;
94    }
95
96    /// Gets the 1st syscall argument.
97    pub const fn arg1(&self) -> usize {
98        self.regs.a1
99    }
100
101    /// Sets the 1st syscall argument.
102    pub const fn set_arg1(&mut self, a1: usize) {
103        self.regs.a1 = a1;
104    }
105
106    /// Gets the 2nd syscall argument.
107    pub const fn arg2(&self) -> usize {
108        self.regs.a2
109    }
110
111    /// Sets the 2nd syscall argument.
112    pub const fn set_arg2(&mut self, a2: usize) {
113        self.regs.a2 = a2;
114    }
115
116    /// Gets the 3rd syscall argument.
117    pub const fn arg3(&self) -> usize {
118        self.regs.a3
119    }
120
121    /// Sets the 3rd syscall argument.
122    pub const fn set_arg3(&mut self, a3: usize) {
123        self.regs.a3 = a3;
124    }
125
126    /// Gets the 4th syscall argument.
127    pub const fn arg4(&self) -> usize {
128        self.regs.a4
129    }
130
131    /// Sets the 4th syscall argument.
132    pub const fn set_arg4(&mut self, a4: usize) {
133        self.regs.a4 = a4;
134    }
135
136    /// Gets the 5th syscall argument.
137    pub const fn arg5(&self) -> usize {
138        self.regs.a5
139    }
140
141    /// Sets the 5th syscall argument.
142    pub const fn set_arg5(&mut self, a5: usize) {
143        self.regs.a5 = a5;
144    }
145
146    /// Get the syscall number.
147    pub const fn sysno(&self) -> usize {
148        self.regs.a7
149    }
150
151    /// Sets the syscall number.
152    pub const fn set_sysno(&mut self, a7: usize) {
153        self.regs.a7 = a7;
154    }
155
156    /// Gets the instruction pointer.
157    pub const fn ip(&self) -> usize {
158        self.era
159    }
160
161    /// Sets the instruction pointer.
162    pub const fn set_ip(&mut self, pc: usize) {
163        self.era = pc;
164    }
165
166    /// Gets the stack pointer.
167    pub const fn sp(&self) -> usize {
168        self.regs.sp
169    }
170
171    /// Sets the stack pointer.
172    pub const fn set_sp(&mut self, sp: usize) {
173        self.regs.sp = sp;
174    }
175
176    /// Gets the return value register.
177    pub const fn retval(&self) -> usize {
178        self.regs.a0
179    }
180
181    /// Sets the return value register.
182    pub const fn set_retval(&mut self, a0: usize) {
183        self.regs.a0 = a0;
184    }
185
186    /// Sets the return address.
187    pub const fn set_ra(&mut self, ra: usize) {
188        self.regs.ra = ra;
189    }
190
191    /// Gets the TLS area.
192    pub const fn tls(&self) -> usize {
193        self.regs.tp
194    }
195
196    /// Sets the TLS area.
197    pub const fn set_tls(&mut self, tls_area: usize) {
198        self.regs.tp = tls_area;
199    }
200
201    /// Unwind the stack and get the backtrace.
202    pub fn backtrace(&self) -> axbacktrace::Backtrace {
203        axbacktrace::Backtrace::capture_trap(self.regs.fp as _, self.era as _, self.regs.ra as _)
204    }
205}
206
207/// Saved hardware states of a task.
208///
209/// The context usually includes:
210///
211/// - Callee-saved registers
212/// - Stack pointer register
213/// - Thread pointer register (for kernel-space thread-local storage)
214/// - FP/SIMD registers
215///
216/// On context switch, current task saves its context from CPU to memory,
217/// and the next task restores its context from memory to CPU.
218#[allow(missing_docs)]
219#[repr(C)]
220#[derive(Debug, Default)]
221pub struct TaskContext {
222    /// Return Address
223    pub ra: usize,
224    /// Stack Pointer
225    pub sp: usize,
226    /// loongArch need to save 10 static registers from $r22 to $r31
227    pub s: [usize; 10],
228    /// Thread Pointer
229    pub tp: usize,
230    #[cfg(feature = "uspace")]
231    /// user page table root
232    pub pgdl: usize,
233    #[cfg(feature = "fp-simd")]
234    /// Floating Point Unit states
235    pub fpu: FpuState,
236}
237
238impl TaskContext {
239    /// Creates a new default context for a new task.
240    pub fn new() -> Self {
241        Self::default()
242    }
243
244    /// Initializes the context for a new task, with the given entry point and
245    /// kernel stack.
246    pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) {
247        self.sp = kstack_top.as_usize();
248        self.ra = entry;
249        self.tp = tls_area.as_usize();
250    }
251
252    /// Changes the page table root in this context.
253    ///
254    /// The hardware register for user page table root (`pgdl` for loongarch64)
255    /// will be updated to the next task's after [`Self::switch_to`].
256    #[cfg(feature = "uspace")]
257    pub fn set_page_table_root(&mut self, pgdl: ax_memory_addr::PhysAddr) {
258        self.pgdl = pgdl.as_usize();
259    }
260
261    /// Switches to another task.
262    ///
263    /// It first saves the current task's context from CPU to this place, and then
264    /// restores the next task's context from `next_ctx` to CPU.
265    pub fn switch_to(&mut self, next_ctx: &Self) {
266        #[cfg(feature = "tls")]
267        {
268            self.tp = crate::asm::read_thread_pointer();
269            unsafe { crate::asm::write_thread_pointer(next_ctx.tp) };
270        }
271        #[cfg(feature = "uspace")]
272        {
273            if self.pgdl != next_ctx.pgdl {
274                unsafe { crate::asm::write_user_page_table(pa!(next_ctx.pgdl)) };
275                crate::asm::flush_tlb(None); // currently flush the entire TLB
276            }
277        }
278        #[cfg(feature = "fp-simd")]
279        {
280            self.fpu.save();
281            next_ctx.fpu.restore();
282        }
283        unsafe { context_switch(self, next_ctx) }
284    }
285}
286
287#[cfg(feature = "fp-simd")]
288#[unsafe(naked)]
289unsafe extern "C" fn save_fp_registers(fpu: &mut FpuState) {
290    naked_asm!(
291        include_fp_asm_macros!(),
292        "
293        SAVE_FP $a0
294        addi.d $t8, $a0, {fcc_offset}
295        SAVE_FCC $t8
296        addi.d $t8, $a0, {fcsr_offset}
297        SAVE_FCSR $t8
298        ret",
299        fcc_offset = const offset_of!(FpuState, fcc),
300        fcsr_offset = const offset_of!(FpuState, fcsr),
301    )
302}
303
304#[cfg(feature = "fp-simd")]
305#[unsafe(naked)]
306unsafe extern "C" fn restore_fp_registers(fpu: &FpuState) {
307    naked_asm!(
308        include_fp_asm_macros!(),
309        "
310        RESTORE_FP $a0
311        addi.d $t8, $a0, {fcc_offset}
312        RESTORE_FCC $t8
313        addi.d $t8, $a0, {fcsr_offset}
314        RESTORE_FCSR $t8
315        ret",
316        fcc_offset = const offset_of!(FpuState, fcc),
317        fcsr_offset = const offset_of!(FpuState, fcsr),
318    )
319}
320
321#[unsafe(naked)]
322unsafe extern "C" fn context_switch(_current_task: &mut TaskContext, _next_task: &TaskContext) {
323    naked_asm!(
324        include_asm_macros!(),
325        "
326        // save old context (callee-saved registers)
327        STD     $ra, $a0, 0
328        STD     $sp, $a0, 1
329        STD     $s0, $a0, 2
330        STD     $s1, $a0, 3
331        STD     $s2, $a0, 4
332        STD     $s3, $a0, 5
333        STD     $s4, $a0, 6
334        STD     $s5, $a0, 7
335        STD     $s6, $a0, 8
336        STD     $s7, $a0, 9
337        STD     $s8, $a0, 10
338        STD     $fp, $a0, 11
339
340        // restore new context
341        LDD     $fp, $a1, 11
342        LDD     $s8, $a1, 10
343        LDD     $s7, $a1, 9
344        LDD     $s6, $a1, 8
345        LDD     $s5, $a1, 7
346        LDD     $s4, $a1, 6
347        LDD     $s3, $a1, 5
348        LDD     $s2, $a1, 4
349        LDD     $s1, $a1, 3
350        LDD     $s0, $a1, 2
351        LDD     $sp, $a1, 1
352        LDD     $ra, $a1, 0
353
354        ret",
355    )
356}