|
1 | 1 | # HAL: Context Switching for RISC-V
|
2 | 2 |
|
3 | 3 | ## Context Switching
|
4 |
| - |
5 |
| -Context switching is essential to Linmo's preemptive multitasking kernel, facilitating smooth task transitions. In Linmo, this is managed through `setjmp` and `longjmp` functions, implemented in [arch/riscv/hal.c](../arch/riscv/hal.c) and declared in [arch/riscv/hal.h](../arch/riscv/hal.h). These functions enable the kernel to save and restore task states, supporting reliable multitasking. The process involves unique considerations due to the non-standard use of these functions, requiring careful handling to ensure system stability. |
6 |
| - |
7 |
| -### Repurposed setjmp and longjmp |
8 |
| - |
9 |
| -The `setjmp` and `longjmp` functions, typically used for exception handling, are repurposed in Linmo for context switching. They save additional states beyond standard registers, including CSRs like `mcause`, `mepc`, and `mstatus`, which may lead to unexpected behavior if not properly managed. During timer interrupts, `setjmp` must handle `mstatus.MIE` being cleared, relying on `MPIE` reconstruction, a departure from their usual role. Similarly, `longjmp` restores context without reinitializing local variables or the call stack, posing risks of resource leaks in tasks with dynamic memory. These deviations demand precise implementation to maintain task switching integrity. |
10 |
| - |
11 |
| -### Context Switch Process |
12 |
| - |
13 |
| -1. Save Current Task State: The `setjmp` function captures the current task's CPU state, storing it in a `jmp_buf` structure defined as `uint32_t jmp_buf[19]` in [arch/riscv/hal.h](../arch/riscv/hal.h). This includes callee-saved general-purpose registers (such as `s0` to `s11`), essential pointers (`gp`, `tp`, `sp`, `ra`), and CSRs (`mcause`, `mepc`, `mstatus`). The layout is defined by `CONTEXT_*` macros in [arch/riscv/hal.c](../arch/riscv/hal.c). Failing to reconstruct `mstatus` from `MPIE` during this step can cause incorrect interrupt settings, leading to missed timer interrupts or stalls, which is mitigated by ensuring proper `mstatus` reconstruction. |
14 |
| - |
15 |
| -2. Select Next Task: The scheduler, invoked via `dispatcher()` during a machine timer interrupt, selects the next task using a priority-based round-robin algorithm or a user-defined scheduler. This evaluates task priorities and readiness, ensuring system responsiveness. The process relies on accurate `mstatus` restoration to avoid timing issues. |
16 |
| - |
17 |
| -3. Restore Next Task State: The `longjmp` function restores the CPU state from the selected task’s `jmp_buf`, resuming execution where `setjmp` was called. For new tasks, `hal_context_init` initializes the `jmp_buf` with `sp`, `ra`, and `mstatus` set to `MSTATUS_MIE` and `MSTATUS_MPP_MACH`, while `hal_dispatch_init` launches the task. The `hal_interrupt_tick` function enables interrupts (`_ei()`) after the first task, ensuring a consistent environment. Premature restoration of `mstatus` can disrupt scheduling, addressed by prioritizing it before other registers. |
18 |
| - |
19 |
| -## Machine Status Management |
20 |
| - |
21 |
| -The machine status in RISC-V is managed through the `mstatus` CSR, which controls critical system states, such as the Machine Interrupt Enable (`MIE`) bit. Proper handling of `mstatus` during context switching is essential to maintain correct interrupt behavior and ensure tasks execute as expected. |
22 |
| - |
23 |
| -### Role of `mstatus` |
24 |
| - |
25 |
| -The `mstatus` register includes the `MIE` bit (bit 3), which enables or disables machine-mode interrupts globally, and the `MPIE` bit (bit 7), which preserves the previous interrupt enable state during a trap, allowing `MIE` to be reconstructed afterward. Other fields, such as those for privilege mode and memory protection, are less relevant in Linmo’s single-address-space model but must still be preserved. The `hal_interrupt_set` function in [arch/riscv/hal.h](../arch/riscv/hal.h) manipulates `MIE` using `read_csr(mstatus)` and `write_csr(mstatus, val)`, with convenience macros `_di()` (disable interrupts) and `_ei()` (enable interrupts). |
26 |
| - |
27 |
| -### Saving and Restoring `mstatus` |
28 |
| - |
29 |
| -1. Saving in `setjmp`: The `setjmp` function reads `mstatus` using the `csrr` instruction. Since `mstatus.MIE` is cleared during a trap, the previous interrupt state is reconstructed from `MPIE` by shifting bit 7 to bit 3, clearing the original `MIE`, and restoring it, then storing the result in `jmp_buf` at `CONTEXT_MSTATUS` (18). This ensures accurate interrupt context preservation, preventing issues from incorrect handling. |
30 |
| - |
31 |
| -2. Restoring in `longjmp`: The `longjmp` function loads `mstatus` from `jmp_buf` and writes it back with `csrw` before other registers, ensuring early interrupt state establishment. This prevents premature interrupt handling and maintains consistency. |
32 |
| - |
33 |
| -3. Timing and drift considerations: Incorrect `mstatus` restoration can disrupt scheduling by enabling interrupts at the wrong time, resolvable through testing with scheduler-stressing applications like message queues. Timer interrupt drift, if not handled carefully, is addressed by `do_trap` scheduling relative to the previous `mtimecmp` value, requiring `mstatus.MIE` to be enabled for effective reception. |
34 |
| - |
35 |
| -### Best Practices for Machine Status |
36 |
| - |
37 |
| -- Prioritize `mstatus` restoration: Restore `mstatus` before other registers in `longjmp` to establish the correct interrupt state early. |
38 |
| - |
39 |
| -- Use safe CSR access: Use `read_csr` and `write_csr` macros in [arch/riscv/hal.h](../arch/riscv/hal.h) for consistent CSR manipulation. |
40 |
| - |
41 |
| -- Initialize `mstatus` for new tasks: Set `MSTATUS_MIE` and `MSTATUS_MPP_MACH` in `hal_context_init` to ensure new tasks start with interrupts enabled. |
| 4 | +Context switching is essential to Linmo's preemptive multitasking kernel, |
| 5 | +facilitating smooth task transitions. |
| 6 | +In Linmo, context switching is implemented through a clean separation of concerns architecture that combines the portability of standard C library functions with the performance requirements of real-time systems. |
| 7 | +This approach provides both `setjmp` and `longjmp` functions following standard C library semantics for application use, |
| 8 | +and dedicated HAL routines (`hal_context_save` and `hal_context_restore`) for optimized kernel scheduling. |
| 9 | + |
| 10 | +## Separation of Concerns Architecture |
| 11 | +The context switching implementation follows a clean layered approach that separates execution context management from processor state management: |
| 12 | + |
| 13 | +### Standard C Library Layer |
| 14 | +Portable, standards-compliant context switching for applications |
| 15 | +- `setjmp` - Saves execution context only (elements 0-15) |
| 16 | +- `longjmp` - Restores execution context only |
| 17 | +- Semantics: Pure C library behavior, no processor state management |
| 18 | +- Use Cases: Exception handling, coroutines, application-level control flow |
| 19 | +- Performance: Standard overhead, optimized for portability |
| 20 | + |
| 21 | +### HAL Context Switching Layer |
| 22 | +Context switching for kernel scheduling |
| 23 | +- `hal_context_save` - Saves execution context AND processor state |
| 24 | +- `hal_context_restore` - Restores complete task state |
| 25 | +- Semantics: System-level optimization with interrupt state management |
| 26 | +- Use Cases: Preemptive scheduling, cooperative task switching |
| 27 | +- Performance: Optimized for minimal overhead |
| 28 | + |
| 29 | +### Unified Context Buffer |
| 30 | +Both layers use the same `jmp_buf` structure but access different portions: |
| 31 | + |
| 32 | +```c |
| 33 | +typedef uint32_t jmp_buf[17]; |
| 34 | + |
| 35 | +/* Layout: |
| 36 | + * [0-11]: s0-s11 (callee-saved registers) - both layers |
| 37 | + * [12]: gp (global pointer) - both layers |
| 38 | + * [13]: tp (thread pointer) - both layers |
| 39 | + * [14]: sp (stack pointer) - both layers |
| 40 | + * [15]: ra (return address) - both layers |
| 41 | + * [16]: mstatus (processor state) - HAL layer only |
| 42 | + */ |
| 43 | +``` |
| 44 | + |
| 45 | +## Context Switch Process |
| 46 | + |
| 47 | +### 1. Save Current Task State |
| 48 | +The `hal_context_save` function captures complete task state including both execution context and processor state. |
| 49 | +The function saves all callee-saved registers as required by the RISC-V ABI, |
| 50 | +plus essential pointers (gp, tp, sp, ra). |
| 51 | +For processor state, it performs sophisticated interrupt state reconstruction: |
| 52 | + |
| 53 | +```c |
| 54 | +/* mstatus reconstruction during timer interrupts */ |
| 55 | +csrr t0, mstatus // Read current mstatus (MIE=0 in trap) |
| 56 | +srli t1, t0, 4 // Shift MPIE (bit 7) to bit 3 position |
| 57 | +andi t1, t1, 8 // Isolate the reconstructed MIE bit |
| 58 | +li t2, ~8 // Create mask to clear old MIE bit |
| 59 | +and t0, t0, t2 // Clear the current MIE bit |
| 60 | +or t0, t0, t1 // Set MIE to pre-trap value (from MPIE) |
| 61 | +sw t0, 16*4(%0) // Store in jmp_buf[16] |
| 62 | +``` |
| 63 | + |
| 64 | +This ensures that tasks resume with correct interrupt state, |
| 65 | +maintaining system responsiveness and preventing interrupt state corruption. |
| 66 | + |
| 67 | +### 2. Select Next Task |
| 68 | +The scheduler, invoked via `dispatcher()` during machine timer interrupts, |
| 69 | +uses a priority-based round-robin algorithm or user-defined scheduler to select the next ready task. |
| 70 | +The scheduling logic evaluates task priorities and readiness states to ensure optimal system responsiveness. |
| 71 | + |
| 72 | +### 3. Restore Next Task State |
| 73 | +The `hal_context_restore` function performs complete state restoration with processor state restored first to establish correct execution environment: |
| 74 | + |
| 75 | +```c |
| 76 | +lw t0, 16*4(%0) // Load saved mstatus from jmp_buf[16] |
| 77 | +csrw mstatus, t0 // Restore processor state FIRST |
| 78 | +// ... then restore all execution context registers |
| 79 | +``` |
| 80 | + |
| 81 | +This ordering ensures that interrupt state and privilege mode are correctly established before resuming task execution. |
| 82 | + |
| 83 | +## Processor State Management |
| 84 | + |
| 85 | +### Interrupt State Reconstruction |
| 86 | +The HAL context switching routines include sophisticated interrupt state management that handles the complexities of RISC-V interrupt processing: |
| 87 | + |
| 88 | +During Timer Interrupts: |
| 89 | +- `mstatus.MIE` is automatically cleared by hardware when entering the trap |
| 90 | +- `mstatus.MPIE` preserves the previous interrupt enable state |
| 91 | +- HAL functions reconstruct the original interrupt state from `MPIE` |
| 92 | +- This ensures consistent interrupt behavior across context switches |
| 93 | + |
| 94 | +State Preservation: |
| 95 | +- Each task maintains its own interrupt enable state |
| 96 | +- Context switches preserve privilege mode (Machine mode for kernel tasks) |
| 97 | +- Interrupt state is reconstructed accurately for reliable task resumption |
| 98 | + |
| 99 | +### Task Initialization |
| 100 | +New tasks are initialized with proper processor state: |
| 101 | + |
| 102 | +```c |
| 103 | +void hal_context_init(jmp_buf *ctx, size_t sp, size_t ss, size_t ra) |
| 104 | +{ |
| 105 | + /* Set execution context */ |
| 106 | + (*ctx)[CONTEXT_SP] = (uint32_t) stack_top; // Stack pointer |
| 107 | + (*ctx)[CONTEXT_RA] = (uint32_t) ra; // Entry point |
| 108 | + /* Set processor state */ |
| 109 | + (*ctx)[CONTEXT_MSTATUS] = MSTATUS_MIE | MSTATUS_MPP_MACH; |
| 110 | +} |
| 111 | +``` |
| 112 | +
|
| 113 | +This ensures new tasks start with interrupts enabled in machine mode. |
| 114 | +
|
| 115 | +## Implementation Details |
| 116 | +
|
| 117 | +### Kernel Integration |
| 118 | +The kernel scheduler uses the context switching routine: |
| 119 | +
|
| 120 | +```c |
| 121 | +/* Preemptive context switching */ |
| 122 | +void dispatch(void) |
| 123 | +{ |
| 124 | + /* Save current task with processor state */ |
| 125 | + if (hal_context_save(current_task->context) != 0) |
| 126 | + return; /* Restored from context switch */ |
| 127 | + |
| 128 | + /* ... scheduling logic ... */ |
| 129 | +
|
| 130 | + /* Restore next task with processor state*/ |
| 131 | + hal_context_restore(next_task->context, 1); |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +## Best Practices |
| 136 | + |
| 137 | +### Architecture Principles |
| 138 | +- Layer Separation: Keep application and system contexts separate |
| 139 | +- Standard Compliance: Use standard functions for portable code |
| 140 | +- Performance Optimization: Use HAL functions for time-critical system code |
| 141 | +- State Management: Let HAL functions handle processor state automatically |
| 142 | + |
| 143 | +### Development Guidelines |
| 144 | +- Application Code: Always use `setjmp` and `longjmp` for exception handling |
| 145 | +- System Code: Always use `hal_context_save` and `hal_context_restore` for scheduling |
| 146 | +- Mixed Use: Both can operate on the same `jmp_buf` without interference |
| 147 | +- Testing: Verify interrupt state preservation across context switches |
| 148 | + |
| 149 | +### Interrupt State Machinery |
| 150 | +RISC-V Interrupt Behavior During Traps: |
| 151 | +1. Hardware automatically clears `mstatus.MIE` on trap entry |
| 152 | +2. Previous interrupt state saved in `mstatus.MPIE` |
| 153 | +3. Privilege level preserved in `mstatus.MPP` |
| 154 | +4. HAL functions reconstruct original interrupt state for task resumption |
| 155 | + |
| 156 | +State Reconstruction Logic: |
| 157 | +``` |
| 158 | +Original MIE state = Current MPIE bit |
| 159 | +Reconstructed mstatus = (current_mstatus & ~MIE) | (MPIE >> 4) |
| 160 | +``` |
| 161 | + |
| 162 | +This ensures tasks resume with their original interrupt enable state rather than the disabled state from trap entry. |
| 163 | + |
| 164 | +### Task State Lifecycle |
| 165 | +New Task Creation: |
| 166 | +1. `hal_context_init` sets up initial execution context |
| 167 | +2. Stack pointer positioned with ISR frame reservation |
| 168 | +3. Return address points to task entry function |
| 169 | +4. Processor state initialized with interrupts enabled |
| 170 | + |
| 171 | +First Task Launch: |
| 172 | +1. `hal_dispatch_init` transfers control from kernel to first task |
| 173 | +2. Global interrupts enabled just before task execution |
| 174 | +3. Timer interrupts activated for preemptive scheduling |
| 175 | +4. Task begins execution at its entry point |
| 176 | + |
| 177 | +Context Switch Cycle: |
| 178 | +1. Timer interrupt triggers scheduler entry |
| 179 | +2. `hal_context_save` preserves complete current task state |
| 180 | +3. Scheduler selects next ready task based on priority |
| 181 | +4. `hal_context_restore` resumes selected task execution |
| 182 | +5. Task continues from its previous suspension point |
0 commit comments