跳转至

实验步骤

在开始本实验前,你的 CPU 需要首先能够运行监控程序完整版本,包含中断、异常和页表支持。接下来,你需要继续实现时钟中断、S 态,完善异常处理,支持完整的 rv32i 指令。

指令

请参照 rv-spec 实现

  • RV32I 指令集中除 FENCE , FENCE.TSOPAUSE 外的全部指令
  • Zicsr 扩展中的全部 6 条 CSR 指令
  • Zicntr 扩展(rv-spec 10.1 Base Counters and Timers 章节)中的 RDTIMERDTIMEH 指令

同时要实现 S 态返回指令 SRETSFENCE.VMA (没有 TLB 可实现为 NOP, 但需要实现指令译码)。

虽然 ucore 和 rbl 中有 WFI 指令,但若 ucore 和 rbl 正确运行,则不会执行 WFI 指令,因此不需要实现这条指令。但这里建议实现 INVALID INSTRUCTION 异常,在遇到 WFI 时直接出错,从而便于错误的检查。切记千万不要把 WFI 译码成 NOP,这样会掩盖错误,同时让你的 CPU 更加难以调试。

阅读手册:

  • RISC-V Spec, Chapter 2 RV32I Base Integer Instruction Set
  • RISC-V Spec, Chapter 9 "Zicsr", Control and Status Register(CSR) Instructions
  • RISC-V Spec, 10.1 Base Counters and Timers
  • RISC-V Privileged, 3.3.2 Trap-Return Instructions
  • RISC-V Privileged, 4.2.1 Supervisor Memory-Management Fence Instruction

在完成后请对于每条实现的指令设计完善的测例进行测试,以尽可能减少指令实现出错的概率。

CSR 寄存器

在监控程序需要的 CSR 寄存器的基础上,你还要实现以下 CSR 寄存器的以下字段:

  • mhartid: Hart ID
  • mstatus: SPP, SPIE, SIE, SUM
  • mideleg: Interrupts
  • medeleg: Synchronous Exceptions
  • mip: STIP
  • mie: STIE
  • mtval: mtval
  • sstatus: SPP, SPIE, SIE, SUM
  • sepc: sepc
  • scause: Interrupt, Exception Code
  • stval: stval
  • stvec: BASE, MODE
  • sscratch: sscratch
  • sie: STIE
  • sip: STIP

PMP CSR 寄存器

注意,为了和 QEMU 兼容, rbl 在启动时会写入 pmpcfg0pmpaddr0 这两个 CSR 寄存器,将其配置为 0x00000000 ~ 0xFFFFFFFF RWX,即全地址空间可读、可写、可执行。因此,你可以忽略对 PMP CSR 的任何操作,但请不要在访问这些 CSR 地址时出现例外的情况(如卡死、异常等)。

阅读手册:

  • RISC-V Privileged, 2.2 CSR Listing
  • RISC-V Privileged, 2.3 CSR Field Specifications
  • RISC-V Privileged, 3.1.5 Hart ID Register mhartid
  • RISC-V Privileged, 3.1.8 Machine Trap Delegation Registers (medeleg and mideleg)
  • RISC-V Privileged, 3.1.16 Machine Trap Value Register (mtval)
  • RISC-V Privileged, 4.1.1 Supervisor Status Register (sstatus)
  • RISC-V Privileged, 4.1.4 Supervisor Trap Vector Base Address Register (stvec)
  • RISC-V Privileged, 4.1.5 Supervisor Interrupt Registers (sip and sie)
  • RISC-V Privileged, 4.1.8 Supervisor Scratch Register (sscratch)
  • RISC-V Privileged, 4.1.9 Supervisor Exception Program Counter (sepc)
  • RISC-V Privileged, 4.1.10 Supervisor Cause Register (scause)
  • RISC-V Privileged, 4.1.11 Supervisor Trap Value (stval) Register

在完成后请对于每个新实现的 CSR 寄存器进行测试,确认读写、权限和转移等等。

异常处理

相比监控程序,需要额外实现三种 Page Fault 异常和 S 态 ECALL 异常。

同时需要额外实现 S 态的时钟中断,将在下一节中详细描述。

并建议实现非法指令异常,以便快速查出没有实现的新指令。

