首先要明確兩個概念:Linux內(nèi)核 PCI設(shè)備驅(qū)動和設(shè)備本身驅(qū)動兩部分。工作中所謂的編寫設(shè)備驅(qū)動,其實就是編寫設(shè)備本身驅(qū)動。因為Linux 內(nèi)核的PCI驅(qū)動是內(nèi)核自帶的。
當然,并不是說內(nèi)核幫咱們寫好了Linux PCI驅(qū)動我們什么就不用做了,至少你要明白內(nèi)核大致都干了些什么,這樣你才能明白你該干什么,如何完成設(shè)備本身的驅(qū)動。我們下面就來研究下Linux PCI驅(qū)動到底都干了些什么。
Linux PCI 初始化代碼邏輯上分為三個部分:
(1)內(nèi)核的PCI設(shè)備驅(qū)動程序
這個偽設(shè)備驅(qū)動程序從總線0開始查詢PCI系統(tǒng)并且定位系統(tǒng)中所有的PCI設(shè)備和PCI橋。它建立一個可以用來描述這個PCI系統(tǒng)拓樸層次的數(shù)據(jù)結(jié)構(gòu)鏈表。并且對所有的發(fā)現(xiàn)的PCI橋編號。
(2)PCI BIOS
這個軟件層提供在bib-pci-bios歸約中描述的服務(wù)。雖然Alpha AXP不提供BIOS服務(wù),在其Linux版本中包含了相應(yīng)的功能。
(3)PCI Fixup
與特定系統(tǒng)相關(guān)的PCI初始化修補代碼
而這里主要就是探討Linux內(nèi)核 PCI設(shè)備驅(qū)動,會在最后列出一段包含設(shè)備本身驅(qū)動的示例代碼,僅供參考。
一、概述及簡介
PCI(Periheral Component Interconnect)有三種地址空間:PCI I/O空間、PCI內(nèi)存地址空間和PCI配置空間。其中,PCI I/O空間和PCI內(nèi)存地址空間由設(shè)備驅(qū)動程序(即上面提到的設(shè)備本身驅(qū)動)使用,而PCI配置空間由Linux PCI初始化代碼使用,這些代碼用于配置PCI設(shè)備,比如中斷號以及I/O或內(nèi)存基地址。所以這里的PCI設(shè)備驅(qū)動就是要大致描述對于PCI設(shè)備驅(qū)動,Linux內(nèi)核都幫我們做了什么(主),接著就是我們應(yīng)該完成什么(次)。
(1)Linux內(nèi)核做了什么
簡單的說,Linux內(nèi)核主要就做了對PCI設(shè)備的枚舉和配置;這些工作都是在Linux內(nèi)核初始化時完成的。
枚舉:對于PCI總線,有一個叫做PCI橋的設(shè)備用來將父總線與子總線連接。作為一種特殊的PCI設(shè)備,PCI橋主要包括以下三種:
Host/PCI橋: 用于連接CPU與PCI根總線,第1個根總線的編號為0。在PC中,內(nèi)存控制器也通常被集成到Host/PCI橋設(shè)備芯片中,因此Host/PCI橋通常也被稱為“北橋芯片組(North Bridge Chipset)”。
PCI/ISA橋: 用于連接舊的ISA總線。通常,PCI中類似i8359A中斷控制器這樣的設(shè)備也會被集成到PCI/ISA橋設(shè)備中。因此,PCI/ISA橋通常也被稱為“南橋芯片組(South Bridge Chipset)”
PCI-to-PCI橋(以下稱為PCI-PCI橋): 用于連接PCI主總線(Primary Bus)和次總線(Secondary Bus)。PCI-PCI橋所處的PCI總線稱為主總線,即次總線的父總線;PCI-PCI橋所連接的PCI總線稱為次總線,即主總線的子總線。
圖1 PCI系統(tǒng)示意圖
下圖摘自PCI Local Bus Specification Revision 2.1,可以看到PCI-PCI橋的Class Code(見圖3)就是0x060400。
圖2 Base class 06h
CPU通過Host/PCI橋與一條PCI總線相連,處在這種配置上的PCI總線稱為根總線。PC機中通常只有一個Host/PCI橋,在一條PCI總線的基礎(chǔ)上,可以再通過PCI橋連接到其他次一層的總線,例如通過PCI-PCI橋可以連接到另一條PCI總線,通過PCI-ISA橋可以連接到一條ISA總線。
事實上,現(xiàn)代PC機中的ISA總線正是通過PCI-ISA橋連接在PCI總線上的。這樣,通過使用PCI-PCI橋,就構(gòu)筑起了一個層次的、樹狀的PCI系統(tǒng)結(jié)構(gòu)。對于上層的總線而言,連接在這條總線上的PCI橋也是一個設(shè)備。但是這是一種特殊的設(shè)備,它既是上層總線上的一個設(shè)備,實際上又是上層總線的延伸。
所謂枚舉,就是從Host/PCI橋開始進行探測和掃描,逐個“枚舉”連接在第一條PCI總線上的所有設(shè)備并記錄在案。如果其中的某個設(shè)備是PCI-PCI橋,則又進一步再探測和掃描連在這個橋上的次級PCI總線。就這樣遞歸下去,直到窮盡系統(tǒng)中的所有PCI設(shè)備。
其結(jié)果,是在內(nèi)存中建立起一棵代表著這些PCI總線和設(shè)備的PCI樹。每個PCI設(shè)備(包括PCI橋設(shè)備)都由一個pci_dev結(jié)構(gòu)體來表示,而每條PCI總線則由pci_bus結(jié)構(gòu)來表示。你有通過PCI橋建立起的硬件設(shè)備樹,我有內(nèi)存中通過數(shù)據(jù)結(jié)構(gòu)構(gòu)建的軟件樹,很和諧。
配置:PCI設(shè)備中一般都帶有一些RAM和ROM 空間,通常的控制/狀態(tài)寄存器和數(shù)據(jù)寄存器也往往以RAM區(qū)間的形式出現(xiàn),而這些區(qū)間的地址在設(shè)備內(nèi)部一般都是從0開始編址的,那么當總線上掛接了多個設(shè)備時,對這些空間的訪問就會產(chǎn)生沖突。
所以,這些地址都要先映射到系統(tǒng)總線上,再進一步映射到內(nèi)核的虛擬地址空間。而所謂的配置就是通過對PCI配置空間的寄存器進行操作從而完成地址的映射(只完成內(nèi)部編址映射到總線地址的工作,而映射到內(nèi)核的虛擬地址空間是由設(shè)備本身的驅(qū)動要做的工作)。
(2)Linux內(nèi)核怎么做的
這里首先要說明的是,對于PCI的設(shè)備初始化(即上面提到的枚舉和配置工作),PC機的BIOS和Linux內(nèi)核都可以做。一般而言,只要是采用PCI總線的PC機,其BIOS就必須提供對PCI總線操作的支持,因而稱為PCI BIOS。
而且最早Linux內(nèi)核也是通過這種BIOS調(diào)用的方式來獲取系統(tǒng)中的PCI設(shè)備信息的,但是不是所有的平臺都有BIOS(比如某些嵌入式系統(tǒng)),并且在實踐中也發(fā)現(xiàn)有些母板上的PCI BIOS存在這樣那樣的問題,所以后來就改由Linux內(nèi)核自己動手了,自己動手豐衣足食呵呵。
不過,Linux內(nèi)核還是很體貼的在make menuconfig的選項里為我們提供了自己選擇的權(quán)利,即PCI access mode,里面提供了四個選項分別是BIOS、MMconfig、Direct和Any。Direct方式就是拋開BIOS而由內(nèi)核自己完成初始化工作的意思。
二、開始我們的枚舉與配置之路
前面提到了PCI有三種地址空間,其中的PCI配置空間是給Linux內(nèi)核中的PCI初始化代碼用的,也就是我們這里的枚舉與配置時用到的。那么這個PCI配置空間里放的是什么東西呢,顯然應(yīng)該是寄存器,稱為配置寄存器組。當PCI設(shè)備上電時,硬件保持未激活狀態(tài)。即該設(shè)備只會對配置事務(wù)做出響應(yīng)。上電時,設(shè)備上不會有內(nèi)存和I/O端口映射到計算機的地址空間;其他設(shè)備相關(guān)的功能,例如中斷報告,也被禁止。
PCI標準規(guī)定每個設(shè)備的配置寄存器組最多可以有256字節(jié)的連續(xù)空間,其中開頭的64字節(jié)的用途和格式是標準的,稱為配置寄存器的頭部。系統(tǒng)中提供一些與硬件有關(guān)的機制,使得PCI配置代碼可以檢測在一個給定的PCI總線上所有可能的PCI配置寄存器頭部,從而知道哪個PCI插槽上目前有設(shè)備,哪個插槽上暫無設(shè)備。這是通過讀PCI配置寄存器頭部上的某個域完成的(一般是“Vendor ID" 域)。如果一個插槽上為空,上述操作會返回一些錯誤返回值,如0xFFFFFFFF。
這種頭部(指64字節(jié)頭部)又有三種,其中“0型”(type 0)頭部用于一般的PCI設(shè)備,“1型”頭部用于各種PCI-PCI橋, “2型”頭部是用于PCI-CardBus橋的,CardBus是筆記本電腦中使用的總線,我們不關(guān)心。
而64字節(jié)頭部中的16個字節(jié)中又包含著有關(guān)頭部的類型、設(shè)備的種類、設(shè)備的一些性質(zhì)、由誰制造等等信息。根據(jù)這16個字節(jié)中提供的信息,來確定應(yīng)該怎樣進一步解釋和處理剩余頭部中的48個字節(jié)。對于這16個字節(jié)的地址,include/linux/pci.h中定義了這樣一些常數(shù):
#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 */
對應(yīng)我們的圖3(見下)中的前16字節(jié)。而且我們也看到了緊挨著PCI_HEADER_TYPE(即存放頭部類型的寄存器)下面定義的就是上面提到的三種類型的頭部:
#define PCI_HEADER_TYPE_NORMAL 0#define PCI_HEADER_TYPE_BRIDGE 1#define PCI_HEADER_TYPE_CARDBUS 2
在Linux系統(tǒng)上,可以通過cat /proc/pci 等命令查看系統(tǒng)中所有PCI設(shè)備的類別、型號以及廠商等信息,那就是從這些寄存器來的。下面是在虛擬機中用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寄存器是小端字節(jié)序格式的。那么根據(jù)最下面的PCI配置寄存器組的結(jié)構(gòu)(圖4),顯然這個Host bridge的Vendor ID是0x8086,我不說你也能猜到這個Vendor就是Intel。
這里有個問題要先說清楚,就是這些寄存器的地址問題,不然往后就進行不下去了。配置寄存器可以讓我們來進行配置以便完成PCI設(shè)備上的存儲空間的訪問,但這些配置寄存器本身也位于PCI設(shè)備地址空間中,如何訪問這部分空間也就成了我們整個初始化工作的一個入口點,就像每個可執(zhí)行程序都要有入口點一樣。
PCI采用的辦法是讓所有設(shè)備的配置寄存器組都采用相同的地址,由所在總線的PCI橋在訪問時附加上其他條件來區(qū)分。而CPU則通過一個統(tǒng)一的入口地址向“宿主--PCI橋”發(fā)出命令,由相應(yīng)的PCI橋間接的完成具體的讀寫。對于i386結(jié)構(gòu)的處理器,PCI總線的設(shè)計者在I/O地址空間保留了8個字節(jié)用于這個目的,那就是0xCF8~0xCFF。
這8個字節(jié)構(gòu)成了兩個32位的寄存器,第一個是“地址寄存器”0xCF8,第二個是“數(shù)據(jù)寄存器”0xCFC。要訪問某個設(shè)備中的某個配置寄存器時,CPU先往地址寄存器中寫入目標地址,然后通過數(shù)據(jù)寄存器讀寫數(shù)據(jù)。不過,寫入地址寄存器的目標地址是一種總線號、設(shè)備號、功能號以及設(shè)備寄存器地址在內(nèi)的綜合地址。格式如下圖:
圖3 寫入地址寄存器0xCF8的綜合地址
這里的總線號、設(shè)備號和功能號是對配置寄存器地址的擴充,就是上面提到的附加的其他條件。
首先每個PCI總線都有個總線號,主總線的總線號為0,其余的則由CPU在枚舉階段每當探測到一個PCI橋時便為其指定一個,依次遞增。設(shè)備號一般代表著一塊PCI接口卡(更確切的說是PCI總線接口芯片),通常取決于插槽的位置。PCI接口卡上可以有若干個功能模塊,這些功能模塊共用一個PCI總線接口芯片,包括其中用于地址映射的電子線路,以降低成本。
從邏輯的角度說,每個“功能”實際上就是一個設(shè)備(看過USB設(shè)備驅(qū)動的人很眼熟吧 ,呵呵),所以設(shè)備號和功能號合在一起又可以稱作“邏輯設(shè)備號”,而每塊卡上最多可以容納8個設(shè)備。
顯然,這些字段(指整個32bit)結(jié)合在一起就惟一確定了系統(tǒng)中的一項PCI邏輯設(shè)備。開始時,只有0號總線可以訪問,在掃描0號總線時如果發(fā)現(xiàn)上面某個設(shè)備是PCI橋,就為之指定一個新的總線號,例如1,這樣1號總線就可以訪問了,這就是枚舉階段的任務(wù)之一。
現(xiàn)在請讀者考慮一個問題:當我們拿到一塊PCI網(wǎng)卡,把它插到PC的主板上,打算寫個這個網(wǎng)卡的驅(qū)動。那么第一步該干什么呢?讀者可以回顧前面的內(nèi)容,既然我們說Linux內(nèi)核幫我們做了設(shè)備的枚舉和配置工作,那么我在寫網(wǎng)卡驅(qū)動之前是不是可以先看看Linux內(nèi)核對我們的這個PCI網(wǎng)卡設(shè)備完成的枚舉工作的結(jié)果呢?或者直白些說,我把網(wǎng)卡插上了,現(xiàn)在Linux內(nèi)核有沒有識別出這塊設(shè)備呢?注意識別出設(shè)備跟能正常使用設(shè)備是不同的概念,這很好理解。
安裝過PC網(wǎng)卡驅(qū)動的人都知道,當設(shè)備的驅(qū)動沒有安裝時,我們在設(shè)備管理器中是可以看到這個設(shè)備的,不過上面是一個黃色的大問號。而在Linux系統(tǒng)中,我們可以通過lspci命令來查看。
下面是在LDD3的PCI驅(qū)動那一章截取的一段內(nèi)容: lspci 的輸出( pciutils 的一部分, 在大部分發(fā)布中都有)和在 /proc/pci 和 /porc/bus/pci 中的信息排布. PCI 設(shè)備的 sysfs 表示也顯示了這種尋址方案, 還有 PCI 域信息,當顯示硬件地址時, 它可被顯示為 2 個值( 一個 8-位總線號和一個 8-位 設(shè)備和功能號), 作為 3 個值( bus, device, 和 function), 或者作為 4 個值(domain, bus, device, 和 function); 所有的值常常用 16 進制顯示.
例如, /proc/bus/pci/devices 使用一個單個16位字段(來便于分析和排序), 而 /proc/bus/busnumber 劃分地址為3個字段. 下面內(nèi)容顯示了這些地址如何顯示, 只顯示了輸出行的開始 :
$ lspci | cut -d: -f1-3000000.0 Host bridge 000000.1 RAM memory 000000.2 RAM memory 000002.0 USB Controller 000004.0 Multimedia audio controller 000006.0 Bridge 000007.0 ISA bridge 000009.0 USB Controller 000009.1 USB Controller 000009.2 USB Controller 00000c.0 CardBus bridge 00000f.0 IDE interface 000010.0 Ethernet controller 000012.0 Network controller 000013.0 FireWire (IEEE 1394) 000014.0 VGA compatible controller $ cat /proc/bus/pci/devices | cut -f1 0000 0001 0002 0010 0020 0030 0038 0048 0049 004a 0060 0078 0080 0090 0098 00a0 $ tree /sys/bus/pci/devices/ /sys/bus/pci/devices/ |-- 000000.0 -> ../../../devices/pci0000:00/000000.0 |-- 000000.1 -> ../../../devices/pci0000:00/000000.1 |-- 000000.2 -> ../../../devices/pci0000:00/000000.2 |-- 000002.0 -> ../../../devices/pci0000:00/000002.0 |-- 000004.0 -> ../../../devices/pci0000:00/000004.0 |-- 000006.0 -> ../../../devices/pci0000:00/000006.0 |-- 000007.0 -> ../../../devices/pci0000:00/000007.0 |-- 000009.0 -> ../../../devices/pci0000:00/000009.0 |-- 000009.1 -> ../../../devices/pci0000:00/000009.1 |-- 000009.2 -> ../../../devices/pci0000:00/000009.2 |-- 00000c.0 -> ../../../devices/pci0000:00/00000c.0 |-- 00000f.0 -> ../../../devices/pci0000:00/00000f.0 |-- 000010.0 -> ../../../devices/pci0000:00/000010.0 |-- 000012.0 -> ../../../devices/pci0000:00/000012.0 |-- 000013.0 -> ../../../devices/pci0000:00/000013.0 |--000014.0->../../../devices/pci0000:00/000014
所有的 3 個設(shè)備列表都以相同順序排列, 因為 lspci 使用 /proc 文件作為它的信息源。拿 VGA 視頻控制器作一個例子, 0x00a0 意思是 000014.0 當劃分為域(16位), 總線(8位), 設(shè)備(5位)和功能(3位).為什么0x00a0對應(yīng)的是000014.0呢,這就要看圖2中的內(nèi)容了,根據(jù)圖2中的寄存器對應(yīng)0x00a0就代表著總線(8位), 設(shè)備(5位)和功能(3位).
0x00a0=0000000010100000,很容易看出高8位是總線號也就是0。剩下的0xa0=10100000,可以看出如果低3位表示功能號,那么剩下的10100就是設(shè)備號,補全成8位的值就是00010100即0x14.
圖4 PCI配置寄存器組
-
Linux
+關(guān)注
關(guān)注
87文章
11123瀏覽量
207921 -
PCI
+關(guān)注
關(guān)注
4文章
643瀏覽量
129960 -
PCI設(shè)備
+關(guān)注
關(guān)注
0文章
9瀏覽量
8113
原文標題:PCI設(shè)備驅(qū)動(一)
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論