编写linux网络设备驱动(下)

来源:互联网 发布:linux服务器编程模型 编辑:程序博客网 时间:2024/06/04 18:54


转载出处:

http://arttech.us/y-2011/writing-network-device-driver-b.html

 

本文介绍基于Realtek 8139芯片PCI接口的网卡驱动程序。我选择了Realtek芯片有两个原因:首先,Realtek提供免费的芯片技术手册; 第二,芯片相当便宜。

本文介绍的驱动程序是最基本的,它只有发送和接收数据包功能,和做一些简单的统计。对于一个全面和专业级的驱动程序,请参阅Linux源码。

本文(下)的主要内容是在前一文(上)实现的驱动模板的基础上进一步实现网卡驱动的分组收发功能。除了实现发包接口和收包接口,原模板上的初始化接口、打开接口和私有数据都要改动。另外,网卡的硬件收发原理在《RTL8139的收发原理》(下述简称《收发原理》)已经阐述得很清楚,本文是对《收发原理》中的理念用代码具体实现。

目录

网络设备驱动程序的开发,分解成以下步骤:

上:

  • 1.检测设备
  • 2.启用设备
  • 3.认识网络设备
  • 4.总线无关的设备访问
  • 5.理解PCI配置空间
  • 6.初始化网络设备(net_device)

中:

  • 7.RTL8139收发原理

下:

  • 8.编写网络设备的发包功能
  • 9.编写网络设备的收包功能

8.实现网络设备的发包功能

8.1 扩展rtl8139_private

在实现网络设备的打开和发送接口前,我必须先扩展我们的设备私有数据结构——rtl8139_private。

[cpp] view plain copy print?
  1. #define NUM_TX_DESC 4  
  2. struct rtl8139_private  
  3. {  
  4.     struct pci_dev *pci_dev;  /* PCI device */  
  5.     void *mmio_addr; /* memory mapped I/O addr */  
  6.     unsigned long regs_len; /* length of I/O or MMI/O region */  
  7.     unsigned int tx_flag;  
  8.     unsigned int cur_tx;  
  9.     unsigned int dirty_tx;  
  10.     unsigned char *tx_buf[NUM_TX_DESC];   /* Tx bounce buffers */  
  11.     unsigned char *tx_bufs;        /* Tx bounce buffer region. */  
  12.     dma_addr_t tx_bufs_dma;  
  13. };  


 

Table 9: rtl8139_private structure

rtl8139_private为实现发包功能新添6个成员,我们一一介绍它们。tx_flag 成员从名字可知,发送状态字,它是《收发原理》中的TSD在主机方的一个副本,用来启动发送,下面代码中你将会看到;cur_tx成员和dirty_tx成员是《收发原理》中发包原理部分提到的两个全局变量——write_buff(记录最新可用缓冲区号)和read_buff(记录最后发送缓冲区号)的实现。tx_bufs是缓冲区的起始地址,tx_buf是地址数组,分别记录四块缓冲区的起始地址,它是《收发原理》中的TSAD在主机方的一个副本。注意这两种地址都是逻辑地址,在配置TSAD前必须转换为物理地址(转换方法在代码中可以看到)。转换的根据就是下一个成员,tx_bufs_dma。tx_bufs_dma保存缓冲区的物理地址。

8.2 扩展open接口

改写了rtl8139_private后,我们看open接口的新实现:

[cpp] view plain copy print?
  1. static int rtl8139_open(struct net_device *dev)  
  2. {  
  3.     int retval;  
  4.     struct rtl8139_private *tp = dev->priv;  
  5.   
  6.     /* get the IRQ 
  7.     * second arg is interrupt handler 
  8.     * third is flags, 0 means no IRQ sharing 
  9.     */  
  10.     retval = request_irq(dev->irq, rtl8139_interrupt, 0, dev->name, dev);  
  11.     if(retval)  
  12.         return retval;  
  13.   
  14.     /* get memory for Tx buffers 
  15.     * memory must be DMAable 
  16.     */  
  17.     tp->tx_bufs = pci_alloc_consistent(  
  18.     tp->pci_dev, TOTAL_TX_BUF_SIZE, &tp->tx_bufs_dma);  
  19.   
  20.     if(!tp->tx_bufs) {  
  21.         free_irq(dev->irq, dev);  
  22.         return -ENOMEM;  
  23.     }  
  24.   
  25.     tp->tx_flag = 0;  
  26.     rtl8139_init_ring(dev);  
  27.     rtl8139_hw_start(dev);  
  28.   
  29.     return 0;  
  30. }  
  31.   
  32. static void rtl8139_init_ring (struct net_device *dev)  
  33. {  
  34.     struct rtl8139_private *tp = dev->priv;  
  35.     int i;  
  36.   
  37.     tp->cur_tx = 0;  
  38.     tp->dirty_tx = 0;  
  39.   
  40.     for (i = 0; i < NUM_TX_DESC; i++)  
  41.         tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];  
  42.   
  43.     return;  
  44. }  

Table 11: Writing the open function
 
现在简要分析一下,open函数首先通过API(request_irq)向系统申请绑定IRQ号(KEMIN:逻辑中断号是怎么得到的?)和中断处理函数(rtl8139_interrupt);接着向PCI子系统申请内存空间给发送缓冲区(tp->tx_bufs),注意,pci_alloc_consistent直接返回的是虚拟地址,物理地址通过第三个参数返回。然后在rtl8139_init_ring 函数中将缓冲区分为四段(tp->tx_buf[i])。
 
[cpp] view plain copy print?
  1. static void rtl8139_chip_reset (void *ioaddr)  
  2. {  
  3.     int i;  
  4.   
  5.     /* Soft reset the chip. */  
  6.     writeb(CmdReset, ioaddr + CR);  
  7.   
  8.     /* Check that the chip has finished the reset. */  
  9.     for (i = 1000; i > 0; i--) {  
  10.         barrier();  
  11.         if ((readb(ioaddr + CR) & CmdReset) == 0)  
  12.             break;  
  13.         udelay (10);  
  14.     }  
  15.     return;  
  16. }  


 

缓冲区分配好后,rtl8139_hw_start函数可以启动硬件了。我们首先做是的重置(reset)设备——向设备命令寄存器(CR)写入重置值,让RTL8139回到预定状态;rtl8139_chip_reset函数使用了一个循环来检测重置操作,注意循环开始用了一个内存防护(barrier)操作,确保内核每次循环确切读取设备,而不作优化去读缓存。

[cpp] view plain copy print?
  1. static void rtl8139_hw_start (struct net_device *dev)  
  2. {  
  3.     struct rtl8139_private *tp = dev->priv;  
  4.     void *ioaddr = tp->mmio_addr;  
  5.     u32 i;  
  6.   
  7.     rtl8139_chip_reset(ioaddr);  
  8.   
  9.     /* Must enable Tx before setting transfer thresholds! */  
  10.     writeb(CmdTxEnb, ioaddr + CR);  
  11.   
  12.     /* tx config */  
  13.     writel(0x00000600, ioaddr + TCR); /* DMA burst size 1024 */  
  14.   
  15.     /* init Tx buffer DMA addresses */  
  16.     for (i = 0; i < NUM_TX_DESC; i++) {  
  17.         writel(tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs),ioaddr + TSAD0 + (i * 4));  
  18.     }  
  19.   
  20.     /* Enable all known interrupts by setting the interrupt mask. */  
  21.     writew(INT_MASK, ioaddr + IMR);  
  22.   
  23.     netif_start_queue (dev);  
  24.     return;  
  25. }  


 

完成重置操作后,我们启用设备的发送功能——向设备命令寄存器(CR)写入启用值。然后,我们配置发送模式—— TCR (Transmission Configuration Register),这里我们只配置了“DMA突发传输的最大值”(Max DMA Burst Size per Tx DMA Burst),其它使用默认配置。接着我们把刚分配好四段缓冲区地址(转为物理地址后)写入四个发送描述符(TSAD),最后打开所有中断——将IMR (Interrupt Mask Register)全部位置1;

至此,设备使用前配置基本完成,最后调用netif_start_queue,把设备挂到系统活动的网络设备列表,供网络协议栈使用。

8.3 发送接口hard_start_xmit

我们现在可以进一点充实发包接口了。代码如下:

