网络驱动程序主要完成系统的初始化、数据包的发送和接收。在以前的内核版本中,网络设备的初始化主要由net_device数据结构中的init函数指针所指向的初始化函数来完成。在现在较新的2.6内核中,网络设备的初始化主要由device_driver数据结构中的probe函数指针所指向的函数来完成。数据包的发送和接收是实现Linux网络驱动程序中两个最关键的过程,对这两个过程处理的好坏将直接影响到驱动程序的整体运行质量。首先来分析CS8900A网卡设备驱动的初始化。
1初始化
CS8900A网卡设备驱动的初始化主要由device_driver数据结构中的probe函数指针所指向的初始化函数来完成,当内核启动或加载网络驱动模块的时候,就会调用这个初始化函数。该模块加载函数实现如下:
1 static int __init cirrus_init(void)
2 {
3 return driver_register(&cirrus_driver);
4 }
模块加载函数cirrus_init通过调用内核函数driver_register来注册CS8900A网卡设备驱动,driver_register函数的实现在内核<drivers/base/driver.c>文件中。对设备驱动程序进行注册和初始化是两件不同的事情。设备驱动程序应当尽快被注册,以便用户应用程序通过相应的设备文件使用它。通常设备驱动程序在最后可能的时刻才被初始化。事实上,初始化驱动程序意味着分配系统宝贵的资源,这些被分配的资源因此就对其他驱动程序不能用。关于注册的网络设备驱动结构cirrus_driver的定义如下:
1 static struct device_driver cirrus_driver = {
2 .name = "cirrus-cs89x0",
3 .bus = &platform_bus_type,
4 .probe = cirrus_drv_probe,
5 .remove = cirrus_remove,
6 .suspend = cirrus_suspend,
7 .resume = cirrus_resume,
8 };
第1行,定义变量cirrus_driver为device_driver结构类型,关于device_driver结构的定义在<include/linux/device.h>文件中。第2行,定义设备驱动名称为cirrus-cs89x0。第3行,定义bus类型为platform_bus_type。第4行,定义probe函数为cirrus_drv_probe,也就是说该网络设备的初始化是由cirrus_drv_probe函数来完成的,下面会具体讲述这个函数。第5行,定义remove函数为cirrus_remove,该函数主要完成网络设备的退出功能。第6行,定义suspend函数为cirrus_suspend,用来实现设备驱动的挂起操作,一般不用实现。第7行,定义resume函数为cirrus_resume,该函数用来实现从挂起状态返回到继续执行状态,一般也不用实现。
现在来分析一下初始化函数cirrus_drv_probe的具体实现。在初始化函数中通过检测物理设备的硬件特征来侦测网络物理设备是否存在,然后再对设备进行资源配置,以及内存映射,接下来构造设备的net_device数据结构,并用检测到的数据对net_device中的变量初始化,最后向Linux内核注册该设备并申请内存空间。
1 int __init cirrus_drv_probe (struct device *dev)
2 {
3 struct platform_device *pdev = to_platform_device(dev);
4 struct resource *res;
5 unsigned int *addr;
6 int ret;
7
8 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
9 if (!res) {
10 ret = -ENODEV;
11 goto out;
12 }
13
14 /* Request the regions. */
15 if (!request_mem_region(res->start, 16, "cirrus-cs89x0")) {
16 ret = -EBUSY;
17 goto out;
18 }
19
20 /* remap it. */
21 addr = ioremap(res->start, res->end - res->start);
22 if (!addr) {
23 ret = -ENOMEM;
24 goto release_1;
25 }
26
27 ndev = alloc_etherdev(sizeof(cirrus_t));
28 if (!ndev) {
29 printk("cirrus-cs89x0: could not allocate device.\n");
30 ret = -ENOMEM;
31 goto release_2;
32 }
33
34 SET_NETDEV_DEV(ndev, dev);
35
36 ndev->irq = platform_get_irq(pdev, 0);
37 printk(KERN_DEBUG "cirrus: irq:%d\n",ndev->irq);
38
39 dev_set_drvdata(dev, ndev);
40
41 ret = cirrus_probe(ndev, (unsigned long)addr);
42 if (ret != 0)
43 goto release_3;
44 return 0;
45
46 release_3:
47 dev_set_drvdata(dev, NULL);
48 free_netdev(ndev);
49 release_2:
50 iounmap(addr);
51 release_1:
52 release_mem_region(res->start, res->end - res->start);
53 out:
54 printk("cirrus-cs89x0: not found (%d).\n", ret);
55 return ret;
56 }
        现在来分析上述代码。第3行,调用to_platform_device宏将device结构转化为platform_device结构的指针。第4-6行,定义一些该函数内部将用到的局部变量。第8-12行,调用platform_get_resource内核函数来为该平台设备申请内存资源,当该函数返回值为0时,表示申请内存资源失败,否则表示成功,返回申请的资源地址。第15-18行,调用request_mem_region宏来请求分配指定的I/O内存资源,如果返回值为0表示设备或资源被占用。第21-25行,调用ioremap函数将该设备的物理地址转化为内核地址,如果返回值为0,表示失败,此时需要释放指定的I/O内存资源。第27-32行,调用alloc_etherdev函数分配和设置一个以太网设备,如果返回值为0,表示分配失败,然后调用iounmap函数取消之前的内存映射。第34行,调用SET_NETDEV_DEV宏实现为系统文件系统中物理设备创建一个与网络类逻辑设备的链接,也就是说将物理设备与网络设备联系起来。第36行,调用platform_get_irq函数获得一个设备IRQ,并将获得结果传递给ndev->irq。第39行,调用dev_set_drvdata函数将网络设备与驱动具体的数据关联起来。第41-44行,调用cirrus_probe函数实现对网络设备的初始化工作,如果返回0,表示初始化失败,然后调用dev_set_drvdata将驱动设备的具体数据设为空,最后调用free_netdev内核函数释放之前注册的网络设备。关于cirrus_probe函数的具体实现,下面将具体介绍。在这里为止,对cirrus_drv_probe函数的介绍基本完毕。
