Coffer 引导 Linux 与 RISC-V 定时器的坑

1 minute read

Published:

最近尝试用 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 异常,无法被委托。 只能以下列顺序传递。


  1. machine mode 接收到中断,清除 mie.MTIE (防止再次触发),设置 mip.STIP (触发 supervisor timer 中断)。
  2. 在 mideleg 被正确配置的情况下,supervisor timer 中断被 stvec 中的中断处理函数处理。
  3. stvec 处理完业务,调用 set_timer 约定下次定时器中断。
  4. 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();
    }
}