[cpp] view plain copy print?
  1. #define ETH_MIN_LEN 60  /* minimum Ethernet frame size */  
  2.   
  3. static int rtl8139_start_xmit(struct sk_buff *skb, struct net_device *dev)  
  4. {  
  5.     struct rtl8139_private *tp = dev->priv;  
  6.     void *ioaddr = tp->mmio_addr;  
  7.     unsigned int entry = tp->cur_tx;  
  8.     unsigned int len = skb->len;  
  9.   
  10.     if (len < TX_BUF_SIZE) {  
  11.         if(len < ETH_MIN_LEN)  
  12.             memset(tp->tx_buf[entry], 0, ETH_MIN_LEN);  
  13.         skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);  
  14.         dev_kfree_skb(skb);  
  15.     } else {  
  16.         dev_kfree_skb(skb);  
  17.         return 0;  
  18.     }  
  19.   
  20.     writel(tp->tx_flag | max(len, (unsigned int)ETH_MIN_LEN),  
  21.     ioaddr + TSD0 + (entry * sizeof (u32)));  
  22.     entry++;  
  23.     tp->cur_tx = entry % NUM_TX_DESC;  
  24.   
  25.     if(tp->cur_tx == tp->dirty_tx) {  
  26.         netif_stop_queue(dev);  
  27.     }  
  28.     return 0;  
  29. }  


 

Table 12: Writing start_xmit function

发送接口函数还是比较直观的。首先,函数检查数据包大小,确保不大于缓冲区,亦不小于以太网帧最小值(60字节);然后调用skb_copy_and_csum_dev函数将其拷入第cur_tx号缓冲区。完了后,配置tx_flag(这里只配置了数据包的大小,不限阀值)后调用writel将其写TSD,启动发送。接着,更新cur_tx,代码用模操作(%)来实现缓冲区循环制使用。最后判断缓冲区是否满,然则通知协议栈停止发送。

8.4 发包完成中断处理

数据包发走后还需要后续处理,处理在中断处理函数内完成。设备驱动只有一支中断处理函数,设备的中断事件需在函数内进一步区分。

rtl8139_interrupt
[cpp] view plain copy print?
  1. static void rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)  
  2. {  
  3.     struct net_device *dev = (struct net_device*)dev_instance;  
  4.     struct rtl8139_private *tp = dev->priv;  
  5.     void *ioaddr = tp->mmio_addr;  
  6.     unsigned short isr = readw(ioaddr + ISR);  
  7.   
  8.     /* clear all interrupt. 
  9.     * Specs says reading ISR clears all interrupts and writing 
  10.     * has no effect. But this does not seem to be case. I keep on 
  11.     * getting interrupt unless I forcibly clears all interrupt  
  12.     */  
  13.     writew(0xffff, ioaddr + ISR);  
  14.   
  15.     if((isr & TxOK) || (isr & TxErr))  
  16.     {  
  17.         while((tp->dirty_tx != tp->cur_tx) || netif_queue_stopped(dev))  
  18.         {  
  19.             unsigned int txstatus =  
  20.             readl(ioaddr + TSD0 + tp->dirty_tx * sizeof(int));  
  21.   
  22.             if(!(txstatus & (TxStatOK | TxAborted | TxUnderrun)))  
  23.                 break/* yet not transmitted */  
  24.   
  25.             if(txstatus & TxStatOK) {  
  26.                 LOG_MSG("Transmit OK interrupt\n");  
  27.                 tp->stats.tx_bytes += (txstatus & 0x1fff);  
  28.                 tp->stats.tx_packets++;  
  29.             }  
  30.             else {  
  31.                 LOG_MSG("Transmit Error interrupt\n");  
  32.                 tp->stats.tx_errors++;  
  33.         }  
  34.   
  35.         tp->dirty_tx++;  
  36.         tp->dirty_tx = tp->dirty_tx % NUM_TX_DESC;  
  37.   
  38.         if((tp->dirty_tx == tp->cur_tx) & netif_queue_stopped(dev))  
  39.         {  
  40.             LOG_MSG("waking up queue\n");  
  41.             netif_wake_queue(dev);  
  42.         }  
  43.         }  
  44.     }  
  45. .......  
  46. }  

代码中我们可以看到,中断处理首先将8139的中断状态复制到本地,然后重置中断状态;接着判断是什么中断事件,如果是发包完成(TxOK),则取得发送状态信息,然后作一些统计。最后更新读指针(dirty_tx),并唤醒发包队列。

