Skip to content

Commit 0b74337

Browse files
authored
Merge pull request #51 from sysprog21/rtsched
Implement EDF scheduler with ecall-based context switch
2 parents b72adae + 424616f commit 0b74337

File tree

10 files changed

+1013
-184
lines changed

10 files changed

+1013
-184
lines changed

app/rtsched.c

Lines changed: 446 additions & 99 deletions
Large diffs are not rendered by default.

arch/riscv/boot.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,19 @@ __attribute__((naked, aligned(4))) void _isr(void)
156156
/* Save trap-related CSRs and prepare arguments for do_trap */
157157
"csrr a0, mcause\n" /* Arg 1: cause */
158158
"csrr a1, mepc\n" /* Arg 2: epc */
159+
"mv a2, sp\n" /* Arg 3: isr_sp (current stack frame) */
159160
"sw a0, 30*4(sp)\n"
160161
"sw a1, 31*4(sp)\n"
161162

162-
/* Call the high-level C trap handler */
163+
/* Call the high-level C trap handler.
164+
* Returns: a0 = SP to use for restoring context (may be different
165+
* task's stack if context switch occurred).
166+
*/
163167
"call do_trap\n"
164168

169+
/* Use returned SP for context restore (enables context switching) */
170+
"mv sp, a0\n"
171+
165172
/* Restore context. mepc might have been modified by the handler */
166173
"lw a1, 31*4(sp)\n"
167174
"csrw mepc, a1\n"

arch/riscv/hal.c

Lines changed: 222 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@
4242
*/
4343
#define ISR_STACK_FRAME_SIZE 128
4444

45+
/* Global variable to hold the new stack pointer for pending context switch.
46+
* When a context switch is needed, hal_switch_stack() saves the current SP
47+
* and stores the new SP here. The ISR epilogue then uses this value.
48+
* NULL means no context switch is pending, use current SP.
49+
*/
50+
static void *pending_switch_sp = NULL;
51+
52+
/* Global variable to hold the ISR frame SP for the current trap.
53+
* Set at the start of do_trap() so hal_switch_stack() can save the correct
54+
* SP to the previous task (the ISR frame SP, not the current function's SP).
55+
*/
56+
static uint32_t current_isr_frame_sp = 0;
57+
4558
/* NS16550A UART0 - Memory-mapped registers for the QEMU 'virt' machine's serial
4659
* port.
4760
*/
@@ -248,31 +261,48 @@ void hal_cpu_idle(void)
248261

249262
/* Interrupt and Trap Handling */
250263

