实验步骤
在开始本实验前,你的 CPU 需要首先能够运行监控程序完整版本,包含中断、异常和页表支持。接下来,你需要继续实现时钟中断、S 态,完善异常处理,支持完整的 rv32i 指令。
指令
请参照 rv-spec 实现
- RV32I 指令集中除
FENCE
,FENCE.TSO
和PAUSE
外的全部指令 - Zicsr 扩展中的全部 6 条 CSR 指令
- Zicntr 扩展(rv-spec 10.1 Base Counters and Timers 章节)中的
RDTIME
和RDTIMEH
指令
同时要实现 S 态返回指令 SRET
和 SFENCE.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 在启动时会写入 pmpcfg0
和 pmpaddr0
这两个 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 定义了两个和时钟相关的寄存器 mtime
和 mtimecmp
:
mtime
表示当前时间mtimecmp
表示下次发生时钟中断的时间
这两个寄存器被定义为 MMIO,位于 CLINT 设备的地址空间中。在 QEMU 的 virt 板子中,CLINT 位于物理 0x2000000
,其中 mtime
的偏移为 0xBFF8
,mtimecmp
的偏移为 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 < mtimecmp
时 MTIP
位被置 0。一次完整的时钟中断处理就结束了。
但是,引入 S 态之后事情变得更加复杂:由于 mtime
和 mtimecmp
触发的是 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 态,结束中断处理流程。
总结一下,你需要实现:
rdtime
和rdtimeh
指令(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 态中断