鐵人賽
今日的這篇是參考了某個國外網站做的統整,筆者認為挺不錯的,因此也翻譯之後做整理
MSI 不需要 interrupt request pin 的情況下發出interrupt signal,MSI最常見的用途之一是 PCI 匯流排,PCI 規範定義了 MSI 和 MSI-X 標準。潛在的好處包含:1.減少從設備到 CPU 或 interrupt controller的direct wires 數量。
MSI 由 message 觸發,是 memory write 的一種術語,事實上我們可以透過dereference MMIO poniter 來觸發 message。
上述程式碼寫入MMIO的地址(0x2400_0000),qemu的virt 機器在該地址連接hart 0的M mode IMSIC。hart 0的S mode IMSIC連接至0x2800_0000,每個hart彼此隔著一page大小,這表示hart 1的 M mode IMSIC連接至0x2400_1000,hart 1的S mode IMSIC 連接至0x2800_1000。
對於許多 embedded system(嵌入式系統),這些 value來自於規範或 FDT(flattened device tree)的開放 firmware,該 tree 指定 MMIO 的地址和連接到他的內容,以下是 qemu 的 virt-FDT 文字範例
完整device tree 格式: https://www.devicetree.org/
有關linux中的device tree 資訊: https://www.kernel.org/doc/html/latest/devicetree/usage-model.html
Qemu's repo: https://github.com/qemu
設備將字寫入特定MMIO的地址後,會觸發interrupt。這表示設備不需要將其連接到IRQ 控制器(例如: PLIC),反之,設備只需要進行 memory write 便可以讓它觸發 interrupt。
雖然觸發message很簡單,但我們仍需要一種機制來啟用這些message並其進行優先順序的排序,在某些情況下,我們可能不想知道某些message,此時便能發會IMSIC發會作用的地方。
為了能夠支援MSI,某些設備要需要對memory進行寫入,並且將其轉成interrupt,此外,設備還需要提供一種機制來啟用或停用interrupt並確認interupt的優先順序,就像一般的中斷控制器,而這將由IMSIC來完成。
警告:AIA仍然在進行中,並且有重大的更改或是添加CSR相關訊息,所以有些程式碼或表格已經過時了。
IMSIC的暫存器機制將由CSR以及透過access內部暫存器機制所組成。
AIA定義了幾個新的CSR,將machine mode以及supervisor mode分開
下面介紹新增暫存器:
Misecect以及mireg允許我們透過寫入Misecect來選擇暫存器,然後透過Mireg來表示被選到的暫存器。
舉個例子:如果我們從Mireg讀取,則我們從被選到的暫存器讀取,如果我們寫入Mireg,則我們將寫入被選到的暫存器。
下面有四個暫存器可選,這些暫存器分成machine mode以及supervisor mode。例如:如果我們寫入Siselect,我們便是使用superviosr mode的版本。
暫存器可以由Miselect/Sisecect選擇,並透過Mireg/Sireg來讀取/寫入
我們需要做的第一件事便是啟用IMSIC,而這是透過EIDELIVERY來完成,該暫存器用來啟動interrupt傳遞,該站存標含以下三個值之一:
因此,我們需要寫一到EIDELIVERY:
// First, enable the interrupt file
// 0 = disabled
// 1 = enabled
// 0x4000_0000 = use PLIC instead
imsic_write(MISELECT, EIDELIVERY);
imsic_write(MIREG, 1);
透過EITHRESHOLD來建立一個閥值,interrupt優先權須達到此閥值才能被使用,如果interrupt 優先級小於EITHRESHOLD的值,才會被使用(此處指的是至少interrupt優先順序應大於此閥值才能使用),否則便不會被使用。例如:EITHRESHOLD的為5而此時只有範圍1~4才會被使用。message 0 表示沒有message。
由於較大的閥值能使用更多message,因此數字較小的message表示具有較高優先順序。
// Set the interrupt threshold.
// 0 = enable all interrupts
// P = enable < P only
imsic_write(MISELECT, EITHRESHOLD);
// Only hear 1, 2, 3, and 4
imsic_write(MIREG, 5);
AIA 使用message 本身作為優先級別順序,因此message 1則具有優先順序1,而message 1234則具有優先順序1234,這更方便因為可以直接控制message,然而由於每個message 都有enable 以及pending bit,因此最高interrupt ID 有限制,該規範最多有 32*64-1=2047的message。
暫存器EIE是用來控制是否要啟用或停用message。對於RV64,這些暫存器寬度為64bit,但仍然佔用兩格相鄰的暫存器號碼,因此對於RV64,只能使用偶數的暫存器碼(如:EIE0、EIE2...EIE62),如果嘗試使用奇數的EIE會得到無效的instruction trap,對於RV32來說,EIE只有32bit,EIE0到EIE63都是可使用的。
EIE是一個bitset,如果相對應的message的bit為1,則它被取消mask並且啟用,反之則被mask並停用。
對於RV64,message0到message63都在EIE0[63:0]當中,bit是message。我們可用以下公式來確認RV64使用哪個暫存器。
// Enable a message number for machine mode (RV64)
fn imsic_m_enable(which: usize) {
let eiebyte = EIE0 + 2 * which / 64;
let bit = which % 64; imsic_write(MISELECT, eiebyte);
let reg = imsic_read(MIREG);
imsic_write(MIREG, reg | 1 << bit);
}
RV32 行為與64相似,只是我們不用將其縮放兩倍
// Enable a message number for machine mode (RV32)
fn imsic_m_enable(which: usize) {
let eiebyte = EIE0 + which / 32;
let bit = which % 32;
imsic_write(MISELECT, eiebyte);
let reg = imsic_read(MIREG);
imsic_write(MIREG, reg | 1 << bit);
}
有了上面的程式碼之後,我們可以啟用我們需要的message,下面啟用message2、4、10為範例:
imsic_m_enable(2);
imsic_m_enable(4);
imsic_m_enable(10);
EIP與EIE行為相同,只是當bit被舉起時表示特定message 處於pending狀態,這代表發送一個該編號的message對IMSIC進行寫入的動作。如果我們從EIP進行讀取,我們可以確定哪些message處於pending狀態;如果我們寫入它,我們可以透過相對應的message的bit手動觸發interrupt message。
// Trigger a message by writing to EIP for Machine mode in RV64
fn imsic_m_trigger(which: usize) {
let eipbyte = EIP0 + 2 * which / 64;
let bit = which % 64; imsic_write(MISELECT, eipbyte);
let reg = imsic_read(MIREG);
imsic_write(MIREG, reg | 1 << bit);
}
現在我們可以啟用傳送單個message,我們可以透過下面兩種方式來觸發他們
unsafe {
// We are required to write only 32 bits.
// Write the message directly to MMIO to trigger
write_volatile(0x2400_0000 as *mut u32, 2);
} // Set the EIP bit to trigger imsic_m_trigger(2);
imsic_m_trigger(2);
當message 發送到IMSIC時,它將作為external interrupt發送到指定的hart。對於machine mode IMSIC,將作為非同步原因11,對於supervisor mode IMSIC,將作為非同步原因9。
當我們收到由發送message所引發的interrupt時,我們需要根據特權模式從MTOPERI或是STOPEI讀取,進而pop off最高level的peding interrupt。這裡提供一個value其中bit26:16message編號,bit 10:0為interrupt 優先順序,message 編號與message 優先順序具有相同位數,因此我們可以選擇其中之一。
// Pop the top pending message
fn imsic_m_pop() -> u32 {
let ret: u32;
unsafe {
// 0x35C is the MTOPEI CSR.
asm!("csrrw {retval}, 0x35C, zero", retval = out(reg) ret),
}
// Message number starts at bit 16
ret >> 16
}
我的compiler不支援此規範的CSR名稱,因此我使用了CSR編號,總之0x35C就是mtopei,當我們從MTOPEI(0x35C)讀取時,它將為我們提供最高優先順序message的message 號碼,csrrw會自動將CSR的值讀取到暫存器當中,然後將0儲存到CSR當中。
當我們將0寫入MTOPEI(0x35c)時,我們告訴IMSIC目前正在處理最高level的message(claim的動作),這也會清除相對應message 號碼的EIP bit
message 0不是有效的message。
如果你使用新的QEMU,在成功運行之後會看到以下內容
在這邊文章中,我們看到了risc-v為了支援IMSIC而新增了CSR,我們還啟用IMSIC delivery,並且使用了兩種方式發送message的方式:
最後我們處理了trap interrupt