Archive:

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


最近尝试用 Coffer 为哪吒 D1 引导 RISC-V Linux 遇到了下面一些问题,记录一下。

mcounteren 寄存器设置

mcounteren 是与硬件性能监视器相关的寄存器,在刚开始移植时我只顾及了与异常、中断等相关的寄存器例如 mstatusmideleg 等。而忽视了性能监视器相关的寄存器。

实际上,uboot 与 Linux 都需要这个寄存器被正确的设置,因为该寄存器除了 28 个HPMn 寄存器位管理各类事件发生的情况之外 ,还有三个比特位分别管理 cycletimeinstret 能否被低权限访问, CYTMIR

目前不考虑安全性等机制的情况下,直接将其设置成全部可以访问即可。 如果需要固件虚拟结果,可以阻止访问,在出现 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();
    }
}