最近在實作RISC-V OS的Trap時,剛好要做Timer Interrupt,這在QEMU中非常簡單,只要follow xv6的做法就可以實現,但在Milkv開發版上就不太一樣。
因為我的Milkv開發版上不是裸機執行OS,而是透過OpenSBI來載入並降級到S-Mode,且又因為RISC-V的Timer Interrupt 是只能在M-Mode設置,因此做起來就比較麻煩,需要在S-Mode下透過ecall 委託M-Mode的OpenSBI來完成。
xv6在QEMU上的做法
先來快速go through xv6是怎麼做Timer Interrupt的,首先需要先還在M-Mode時就先設置menvcfg (Machine Environment Configuration) 這個csr暫存器的bit 63來允許 S-mode 使用 stimecmp (Sstc or Supervisor-mode Timer Compare extension)。 (xv6-start.c line:59)
然後再設置mcounteren的bit 1,允許 S-mode 讀 time 計數器,沒設的話在執行
asm volatile("csrr %0, time" : "=r" (x));
時就會觸發 illegal instruction。
以上兩個都開啟後,就可以在S-Mode中設置stimecmp來開啟timer的計時功能了,時間到就會Trap,然後跳到我們寫好的Trap Handler處理。
Milkv Duo S (OpenSBI) 上的做法
然而因為我目前的Milkv Duo S是跑在OpenSBI下的,所以OS一進來就是S-Mode,沒辦法碰到M-Mode的暫存器,加上他的CPU C906不支援Sstc,不能直接在S-Mode設timer。
但好在OpenSBI有開啟前面兩點,並提供直接設置Timer計數的功能,因此只要ecall呼叫一下OpenSBI請他開始計數即可。
為了實作這個,我寫了一個Helper Funtion如下
// SBI Timer Extension
// 請 M-mode(OpenSBI)設定 timer,當 time >= x 時觸發 S-mode timer interrupt
// C906 沒有 Sstc extension,S-mode 不能直接寫 stimecmp,
// 必須透過 SBI ecall 請 M-mode 代勞設定 mtimecmp
//
// SBI ecall 呼叫慣例:
// a7 = Extension ID(哪個功能模組)
// a6 = Function ID (該模組的哪個功能)
// a0 = 第一個參數
// 執行 ecall → 陷入 M-mode → OpenSBI 根據 a7/a6 分發處理
static inline void sbi_set_timer(uint64 x){
asm volatile(
"mv a0, %0\n" // a0 = x(參數:timer 目標時間)
"li a6, 0\n" // a6 = FID = 0(set_timer 功能)
"li a7, 0x54494D45\n" // a7 = EID = "TIME"(Timer Extension)
"ecall" // 陷入 M-mode,OpenSBI 幫忙設定 mtimecmp
: : "r"(x) : "a0", "a6", "a7"
// "r"(x): 把 x 放進暫存器,用 %0 引用
// clobber list: 告訴 compiler a0/a6/a7 被動過
);
}
如此一來只要在需要的地方去呼叫就可以了。
sbi_set_timer(get_time() + 1000000);