【原創】Linux PCI驅動框架分析(三)
阿新 • • 發佈:2021-01-09
背 景
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
歡迎關注個人公眾號,不定期分享技術文章: