Realtek 8126是 5G 網卡,因為和 8125 較為接近,第四篇從這里開始也無不可。本篇主要是講 multi queue 相關,其他的一些內容在之前就已經提過,不加贅述。
1 初始化
1.1?rtl8126_init_one
從第一篇我們可以知道每個 PCI 驅動都注冊了一個 probe() 方法,為設備尋找驅動就是調用其 probe() 方法,即?rtl8126_init_one。這里我們重點講 multi queue 相關的內容。
1.1.1 rtl8126_init_board
/* dev zeroed in alloc_etherdev */
dev = alloc_etherdev_mq(sizeof (*tp), R8126_MAX_QUEUES);
分配并設置以太網設備,這里的?R8126_MAX_QUEUES 指 TX 和 RX 都有這么多個 queue。
1.1.2?rtl8126_try_msi
該函數里主要是先確定了支援的 irq vector number,最關鍵的是?rtl8126_enable_msix 函數。這樣內核就根據硬件能力分配了實際的 vector 即中斷號。
static int rtl8126_enable_msix(struct rtl8126_private *tp)
{int i, nvecs = 0;struct msix_entry msix_ent[R8126_MAX_MSIX_VEC];/** 這里的entry表示設備支持的MSI-X表項索引(即設備硬件層面的中斷條目編號)。* 例如,設備支持16個MSI-X中斷,entry可以是0-15* 而vector表示系統范圍內唯一的中斷向量號(Interrupt Vector Number)。* 該向量號是CPU中斷描述符表(IDT)的索引,用于在硬件觸發中斷時,CPU查找對應的中斷處理函數*/for (i = 0; i < R8126_MAX_MSIX_VEC; i++) {msix_ent[i].entry = i;msix_ent[i].vector = 0;}/* 調用pci_enable_msix_range(),內核根據硬件能力和系統資源分配實際的vector,* 并回填到msix_entry.vector*/nvecs = pci_enable_msix_range(tp->pci_dev, msix_ent,tp->min_irq_nvecs, tp->max_irq_nvecs);if (nvecs < 0)goto out;for (i = 0; i < nvecs; i++) {struct r8126_irq *irq = &tp->irq_tbl[i];irq->vector = msix_ent[i].vector;}out:return nvecs;
}
1.1.3?rtl8126_init_software_variable
這個函數主要是初始化一些變量,諸如 HwSuppNumTxQueues 和?HwSuppNumRxQueues。
這里要注意以下代碼,這里調用內核限制設置了 rss queue 的數量上限。
/* 此例程應設置多隊列設備默認使用的 RSS 隊列數量的上限。*/
u8 rss_queue_num = netif_get_num_default_rss_queues();
tp->num_rx_rings = (tp->HwSuppNumRxQueues > rss_queue_num)?rss_queue_num : tp->HwSuppNumRxQueues;
然后根據之前設置的變量的值,在?rtl8126_setup_mqs_reg 函數中,設置不同的 ring 對應的硬件寄存器的值,這里比較重要的就是如果支援 multi queue,那么每個 queue 對應的 descriptor start address 都要設置。另外對于 RX 還要額外設置 ISR 和 IMR。
在函數?rtl8126_set_ring_size 中,設置每個 ring 的 ring size 即 descriptor number。
最關鍵的就是函數rtl8126_init_rss。
void rtl8126_init_rss(struct rtl8126_private *tp)
{int i;/* 0~HwSuppIndirTblEntries對queue numbers取余,得到hash indirection table*/for (i = 0; i < rtl8126_rss_indir_tbl_entries(tp); i++)tp->rss_indir_tbl[i] = ethtool_rxfh_indir_default(i, tp->num_rx_rings);/* 該函數依賴內核的強隨機數生成器,生成 RSS 哈希密鑰*/netdev_rss_key_fill(tp->rss_key, RTL8126_RSS_KEY_SIZE);
}
1.1.4?rtl8126_init_napi
這個函數主要是為每一個 irq vector 注冊napi的poll方法。在中斷線程被觸發后,內核會執行中斷處理函數 ISR,進而進入軟中斷收包環節。而進入軟中斷,就會調用到這里注冊的 poll 函數。
1.1.5?rtl8126_set_real_num_queue
通過合理設置實際隊列數,可最大化利用多核CPU和硬件加速特性(如RSS),同時避免因配置不當導致的性能瓶頸或穩定性問題。
1.2?rtl8126_open
ndo_open 在 8126 驅動中就是 rtl8126_open,當網絡設備轉換為啟動狀態時,將調用此函數。
1.2.1 alloc desc
這兩個函數初始化DMA描述符環形緩沖區,用于網卡收發包。
1.2.2?rtl8126_init_ring
int
rtl8126_init_ring(struct net_device *dev)
{struct rtl8126_private *tp = netdev_priv(dev);int i;/* 初始化ring的一些變量 */rtl8126_init_ring_indexes(tp);/* 初始化trx desc ring,并為tx ring設置 end of ring */rtl8126_tx_desc_init(tp);rtl8126_rx_desc_init(tp);for (i = 0; i < tp->num_tx_rings; i++) {struct rtl8126_tx_ring *ring = &tp->tx_ring[i];memset(ring->tx_skb, 0x0, sizeof(ring->tx_skb));}for (i = 0; i < tp->num_rx_rings; i++) {struct rtl8126_rx_ring *ring = &tp->rx_ring[i];
#ifdef ENABLE_PAGE_REUSEring->rx_offset = R8126_RX_ALIGN;
#elsememset(ring->Rx_skbuff, 0x0, sizeof(ring->Rx_skbuff));
#endif //ENABLE_PAGE_REUSEif (rtl8126_rx_fill(tp, ring, dev, 0, ring->num_rx_desc, 0) != ring->num_rx_desc)goto err_out;rtl8126_mark_as_last_descriptor(tp, rtl8126_get_rxdesc(tp, ring->RxDescArray, ring->num_rx_desc - 1));}return 0;err_out:rtl8126_rx_clear(tp);return -ENOMEM;
}
1.2.3?rtl8126_alloc_irq
該函數主要是為每個 irq 設置 中斷處理函數 iSR rtl8126_interrupt_msix,并利用 request_irq 注冊中斷。
1.2.4?rtl8126_hw_config
void rtl8126_config_rss(struct rtl8126_private *tp)
{if (!tp->EnableRss) {rtl8126_disable_rss(tp);return;}_rtl8126_config_rss(tp);
}
著重就是講這個函數。
void _rtl8126_config_rss(struct rtl8126_private *tp)
{/* 設置寄存器?RSS_CTRL_8125 的值 */_rtl8126_set_rss_hash_opt(tp);/* 把之前得到的 redirection table 寫進寄存器里 */rtl8126_store_reta(tp);/* 將之前內核生成的 rss key 寫進寄存器里 */rtl8126_store_rss_key(tp);
}
rtl8126_set_rx_q_num 和?rtl8126_set_tx_q_num 要向寄存器里寫入 TRX queue 的數目。
到此為止,關于 multi queue 的所有初始化就已經完成了。
2 收發包
2.1 收包
如果有數據包過來,并且觸發了硬件中斷,那么就會調用?rtl8126_interrupt_msix 函數,首先判斷是否是 link change,這顯然不是,然后就會 disable hw interrupt,隨后要轉入軟中斷。
進而就會調用對應的 poll 函數,這里我們不去區分 mapping 方式,最終 RX 都會調用到函數?rtl8126_rx_interrupt。這里其他的我們不用去管,只要看一個函數?rtl8126_rx_hash。
以?rtl8126_rx_hash_v3 為例,關于 hash 的信息保存在 descriptor 中,提取出 hash 值后,賦給 skb->hash,至此工作完成。
2.2 發包
發包的部分更為簡單,發包函數?rtl8126_start_xmit 會通過?skb_get_queue_mapping 來選擇用哪個 queue,得出?queue_mapping 用作 queue number 即可。這里的 skb->queue_mapping 是上層計算出的結果。
到此為止,主要的 multi queue 相關的就已經完成了,但是在 linux driver 中,還有通過 ethtool 來修改 rss 相關的 code 需要介紹。
3 ethtool
.get_rxnfc =>?rtl8126_get_rxnfc 該函數用于返回 rx ring number 以及 返回 rss hash option。
.set_rxnfc >= rtl8126_set_rxnfc 該函數用于設置 rss hash option,這里要注意,UDP RSS 是需要通過這個函數來開啟的,default 不支持。
.get_rxfh_indir_size >= rtl8126_rss_indir_size 該函數用于返回?indirection table 的 size。
.get_rxfh_key_size >= rtl8126_get_rxfh_key_size 該函數返回 rss_key 的 size。
.get_rxfh >= rtl8126_get_rxfh 該函數用于獲得 indirection table 和 rss_key。
.set_rxfh >= rtl8126_set_rxfh 該函數用于將 ethtool 給的 indirection table 和 rss_key 寫入寄存器,采用它們。
如果覺得這篇文章有用的話,可以點贊、評論或者收藏,萬分感謝,goodbye~