淺談Linux PCI裝置驅動(一)

來源:互聯網
上載者:User

    要弄清楚Linux PCI裝置驅動,首先要明白,所謂的Linux PCI裝置驅動實際包括Linux PCI裝置驅動和裝置本身驅動兩部分。不知道讀者理不理解這句話,本人覺得這句話很重要,對於PCI、USB這樣的驅動來說,必須要理解這個概念,才能明白該如何看待Linux下的PCI和USB以及類似的匯流排型的驅動。理由也很簡單,就是Linux PCI驅動是核心內建的,或者說核心幫你寫好了!而我們需要完成的就是裝置本身的驅動,比如網卡驅動等。當然,並不是說核心幫咱們寫好了Linux
PCI驅動我們什麼就不用做了,至少你要明白核心大致都幹了些什麼,這樣你才能明白你該幹什麼,如何完成裝置本身的驅動。這跟我們學習作業系統時要學習很多系統調用介面一樣的道理,不知道這些介面,怎麼用作業系統或者說作業系統給你提供的功能呢? 所以這裡我們就來研究下Linux PCI驅動到底都幹了些什麼,以便我們在此基礎上完成我們裝置本身的驅動。

 

在http://tldp.org/LDP/tlk/dd/pci.html這篇文章裡(整本書叫做The Linux Kernel,中文翻譯見http://oss.org.cn/ossdocs/linux/kernel/ 本文也參考了該中文翻譯)  提到了:

Linux PCI 初始化代碼邏輯上分為三個部分:
(1)PCI裝置驅動程式即上面提到的Linux PCI裝置驅動
這個偽裝置驅動程式從匯流排0開始查詢PCI系統並且定位系統中所有的PCI裝置和PCI橋。它建立一個
可以用來描述這個PCI系統拓樸層次的資料結構鏈表。並且對所有的發現的PCI橋編號。
(2)PCI BIOS
這個軟體層提供在bib-pci-bios歸約中描述的服務。雖然Alpha AXP不提供BIOS服務,在其Linux
版本中包含了相應的功能。
(3)PCI Fixup
與特定系統相關的PCI初始化修補代碼

而這裡主要就是探討Linux PCI裝置驅動,會在最後列出一段包含裝置本身驅動的範例程式碼,僅供參考。

 

一、概述及簡介

    PCI(Periheral Component Interconnect)有三種地址空間:PCI I/O空間、PCI記憶體位址空間和PCI配置空間。其中,PCI I/O空間和PCI記憶體位址空間由裝置驅動程式(即上面提到的裝置本身驅動)使用,而PCI配置空間由Linux PCI初始化代碼使用,這些代碼用於配置PCI裝置,比如中斷號以及I/O或記憶體基地址。所以這裡的PCI裝置驅動就是要大致描述對於PCI裝置驅動,Linux核心都幫我們做了什麼(主),接著就是我們應該完成什麼(次)。

(1)Linux核心做了什麼

    簡單的說,Linux核心主要就做了對PCI裝置的枚舉和配置;這些工作都是在Linux核心初始化時完成的。

    枚舉:對於PCI匯流排,有一個叫做PCI橋的裝置用來將父匯流排與子匯流排串連。作為一種特殊的PCI裝置,PCI橋主要包括以下三種:

    1). Host/PCI橋:  用於串連CPU與PCI根匯流排,第1個根匯流排的編號為0。在PC中,記憶體控制器也通常被整合到Host/PCI橋裝置晶片中,因此Host/PCI橋通常也被稱為“北橋晶片集(North Bridge Chipset)”。

    2). PCI/ISA橋:   用於串連舊的ISA匯流排。通常,PCI中類似i8359A中斷控制器這樣的裝置也會被整合到PCI/ISA橋裝置中。因此,PCI/ISA橋通常也被稱為“南橋晶片集(South Bridge Chipset)”

    3). PCI-to-PCI橋(以下稱為PCI-PCI橋):  用於串連PCI主匯流排(Primary Bus)和次匯流排(Secondary Bus)。PCI-PCI橋所處的PCI匯流排稱為主匯流排,即次匯流排的父匯流排;PCI-PCI橋所串連的PCI匯流排稱為次匯流排,即主匯流排的子匯流排。

    摘自PCI Local Bus Specification Revision 2.1,可以看到PCI-PCI橋的Class Code(見圖3)就是0x060400。

    CPU通過Host/PCI橋與一條PCI匯流排相連,處在這種位置上的PCI匯流排稱為根匯流排。PC機中通常只有一個Host/PCI橋,在一條PCI匯流排的基礎上,可以再通過PCI橋串連到其他次一層的匯流排,例如通過PCI-PCI橋可以串連到另一條PCI匯流排,通過PCI-ISA橋可以串連到一條ISA匯流排。事實上,現代PC機中的ISA匯流排正是通過PCI-ISA橋串連在PCI匯流排上的。這樣,通過使用PCI-PCI橋,就構築起了一個層次的、樹狀的PCI系統結構。對於上層的匯流排而言,串連在這條匯流排上的PCI橋也是一個裝置。但是這是一種特殊的裝置,它既是上層匯流排上的一個裝置,實際上又是上層匯流排的延伸。
