網絡報文接收流程所涉及的內容很多,如報文vlan 單播組播等過濾、mac層卸載、報文接收描述符、校驗和卸載以及分離報文有效載荷和頭部等,具體內容需要看 網卡datasheet, 以82599 網卡為例:http://www.intel.com/content/www/us/en/embedded/products/networking/82599-10-gbe-controller-datasheet.html
具體可以查看官網。
這里主要說網卡接收報文,網卡接收報文肯定會涉及到一個東西--- (Legacy/Advanced ) Receive Descriptor Format-報文接收描述符,當網卡收到網絡報文的時候,會往報文接收描述符中指定的地址寫入報文數據,而網卡驅動則會從報文接收描述符中指定的地址讀取報文,並送往上層協議棧處理。
對於82599網卡而言,其支持兩種格式的報文接收描述符,即傳統格式和高級格式--Legacy/Advanced;兩種格式的報文接收描述符所占用的內存大小都是16Bytes;

報文接收描述符的低八個字節存放的是用於存放報文的內存起始地址,而高八個字節存放的是網卡對報文進行預處理得到的一些信息,如報文長度,VLAN Tag以及校驗和信息等

讀格式的報文描述符中主要有四個部分,分別是報文緩沖區地址、A0位、頭緩沖區地址和DD位。對於報文緩沖區地址和頭緩沖區地址,顧名思義,存儲的就是用來存放報文有效載荷和頭部的緩沖區首地址。而對於DD位的作用,網卡驅動可以通過讀取該位的值來判斷該描述符對應的緩沖區中是否已經存放了網卡接收的報文。
報文接收描述符承載了報文從網卡流入到主存的過程,是網卡驅動和網卡都會操作的對象; 兩個action 處理同一個對象,一個寫一個讀,那怎樣處理的呢???


