RISC-V FE310-G002开发板中断处理与WFI指令正确用法咨询
Hey there! Let's unpack your confusion around WFI and interrupts on the FE310-G002—this is a super common hurdle for bare-metal RISC-V beginners, so you’re definitely not alone. Let’s start with clarifying hardware behavior, then move to practical, safe usage.
First: What FE310’s WFI Actually Does
The RISC-V spec frames WFI as a "hint" instruction because behavior can vary across cores, but the SiFive E31 core (used in FE310-G002) does implement proper low-power sleep with WFI when conditions are met:
- If there are pending enabled interrupts when WFI executes, it acts like a NOP—completes immediately, and the CPU moves to the next instruction.
- If no pending enabled interrupts exist, the core enters a low-power halt state. It wakes up as soon as an enabled interrupt triggers, then completes the WFI instruction and proceeds to the next line of code.
Your initial frustration with the wfi_loop comes from a misunderstanding of how interrupts interact with WFI: when an interrupt triggers while the core is halted in WFI, the CPU doesn’t just resume WFI—it first handles the interrupt exception, then returns to the WFI instruction via mret. That’s why you ended up stuck in the loop.
Safe, Practical WFI + Interrupt Workflow
The goal is to have the interrupt handler return to your scheduler (not back to WFI) so you can re-run your scheduling logic. Here’s how to do it properly on FE310:
1. Configure Interrupts First
Before using WFI, make sure your interrupt setup is complete:
- Set
mtvecto point to your interrupt handler (use direct mode for simplicity on FE310). - Enable the specific interrupt in
mie(e.g., set bit 7 for machine timer interrupt,MTIE). - Enable global machine interrupts by setting the
MIEbit (bit 3) inmstatus.
2. The WFI Idle Code
You don’t need an infinite loop around WFI—just call it once, followed by a jump to your scheduler:
idle: # Ensure global interrupts are enabled (redundant but safe) csrrsi zero, mstatus, 0x8 # Enter low-power state if no pending interrupts wfi # After WFI completes (either immediately or post-wakeup), jump to scheduler j scheduler_entry
3. Adjust the Interrupt Handler to Skip WFI on Return
The key fix is modifying mepc in your interrupt handler so mret jumps to the instruction after WFI, not back to WFI itself. Here’s a simplified handler:
interrupt_handler: # Save all caller-saved registers to the stack (critical for bare-metal!) addi sp, sp, -32 sw ra, 0(sp) sw t0, 4(sp) sw t1, 8(sp) sw t2, 12(sp) sw a0, 16(sp) sw a1, 20(sp) sw a2, 24(sp) sw a3, 28(sp) # Handle the interrupt (example: clear timer interrupt pending bit in CLINT) li t0, 0x80000000 # Base address of CLINT on FE310 sw zero, 0x40(t0) # Clear mtimecmp interrupt pending # Update mepc to point to the instruction AFTER WFI (WFI is 4 bytes) csrr t0, mepc addi t0, t0, 4 csrw mepc, t0 # Restore saved registers lw ra, 0(sp) lw t0, 4(sp) lw t1, 8(sp) lw t2, 12(sp) lw a0, 16(sp) lw a1, 20(sp) lw a2, 24(sp) lw a3, 28(sp) addi sp, sp, 32 # Return to the updated mepc (scheduler_entry) mret
Why This Works:
- When the core is halted in WFI and an interrupt triggers, it wakes up, triggers an interrupt exception, and saves the address of the WFI instruction to
mepc. - Your handler modifies
mepcto skip WFI, somretjumps straight toscheduler_entryinstead of re-executing WFI. - If WFI completed immediately (because a pending interrupt existed), the CPU just jumps to the scheduler naturally—no loop needed.
Addressing Your Proposed CSRRSI + WFI Loop
Your idea of atomically enabling interrupts before WFI is solid (to avoid a race where an interrupt triggers between enabling interrupts and executing WFI), but you don’t need the infinite loop or NOP padding. Instead, combine the atomic enable with the simplified idle code:
safe_idle: # Atomically enable global machine interrupts and execute WFI csrrsi zero, mstatus, 0x8 wfi j scheduler_entry
The csrrsi instruction is atomic, so there’s no window where interrupts are enabled but WFI hasn’t executed yet—this prevents race conditions perfectly.
Avoiding Stack Rollback Issues
When modifying mepc to jump to the scheduler, you must ensure your interrupt handler has fully saved and restored all registers. If your scheduler switches tasks, it will manage its own context stack—just make sure the interrupt handler leaves the stack in the same state it found it, so the scheduler can pick up correctly.
内容的提问来源于stack exchange,提问作者Echelon X-Ray




