You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

RISC-V FE310-G002开发板中断处理与WFI指令正确用法咨询

Understanding RISC-V WFI and Interrupt Handling on FE310-G002

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 mtvec to 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 MIE bit (bit 3) in mstatus.

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 mepc to skip WFI, so mret jumps straight to scheduler_entry instead 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

火山引擎 最新活动