264+
/* Direct UART output for trap context (avoids printf deadlock) */
265+
extern int _putchar(int c);
266+
static void trap_puts(const char *s)
267+
{
268+
while (*s)
269+
_putchar(*s++);
270+
}
271+
272+
/* Exception message table per RISC-V Privileged Spec */
273+
static const char *exc_msg[] = {
274+
[0] = "Instruction address misaligned",
275+
[1] = "Instruction access fault",
276+
[2] = "Illegal instruction",
277+
[3] = "Breakpoint",
278+
[4] = "Load address misaligned",
279+
[5] = "Load access fault",
280+
[6] = "Store/AMO address misaligned",
281+
[7] = "Store/AMO access fault",
282+
[8] = "Environment call from U-mode",
283+
[9] = "Environment call from S-mode",
284+
[10] = "Reserved",
285+
[11] = "Environment call from M-mode",
286+
[12] = "Instruction page fault",
287+
[13] = "Load page fault",
288+
[14] = "Reserved",
289+
[15] = "Store/AMO page fault",
290+
};
291+
251292
/* C-level trap handler, called by the '_isr' assembly routine.
252293
* @cause : The value of the 'mcause' CSR, indicating the reason for the trap.
253294
* @epc : The value of the 'mepc' CSR, the PC at the time of the trap.
295+
* @isr_sp: The stack pointer pointing to the ISR frame.
296+
*
297+
* Returns The SP to use for restoring context (same or new task's frame).
254298
*/
255-
void do_trap(uint32_t cause, uint32_t epc)
299+
uint32_t do_trap(uint32_t cause, uint32_t epc, uint32_t isr_sp)
256300
{
257-
static const char *exc_msg[] = {
258-
/* For printing helpful debug messages */
259-
[0] = "Instruction address misaligned",
260-
[1] = "Instruction access fault",
261-
[2] = "Illegal instruction",
262-
[3] = "Breakpoint",
263-
[4] = "Load address misaligned",
264-
[5] = "Load access fault",
265-
[6] = "Store/AMO address misaligned",
266-
[7] = "Store/AMO access fault",
267-
[8] = "Environment call from U-mode",
268-
[9] = "Environment call from S-mode",
269-
[10] = "Reserved",
270-
[11] = "Environment call from M-mode",
271-
[12] = "Instruction page fault",
272-
[13] = "Load page fault",
273-
[14] = "Reserved",
274-
[15] = "Store/AMO page fault",
275-
};
301+
/* Reset pending switch at start of every trap */
302+
pending_switch_sp = NULL;
303+
304+
/* Store ISR frame SP so hal_switch_stack() can save it to prev task */
305+
current_isr_frame_sp = isr_sp;
276306

277307
if (MCAUSE_IS_INTERRUPT(cause)) { /* Asynchronous Interrupt */
278308
uint32_t int_code = MCAUSE_GET_CODE(cause);
@@ -282,28 +312,64 @@ void do_trap(uint32_t cause, uint32_t epc)
282312
* consistent tick frequency even with interrupt latency.
283313
*/
284314
mtimecmp_w(mtimecmp_r() + (F_CPU / F_TIMER));
285-
dispatcher(); /* Invoke the OS scheduler */
315+
/* Invoke scheduler - parameter 1 = from timer, increment ticks */
316+
dispatcher(1);
286317
} else {
287318
/* All other interrupt sources are unexpected and fatal */
288-
printf("[UNHANDLED INTERRUPT] code=%u, cause=%08x, epc=%08x\n",
289-
int_code, cause, epc);
290319
hal_panic();
291320
}
292321
} else { /* Synchronous Exception */
293322
uint32_t code = MCAUSE_GET_CODE(cause);
294-
const char *reason = "Unknown exception";
323+
324+
/* Handle ecall from M-mode - used for yielding in preemptive mode */
325+
if (code == MCAUSE_ECALL_MMODE) {
326+
/* Advance mepc past the ecall instruction (4 bytes) */
327+
uint32_t new_epc = epc + 4;
328+
write_csr(mepc, new_epc);
329+
330+
/* Also update mepc in the ISR frame on the stack!
331+
* The ISR epilogue will restore mepc from the frame (offset 31*4 =
332+
* 124 bytes). If we don't update the frame, mret will jump back to
333+
* the ecall instruction!
334+
*/
335+
uint32_t *isr_frame = (uint32_t *) isr_sp;
336+
isr_frame[31] = new_epc;
337+
338+
/* Invoke dispatcher for context switch - parameter 0 = from ecall,
339+
* don't increment ticks.
340+
*/
341+
dispatcher(0);
342+
343+
/* Return the SP to use - new task's frame or current frame */
344+
return pending_switch_sp ? (uint32_t) pending_switch_sp : isr_sp;
345+
}
346+
347+
/* Print exception info via direct UART (safe in trap context) */
348+
trap_puts("[EXCEPTION] ");
295349
if (code < ARRAY_SIZE(exc_msg) && exc_msg[code])
296-
reason = exc_msg[code];
297-
printf("[EXCEPTION] code=%u (%s), epc=%08x, cause=%08x\n", code, reason,
298-
epc, cause);
350+
trap_puts(exc_msg[code]);
351+
else
352+
trap_puts("Unknown");
353+
trap_puts(" epc=0x");
354+
for (int i = 28; i >= 0; i -= 4) {
355+
uint32_t nibble = (epc >> i) & 0xF;
356+
_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10);
357+
}
358+
trap_puts("\r\n");
359+
299360
hal_panic();
300361
}
362+
363+
/* Return the SP to use for context restore - new task's frame or current */
364+
return pending_switch_sp ? (uint32_t) pending_switch_sp : isr_sp;
301365
}
302366

303367
/* Enables the machine-level timer interrupt source */
304368
void hal_timer_enable(void)
305369
{
306-
mtimecmp_w(mtime_r() + (F_CPU / F_TIMER));
370+
uint64_t now = mtime_r();
371+
uint64_t target = now + (F_CPU / F_TIMER);
372+
mtimecmp_w(target);
307373
write_csr(mie, read_csr(mie) | MIE_MTIE);
308374
}
309375

