歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核分析 - 網絡[二]:網卡驅動接收報文

糾結了好多天,終於弄懂了B440X的處理。

上篇講到通過中斷,最終網卡調用了b44_rx()來接收報文

對這個函 數中的一些參數,可以這樣理解:

bp->rx_cons – 處理器處理到的緩沖區號   
       
bp->rx_pending – 分配的緩沖區個數   
       
bp->rx_prod – 當前緩沖區的最後一個緩沖號

這裡要參數B440X的手冊了解下寄存器的作用:

#define 

B44_DMARX_ADDR        0x0214UL /* DMA RX Descriptor Ring Address */   
       
#define B44_DMARX_PTR  0x0218UL /* DMA RX Last Posted Descriptor */   
       
#define B44_DMARX_STAT 0x021CUL /* DMA RX Current Active Desc. + Status */

僅b44_rx()來說, B44_DMARX_ADDR儲存了環形緩沖的基地址,B44_DMARX_PTR存儲了環形緩沖最後一個緩沖區號,這兩個寄存器都由處理來設置; B44_DMARX_STAT儲存了狀態及網卡當前處理到的緩沖區號,這個寄存器只能由網卡來設置。

網卡中DMA也很重要:

在網卡初始化階段,b44_open() -> b44_alloc_consistent()

bp->rx_buffers = kzalloc(size, gfp); 

 // size = B44_RX_RING_SIZE * sizeof(struct ring_info)   
       
bp->rx_ring = ssb_dma_alloc_consistent(bp->sdev, size, &bp->rx_ring_dma, gfp);   
       
     // size = DMA_TABLE_BYTES

rx_ring是DMA映射的虛擬地址,rx_rind_dma是DMA映射的總線地址,這個地址將會 寫入B44_DMARX_ADDR寄存器,作為環形緩沖的基地址。

bw32(bp, B44_DMARX_ADDR, bp->rx_ring_dma + bp- >dma_offset);

稍後在rx_init_rings() -> b44_alloc_rx_skb()

mapping = ssb_dma_map_single(bp- >sdev, skb->data,RX_PKT_BUF_SZ,DMA_FROM_DEVICE);

將rx_buffers進行DMA映射,並將映射地址存儲在rx_ring 中

dp->addr = cpu_to_le32((u32) mapping + bp->dma_offset); // dp是rx_ring中一個

DMA的大致流程 :

不准確,但可以參考下大致意思

網卡讀取B44_DMARX_ADDR與B44_DMARX_STAT寄存器,得到下一個處理的struct dma_desc ,然後根據dma_desc中的addr找到報文緩沖區,通過DMA處理器將網卡收到報文拷貝到addr地址處,這個過程CPU是不參與的。

prod – 網卡[硬件]處理到的緩沖區號

prod  = br32(bp, B44_DMARX_STAT) & DMARX_STAT_CDMASK;   

prod /= sizeof(struct dma_desc);   

cons = bp->rx_cons;

根據上面分析,prod讀取B44_DMARX_STAT寄存器,存儲網卡當前處理到的緩沖區號;cons存 儲處理器處理到的緩沖區號。

while (cons != prod && budget > 0) {

處理報文當前時刻網卡接收 到的所有報文,每處理一個報文cons都會加1,由於是環形緩沖,因此這裡用相等,而不是大小比較。

struct 

ring_info *rp = &bp->rx_buffers[cons];   

struct sk_buff *skb = rp->skb;   

dma_addr_t map = rp->mapping;

skb和map保存了當關地址,下面在交換緩沖區後會用到。

ssb_dma_sync_single_for_cpu(bp->sdev, map,RX_PKT_BUF_SZ,DMA_FROM_DEVICE);

CPU取得rx_buffer[cons] 的控制權,此時網卡不能再處理該緩沖區。

rh = (struct rx_header *) skb->data;   

len = le16_to_cpu(rh->len);   

….   

len -= 4;

CPU取得控制權後,取得報文頭,再從報文頭取出報文長度len,len-=4表示忽略了最後4節字的CRC,從這裡 可以看出,B440X網卡驅動不會檢查CRC校驗。而每個報文數據最前面添加了網卡的頭部信息struct rx_header,這裡是28字節。

struct sk_buff *copy_skb;   

b44_recycle_rx(bp, cons, bp->rx_prod);   

copy_skb = netdev_alloc_skb(bp->dev, len + 2);

copy_skb作為傳送報文的中間量,在第三句為其分配了len + 2的空間(為了IP頭對齊,稍後提到)。b44_recycle_rx()函數很關鍵,它作了如下工作:

1. 將緩 沖區號cons賦值給緩沖區號rx_prod;

2. rx_buffers[cons].skb = NULL

3. 將緩沖區號rx_prod控制權給網卡

簡單來說,就是將cons號緩沖區交由CPU處理,而用rx_prod號緩沖區代 替其給網卡使用。

 

a.b44_recycle_rx前                          b. b44_recycle_rx後

以起始狀態為例,緩沖區rx_ring分配了512個,但 rx_buffers僅分配了200個,此時cons = 0,rx_prod = 200。執行b44_recycle_rx()後,網卡處理緩沖區變為1~200,而0號緩沖 區交由CPU處理,將報文拷貝,並向上送至協議棧。注意rx_ring和rx_buffer是不同的。

這樣做的好處在於,不用等待 CPU處理完0號緩沖區,網卡的緩沖區數保持200,而不會減少,這也是rx_pending = 200的原因所在。

skb_reserve

(copy_skb, 2);   

skb_put(copy_skb, len);

關於skb的操作自己去了解,這裡skb_reserve()在報文頭部保留了兩個字節,我們知道鏈路 層報頭是14字節,正常IP報文會從14字節開始,這樣就不是4字節對齊了,所以在頭部保留2字節,使IP報文從16字節開始。

skb_copy_from_linear_data_offset(skb, RX_PKT_OFFSET,copy_skb->data, len);   

skb = copy_skb;

CPU將報文從skb拷貝到copy_skb中,跳過了網卡報頭的額外信息,因為這部分信息在上層協議站是沒 用的,所以去掉。在函數開始時說過skb是保存了cons號的地址,因為在b44_recycle_rx()後cons號不再引用skb指向的空間,而 僅由skb引用,這樣便可以向上層傳送,而不用額外復制。

netif_receive_skb(skb);   

received++;   

budget--;   

next_pkt:   

     bp->rx_prod = (bp->rx_prod + 1) & (B44_RX_RING_SIZE - 1);   

     cons = (cons + 1) & (B44_RX_RING_SIZE - 1);

netif_receive_skb()將報文交由上層協議棧處理,這是下 一節的內容,然後CPU處理下一個報文,rx_prod和cons各加1,它們代表的含義開頭有說明。

如此循環,直到cons == prod,此時網卡收到的報文都已被CPU處理,更新變量:

bp->rx_cons = cons;   

bw32(bp, B44_DMARX_PTR, cons * sizeof(struct dma_desc));
Copyright © Linux教程網 All Rights Reserved