网卡驱动和队列层中的数据包接收
一、从网卡说起
这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分
析。这里以
Intel
的
e100
驱动为例进行分析。
大多数网卡都是一个
PCI
设备,
PCI
设备都包含了一个标准的配置寄存器,寄存器中,包含
了
PCI
设备的厂商
ID
、设备
ID
等等信息,驱动
程序使用来描述这些寄存器的标识符。如下:
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_device_id
类型的数组,告诉内核
支持不同类型的
PCI
设备的列表,以
e100
驱动为例:
#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {\
PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, \
PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich }
static struct pci_device_id e100_id_table[] = {
INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),
INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),
INTEL_8255X_ETHERNET_DEVICE(0x1031, 3),
„„
/*
略过一大堆支持的设备
*/
{ 0, }
};
在内核中,一个
PCI
设备,使用
struct pci_driver
结构来描述,
struct pci_driver {
struct list_head node;
char *name;
struct module *owner;
const struct pci_device_id *id_table;
/* must be non-NULL for probe to be called */
int (*probe) (struct
pci_dev
*dev,
const
struct
pci_device_id
*id);
/*
New
device
inserted */
void (*remove) (struct pci_dev *dev);
/* Device removed (NULL if not a hot-plug
capable driver) */
int (*suspend) (struct pci_dev *dev, pm_message_t state);
/* Device suspended */
int (*resume) (struct pci_dev *dev);
/* Device woken up */
int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);
/* Enable wake
event */
void (*shutdown) (struct pci_dev *dev);
struct device_driver
driver;
struct pci_dynids dynids;
};
因为在系统引导的时候,
PCI
设备已经被识别,
当内核发现一个已经检测到的设备同驱动注
册的
id_table
中的信息相匹配时,
它就会触发驱动的
probe
函数,以
e100
为例:
/*
*
定义一个名为
e100_driver
的
PCI
设备
* 1
、设备的探测函数为
e100_probe;
* 2
、设备的
id_table
表为
e100_id_table
*/
static struct pci_driver e100_driver = {
.name =
DRV_NAME,
.id_table =
e100_id_table,
.probe =
e100_probe,
.remove =
__devexit_p(e100_remove),
#ifdef CONFIG_PM
.suspend =
e100_suspend,
.resume =
e100_resume,
#endif
.driver = {
.shutdown = e100_shutdown,
}
};
这样,如果系统检测到有与
id_table
中对应的设备时,就调用驱动的
probe
函数。
驱动设备在
init
函数中,调用
pci_module_init
函数初始化
PCI
设备
e100_driver:
static int __init e100_init_module(void)
{
if(((1 << debug) - 1) & NETIF_MSG_DRV) {
printk(KERN_INFO PFX "%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
printk(KERN_INFO PFX "%s\n", DRV_COPYRIGHT);
}
return pci_module_init(&e100_driver);
}
一切顺利的话,注册的
e100_probe
函数将被内核调用,这个函数完成两个重要的工作:
1
、分配
/
初始化
/
注册网络设备;
2
、完成
PCI
设备的
I/O
区域的分配和映射,以及完成硬件的其它初始化工作;
网络设备使用
struct
net_device
结构来描述,这个结构非常之大,许多重要的参考书籍对它
都有较为深入的描述,可以参考《
Linux
设备驱动程序》中网卡驱动设计的相关章节。我会
在后面的内容中,对其重要的成员进行注释;
当
probe
函数被调用,
证明已经发现了我们所支持的网卡,
这样,
就可以调用
register_netdev
函数向内核注册网络设备了,注册之前,一般会调用
alloc_etherdev
为以太网分析一个
net_device
,然后初始化它的重要成员。
除了向内核注册网络设备之外,
探测函数另一项重要的工作就是需要对硬件进行初始化,
比
如,要访问其
I/O
区域,需要为
I/O
区域分配内存区域,然后进行映射,这一步一般的流程
是:
1
、
request_mem_region()
2
、
ioremap()
对于一般的
PCI
设备而言,可以调用:
1
、
pci_request_regions()
2
、
ioremap()
pci_request_regions
函数对
PCI
的
6
个寄存器都会调用资源分配函数进行申请(需要判断是
I/O
端口还是
I/O
内存)
,例如:
int pci_request_regions(struct pci_dev *pdev, char *res_name)
{
int i;
for (i = 0; i < 6; i++)
if(pci_request_region(pdev, i, res_name))
goto err_out;
return 0;
}
int pci_request_region(struct pci_dev *pdev, int bar, char *res_name)
{
if (pci_resource_len(pdev, bar) == 0)
return 0