值得注意的是,统计和更新操作在一个while循环内完成的,那是因为8139不是在成功发送一个包后发出中断,而是将缓冲区上所有包发走后才发出中断的。可以想像一下发包情形,由于相对于8139,主机CPU较快,它会在很短的时间内(或在发完第一个包之前)[注]将发包缓冲区填满而停掉发包队列,等待缓冲区再次可用,而8139在处理完所有TSD.OWN为0的缓冲区后,发出中断,唤醒发包队列,如此循环往复。

注:事实上,主机CPU在8139发完哪个包之前停掉发包队列均可,因为主机CPU和8139是独立工作的。只有一个情况值得特别注意,就是当主机CPU在完成写入一块缓冲区前,发包完成中断(TxOK)出现。由于发包完成中断是硬件中断,优先级较高,它会中断发包函数,优先处理。所以必须特别小心两个函数的共享变量——cur_tx和dirty_tx的访问顺序。

现在我们的驱动程序已经具有发包功能了。你可以编译并安装它,尝试ping一个远程主机,如无意外,你将在远程主机看到有ARP数据包收到;不过在本地,我们看不到远程主机发回的ARP应答包,因为我们还没有实现收包功能。

9.实现网络设备的收包功能

接下来,我们实现网络设备的收包功能,与发包功能实现相似,我们需要扩展现有打开接口和设备私有数据。

9.1 扩展rtl8139_private

[cpp] view plain copy print?
  1. struct rtl8139_private  
  2. {  
  3.     struct pci_dev *pci_dev;  /* PCI device */  
  4.     void *mmio_addr; /* memory mapped I/O addr */  
  5.     unsigned long regs_len; /* length of I/O or MMI/O region */  
  6.     unsigned int tx_flag;  
  7.     unsigned int cur_tx;  
  8.     unsigned int dirty_tx;  
  9.     unsigned char *tx_buf[NUM_TX_DESC];   /* Tx bounce buffers */  
  10.     unsigned char *tx_bufs;        /* Tx bounce buffer region. */  
  11.     dma_addr_t tx_bufs_dma;  
  12.   
  13.     struct net_device_stats stats;  
  14.     unsigned char *rx_ring;  
  15.     dma_addr_t rx_ring_dma;  
  16.     unsigned int cur_rx;  
  17. };  


 

Table 13: Extending rtl8139_private structure

rtl8139_private为实现收包功能新添4个成员。stats成员是net_device_stats实例[注],记录设备运作的统计信息(如大部分通过ifconfig查询到的统计信息来源此成员)。rx_ring成员是收包缓冲区(环形缓冲区)的逻辑地址,rx_ring_dma成员是对应的物理地址。cur_rx成员是环缓冲的读指针,不过它的类型不是指针,因为它是一个16位偏移值。

注:本文使用的代码比较原始,此成员目前已经被标准化,抽象到通用的结构net_device上去了。

9.2 扩展open接口

扩展的第一步是分配收包缓冲区。

[cpp] view plain copy print?
  1. /* Size of the in-memory receive ring. */  
  2. #define RX_BUF_LEN_IDX 2         /* 0==8K, 1==16K, 2==32K, 3==64K */  
  3. #define RX_BUF_LEN     (8192 << RX_BUF_LEN_IDX)  
  4. #define RX_BUF_PAD     16           /* see 11th and 12th bit of RCR: 0x44 */  
  5. #define RX_BUF_WRAP_PAD 2048   /* spare padding to handle pkt wrap */  
  6. #define RX_BUF_TOT_LEN  (RX_BUF_LEN + RX_BUF_PAD + RX_BUF_WRAP_PAD)  
  7.   
  8. /* this we have already done */  
  9. tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TOTAL_TX_BUF_SIZE, &tp->tx_bufs_dma);  
  10.   
  11. /* add this code to rtl8139_function */  
  12. tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN,  
  13.                &tp->rx_ring_dma);  
  14.   
  15. if((!tp->tx_bufs)  || (!tp->rx_ring)) {  
  16.     free_irq(dev->irq, dev);  
  17.   
  18. if(tp->tx_bufs) {  
  19.     pci_free_consistent(tp->pci_dev, TOTAL_TX_BUF_SIZE, tp->tx_bufs, tp->tx_bufs_dma);  
  20.     p->tx_bufs = NULL;  
  21. }  
  22. if(tp->rx_ring) {  
  23.     pci_free_consistent(tp->pci_dev, RX_BUF_TOT_LEN, tp->rx_ring,  
  24.                     tp->rx_ring_dma);  
  25.     tp->rx_ring = NULL;  
  26.     }  
  27. return -ENOMEM;  
  28. }  


 

