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 */
304368void 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 */
509674static 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