所謂枚舉,就是從Host/PCI橋開始進行探測和掃描,逐個“枚舉”串連在第一條PCI匯流排上的所有裝置並記錄在案。如果其中的某個裝置是PCI-PCI橋,則又進一步再探測和掃描連在這個橋上的次級PCI匯流排。就這樣遞迴下去,直到窮盡系統中的所有PCI裝置。其結果,是在記憶體中建立起一棵代表著這些PCI匯流排和裝置的PCI樹。每個PCI裝置(包括PCI橋裝置)都由一個pci_dev結構體來表示,而每條PCI匯流排則由pci_bus結構來表示。你有通過PCI橋建立起的硬體裝置樹,我有記憶體中通過資料結構構建的軟體樹,多麼和諧
呵呵。

                                                                  圖1    PCI系統   

    配置:PCI裝置中一般都帶有一些RAM和ROM 空間,通常的控制/狀態寄存器和資料寄存器也往往以RAM區間的形式出現,而這些區間的地址在裝置內部一般都是從0開始編址的,那麼當匯流排上掛接了多個裝置時,對這些空間的訪問就會產生衝突。所以,這些地址都要先映射到系統匯流排上,再進一步映射到核心的虛擬位址空間。而所謂的配置就是通過對PCI配置空間的寄存器進行操作從而完成地址的映射(只完成內部編址映射到匯流排地址的工作,而映射到核心的虛擬位址空間是由裝置本身的驅動要做的工作)。

(2)Linux核心怎麼做的

    這裡首先要說明的是,對於PCI的裝置初始化(即上面提到的枚舉和配置工作),PC機的BIOS和Linux核心都可以做。一般而言,只要是採用PCI匯流排的PC機,其BIOS就必須提供對PCI匯流排操作的支援,因而稱為PCI BIOS。而且最早Linux核心也是通過這種BIOS調用的方式來擷取系統中的PCI裝置資訊的,但是不是所有的平台都有BIOS(如某些嵌入式系統),並且在實踐中也發現有些母板上的PCI BIOS存在這樣那樣的問題,所以後來就改由Linux核心自己動手了,自己動手 豐衣足食 呵呵。不過,Linux核心還是很體貼的在make
menuconfig的選項裡為我們提供了自己選擇的權利,即PCI access mode,裡面提供了四個選項分別是BIOS、MMconfig、Direct和Any。Direct方式就是拋開BIOS而由核心自己完成初始化工作的意思。

                                                                                                     

二、開始我們的枚舉與配置之路

     註:為了更清晰,簡單的描述PCI裝置的初始化過程(因為2.4.18中還沒有引入裝置驅動模型,這樣可以讓我們專心研究PCI裝置驅動本身)。這裡是對Linux-2.4.18的核心進行的分析,主要原因大家從參考資料中也應該能明白,這裡很多就是參考了[1]中的資料來分析的。如果想學PCI裝置驅動,那麼應該好好看看[1]的第八章中的PCI匯流排一節。然後再能找到一個驅動的例子代碼看看,就可以說算是對PCI裝置驅動入門了,當然,前提是都看懂了 呵呵。

    廢話少說,下面進入正題。前面提到了PCI有三種地址空間,其中的PCI配置空間是給Linux核心中的PCI初始化代碼用的,也就是我們這裡的枚舉與配置時用到的。那麼這個PCI配置空間裡放的是什麼東西呢,顯然應該是寄存器,稱為配置寄存器組。當PCI裝置上電時,硬體保持未啟用狀態。即該裝置只會對配置事務做出響應。上電時,裝置上不會有記憶體和I/O連接埠映射到電腦的地址空間;其他裝置相關的功能,例如中斷報告,也被禁止。

    PCI標準規定每個裝置的配置寄存器組最多可以有256位元組的連續空間,其中開頭的64位元組的用途和格式是標準的,稱為配置寄存器的頭部。系統中提供一些與硬體有關的機制,使得PCI配置代碼可以檢測在一個給定的PCI匯流排上所有可能的PCI配置寄存器頭部,從而知道哪個PCI插槽上目前有裝置,哪個插槽上暫無裝置。這是通過讀PCI配置寄存器頭部上的某個域完成的(一般是“Vendor ID" 域)。如果一個插槽上為空白,上述操作會返回一些錯誤傳回值,如0xFFFFFFFF。這種頭部(指64位元組頭部)又有三種,其中“0型”(type
0)頭部用於一般的PCI裝置,“1型”頭部用於各種PCI-PCI橋, “2型”頭部是用於PCI-CardBus橋的,CardBus是膝上型電腦中使用的匯流排,我們不關心。而64位元組頭部中的16個位元組中又包含著有關頭部的類型、裝置的種類、裝置的一些性質、由誰製造等等資訊。根據這16個位元組中提供的資訊,來確定應該怎樣進一步解釋和處理剩餘頭部中的48個位元組。對於這16個位元組的地址,include/linux/pci.h中定義了這樣一些常數。

 