@@ -313,20 +379,66 @@ void hal_timer_disable(void)
313379
write_csr(mie, read_csr(mie) & ~MIE_MTIE);
314380
}
315381

316-
/* Hook called by the scheduler after a context switch.
317-
* Its primary purpose is to enable global interrupts ('mstatus.MIE') only
318-
* AFTER the first task has been launched. This ensures interrupts are not
319-
* globally enabled until the OS is fully running in a valid task context.
382+
/* Enable timer interrupt bit only - does NOT reset mtimecmp.
383+
* Use this for NOSCHED_LEAVE to avoid pushing the interrupt deadline forward.
320384
*/
321-
void hal_interrupt_tick(void)
385+
void hal_timer_irq_enable(void)
322386
{
323-
tcb_t *task = kcb->task_current->data;
324-
if (unlikely(!task))
325-
hal_panic(); /* Fatal error - invalid task state */
387+
write_csr(mie, read_csr(mie) | MIE_MTIE);
388+
}
326389

327-
/* The task's entry point is still in RA, so this is its very first run */
328-
if ((uint32_t) task->entry == task->context[CONTEXT_RA])
329-
_ei(); /* Enable global interrupts now that execution is in a task */
390+
/* Disable timer interrupt bit only - does NOT touch mtimecmp.
391+
* Use this for NOSCHED_ENTER to temporarily disable preemption.
392+
*/
393+
void hal_timer_irq_disable(void)
394+
{
395+
write_csr(mie, read_csr(mie) & ~MIE_MTIE);
396+
}
397+
398+
/* Linker script symbols - needed for task initialization */
399+
extern uint32_t _gp, _end;
400+
401+
/* Build initial ISR frame on task stack for preemptive mode.
402+
* Returns the stack pointer that points to the frame.
403+
* When ISR restores from this frame, it will jump to task_entry.
404+
*
405+
* CRITICAL: ISR deallocates the frame before mret (sp += 128).
406+
* We place the frame such that after deallocation, SP is at a safe location.
407+
*
408+
* ISR Stack Frame Layout (must match boot.c _isr):
409+
* 0: ra, 4: gp, 8: tp, 12: t0, ... 116: t6
410+
* 120: mcause, 124: mepc
411+
*/
412+
void *hal_build_initial_frame(void *stack_top, void (*task_entry)(void))
413+
{
414+
#define INITIAL_STACK_RESERVE \
415+
256 /* Reserve space below stack_top for task startup */
416+
417+
/* Place frame deeper in stack so after ISR deallocates (sp += 128),
418+
* SP will be at (stack_top - INITIAL_STACK_RESERVE), not at stack_top.
419+
*/
420+
uint32_t *frame =
421+
(uint32_t *) ((uint8_t *) stack_top - INITIAL_STACK_RESERVE -
422+
ISR_STACK_FRAME_SIZE);
423+
424+
/* Zero out entire frame */
425+
for (int i = 0; i < 32; i++) {
426+
frame[i] = 0;
427+
}
428+
429+
/* Compute tp value same as boot.c: aligned to 64 bytes from _end */
430+
uint32_t tp_val = ((uint32_t) &_end + 63) & ~63U;
431+
432+
/* Initialize critical registers for proper task startup:
433+
* - frame[1] = gp: Global pointer, required for accessing global variables
434+
* - frame[2] = tp: Thread pointer, required for thread-local storage
435+
* - frame[31] = mepc: Task entry point, where mret will jump to
436+
*/
437+
frame[1] = (uint32_t) &_gp; /* gp - global pointer */
438+
frame[2] = tp_val; /* tp - thread pointer */
439+
frame[31] = (uint32_t) task_entry; /* mepc - entry point */
440+
441+
return (void *) frame;
330442
}
331443

332444
/* Context Switching */
@@ -468,6 +580,18 @@ __attribute__((noreturn)) void hal_context_restore(jmp_buf env, int32_t val)
468580
if (unlikely(!env))
469581
hal_panic(); /* Cannot proceed with invalid context */
470582

583+
/* Validate RA is in text section (simple sanity check) */
584+
uint32_t ra = env[15]; /* CONTEXT_RA = 15 */
585+
if (ra < 0x80000000 || ra > 0x80010000) {
586+
trap_puts("[CTX_ERR] Bad RA=0x");
587+
for (int i = 28; i >= 0; i -= 4) {
588+
uint32_t nibble = (ra >> i) & 0xF;
589+
_putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10);
590+
}
591+
trap_puts("\r\n");
592+
hal_panic();
593+
}
594+
471595
if (val == 0)
472596
val = 1; /* Must return a non-zero value after restore */
473597