cirrus_probe函数主要用来初始化网络设备,包括数据包发送、接收函数的定义,网络设备的注册等。下面将具体分析该函数的实现:
1 int __init cirrus_probe (struct net_device *dev, unsigned long ioaddr)
2 {
3 cirrus_t *priv = netdev_priv(dev);
4 int i;
5 u16 value;
6
7 ether_setup (dev);
8
9 dev->open = cirrus_start;
10 dev->stop = cirrus_stop;
11 dev->hard_start_xmit = cirrus_send_start;
12 dev->get_stats = cirrus_get_stats;
13 dev->set_multicast_list = cirrus_set_receive_mode;
14 dev->set_mac_address = cirrus_set_mac_address;
15 dev->tx_timeout = cirrus_transmit_timeout;
16 dev->watchdog_timeo = HZ;
17
18 dev->if_port = IF_PORT_10BASET;
19 dev->priv = (void *)priv;
20
21 spin_lock_init(&priv->lock);
22
23 SET_MODULE_OWNER (dev);
24
25 dev->base_addr = ioaddr;
26
27 /* if an EEPROM is present, use it's MAC address */
28 if (!cirrus_eeprom(dev,&priv->eeprom))
29 for (i = 0; i < 6; i++)
30 dev->dev_addr[i] = priv->eeprom.mac[i];
31 else
32 cirrus_parse_mac(cirrus_mac, dev->dev_addr);
33
34 /* verify EISA registration number for Cirrus Logic */
35 if ((value = cirrus_read (dev,PP_ProductID)) != EISA_REG_CODE) {
36 printk (KERN_ERR "%s: incorrect signature 0x%.4x\n",dev->name,value);
37 return (-ENXIO);
38 }
39
40 /* verify chip version */
41 value = cirrus_read (dev,PP_ProductID + 2);
42 if (VERSION (value) != CS8900A) {
43 printk (KERN_ERR "%s: unknown chip version 0x%08x\n",dev->name,VERSION (value));
44 return (-ENXIO);
45 }
46 printk (KERN_INFO "%s: CS8900A rev %c detected\n",dev->name,'B' + REVISION (value) - REV_B);
47
48 /* setup interrupt number */
49 cirrus_write (dev,PP_IntNum,0);
50
51 /* configure MAC address */
52 for (i = 0; i < ETH_ALEN; i += 2)
53 cirrus_write (dev,PP_IA + i,dev->dev_addr[i] | (dev->dev_addr[i + 1] << 8));
54
55 return register_netdev(dev);
56 }
现在来分析上述代码。第3行,调用netdev_priv函数获得net_device结构末端地址,也就是网卡私有数据结构的起始地址。关于netdev_priv函数的内核实现如下:
static inline void *netdev_priv(struct net_device *dev)
{
return (char *)dev + ((sizeof(struct net_device)
+ NETDEV_ALIGN_CONST)
& ~NETDEV_ALIGN_CONST);
}
第4-5行,定义两个局部变量供函数内部使用。第7行,调用ether_setup函数设置以太网相关的网络设备属性。第9-10行,定义网络设备的open方法和stop方法分别由cirrus_start和cirrus_stop函数实现,这两个函数会在后面具体讲解。第11行,定义网络设备的数据包发送函数hard_start_xmit由cirrus_send_start函数具体实现。该函数是网络设备驱动中非常重要的一个函数,后面将会具体介绍该函数的实现。第12行,定义网络设备的get_stats方法由cirrus_get_stats函数实现,主要用来获得网络设备的状态信息。第13行,设备网络设备的set_multicast_list方法由cirrus_set_receive_mode函数完成,主要实现设置接收数据包的类型。第14行,定义网络设备的set_mac_address方法由cirrus_set_mac_address函数实现,该函数用来设备网络设备的MAC地址。第15行,定义网络设备的tx_timeout方法由cirrus_transmit_timeout函数完成,该函数的用于当传输数据包超时时告诉内核来调度网络处理事件队列。第16行,初始化网络设备watchdog_timeo的值为HZ。第18行,初始化网络设备的if_port属性为IF_PORT_10BASET,即选择10M以太网。第19行,初始化网络设备的私有数据。第21行,调用spin_lock_init宏来初始化网络设备的自旋锁。第23行,调用SET_MODULE_OWNER宏设置网络设备的模块拥有属性。第25行,初始化网络设备的base_addr为ioremap映射后的地址。第28-32行,检查是否有EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦写可编程只读存储器),如果有的话,设置网络设备的MAC地址为EEPROM的MAC地址,否则,调用cirrus_parse_mac函数来获得网络设备的MAC地址。第35-38行,通过读CS8900A的产品标识寄存器来检查是否是已定义的产品,如果不是,返回无此设备的错误标志。第41-45行,验证芯片的版本是否与定义的版本相同,如果不同,返回无此设备的错误标志。第49行,调用cirrus_write函数来设置网络设备中断的个数在相应的寄存器中。第52-53行,调用cirrus_write函数将MAC地址写到相应的寄存器。第55行,调用register_netdev函数来注册网络设备到内核中去,根据该函数的返回值可以判断是否注册成功,返回值为0表示成功,否则表示错误。
关于CS8900A网卡设备驱动的初始化过程已经介绍完毕,下面将接着介绍网络设备的打开和关闭函数的实现。
2打开和关闭
网络设备的打开(open)和关闭(stop)是非常重要的网络设备驱动的两个实现函数。打开方法的作用就是激活网络接口,使它能接收来自网络的数据并且传递到网络协议栈的上层,也可以将数据发送到网络上。先来分析CS8900A网卡设备的打开实现函数cirrus_start:
1 static int cirrus_start (struct net_device *dev)
2 {
3 int result;
4
5 /* valid ethernet address? */
6 if (!is_valid_ether_addr(dev->dev_addr)) {
7 printk(KERN_ERR "%s: invalid ethernet MAC address\n",dev->name);
8 return (-EINVAL);
9 }
10
11 /* install interrupt handler */
12 if ((result = request_irq (dev->irq,&cirrus_interrupt,0,dev->name,dev)) < 0) {
13 printk (KERN_ERR "%s: could not register interrupt %d\n",dev->name,dev->irq);
14 return (result);
15 }
16
17 set_irq_type(dev->irq, IRQT_RISING);
18
19 /* enable the ethernet controller */
20 cirrus_set (dev,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);
21 cirrus_set (dev,PP_RxCTL,RxOKA | IndividualA | BroadcastA);
22 cirrus_set (dev,PP_TxCFG,TxOKiE | Out_of_windowiE | JabberiE);
23 cirrus_set (dev,PP_BufCFG,Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);
24 cirrus_set (dev,PP_LineCTL,SerRxON | SerTxON);
25 cirrus_set (dev,PP_BusCTL,EnableRQ);
26
27 #ifdef FULL_DUPLEX
28 cirrus_set (dev,PP_TestCTL,FDX);
29 #endif /* #ifdef FULL_DUPLEX */
30
31 /* start the queue */
32 netif_start_queue (dev);
33
34 return (0);
35 }
现在来分析上述代码。第6-9行,调用is_valid_ether_addr函数来确认已知的以太网地址是否有效,如果不是返回无效错误标志。第12-15行,调用request_irq函数来申请一个IRQ资源,并且定义该中断处理函数为cirrus_interrupt,这个函数在后面会专门介绍。第17行,调用set_irq_type函数来设置刚才申请的IRQ类型,设置类型为上升沿触发(IRQT_RISING)。第20-25行,通过调用cirrus_set函数来设置CS8900A芯片的相关寄存器,来启动CS8900A以太网控制器。关于需要设置哪些寄存器需要参考CS8900A芯片的用户手册。第27-29行,如果支持全双工传输模式,需要调用cirrus_set函数来设置相应的寄存器使该功能启动。第32行,调用netif_start_queue函数用来告诉上层网络协议该驱动程序有空的缓冲区可用,请开始送数据包。第34行,返回0。
关闭方法用于停止网络设备,它的作用与open方法相反,CS8900A网卡设备的关闭方法由cirrus_stop函数实现,具体实现如下:
1 static int cirrus_stop (struct net_device *dev)
2 {
3 /* disable ethernet controller */
4 cirrus_write (dev,PP_BusCTL,0);
5 cirrus_write (dev,PP_TestCTL,0);
6 cirrus_write (dev,PP_SelfCTL,0);
7 cirrus_write (dev,PP_LineCTL,0);
8 cirrus_write (dev,PP_BufCFG,0);
9 cirrus_write (dev,PP_TxCFG,0);
10 cirrus_write (dev,PP_RxCTL,0);
11 cirrus_write (dev,PP_RxCFG,0);
12
13 /* uninstall interrupt handler */
14 free_irq (dev->irq,dev);
15
16 /* stop the queue */
17 netif_stop_queue (dev);
18
19 return (0);
20 }
        现在来分析上述代码。第4-11行,调用cirrus_write函数写CS8900A芯片相应的寄存器,从而关闭CS8900A以太网控制器。第14行,调用free_irq函数释放之前申请的IRQ资源。第17行,调用netif_stop_queue函数告诉上层网络请不要再送数据包。
