Coffer 引导 Linux 与 RISC-V 定时器的坑
最近尝试用 Coffer 为哪吒 D1 引导 RISC-V Linux 遇到了下面一些问题,记录一下。
mcounteren
寄存器设置
mcounteren
是与硬件性能监视器相关的寄存器,在刚开始移植时我只顾及了与异常、中断等相关的寄存器例如 mstatus
与 mideleg
等。而忽视了性能监视器相关的寄存器。
实际上,uboot 与 Linux 都需要这个寄存器被正确的设置,因为该寄存器除了 28 个HPMn
寄存器位管理各类事件发生的情况之外
,还有三个比特位分别管理 cycle
、time
与 instret
能否被低权限访问, CY
、TM
、IR
。
目前不考虑安全性等机制的情况下,直接将其设置成全部可以访问即可。 如果需要固件虚拟结果,可以阻止访问,在出现 illegal instruction 异常时,模拟该条指令的结果。
PLIC
权限设置
在解决了 mcounteren
的权限设置后,可以正常进入 uboot,但在启动过程中会出现两个 load access fault。
检查全志提供的 Tina 中的 OpenSBI 代码,发现 PLIC 权限管理寄存器设置,如果未被正常设置,在低权限访问 PLIC 所在区域时会触发权限异常。
需要正确设置 PLIC 寄存器。
// I Love Allwinner
write_volatile(0x101F_FFFC as *mut u32, 0x1);
定时器中断
尽管 SBI 标准提供了很多功能,但 Linux 目前只依赖了 SBI v0.1 中的 legacy 标准中的 set_timer
SBI 调用用于提供系统时钟。
不知道为什么,尽管配置了正确的 mideleg
,仍然会出现定时器中断打到 machine mode 的情况,需要对其进行处理。
原因是 RISC-V 在默认情况下,所有的异常/中断均为 machine mode 处理,但为了提升性能,可以将一部分委托给低权限处理。 在这种情况下,只有同权限/更低权限的异常/中断可以被委托,例如发生在 supervisor 的异常可以被 supervisor 的异常处理函数处理。 而 RISC-V 目前并没有
stimer
寄存器,所有定时器异常均为 machine mode 异常,无法被委托。 只能以下列顺序传递。
- machine mode 接收到中断,清除
mie.MTIE
(防止再次触发),设置mip.STIP
(触发 supervisor timer 中断)。 - 在
mideleg
被正确配置的情况下,supervisor timer 中断被stvec
中的中断处理函数处理。 stvec
处理完业务,调用set_timer
约定下次定时器中断。set_timer
清理mip.STIP
,设置mie.MTIE
启用定时器中断。
PS:
mip.MTIP
机器中断位为只读位,只能通过写入mtimecmp
寄存器进行清理。 因此若希望清理mip.MTIP
而不希望约定下次定时器中断时, 可以向其中写入一个较大的值。
pub(crate) fn set_timer(stime_value: u64) -> SbiRet {
if let Some(timer) = TIMER.lock().as_mut() {
timer.set_timer(stime_value);
unsafe {
// clear pending stimer
mip::clear_stimer();
// set mtimer for next event
mie::set_mtimer();
}
SbiRet::ok(0)
} else {
SbiRet::not_supported()
}
}
pub(crate) fn process_timer() {
unsafe {
// close mtimer
mie::clear_mtimer();
// transfer to stimer
mip::set_stimer();
}
}