圖文詳解 PCI-PCIe 協議
作者:晏舒
RK3568 總線體系結構:
-
速率:APB<AHB<AXI;
-
AXI 支持讀寫並行操作;
-
AXI/AHB 支持多主 / 從設備,有總裁機制,APB 不支持;
Soc 廠商會做好 Address Mapping,當 CPU 對不同地址訪問時,由內存控制器將地址或者數據發到對應的總線最終到達最終的設備上。
Example:From Rockchip SPEC
爲什麼要引入 PCI/PCIe 總線:
-
提高設備訪問速率:硬件設計 + 協議;
-
提高擴展性;
-
設備訪問方式的改變;
CPU 內存地址空間和 PCI 總線域地址空間:
CPU 訪問 PCI 設備流程:
-
CPU 發出 CPU 內存域地址;
-
HOST 主橋 y 發現該地址屬於自己內部內存域地址和 PCI 地址的轉換範圍;
-
HOST 主橋 y 將該地址轉換爲 PCI 域地址;
-
根據該 PCI 域地址通過 PCI 橋 y1 最終找到 PCI 設備 y12;
PCI 設備之間的訪問流程:
-
PCI 設備 y11 發出 PCI 域地址;
-
該地址通過 PCI 橋 y1 找到掛載在 PCI 總線 y0 上的 PCI 設備 y02;
PCI 設備訪問 DDR 的流程:
-
PCI 設備 y11 嘗試將數據給到 DDR 地址空間;
-
HOST 主橋 y 接收到 PCI 設備 y11 嘗試訪問的地址,並將其轉換爲 CPU 域地址;
-
DMA 實現 PCI 設備數據到 DDR 的數據搬運(可以不使用 DMA,通過 CPU 進行數據搬運,但效率低);
PCI 的總線信號:
如果不是做 PCI 相關電路設計,則只需要關注上述幾個關鍵信號管腳:
-
AD[31:0]:Address/Data 複用信號線,PCI 總線事務在啓動後的第一個時鐘週期傳送地址,下一個時鐘週期傳送數據;
-
C/BE[3:0]#:Bus Command/Byte Enables,總線命令或者字節選通複用信號,地址週期表示命令,數據週期時輸出字節選通信號,使用這組信號可以對 PCI 設備進行單個字節、字和雙字訪問;
-
FRAME#:指示 PCI 總線事務的開始和結束;
-
IDSEL 信號:PCI 總線在進行配置讀寫總線事務時,使用該信號選擇 PCI 目標設備。
-
REQ#/GNT#:總線仲裁信號;
PCI 總線的存儲器讀寫總線事務:
PCI 設備的配置空間中,一共有 6 個 BAR 寄存器。BAR 寄存器存放 PCI 設備使用的 PCI 總線域的物理地址,通過 HOST 主橋完成內存域地址到 PCI 域地址的映射關係。
-
CPU 將要傳遞的數據放入通用寄存器,之後向 PCI 設備 y12 映射到的 CPU 域地址進行寫操作;
-
HOST 主橋 y 接收來自處理器的存儲器寫請求,處理器釋放總線,HOST 主橋將 CPU 域地址轉換爲 PCI 總線域的 PCI 總線地址,並向 PCI 總線 y0 發起 PCI 寫請求總線事務;
-
PCI 總線 y0 上的 PCI 設備 y01、y01 和 PCI 橋 y1 同時監聽這個 PCI 寫總線事務,PCI 橋 y1 接收這個寫總線事務;
-
PCI 橋 y1 上 PCI 設備同時監聽這個 PCI 寫總線事務,PCI 設備 y12 通過地址譯碼接收這個寫總線事務;
場景 2:PCI 設備 y12 向主存儲器寫數據,PCI 設備進行 DMA 寫操作。
-
PCI 設備 y12 將存儲器寫請求發向 PCI 總線 x1;
-
PCI 總線 y1 上的所有設備監聽這個請求;
-
PCI 橋 y1 發現當前總線事務使用的 PCI 地址不是其下游設備使用的 PCI 總線地址,則接收這個數據請求,並推送到上游;
-
PCI 總線 y0 上的所有 PCI 設備監聽這個請求;
-
HOST 主橋發現這個請求發向存儲器,則將來自 PCI 總線 y0 的 PCI 總線地址轉換爲存儲器地址,之後通過存儲器控制器將數據寫入存儲器;
PCI 橋和 PCI 設備的配置空間:
配置空間爲 PCI 橋設備或者 PCI Agent 設備上的一塊 ROM 區間。
PCI 橋:
PCI 橋跨接在兩個 PCI 總線之間,其中距離 HOST 主橋較近的 PCI 總線稱爲該橋片的上游總線(Primary Bus),距離 HOST 主橋較遠的 PCI 總線稱爲該橋片的下游總線(Secondary Bus)。
PCI 橋 y1 的上游總線爲 PCI 總線 y0,下游總線爲 PCI 總線 y1。
通過 PCI 橋組成一個胖樹結構,其中每個橋片都是父節點,而 PCI Agent 設備只能是子節點。
通過 PCI 橋可以擴展一條新的 PCI 總線,但是不能擴展新的 PCI 總線域。在上述 PCI 總線域 y 中,所有設備共享該總線域的地址空間大小。
PCI Agent 設備的配置空間:
- Device ID/Vendor ID:PCISIG 分配,Vendor ID 代表生產廠商,Device ID 代表這個廠商所生產的具體設備;
-
Header Type:8 位,第七位爲 1 表示多功能設備,爲 0 表示單功能設備。第 6~0 位表示當前配置空間的類型,0 爲 PCI Agent,1 爲 PCI 橋;
-
Base Address Register 0~5 寄存器:BAR 寄存器組,保存 PCI 設備使用的地址空間的基地址,該基地址是該設備在 PCI 總線域中的地址。PCI 設備復位後,該寄存器存放 PCI 設備需要使用的基地址空間大小;
以上述 VGS GD 5446 爲例,我們可以通過 mmap 映射 BAR Region0 來讀取 PCI 設備 BAR 1 對應空間的內容,當然對 BAR 1 空間地址的讀寫具體代表什麼含義要參考 GD 5446 的數據手冊了:
示例代碼:(我們可以通過內存訪問的方式去和 PCI 設備打交道了)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
#define MEMORY_REGION_SIZE 4096
#define READ_SIZE 100
int main() {
int fd;
off_t pci_addr = 0xfeb90000;
void *mmap_addr;
unsigned char *pci_data;
fd = open("/dev/mem", O_RDONLY);
if (fd == -1) {
perror("Error opening /dev/mem");
return EXIT_FAILURE;
}
mmap_addr = mmap(NULL, MEMORY_REGION_SIZE, PROT_READ, MAP_SHARED, fd, pci_addr);
if (mmap_addr == MAP_FAILED) {
perror("Error mmapping the device");
close(fd);
return EXIT_FAILURE;
}
pci_data = (unsigned char *)mmap_addr;
printf("Reading first 20 bytes of PCI BAR 0 at address 0xfc000000:\n");
for (int i = 0; i < READ_SIZE; i++) {
printf("%02hhx ", pci_data[i]);
}
printf("\n");
if (munmap(mmap_addr, MEMORY_REGION_SIZE) == -1) {
perror("Error unmapping memory");
close(fd);
return EXIT_FAILURE;
}
close(fd);
return 0;
}
sudo ./a.out
Reading first 20 bytes of PCI BAR 0 at address 0xfc000000:
20 20 00 00 07 f5 ff 00 00 00 00 00 67 00 06 01 00 00 00 00 1d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
PCI 橋的配置空間:
-
Subordinate Bus Number:存放當前 PCI 子樹中,編號最大的 PCI 總線號;
-
Secondart Bus Bumber:存放當前 PCI 橋 Secondary Bus 使用的總線號,這個 PCI 號是該 PCI 橋管理的 PCI 子樹中編號最小的 PCI 總線號;
-
Primary Bus Number:存放該 PCI 橋上游 PCI 總線號;
PCI 總線樹 BUS/Device 號的初始化:
以 X86 爲例,BIOS 或 UEFI 會負責 PCI 總線樹的遍歷,查找所有 PCI 設備,分配所需資源,比如內存地址空間,IO 空間和中斷等。之前提到 PCI 設備配置空間的初始化是通過 ID 譯碼的,因此這裏要講一下 BUS:Device.Function 是如何被定義的。
示例:
cuixujia.cxj@posix-team:~$ lspci -tv
-[0000:00]-+-00.0 Intel Corporation 440FX - 82441FX PMC [Natoma]
+-01.0 Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
+-01.1 Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
+-01.3 Intel Corporation 82371AB/EB/MB PIIX4 ACPI
+-02.0 Cirrus Logic GD 5446
+-03.0 Red Hat, Inc. Virtio network device
+-04.0 Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #1
+-04.1 Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #2
+-04.2 Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #3
+-04.7 Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #1
+-05.0 Red Hat, Inc. Virtio block device
\-06.0 Red Hat, Inc. Virtio memory balloon
如上 docker 所示,該系統只有一個 PCI 總線(總線 0),該總線下共有 6 個 Device,其中 Device1/4 有多個 Function。
BUS 號的初始化:
使用深度優先 DFS 算法遍歷 PCI 總線樹。
示例:
-
HOST 主橋掃描 PCI 總線 0 上的設備,首先忽略這條總線上的所有 PCI Agent 設備;
-
HOST 主橋發現了 PCI 橋 1,並將橋 1 的 Secondary Bus 命令爲 PCI 總線 1,。系統軟件初始化 PCI 橋 1 的配置空間,將 PCI 橋 1 的 Primary Bus Number 寄存器賦值爲 0,將 Secondary Bus Number 寄存器賦值爲 1,即 PCI 橋 1 的上游 PCI 總線號爲 0,下游 PCI 總線號爲 1;
-
掃描 PCI 總線 1,發現橋 2,並將 PCI 橋 2 的 Secondary Bus 命名爲 PCI 總線 2。系統軟件將初始化 PCI 橋 2 的配置空間,將 PCI 橋 2 的 Primary Bus Number 寄存器賦值爲 1,將 Secondary Bus Number 寄存器賦值爲 2;
-
掃描 PCI 總線 2,發現 PCI 橋 3,並將橋 3 的 Secondary Bus 命名爲 PCI 總線 3。系統軟件初始化 PCI 橋 3 的配置空間,將 PCI 橋 3 的 Primary Bus Number 寄存器賦值爲 2,將 Secondary Bus Number 寄存器賦值爲 3;
-
掃描 PCI 總線 3,沒有發現橋設備,表示 PCI 總線 3 下不能有新的總線,系統軟件將 PCI 橋 3 的 Subordinate Bus Numer 寄存器賦值爲 3。系統軟件在完成 PCI 總線 3 的掃描後,將回退到 PCI 總線 3 的上一級總線,即 PCI 總線 2,繼續掃描;
-
在重新掃描 PCI 總線 2 時,系統軟件發現 PCI 總線 2 上除了 PCI 橋 3 之外沒有發現新的 PCI 橋,而 PCI 橋 3 下所有設備已經掃描完成,此時系統軟件將 PCI 橋 2 的 Subordinate Bus Number 寄存器賦值爲 3,回退到 PCI 總線 1;
-
PCI 總線 1 上除了 PCI 橋 2 外,沒有其他橋片,於是繼續回退到 PCI 總線 0,並將 PCI 橋的 Subordinate Bus Number 寄存器賦值爲 3;
-
在 PCI 總線 0 上,系統軟件掃描到 PCI 橋 4,則首先將 PCI 橋 4 的 Primary Bus Number 寄存器賦值爲 0,將 Secondary Bus Number 寄存器賦值爲 4,即 PCI 橋 4 的上游總線號爲 0,下游總線號爲 4;
-
系統軟件發現 PCI 總線 4 上沒有 PCI 橋,結束對 PCI 總線 4 的掃描,將 PCI 橋 4 的 Subordinate Bus Number 寄存器賦值爲 4,回退到 PCI 總線 4 的上游總線 PCI 總線 0;
-
系統軟件發現在 PCI 總線 0 上的兩個橋片都已經掃描完成,結束對 PCI 總線的遍歷;
Device 號的分配:
Device 號的確定是通過 IDSEL 信號確定,但是這部分就和硬件相關了,而且在將 IDSEL 是如何確定 Device 號之間,必須要先看一下 PCI 的配置讀寫事務是如何工作的:
PCI 總線的配置讀寫事務:
PCI 總線定義了兩類配置請求,一類是 Type 01 配置請求,另一類是 Type 01 配置請求。
-
HOST 主橋或者 PCI 橋使用 Type 00h 配置請求,訪問與 HOST 主橋或者 PCI 橋直接相連的 PCI Agent 設備或者 PCI 橋。
-
HOST 主橋或者 PCI 橋使用 Type 01h 配置請求,訪問沒有與其直接兩連的 PCI Agent 或者 PCI 橋;
-
處理器首先將目標 PCI 設備的 ID 號保存在 CONFIG_ADDRESS(x86 方式)寄存器中,之後 HOST 主橋根據寄存器的 Bus Number 字段,決定是產生 Type 00h 還是 01h 配置請求。當 Bus Number 字段爲 0,將產生 Type 01h 配置請求,當 Bus Number 大於 0 時,產生 Type 01 配置請求;
CONFIG_ADDRESS 寄存器與 Type 01h 配置請求:
CONFIG_ADDRESS 寄存器與 Type 00h 配置請求:
繼續回到 Device 號的分配,在 Type 00h 配置請求中,Device Number 會給到地址線的 [31:11] 管腳上,如果在 PCI 插槽的硬件設計中,將同一 PCI 總線下的 PCI 設備的 IDSEL 管腳和不同該 PCI 設備的 AD 管腳相連,就可以確定 Device 號了。
PCI Agent 0 的 IDSEL 和 AD16 相連,PCI Agent 1 的 IDSEL 和 AD17 相連,PCI Agent 2 的 IDSEL 和 AD18 相連,這樣在 Type 00h 配置請求中 AD 信號就可以選中不同的 PCI 設備。
示例:
通過 X86 IO Port 操作 CONFIG_ADDRESS 和 CONFIG_DATA 來掃描 PCI 總線樹,獲取所有 PCI 設備及其配置空間的內容:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/io.h>
#define PCI_MAX_BUS 255
#define PCI_MAX_DEV 31
#define PCI_MAX_FUN 7
#define PCI_BASE_ADDR 0x80000000L
#define CONFIG_ADDR 0xcf8
#define CONFIG_DATA 0xcfc
typedef unsigned long DWORD;
typedef unsigned int WORD;
int main()
{
WORD bus, dev, fun;
DWORD addr, data;
printf("\nbus#\tdev#\tfun#\tvendor#\t\tdevice#\n");
iopl(3);
for (bus = 0; bus <= PCI_MAX_BUS; bus++) {
for (dev = 0; dev <= PCI_MAX_DEV; dev++) {
for (fun = 0; fun <= PCI_MAX_FUN; fun++)
{
addr = PCI_BASE_ADDR | (bus << 16) | (dev << 11) | (fun << 8);
outl(addr, CONFIG_ADDR);
data = inl(CONFIG_DATA);
if (((data & 0xFFFF) != 0xFFFF) && (data != 0))
{
printf("%02d \t%02d \t%02d \t", bus, dev, fun);
printf("%04x \t\t%04x", (data & 0xFFFF), (data & 0xFFFF0000) >> 16);
printf("\n--------------------------------------------\n");
}
}
}
}
iopl(0);
return 0;
}
PCI 設備 BAR 空間的初始化:
假設,存儲器域的 0xF000-0000 ~ 0xF7FF-FFFF(128M)物理地址空間與 PCI 總線的地址空間存在映射關係。
當處理器訪問這段儲存器地址空間時,HOST 主橋會認領這個存儲器訪問,通過 outbound 將該存儲器訪問使用的物理地址空間轉換爲 PCI 總線地址空間,並於 0x7000-0000 ~ 0x77FF-FFFF 這段 PCI 總線地址空間對應。
反過來也一樣,但是使用 Inbount 寄存器實現 PCI 總線地址到存儲器域地址的轉換,這裏就不畫了。
PCI 設備 BAR 寄存器和 PCI 橋 Base、Limit 寄存器的初始化:
假定所有 PCI Agent 只使用 BAR0 寄存器,申請的數據空間大小爲 16M(0x1000000B)。
-
根據 DFS 算法,首先尋找第一組 PCI 設備,分別爲 PCI 設備 31 和 PCI 設備 32,並根據這兩個設備需要的 PCI 空間大小,從 PCI 總線地址中(0x7000-0000 ~ 0x77FF-FFFF)爲這兩個 PCI 設備的 BAR0 寄存器分配基地址,分別爲 0x7000-0000 和 0x71000-0000。
-
當系統軟件完成 PCI 總線 3 下所有設備的 BAR 空間分配後,初始化 PCI 橋 3 的配置空間,這個 PCI 橋的 Memory Base 寄存器保存其下所有 PCI 設備使用的 PCI 總線域地址空間的基地址,Memory Limit 寄存器保存其下 PCI 設備使用的 PCI 總線域地址空間的大小。系統軟件將 Memory Base 寄存器賦值爲 0x7000-0000,而將 Memory Limit 寄存器賦值爲 0x200-0000。
-
系統軟件回朔到 PCI 總線 2,並找到 PCI 總線 2 上的 PCI 設備 21,並將 PCI 設備 21 的 BAR0 寄存器賦值爲 0x7200-0000。
-
完成 PCI 總線 2 的遍歷後,系統軟件初始化 PCI 橋 2 的配置寄存器,將 Memory Base 寄存器賦值爲 0x7000-0000,Memory Limit 寄存器賦值爲 0x300-0000。
-
系統軟件回朔到 PCI 總線 1,並找到 PCI 設備 11,並將這個設備的 BAR0 寄存器賦值爲 0x7300-0000。並將 PCI 橋 1 的 Memory Base 寄存器賦值爲 0x7000-0000,Memory Limit 寄存器賦值爲 0x400-0000。
-
系統軟件回朔到 PCI 總線 0,並在這條總線上發現另外一個 PCI 橋,即 PCI 橋 4。並使用 DFS 算法繼續遍歷 PCI 橋 4。首先系統軟件將遍歷 PCI 總線 4,並發現 PCI 設備 41 和 PCI 設備 42,並將這兩個 PCI 設備的 BAR0 寄存器分別賦值爲 0x7400-0000 和 0x7500-0000。
-
系統軟件初始化 PCI 橋 4 的配置寄存器,將 Memory Base 寄存器賦值爲 0x7400-0000,Memory Limit 寄存器賦值爲 0x200-0000。系統軟件再次回到 PCI 總線 0,這一次系統軟件沒有發現新的 PCI 橋,於是將初始化這條總線上的所有 PCI 設備。
-
PCI 總線 0 上只有一個 PCI 設備,PCI 設備 01。系統軟件將這個設備的 BAR0 寄存器賦值爲 0x7600-0000,並結束整個 DFS 遍歷過程。
PCIE 總線協議:
硬件接口:
PCIE 使用差分信號進行數據的串行傳輸以提高最大的傳輸速率,因此發送和接收共使用 4 條傳輸線,這 4 條傳輸線共同組成一個 Lane:
可以根據具體的 PCIe 外設,將 PCIe 鏈路配置成 X1、X2、X4...、X32 Lane:
數據傳輸方式:
相比於並行傳輸接口,使用串行數據傳輸方式,就只能使用數據包的方式進行數據傳輸,同時需要定義數據傳輸協議:
硬件拓撲:
PCIE 配置過程:
通過事務層的 TLP Header 決定當前發送的 TLP 的總線事務:
PCIE 總線樹的初始化:
-
從 Bus 0 開始掃描,首先嚐試讀到 0:0.0 設備的 Vendor ID,如果不成功表示沒有這個設備,就嘗試下一個設備 0:1.0。一個橋下面最多可以連接 32 個設備。
-
讀到了設備 0:0.0 的 Vendor ID,表明設備存在,再讀取其配置空間的 Header Typer,發現是 01h,表明它是一個橋設備;
-
發現了設備 A 是一個橋設備,然後配置其:
-
Primary Bus Number Register = 0:上游總線是 0;
-
Secondary Bus Number Register = 1:它自身發出的總線是 1;
-
Subordinate Bus Number Register = 255:先設置最大,因爲還不知道其下游總線的最大值;
-
發現橋 A,執行深度優先搜索:先去枚舉 A 下面的設備,再回來枚舉跟 A 同級的設備 B;
-
讀取設備 1:0.0(設備 C)的 Vendor ID,ID 存在,表明設備存在;
-
讀取其配置空間的 Header Type,發現爲 01h,表明這是一個橋設備;
-
配置橋 C:
-
Primary Bus Number Register = 1:上游總線是 1;
-
Secondary Bus Number Register = 2:它自身發出的總線是 2;
-
Subordinate Bus Number Register = 255:先設置最大,因爲還不知道其下游總線的最大值;
-
繼續從橋 C 執行深度優先配置,枚舉 Bus 2 下面的設備,從 2:0.0 開始;
-
讀取設備 2:0.0(設備 D) 的 Vendor ID,ID 存在,表明這個設備存在;
-
讀取其配置空間 Header Typer 是 01h,表示這是一個橋設備;
-
配置橋 D:
-
Primary Bus Number Register = 2:上游總線是 2;
-
Secondary Bus Number Register = 3:它自身發出的總線是 3;
-
Subordinate Bus Number Register = 255:先設置最大,因爲還不知道其下游總線的最大值;
-
繼續從橋 D 執行深度優先配置,枚舉 Bus 3 下面的設備,從 3:0.0 開始;
-
讀取設備 3:0.0 的 Vendor ID,ID 存在,表明這個設備存在;
-
讀取其配置空間 Header Typer 是 80h,表示這是一個 Endpoing,並且是一個多功能設備;
-
軟件枚舉這個設備的所有 8 個 Function,發現它有兩個 Function0 和 1;
-
軟件繼續枚舉 Bus 3 上其他設備,沒有發現更多設備;
-
現在已經掃描完橋 D 下的所有設備,它下面沒有橋,所有橋 D 的 Subordinate Bus Number 等於 3。掃描完 Bus 3 後,回退到上一級 Bus 2,繼續掃描其他設備,從 2:1.0 開始;
-
讀取 2:1.0 設備的 Vendor ID,ID 存在,即設備存在;
-
其 Header Typer 爲 01h,表明它是一個橋設備;
-
配置橋 E:
-
Primary Bus Number Register = 2:上游總線是 2;
-
Secondary Bus Number Register = 4:它自身發出的總線是 4;
-
Subordinate Bus Number Register = 255:先設置最大,因爲還不知道其下游總線的最大值;
-
繼續從橋 D 執行深度優先配置,枚舉 Bus 4 下的設備,從 4:0.0 開始;
-
讀取 4:0.0 設備的 Vendor ID,ID 存在,設備存在;
-
其 Header Typer 是 00h,表明是一個 Endpoing 設備,且是一個單功能設備;
-
軟件繼續枚舉 Bus 4 上其他設備,沒有發現更多設備;
-
已經枚舉完設備 E 即 Bus 4 下所有設備,更新設備 E 的 Subordinate Bus Number 爲 4,然後繼續掃描 E 的同級設備,沒有發現新的設備;
-
軟件更新設備 C 即 Bus 2 的橋,把它的 Subordinate Bus Number 設置爲 4,然後繼續掃描設備 C 的同級設備,沒有找到新的設備;
-
軟件更新設備 A 即 Bus 1 的橋,把它的 Subordinate Bus Number 設置爲 4,然後繼續掃描設備 A 的同級設備,發現橋設備 B;
-
配置橋 B:
-
Primary Bus Number Register = 0:上游總線是 0;
-
Secondary Bus Number Register = 5:它自身發出的總線是 5;
-
Subordinate Bus Number Register = 255:先設置最大,因爲還不知道其下游總線的最大值;
-
從橋 B 開始,繼續執行上述過程,知道全部遍歷完成;
MSI/MSI-X 中斷機制:
在講 MSI 和 MSI-X 中斷機制之前,需要先了解一下 Legacy 的中斷方式,即使用 INTx 的中斷。
PCI INTx 中斷觸發機制:
PCI 設備存在物理中斷線 INTA~INTD,使用邊帶信號將 INTx 和 SOC GPIO 相連,並配置 GIC 中斷向量。
通過對 PCI 設備的配置空間中 Interrupt Pin 配置來決定該 PCI 設備觸發中斷時使用的中斷線。
PCIe INTx 中斷觸發機制:
PCIe 不存在 INTx 中斷線,中斷信號通過 TLP 發送給 PCIe controller。
-
配置 Endpoing 的配置空間,決定其發送中斷時使用的 INTx;
-
Endpoing 發送中斷,中斷髮送通過 TLP 的方式給到 PCIe 控制器;
-
PCIe 控制器收到 INTx 中斷包,PCIe 控制器通過 Legacy 中斷引腳通知 GIC 中斷控制器;
- CPU 處理中斷,調用 rockchip_pcie_legacy_int_handler,處理 PCIe 設備發來的 INTA/INTB/INTC/INTD 中斷;
MSI 中斷機制:
MSI 可以使 PCI/PCIe 設備直接通過 PCI/PCIe 控制器的內存寫事務向 GIC 發起 ITS 中斷請求。
-
配置空間 0x34 的位置,存放第一個 Capability 的位置 A;
-
在配置空間 A 位置,找到第一個 Capablility:
-
第一個字節表示 ID,如果是 MSI 中斷,則 ID 爲 05H;
-
第二個字節表示下一個 Capablility 位置,爲 0 表示這是最後一個;
-
Message Address:表示 PCI/PCIe 設備觸發中斷時向哪個地址進行內存寫事務,這個地址由系統軟件在初始化 PCI 設備時分配;
-
Message Data:可以理解爲發出的中斷 ID,或者 EventID;
MSI-X 中斷機制:
MSI 中斷機制有一些缺陷:
-
每個設備只能最大支持 32 箇中斷;
-
中斷需要連續分配;
關於 DeviceID 如何傳送到 ITS:
其實到上面都比較好理解,但是唯獨有一個困惑,就是 GIC ITS 在轉換時還需要一個 DeviceID,但是 MSI Message 裏並沒有傳輸這個 DeivceID,那麼 ITS 如何知道這次 LPI 中斷時的設備呢?
這部分資料很少很少,找了大量資料,從以下文檔找到這這段話:
corelink_gic600_generic_interrupt_controller_technical_reference_manual_100336_0106_00_en.pdf
其實按照我的理解就是我們在配置 ITS 的 DeviceID 時,這個 DeviceID 是由 BDF 以規定的規則組成一個 Requester ID(這也解釋了爲什麼我們在 ITS 中註冊的 DeviceID 也必須是 RID),在 PCIE 觸發 MSI 中斷時,這個 Requester ID 也會跟隨總線傳輸到比如 AXI 總線上,ITS 在硬件設計中可能會使用比如 AWUSER 信號來獲取 DeviceID。上述設計都是 soc 在設計時決定的,Requester ID 的傳遞對用戶是透明的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5JMtjB9zMZLO-9TC2RXe0g