Table 14: Extending rtl8139_open function

代码14首先算得收包缓冲区所需大小。RX_BUF_TOT_LEN 的值取决于收包配置(RCR寄存器)。我将在下面扩展的rtl8139_hw_start函数看到,我们配置了RCR的位12-11为10,位7为1;前者意为收包缓冲区大小为32K+16,后者意为当写入收到的数据包写到了缓冲区的末端,而数据包还没有收完时,剩下的数据继续往下写,而不写到缓冲区的始端,因此我们为收包缓冲区分配了2K额外的附加区。

现在我们扩展rtl8139_hw_start:

[cpp] view plain copy print?
  1. static void rtl8139_hw_start (struct net_device *dev)  
  2. {  
  3.     struct rtl8139_private *tp = dev->priv;  
  4.     void *ioaddr = tp->mmio_addr;  
  5.     u32 i;  
  6.   
  7.     rtl8139_chip_reset(ioaddr);  
  8.   
  9.     /* Must enable Tx/Rx before setting transfer thresholds! */  
  10.     writeb(CmdTxEnb | CmdRxEnb, ioaddr + CR);  
  11.   
  12.     /* tx config */  
  13.     writel(0x00000600, ioaddr + TCR); /* DMA burst size 1024 */  
  14.   
  15.     /* rx config */  
  16.     writel(((1 << 12) | (7 << 8) | (1 << 7) |  
  17.     (1 << 3) | (1 << 2) | (1 << 1)), ioaddr + RCR);  
  18.   
  19.     /* init Tx buffer DMA addresses */  
  20.     for (i = 0; i < NUM_TX_DESC; i++) {  
  21.         writel(tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs),  
  22.         ioaddr + TSAD0 + (i * 4));  
  23.     }  
  24.   
  25.     /* init RBSTART */  
  26.     writel(tp->rx_ring_dma, ioaddr + RBSTART);  
  27.   
  28.     /* initialize missed packet counter */  
  29.     writel(0, ioaddr + MPC);  
  30.   
  31.     /* no early-rx interrupts */  
  32.     writew((readw(ioaddr + MULINT) & 0xF000), ioaddr + MULINT);  
  33.   
  34.     /* Enable all known interrupts by setting the interrupt mask. */  
  35.     writew(INT_MASK, ioaddr + IMR);  
  36.   
  37.     netif_start_queue (dev);  
  38.     return;  
  39. }  

Table 15: Extending rtl8139_hw_start function

 
代码15中,首先改动的是写入CR寄存器值 CmdTxEnb | CmdRxEnb,意为同时开启收包和发包功能;接着配置了收包功能,我在代码没有使用macros作配置值,但意思已经很明白了,RCR的配置位意思如下:
  • Bit 1 – 接受物理匹配的包
  • Bit 2 – 接受组播包
  • Bit 3 – 接受广播包
  • Bit 7 – WRAP,当写入收到的数据包写到了缓冲区的末端,而数据包还没有收完时,剩下的数据继续往下写,还是截断写到始端
  • Bit 8-10 – 最大DMA单次突发传输量,我们配置为111,即无限制
  • Bit 11-12 – 收包缓冲区大小,我们配置为10,即32K+16 bytes

接着的改动是配置了8139的RBSTART寄存器,告诉8139收包缓冲区的起始地址;最后初始化了MPC (Missed Packet Counter)寄存和屏蔽了预收包中断(early rx interrupts)。

9.3 收包中断处理函数

收包功能最后一步是收包中断处理。

