@@ -248,32 +248,40 @@ void hal_cpu_idle(void)
248248
249249/* Interrupt and Trap Handling */
250250
251+ /* Direct UART output for trap context (avoids printf deadlock) */
252+ extern int _putchar (int c );
253+ static void trap_puts (const char * s )
254+ {
255+ while (* s )
256+ _putchar (* s ++ );
257+ }
258+
259+ /* Exception message table per RISC-V Privileged Spec */
260+ static const char * exc_msg [] = {
261+ [0 ] = "Instruction address misaligned" ,
262+ [1 ] = "Instruction access fault" ,
263+ [2 ] = "Illegal instruction" ,
264+ [3 ] = "Breakpoint" ,
265+ [4 ] = "Load address misaligned" ,
266+ [5 ] = "Load access fault" ,
267+ [6 ] = "Store/AMO address misaligned" ,
268+ [7 ] = "Store/AMO access fault" ,
269+ [8 ] = "Environment call from U-mode" ,
270+ [9 ] = "Environment call from S-mode" ,
271+ [10 ] = "Reserved" ,
272+ [11 ] = "Environment call from M-mode" ,
273+ [12 ] = "Instruction page fault" ,
274+ [13 ] = "Load page fault" ,
275+ [14 ] = "Reserved" ,
276+ [15 ] = "Store/AMO page fault" ,
277+ };
278+
251279/* C-level trap handler, called by the '_isr' assembly routine.
252280 * @cause : The value of the 'mcause' CSR, indicating the reason for the trap.
253281 * @epc : The value of the 'mepc' CSR, the PC at the time of the trap.
254282 */
255283void do_trap (uint32_t cause , uint32_t epc )
256284{
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- };
276-
277285 if (MCAUSE_IS_INTERRUPT (cause )) { /* Asynchronous Interrupt */
278286 uint32_t int_code = MCAUSE_GET_CODE (cause );
279287 if (int_code == MCAUSE_MTI ) { /* Machine Timer Interrupt */
@@ -282,28 +290,59 @@ void do_trap(uint32_t cause, uint32_t epc)
282290 * consistent tick frequency even with interrupt latency.
283291 */
284292 mtimecmp_w (mtimecmp_r () + (F_CPU / F_TIMER ));
285- dispatcher (); /* Invoke the OS scheduler */
293+ /* Invoke scheduler - parameter 1 = from timer, increment ticks */
294+ dispatcher (1 );
286295 } else {
287296 /* All other interrupt sources are unexpected and fatal */
288- printf ("[UNHANDLED INTERRUPT] code=%u, cause=%08x, epc=%08x\n" ,
289- int_code , cause , epc );
290297 hal_panic ();
291298 }
292299 } else { /* Synchronous Exception */
293300 uint32_t code = MCAUSE_GET_CODE (cause );
294- const char * reason = "Unknown exception" ;
301+
302+ /* Handle ecall from M-mode - used for yielding in preemptive mode */
303+ if (code == MCAUSE_ECALL_MMODE ) {
304+ /* Advance mepc past the ecall instruction (4 bytes) */
305+ uint32_t new_epc = epc + 4 ;
306+ write_csr (mepc , new_epc );
307+
308+ /* Also update mepc in the ISR frame on the stack!
309+ * The ISR epilogue will restore mepc from the frame (offset 31*4 =
310+ * 124 bytes). If we don't update the frame, mret will jump back to
311+ * the ecall instruction!
312+ */
313+ uint32_t * isr_frame = (uint32_t * ) __builtin_frame_address (0 );
314+ isr_frame [31 ] = new_epc ;
315+
316+ /* Invoke dispatcher for context switch - parameter 0 = from ecall,
317+ * don't increment ticks.
318+ */
319+ dispatcher (0 );
320+ return ;
321+ }
322+
323+ /* Print exception info via direct UART (safe in trap context) */
324+ trap_puts ("[EXCEPTION] " );
295325 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 );
326+ trap_puts (exc_msg [code ]);
327+ else
328+ trap_puts ("Unknown" );
329+ trap_puts (" epc=0x" );
330+ for (int i = 28 ; i >= 0 ; i -= 4 ) {
331+ uint32_t nibble = (epc >> i ) & 0xF ;
332+ _putchar (nibble < 10 ? '0' + nibble : 'A' + nibble - 10 );
333+ }
334+ trap_puts ("\r\n" );
335+
299336 hal_panic ();
300337 }
301338}
302339
303340/* Enables the machine-level timer interrupt source */
304341void hal_timer_enable (void )
305342{
306- mtimecmp_w (mtime_r () + (F_CPU / F_TIMER ));
343+ uint64_t now = mtime_r ();
344+ uint64_t target = now + (F_CPU / F_TIMER );
345+ mtimecmp_w (target );
307346 write_csr (mie , read_csr (mie ) | MIE_MTIE );
308347}
309348
@@ -313,20 +352,50 @@ void hal_timer_disable(void)
313352 write_csr (mie , read_csr (mie ) & ~MIE_MTIE );
314353}
315354
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.
355+ /* Enable timer interrupt bit only - does NOT reset mtimecmp.
356+ * Use this for NOSCHED_LEAVE to avoid pushing the interrupt deadline forward.
357+ */
358+ void hal_timer_irq_enable (void )
359+ {
360+ write_csr (mie , read_csr (mie ) | MIE_MTIE );
361+ }
362+
363+ /* Disable timer interrupt bit only - does NOT touch mtimecmp.
364+ * Use this for NOSCHED_ENTER to temporarily disable preemption.
365+ */
366+ void hal_timer_irq_disable (void )
367+ {
368+ write_csr (mie , read_csr (mie ) & ~MIE_MTIE );
369+ }
370+
371+ /* Build initial ISR frame on task stack for preemptive mode.
372+ * Returns the stack pointer that points to the frame.
373+ * When ISR restores from this frame, it will jump to task_entry.
374+ *
375+ * CRITICAL: ISR deallocates the frame before mret (sp += 128).
376+ * We place the frame such that after deallocation, SP is at a safe location.
320377 */
321- void hal_interrupt_tick (void )
378+ void * hal_build_initial_frame (void * stack_top , void ( * task_entry )( void ) )
322379{
323- tcb_t * task = kcb -> task_current -> data ;
324- if (unlikely (!task ))
325- hal_panic (); /* Fatal error - invalid task state */
380+ #define INITIAL_STACK_RESERVE \
381+ 256 /* Reserve space below stack_top for task startup */
326382
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 */
383+ /* Place frame deeper in stack so after ISR deallocates (sp += 128),
384+ * SP will be at (stack_top - INITIAL_STACK_RESERVE), not at stack_top.
385+ */
386+ uint32_t * frame =
387+ (uint32_t * ) ((uint8_t * ) stack_top - INITIAL_STACK_RESERVE -
388+ ISR_STACK_FRAME_SIZE );
389+
390+ /* Zero out entire frame */
391+ for (int i = 0 ; i < 32 ; i ++ ) {
392+ frame [i ] = 0 ;
393+ }
394+
395+ /* Set mepc (offset 31 words) to task entry point */
396+ frame [31 ] = (uint32_t ) task_entry ;
397+
398+ return (void * ) frame ;
330399}
331400
332401/* Context Switching */
@@ -503,12 +572,43 @@ __attribute__((noreturn)) void hal_context_restore(jmp_buf env, int32_t val)
503572 __builtin_unreachable (); /* Tell compiler this point is never reached */
504573}
505574
575+ /* Stack pointer switching for preemptive context switch.
576+ * Saves current SP to *old_sp and loads new SP from new_sp.
577+ * Called by dispatcher when switching tasks in preemptive mode.
578+ * After this returns, ISR will restore registers from the new stack.
579+ *
580+ * @old_sp: Pointer to location where current SP should be saved
581+ * @new_sp: New stack pointer to switch to
582+ */
583+ void hal_switch_stack (void * * old_sp , void * new_sp )
584+ {
585+ asm volatile (
586+ "sw sp, 0(%0)\n" /* Save current SP to *old_sp */
587+ "mv sp, %1\n" /* Load new SP from new_sp */
588+ : /* no outputs */
589+ : "r" (old_sp ), "r" (new_sp )
590+ : "memory" );
591+ }
592+
506593/* 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.
594+ * Restores the GPRs, mstatus, and jumps to the restored return address.
595+ *
596+ * This function must restore mstatus from the context to be
597+ * consistent with hal_context_restore(). The first task context is initialized
598+ * with MSTATUS_MIE | MSTATUS_MPP_MACH by hal_context_init(), which enables
599+ * interrupts. Failing to restore this value would create an inconsistency
600+ * where the first task inherits the kernel's mstatus instead of its own.
508601 */
509602static void __attribute__((naked , used )) __dispatch_init (void )
510603{
511604 asm volatile (
605+ /* Restore mstatus FIRST to ensure correct processor state.
606+ * This is critical for interrupt enable state (MSTATUS_MIE).
607+ * Context was initialized with MIE=1 by hal_context_init().
608+ */
609+ "lw t0, 16*4(a0)\n"
610+ "csrw mstatus, t0\n"
611+ /* Now restore all general-purpose registers */
512612 "lw s0, 0*4(a0)\n"
513613 "lw s1, 1*4(a0)\n"
514614 "lw s2, 2*4(a0)\n"
@@ -536,6 +636,7 @@ __attribute__((noreturn)) void hal_dispatch_init(jmp_buf env)
536636
537637 if (kcb -> preemptive )
538638 hal_timer_enable ();
639+
539640 _ei (); /* Enable global interrupts just before launching the first task */
540641
541642 asm volatile (
0 commit comments