Realtek 8126驱动分析第四篇——multi queue相关
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~