Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(二)
根據(jù)上一章的概念,PCI驅(qū)動包括PCI通用的驅(qū)動,以及根據(jù)實際需要設(shè)備本身的驅(qū)動。
所謂的編寫設(shè)備驅(qū)動,其實就是編寫設(shè)備本身驅(qū)動,因為linux內(nèi)核的PCI驅(qū)動是內(nèi)核自帶的。
為了更好的學(xué)習(xí)PCI設(shè)備驅(qū)動,我們需要明白內(nèi)核具體做了什么,下面我們研究一下,linux PCI通用的驅(qū)動到底做了什么?
注:代碼對應(yīng)的 kernel-3.10.1
一、PCI 拓撲架構(gòu)
1.1 PCI的系統(tǒng)拓撲
在分析PCIe初始化枚舉流程之前,先描述下PCIe的拓撲結(jié)構(gòu)。
如下圖所示:
????????
整個PCIe是一個樹形的拓撲:
(1) root complex是樹的根,它一般實現(xiàn)了一個主橋設(shè)備(host bridge),一條內(nèi)部PCIe總線bus0,以及通過若干PCI bridge擴展出一些root port。host bridge可以完成CPU地址總線到PCI域地址的轉(zhuǎn)換,pci bridge用于系統(tǒng)擴展,沒有地址轉(zhuǎn)換功能;
(2) switch是轉(zhuǎn)換設(shè)備,目的是擴展PCIe總線。switch中有一個upstream port和若干個downstream port,每個端口相當于一個pci bridge;
(3) PCIe EP device是葉子節(jié)點設(shè)備,比如PCIe網(wǎng)卡,顯卡。NVMe卡等;
1.2 PCIe的軟件框架
PCIe模塊涉及到的代碼文件很多,在分析PCIe的代碼前,先對PCIe涉及的代碼梳理如下:
這里以arm架構(gòu)為例,PCIe代碼主要分散在3個目錄:
drivers/pci/*
drivers/acpi/pci/*
arch/arm/match-xxx/pci.c
將PCIe代碼按照如下層次劃分:
arch PCIe driver:放一些和架構(gòu)強相關(guān)的PCIe的函數(shù)實現(xiàn),對應(yīng)arch/arm/xxx/pci.c
acpi PCIe driver: acpi掃描時所涉及的PCIe代碼,包括host bridge的解析初始化,PCIe bus的創(chuàng)建,ecam的映射等,對應(yīng)drivers/acpi/pci*.c
PCIe core driver:PCIe的子系統(tǒng)代碼,包括PCIe的枚舉流程,資源分配流程,中斷流程等,主要對應(yīng)drivers/pci/*.c
PCIe port bus driver:PCIe port的四個service代碼的整合,四個service主要是指PCIe dpc/pme/aer/hp,對應(yīng)drivers/pci/pcie/*
PCIe ep driver:葉子節(jié)點的設(shè)備驅(qū)動,比如顯卡、網(wǎng)卡、NVMe;
二、Linux內(nèi)核實現(xiàn)
PCIe的代碼文件這么多,初始化涉及的調(diào)用也很多,從哪里開始看呢?
1. PCIe初始化流程
內(nèi)核通過initcore的level決定模塊的啟動順序:
cat System.map |grep pci|grep initcall
可以看出關(guān)鍵symbol的調(diào)用順序如下:
pcibus_class_init:注冊pci_bus_class,完成后創(chuàng)建了/sys/class/pci_bus目錄;
pci_driver_init:注冊pci_bus_type,完成后創(chuàng)建了/sys/bus/pci目錄;
acpi_pci_init:注冊acpi_pci_bus,并設(shè)置電源管理相應(yīng)的操作;
acpi_init():acpi啟動所涉及到的初始化流程,PCIe基于acpi的啟動流程從該接口進入;
下面對acpi_init()流程展開,主要找和PCI初始化相關(guān)的調(diào)用:
static int __init acpi_init(void)
{
...
pci_mmcfg_late_init();
acpi_scan_init();
...
acpi_pci_root_init();
...
static struct acpi_scan_handler pci_root_handler = {
.ids = root_device_ids,
.attach = acpi_pci_root_add,
.detach = acpi_pci_root_remove,
}
acpi_pci_link_init();
acpi_platform_init();
acpi_lpss_init();
acpi_container_init();
acpi_memory_hotplug_init();
acpi_dock_init();
...
acpi_ec_init();
acpi_debugfs_init();
acpi_sleep_proc_init();
acpi_wakeup_device_init();
...
}
mmcfg_late_init():acpi先掃描MCFG表,MCFG表定義了ecam的相關(guān)資源;
acpi_pci_root_init():定義pcie host bridge device的attach函數(shù),ACPI的definition block中使用PNP0A03表示一個PCI host bridge;
acpi_pci_link_init():注冊pci_link_handler,主要和PCIe IRQ相關(guān);
acpi_bus_scan():會通過acpi_walk_namespace()遍歷system中所有的device,并為這些acpi device創(chuàng)建數(shù)據(jù)結(jié)構(gòu),執(zhí)行對應(yīng)device的attach函數(shù)。根據(jù)ACPI spec定義,PCIe host bridge device定義在DSDT表中,acpi在掃描中掃描DSDT,如果發(fā)現(xiàn)了PCIe host bridge,就會執(zhí)行device對應(yīng)的attach函數(shù),調(diào)用acpi_pci_root_add();
acpi_pci_root_add():
(1)通過ACPI的SEG參數(shù),獲取host bridge使用的segment號,segment指的是PCIe domain,主要目的是為了突破PCIe最大256條bus的限制;
(2)通過ACPI的CRS里的bus range類型資源取得該host bridge的secondary總線范圍,保存在root->secondary這個resource中;
(3)通過ACPI的BNN參數(shù)獲取host bridge的根總線號;
printk(KERN_INFO PREFIX "%s [%s] (domain %04x %pR)\n",
acpi_device_name(device), acpi_device_bid(device),
root->segment, &root->secondary);
以上流程主要是獲取PCI設(shè)備的bdf號;
1. PCIe枚舉流程
我們先看內(nèi)核代碼:
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
struct acpi_device *device = root->device;
struct pci_root_info *info = NULL;
int domain = root->segment;
int busnum = root->secondary.start;
...
if (!setup_mcfg_map(info, domain, (u8)root->secondary.start,
(u8)root->secondary.end, root->mcfg_addr))
bus = pci_create_root_bus(NULL,busnum, &pci_root_ops, sd, &resources);
...
}
這個函數(shù)主要是建立ecam映射,將ecam的空間進行映射,這樣cpu就可以通過內(nèi)存訪問到相應(yīng)設(shè)備的配置空間;
pci_create_root_bus():用來創(chuàng)建該{segment: busnr}下的根總線。傳遞的參數(shù):
NULL:host bridge設(shè)備的parent節(jié)點;
busnum:總線號;
pci_root_ops:配置空間的操作接口;
resource:私有數(shù)據(jù),用來保存總線號,IO空間,mem空間等信息;
以下依次函數(shù)調(diào)用是:
pci_scan_child_bus()
+-> pci_scan_child_bus_extend()
+-> for dev range(0, 256)
pci_scan_slot()
+-> pci_scan_single_device()
+-> pci_scan_device()
+-> pci_bus_read_dev_vendor_id()
+-> pci_alloc_dev()
+-> pci_setip_device()
+-> pci_add_device()
+-> for each pci bridge
+-> pci_scan_bridge_extend()
更詳細的分析請參見后面的參考資料
總的來說,枚舉流程分為3步:
1. 發(fā)現(xiàn)主橋設(shè)備和根總線
2. 發(fā)現(xiàn)主橋設(shè)備下的所有PCI設(shè)備
3. 如果主橋下面的是PCI bridge,那么再次遍歷這個PCI bridge橋下的所有PCI設(shè)備,依次遞歸,直到將當前PCI總線樹遍歷完畢,返回host bridge的subordinate總線號。
3. PCIe的資源分配
PCIe設(shè)備枚舉完成后,PCI總線號已經(jīng)分配,PCIe ecam的映射、PCIe設(shè)備信息、bar的個數(shù)以及大小等已經(jīng)ready,但是此時并沒有給PCI device的bar、IO、mem分配資源。
這時就需要走到PCIe的資源分配流程,整個資源分配的過程就是從系統(tǒng)的總資源里給每個PCI device的bar分配資源。給每個PCI橋的base、limit的寄存器分配資源。
PCIe的資源分配流程整體比較復(fù)雜,主要介紹下總體的流程,對關(guān)鍵的函數(shù)再做展開。
PCIe資源分配的入口在pci_acpi_scan_root()->pci_bus_assign_resources(),詳細代碼如下:
void __ref __pci_bus_assign_resources(const struct pci_bus *bus,
struct list_head *realloc_head,
struct list_head *fail_head)
{
struct pci_bus *b;
struct pci_dev *dev;
pbus_assign_resources_sorted(bus, realloc_head, fail_head);
list_for_each_entry(dev, &bus->devices, bus_list) {
b = dev->subordinate;
if (!b)
continue;
__pci_bus_assign_resources(b, realloc_head, fail_head);
switch (dev->class >> 8) {
case PCI_CLASS_BRIDGE_PCI:
if (!pci_is_enabled(dev))
pci_setup_bridge(b);
break;
case PCI_CLASS_BRIDGE_CARDBUS:
pci_setup_cardbus(b);
break;
default:
dev_info(&dev->dev, "not setting up bridge for bus "
"%04x:%02x\n", pci_domain_nr(b), b->number);
break;
}
}
}
其中pbus_assign_resources_sorted,這個函數(shù)先對當前總線下設(shè)備請求的資源進行排序。
總而言之,PCIe的資源枚舉過程可以概括為如下:
1. 獲取上游PCI橋設(shè)備所管理的系統(tǒng)資源范圍;
2. 使用DFS對所有的pci ep device進行bar資源的分配;
3. 使用DFS對當前PCI橋設(shè)備的base limit的值,并對這些寄存器更新;
四、總結(jié)
1. 枚舉過程
主要是發(fā)現(xiàn)設(shè)備,主要流程如下:
1. 發(fā)現(xiàn)主橋設(shè)備和根總線
2. 發(fā)現(xiàn)主橋設(shè)備下的所有PCI設(shè)備
3. 如果主橋下面的是PCI bridge,那么再次遍歷這個PCI bridge橋下的所有PCI設(shè)備,依次遞歸,直到將當前PCI總線樹遍歷完畢,返回host bridge的subordinate總線號。
2. 資源分配過程
主要是管理設(shè)備,方便我們使用設(shè)備,主要流程如下:
1. 獲取上游PCI橋設(shè)備所管理的系統(tǒng)資源范圍;
2. 使用DFS對所有的pci ep device進行bar資源的分配;
3. 使用DFS對當前PCI橋設(shè)備的base limit的值,并對這些寄存器更新;
五、未完待續(xù)
Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(三),從內(nèi)核角度來說,一切皆文件,下面從總線、設(shè)備、驅(qū)動的角度,詳細看一下PCI設(shè)備如何變成文件的。
四、參考資料
https://blog.csdn.net/kunkliu/article/details/108950970
<PCI Express Base Specification Revision 5.0, Version 1.0>文章來源:http://www.zghlxwxcb.cn/news/detail-803640.html
https://pcisig.com/文章來源地址http://www.zghlxwxcb.cn/news/detail-803640.html
到了這里,關(guān)于Linux下PCI設(shè)備驅(qū)動開發(fā)詳解(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!