#define PCI_VENDOR_ID  0x00 /* 16 bits */
#define PCI_DEVICE_ID  0x02 /* 16 bits */
#define PCI_COMMAND  0x04 /* 16 bits */

 

#define PCI_STATUS  0x06 /* 16 bits */

 

#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8  revision */
#define PCI_REVISION_ID         0x08    /* Revision ID */
#define PCI_CLASS_PROG          0x09    /* Reg. Level Programming Interface */
#define PCI_CLASS_DEVICE        0x0a    /* Device class */

#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */
#define PCI_LATENCY_TIMER 0x0d /* 8 bits */
#define PCI_HEADER_TYPE  0x0e /* 8 bits */

 

    對應我們的圖3(見下)中的前16位元組。而且我們也看到了緊挨著PCI_HEADER_TYPE (即存放頭部類型的寄存器)下面定義的就是上面提到的三種類型的頭部:

#define  PCI_HEADER_TYPE_NORMAL   0
#define  PCI_HEADER_TYPE_BRIDGE      1
#define  PCI_HEADER_TYPE_CARDBUS  2

 

    在Linux系統上,可以通過cat /proc/pci  等命令查看系統中所有PCI裝置的類別、型號以及廠商等等資訊,那就是從這些寄存器來的。下面是在虛擬機器中用lspci -x命令的資訊截取(lspci命令也是使用/proc檔案作為其資訊來源):

00:00.0 Host bridge: Intel Corp. 440BX/ZX/DX - 82443BX/ZX/DX Host bridge (rev 01)
00: 86 80 90 71 06 00 00 02 01 00 00 06 00 00 00 00
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 ad 15 76 19
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 

    首先要說明的是PCI寄存器是小端位元組序格式的。那麼根據最下面的PCI配置寄存器組的結構(圖 3),顯然這個Host bridge的Vendor ID是0x8086,我不說你也能猜到這個Vendor就是Intel了。

    這裡有個問題要先說清楚,就是這些寄存器的地址問題,不然往後就進行不下去了。配置寄存器可以讓我們來進行配置以便完成PCI裝置上的儲存空間的訪問,但這些配置寄存器本身也位於PCI裝置地址空間中,如何訪問這部分空間也就成了我們整個初始化工作的一個進入點,就像每個可執行程式都要有進入點一樣。PCI採用的辦法是讓所有裝置的配置寄存器組都採用相同的地址,由所在匯流排的PCI橋在訪問時附加上其他條件來區分。而CPU則通過一個統一的入口地址向“宿主--PCI橋”發出命令,由相應的PCI橋間接的完成具體的讀寫。對於i386結構的處理器,PCI匯流排的設計者在I/O地址空間保留了8個位元組用於這個目的,那就是0xCF8~0xCFF。這8個位元組構成了兩個32位的寄存器,第一個是“地址寄存器”0xCF8,第二個是“資料寄存器”0xCFC。要訪問某個裝置中的某個配置寄存器時,CPU先往地址寄存器中寫入目標地址,然後通過資料寄存器讀寫資料。不過,寫入地址寄存器的目標地址是一種匯流排號、裝置號、功能號以及裝置寄存器地址在內的綜合地址。格式2:

 

 

                                                     圖2    寫入地址寄存器0xCF8的綜合地址

     這裡的匯流排號、裝置號和功能號是對配置寄存器地址的擴充,就是上面提到的附加的其他條件。首先每個PCI匯流排都有個匯流排號,主匯流排的匯流排號為0,其餘的則由CPU在枚舉階段每當探測到一個PCI橋時便為其指定一個,依次遞增。裝置號一般代表著一塊PCI介面卡(更確切的說是PCI匯流排介面晶片),通常取決於插槽的位置。每塊PCI介面卡上可以有若干個功能模組,這些功能模組共用一個PCI匯流排介面晶片,包括其中用於地址映射的電子線路,以降低成本。從邏輯的角度說,每個“功能”實際上就是一個裝置(看過USB裝置驅動的人很眼熟吧
呵呵),所以裝置號和功能號合在一起又可以稱作“邏輯裝置號”,而每塊卡上最多可以容納8個裝置。顯然,這些欄位(指整個32bit)結合在一起就惟一確定了系統中的一項PCI邏輯裝置。開始時,只有0號匯流排可以訪問,在掃描0號匯流排時如果發現上面某個裝置是PCI橋,就為之指定一個新的匯流排號,例如1,這樣1號匯流排就可以訪問了,這就是枚舉階段的任務之一。

    現在請讀者考慮一個問題:當我們拿到一塊PCI網卡,我們把它插到PC的主板上,打算寫個這個網卡的驅動。那麼第一步該幹什麼呢?讀者可以回顧前面的內容,既然我們說Linux核心幫我們做了裝置的枚舉和配置工作,那麼我在寫網卡驅動之前是不是可以先看看Linux核心對我們的這個PCI網卡裝置完成的枚舉工作的結果呢?或者直白些說,我把網卡插上了,現在Linux核心有沒有識別出這塊裝置呢? 注意識別出裝置跟能正常使用裝置是不同的概念,這很好理解。安裝過PC網卡驅動的人都知道,當裝置的驅動沒有安裝時,我們在裝置管理員中是可以看到這個裝置的,不過上面是一個黃色的大問號。而在Linux系統中,我們可以通過lspci命令來查看。

     下面是在LDD3的PCI驅動那一章截取的一段內容:  lspci 的輸出( pciutils 的一部分, 在大部分發布中都有)和在 /proc/pci 和 /porc/bus/pci 中的資訊排布. PCI 裝置的 sysfs 表示也顯示了這種定址方案, 還有 PCI 域資訊. 當顯示硬體地址時, 它可被顯示為 2 個值( 一個 8-位匯流排號和一個 8-位 裝置和功能號), 作為 3 個值( bus, device, 和 function), 或者作為 4 個值(domain, bus, device,
和 function); 所有的值常常用 16 進位顯示.

