深入分析Linux PCI驅動框架(三)
說明:
Kernel版本:4.14
ARM64處理器
使用工具:Source Insight 3.5, Visio
1. 概述
先回顧一下PCIe的架構圖:

本文將講PCIe Host的驅動,對應為
Root Complex
部分,相當于PCI的Host Bridge
部分;本文會選擇Xilinx的
nwl-pcie
來進行分析;驅動的編寫整體偏簡單,往現(xiàn)有的框架上套就可以了,因此不會花太多筆墨,點到為止;
2. 流程分析
但凡涉及到驅動的分析,都離不開驅動模型的介紹,驅動模型的實現(xiàn)讓具體的驅動開發(fā)變得更容易;
所以,還是回顧一下上篇文章提到的驅動模型:Linux內核建立了一個統(tǒng)一的設備模型,分別采用總線、設備、驅動三者進行抽象,其中設備與驅動都掛在總線上,當有新的設備注冊或者新的驅動注冊時,總線會去進行匹配操作(
match
函數(shù)),當發(fā)現(xiàn)驅動與設備能進行匹配時,就會執(zhí)行probe函數(shù)的操作;

《Linux PCI驅動框架分析(二)》
中提到過PCI設備、PCI總線和PCI驅動的創(chuàng)建,PCI設備和PCI驅動掛接在PCI總線上,這個理解很直觀。針對PCIe的控制器來說,同樣遵循設備、總線、驅動的匹配模型,不過這里的總線是由虛擬總線platform
總線來替代,相應的設備和驅動分別為platform_device
和platform_driver
;
那么問題來了,platform_device
是在什么時候創(chuàng)建的呢?那就不得不提到Device Tree
設備樹了。
【文章福利】小編推薦自己的Linux內核技術交流群:【749907784】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


2.1 Device Tree
設備樹用于描述硬件的信息,包含節(jié)點各類屬性,在dts文件中定義,最終會被編譯成dtb文件加載到內存中;
內核會在啟動過程中去解析dtb文件,解析成
device_node
描述的Device Tree
;根據(jù)
device_node
節(jié)點,創(chuàng)建platform_device
結構,并最終注冊進系統(tǒng),這個也就是PCIe Host設備的創(chuàng)建過程;
我們看看PCIe Host的設備樹內容:
關鍵字段描述如下:
compatible
:用于匹配PCIe Host驅動;msi-controller
:表示是一個MSI(Message Signaled Interrupt
)控制器節(jié)點,這里需要注意的是,有的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流程
系統(tǒng)會根據(jù)dtb文件創(chuàng)建對應的platform_device并進行注冊;
當驅動與設備通過
compatible
字段匹配上后,會調用probe函數(shù),也就是nwl_pcie_probe
;

看一下nwl_pcie_probe
函數(shù):

通常probe函數(shù)都是進行一些初始化操作和注冊操作:
初始化包括:數(shù)據(jù)結構的初始化以及設備的初始化等,設備的初始化則需要獲取硬件的信息(比如寄存器基地址,長度,中斷號等),這些信息都從DTS而來;
注冊操作主要是包含中斷處理函數(shù)的注冊,以及通常的設備文件注冊等;
?
針對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
中斷號對應的中斷處理函數(shù),該處理函數(shù)用于中斷的級聯(lián);nwl_pcie_bridge_init
:硬件的Controller一堆設置,這部分需要去查閱Spec,了解硬件工作的細節(jié)。此外,通過devm_request_irq
注冊misc
中斷號對應的中斷處理函數(shù),該處理函數(shù)用于控制器自身狀態(tài)的處理;pci_parse_request_of_pci_ranges
:用于解析PCI總線的總線范圍和總線上的地址范圍,也就是CPU能看到的地址區(qū)域;nwl_pcie_init_irq_domain
和mwl_pcie_enable_msi
與中斷級聯(lián)相關,下個小節(jié)介紹;pci_scan_root_bus_bridge
:對總線上的設備進行掃描枚舉,這個流程在
中分析過。《Linux PCI驅動框架分析(二)》
brdige
結構體中的pci_ops
字段,用于指向PCI的讀寫操作函數(shù)集,當具體掃描到設備要讀寫配置空間時,調用的就是這個函數(shù),由具體的Controller驅動實現(xiàn);
2.3 中斷處理
PCIe控制器,通過PCIe總線連接各種設備,因此它本身充當一個中斷控制器,級聯(lián)到上一層的中斷控制器(比如GIC),如下圖:

PCIe總線支持兩種中斷的處理方式:
Legacy Interrupt:總線提供
INTA#, INTB#, INTC#, INTD#
四根中斷信號,PCI設備借助這四根信號使用電平觸發(fā)方式提交中斷請求;MSI(
Message Signaled Interrupt
) Interrupt:基于消息機制的中斷,也就是往一個指定地址寫入特定消息,從而觸發(fā)一個中斷;
針對兩種處理方式,NWL PCIe
驅動中,實現(xiàn)了兩個irq_chip
,也就是兩種方式的中斷控制器:

irq_domain
對應一個中斷控制器(irq_chip
),irq_domain
負責將硬件中斷號映射到虛擬中斷號上;來一張舊圖吧,具體文章可以去參考中斷子系統(tǒng)相關文章;

再來看一下nwl_pcie_enable_msi
函數(shù):

在該函數(shù)中主要完成的工作就是設置級聯(lián)的中斷處理函數(shù),級聯(lián)的中斷處理函數(shù)中最終會去調用具體的設備的中斷處理函數(shù);
?
所以,稍微匯總一下,作為兩種不同的中斷處理方式,套路都是一樣的,都是創(chuàng)建irq_chip
中斷控制器,為該中斷控制器添加irq_domain
,具體設備的中斷響應流程如下:
設備連接在PCI總線上,觸發(fā)中斷時,通過PCIe控制器充當?shù)闹袛嗫刂破髀酚傻缴弦患壙刂破?,最終路由到CPU;
CPU在處理PCIe控制器的中斷時,調用它的中斷處理函數(shù),也就是上文中提到過的
nwl_pcie_leg_handler
,nwl_pcie_msi_handler_high
,和nwl_pcie_leg_handler_low
;在級聯(lián)的中斷處理函數(shù)中,調用
chained_irq_enter
進入中斷級聯(lián)處理;調用
irq_find_mapping
找到具體的PCIe設備的中斷號;調用
generic_handle_irq
觸發(fā)具體的PCIe設備的中斷處理函數(shù)執(zhí)行;調用
chained_irq_exit
退出中斷級聯(lián)的處理;
2.4 總結
PCIe控制器驅動,各家的IP實現(xiàn)不一樣,驅動的差異可能會很大,單獨分析一個驅動畢竟只是個例,應該去掌握背后的通用框架;
各類驅動,大體都是硬件初始化配置,資源申請注冊,核心是處理與硬件的交互(一般就是中斷的處理),如果需要用戶來交互的,則還需要注冊設備文件,實現(xiàn)一堆
file_operation
操作函數(shù)集;
原文作者:LoyenWang
