PCI设备驱动
分类: linux编程2010-12-31 16:55 132人阅读 评论(0) 收藏 举报
申明:此文章并非本人所写,只是加以修改和备注
原帖请看:http://hi.baidu.com/linux_kernel/blog/category/pci%C9%E8%B1%B8%C7%FD%B6%AF
那位大大可是个大牛人,上面有不少好东东!
一、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是他所映射的内存地址空间,占据了内存地址空间的1024bytes的位置,而0000:00:1f.1则是个PCI外设的地址,以冒号和逗号分隔为4个部分:
第一个16位
示域;
第二个8位表示一个总线编号,2^8-256,故每个域最多能有256个总线;
第三个5位表示一个设备号,每个总线最多能挂载32个设备;
最后是3位,表示功能号,每个设备最多能有8种功能,也就是最多能够对应8个逻辑设备,每种功能都唯一的对应一个pci_dev结构体。
注:因为PCI规范允许单个系统拥有高达256个总线,但对于大型系统而言,这是不够的,所以,引入了域的概念。
由此,我们能得出上述的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系统的树型结构:
0号总线 (主桥)--00:01(PCI桥)
|--00:1d(USB号控制器,提供了0、1这两个逻辑设备,即功能)
|--00:1e:0(PCI桥)
|--00:1f.多功能卡(提供了0 ISA bridge、1 IDE interface、3SMBus、5 Multimedia audio controller、6 Modem这5个逻辑设备,即功能)
1号总线(主桥)--01:00.0 VGA compatible controller
2号总线(主桥)--02:00.0 IEEE1394
|--02:01.0 8139网卡
|--02:04 CardBus桥(提供了桥0、1两个逻辑设备,即功能)
由上图能得出,我的计算机上共有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控制器中的寄存器,提供了对设备设置地址空间的访问。设置地址空间的前64字节是标准化的,他提供了厂商号,设备号,版本号等信息,唯一标识一个PCI设备。同时,他也提供了最多可多达6个的I/O地址区域,每个区域能是内存也能是I/O地址。这几个I/O地址区域是驱动程式找到设备映射到内存和I/O空间的具体位置的唯一途径。有了这两点,PCI驱动程式就完成了相当于探测的功能。关于这64个字节的设置空间的周详情况,可参阅《Linux设备驱动程式第三版》P306,图12-2,不再详述。
下面,我们来看一下8139too网卡设备的设置空间的周详情况。在2.6内核的系统中,能在目录/sys/bus/pci/devices/下看到非常
多以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核心自己能够驱动哪些设备。下面,就介绍两个相关的重要数据结构。
/* 用于定义该驱动程序支持的不同类型的PCI设备列表*/
struct pci_device_id {
__u32 vendor;
__u32 device; /* 指定设备的PCI厂商和设备ID,如驱动程序可以处理任何厂商或设备ID,可使用值PCI_ANY_ID*/
__u32 subvendor;
__u32 subdevice; /* 指定设备的PCI子系统厂商和设备ID,如驱动程序可处理任何子系统厂商或设备ID,可使用PCI_ANY_ID*/
__u32 class;
__u32 class_mask; /* 可使驱动程序指定一种PCI类(class)设备,如果可以处理任何类型,则使用PCI_ANY_ID */
kernel_ulong_t driver_data; /* 如果需要,则用来保存PCI驱动程序用于区分不同设备的信息*/
};
/* 用于向PCI核心描述PCI驱动程序*/
struct pci_driver {
struct list_head node;
char *name; /* 在内核的所有PCI驱动程序的名字必须唯一,通常设置为和驱动程序模块名相同的名字*/
struct module *owner;
const struct pci_device_id *id_table; //驱动所能操纵的设备id列表。
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); //指向PCI驱动程序中的探测函数,用于插入新设备
void (*remove)(struct pci_dev *dev); //移除设备
int (*suspend)(struct pci_dev *dev, pm_message_t state); //指向挂起函数,挂起状态以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 //for struct pci_device_id
#include //for MODULE_DEVICE_TABLE
#include //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
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);
这段代码读取了网卡设备的设置地址空间的前四位,他正好是设备的厂商号和设备号。下面是输出:
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地址区域的址始位置。下面是输出:
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 );
运行结果:
mmio start: E0000800
ioaddr: f8aa6800
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 );
运行结果:
pio start: 3400
ioaddr: 00013400
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 );
运行结果:
port io start: 3400
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 );
运行结果:
port io start: 3400
00.02.3F.AC.41.9D
pci驱动的注册和初始化
分类: linux编程2011-01-13 10:30 222人阅读 评论(1) 收藏 举报
大家好,最近在看网络部分的代码,目前看到了网卡的初始化部分。书上讲到的内容主要是网卡驱动程序对网卡自身的初始化部分,即网卡驱动的probe函数是如何执行的,而很少讲到网卡是如何注册到系统中去的这一部分。
现在的网卡大部分都是连接到PCI总线上的。因此,网卡驱动是如何连接到PCI总线,又是如何与网卡设备联系起来,网卡在注册的最后又是如何调用到该网卡的probe函数的,这一个过程将在后面的文章中进行描述。整个文章分成两个部分,第一部分是讲解总线、设备以及驱动三者的联系,为第二部分具体讲解PCI总线、网卡设备和驱动做一点铺垫。
由于我在这方面也是初学,之所以想
出来是想到在总结的过程中对自己的学习也是一个梳理的过程。所以有什么地方写得不好的,还请各位多多指正,非常感谢!也希望能在这里结识更多的朋友。
在总结的过程中参考了下面一些资料,在此表示感谢:
[1]
[2]
[3]
[4] 3rd Edition.
1. 总线、设备和驱动
1.1 简单介绍
Linux设备模型中三个很重要的概念就是总线、设备和驱动,即bus,device和driver。它们分别对应的数据结构分别为struct bus_type,struct device和struct device_driver。
总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连。在最底层,Linux系统中的每一个设备都用device结构的一个实例来表示。而驱动则是使总线上的设备能够完成它应该完成的功能。
在系统中有多种总线,如PCI总线、SCSI总线等。系统中的多个设备和驱动是通过总线让它们联系起来的。在bus_type中两个很重要的成员就是struct kset drivers和struct kset devices。它分别代表了连接在这个总线上的两个链,一个是设备链表,另一个则是设备驱动链表。也就是说,通过一个总线描述符,就可以找到挂载到这条总线上的设备,以及支持该总线的不同的设备驱动程序。
1.2 总线、设备与驱动的绑定
在系统启动时,它会对每种类型的总线创建一个描述符,并将使用该总线的设备链接到该总线描述符的devices链上来。也即是说在系统初始化时,它会扫描连接了哪些设备,并且为每个设备建立一个struce device变量,然后将该变量链接到这个设备所连接的总线的描述符上去。另一方面,每当加载了一个设备驱动,则系统也会准备一个struct device_driver结构的变量,然后再将这个变量也链接到它所在总线的描述符的drivers链上去。
对于设备来说,在结构体struct device中有两个重要的成员,一个是struct bus_type *bus,另一个是struct device_driver *driver。bus成员就表示该设备是链接到哪一个总线上的,而driver成员就表示当前设备是由哪个驱动程序所驱动的。对于驱动程序来说,在结构体struct device_driver中也有两个成员,struct bus_type *bus和struct list_head devices,这里的bus成员也是指向这个驱动是链接到哪个总线上的,而devices这个链表则是表示当前这个驱动程序可以去进行驱动的那些设备。一个驱动程序可以支持一个或多个设备,而一个设备则只会绑定给一个驱动程序。
对于device与device_driver之间建立联系的方式,主要有两种方式。第一种,在计算机启动的时候,总线开始扫描连接在其上的设备,为每个设备建立一个struct device变量并链接到该总线的devices链上,然后开始初始化不同的驱动程序,驱动程序到它所在的总线的devices链上去遍历每一个还没有被绑定给某个驱动的设备,然后再查看是否能够支持这种设备,如果它能够支持这种设备,则将这个设备与这个驱动联系起来。即,将这个设备的device变量加到驱动的devices链上,同时让struct device中的device_driver指向当前这个驱动。第二种则是热插拔。也即是在系统运行时插入了设备,此时内核会去查找在该bus链上注册了的device_driver,然后再将设备与驱动联系起来。设备与驱动根据什么规则联系起来,它们是如何被联系起来的代码我们将在后面的章节进行详细的描述。
1.3 PCI总线
PCI是一种在CPU与I/O设备之间进行高速数据传输的一种总线。有很多设备都是使用PCI总线的,网卡就是其中之一。我们在前面讲了那些总线、设备与驱动方面的知识,原因就在于网卡是连接到PCI总线上,所以PCI总线、网卡设备以及网卡驱动就成了我们研究网卡的一个很重要的线索,尤其是在网络的链路层部分。下图显示了在一个系统中PCI设备的一个框图:
PCI子系统声明了一个bus_type结构,为pci_bus_type。它就是PCI总线的描述符。在这个变量上,链接了PCI设备以及支持PCI设备的驱动程序。
1.4 PCI设备与驱动
PCI设备通常由一组参数唯一地标识,它们被vendorID,deviceID和class nodes所标识,即设备厂商,型号等,这些参数保存在pci_device_id结构中。每个PCI设备都会被分配一个pci_dev变量,内核就用这个数据结构来表示一个PCI设备。
所有的PCI驱动程序都必须定义一个pci_driver结构变量,在该变量中包含了这个PCI驱动程序所提供的不同功能的函数,同时,在这个结构中也包含了一个device_driver结构,这个结构定义了PCI子系统与PCI设备之间的接口。在注册PCI驱动程序时,这个结构将被初始化,同时这个pci_driver变量会被链接到pci_bus_type中的驱动链上去。
在pci_driver中有一个成员struct pci_device_id *id_table,它列出了这个设备驱动程序所能够处理的所有PCI设备的ID值。
1.5 PCI设备与驱动的绑定过程
下面描述一下对于PCI设备与驱动绑定的过程。首先在系统启动的时候,PCI总线会去扫描连接到这个总线上的设备,同时为每一个设备建立一个pci_dev结构,在这个结构中有一个device成员,并将这些pci_dev结构链接到PCI总线描述符上的devices链。如下图所示:
第二步是当PCI驱动被加载时,pci_driver结构体将被初始化,这一过程在函数pci_register_driver中:
drv->driver.bus = &pci_bus_type;
drv->driver.probe = pci_device_probe;
最后会调用driver_register(&drv->driver)将这个PCI驱动挂载到总线描述符的驱动链上。同时在注册的过程中,会根据pci_driver中的id_table中的ID值去查看该驱动支持哪些设备,将这些设备挂载到pci_driver中的devices链中来。如下图所示:
对于不同的设备,可能驱动程序也不一样,因此,对于上图中的Dev3,可能就需要另外一个驱动程序来对其进行驱动。所以当加载了Dev3的驱动程序时,其示意图如下图所示:
上面这三个示意图就描述了总线、设备以及驱动在系统中是如何进行相互联系的。前面对于驱动注册这些函数的描述较为简单,因为网卡是一个PCI设备,因此在后面具体地讲到网卡注册时再来详细地讲解和PCI相关的注册等函数。
1.6 小结
本部分主要讲解了总线、设备以及驱动方面的一些知识,由于网卡是一个PCI设备,因此具体地讲到了一点PCI总线、PCI设备及相应的PCI驱动方面的知识,但是由于PCI本身就是很大的一个子系统,因此这里不可能对其进行详细地讲解,在后面对网卡的分析中,将对网卡中涉及到的和PCI相关的部分进行讲解。
2. 网卡在PCI层的注册
2.1 数据结构
前面第一章讲了总线、设备以及驱动方面的关系,也讲到了大多数网卡设备实际上是一个PCI设备。因此,本章就讲解网卡设备在注册时是如何注册到PCI总线上去的。在这里,以Intel的E100网卡驱动进行讲解。
前面讲到每个PCI设备都由一组参数唯一地标识,这些参数保存在结构体pci_device_id中,如下所示:
1. struct pci_device_id {
2. __u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
3. __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
4. __u32 class, class_mask; /* (class,subclass,prog-if) triplet */
5. kernel_ulong_t driver_data; /* Data private to the driver */
6. };
每个PCI设备驱动都有一个pci_driver变量,它描述了一个PCI驱动的信息,如下所示:
1. struct pci_driver {
2. struct list_head node;
3. char *name;
4. const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */
5. int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
6. void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
7. int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
8. int (*suspend_late) (struct pci_dev *dev, pm_message_t state);
9. int (*resume_early) (struct pci_dev *dev);
10. int (*resume) (struct pci_dev *dev); /* Device woken up */
11. int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable); /* Enable wake event */
12. void (*shutdown) (struct pci_dev *dev);
13. struct pci_error_handlers *err_handler;
14. struct device_driver driver;
15. struct pci_dynids dynids;
16. int multithread_probe;
17. };
每个PCI驱动中都有一个id_table成员变量,记录了当前这个驱动所能够进行驱动的那些设备的ID值。
对于E100网卡驱动来说,它的pci_driver变量定义为:
1. static struct pci_driver e100_driver = {
2. .name = DRV_NAME,
3. .id_table = e100_id_table,
4. .probe = e100_probe,
5. .remove = __devexit_p(e100_remove),
6. #ifdef CONFIG_PM
7. /* Power Management hooks */
8. .suspend = e100_suspend,
9. .resume = e100_resume,
10. #endif
11. .shutdown = e100_shutdown,
12. .err_handler = &e100_err_handler,
13. };
里面e100_id_table就表示该E100驱动所能够支持的PCI设备的ID号,其定义为:
1. #define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/
2. PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, /
3. PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich }
4. static struct pci_device_id e100_id_table[] = {
5. INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),
6. INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),
7. …
8. { 0, }
9. };
当PCI层检测到一个PCI设备能够被某PCI驱动所支持时(这是通过函数pci_match_one_device来进行检测的),就会调用这个PCI驱动上的probe函数,在该函数中会对该特定的PCI设备进行一些具体的初始化等操作。比如对于E100设备驱动来说,其probe函数为e100_probe。在这个函数中,会对网卡设备进行初始化。
e100_probe主要就涉及到网卡设备net_device的初始化,我们现在先来关注一下从网卡注册一直到调用e100_probe这一个过程的整个流程。
2.2 E100初始化
E100驱动程序的初始化是在函数e100_init_module()中的,如下:
1. static int __init e100_init_module(void)
2. {
3. if(((1 << debug) - 1) & NETIF_MSG_DRV) {
4.