例如, /proc/bus/pci/devices 使用一個單個16位欄位(來便於分析和排序), 而 /proc/bus/busnumber 劃分地址為3個欄位. 下面內容顯示了這些地址如何顯示, 只顯示了輸出行的開始:

$ lspci | cut -d: -f1-30000:00:00.0 Host bridge0000:00:00.1 RAM memory0000:00:00.2 RAM memory0000:00:02.0 USB Controller0000:00:04.0 Multimedia audio controller0000:00:06.0 Bridge0000:00:07.0 ISA bridge0000:00:09.0 USB Controller0000:00:09.1 USB Controller0000:00:09.2 USB Controller0000:00:0c.0 CardBus bridge0000:00:0f.0 IDE interface0000:00:10.0 Ethernet controller0000:00:12.0 Network controller0000:00:13.0 FireWire (IEEE 1394)0000:00:14.0 VGA compatible controller$ cat /proc/bus/pci/devices | cut -f1000000010002001000200030003800480049004a0060007800800090009800a0$ tree /sys/bus/pci/devices//sys/bus/pci/devices/|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0|-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1|-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2|-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0|-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0|-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0|-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0|-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0|-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1|-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2|-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0|-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0|-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0|-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0|-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0`-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0

所有的 3 個裝置列表都以相同順序排列, 因為 lspci 使用 /proc 檔案作為它的資訊源. 拿 VGA 視頻控制器作一個例子, 0x00a0 意思是 0000:00:14.0 當劃分為域(16位), 匯流排(8位), 裝置(5位)和功能(3位).為什麼0x00a0對應的是0000:00:14.0呢,這就要看圖2中的內容了,根據圖2中的寄存器對應0x00a0就代表著匯流排(8位), 裝置(5位)和功能(3位).0x00a0=0000000010100000,很容易看出高8位是匯流排號也就是0。剩下的0xa0=10100000,可以看出如果低3位表示功能號,那麼剩下的10100就是裝置號,補全成8位的值就是00010100即0x14.

   

   

 

                                                                      圖3    PCI配置寄存器組 

  

 

參考資料:

[1] Linux核心原始碼情景分析(下冊)

[2] Linux裝置驅動開發詳解

[3] Linux裝置驅動(第三版)

[4] 核心Documentation下的pci.txt

[5] 精通Linux裝置驅動開發

[6] http://tldp.org/LDP/tlk/dd/pci.html

[7] http://linux.die.net/man/8/lspci

[8] http://www.ibm.com/developerworks/cn/linux/l-pci/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.