以文本方式查看主题

-  曙海教育集团论坛  (http://peixun0.cn/bbs/index.asp)
--  Linux驱动开发  (http://peixun0.cn/bbs/list.asp?boardid=33)
----  NAPI技术在Linux网络驱动上的应用  (http://peixun0.cn/bbs/dispbbs.asp?boardid=33&id=1734)

--  作者:wangxinxin
--  发布时间:2010-11-24 11:29:37
--  NAPI技术在Linux网络驱动上的应用
这个方法通常被网络层在向驱动的接收循环队列获取新的数据包时刻调用,而驱动的接收循环队列中可以向网络层交付的包数量则在 dev->quota 字段中表示,我们来看 8139cp 中 POLL 的原型:

  static int cp_rx_poll (struct net_device *dev, int *budget)   

  参数 budget 的上层任务所需要底层传递的数据包的数量,这个数值不能超过netdev_max_backlog 的值。   

  总而言之,POLL 方法被网络层调用,只负责按照网络层的要求值("预算"值)提交对应数量的数据包。8139CP 的 POLL 方法注册通常在设备驱动程序模块初始化(调用 probe)的时候进行,如下:

  static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)

  {

  … …

  dev->poll = cp_rx_poll;

  … …

  }

  

  设备的 POLL 方法正如前所说的是被网络层上的软中断 net_rx_action 调用,我们现在来看具体的流程:

  

  static int cp_rx_poll (struct net_device *dev, int *budget)

  {

   struct cp_private *cp = netdev_priv(dev);

   unsigned rx_tail = cp->rx_tail;

   /*设定每次进行调度的时候从设备发送到网络层次最大的数据包的大小*/

  unsigned rx_work = dev->quota;

   unsigned rx;

  

  rx_status_loop:

   rx = 0;

  /*重新打开NIC中断,在 cp_interrupt 中断句柄中中断关闭了,现在 POLl 已经开始处理环行缓冲队列中的数据,

  所以中断可以打开,准备接收新的数据包*/

   cpw16(IntrStatus, cp_rx_intr_mask);  

   while (1) {/*POLL循环的开始*/

   u32 status, len;

   dma_addr_t mapping;

   struct sk_buff *skb, *new_skb;

   struct cp_desc *desc;

   unsigned buflen;

  /*从下标为rx_tail的内存中的环行缓冲队列接收队列rx_skb上"摘下"套接字缓冲区*/

   skb = cp->rx_skb[rx_tail].skb;

   if (!skb)

   BUG();

  

   desc = &cp->rx_ring[rx_tail];

  /*检查在 NIC 的环形队列(rx_ring)上的最后的数据接收状态,是否有出现接收或者 FIFO 的错误,是否*/

   status = le32_to_cpu(desc->opts1);

   if (status & DescOwn)

   break;

  

   len = (status & 0x1fff) - 4;

   mapping = cp->rx_skb[rx_tail].mapping;

  

   if ((status & (FirstFrag   LastFrag)) != (FirstFrag   LastFrag)) {

   /* we don\'t support incoming fragmented frames.

   * instead, we attempt to ensure that the

   * pre-allocated RX skbs are properly sized such

   * that RX fragments are never encountered

   */

   cp_rx_err_acct(cp, rx_tail, status, len);

   cp->net_stats.rx_dropped++;

   cp->cp_stats.rx_frags++;

   goto rx_next;

   }

  

   if (status & (RxError   RxErrFIFO)) {

   cp_rx_err_acct(cp, rx_tail, status, len);

   goto rx_next;

   }

  

   if (netif_msg_rx_status(cp))

   printk(KERN_DEBUG "%s: rx slot %d status 0x%x len %d\\n",

   cp->dev->name, rx_tail, status, len);

  

   buflen = cp->rx_buf_sz + RX_OFFSET;

  /*创建新的套接字缓冲区*/

   new_skb = dev_alloc_skb (buflen);

   if (!new_skb) {

   cp->net_stats.rx_dropped++;

   goto rx_next;

   }

  

   skb_reserve(new_skb, RX_OFFSET);

   new_skb->dev = cp->dev;

  /*解除原先映射的环行队列上的映射区域*/

   pci_unmap_single(cp->pdev, mapping,

   buflen, PCI_DMA_FROMDEVICE);

  /*检查套接字缓冲区(sk_buff)上得到的数据校验和是否正确*/

   /* Handle checksum offloading for incoming packets. */

   if (cp_rx_csum_ok(status))

   skb->ip_summed = CHECKSUM_UNNECESSARY;

   else

   skb->ip_summed = CHECKSUM_NONE;

  /*按照数据的实际大小重新定义套接字缓冲区的大小*/

   skb_put(skb, len);  

   mapping =

   cp->rx_skb[rx_tail].mapping =

  /*DMA影射在前面新创建的套接字缓冲区虚拟地址new_buf->tail到实际的物理地址上,

  并且把这个物理地址挂在接收缓冲区的队列中*/

   pci_map_single(cp->pdev, new_skb->tail,

   buflen, PCI_DMA_FROMDEVICE);

  /*把新建立的缓冲区的虚拟地址挂在接收缓冲区的队列中,在下一次访问rx_skb数组的这个结构时候,

  POLL方法会从这个虚拟地址读出接收到的数据包*/

   cp->rx_skb[rx_tail].skb = new_skb;

  /*在cp_rx_skb调用netif_rx_skb,填充接收数据包队列,等待网络层在Bottom half队列中调用ip_rcv接收网络数据,

  这个函数替代了以前使用的netif_rx*/

   cp_rx_skb(cp, skb, desc);

   rx++;  

  rx_next:

  /*把前面映射的物理地址挂在NIC设备的环行队列上(也就是rx_ring上,它是在和NIC中物理存储区进行了DMA映射的,

  而不是驱动在内存中动态建立的),准备提交给下层(NIC)进行数据传输*/

   cp->rx_ring[rx_tail].opts2 = 0;

   cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);

  /*在相应的传输寄存器中写入控制字,把rx_ring的控制权从驱动程序交还给NIC硬件*/

   if (rx_tail == (CP_RX_RING_SIZE - 1))

   desc->opts1 = cpu_to_le32(DescOwn   RingEnd  

   cp->rx_buf_sz);

   else

   desc->opts1 = cpu_to_le32(DescOwn   cp->rx_buf_sz);

  /*步进到下一个接收缓冲队列的下一个单元*/

   rx_tail = NEXT_RX(rx_tail);

  

   if (!rx_work--)

   break;  

   cp->rx_tail = rx_tail;

  /*递减配额值quota,一旦quota递减到0表示这次的POLL传输已经完成了使命,

  就等待有数据到来的时候再次唤醒软中断执行POLL方法*/

   dev->quota -= rx;

   *budget -= rx;  

   /* if we did not reach work limit, then we\'re done with

   * this round of polling

   */

   if (rx_work) {

  /*如果仍然有数据达到,那么返回POLL方法循环的开始,继续接收数据*/

   if (cpr16(IntrStatus) & cp_rx_intr_mask)

   goto rx_status_loop;

  /*这里表示数据已经接收完毕,而且没有新的接收中断产生了,这个时候使能NIC的接收中断,

  并且调用__netif_rx_complete把已经完成POLL的设备从poll_list上摘除,等待下一次中断产生的时候,

  再次把设备挂上poll_list队列中。*/

   local_irq_disable();

   cpw16_f(IntrMask, cp_intr_mask);

   __netif_rx_complete(dev);

   local_irq_enable();  

   return 0; /* done */

   }  

   return 1; /* not done */

  }  

  其他的使用 NAPI 的驱动程序和 8139CP 大同小异,只是使用了网络层专门提供的 POLL 方法--proecess_backlog(/net/dev.c),在 NIC 中断接收到了数据包后,调用网络层上的 netif_rx(/net/dev.c)将硬件中断中接收到数据帧存入 sk_buff 结构, 然后检查硬件帧头,识别帧类型, 放入接收队列(softnet_data 结构中的 input_pkt_queue 队列上), 激活接收软中断作进一步处理. 软中断函数(net_rx_action)提取接收包,而 process_backlog(也就是 POLL 方法)向上层提交数据。