还需要实现中断异常委托:mideleg, medeleg,将相应位为 1 的中断异常转发到 S 态处理。

rbl 启动时会初始化这两个寄存器,对应的中断异常都会直接在 S 态处理。

如果运行时输出 rbl panic 显示异常,可能是因为没有正确实现异常委托。

注意:CPU 在低特权级运行时,高特权级的中断自动打开! 例如在 S 态运行时,无论当前 MIE 是否为 1,M 态都可以收到中断。

阅读手册:

  • RISC-V Privileged, 3.1.15 Machine Cause Register (mcause)
  • RISC-V Privileged, 3.1.8 Machine Trap Delegation Registers (medeleg and mideleg)
  • RISC-V Privileged, 3.1.9 Machine Interrupt Registers (mip and mie)

在完成之后,可以尝试在监控程序中编写代码进行测试,建议将每种异常和对应的委托在监控程序中测试完成后,再来完成最后的最复杂的 S 态时钟中断处理。

时钟中断

阅读手册:

  • RISC-V Privileged, 3.1.10 Machine Timer Registers (mtime and mtimecmp)
  • RISC-V Spec, 10.1 Base Counters and Timers

RISC-V 定义了两个和时钟相关的寄存器 mtimemtimecmp

  • mtime 表示当前时间
  • mtimecmp 表示下次发生时钟中断的时间

这两个寄存器被定义为 MMIO,位于 CLINT 设备的地址空间中。在 QEMU 的 virt 板子中,CLINT 位于物理 0x2000000,其中 mtime 的偏移为 0xBFF8mtimecmp 的偏移为 0x4000。为了和 QEMU 兼容,我们也需要在相同的位置上实现这两个寄存器。

除了读取 mtime 寄存器以外,程序还可以通过 rdtime 指令获取当前时间。rdtime 是个伪指令,实际上是在读取名为 time 的 CSR 寄存器。RV32 的 CSR 是 32 位的,但是 mtime 是 64 位的,所以需要两个 CSR 才能完整表示时间,于是还有另一个 rdtimeh 指令用来获取当前时间的高 32 位。特别的,不需要关心 mcounteren 寄存器的相关内容。

mtime >= mtimecmp 时,就会触发时钟中断,MTIP 位被置 1,然后 CPU 开始响应中断。中断处理程序最后会更新 mtimecmp 设置下一次时钟中断的时间(一般是 mtime + interval)。当 mtime < mtimecmpMTIP 位被置 0。一次完整的时钟中断处理就结束了。

但是,引入 S 态之后事情变得更加复杂:由于 mtimemtimecmp 触发的是 M 态中断,只能进 M 态处理,不能委托给低特权级。因此 S 态的时钟中断需要经过 M 态软件转发,设置 STIP 位来完成。

具体而言,当运行在 S 态的 CPU 收到时钟中断后,会进入 M 态,由软件设置 MTIE=0 关闭 M 态时钟中断,然后设置 STIP=1 告诉 S 态有时钟中断发生,最后通过 mret 返回 S 态,此时 STIP=1 进一步触发 S 态的时钟中断,由于 mideleg.STIE=1,会委托给 S 态处理。

这样 uCore 就收到了时钟中断,它会调用 SBI (也就是 rbl) 的 set_timer 接口回到 M 态更新 mtimecmp,同时 M 态设置 MTIE=1 恢复接收 M 态时钟中断,设置 STIP=0 清除 S 态时钟中断信号,最后返回 S 态,结束中断处理流程。

总结一下,你需要实现:

  • rdtimerdtimeh 指令(CSR 0xC01 0xC81)读取当前时间(mtime
  • mtime 寄存器,位于 0x200BFF8,可读写
  • mtimecmp 寄存器,位于 0x2004000,可读写
  • MTIP := mtime >= mtimecmp,只读。当 MTIP=1 && MTIE=1 && (MIE=1 || Mode<M) 时触发 M 态中断
  • STIP,M 态可读写,S 态只读。当 STIP=1 && STIE=1 && (Mode=S && SIE=1 || Mode<S) 时触发 S 态中断

最后更新: 2023年12月7日
作者:Jiajie Chen (0.81%), cuibst (99.19%)