# RISC-V Platform-Level Interrupt Controller(PLIC) 探討 >[name=Jacky Shia][time=Oct 1,2022] [Jacky](https://www.linkedin.com/in/%E8%9C%83%E8%A9%B0-%E5%A4%8F-b854191b7/) --- 相信有接觸過embedded system都對interrupt不陌生,就以筆者來說,只知道有中斷發生,CPU進入到相對應的ISR(Interrupt Service Routine),要做的事情就只是針對中斷訊號來做處理,但這之間硬體做了些什麼事情卻無從而知.此篇文章為以RISC-V架構探討中斷發生,硬體是如何處置,這樣軟體思維上就能更清楚掌控中斷的處理. [參考內容](https://github.com/riscv) : >* RISC-V Platform-Level Interrupt Controller Specification >* Volume I: User-Level ISA >* Volume II: Privileged Architecture --- ## 本文大綱 [toc] --- ## Target and Hart RISC-V把hardware thread稱為hart,代表一個CPU內核中可能有多個hart,每一個hart又可以再接上M-mode externel interrupt & S-mode externel interrupt,不同的mode視為不同的context,意味著不同特權下,中斷來源是可以區分的,然而不同的中斷接線來源會直接影響CSR,產生不同的系統行為,後續[特權模式下中斷處理](https://hackmd.io/BZZXfv2DTl6xFQC-yHKuSQ?both#%E7%89%B9%E6%AC%8A%E6%A8%A1%E5%BC%8F%E4%B8%8B%E4%B8%AD%E6%96%B7%E8%99%95%E7%90%86)會進一步探討,後面內容就先以單純M-mode情況下來進行說明。 :::info :bulb: Target這一詞是包含hart,並且包含CPU以外裝置(ex. DMA) ::: 讀者到這裡可能開始好奇PLIC內可以設定這麼多中斷來源,但只接兩條線到CPU,這是怎麼做到的? 後續[Hardware Architecture](https://hackmd.io/BZZXfv2DTl6xFQC-yHKuSQ?both#PLIC-Hardware-Architecture)提到實際上的應用。  >* RISC-V Platform-Level Interrupt Controller Specification --- ## PLIC Hardware Architecture PLIC register table :  >* RISC-V Platform-Level Interrupt Controller Specification * Interrupt Enables registers,讀者可以把文中提及的context視為target,意味著每一個中斷可以決定在那些target上執行。 * RISC-V寫到可以支援到15872 targets,每個context占了128bytes,但實際上並不會有這麼多接口,每多一個就等於是chip面積的增加,所以要考量到實際狀況。  >* RISC-V Platform-Level Interrupt Controller Specification :::warning :bulb: PLIC 內總共有1024中斷源設置,其中source 0是hardwired to 0。 ::: 接下來就是要從block diagram中了解PLIC該如何去設定 1. 所有外部中斷觸發,會先抵達Gateway,由Gateway決定要不要送入PLIC內的pending array,最後由PLIC送出level訊號通知target。 2. 這之間會影響PLIC送出中斷訊號到target有三個數值會影響 * Enable register,若enable bit = 0,則不會送出訊號 * Priority register,若priority = 0,則不會送出訊號 * Priority Threshold value,若priority<threshold value,則不會送出訊號 3. claim的操作來取回相對應interrupt ID,同時清除pending array。 4. complete的操作為通知gateway,這時相同中斷來源才能再次被記錄在pending array :::info :bulb: 中斷來源沒有被Enable或者Priority = 0情況下,claim/complete是會被忽略的 :::  >* RISC-V Platform-Level Interrupt Controller Specification --- ## PLIC Interrupt handle flow 這個章節的探討會分成三個部分, * Single Target Interrupt Handle * Multi-Target Interrupt Handle * Peepmtive Handle --- ### Single Target Interrupt Handle 在介紹PLIC中斷如何交握之前,先說明CPU內核中會使用到相對應的CSR(Control and Status Registers),如先前所說明,目前內容都只先針對M-mode下進行探討。 ``` mtvec : 當進入異常時,會是fetch mtvec所指向的地址並繼續進行(這裡沒有探討vector interrupt使用) mcause : 紀載異常原因,用來判斷是否為外部中斷 mtval : 紀載異常原因的位置,或讀寫數值 mepc : 進入異常前PC所指向的地址,執行mret後會返回mepc mstatus : MPIE紀錄MIE當前數值,當執行mret時將MPIE存回MIE(global interrupt enable) mie : MEIE外部中斷開關 mip : MEIP外部中斷等待 ``` >* Volume II: Privileged Architecture :::success 在==Volume II: Privileged Architecture==中提到Trap,Trap觸發的定義包含了中斷(interrupt)與例外狀況(exception),中斷又可以細分為內部中斷(local interrupt)與外部中斷(external interrupt),PLIC所做的事情就是將外部中斷轉換成內部中斷給CPU進行處理。 ::: 有了上述準備可以開始探討CPU與PLIC之間中斷的交握與處裡 --- #### 中斷處理前的前置作業 * CPU 必須設定mtvec到trap handler * CPU CSR中mstatus.MIE與mie.MEIE開啟 * PLIC內enable相對應中斷 --- #### 中斷觸發到通知CPU * 中斷觸發進入到gateway決定是否送入PLIC core * PLIC根據設定決定是否通知CPU  >* RISC-V Platform-Level Interrupt Controller Specification --- #### CPU接收後處理流程 * CPU 收到interrupt通知後mip.MEIP舉起 * CPU 紀錄當前mepc * CPU fetch mtvec進入trap,將mstatus.MIE存進mstatus.MPIE,清除mstatus.MIE * CPU claim Interrupt ID進入ISR(interrupt service routine) * CPU interrupt handle * CPU send complete with interrupt ID * CPU 執行mret,mstatus.MPIE存回mstatus.MIE,回到mepc位置  >* Volume II: Privileged Architecture 然而context switch階段必要保留的GPR(General Purpose Register)就會是在CPU接受中斷後先進行push stack動作,結束ISR並且send complete後進行pop stack。 --- ### Multi-Target Interrupt Handle 綜觀上述的內容,讀者可以更清楚了解到在CPU上interrupt操作,衍伸到multi-target概念上也大致相同,但有幾個議題需要討論: * 假設PLIC上一個中斷源在多個target enable情況下,PLIC會一次notice所有符合設定的target,此時所有target接受到訊號後同時claim ID後會發生什麼事?  >* RISC-V Platform-Level Interrupt Controller Specification RISC-V允許所有target去race interrupt ID,在[你所不知道的memory map](https://hackmd.io/BZZXfv2DTl6xFQC-yHKuSQ?both#%E4%BD%A0%E6%89%80%E4%B8%8D%E7%9F%A5%E9%81%93%E7%9A%84Memory-map)會探討bus的實際狀況,當某個target得到ID後,pending array是會被清空的,這樣的話其他target得到的數值會是==0==,0在PLIC內定義就是沒有interrupt,這是特例情況,當所有target進入trap handle後要取得ID,但沒搶到資源情況得到0,這時就必須要對0做特殊處理,後續還會提及另一個例子。 * 考慮到affinity,interrupt可以選擇在某個target上作執行 --- ### Peepmtive Handle Note: local interrupt會根據mcause數值而有優先權分別 前面提及內容,跳過Priority Thresholds register,現在回顧到先前內容。 * CPU claim Interrupt * claim相當於告訴PLIC正在執行interrupt的交握,此時更新priority threshold value為目前處理中斷的優先權,下一筆中斷沒有大於threshold value就不會被PLIC送出。 * CPU interrupt handle * CPU complete with interrupt ID * 需要將threshold value更新為執行中斷前的數值 當中斷被PLIC送進CPU時會相當於local interrupt,此時mcause也會被更新成對應數值,此時MEIP也是舉起的。 當對PLIC發出claim會清除tip訊號,此時MEIP也會清除,這時高優中斷又被PLIC送進CPU時,MEIP再度舉起,CPU就會重複進入mtvec所設定地址。  >* Volume II: Privileged Architecture  >* Volume II: Privileged Architecture Local interrupt會根據號碼不同而有優先權之分,而有先後的執行順序,此時是hart的行為,就不是PLIC所控制的。 --- ## 你所不知道的Memory map 現在假設使用的裝置上的PLIC位置為0xE3000000,現在看以下一段程式碼 : ```clike= typedef struct plic plic_t plic_t *dev_plic = (plic_t *)(0xE3000000); dev_plic->int_enable = 0x2000; ``` 0xE3000000這個位置對於軟體來說就是直接用一個pointer指向這個位置就可以對這個位置修改,但硬體上是怎麼訂出的個區間,這要參考到何謂bus matrix。 參考下圖簡易[bus matrix](https://blog.csdn.net/uiojhi/article/details/112855204),通常會有不只一個的master,這個master通常是CPU或者是DMA,之間通訊採用[AMBA](https://developer.arm.com/Architectures/AMBA) 的AXI、AHB、APB,細部也可以參考SMP架構。 實際在RTL level時,會設定slave的base address,這就是所謂的memory map address,當軟體對於指標進行操作,就會由CPU發出到達bus matrix,經由仲裁後才會由PLIC接收,當對一個device的操作勢必會產生latency。 :::warning :bulb:Device access在系統上會設置inorder避免亂序 ::: 當多個master發出request或者CPU內核發出多個讀寫指令,bus matrix會呈現塞車的現象,未處理的項目會存在FIFO中等待,對於inorder的access來說,不同處理器架構下是可能會blocking pipeline。 針對塞車的狀況,大多的仲裁器會採用[round-robin](http://eportfolio.lib.ksu.edu.tw/~4000E039/blog?node=000000005)來分配bus service的狀況。 回到先前討論claim ID的部分,即使core 0先執行claim ID的動作,但未必會馬上到bus上取得PLIC內的ID,有可能會是其他core先抵達bus matrix,這也要取決於CPU內bus busy狀況。  --- 接下來討論一個實際案例,讀者是否有思考過如果已經在PLIC內enable的中斷源,經過一段時間後去作disable,這之間的探討非常細微,考量到bus matrix的latency,假設軟體端認知指令執行後,disable動作應該已經完成了,這就大錯特錯。 * Disable指令很有可能還停留在bus matrix時,interrupt發生 * CPU進入中斷處理後,disable訊息抵達PLIC * CPU準備claim ID,但此中斷已經被disable * CPU這時claim ID只會得到0,意味著如果沒有對於no interrupt value 0處理,系統就可能異常 這就是軟體開發需要對於bus matrix有一定概念的原因。 --- ## 特權模式下中斷處理 最後一個部分開始前一樣先介紹對應CSR,此章節就比較貼近作業系統模式切換,但當更了解單一模式下中斷處理後,對於特權模式的切換馬上就能駕輕就熟。 ``` stvec : 當進入異常時,會是fetch stvec所指向的地址並繼續進行(這裡沒有探討vector interrupt使用,與MMU) scause : 紀載異常原因,用來判斷是否為外部中斷 stval : 紀載異常原因的位置,或讀寫數值 sepc : 進入異常前PC所指向的地址,執行sret後會返回mepc sstatus : SPIE紀錄SIE當前數值,當執行sret時將SPIE存回SIE(global interrupt enable) sie : SEIE外部中斷開關 sip : SEIP外部中斷等待 ``` >* Volume II: Privileged Architecture :::warning :warning: S/U-mode的狀態下不能對M-mode CSR操作 ::: 首先S-mode下的CSR與M-mode下的大同小異,針對中斷處理上RISC-V提供了delegate的機制,意味著將S/U-mode下觸發的中斷委任給當前模式下處理,現在重新看以下ISA與CSR : ``` mret : M-mode下執行的return sret : S-mode下執行的return ``` ``` mstatus.MPP : 紀錄跳到trap前的privilege level sstatus.SPP : 紀錄跳到trap前的privilege level ``` 先前提及到的mret刻意跳過mstatus.MPP的變化,現在說明這個register是如何操作。 觀察到mideleg這個CSR,SEI bit是設定是否要將S-mode下觸發的外部中斷委任給S-mode自行處理 * 委任下中斷任務處理 * 委任後進入到trap,sstatus.SPP會記錄當前模式 * 此時中斷異常紀錄在scause,中斷等待紀錄在sip * 執行sret後恢復成sstatus.SPP所記錄的模式  >* Volume II: Privileged Architecture * 如果沒有委任又是怎麼樣的行為 * 進入到trap會跳回M-mode,變成mstatus.MPP紀錄先前模式 * 中斷狀況記錄在mcause,中斷等待紀錄在mip.SEIP * 執行mret恢復成mstatus.MPP模式 結合到U-mode情況下,作業系統是如何呼叫system-call,RISC-V提供ecall作為一個呼叫system-call的方式,同時M-mode delegate U-mode下發生的interrupt給S-mode代為處理,則就是呼叫system-call的行為本身。 在沒有設定delegate情況,高權限中斷可以打斷低權限正在執行的程序,如M-mode中斷打斷S-mode下所執行的程序,然而hart的限制,最多只能有兩層堆疊。 --- ```筆者第一次分享自己所學經驗貢獻開源社群,文筆有待加強,技術上有什麼地方需要糾正與交流的,隨時都歡迎與我linkedin聯繫。```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up