网卡驱动架构以及源码分析

📅 发布时间:2026/7/11 16:41:26 👁️ 浏览次数:
网卡驱动架构以及源码分析
由于工作很忙一直没有时间静下心来好好整理文档。文档的功夫不仅反映了一个人对相关知识的掌握也可以发现不足之处从而提高自己增加自己的内功。希望从这篇开始日积月累成为一个优秀的程序员。本文的stmmac驱动以linux内核5.15为参考。stmmac驱动用于支持synopsys ip的系列网卡芯片包括xgmac,以及gmac。xgmac对应的是10G网卡芯片gmac对应的则是千兆网卡芯片。我将从三个方面进行网卡驱动的分析分别是网卡驱动架构link方式以及收发包流程。1.网卡驱动架构xgmac 网卡主要有两种应用场景分别是Local xgmac和PCIe xgmac 应用场景。Local xgmac方式下cpu通过内部高速总线访问xgmac,而PCIe xgmac则通过PCIe总线与host主机相连。stmmac目录中dwmac-intel-plat对应着Local xgmac方式而dwmac-intel.cstmmac_pci.c则对应这PCIe xgmac的方式。stmmac_main.c包括对xgamc硬件进行操作调用dwxgmac_core.c,dwxgamc_desc.c,dwxgmac_dma.c以及提供各种网卡接口feature包括mmc软硬件计数ethtoolptp以及xdp等。本文主要以PCIe方式即以stmmac_pci.c进行说明。static struct pci_driver stmmac_pci_driver { .name STMMAC_RESOURCE_NAME, .id_table stmmac_id_table, .probe stmmac_pci_probe, .remove stmmac_pci_remove, .driver { .pm stmmac_pm_ops, }, };利用stmmac_id_table的device_id以及vendor_id进行driver和devcie的匹配匹配成功后调用stmmac_pci_probe函数。stmmac_pci_probe函数流程为1.分配plat、plat-mdio_bus_data、plat-dma_cfg结构体plat结构体提供硬件以及ndev的一些基本参数mdio_bus_data主要与mdio相关dma_cfg配置xgmac dma 通路比如rxpbltxpbl等等。plat devm_kzalloc(pdev-dev, sizeof(*plat), GFP_KERNEL) plat-mdio_bus_data devm_kzalloc(pdev-dev,sizeof(*plat-mdio_bus_data), GFP_KERNEL); plat-dma_cfg devm_kzalloc(pdev-dev, sizeof(*plat-dma_cfg),GFP_KERNEL) plat-safety_feat_cfg devm_kzalloc(pdev-dev,sizeof(*plat-safety_feat_cfg),GFP_KERNEL)2.由于PCIe xgmac下网卡是一个PCIe设备既然是PCIe设备则必须对网卡的bar空间进行初始化。包括使能pci设备获取网卡设备的bar空间机制并对bar空间进行映射。pcim_enable_device(pdev) for(i0; i6; i) ret pcim_iomap_regions(pdev, BIT(i), pci_name(pdev)); pci_set_master(pdev);3.赋值一些必要信息中断初始化(msi或者msix)处理完毕后进入stmmac_dvr_probe函数。4.进入stmmac_dvr_probe函数后就是申请网卡设备net_device和私有数据stmmac_priv,网卡设备和私有数据紧紧挨在一起网卡设备私有数据结构通过netdev_pri获取私有数据结构。5.dwxgmac2_core.cdwxgmac2_dma.c,dwxgmac2_desc.c实现了xgmac 的操作接口那么如何将xgmac的操作接口与netdev或者priv强绑定呢stmmac的做法是根据网卡芯片的型号(xgmac以及gmac芯片的型号都不一样)去绑定不同的操作接口的回调函数。hwif.c中的stmmac_hw数组来进行注册管理。// dwxgmac210_ops mac相关操作接口注册如下 const struct stmmac_ops dwxgmac210_ops { .core_init dwxgmac2_core_init, .set_mac dwxgmac2_set_mac, .rx_ipc dwxgmac2_rx_ipc, .rx_queue_enable dwxgmac2_rx_queue_enable, .rx_queue_prio dwxgmac2_rx_queue_prio, .tx_queue_prio dwxgmac2_tx_queue_prio, ...... } // dwxgmac210_ops dma 相关操作接口注册如下 const struct stmmac_dma_ops dwxgmac210_dma_ops { .start_tx dwxgmac2_dma_start_tx, .stop_tx dwxgmac2_dma_stop_tx, .start_rx dwxgmac2_dma_start_rx, .stop_rx dwxgmac2_dma_stop_rx, ...... } const struct stmmac_desc_ops ndesc_ops { .tx_status ndesc_get_tx_status, .rx_status ndesc_get_rx_status, .get_tx_len ndesc_get_tx_len, .init_rx_desc ndesc_init_rx_desc, .init_tx_desc ndesc_init_tx_desc, .get_tx_owner ndesc_get_tx_owner, ...... } static const struct stmmac_hwif_entry { bool gmac; bool gmac4; bool xgmac; ...... const void *desc; const void *dma; const void *mac; const void *hwtimestamp; const void *mode; const void *tc; const void *mmc; int (*setup)(struct stmmac_priv *priv); int (*quirks)(struct stmmac_priv *priv); } stmmac_hw[] { { .gmac false, .gmac4 false, .xgmac true, ...... .desc dwxgmac210_desc_ops, .dma dwxgmac210_dma_ops, .mac dwxgmac210_ops, .hwtimestamp stmmac_ptp, .mode NULL, .tc dwmac510_tc_ops, .mmc dwxgmac_mmc_ops, .setup dwxgmac2_setup, .quirks NULL, }, ..... // .gmac true, }6.stmmac_dvr_probe函数主要作用是申请netdev结构体和priv结构体并对结构体进行赋值包括将各个接口的回调函数赋给priv结构体。而stmmac_open 函数则对应着上层的命令(ifconfig eth up),通过调用stmmac_open函数网卡真正能工作起来即可以开始收发包。stmmac_open |--- stmmac_hw_setup(dev, true) |---stmmac_init_dma_engine(priv) /* DMA initialization and SW reset */ |---stmmac_core_init(priv, priv-hw, dev) /* Initialize the MAC Core */ |---stmmac_mtl_configuration(priv) /* Initialize MTL*/ |---stmmac_mac_set(priv, priv-ioaddr, true) /* Enable the MAC Rx/Tx */ |---stmmac_set_rings_length(priv) /* set TX and RX rings length */ |---stmmac_start_all_dma(priv) /* Start the ball rolling... */ |---stmmac_start_rx_dma(priv, chan) |---stmmac_start_rx(priv, priv-ioaddr, chan) |---stmmac_start_tx_dma(priv, chan) |---stmmac_start_tx(priv, priv-ioaddr, chan) |---stmmac_do_void_callback(__priv, dma, start_tx, __args) //#define stmmac_do_void_callback(__priv, __module, __cname, __arg0, __args...) // (__priv)-hw-__module-__cname((__arg0), ##__args); |--- __priv-hw-dam-start_tx((__arg0), ##__args); |---dwxgmac2_dma_start_tx2.link架构stmmac支持两种形式的phy一种是外置phyphy单独存在通过mdio总线与xgmac相连另一种是内置phy即pcs。xpcs作为xgmac的一部分访问时不再需要通过mdio访问而是将xpcs相关寄存器映射到一段bar空间上直接通过读写寄存器的方式访问。在5.15内核stmmac驱动的link架构依赖于phylink模块。phylink层是一个软件层面的中间层它没有对应的硬件主要功能是连接phy_device和mac层以及phy层的状态。对于外置phy的方式phylink工作方式如下当phy_device的状态改变时mac层能及时作出改变。phy_device层和phylink层均采用了定时器轮询的方式phy_device层通过phy_state_machine()函数获取phy的状态当状态改变时将信息通过phylink_phy_change函数传递给phylink.phylink层也采用了轮询的方式利用phylink_resolve函数将信息传给mac层。对于内置phy即xpcs的方式phylink的工作方式如下phylink层维护了1s钟轮询一次的定时器通过该定时器会定期去查看xpcs的link状态如果xpcs是linkup的则会配置xgmac的mac层使其linkup。phylink_resolve函数的核心逻辑如下static void phylink_resolve(struct work_struct *w) { ... bool mac_config false; bool retrigger false; bool cur_link_state; if (pl-netdev) //得到当前的link状态 cur_link_state netif_carrier_ok(ndev); else cur_link_state pl-old_link_state; if (pl-phylink_disable_state) { //判断当前状态phylink_disable_state和mac_link_dropped状态 pl-mac_link_dropped false; link_state.link false; } else if (pl-mac_link_dropped) { link_state.link false; retrigger true; } else { //其他情况 switch (pl-cur_link_an_mode) { ... case MLO_AN_INBAND: /*获取当前的link_state*/ phylink_mac_pcs_get_state(pl, link_state); if (!link_state.link) { if (cur_link_state) retrigger true; else phylink_mac_pcs_get_state(pl,link_state); } phylink_apply_manual_flow(pl, link_state); break; } } ... /*这里我的理解是如果link_state的link不等于当前状态的link 那么如果link0,实际是link的所以需要link up; 如果link1那么实际是没link所以需要link down.如果两者都为0和1就不需要动作了*/ if (link_state.link ! cur_link_state) { pl-old_link_state link_state.link; if (!link_state.link) phylink_link_down(pl); else phylink_link_up(pl, link_state); } if (!link_state.link retrigger) { //如果link为0并且需要马上retrigger那么就重新调度resolve函数 pl-mac_link_dropped false; queue_work(system_power_efficient_wq, pl-resolve); } mutex_unlock(pl-state_mutex); }对于外置phy其与xgmac通信的总线为mdio。这里主要说明以下mdio总线的注册流程。要用到mdio读写寄存器的方式对phy进行配置一般读写方式有两种分别是c45和c22.对于xpcs的方式主要需要获取xpcs的型号从而执行不同的硬件操作函数。pcs是物理编码子层位于协调子层(通过GMII和物理介入接入层PMA子层之间。pcs子层完成将经过完善定义的以太网MAC功能映射到现存的编码和物理层信号系统的功能上去、pcs子层和上层MAC的接口由MII提供与下层PMA接口使用PMA服务接口。而XPCS顾名思义则是支持更高速率的pcs层。不同厂家的xpcs不大相同以stmmac源码为参考pcs_xpcs的初始化位置位于stmmac_main.c的stmmac_dvr_probe函数下的stmmac_xpcs_setup函数中该函数流程为利用从0到32的phy地址进行循环遍历的方式进行mdio设备以及xpcs的创建如果phy地址不正确那么xpcs则无法正确创建。若xpcs成功创建将xpcs_create函数返回的xpcs结构体给priv-hw-xpcs。for (addr 0; addr PHY_MAX_ADDR; addr) { mdiodev mdio_device_create(bus, addr); if (IS_ERR(mdiodev)) continue; xpcs xpcs_create(mdiodev, mode); if (IS_ERR_OR_NULL(xpcs)) { mdio_device_free(mdiodev); continue; } priv-hw-xpcs xpcs; break;}与前面xgmac的硬件操作函数一致xpcs也需要根据不同的型号来执行不同操作函数。//关键结构体数组 xpc_id_list[] { { .id SYNOPSYS_XPCS_ID, .mask SYNOPSYS_XPCS_MASK, .compat synopsys_xpcs_compat, }, { .id NXP_SJA1105_XPCS_ID, .mask SYNOPSYS_XPCS_MASK, .compat nxp_sja1105_xpcs_compat, }, { .id NXP_SJA1110_XPCS_ID, .mask SYNOPSYS_XPCS_MASK, .compat nxp_sja1110_xpcs_compat, }, }; //赋值后的xpcs结构体 struct dw_xpcs { struct mdio_device *mdiodev; const struct xpcs_id *id; struct phylink_pcs pcs; }xpcs; xpcs.mdiodevmdiodev; xpcs-idxpcs_id_list[i]; //根据id匹配得到 xpcs-pcs.ops xpcs_phylink_ops; xpcs-pcs.poll true;3.收发包流程Stmmac 以太网收发包驱动核心是两个函数一个是 stmmac_xmit, 用于将协议栈发送的数据包映射出DMA地址给硬读取 另一个是 stmmac_rx用于将硬件写入内存的数据组织成 skb 并传递给协议层。TX方向的流程如上图所示网络设备层_qdisc_run 函数调用 驱动注册的 stmmac_xmit 函数进行发送数据包Stmmac_xmit 对skb-data进行dma_map_single流式映射获取dma物理地址供网卡芯片 DMA 获取数据报文stammac_xmit 将 skb映射后的dma 物理地址 更新到 TX Ring 的描述符中然后更新描述符队列的 cur_txstammac_xmit 将当前生产的数据包位置 写入 XGMAC 的doorbell寄存器同时flush 描述符并开启软件定时器进行tx方向的中断聚合硬件DMA 读取数据报文传递给 MAC 层之后根据描述符的IC值判断是否发送硬中断给CPUCPU 执行硬中断处理函数 stmmac_msi_intr_tx硬中断处理函数 调用 napi_schedule(tx_napi)Linux 内核对应当前CPU 核心的软中断线程 Ksoftirqd/N 通过net_rx_action, 调用驱动注册软中断处理函数stmmac_napi_poll_txPoll tx 调用 stmmac_tx_clean 清理 tx 描述符tx skb buffertx dma mpping 等RX方向的流程如上图所示网卡收到数据后产生rx中断并发送给CPUCPU执行硬中断处理函数stmmac_msi_intr_rx硬中断处理函数调用napi_schedule(rx_napi)执行netif_napi_add中绑定的stmmac_napi_poll_rx函数进入驱动层的stmmac_rx;调用dma_sync_single_for_cpu确保在读取rx buffer数据之前dma操作已经完成申请skb将rx buffer的数据copy到skb中并更新描述符 cur_rx。将skb通过napi_gro_receive 上送到协议栈。