iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
Software Development

RISC-V: 深入淺出從入門到放棄系列 第 26

DAY26: RISC-V: riscv is getting MSI

  • 分享至 

  • xImage
  •  
tags: 鐵人賽

前言

今日的這篇是參考了某個國外網站做的統整,筆者認為挺不錯的,因此也翻譯之後做整理

overview

MSI 不需要 interrupt request pin 的情況下發出interrupt signal,MSI最常見的用途之一是 PCI 匯流排,PCI 規範定義了 MSI 和 MSI-X 標準。潛在的好處包含:1.減少從設備到 CPU 或 interrupt controller的direct wires 數量。

Message Signaled Interrupts (MSI)

MSI 由 message 觸發,是 memory write 的一種術語,事實上我們可以透過dereference MMIO poniter 來觸發 message。

Memory Mapped IO Addresses for Interrupt Files

上述程式碼寫入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

Triggering an Interrupt by Sending a Message

設備將字寫入特定MMIO的地址後,會觸發interrupt。這表示設備不需要將其連接到IRQ 控制器(例如: PLIC),反之,設備只需要進行 memory write 便可以讓它觸發 interrupt

雖然觸發message很簡單,但我們仍需要一種機制來啟用這些message並其進行優先順序的排序,在某些情況下,我們可能不想知道某些message,此時便能發會IMSIC發會作用的地方。

Incoming MSI Controller (IMSIC)

為了能夠支援MSI,某些設備要需要對memory進行寫入,並且將其轉成interrupt,此外,設備還需要提供一種機制來啟用或停用interrupt並確認interupt的優先順序,就像一般的中斷控制器,而這將由IMSIC來完成。

警告:AIA仍然在進行中,並且有重大的更改或是添加CSR相關訊息,所以有些程式碼或表格已經過時了。

IMSIC的暫存器機制將由CSR以及透過access內部暫存器機制所組成。

Newly Added Registers

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傳遞,該站存標含以下三個值之一:

Enabling the IMSIC

因此,我們需要寫一到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);

Interrupt Priorities

AIA 使用message 本身作為優先級別順序,因此message 1則具有優先順序1,而message 1234則具有優先順序1234,這更方便因為可以直接控制message,然而由於每個message 都有enable 以及pending bit,因此最高interrupt ID 有限制,該規範最多有 32*64-1=2047的message。

Enabling Messages

暫存器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);

Pending Messages

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);
}

Testing

現在我們可以啟用傳送單個message,我們可以透過下面兩種方式來觸發他們

  1. 將message直接寫入MMIO地址
  2. 將相對應的message bit舉起
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 Traps

當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。

Example Output

如果你使用新的QEMU,在成功運行之後會看到以下內容

Conclusion

在這邊文章中,我們看到了risc-v為了支援IMSIC而新增了CSR,我們還啟用IMSIC delivery,並且使用了兩種方式發送message的方式:

  1. 直接透過MMIO
  2. 設置相對應EIP

最後我們處理了trap interrupt

reference

RISC-V Is Getting MSIs!


上一篇
DAY25: RISC-V: MSI ID & page table
下一篇
Day27: 你所應該知道的 perf 介紹及使用
系列文
RISC-V: 深入淺出從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言