Skip to content

Commit 216c582

Browse files
committed
Separate concerns for context switching
setjmp and longjmp are now refined for execution context only. jmp_buf is changed from 19 to 17 elements, and this commit preserves mstatus per task while maintaining interrupt state consistency.
1 parent 0516bf7 commit 216c582

File tree

4 files changed

+316
-97
lines changed

4 files changed

+316
-97
lines changed
Lines changed: 179 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,182 @@
11
# HAL: Context Switching for RISC-V
22

33
## 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

Comments
 (0)