[cpp] view plain copy print?
  1. static void rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)  
  2. {  
  3.     struct net_device *dev = (struct net_device*)dev_instance;  
  4.     struct rtl8139_private *tp = dev->priv;  
  5.     void *ioaddr = tp->mmio_addr;  
  6.     unsigned short isr = readw(ioaddr + ISR);  
  7.   
  8.     /* clear all interrupt. 
  9.     * Specs says reading ISR clears all interrupts and writing 
  10.     * has no effect. But this does not seem to be case. I keep on 
  11.     * getting interrupt unless I forcibly clears all interrupt  
  12.     */  
  13.     writew(0xffff, ioaddr + ISR);  
  14.   
  15.     if(isr & RxOK) {  
  16.     LOG_MSG("receive interrupt received\n");  
  17.     while((readb(ioaddr + CR) & RxBufEmpty) == 0)  
  18.     {  
  19.         unsigned int rx_status;  
  20.         unsigned short rx_size;  
  21.         unsigned short pkt_size;  
  22.         struct sk_buff *skb;  
  23.   
  24.         if(tp->cur_rx > RX_BUF_LEN)  
  25.             tp->cur_rx = tp->cur_rx % RX_BUF_LEN;  
  26.   
  27.         /* TODO: need to convert rx_status from little to host endian 
  28.         * XXX: My CPU is little endian only  
  29.         */  
  30.         rx_status = *(unsigned int*)(tp->rx_ring + tp->cur_rx);  
  31.         rx_size = rx_status >> 16;  
  32.   
  33.         /* first two bytes are receive status register 
  34.         * and next two bytes are frame length 
  35.         */  
  36.         pkt_size = rx_size - 4;  
  37.   
  38.         /* hand over packet to system */  
  39.         skb = dev_alloc_skb (pkt_size + 2);  
  40.         if (skb) {  
  41.             skb->dev = dev;  
  42.             skb_reserve (skb, 2); /* 16 byte align the IP fields */  
  43.   
  44.             eth_copy_and_sum(  
  45.             skb, tp->rx_ring + tp->cur_rx + 4, pkt_size, 0);  
  46.   
  47.             skb_put (skb, pkt_size);  
  48.             skb->protocol = eth_type_trans (skb, dev);  
  49.             netif_rx (skb);  
  50.   
  51.             dev->last_rx = jiffies;  
  52.             tp->stats.rx_bytes += pkt_size;  
  53.             tp->stats.rx_packets++;  
  54.         }  
  55.         else {  
  56.             LOG_MSG("Memory squeeze, dropping packet.\n");  
  57.             tp->stats.rx_dropped++;  
  58.         }  
  59.   
  60.         /* update tp->cur_rx to next writing location  * / 
  61.         tp->cur_rx = (tp->cur_rx + rx_size + 4 + 3) & ~3; 
  62.  
  63.         /* update CAPR */  
  64.         writew(tp->cur_rx, ioaddr + CAPR);  
  65.         }  
  66.     }  
  67. //......  
  68. }  

Table 16: Interrupt Handler
 
代码中我们可以看到,中断处理首先将8139的中断状态复制到本地,然后重置中断状态;接着判断收包中断事件(RxOK)。收包处理使用了一个while循环,可见收包与发包类似,都不是每包一次中断,而每缓冲区一次中断,这样降低的中断次数,提高收发效率。收包循环第一件事是判断读指针(cur_rx)是否越出缓冲区边界(从前面的收包功能配置可知,收包缓冲区可配置附加区的),如果越出,则wrap回来。完了后开始正式的收包操作,如取得收包状态、分析包大小、分配skb、对skb作一些链路层处理后将其交与内核的收包接口netif_rx,最后统计。

收包处理最后一步是更新读指针,包括主机CPU副本cur_rx和8139的CAPR。更新8139的CAPR还有一个重要的副作用,就是如果CAPR==CBA时,8139置CR.RxBufEmpty为1,表示缓冲为空,8139对外发出消除暂停控制帧,驱动程序退出while循环,完成收包中断处理。

9.4 统计

最后实现的接口是rtl8139_get_stats,它只是简单地返回tp->stats:

[html] view plain copy print?
  1. static struct net_device_stats* rtl8139_get_stats(struct net_device *dev)  
  2. {  
  3.     struct rtl8139_private *tp = dev->priv;  
  4.     return &(tp->stats);  
  5. }  

Table 17: rtl8139_get_stats function

到此,网卡设备驱动基本完成,你可以再次编译并安装它,尝试ping一个远程主机,如无意外,你将在远程主机看到有ARP数据包收到;在本地,亦可以收到远程主机发回的ARP应答包。

10.小结

虽然专业级的设备驱动比本文的基本驱动需要更多的功能,然而,本文的基本驱动对你理解网卡驱动和开发产品级驱动是有帮助的。


 

0 0
原创粉丝点击