关于CS8900A网络设备的打开和关闭方法已经分析完毕,相对来说这两个方法的实现比较简单,但是非常重要。下面将介绍网卡驱动的中断处理函数。
3中断处理
CS8900A网卡设备驱动的中断处理函数是对接收帧(Frame,即帧。数据在网络上是以很小的称为帧的单位传输的)事件,发送帧事件,Buffer事件,传输出现冲突事件和获取丢失帧事件等进行处理。关于该中断处理函数由cirrus_interrupt函数实现,具体实现如下:
1 static irqreturn_t cirrus_interrupt (int irq,void *id)
2 {
3 struct net_device *dev = (struct net_device *) id;
4 cirrus_t *priv = netdev_priv(dev);
5 u16 status;
6
7 if (dev->priv == NULL) {
8 printk (KERN_WARNING "%s: irq %d for unknown device.\n",dev->name,irq);
9 return IRQ_RETVAL(IRQ_NONE);
10 }
11
12 spin_lock(&priv->lock);
13
14 while ((status = cirrus_read (dev,PP_ISQ))) {
15 switch (RegNum (status)) {
16 case RxEvent:
17 cirrus_receive (dev);
18 break;
19
20 case TxEvent:
21 priv->stats.collisions += ColCount (cirrus_read (dev,PP_TxCOL));
22 if (!(RegContent (status) & TxOK)) {
23 priv->stats.tx_errors++;
24 if ((RegContent (status) & Out_of_window)) priv->stats.tx_window_errors++;
25 if ((RegContent (status) & Jabber)) priv->stats.tx_aborted_errors++;
26 break;
27 } else if (priv->txlen) {
28 priv->stats.tx_packets++;
29 priv->stats.tx_bytes += priv->txlen;
30 }
31 priv->txlen = 0;
32 netif_wake_queue (dev);
33 break;
34
35 case BufEvent:
36 if ((RegContent (status) & RxMiss)) {
37 u16 missed = MissCount (cirrus_read (dev,PP_RxMISS));
38 priv->stats.rx_errors += missed;
39 priv->stats.rx_missed_errors += missed;
40 }
41 if ((RegContent (status) & TxUnderrun)) {
42 priv->stats.tx_errors++;
43 /* Shift start tx, if underruns come too often */
44 switch (priv->stats.tx_fifo_errors++) {
45 case 3: priv->txafter = After381; break;
46 case 6: priv->txafter = After1021; break;
47 case 9: priv->txafter = AfterAll; break;
48 default: break;
49 }
50 }
51 /* Wakeup only for tx events ! */
52 if ((RegContent (status) & (TxUnderrun | Rdy4Tx))) {
53 priv->txlen = 0;
54 netif_wake_queue (dev);
55 }
56 break;
57
58 case TxCOL:
59 priv->stats.collisions += ColCount (cirrus_read (dev,PP_TxCOL));
60 break;
61
62 case RxMISS:
63 status = MissCount (cirrus_read (dev,PP_RxMISS));
64 priv->stats.rx_errors += status;
65 priv->stats.rx_missed_errors += status;
66 break;
67
68 default:
69 break;
70 }
71 }
72
73 spin_unlock(&priv->lock);
74 return IRQ_RETVAL(IRQ_HANDLED);
75 }
现在来分析上述代码。第3行,将参数id强制转化为net_device结构的指针然后赋值给dev变量。第4行,调用netdev_priv函数来获得网卡私有数据结构的起始地址赋值给变量priv。第7-10行,如果网络设备的私有数据为空,直接返回错误。第12行,调用spin_lock宏来获得网络设备私有数据的自旋锁。第14行,调用cirrus_read函数读CS8900A的中断状态队列寄存器来获得当前的中断状态。第15行,调用RegNum宏将得出具体的中断类型。第16-17行,此时为接收帧事件,当网卡接收到数据就会触发一次该中断,然后调用cirrus_receive函数来处理数据包的接收,关于该函数的实现在下面会具体介绍。第20-33行,如果网卡将数据发送成功,则会触发一次该中断,在中断处理例程中将net_device_stats结构体中的tx_packets(表示发送的包的个数)元素递增并通知上层可继续送包下来。第35-56行,Buffer事件当RxMiss置位表示由于数据从缓冲区中搬移到主机速度较慢而丢失了一些接收的帧,读寄存器获取丢失的包的数目。当TxUnderrun置位表示在帧结束前网卡运行已过时。改变网络状态结构体中对应元素的值,接着通知上层可往下发送包数据。第58-60行,当传输出现冲突错误时,通过读寄存器值得到当前冲突的个数,加到统计结构体中的对应元素collisions上。第62-66行,读寄存器获取丢失帧的个数,并将其状态结果加到统计结构体对应的元素值上。第73行,调用spin_unlock宏来解锁。第74行,返回1表示中断处理完成。
关于CS8900A网卡设备的中断处理过程已经分析完毕,下面将介绍网卡驱动的发送数据函数。
4发送数据
网络设备数据发送是由net_device结构中的hard_start_xmit方法实现的,所有的网络设备驱动程序都必须实现该方法。CS8900A网卡设备驱动的数据发送具体是由cirrus_send_start函数实现,其实现代码如下:
1 static int cirrus_send_start (struct sk_buff *skb,struct net_device *dev)
2 {
3 cirrus_t *priv = netdev_priv(dev);
4 u16 status;
5
6 /* Tx start must be done with irq disabled
7 * else status can be wrong */
8 spin_lock_irq(&priv->lock);
9
10 netif_stop_queue (dev);
11
12 cirrus_write (dev,PP_TxCMD,TxStart (priv->txafter));
13 cirrus_write (dev,PP_TxLength,skb->len);
14
15 status = cirrus_read (dev,PP_BusST);
16
17 if ((status & TxBidErr)) {
18 printk (KERN_WARNING "%s: Invalid frame size %d!\n",dev->name,skb->len);
19 priv->stats.tx_errors++;
20 priv->stats.tx_aborted_errors++;
21 priv->txlen = 0;
22 spin_unlock_irq(&priv->lock);
23 return (1);
24 }
25
26 if (!(status & Rdy4TxNOW)) {
27 printk (KERN_WARNING "%s: Transmit buffer not free!\n",dev->name);
28 priv->stats.tx_errors++;
29 priv->txlen = 0;
30 spin_unlock_irq(&priv->lock);
31 return (1);
32 }
33
34 cirrus_frame_write (dev,skb);
35
36 #ifdef DEBUG
37 dump_packet (dev,skb,"send");
38 #endif /* #ifdef DEBUG */
39
40 dev->trans_start = jiffies;
41 spin_unlock_irq(&priv->lock);
42
43 dev_kfree_skb (skb);
44 priv->txlen = skb->len;
45
46 return (0);
47 }
现在来分析上述代码。第3行,调用netdev_priv函数来获得网卡私有数据结构的起始地址赋值给变量priv。第8行,调用spin_lock_irq宏获得自旋锁,该宏首先会关闭当前CPU的中断,然后获得锁,因为在处理发送数据之前,必须先关闭中断,否则会引起错误。第10行,调用netif_stop_queue函数告诉上层网络请不要再送数据包。第12-13行,调用cirrus_write函数写数据发送命令寄存器开始发送数据,并且给发送数据长度寄存器中写要发送数据包的长度。第15行,调用cirrus_read函数读Bus状态寄存器。第16-24行,当数据帧的大小不在规定的大小范围之内时产生Bid错误。当Bus状态寄存器中的TxBidErr置位时,表明已经产生了Bid错误,此时在统计数据结构中增加相应的错误计数值,然后调用spin_unlock_irq宏解锁,最后退出发送函数。第26-32行,当Bus状态寄存器中的Rdy4TxNOW置0时,表明Bus状态还没有准备就绪发送数据,此时在统计数据结构中增加相应的错误计数值,然后调用spin_unlock_irq宏解锁,最后退出发送函数。第34行,调用cirrus_frame_write函数将sk_buff结构的data值写进设备I/O地址中,该函数的实质就是将数据发送到硬件层。第36-38行,如果打开了调试选项,将调用dump_packet函数来打印发送数据包的一些数据信息。第40行,将jiffies值赋给设备结构的trans_start项,jiffies用来统计系统自运行以来的时钟中断的次数,每次时钟中断都会使jiffies的加1。第41行,调用spin_unlock_irq宏解锁。第43行,调用dev_kfree_skb函数来释放缓冲区,并把它返回给缓冲池(缓存)。第46行,退出发送数据函数。
关于CS8900A网卡设备的发送数据过程已经分析完毕,下面将介绍网卡驱动的接收数据函数。
5接收数据
当有数据到达时产生中断信号,网络设备驱动功能层调用中断处理程序来处理数据包的接收,在中断处理程序中调用cirrus_receive函数具体完成数据包的接收,关于cirrus_receive函数的具体实现如下:
1 static void cirrus_receive (struct net_device *dev)
2 {
3 cirrus_t *priv = netdev_priv(dev);
4 struct sk_buff *skb;
5 u16 status,length;
6
7 status = cirrus_read (dev,PP_RxStatus);
8 length = cirrus_read (dev,PP_RxLength);
9
10 if (!(status & RxOK)) {
11 priv->stats.rx_errors++;
12 if ((status & (Runt | Extradata))) priv->stats.rx_length_errors++;
13 if ((status & CRCerror)) priv->stats.rx_crc_errors++;
14 return;
15 }
16
17 if ((skb = dev_alloc_skb (length + 4)) == NULL) {
18 priv->stats.rx_dropped++;
19 return;
20 }
21
22 skb->dev = dev;
23 skb_reserve (skb,2);
24
25 cirrus_frame_read (dev,skb,length);
26
27 #ifdef DEBUG
28 dump_packet (dev,skb,"recv");
29 #endif /* #ifdef DEBUG */
30
31 skb->protocol = eth_type_trans (skb,dev);
32
33 netif_rx (skb);
34 dev->last_rx = jiffies;
35
36 priv->stats.rx_packets++;
37 priv->stats.rx_bytes += length;
38 }
        现在来分析上述代码。第3行,调用netdev_priv函数来获得网卡私有数据结构的起始地址赋值给变量priv。第4行,定义一个sk_buff结构的指针,用来存放接收的数据包。第7-8行,通过调用cirrus_read函数来读取接收状态寄存器和接收数据长度寄存器来分别获得接收数据的状态和接收到数据的长度。第10-15行,当接收状态未就绪时,将统计结构的相应元素值加1,最后退出数据接收函数。第17-20行,调用dev_alloc_skb函数为sk_buff结构指针申请内存空间,当申请失败时,退出数据接收函数。第22行,将网络设备的指针赋给sk_buff结构的dev元素。第23行,调用skb_reserve函数来填充2个字节到数据头,这样确保接收到的数据中IP包的起始地址是字节对齐的。第25行,调用cirrus_frame_read函数从硬件读取发送的数据,将数据存放到之前申请的skb内存中。第27-29行,如果开启调试功能,调用dump_packet函数来打印接收数据的相关信息。第31行,调用eth_type_trans函数来获得数据包的协议类型。第33行,调用netif_rx函数把接收到的数据包传输到网络协议的上层进行处理。第34行,将jiffies值赋给网络设备结构的last_rx元素。第36-37行,增加统计结构的接收包个数以及接收包的字节数据长度。
         到此为止,关于CS8900A网卡设备驱动核心实现代码基本分析完毕,有兴趣的读者可以参考光盘中提供的完整代码进行分析学习。