@@ -503,12 +627,60 @@ __attribute__((noreturn)) void hal_context_restore(jmp_buf env, int32_t val)
503627
__builtin_unreachable(); /* Tell compiler this point is never reached */
504628
}
505629

630+
/* Stack pointer switching for preemptive context switch.
631+
* Saves current SP to *old_sp and loads new SP from new_sp.
632+
* Called by dispatcher when switching tasks in preemptive mode.
633+
* After this returns, ISR will restore registers from the new stack.
634+
*
635+
* @old_sp: Pointer to location where current SP should be saved
636+
* @new_sp: New stack pointer to switch to
637+
*/
638+
void hal_switch_stack(void **old_sp, void *new_sp)
639+
{
640+
/* Save the ISR frame SP (NOT current SP which is deep in call stack!)
641+
* to prev task. DO NOT change SP here - that would corrupt the C call
642+
* stack! Instead, store new_sp in pending_switch_sp for ISR epilogue.
643+
*/
644+
*old_sp = (void *) current_isr_frame_sp;
645+
646+
/* Set pending switch - ISR epilogue will use this SP for restore */
647+
pending_switch_sp = new_sp;
648+
}
649+
650+
/* Enable interrupts on first run of a task.
651+
* Checks if task's return address still points to entry (meaning it hasn't
652+
* run yet), and if so, enables global interrupts.
653+
*/
654+
void hal_interrupt_tick(void)
655+
{
656+
tcb_t *task = kcb->task_current->data;
657+
if (unlikely(!task))
658+
hal_panic();
659+
660+
/* The task's entry point is still in RA, so this is its very first run */
661+
if ((uint32_t) task->entry == task->context[CONTEXT_RA])
662+
_ei();
663+
}
664+
506665
/* Low-level context restore helper. Expects a pointer to a 'jmp_buf' in 'a0'.
507-
* Restores the GPRs and jumps to the restored return address.
666+
* Restores the GPRs, mstatus, and jumps to the restored return address.
667+
*
668+
* This function must restore mstatus from the context to be
669+
* consistent with hal_context_restore(). The first task context is initialized
670+
* with MSTATUS_MIE | MSTATUS_MPP_MACH by hal_context_init(), which enables
671+
* interrupts. Failing to restore this value would create an inconsistency
672+
* where the first task inherits the kernel's mstatus instead of its own.
508673
*/
509674
static void __attribute__((naked, used)) __dispatch_init(void)
510675
{
511676
asm volatile(
677+
/* Restore mstatus FIRST to ensure correct processor state.
678+
* This is critical for interrupt enable state (MSTATUS_MIE).
679+
* Context was initialized with MIE=1 by hal_context_init().
680+
*/
681+
"lw t0, 16*4(a0)\n"
682+
"csrw mstatus, t0\n"
683+
/* Now restore all general-purpose registers */
512684
"lw s0, 0*4(a0)\n"
513685
"lw s1, 1*4(a0)\n"
514686
"lw s2, 2*4(a0)\n"
@@ -536,6 +708,7 @@ __attribute__((noreturn)) void hal_dispatch_init(jmp_buf env)
536708

537709
if (kcb->preemptive)
538710
hal_timer_enable();
711+
539712
_ei(); /* Enable global interrupts just before launching the first task */
540713

541714
asm volatile(
@@ -574,6 +747,15 @@ void hal_context_init(jmp_buf *ctx, size_t sp, size_t ss, size_t ra)
574747
/* Zero the context for predictability */
575748
memset(ctx, 0, sizeof(*ctx));
576749

750+
/* Compute tp value same as boot.c: aligned to 64 bytes from _end */
751+
uint32_t tp_val = ((uint32_t) &_end + 63) & ~63U;
752+
753+
/* Set global pointer and thread pointer for proper task execution.
754+
* These are critical for accessing global variables and TLS.
755+
*/
756+
(*ctx)[CONTEXT_GP] = (uint32_t) &_gp;
757+
(*ctx)[CONTEXT_TP] = tp_val;
758+
577759
/* Set the essential registers for a new task:
578760
* - SP is set to the prepared top of the task's stack.
579761
* - RA is set to the task's entry point.

0 commit comments

Comments
 (0)