RDBA寄存器。這個寄存器存放了報文接收描述符環形隊列的起始地址,也就是上圖中Base指向的地址。
RDLEN寄存器。這個寄存器存放了報文接收描述符環形隊列的長度,也就是接收描述符環形隊列所占用的字節數,對應上圖中的Size。
RDH寄存器。這個寄存器存放的是一個距離隊列頭部的偏移值,代表的是第一個可以被網卡用來存放報文信息的描述符。當網卡完成了將一個報文信息存放到描述符后,就會更新RDH寄存器的值,使之指向下一個即將用來存放報文信息的描述符。也就是說這個寄存器的值是由網卡來更新的,該寄存器對應上圖中的Head。
RDT寄存器。這個寄存器存放的也是一個距離隊列頭部的偏移值,代表的是硬件可以用來存放報文信息的最后一個描述符的下一個描述符。當網卡驅動填充了報文描述中的報文緩沖區地址后就會更新該寄存器的值,使之指向下一個即將填充地址信息並給網卡使用的描述符,該寄存器對應上圖中的Tail
網卡中接收報文的最小單位是一個隊列,即RX隊列。所以一般來說就是一個RX隊列對應一個報文接收描述符環形隊列
struct ixgbe_q_vector { struct ixgbe_adapter *adapter; int cpu; /* CPU for DCA */ u16 v_idx; /* index of q_vector within array, also used for * finding the bit in EICR and friends that * represents the vector for this ring */ u16 itr; /* Interrupt throttle rate written to EITR */ struct ixgbe_ring_container rx, tx; struct napi_struct napi; #ifndef HAVE_NETDEV_NAPI_LIST struct net_device poll_dev; #endif #ifdef HAVE_IRQ_AFFINITY_HINT cpumask_t affinity_mask; #endif #ifndef IXGBE_NO_LRO struct ixgbe_lro_list lrolist; /* LRO list for queue vector*/ #endif int numa_node; char name[IFNAMSIZ + 9]; /* for dynamic allocation of rings associated with this q_vector 柔性數組成員就是由該中斷向量所管理的隊列,這里包括了RX隊列和TX隊列*/ struct ixgbe_ring ring[0] ____cacheline_internodealigned_in_smp; };
報文接收流程只需要關注其中的RX隊列即可。一般來說一個中斷向量會關聯一個硬件中斷。當網卡往中斷向量中的某個RX隊列的描述符中寫入報文信息時,就會觸發對應的硬件中斷,然后中斷子系統就會調用我們注冊的中斷處理函數來處理這個中斷,在ixgbe驅動中對應的就是ixgbe_intr();在ixgbe網卡驅動的實現中,是以一個叫做struct ixgbe_ring的對象來管理報文描述符環形隊列(不管是接收還是發送),其定義如下:
struct ixgbe_ring { struct ixgbe_ring *next; /* pointer to next ring in q_vector */ struct ixgbe_q_vector *q_vector; /* backpointer to host q_vector */ struct net_device *netdev; /* netdev ring belongs to */ struct device *dev; /* device for DMA mapping */ /* 環形隊列緩沖區中的報文描述符數組 有desc成員的內核虛擬地址進行一致性dma映射得到。 這樣ixgbe驅動可以通過desc來操作描述符環形隊列,而網卡可以通過dma成員來操作描述符環形隊列。*/ void *desc; /* descriptor ring memory */ union {/* 與報文描述符數組一一對應的報文緩沖區對象 */ struct ixgbe_tx_buffer *tx_buffer_info; struct ixgbe_rx_buffer *rx_buffer_info; }; unsigned long state; u8 __iomem *tail;/* 指向RDT寄存器對應的內核虛擬地址 */ /* 報文描述符數組對應的物理地址 有desc成員的內核虛擬地址進行一致性dma映射得到。 這樣ixgbe驅動可以通過desc來操作描述符環形隊列,而網卡可以通過dma成員來操作描述符環形隊列。*/ dma_addr_t dma; /* desc成員對應的物理地址 phys. address of descriptor ring */ unsigned int size; /* length in bytes */ /* 環形隊列緩沖區中的報文描述符個數 */ u16 count; /* amount of descriptors */ /* * 環形隊列緩沖區關聯的rx隊列索引,這個索引是用來在adapter->rx數組索引環形隊列緩沖區的*/ u8 queue_index; /* needed for multiqueue queue management */ u8 reg_idx; /* holds the special value that gets * the hardware register offset * associated with this ring, which is * different for DCB and RSS modes */ // next_to_use是環形隊列緩沖區中將要提供給硬件使用的第一個報文描述符的索引,對應的就是RDT寄存器 //next_to_clean是環形隊列緩沖區中驅動將要處理的第一個報文描述符的索引 u16 next_to_use; u16 next_to_clean; #ifdef HAVE_PTP_1588_CLOCK unsigned long last_rx_timestamp; #endif union { #ifdef CONFIG_IXGBE_DISABLE_PACKET_SPLIT u16 rx_buf_len; #else u16 next_to_alloc; #endif struct { u8 atr_sample_rate; u8 atr_count; }; }; u8 dcb_tc; struct ixgbe_queue_stats stats; ------------------------------------------ } ____cacheline_internodealigned_in_smp;
dma_addr_t-------dma成員就是desc成員對應的物理地址,有desc成員的內核虛擬地址進行一致性dma映射得到-------->ixgbe驅動可以通過desc來操作描述符環形隊列,而網卡可以通過dma成員來操作描述符環形隊列
int ixgbe_setup_rx_resources(struct ixgbe_ring *rx_ring) { struct device *dev = rx_ring->dev; int orig_node = dev_to_node(dev); int numa_node = -1; int size; /* 環形隊列中報文接收描述符個數申請報文描述符數組所需要的內存,以及對應的用來管理報文緩沖區地址信息的緩沖區對象, 這個時候緩沖區對象中用來存放報文內容的地址仍然是無效的,因為還沒有申請內存,在函數ixgbe_alloc_rx_buffers()處理完成之后, 緩沖區對象中存放報文內容的地址就是有效的,可以提供給網卡用來存放報文數據。此外,對報文接收描述符數組內存進行一致性dma映射, 獲取對應的物理地址,網卡需要使用物理地址,而不是虛擬地址 */ size = sizeof(struct ixgbe_rx_buffer) * rx_ring->count; if (rx_ring->q_vector) numa_node = rx_ring->q_vector->numa_node; rx_ring->rx_buffer_info = vzalloc_node(size, numa_node); if (!rx_ring->rx_buffer_info) rx_ring->rx_buffer_info = vzalloc(size); if (!rx_ring->rx_buffer_info) goto err; /* Round up to nearest 4K */ rx_ring->size = rx_ring->count * sizeof(union ixgbe_adv_rx_desc); rx_ring->size = ALIGN(rx_ring->size, 4096); set_dev_node(dev, numa_node); rx_ring->desc = dma_alloc_coherent(dev, rx_ring->size, &rx_ring->dma, GFP_KERNEL); set_dev_node(dev, orig_node); if (!rx_ring->desc) rx_ring->desc = dma_alloc_coherent(dev, rx_ring->size, &rx_ring->dma, GFP_KERNEL); if (!rx_ring->desc) goto err; //經過ixgbe_setup_rx_resources()函數的處理,就已經成功創建了一個描述符環形的管理對像 return 0; err: vfree(rx_ring->rx_buffer_info); rx_ring->rx_buffer_info = NULL; dev_err(dev, "Unable to allocate memory for the Rx descriptor ring\n"); return -ENOMEM; }
- dma_alloc_coherent() -- 獲取物理頁,並將該物理頁的總線地址保存於dma_handle,返回該物理頁的虛擬地址DMA映射建立了一個新的結構類型---------dma_addr_t來表示總線地址。dma_addr_t類型的變量對驅動程序是不透明的;唯一允許的操作是將它們傳遞給DMA支持例程以及設備本身。作為一個總線地址,如果CPU直接使用了dma_addr_t,將會導致發生不可預期的后果!一致性DMA映射存在與驅動程序的生命周期中,它的緩沖區必須可同時被CPU和外圍設備訪問
注意ixgbe_adv_rx_desc的結構體如下:
/* Receive Descriptor - Advanced */ union ixgbe_adv_rx_desc { struct { __le64 pkt_addr; /* Packet buffer address */ __le64 hdr_addr; /* Header buffer address */ } read; struct { struct { union { __le32 data; struct { __le16 pkt_info; /* RSS, Pkt type */ __le16 hdr_info; /* Splithdr, hdrlen */ } hs_rss; } lo_dword; union { __le32 rss; /* RSS Hash */ struct { __le16 ip_id; /* IP id */ __le16 csum; /* Packet Checksum */ } csum_ip; } hi_dword; } lower; struct { __le32 status_error; /* ext status/error */ __le16 length; /* Packet length */ __le16 vlan; /* VLAN tag */ } upper; } wb; /* writeback */ };
- ixgbe_setup_rx_resources()函數已經成功創建了一個描述符環形的管理對象。接下來就需要告訴網卡這個描述符環形隊列的信息,這個就是函數ixgbe_configure_rx_ring()所要做的事情
void ixgbe_configure_rx_ring(struct ixgbe_adapter *adapter, struct ixgbe_ring *ring) { struct ixgbe_hw *hw = &adapter->hw; u64 rdba = ring->dma;/* 環形隊列緩沖區中報文描述符數組對應的物理地址 */ u32 rxdctl; u8 reg_idx = ring->reg_idx; /* disable queue to avoid issues while updating state/ * 將報文描述符數組的首地址寫入到RDBAH和RDBAL寄存器中,並將描述符數組的長度 * 寫入到RDLEN寄存器中,這樣網卡芯片就知道了報文描述符的信息,后續可以收到 * 合適的網絡報文后,就會將報文存放到描述符里面的dma地址中,並遞增內部的 * head寄存器值 */ rxdctl = IXGBE_READ_REG(hw, IXGBE_RXDCTL(reg_idx)); ixgbe_disable_rx_queue(adapter, ring); IXGBE_WRITE_REG(hw, IXGBE_RDBAL(reg_idx), rdba & DMA_BIT_MASK(32)); IXGBE_WRITE_REG(hw, IXGBE_RDBAH(reg_idx), rdba >> 32); IXGBE_WRITE_REG(hw, IXGBE_RDLEN(reg_idx), ring->count * sizeof(union ixgbe_adv_rx_desc)); /* reset head and tail pointers * 初始狀態下,網卡芯片的head和tail指針都為0,表示網卡沒有可用的報文描述符 * 等后面驅動申請了n個報文描述符中的dma地址后,就會將tail寄存器值設置為n, * 表示目前網卡可用的報文描述符數量為n個。這樣,等網卡收到了合適的報文之后 * 就會存到報文描述符中的dma地址處。*/ IXGBE_WRITE_REG(hw, IXGBE_RDH(reg_idx), 0); IXGBE_WRITE_REG(hw, IXGBE_RDT(reg_idx), 0); ring->tail = hw->hw_addr + IXGBE_RDT(reg_idx); /* reset ntu and ntc to place SW in sync with hardwdare */ ring->next_to_clean = 0; ring->next_to_use = 0; #ifndef CONFIG_IXGBE_DISABLE_PACKET_SPLIT ring->next_to_alloc = 0; #endif ixgbe_configure_srrctl(adapter, ring); /* In ESX, RSCCTL configuration is done by on demand */ ixgbe_configure_rscctl(adapter, ring); switch (hw->mac.type) { case ixgbe_mac_82598EB: /* * enable cache line friendly hardware writes: * PTHRESH=32 descriptors (half the internal cache), * this also removes ugly rx_no_buffer_count increment * HTHRESH=4 descriptors (to minimize latency on fetch) * WTHRESH=8 burst writeback up to two cache lines */ rxdctl &= ~0x3FFFFF; rxdctl |= 0x080420; break; case ixgbe_mac_X540: rxdctl &= ~(IXGBE_RXDCTL_RLPMLMASK | IXGBE_RXDCTL_RLPML_EN); #ifdef CONFIG_IXGBE_DISABLE_PACKET_SPLIT /* If operating in IOV mode set RLPML for X540 */ if (!(adapter->flags & IXGBE_FLAG_SRIOV_ENABLED)) break; rxdctl |= ring->rx_buf_len | IXGBE_RXDCTL_RLPML_EN; #endif /* CONFIG_IXGBE_DISABLE_PACKET_SPLIT */ break; default: break; } /* enable receive descriptor ring */ rxdctl |= IXGBE_RXDCTL_ENABLE; IXGBE_WRITE_REG(hw, IXGBE_RXDCTL(reg_idx), rxdctl); ixgbe_rx_desc_queue_enable(adapter, ring); /* 申請報文描述符中用於存儲報文數據的內存 */ ixgbe_alloc_rx_buffers(ring, ixgbe_desc_unused(ring)); }
網卡驅動就是通過將接收報文描述符數組對應的物理地址寫入到RDBA寄存器,並初始化RDH和RDT寄存器。通過寫RDBA、RDH和RDT寄存器,網卡就知道了當前的描述符環形隊列的信息。接着調用函數ixgbe_alloc_rx_buffers()申請用來存放報文數據的內存,並將對應的物理地址保存到接收描述符中,然后設置RDT寄存器,這樣網卡就可以使用RDH和RDT之間的描述符進行接收報文處理了
struct ixgbe_rx_buffer { struct sk_buff *skb; dma_addr_t dma; #ifndef CONFIG_IXGBE_DISABLE_PACKET_SPLIT struct page *page; unsigned int page_offset; #endif };
static bool ixgbe_alloc_mapped_skb(struct ixgbe_ring *rx_ring, struct ixgbe_rx_buffer *bi) { struct sk_buff *skb = bi->skb; dma_addr_t dma = bi->dma; if (unlikely(dma)) return true; if (likely(!skb)) { skb = netdev_alloc_skb_ip_align(netdev_ring(rx_ring), rx_ring->rx_buf_len); if (unlikely(!skb)) { rx_ring->rx_stats.alloc_rx_buff_failed++; return false; } bi->skb = skb; }pci_alloc_ dma = dma_map_single(rx_ring->dev, skb->data, rx_ring->rx_buf_len, DMA_FROM_DEVICE); /* * if mapping failed free memory back to system since * there isn't much point in holding memory we can't use */ if (dma_mapping_error(rx_ring->dev, dma)) { dev_kfree_skb_any(skb); bi->skb = NULL; rx_ring->rx_stats.alloc_rx_buff_failed++; return false; } bi->dma = dma; return true; } void ixgbe_alloc_rx_buffers(struct ixgbe_ring *rx_ring, u16 cleaned_count) { union ixgbe_adv_rx_desc *rx_desc; struct ixgbe_rx_buffer *bi; u16 i = rx_ring->next_to_use; /* nothing to do */ if (!cleaned_count) return; /* * 獲取下一個將要提供給硬件使用的報文描述符(對應的索引為rx_ring->next_to_use), * 以及報文描述符對應的緩沖區對象,緩沖區對象中保存了用於存放報文數據的內存地址信息, * 當然用於存放報文的內存對應的物理地址也會保存到報文描述符中。 #define IXGBE_RX_DESC(R, i) \ (&(((union ixgbe_adv_rx_desc *)((R)->desc))[i])) */ rx_desc = IXGBE_RX_DESC(rx_ring, i); bi = &rx_ring->rx_buffer_info[i]; /*報文描述符隊列在邏輯上是環形的(實際上是線性的,因為內存地址是線性分布的),當我們操作這個隊列到達末尾的時候, 通過將索引重新指向隊列開頭來實現環形操作。所以呢,在計算之后, i表示的就是 目前位置距離隊列末尾之間還沒有提供給硬件使用的報文描述符個數的相反數,也就是 * 當前處理位置和隊列末尾距離。在下面的循環中,每處理一個報文描述符(申請用於存放報文數據的內存)都會將i遞增, * 當i等於0的時候,說明達到了隊列的末尾,下次處理就要從隊列頭開始了,從而實現 * 隊列的環形操作。 */ i -= rx_ring->count; do { /* 申請用於存放報文數據的內存,並進行dma流式映射*/ #ifdef CONFIG_IXGBE_DISABLE_PACKET_SPLIT if (!ixgbe_alloc_mapped_skb(rx_ring, bi)) #else if (!ixgbe_alloc_mapped_page(rx_ring, bi)) #endif break; /* * Refresh the desc even if buffer_addrs didn't change * because each write-back erases this info. */ #ifdef CONFIG_IXGBE_DISABLE_PACKET_SPLIT /* rx_desc->read.pkt_addr存放的地址就是用於存放報文的dma起始地址 */ rx_desc->read.pkt_addr = cpu_to_le64(bi->dma); #else rx_desc->read.pkt_addr = cpu_to_le64(bi->dma + bi->page_offset); #endif /* rx_desc和bi遞增,指向下一個描述符和對應的緩沖區對象 */ rx_desc++; bi++; i++; /* 如果i == 0,說明操作環形隊列緩沖區已經轉了一圈了,這個時候就需要重新讓 * rx_desc和bi分別指向描述符數組和緩沖區數組的起始位置,從頭開始處理,當然 * 對應的i值也就要重新計算了,此時的值為隊列中描述符個數的相反數。*/ if (unlikely(!i)) { rx_desc = IXGBE_RX_DESC(rx_ring, 0); bi = rx_ring->rx_buffer_info; i -= rx_ring->count; } /* clear the hdr_addr for the next_to_use descriptor */ rx_desc->read.hdr_addr = 0; cleaned_count--; } while (cleaned_count); /* i加上rx_ring->count之后指向的就是最后一個可用(對網卡芯片來說)的報文描述符的 * 下一個位置,,這個時候需要將這個索引值i寫入到網卡芯片的tail寄存器中,讓網卡 * 芯片知道目前可用的報文描述數量(tail - head)*/ i += rx_ring->count; if (rx_ring->next_to_use != i) ixgbe_release_rx_desc(rx_ring, i); } static inline void ixgbe_release_rx_desc(struct ixgbe_ring *rx_ring, u32 val) {/* * 因為i指向的是最后一個可用報文描述符的下一個位置,這個位置也是下一次要 * 提供給網卡芯片使用的報文描述符的位置 */ rx_ring->next_to_use = val; #ifndef CONFIG_IXGBE_DISABLE_PACKET_SPLIT /* update next to alloc since we have filled the ring */ rx_ring->next_to_alloc = val; #endif /* * Force memory writes to complete before letting h/w * know there are new descriptors to fetch. (Only * applicable for weak-ordered memory model archs, * such as IA-64). */ wmb(); /* 將val 值寫入到tail寄存器中 */ writel(val, rx_ring->tail); }
對於 ixgbe驅動中NAPI接口的數據包接收流程如下:就不說了,到處都是

至於發包流程:可以看我之前的文章 不說了;或者看Intel的芯片手冊:http://www.intel.com/content/www/us/en/embedded/products/networking/82599-10-gbe-controller-datasheet.html

參考:https://tqr.ink/2017/05/01/intel-82599-transmit-packet/
這里面的緩存關系為:
ixgbe_ring
{
struct ixgbe_rx_buffer *rx_buffer_info;
void *desc; /* descriptor ring memory */----------------------------指向的是環形隊列描述符的內存地址
dma_addr_t dma; /* phys. address of descriptor ring */------------------------ 指向的是環形隊列描述符的內存地址
}
ixgbe_ring 中的desc 以及 dma 指向的是 環形緩沖區的描述符地址!!!
那么存儲報文的地址是???
ixgbe_ring rx_ring[MAX]
rx_desc = IXGBE_RX_DESC(rx_ring, i);
bi = &rx_ring->rx_buffer_info[i];
數據緩沖區:
skb = netdev_alloc_skb_ip_align(netdev_ring(rx_ring),rx_ring->rx_buf_len);
bi->skb = skb;
bi->dma = dma_map_single(rx_ring->dev, skb->data,rx_ring->rx_buf_len, DMA_FROM_DEVICE);
rx_desc->read.pkt_addr = cpu_to_le64(bi->dma + bi->page_offset);
再來看讀取報文的時候:
也就是軟中斷poll讀取的時候
ixgbe_clean_rx_irq----》
rx_desc = IXGBE_RX_DESC(rx_ring, ntc);
rx_buffer = &rx_ring->rx_buffer_info[ntc];
skb = rx_buffer->skb;
napi_gro_receive---------------->進入協議棧
處理完后:
if (cleaned_count)
ixgbe_alloc_rx_buffers(rx_ring, cleaned_count);
-------->繼續分配 數據包緩存 然后設置 ,描述符指針
