<code>Read the fucking source code!</code> --By 魯迅
<code>A picture is worth a thousand words.</code> --By 高爾基
說明:
Kernel版本:4.14
ARM64處理器
使用工具:Source Insight 3.5, Visio
本文将分析Linux PCI子系統的架構,主要圍繞Linux PCI子系統的初始化以及枚舉過程分析;
如果對具體的硬體缺乏了解,建議先閱讀上篇文章<code>《Linux PCI驅動架構分析(一)》</code>;
話不多說,直接開始。

PCI體系結構的拓撲關系如圖所示,而圖中的不同資料結構就是用于來描述對應的子產品;
Host Bridge連接配接CPU和PCI系統,由<code>struct pci_host_bridge</code>描述;
<code>struct pci_dev</code>描述PCI裝置,以及PCI-to-PCI橋裝置;
<code>struct pci_bus</code>用于描述PCI總線,<code>struct pci_slot</code>用于描述總線上的實體插槽;
來一張更詳細的結構體組織圖:
總體來看,資料結構對硬體子產品進行了抽象,資料結構之間也能很便捷的建構一個類似PCI子系統實體拓撲的關系圖;
頂層的結構為<code>pci_host_bridge</code>,這個結構一般由Host驅動負責來初始化建立;
<code>pci_host_bridge</code>指向root bus,也就是編号為0的總線,在該總線下,可以挂接各種外設或實體slot,也可以通過PCI橋去擴充總線;
Linux PCI驅動架構,基于Linux裝置驅動模型,是以有必要先簡要介紹一下,實際上Linux裝置驅動模型也是一個大的topic,先挖個坑,有空再來填。來張圖吧:
簡單來說,Linux核心建立了一個統一的裝置模型,分别采用總線、裝置、驅動三者進行抽象,其中裝置與驅動都挂在總線上,當有新的裝置注冊或者新的驅動注冊時,總線會去進行比對操作(<code>match</code>函數),當發現驅動與裝置能進行比對時,就會執行probe函數的操作;
從資料結構中可以看出,<code>bus_type</code>會維護兩個連結清單,分别用于挂接向其注冊的裝置和驅動,而<code>match</code>函數就負責比對檢測;
各類驅動架構也都是基于圖中的機制來實作,在這之上進行封裝,比如I2C總線架構等;
裝置驅動模型中,包含了很多<code>kset/kobject</code>等内容,建議去看看之前的文章<code>《linux裝置模型之kset/kobj/ktype分析》</code>
好了,點到為止,感覺要跑題了,強行拉回來。
既然說到了裝置驅動模型,那麼首先我們要做的事情,就是先在核心裡邊建立一個PCI總線,用于挂接PCI裝置和PCI驅動,我們的實作來到了<code>pci_driver_init()</code>函數:
核心在PCI架構初始化時會調用<code>pci_driver_init()</code>來建立一個PCI總線結構(全局變量<code>pci_bus_type</code>),這裡描述的PCI總線結構,是指驅動比對模型中的概念,PCI的裝置和驅動都會挂在該PCI總線上;
從<code>pci_bus_type</code>的函數操作接口也能看出來,<code>pci_bus_match</code>用來檢查裝置與驅動是否比對,一旦比對了就會調用<code>pci_device_probe</code>函數,下邊針對這兩個函數稍加介紹;
裝置或者驅動注冊後,觸發<code>pci_bus_match</code>函數的調用,實際會去比對<code>vendor</code>和<code>device</code>等資訊,這個都是廠家固化的,在驅動中設定成<code>PCI_ANY_ID</code>就能支援所有裝置;
一旦比對成功後,就會去觸發<code>pci_device_probe</code>的執行;
實際的過程也是比較簡單,無非就是進行比對,一旦比對上了,直接調用驅動程式的probe函數,寫過驅動的同學應該就比較清楚後邊的流程了;
我們還是順着裝置驅動比對的思路繼續開展;
3.2節描述的是總線的建立,那麼本節中的枚舉,顯然就是裝置的建立了;
所謂裝置的建立,就是在Linux核心中維護一些資料結構來對硬體裝置進行描述,而硬體的描述又跟上文中的資料結構能對應上;
枚舉的入口函數:<code>pci_host_probe</code>
裝置的掃描從<code>pci_scan_root_bus_bridge</code>開始,首先需要先向系統注冊一個<code>host bridge</code>,在注冊的過程中需要建立一個<code>root bus</code>,也就是<code>bus 0</code>,在<code>pci_register_host_bridge</code>函數中,主要是一系列的初始化和注冊工作,此外還為總線配置設定資源,包括位址空間等;
<code>pci_scan_child_bus</code>開始,從<code>bus 0</code>向下掃描并添加裝置,這個過程由<code>pci_scan_child_bus_extend</code>來完成;
從<code>pci_scan_child_bus_extend</code>的流程可以看出,主要有兩大塊:
PCI裝置掃描,從循環也能看出來,每條總線支援32個裝置,每個裝置支援8個功能,掃描完裝置後将裝置注冊進系統,pci_scan_device的過程中會去讀取PCI裝置的配置空間,擷取到BAR的相關資訊,細節不表了;
PCI橋裝置掃描,PCI橋是用于連接配接上一級PCI總線和下一級PCI總線的,當發現有下一級總線時,建立子結構,并再次調用<code>pci_scan_child_bus_extend</code>的函數來掃描下一級的總線,從這個過程看,就是一個遞歸過程。
從裝置的掃描過程看,這是一個典型的DFS(<code>Depth First Search</code>)過程,熟悉資料結構與算法的同學應該清楚,這就類似典型的走迷宮的過程;
如果你對上述的流程還不清楚,再來一張圖:
圖中的數字代表的就是掃描的過程,當周遊到PCI橋裝置的時候,會一直窮究到底,然後再傳回來;
當枚舉過程結束後,系統中就已經維護了PCI裝置的各類資訊了,在裝置驅動比對模型中,總線和裝置都已經具備了,剩下的就是寫個驅動了;
暫且寫這麼多,細節方面不再贅述了,把握大體的架構即可,無法扼住PCI的咽喉,那就扼住它的骨架吧。
歡迎關注個人公衆号,不定期分享Linux核心相關技術文章:
作者:LoyenWang
出處:https://www.cnblogs.com/LoyenWang/
公衆号:<b>LoyenWang</b>
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任