【原創】Linux PCI驅動框架分析(三)

背 景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器
  3. 使用工具: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_deviceplatform_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-namemsi1, msi0用於MSI中斷,intx用於舊式中斷,與interrupts中的中斷號對應;
  • reg:包含用於訪問PCIe控制器操作的寄存器物理地址和大小;
  • reg-name:分別表示Bridge registersPCIe Controller registersConfiguration space region,與reg中的值對應;
  • ranges:PCIe地址空間轉換到CPU的地址空間中的範圍;
  • bus-range:PCIe總線的起始範圍;
  • interrupt-map-maskinterrupt-map:標準PCI屬性,用於定義PCI接口到中斷號的映射;
  • legacy-interrupt-controller:舊式的中斷控制器;

2.2 probe流程

  • 系統會根據dtb文件創建對應的platform_device並進行註冊;
  • 當驅動與設備通過compatible字段匹配上後,會調用probe函數,也就是nwl_pcie_probe

看一下nwl_pcie_probe函數:

  • 通常probe函數都是進行一些初始化操作和註冊操作:
    1. 初始化包括:數據結構的初始化以及設備的初始化等,設備的初始化則需要獲取硬件的信息(比如寄存器基地址,長度,中斷號等),這些信息都從DTS而來;
    2. 註冊操作主要是包含中斷處理函數的註冊,以及通常的設備文件註冊等;

 

  • 針對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_domainmwl_pcie_enable_msi與中斷級聯相關,下個小節介紹;
  • pci_scan_root_bus_bridge:對總線上的設備進行掃描枚舉,這個流程在Linux PCI驅動框架分析(二)中分析過。brdige結構體中的pci_ops字段,用於指向PCI的讀寫操作函數集,當具體掃描到設備要讀寫配置空間時,調用的就是這個函數,由具體的Controller驅動實現;

2.3 中斷處理

PCIe控制器,通過PCIe總線連接各種設備,因此它本身充當一箇中斷控制器,級聯到上一層的中斷控制器(比如GIC),如下圖:

  • PCIe總線支持兩種中斷的處理方式:
    1. Legacy Interrupt:總線提供INTA#, INTB#, INTC#, INTD#四根中斷信號,PCI設備藉助這四根信號使用電平觸發方式提交中斷請求;
    2. 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控制器的中斷時,調用它的中斷處理函數,也就是上文中提到過的nwl_pcie_leg_handlernwl_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退出中斷級聯的處理;

2.4 總結

  • PCIe控制器驅動,各家的IP實現不一樣,驅動的差異可能會很大,單獨分析一個驅動畢竟只是個例,應該去掌握背後的通用框架;
  • 各類驅動,大體都是硬件初始化配置,資源申請註冊,核心是處理與硬件的交互(一般就是中斷的處理),如果需要用戶來交互的,則還需要註冊設備文件,實現一堆file_operation操作函數集;
  • 好吧,我個人不太喜歡分析某個驅動,草草收場了;

下篇開始,繼續迴歸到虛擬化,期待一下吧。

參考

Documentation/devicetree/bindings/pci/xlinx-nwl-pcie.txt

歡迎關注個人公衆號,不定期分享技術文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章