5.1 先驗知識
驅動模型:Linux建立了一個統一的設備模型,分別采用總線、設備、驅動三者進行抽象,其中設備和驅動均掛載在總線上面,當有新的設備注冊或者新的驅動注冊的時候,總線會進行匹配操作(match函數),當發現驅動和設備能進行匹配的時候,就會執行probe函數的操作
對于PCI:PCI設備,PCI總線和PC驅動的創建,PCI設備和PCI驅動掛接在PCI總線上面
對于PCIe的控制器,遵循設備,總線,驅動的匹配模型,這里的總線是由虛擬總線platform總線來替代,相應的設備和驅動分別為platform_device和platform_driver;
5.2 PCIe驅動程序的實現
5.2.1 Linux PCI的初始化過程
Linux PCI初始化的主要工作是遍歷當前處理器系統中的所有PCI總線樹,并且初始化PCI總線樹上面的全部設備,包括PCI橋和PCI Agent設備。在Linux系統中,多次使用DFS算法對PCI總線樹進行遍歷查找,并分配相關的PCI總線號與PCI總線地址資源。
*參考博客:*【原創】Linux PCI驅動框架分析(三) - LoyenWang - 博客園
1.設備樹
設備樹用于描述硬件的信息,包含節點各類屬性,在dts文件中定義,最終編譯為dtb文件加載到內存中
內核會在啟動過程中解析dtb文件,解析為device_node描述的Device Tree;
根據device_node節點,創建platform_device結構,并最終注冊進系統,這個也就是PCIe設備的創建過程。
2.probe流程
系統會根據dtb文件創建對應的platform_device并進行注冊;
當驅動與設備通過compatible字段匹配上后,會調用probe函數,也就是nwl_pcie_probe;
看一下nwl_pcie_probe函數:
通常probe函數都是進行一些初始化操作和注冊操作:
-
初始化包括:數據結構的初始化以及設備的初始化等,設備的初始化則需要獲取硬件的信息(比如寄存器基地址,長度,中斷號等),這些信息都是從DTS獲取
-
注冊操作主要是包含中斷處理函數的注冊,以及通常的設備文件注冊等。
針對PCI控制器的驅動,核心的流程是需要分配并且初始化一個*pci_host_bridge*結構,最終通過這個*bridge*去枚舉PCI總線上的所有設備;
*devm_pci_alloc_host_bridge*:分配并且初始化一個基礎的pci_host_bridge結構
*nwl_pcie_parse_dt*:獲取DTS中的寄存器信息以及中斷信息,并且通過*irq_set_chained_handler_and_data*設置intx中斷號對應的中斷處理函數,該處理函數用于中斷的級聯;
*nwl_pcie_bridge_init*:硬件的Controller一堆設置,這部分需要查閱Spec,了解硬件工作細節。此外,通過*devm_request_irq*注冊misc中斷號對應的中斷處理函數,該處理函數,該處理函數用于控制器自身狀態的處理;
*pci_parse_request_of_pci_ranges*:用于解析PCI總線的總線范圍和總線上的地址范圍,也就是CPU可以看到的地址區域
*nwl_pcie_init_irq_domain*和*mwl_pcie_enable_msi*與中斷級聯相關
pci_scan_root_bus_bridge:對總線上的設備進行*掃描枚舉*,(枚舉具體介紹:【原創】Linux PCI驅動框架分析(二) - LoyenWang - 博客園)bridge結構體中的pci_ops字段,用于指向PCI的讀寫操作函數集,當具體掃描到設備要讀寫的配置空間的時候,調用的就是這個函數,由具體的Controller驅動實現
3.中斷處理
PCIe控制器,通過PCIe總線連接各種設備,因此它本身充當一個中斷控制器,級聯到上一層的中斷控制器(比如GIC),如下圖:
PCIe總線支持兩種中斷的處理方式:
-
Legacy Interrupt:總線提供INTA#,INTB#,INTC#,INTD#四根中斷信號,PCI設備借助這四根信號使用電平觸發方式提交中斷請求;
-
MSI(Message Signaled Interrupt)Interrupt:基于消息機制的中斷,也就是往一個指定地址寫入特定消息,從而觸發一個中斷;
針對兩種處理方式,NWL PCIe驅動中,實現了兩個irq_chip,也就是兩種方式的中斷控制器:
irq_domain對應一個中斷控制器(irq_chip),*irq_domain負責將硬件中斷號映射到虛擬中斷號上面*;
再來看一下nwl_pcie_enable_msi函數:
在該函數中主要完成的工作就是設置級聯的中斷處理函數,級聯的中斷處理函數中最終回去調用具體設備的中斷處理函數;
總結,作為兩種不同的中斷處理方式,套路都是一樣的,均為創建irq_chip中斷控制器,為該中斷控制器添加irq_domain,具體設備的中斷響應流程如下:
1)設備連接在PCI總線上面,觸發中斷的時候,通過PCIe控制器充當的中斷控制器路由到上一級控制器,最終路由到CPU;
2)CPU在處理PCIe控制器的中斷時,調用它的中斷處理函數,也就是上文中提到過的new_pcie_leg_handler,nwl_pcie_msi_handler_high,和nwl_pcie_leg_handler_low;
3)在級聯的中斷處理函數中,調用chained_irq_enter進入中斷級聯處理;
4)調用irq_find_mapping找到具體的PCIe設備的中斷號;
5)調用generic_handle_irq觸發具體的PCIe設備的中斷處理函數執行;
6)調用chained_irq_exit退出中斷級聯的處理
4.總結
各類的驅動,大體都是硬件初始化配置,資源申請注冊,核心時處理和硬件的交互(一般就是中斷的處理),如果需要用戶來進行交互,則還需要注冊設備文件,實現一堆file_operation操作函數集。
PCI驅動程序實現關鍵數據結構
PCI設備有三種地址空間:
PCI的I/O空間
PCI的存儲空間
PCI的配置空間。
CPU可以訪問PCI設備上的所有地址空間,其中I/O空間和存儲空間提供給設備驅動程序使用,而配置空間則由Linux內核中的PCI初始化代碼使用。內核在啟動時負責對所有的PCI設備進行初始化,配置好所有的PCI設備,包括中斷號以及I/O基址,并在文件 /proc/pci中列出所有找到的PCI設備,以及這些設備的參數和屬性