以文本方式查看主題 - 曙海教育集團論壇 (http://www.hufushizhe.com/bbs/index.asp) -- Linux驅動開發 (http://www.hufushizhe.com/bbs/list.asp?boardid=33) ---- NAPI技術在Linux網絡驅動上的應用 (http://www.hufushizhe.com/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 方法)向上層提交數據。 |