背 景
Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器
- 使用工具:Source Insight 3.5, Visio
1. 概述
先回顧一下PCIe的架構圖:
- 本文將講PCIe Host的驅動,對應爲
Root Complex
部分,相當於PCI的Host Bridge
部分; - 本文會選擇Xilinx的
nwl-pcie
來進行分析; - 驅動的編寫整體偏簡單,往現有的框架上套就可以了,因此不會花太多筆墨,點到爲止;
2. 流程分析
- 但凡涉及到驅動的分析,都離不開驅動模型的介紹,驅動模型的實現讓具體的驅動開發變得更容易;
- 所以,還是回顧一下上篇文章提到的驅動模型:Linux內核建立了一個統一的設備模型,分別採用總線、設備、驅動三者進行抽象,其中設備與驅動都掛在總線上,當有新的設備註冊或者新的驅動註冊時,總線會去進行匹配操作(
match
函數),當發現驅動與設備能進行匹配時,就會執行probe函數的操作;
《Linux PCI驅動框架分析(二)》
中提到過PCI設備、PCI總線和PCI驅動的創建,PCI設備和PCI驅動掛接在PCI總線上,這個理解很直觀。針對PCIe的控制器來說,同樣遵循設備、總線、驅動的匹配模型,不過這裏的總線是由虛擬總線platform
總線來替代,相應的設備和驅動分別爲platform_device
和platform_driver
;
那麼問題來了,platform_device
是在什麼時候創建的呢?那就不得不提到Device Tree
設備樹了。
2.1 Device Tree
- 設備樹用於描述硬件的信息,包含節點各類屬性,在dts文件中定義,最終會被編譯成dtb文件加載到內存中;
- 內核會在啓動過程中去解析dtb文件,解析成
device_node
描述的Device Tree
; - 根據
device_node
節點,創建platform_device
結構,並最終註冊進系統,這個也就是PCIe Host設備的創建過程;
我們看看PCIe Host的設備樹內容:
pcie: pcie@fd0e0000 {
compatible = "xlnx,nwl-pcie-2.11";
status = "disabled";
#address-cells = <3>;
#size-cells = <2>;
#interrupt-cells = <1>;
msi-controller;
device_type = "pci";
interrupt-parent = <&gic>;
interrupts = <0 118 4>,
<0 117 4>,
<0 116 4>,
<0 115 4>, /* MSI_1 [63...32] */
<0 114 4>; /* MSI_0 [31...0] */
interrupt-names = "misc", "dummy", "intx", "msi1", "msi0";
msi-parent = <&pcie>;
reg = <0x0 0xfd0e0000 0x0 0x1000>,
<0x0 0xfd480000 0x0 0x1000>,
<0x80 0x00000000 0x0 0x1000000>;
reg-names = "breg", "pcireg", "cfg";
ranges = <0x02000000 0x00000000 0xe0000000 0x00000000 0xe0000000 0x00000000 0x10000000 /* non-prefetchable memory */
0x43000000 0x00000006 0x00000000 0x00000006 0x00000000 0x00000002 0x00000000>;/* prefetchable memory */
bus-range = <0x00 0xff>;
interrupt-map-mask = <0x0 0x0 0x0 0x7>;
interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc 0x1>,
<0x0 0x0 0x0 0x2 &pcie_intc 0x2>,
<0x0 0x0 0x0 0x3 &pcie_intc 0x3>,
<0x0 0x0 0x0 0x4 &pcie_intc 0x4>;
pcie_intc: legacy-interrupt-controller {
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
關鍵字段描述如下:
compatible
:用於匹配PCIe Host驅動;msi-controller
:表示是一個MSI(Message Signaled Interrupt
)控制器節點,這裏需要注意的是,有的SoC中斷控制器使用的是GICv2版本,而GICv2並不支持MSI,所以會導致該功能的缺失;device-type
:必須是"pci"
;interrupts
:包含NWL PCIe控制器的中斷號;interrupts-name
:msi1, msi0
用於MSI中斷,intx
用於舊式中斷,與interrupts
中的中斷號對應;reg
:包含用於訪問PCIe控制器操作的寄存器物理地址和大小;reg-name
:分別表示Bridge registers
,PCIe Controller registers
,Configuration space region
,與reg
中的值對應;ranges
:PCIe地址空間轉換到CPU的地址空間中的範圍;bus-range
:PCIe總線的起始範圍;interrupt-map-mask
和interrupt-map
:標準PCI屬性,用於定義PCI接口到中斷號的映射;legacy-interrupt-controller
:舊式的中斷控制器;
2.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_hsot_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驅動框架分析(二)
中分析過。brdige
結構體中的pci_ops
字段,用於指向PCI的讀寫操作函數集,當具體掃描到設備要讀寫配置空間時,調用的就是這個函數,由具體的Controller驅動實現;
2.3 中斷處理
PCIe控制器,通過PCIe總線連接各種設備,因此它本身充當一箇中斷控制器,級聯到上一層的中斷控制器(比如GIC),如下圖:
- PCIe總線支持兩種中斷的處理方式:
- Legacy Interrupt:總線提供
INTA#, INTB#, INTC#, INTD#
四根中斷信號,PCI設備藉助這四根信號使用電平觸發方式提交中斷請求; - MSI(
Message Signaled Interrupt
) Interrupt:基於消息機制的中斷,也就是往一個指定地址寫入特定消息,從而觸發一箇中斷;
- Legacy Interrupt:總線提供
針對兩種處理方式,NWL PCIe
驅動中,實現了兩個irq_chip
,也就是兩種方式的中斷控制器:
irq_domain
對應一箇中斷控制器(irq_chip
),irq_domain
負責將硬件中斷號映射到虛擬中斷號上;- 來一張舊圖吧,具體文章可以去參考中斷子系統相關文章;
再來看一下nwl_pcie_enable_msi
函數:
- 在該函數中主要完成的工作就是設置級聯的中斷處理函數,級聯的中斷處理函數中最終會去調用具體的設備的中斷處理函數;
所以,稍微彙總一下,作爲兩種不同的中斷處理方式,套路都是一樣的,都是創建irq_chip
中斷控制器,爲該中斷控制器添加irq_domain
,具體設備的中斷響應流程如下:
- 設備連接在PCI總線上,觸發中斷時,通過PCIe控制器充當的中斷控制器路由到上一級控制器,最終路由到CPU;
- CPU在處理PCIe控制器的中斷時,調用它的中斷處理函數,也就是上文中提到過的
nwl_pcie_leg_handler
,nwl_pcie_msi_handler_high
,和nwl_pcie_leg_handler_low
; - 在級聯的中斷處理函數中,調用
chained_irq_enter
進入中斷級聯處理; - 調用
irq_find_mapping
找到具體的PCIe設備的中斷號; - 調用
generic_handle_irq
觸發具體的PCIe設備的中斷處理函數執行; - 調用
chained_irq_exit
退出中斷級聯的處理;
2.4 總結
- PCIe控制器驅動,各家的IP實現不一樣,驅動的差異可能會很大,單獨分析一個驅動畢竟只是個例,應該去掌握背後的通用框架;
- 各類驅動,大體都是硬件初始化配置,資源申請註冊,核心是處理與硬件的交互(一般就是中斷的處理),如果需要用戶來交互的,則還需要註冊設備文件,實現一堆
file_operation
操作函數集; - 好吧,我個人不太喜歡分析某個驅動,草草收場了;
下篇開始,繼續迴歸到虛擬化,期待一下吧。
參考
Documentation/devicetree/bindings/pci/xlinx-nwl-pcie.txt
歡迎關注個人公衆號,不定期分享技術文章: