Skip to main content

ax_cpu/riscv/
context.rs

1use core::arch::naked_asm;
2
3use ax_memory_addr::VirtAddr;
4use riscv::register::sstatus::{self, FS};
5
6/// General registers of RISC-V.
7#[allow(missing_docs)]
8#[repr(C)]
9#[derive(Debug, Default, Clone, Copy)]
10pub struct GeneralRegisters {
11    pub zero: usize,
12    pub ra: usize,
13    pub sp: usize,
14    pub gp: usize,
15    pub tp: usize,
16    pub t0: usize,
17    pub t1: usize,
18    pub t2: usize,
19    pub s0: usize,
20    pub s1: usize,
21    pub a0: usize,
22    pub a1: usize,
23    pub a2: usize,
24    pub a3: usize,
25    pub a4: usize,
26    pub a5: usize,
27    pub a6: usize,
28    pub a7: usize,
29    pub s2: usize,
30    pub s3: usize,
31    pub s4: usize,
32    pub s5: usize,
33    pub s6: usize,
34    pub s7: usize,
35    pub s8: usize,
36    pub s9: usize,
37    pub s10: usize,
38    pub s11: usize,
39    pub t3: usize,
40    pub t4: usize,
41    pub t5: usize,
42    pub t6: usize,
43}
44
45/// Floating-point registers of RISC-V.
46#[repr(C)]
47#[derive(Debug, Clone, Copy)]
48pub struct FpState {
49    /// the state of the RISC-V Floating-Point Unit (FPU)
50    pub fp: [u64; 32],
51    /// the floating-point control and status register
52    pub fcsr: usize,
53    /// the floating-point status (dirty, clean, off)
54    pub fs: FS,
55}
56
57impl Default for FpState {
58    fn default() -> Self {
59        Self {
60            fs: FS::Initial,
61            fp: [0; 32],
62            fcsr: 0,
63        }
64    }
65}
66
67#[cfg(feature = "fp-simd")]
68impl FpState {
69    /// Restores the floating-point registers from this FP state
70    #[inline]
71    pub fn restore(&self) {
72        unsafe { restore_fp_registers(self) }
73    }
74
75    /// Saves the current floating-point registers to this FP state
76    #[inline]
77    pub fn save(&mut self) {
78        unsafe { save_fp_registers(self) }
79    }
80
81    /// Clears all floating-point registers to zero
82    #[inline]
83    pub fn clear() {
84        unsafe { clear_fp_registers() }
85    }
86
87    /// Handles floating-point state context switching
88    ///
89    /// Saves the current task's FP state (if needed) and restores the next task's FP state
90    pub fn switch_to(&mut self, next_fp_state: &FpState) {
91        // get the real FP state of the current task
92        let current_fs = sstatus::read().fs();
93        // save the current task's FP state
94        if current_fs == FS::Dirty {
95            // we need to save the current task's FP state
96            self.save();
97            // after saving, we set the FP state to clean
98            self.fs = FS::Clean;
99        }
100        // restore the next task's FP state
101        match next_fp_state.fs {
102            FS::Clean => next_fp_state.restore(), /* the next task's FP state is clean, we should restore it */
103            FS::Initial => FpState::clear(),      // restore the FP state as constant values(all 0)
104            FS::Off => {}                         // do nothing
105            FS::Dirty => unreachable!("FP state of the next task should not be dirty"),
106        }
107        unsafe { sstatus::set_fs(next_fp_state.fs) }; // set the FP state to the next task's FP state
108    }
109}
110
111/// Saved registers when a trap (interrupt or exception) occurs.
112#[repr(C)]
113#[derive(Debug, Clone, Copy)]
114pub struct TrapFrame {
115    /// All general registers.
116    pub regs: GeneralRegisters,
117    /// Supervisor Exception Program Counter.
118    pub sepc: usize,
119    /// Supervisor Status Register.
120    pub sstatus: sstatus::Sstatus,
121}
122
123impl Default for TrapFrame {
124    fn default() -> Self {
125        Self {
126            regs: GeneralRegisters::default(),
127            sepc: 0,
128            sstatus: sstatus::Sstatus::from_bits(0),
129        }
130    }
131}
132
133impl TrapFrame {
134    /// Gets the 0th syscall argument.
135    pub const fn arg0(&self) -> usize {
136        self.regs.a0
137    }
138
139    /// Sets the 0th syscall argument.
140    pub const fn set_arg0(&mut self, a0: usize) {
141        self.regs.a0 = a0;
142    }
143
144    /// Gets the 1st syscall argument.
145    pub const fn arg1(&self) -> usize {
146        self.regs.a1
147    }
148
149    /// Sets the 1th syscall argument.
150    pub const fn set_arg1(&mut self, a1: usize) {
151        self.regs.a1 = a1;
152    }
153
154    /// Gets the 2nd syscall argument.
155    pub const fn arg2(&self) -> usize {
156        self.regs.a2
157    }
158
159    /// Sets the 2nd syscall argument.
160    pub const fn set_arg2(&mut self, a2: usize) {
161        self.regs.a2 = a2;
162    }
163
164    /// Gets the 3rd syscall argument.
165    pub const fn arg3(&self) -> usize {
166        self.regs.a3
167    }
168
169    /// Sets the 3rd syscall argument.
170    pub const fn set_arg3(&mut self, a3: usize) {
171        self.regs.a3 = a3;
172    }
173
174    /// Gets the 4th syscall argument.
175    pub const fn arg4(&self) -> usize {
176        self.regs.a4
177    }
178
179    /// Sets the 4th syscall argument.
180    pub const fn set_arg4(&mut self, a4: usize) {
181        self.regs.a4 = a4;
182    }
183
184    /// Gets the 5th syscall argument.
185    pub const fn arg5(&self) -> usize {
186        self.regs.a5
187    }
188
189    /// Sets the 5th syscall argument.
190    pub const fn set_arg5(&mut self, a5: usize) {
191        self.regs.a5 = a5;
192    }
193
194    /// Gets the syscall number.
195    pub const fn sysno(&self) -> usize {
196        self.regs.a7
197    }
198
199    /// Sets the syscall number.
200    pub const fn set_sysno(&mut self, a7: usize) {
201        self.regs.a7 = a7;
202    }
203
204    /// Gets the instruction pointer.
205    pub const fn ip(&self) -> usize {
206        self.sepc
207    }
208
209    /// Sets the instruction pointer.
210    pub const fn set_ip(&mut self, pc: usize) {
211        self.sepc = pc;
212    }
213
214    /// Gets the stack pointer.
215    pub const fn sp(&self) -> usize {
216        self.regs.sp
217    }
218
219    /// Sets the stack pointer.
220    pub const fn set_sp(&mut self, sp: usize) {
221        self.regs.sp = sp;
222    }
223
224    /// Gets the return value register.
225    pub const fn retval(&self) -> usize {
226        self.regs.a0
227    }
228
229    /// Sets the return value register.
230    pub const fn set_retval(&mut self, a0: usize) {
231        self.regs.a0 = a0;
232    }
233
234    /// Sets the return address.
235    pub const fn set_ra(&mut self, ra: usize) {
236        self.regs.ra = ra;
237    }
238
239    /// Gets the TLS area.
240    pub const fn tls(&self) -> usize {
241        self.regs.tp
242    }
243
244    /// Sets the TLS area.
245    pub const fn set_tls(&mut self, tls_area: usize) {
246        self.regs.tp = tls_area;
247    }
248
249    /// Unwind the stack and get the backtrace.
250    pub fn backtrace(&self) -> axbacktrace::Backtrace {
251        axbacktrace::Backtrace::capture_trap(self.regs.s0 as _, self.sepc as _, self.regs.ra as _)
252    }
253}
254
255/// Saved hardware states of a task.
256///
257/// The context usually includes:
258///
259/// - Callee-saved registers
260/// - Stack pointer register
261/// - Thread pointer register (for kernel-space thread-local storage)
262/// - FP/SIMD registers
263///
264/// On context switch, current task saves its context from CPU to memory,
265/// and the next task restores its context from memory to CPU.
266#[allow(missing_docs)]
267#[repr(C)]
268#[derive(Debug, Default)]
269pub struct TaskContext {
270    pub ra: usize, // return address (x1)
271    pub sp: usize, // stack pointer (x2)
272
273    pub s0: usize, // x8-x9
274    pub s1: usize,
275
276    pub s2: usize, // x18-x27
277    pub s3: usize,
278    pub s4: usize,
279    pub s5: usize,
280    pub s6: usize,
281    pub s7: usize,
282    pub s8: usize,
283    pub s9: usize,
284    pub s10: usize,
285    pub s11: usize,
286    /// Thread Pointer
287    pub tp: usize,
288    /// The `satp` register value, i.e., the page table root.
289    #[cfg(feature = "uspace")]
290    pub satp: ax_memory_addr::PhysAddr,
291    #[cfg(feature = "fp-simd")]
292    pub fp_state: FpState,
293}
294
295impl TaskContext {
296    /// Creates a dummy context for a new task.
297    ///
298    /// Note the context is not initialized, it will be filled by [`switch_to`]
299    /// (for initial tasks) and [`init`] (for regular tasks) methods.
300    ///
301    /// [`init`]: TaskContext::init
302    /// [`switch_to`]: TaskContext::switch_to
303    pub fn new() -> Self {
304        Self {
305            #[cfg(feature = "uspace")]
306            satp: crate::asm::read_kernel_page_table(),
307            ..Default::default()
308        }
309    }
310
311    /// Initializes the context for a new task, with the given entry point and
312    /// kernel stack.
313    pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) {
314        self.sp = kstack_top.as_usize();
315        self.ra = entry;
316        self.tp = tls_area.as_usize();
317    }
318
319    /// Changes the page table root in this context.
320    ///
321    /// The hardware register for page table root (`satp` for riscv64) will be
322    /// updated to the next task's after [`Self::switch_to`].
323    #[cfg(feature = "uspace")]
324    pub fn set_page_table_root(&mut self, satp: ax_memory_addr::PhysAddr) {
325        self.satp = satp;
326    }
327
328    /// Switches to another task.
329    ///
330    /// It first saves the current task's context from CPU to this place, and then
331    /// restores the next task's context from `next_ctx` to CPU.
332    pub fn switch_to(&mut self, next_ctx: &Self) {
333        #[cfg(feature = "tls")]
334        {
335            self.tp = crate::asm::read_thread_pointer();
336            unsafe { crate::asm::write_thread_pointer(next_ctx.tp) };
337        }
338        #[cfg(feature = "uspace")]
339        if self.satp != next_ctx.satp {
340            unsafe { crate::asm::write_user_page_table(next_ctx.satp) };
341            crate::asm::flush_tlb(None); // currently flush the entire TLB
342        }
343        #[cfg(feature = "fp-simd")]
344        {
345            self.fp_state.switch_to(&next_ctx.fp_state);
346        }
347
348        unsafe { context_switch(self, next_ctx) }
349    }
350}
351
352#[cfg(feature = "fp-simd")]
353#[unsafe(naked)]
354unsafe extern "C" fn save_fp_registers(fp_state: &mut FpState) {
355    naked_asm!(
356        include_fp_asm_macros!(),
357        "
358        PUSH_FLOAT_REGS a0
359        frcsr t0
360        STR t0, a0, 32
361        ret"
362    )
363}
364
365#[cfg(feature = "fp-simd")]
366#[unsafe(naked)]
367unsafe extern "C" fn restore_fp_registers(fp_state: &FpState) {
368    naked_asm!(
369        include_fp_asm_macros!(),
370        "
371        POP_FLOAT_REGS a0
372        LDR t0, a0, 32
373        fscsr x0, t0
374        ret"
375    )
376}
377
378#[cfg(feature = "fp-simd")]
379#[unsafe(naked)]
380unsafe extern "C" fn clear_fp_registers() {
381    naked_asm!(
382        include_fp_asm_macros!(),
383        "
384        CLEAR_FLOAT_REGS
385        ret"
386    )
387}
388
389#[unsafe(naked)]
390unsafe extern "C" fn context_switch(_current_task: &mut TaskContext, _next_task: &TaskContext) {
391    naked_asm!(
392        include_asm_macros!(),
393        "
394        // save old context (callee-saved registers)
395        STR     ra, a0, 0
396        STR     sp, a0, 1
397        STR     s0, a0, 2
398        STR     s1, a0, 3
399        STR     s2, a0, 4
400        STR     s3, a0, 5
401        STR     s4, a0, 6
402        STR     s5, a0, 7
403        STR     s6, a0, 8
404        STR     s7, a0, 9
405        STR     s8, a0, 10
406        STR     s9, a0, 11
407        STR     s10, a0, 12
408        STR     s11, a0, 13
409
410        // restore new context
411        LDR     s11, a1, 13
412        LDR     s10, a1, 12
413        LDR     s9, a1, 11
414        LDR     s8, a1, 10
415        LDR     s7, a1, 9
416        LDR     s6, a1, 8
417        LDR     s5, a1, 7
418        LDR     s4, a1, 6
419        LDR     s3, a1, 5
420        LDR     s2, a1, 4
421        LDR     s1, a1, 3
422        LDR     s0, a1, 2
423        LDR     sp, a1, 1
424        LDR     ra, a1, 0
425
426        ret",
427    )
428}