riscv 中斷處理


中斷(中斷返回)本質上也是一種跳轉,只不過還需要附加一些讀寫CSR寄存器的操作。

RISC-V中斷分為兩種類型,一種是同步中斷,即ECALL、EBREAK等指令所產生的中斷,另一種是異步中斷,即GPIO、UART等外設產生的中斷。

  1. 中斷號保存在 mcause 寄存器中,最高位是 1 說明是同步異常,否則是中斷
  2. mepc 儲存中斷前執行指令的地址,調用 mret 返回后會執行其中的地址
  3. 對於 RISCV 而言,當前運行的狀態保存在mstatus 寄存器中
  • MPP 位記錄當前機器模式的特權等級,0 是用戶級,1 是內核級,2 保留,3 是機器級,權限最高
  • MPIE 記錄觸發中斷前的MIE 位的值,MIE (Machine Interrupt Enable)位為 1 的時候,中斷才會觸發

RISCV 不支持中斷嵌套,即中斷觸發之后會將 mstatus 的 mie 位置 0

中斷處理的第一條指令地址存儲在 mtvec 中,mie 寄存器(不是mstatus 寄存器中的mie位)控制哪些中斷可以被觸發,只有對應位置置一的中斷號的中斷會觸發。

中斷處理完成之后需要返回,從機器模式的中斷返回需要調用 mret 指令,它會 將 PC 設置為 mepc,通過將 mstatus 的 MPIE 域復制到MIE 來恢復之前的中斷使能設置,並將權限模式設置為 mstatus 的 MPP 域中的值。

 

 

 

對於中斷模塊設計,一種簡單的方法就是當檢測到中斷(中斷返回)信號時,先暫停整條流水線,設置跳轉地址為中斷入口地址,然后讀、寫必要的CSR寄存器(mstatus、mepc、mcause等),等讀寫完這些CSR寄存器后取消流水線暫停,這樣處理器就可以從中斷入口地址開始取指,進入中斷服務程序。

下面看tinyriscv的中斷是如何設計的。中斷模塊所在文件:rtl/core/clint.v

輸入輸出信號列表如下:

 

 

 先看中斷模塊是怎樣判斷有中斷信號產生的,如下代碼:

 

 

 

第3~4行,復位后的狀態,默認沒有中斷要處理。

第6~7行,判斷當前指令是否是ECALL或者EBREAK指令,如果是則設置中斷狀態為S_INT_SYNC_ASSERT,表示有同步中斷要處理。

第8~9行,判斷是否有外設中斷信號產生,如果是則設置中斷狀態為S_INT_ASYNC_ASSERT,表示有異步中斷要處理。

第10~11行,判斷當前指令是否是MRET指令,MRET指令是中斷返回指令。如果是,則設置中斷狀態為S_INT_MRET。

下面就根據當前的中斷狀態做不同處理(讀寫不同的CSR寄存器),代碼如下:

 

 

 

 

 

 

 

第1023行,當CSR處於S_CSR_IDLE時,如果中斷狀態為S_INT_SYNC_ASSERT,則在第11行將CSR狀態設置為S_CSR_MEPC,在第12行將當前指令地址保存下來。

在第1323行,根據不同的指令類型,設置不同的中斷碼(Exception Code),這樣在中斷服務程序里就可以知道當前中斷發生的原因了。

第24~28行,目前tinyriscv只支持定時器這個外設中斷。

第30~31行,如果是中斷返回指令,則設置CSR狀態為S_CSR_MSTATUS_MRET。

第34~48行,一個時鍾切換一下CSR狀態。

接下來就是寫CSR寄存器操作,需要根據上面的CSR狀態來寫。

 

 

 

 

第11~15行,寫mepc寄存器。

第17~21行,寫mcause寄存器。

第23~27行,關閉全局異步中斷。

第29~33行,寫mstatus寄存器。

最后就是發出中斷信號,中斷信號會進入到執行階段。

有兩種情況需要發出中斷信號,一種是進入中斷,另一種是退出中斷。

9~12行,寫完mstatus寄存器后發出中斷進入信號,中斷入口地址就是mtvec寄存器的值。

第13~15行,發出中斷退出信號,中斷退出地址就是mepc寄存器的值。

 

編寫一個 BOOT

 mret 指令

為了使 hart 跑在監管者模式下,我們必須使用 mret 。

 參考 RISC-V 的相關資料,在處理 mret 指令時,PC 值會從 mepc 寄存器取得。因此,我們必須將 main 函數的地址存入 mepc 寄存器。

mstatus 寄存器

 

剛開始執行代碼一定是機器模式,但是我們總不能一直讓 hart 在機器模式下運行;此外,全局中斷使能位也需要我們控制。這些都可以在 mstatus 寄存器上找到,關於 mstatus 寄存器,RISC-V 特權架構 和 RISC-V 中文手冊上都有詳細介紹。在此就略寫幾句。

當進入 main 函數時,hart 最好要進入監管者模式。因為 main 函數事實上是我們操作系統內核最主要的函數之一,此外,我們也希望中斷能被打開。對照 mstatus 寄存器的位圖,我們可以在對應位域置 1 ,來打開中斷或者記錄信息等。

比如,我們想先打開機器模式的中斷使能,那么我們需要:

將 mstatus.MIE 位置為 1 ,因為它代表機器模式全局下的中斷使能
將 mstatus.MPIE 位置為 1 ,它代表了在中斷/異常發生前,機器模式全局下的中斷使能(我們肯定不想在中斷/異常發生一次后,使能就失效了吧)
我們還要將 mstatus.MPP 位置為 01,它代表了中斷/異常發生前,代碼運行的模式。之所以置為 01(監管者模式),是為了在執行 mret 的時候進入監管者模式。結合之前所說的,寫下如下代碼:

    li   t0, (0b01 << 11) | (1 << 7) | (1 << 3)
    csrw mstatus, t0

 


 

    # 讓其它(非0號)硬件線程掛起,跳轉至 3
    csrr    t0, mhartid
    bnez    t0, 3f
    csrw    satp, zero   //關閉mmu

 

這里是讀取處理器的核心號碼(mhartid),我們只需要使用 0 號核心進行初始化操作,非 0 的核心會跳轉到后面掛起

    # 先初始化
    li      t0, (0b11 << 13) | (0b11 << 11) | (1 << 7)
    csrw    mstatus, t0
    la      t1, kernel_init
    csrw    mepc, t1
    la      t2, m_trap_vector
    csrw    mtvec, t2
    li      t3, 0xaaa
    csrw    mie, t3
    la      ra, 4f
    mret

這里出現一個關鍵的指令 csrw 意思是寫入狀態控制寄存器。每個核心都有一系列狀態控制寄存器,可以參考 RISCV 手冊。下方列出的是 mstatus 狀態寄存器的每個位的情況。

  • 使 FS 置位,可以開啟浮點運算(不開啟的話使用浮點數會報錯)
  • 使 MPIE 置位,手冊里的說法是,這個位儲存中斷前 MIE 的值,當我們從中斷返回后 MPIE 會放到 MIE 中
  • 使 MPP 置 0b11, MMP 標志着當前的特權級別
  • mepc 放置 m_trap_vector 函數的地址,出發中斷后會跳轉到 m_trap_vector (放在 src/asm/trap.S 中)
  • 調用 mret 之后,會執行 mepc 中的地址,即 kernel_init 函數

Rust 初始化函數

#[no_mangle]
extern "C" fn kernel_init(){
​
}
​
#[no_mangle]
extern "C" fn kernel_start(){
​
}

 

RISC-V from scratch 6 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM