擊左上方藍色“一口Linux”,選擇“設為星標”
第一時間看干貨文章? ?【干貨】嵌入式驅動工程師學習路線 ?【干貨】Linux嵌入式知識點-思維導圖-免費獲取 ?【就業】一個可以寫到簡歷的基于Linux物聯網綜合項目 ?【就業】找工作簡歷模版
作者:晏舒

速率:APB
AXI支持讀寫并行操作;
AXI/AHB支持多主/從設備,有總裁機制,APB不支持;
Soc廠商會做好Address Mapping,當CPU對不同地址訪問時,由內存控制器將地址或者數據發到對應的總線最終到達最終的設備上。
Example:From Rockchip SPEC

提高設備訪問速率:硬件設計+協議;
提高擴展性;
設備訪問方式的改變;


CPU發出CPU內存域地址;
HOST主橋y發現該地址屬于自己內部內存域地址和PCI地址的轉換范圍;
HOST主橋y將該地址轉換為PCI域地址;
根據該PCI域地址通過PCI橋y1最終找到PCI設備y12;

PCI設備y11發出PCI域地址;
該地址通過PCI橋y1找到掛載在PCI總線y0上的PCI設備y02;

PCI設備y11嘗試將數據給到DDR地址空間;
HOST主橋y接收到PCI設備y11嘗試訪問的地址,并將其轉換為CPU域地址;
DMA實現PCI設備數據到DDR的數據搬運(可以不使用DMA,通過CPU進行數據搬運,但效率低);

如果不是做PCI相關電路設計,則只需要關注上述幾個關鍵信號管腳:
AD[31:0]:Address/Data 復用信號線,PCI總線事務在啟動后的第一個時鐘周期傳送地址,下一個時鐘周期傳送數據;
C/BE[3:0]#:Bus Command/Byte Enables,總線命令或者字節選通復用信號,地址周期表示命令,數據周期時輸出字節選通信號,使用這組信號可以對PCI設備進行單個字節、字和雙字訪問;

FRAME#:指示PCI總線事務的開始和結束;
IDSEL信號:PCI總線在進行配置讀寫總線事務時,使用該信號選擇PCI目標設備。
REQ#/GNT#:總線仲裁信號;
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 Agent設備上的一塊ROM區間。

PCI橋跨接在兩個PCI總線之間,其中距離HOST主橋較近的PCI總線稱為該橋片的上游總線(Primary Bus),距離HOST主橋較遠的PCI總線稱為該橋片的下游總線(Secondary Bus)。

PCI橋y1的上游總線為PCI總線y0,下游總線為PCI總線y1。
通過PCI橋組成一個胖樹結構,其中每個橋片都是父節點,而PCI Agent設備只能是子節點。
通過PCI橋可以擴展一條新的PCI總線,但是不能擴展新的PCI總線域。在上述PCI總線域y中,所有設備共享該總線域的地址空間大小。

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
#include
#include
#include
#include
#include
#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
Subordinate Bus Number:存放當前PCI子樹中,編號最大的PCI總線號;
Secondart Bus Bumber:存放當前PCI橋Secondary Bus使用的總線號,這個PCI號是該PCI橋管理的PCI子樹中編號最小的PCI總線號;
Primary Bus Number:存放該PCI橋上游PCI總線號;
以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。
使用深度優先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號的確定是通過IDSEL信號確定,但是這部分就和硬件相關了,而且在將IDSEL是如何確定Device號之間,必須要先看一下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
#include
#include
#include
#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;
}假設,存儲器域的0xF000-0000 ~ 0xF7FF-FFFF(128M)物理地址空間與PCI總線的地址空間存在映射關系。
當處理器訪問這段儲存器地址空間時,HOST主橋會認領這個存儲器訪問,通過outbound將該存儲器訪問使用的物理地址空間轉換為PCI總線地址空間,并于0x7000-0000 ~ 0x77FF-FFFF這段PCI總線地址空間對應。
反過來也一樣,但是使用Inbount寄存器實現PCI總線地址到存儲器域地址的轉換,這里就不畫了。

假定所有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使用差分信號進行數據的串行傳輸以提高最大的傳輸速率,因此發送和接收共使用4條傳輸線,這4條傳輸線共同組成一個Lane:

可以根據具體的PCIe外設,將PCIe鏈路配置成X1、X2、X4...、X32 Lane:

相比于并行傳輸接口,使用串行數據傳輸方式,就只能使用數據包的方式進行數據傳輸,同時需要定義數據傳輸協議:



通過事務層的TLP Header決定當前發送的TLP的總線事務:


從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中斷機制之前,需要先了解一下Legacy的中斷方式,即使用INTx的中斷。
PCI設備存在物理中斷線INTA~INTD,使用邊帶信號將INTx和SOC GPIO相連,并配置GIC中斷向量。

通過對PCI設備的配置空間中Interrupt Pin配置來決定該PCI設備觸發中斷時使用的中斷線。

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可以使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中斷機制有一些缺陷:
每個設備只能最大支持32個中斷;
中斷需要連續分配;

其實到上面都比較好理解,但是唯獨有一個困惑,就是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的傳遞對用戶是透明的。

end
一口Linux?
關注,回復【1024】海量Linux資料贈送
精彩文章合集
文章推薦