http://hi.baidu.com/linux_kernel/blog/item/5c8510dfbfdb9b1363279884.html
为了能看到实际的运行效果,我们选择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地址。不再详述。