天天看點

【原創】Linux PCI驅動架構分析(二)

<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>;

話不多說,直接開始。

【原創】Linux PCI驅動架構分析(二)

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>用于描述總線上的實體插槽;

來一張更詳細的結構體組織圖:

【原創】Linux PCI驅動架構分析(二)

總體來看,資料結構對硬體子產品進行了抽象,資料結構之間也能很便捷的建構一個類似PCI子系統實體拓撲的關系圖;

頂層的結構為<code>pci_host_bridge</code>,這個結構一般由Host驅動負責來初始化建立;

<code>pci_host_bridge</code>指向root bus,也就是編号為0的總線,在該總線下,可以挂接各種外設或實體slot,也可以通過PCI橋去擴充總線;

Linux PCI驅動架構,基于Linux裝置驅動模型,是以有必要先簡要介紹一下,實際上Linux裝置驅動模型也是一個大的topic,先挖個坑,有空再來填。來張圖吧:

【原創】Linux PCI驅動架構分析(二)

簡單來說,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>函數:

【原創】Linux PCI驅動架構分析(二)

核心在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>函數,下邊針對這兩個函數稍加介紹;

【原創】Linux PCI驅動架構分析(二)

裝置或者驅動注冊後,觸發<code>pci_bus_match</code>函數的調用,實際會去比對<code>vendor</code>和<code>device</code>等資訊,這個都是廠家固化的,在驅動中設定成<code>PCI_ANY_ID</code>就能支援所有裝置;

一旦比對成功後,就會去觸發<code>pci_device_probe</code>的執行;

【原創】Linux PCI驅動架構分析(二)

實際的過程也是比較簡單,無非就是進行比對,一旦比對上了,直接調用驅動程式的probe函數,寫過驅動的同學應該就比較清楚後邊的流程了;

我們還是順着裝置驅動比對的思路繼續開展;

3.2節描述的是總線的建立,那麼本節中的枚舉,顯然就是裝置的建立了;

所謂裝置的建立,就是在Linux核心中維護一些資料結構來對硬體裝置進行描述,而硬體的描述又跟上文中的資料結構能對應上;

枚舉的入口函數:<code>pci_host_probe</code>

【原創】Linux PCI驅動架構分析(二)

裝置的掃描從<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>)過程,熟悉資料結構與算法的同學應該清楚,這就類似典型的走迷宮的過程;

如果你對上述的流程還不清楚,再來一張圖:

【原創】Linux PCI驅動架構分析(二)

圖中的數字代表的就是掃描的過程,當周遊到PCI橋裝置的時候,會一直窮究到底,然後再傳回來;

當枚舉過程結束後,系統中就已經維護了PCI裝置的各類資訊了,在裝置驅動比對模型中,總線和裝置都已經具備了,剩下的就是寫個驅動了;

暫且寫這麼多,細節方面不再贅述了,把握大體的架構即可,無法扼住PCI的咽喉,那就扼住它的骨架吧。

歡迎關注個人公衆号,不定期分享Linux核心相關技術文章:

【原創】Linux PCI驅動架構分析(二)

作者:LoyenWang

出處:https://www.cnblogs.com/LoyenWang/

公衆号:<b>LoyenWang</b>

版權:本文版權歸作者和部落格園共有

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任

繼續閱讀