pci设备驱动
一、PCI簡介
??? PCI是一種外設總線規范。我們先來看一下什么是總線:總線是一種傳輸信號的路徑或信道。典型情況是,總線是連接于一個或多個導體的電氣連線,總線上連接的所有設備可在同一時間收到所有的傳輸內容。總線由電氣接口和編程接口組成。本文討論Linux 下的設備驅動,所以,重點關注編程接口。
??? PCI是Peripheral Component Interconnect(外圍設備互聯)的簡稱,是普遍使用在桌面及更大型的計算機上的外設總線。PCI架構被設計為ISA標準的替代品,它有三個主要目標:獲得在計算機和外設之間傳輸數據時更好的性能;盡可能的平臺無關;簡化往系統中添加和刪除外設的工作。
二、PCI尋址
??? 從現在開始,我想盡可能通過一些實際的例子來說明問題,而減少理論方面的問題的描述,因為,相關的理論的東西,可以在其它地方找到。
??? 我們先來看一個例子,我的電腦裝有1G的RAM,1G以后的物理內存地址空間都是外部設備IO在系統內存地址空間上的映射。/proc/iomem描述了系統中所有的設備I/O在內存地址空間上的映射。我們來看地址從1G開始的第一個設備在/proc/iomem中是如何描述的:
??? ??? ??? 40000000-400003ff : 0000:00:1f.1
??? 這是一個PCI設備,40000000-400003ff是它所映射的內存地址空間,占據了內存地址空間的1024 bytes的位置,而0000:00:1f.1則是一個PCI外設的地址,它以冒號和逗號分隔為4個部分,第一個16位表示域,第二個8位表示一個總線編號,第三個5位表示一個設備號,最后是3位,表示功能號。
??? 因為PCI規范允許單個系統擁有高達256個總線,所以總線編號是8位。但對于大型系統而言,這是不夠的,所以,引入了域的概念,每個PCI域可以擁有最多256個總線,每個總線上可支持32個設備,所以設備號是5位,而每個設備上最多可有8種功能,所以功能號是3位。由此,我們可以得出上述的PCI設備的地址是0號域0號總線上的31號設備上的1號功能。那上述的這個PCI設備到底是什么呢?下面是我的電腦上的lspci命令的輸出:
??? 00:00.0 Host bridge: Intel Corporation 82845 845 (Brookdale) Chipset Host Bridge (rev 04)
??? 00:01.0 PCI bridge: Intel Corporation 82845 845 (Brookdale) Chipset AGP Bridge(rev 04)
??? 00:1d.0 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #1) (rev 02)
??? 00:1d.1 USB Controller: Intel Corporation 82801CA/CAM USB (Hub #2) (rev 02)
??? 00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev 42)
??? 00:1f.0 ISA bridge: Intel Corporation 82801CAM ISA Bridge (LPC) (rev 02)
??? 00:1f.1 IDE interface: Intel Corporation 82801CAM IDE U100 (rev 02)
??? 00:1f.3 SMBus: Intel Corporation 82801CA/CAM SMBus Controller (rev 02)
??? 00:1f.5 Multimedia audio controller:Intel Corporation 82801CA/CAM AC'97 Audio Controller (rev 02)
??? 00:1f.6 Modem: Intel Corporation 82801CA/CAM AC'97 Modem Controller (rev 02)
??? 01:00.0 VGA compatible controller: nVidia Corporation NV17 [GeForce4 420 Go](rev a3)
??? 02:00.0 FireWire (IEEE 1394): VIA Technologies, Inc. IEEE 1394 Host Controller(rev 46)
??? 02:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+(rev 10)
??? 02:04.0 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)
??? 02:04.1 CardBus bridge: O2 Micro, Inc. OZ6933 Cardbus Controller (rev 01)
??? lspci沒有標明域,但對于一臺PC而言,一般只有一個域,即0號域。通過這個輸出我們可以看到它是一個IDE interface。由上述的輸出可以看到,我的電腦上共有3個PCI總線(0號,1號,2號)。在單個系統上,插入多個總線是通過橋(bridge)來完成的,橋是一種用來連接總線的特殊PCI外設。所以,PCI系統的整體布局組織為樹型,我們可以通過上面的lspci輸出,來畫出我的電腦上的PCI系統的樹型結構:
00:00.0(主橋)--00:01.0(PCI橋)-----01:00:0(nVidia顯卡)
????????????????? |
????????????????? |---00:1d(USB控制器)--00:1d:0(USB1號控制器)
????????????????? |?????????????????? |
????????????????? |?????????????????? |--00:1d:1(USB2號控制器)???????? ????????? |
????????????????? |-00:1e:0(PCI橋)--02:00.0(IEEE1394)
????????????????? |?????????????? |
????????????????? |?????????????? |-02:01.0(8139網卡)
????????????????? |?????????????? |
????????????????? |?????????????? |-02:04(CardBus橋)-02:04.0(橋1)
????????????????? |????????????????????????????????? |
????????????????? |????????????????????????????????? |--02:04.1(橋2)
????????????????? |
????????????????? |-00:1f(多功能板卡)-00:1f:0(ISA橋)
?????????????????????????????????????? |
?????????????????????????????????????? |--00:1f:1(IDE接口)
?????????????????????????????????????? |
?????????????????????????????????????? |--00:1f:3(SMBus)
?????????????????????????????????????? |
?????????????????????????????????????? |--00:1f:5(多媒體聲音控制器)
?????????????? ? ????????????????????? |
?????????????????????????????????????? |--00:1f:6(調制解調器)
??? 由上圖可以得出,我的電腦上共有8個PCI設備,其中0號總線上(主橋)上連有4個,1號總線上連有1個,2號總線上連有3個。00:1f是一個連有5個功能的多功能板卡。
??? 每一個PCI設備都有它映射的內存地址空間和它的I/O區域,這點是比較容易理解的。除此之外,PCI設備還有它的配置寄存器。有了配置寄存器,PCI的驅動程序就不需要探測就能訪問設備。配置寄存器的布局是標準化的,配置空間的4個字節含有一個獨一無二的功能ID,因此,驅動程序可通過查詢外設的特定 ID來識別其設備。所以,PCI接口標準在ISA之上的主要創新在于配置地址空間。
前文已講過,PCI驅動程序不需要探測就能訪問設備,而這得益于配置地址空間。在系統引導階段,PCI硬件設備保持未激活狀態,但每個PCI主板均配備有能夠處理PCI的固件,固件通過讀寫PCI控制器中的寄存器,提供了對設備配置地址空間的訪問。
??? 配置地址空間的前64字節是標準化的,它提供了廠商號,設備號,版本號等信息,唯一標識一個PCI設備。同時,它也提供了最多可多達6個的I/O地址區域,每個區域可以是內存也可以是I/O地址。這幾個I/O地址區域是驅動程序找到設備映射到內存和I/O空間的具體位置的唯一途徑。有了這兩點,PCI驅動程序就完成了相當于探測的功能。關于這64個字節的配置空間的詳細情況,可參閱《Linux設備驅動程序第三版》P306,不再詳述。
??? 下面,我們來看一下8139too網卡設備的配置空間的詳細情況。在2.6內核的系統中,可以在目錄/sys/bus/pci/drivers/下看到很多以PCI設備名命名的目錄,但不是說這些設備都存在于你的系統中。我們進入8139too目錄,其中有一個以它的設備地址0000:02:01.0命名的目錄。在這個目錄下可以找到該網卡設備相關的很多信息。其中resource記錄了它的6個I/O地址區域。內容如下:
??????? 0x0000000000003400 0x00000000000034ff 0x0000000000000101
??????? 0x00000000e0000800 0x00000000e00008ff 0x0000000000000200
??????? 0x0000000000000000 0x0000000000000000 0x0000000000000000
??????? 0x0000000000000000 0x0000000000000000 0x0000000000000000
??????? 0x0000000000000000 0x0000000000000000 0x0000000000000000
??????? 0x0000000000000000 0x0000000000000000 0x0000000000000000
??????? 0x0000000000000000 0x0000000000000000 0x0000000000000000
??? 由該文件可以看出,8139too設備使用了兩個I/O地址區域,第一個是它映射的I/O端口范圍,第二個是它映射的內存地址空間。關于這兩個值可以在/proc/iomem和/proc/ioport中得到驗證。
為了能看到實際的運行效果,我們選擇8139too網卡作為示例,從該網卡的linux驅動程序中裁剪相關代碼。
??? 一個PCI設備的驅動程序必須要向內核中的PCI核心描述自己。同時,它也必須告訴PCI核心自己能夠驅動哪些設備。下面,就介紹兩個相關的重要數據結構。
??? struct pci_device_id {
??? ??? __u32 vendor, device;?????? /* Vendor and device ID or PCI_ANY_ID*/
??? ??? __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
??? ??? __u32 class, class_mask;??? /* (class,subclass,prog-if) triplet */
??? ??? kernel_ulong_t driver_data; /* Data private to the driver */
??? };
????????
??? struct pci_driver {
??? ??? struct list_head node;
??? ??? char *name;
??? ??? struct module *owner;
??? ??? const struct pci_device_id *id_table; //驅動所能操縱的設備id列表。
??? ??? int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); //插入新設備
??? ??? void (*remove)(struct pci_dev *dev);?? //移除設備。
??? ??? int (*suspend)(struct pci_dev *dev, pm_message_t state);
??? ??? int (*resume)(struct pci_dev *dev);
??? ??? int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);
??? ??? void (*shutdown) (struct pci_dev *dev);
??? ??? struct device_driver??? driver;
??? ??? struct pci_dynids dynids;
??? };
??? pci_device_id唯一標識一個PCI設備。它的幾個成員依次分別表示:廠商號,設備號,子廠商號,子設備號,類別,類別掩碼(類可分為基類,子類),私有數據。每一個PCI設備的驅動程序都有一個pci_device_id的數組,用于告訴PCI核心自己能夠驅動哪些設備。8139too的驅動程序定義它的pci_device_id數組如下:
??? ??? static struct pci_device_id rtl8139_pci_tbl[];
??? 該數組被初始化為8139系列的一組網卡,當PCI核心得到這個數組后,會拿數組中的每一項跟從PCI配置空間中讀取到的數據進行比對,從而為該驅動程序找到正確的設備。而pci_driver代表一個pci驅動程序。成員id_talbe即是指向pci_device_id數組的指針。name是驅動程序的名字,probe完成探測工作,即拿pci_device_id數組與內核中的數據進行比對。remove完成驅動程序的移除工作。關鍵的成員就這幾個。
??? 驅動程序通過pci_module_init向內核注冊自己(我們有時會看到pci_register_driver函數,其實它們是同一個,在內核代碼中會看到,只是一個簡單的#define):
??????????? pci_module_init(&pci_driver);
??? 調用函數后,如果pci_device_id數組中標識的設備存在于系統中,并且該設備恰好還沒有驅動程序,則該驅動程序會被安裝。下面我們來看從8139too驅動代碼中裁剪的pci設備初始化代碼:
pci_driver.h:
/* pci_driver.h
?* helinqiang@hotmail.com
?* 2006-3-5
?*/
#ifndef PCI_DRIVER_H
#define PCI_DRIVER_H
#include <linux/mod_devicetable.h>? //for struct pci_device_id
#include <linux/module.h>?????????? //for MODULE_DEVICE_TABLE
#include <linux/pci.h>????????????? //for struct pci_driver
#define DRV_NAME??? "8139too"
#define DRV_VERSION "0.9.27"
#define RTL8139_DRIVER_NAME?? DRV_NAME " Fast Ethernet driver " DRV_VERSION
typedef enum{
??? RTL8139 = 0,
??? RTL8129,
}board_t;
static struct pci_device_id rtl8139_pci_tbl[] = {
??? {0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1186, 0x1300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1186, 0x1340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x13d1, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1259, 0xa117, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1259, 0xa11e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x14ea, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x14ea, 0xab07, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x11db, 0x1234, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1432, 0x9130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x02ac, 0x1012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x018a, 0x0106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x126c, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x1743, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
??? {0x021b, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#ifdef CONFIG_SH_SECUREEDGE5410
??? /* Bogus 8139 silicon reports 8129 without external PROM :-( */
??? {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#endif
#ifdef CONFIG_8139TOO_8129
??? {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8129 },
#endif
/* some crazy cards report invalid vendor ids like
???? * 0x0001 here.? The other ids are valid and constant,
???? * so we simply don't match on the main vendor id.
???? */
??? {PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0, RTL8139 },
??? {PCI_ANY_ID, 0x8139, 0x1186, 0x1300, 0, 0, RTL8139 },
??? {PCI_ANY_ID, 0x8139, 0x13d1, 0xab06, 0, 0, RTL8139 },
??? {0,}
};
MODULE_DEVICE_TABLE(pci, rtl8139_pci_tbl);
static int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id);
static void __devexit rtl8139_remove_one(struct pci_dev *pdev);
static struct pci_driver rtl8139_pci_driver = {
??? .name?????? = DRV_NAME,
??? .id_table?? = rtl8139_pci_tbl,
??? .probe????? = rtl8139_init_one,
??? .remove???? = __devexit_p(rtl8139_remove_one),
};
#endif //PCI_DRIVER_H
pci_driver.c:
/* pci_driver.c
?* helinqiang@hotmail.com
?* 2006-3-5
?*/
#include "pci_driver.h"
#include <linux/init.h>
MODULE_AUTHOR("Linqiang He, Hangzhou China");
MODULE_LICENSE("Dual BSD/GPL");
static int __init rtl8139_init_module(void)
{
??? /* when we're a module, we always print a version message,
???? * even if no 8139 board is found.
???? */
#ifdef MODULE
??? printk (KERN_INFO RTL8139_DRIVER_NAME "/n");
#endif
??? return pci_module_init(&rtl8139_pci_driver);
}
static void __exit rtl8139_cleanup_module (void)
{
??? pci_unregister_driver(&rtl8139_pci_driver);
}
module_init(rtl8139_init_module);
module_exit(rtl8139_cleanup_module);
int __devinit rtl8139_init_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
??? //這里可插入各種調試代碼,下文會有專門描述。
??? return 0;
}
void __devexit rtl8139_remove_one (struct pci_dev *pdev)
{
}
??? 注冊驅動程序成功后,rtl8139_init_one會被調用,在這個函數中,我們可以通過插入一些打印輸出語句看到PCI的配置地址空間和I/O地址區域的一些情況。
??? 首先,插入以下語句:
??????????? u16 vendor, device;
??????????? pci_read_config_word(pdev, 0, &vendor);
??????????? pci_read_config_word(pdev, 2, &device);
??????????? printk(KERN_INFO "%x, %x/n", vendor, device);
??? 這段代碼讀取了網卡設備的配置地址空間的前四位,它正好是設備的廠商號和設備號。下面是輸出:
??????????? Mar? 9 21:44:39 localhost kernel: 10ec, 8139
??? 10ec和8139就是我的網卡的廠商號和設備號了。
??? 再插入下列代碼:
??????????? u32 addr1,addr2,addr3, addr4,addr5,addr6;
??????????? pci_read_config_dword(pdev, 16, &addr1);
??????????? pci_read_config_dword(pdev, 20, &addr2);
??????????? pci_read_config_dword(pdev, 24, &addr3);
??????????? pci_read_config_dword(pdev, 28, &addr4);
??????????? pci_read_config_dword(pdev, 32, &addr5);
??????????? pci_read_config_dword(pdev, 36, &addr6);
??????????? printk(KERN_INFO "%x,%x,%x,%x,%x,%x/n",addr1, addr2, addr3, addr4,addr5,addr6);
??? 這段代碼讀取網卡設備的6個I/O地址區域的址始位置。下面是輸出:
??? Mar? 9 21:55:06 localhost kernel: 3401,e0000800,0,0,0,0
??? 可見,該設備只使用了前兩個I/O地址區域,分別標識它的I/O端口區域和內存地址空間。
??? 另外,在這里,還可直接打印出網卡的MAC地址。不再詳述。
接著上文給出的源代碼,我們可以在rtl8139_init_one中插入一些不同的調試代碼,觀察設備驅動模塊在內核中的一些動作。
8139too網卡設備的設備內存的頭6個字節存放的是該網卡的48位的MAC地址,我們可以通過訪問設備內存得到這個MAC地址。下面通過在 rtl8139_init_one在插入代碼,以四種不同方式訪問設備內存。第一種是通過訪問I/O內存實現,后三種則是通過訪問I/O端口的形式實現。
第一種:
unsigned long mmio_start, addr1, addr2;
void __iomem *ioaddr;
mmio_start = pci_resource_start( pdev, 1);
ioaddr = pci_iomap(pdev, 1, 0);
addr1 = ioread32( ioaddr );
addr2 = ioread32( ioaddr + 4 );
printk(KERN_INFO "mmio start: %lX/n", mmio_start);
printk(KERN_INFO "ioaddr: %p/n", ioaddr);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX/n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16 ) & 0xFF,
(addr1 >> 24 ) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
運行結果:
Mar 10 22:34:56 localhost kernel: mmio start: E0000800
Mar 10 22:34:56 localhost kernel: ioaddr: f8aa6800
Mar 10 22:34:56 localhost kernel: 00.02.3F.AC.41.9D
第二種:
unsigned long pio_start, pio_len, addr1, addr2;
void __iomem *ioaddr;
pio_start = pci_resource_start( pdev, 0);
pio_len = pci_resource_len (pdev, 0);
ioaddr = ioport_map(pio_start, pio_len);
addr1 = ioread32( ioaddr );
addr2 = ioread32( ioaddr + 4 );
printk(KERN_INFO "pio start: %lX/n", pio_start);
printk(KERN_INFO "ioaddr: %p/n", ioaddr);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX/n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16 ) & 0xFF,
(addr1 >> 24 ) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
運行結果:
Mar 10 22:30:52 localhost kernel: pio start: 3400
Mar 10 22:30:52 localhost kernel: ioaddr: 00013400
Mar 10 22:30:52 localhost kernel: 00.02.3F.AC.41.9D
第三種:
unsigned long pio_start, addr1, addr2;
pio_start = pci_resource_start( pdev, 0 );
addr1 = inl( pio_start );
addr2 = inl( pio_start + 4 );
printk(KERN_INFO "port io start: %lX/n", pio_start);
printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX/n",
(addr1) & 0xFF,
(addr1 >> 8) & 0xFF,
(addr1 >> 16) & 0xFF,
(addr1 >> 24) & 0xFF,
(addr2) & 0xFF,
(addr2 >> 8) & 0xFF );
運行結果:
Mar 10 22:36:18 localhost kernel: port io start: 3400
Mar 10 22:36:18 localhost kernel: 00.02.3F.AC.41.9D
第四種:
unsigned long pio_start;
u8 addr1, addr2, addr3, addr4, addr5, addr6;
pio_start = pci_resource_start( pdev, 0 );
addr1 = inb( pio_start );
addr2 = inb( pio_start + 1 );
addr3 = inb( pio_start + 2 );
addr4 = inb( pio_start + 3 );
addr5 = inb( pio_start + 4 );
addr6 = inb( pio_start + 5 );
printk(KERN_INFO "port io start: %lX/n", pio_start);
printk(KERN_INFO "%02X.%02X.%02X.%02X.%02X.%02X/n",
addr1, addr2, addr3, addr4, addr5, addr6 );
運行結果:
Mar 10 22:37:19 localhost kernel: port io start: 3400
Mar 10 22:37:19 localhost kernel: 00.02.3F.AC.41.9D
?
總結
- 上一篇: docker-ce 的安装与镜像加速
- 下一篇: ruby on rails中的分页插件K