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

打印 上一主题 下一主题

主题 880|帖子 880|积分 2640

由于工作很忙,不停没有时间静下心来好好整理文档。文档的功夫不仅反映了一个人对相关知识的掌握,也可以发现不足之处,从而进步本身,增加本身的内功。希望从这篇开始,日积月累,成为一个优秀的步调员。
本文的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.c,stmmac_pci.c则对应这PCIe xgmac的方式。stmmac_main.c包括对xgamc硬件举行操纵,调用dwxgmac_core.c,dwxgamc_desc.c,dwxgmac_dma.c,以及提供各种网卡接口feature,包括mmc软硬件计数,ethtool,ptp,以及xdp等。

本文重要以PCIe方式即以stmmac_pci.c举行说明。
  1. static struct pci_driver stmmac_pci_driver = {
  2.         .name = STMMAC_RESOURCE_NAME,
  3.         .id_table = stmmac_id_table,
  4.         .probe = stmmac_pci_probe,
  5.         .remove = stmmac_pci_remove,
  6.         .driver         = {
  7.                 .pm     = &stmmac_pm_ops,
  8.         },
  9. };   
复制代码
利用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 通路,好比rxpbl,txpbl等等。
  1. plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
  2. plat->mdio_bus_data = devm_kzalloc(&pdev->dev,sizeof(*plat->mdio_bus_data), GFP_KERNEL);
  3. plat->dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*plat->dma_cfg),GFP_KERNEL);
  4. plat->safety_feat_cfg = devm_kzalloc(&pdev->dev,sizeof(*plat->safety_feat_cfg),GFP_KERNEL);
复制代码
2.由于PCIe xgmac下,网卡是一个PCIe设备,既然是PCIe设备,则必须对网卡的bar空间举行初始化。包括使能pci设备,获取网卡设备的bar空间机制,并对bar空间举行映射。
  1. pcim_enable_device(pdev);
  2. for(i=0; i<6; i++)
  3.     ret = pcim_iomap_regions(pdev, BIT(i), pci_name(pdev));
  4. pci_set_master(pdev);
复制代码
3.赋值一些必要信息,中断初始化(msi或者msix),处理完毕后进入stmmac_dvr_probe函数。
4.进入stmmac_dvr_probe函数后,就是申请网卡设备net_device和私有数据stmmac_priv,网卡设备和私有数据牢牢挨在一起:网卡设备+私有数据结构,通过netdev_pri获取私有数据结构。

5.dwxgmac2_core.c,dwxgmac2_dma.c,dwxgmac2_desc.c实现了xgmac 的操纵接口,那么怎样将xgmac的操纵接口与netdev或者priv强绑定呢,stmmac的做法是根据网卡芯片的型号(xgmac以及gmac芯片的型号都不一样),去绑定差别的操纵接口的回调函数。hwif.c中的stmmac_hw数组来举行注册管理。
  1. // dwxgmac210_ops mac相关操作接口注册如下
  2.   const struct stmmac_ops dwxgmac210_ops = {
  3.         .core_init = dwxgmac2_core_init,
  4.         .set_mac = dwxgmac2_set_mac,
  5.         .rx_ipc = dwxgmac2_rx_ipc,
  6.         .rx_queue_enable = dwxgmac2_rx_queue_enable,
  7.         .rx_queue_prio = dwxgmac2_rx_queue_prio,
  8.         .tx_queue_prio = dwxgmac2_tx_queue_prio,
  9.         ......
  10. }
  11. // dwxgmac210_ops dma 相关操作接口注册如下
  12. const struct stmmac_dma_ops dwxgmac210_dma_ops = {
  13.         .start_tx = dwxgmac2_dma_start_tx,
  14.         .stop_tx = dwxgmac2_dma_stop_tx,
  15.         .start_rx = dwxgmac2_dma_start_rx,
  16.         .stop_rx = dwxgmac2_dma_stop_rx,
  17.         
  18.         ......
  19. }
  20. const struct stmmac_desc_ops ndesc_ops = {
  21.         .tx_status = ndesc_get_tx_status,
  22.         .rx_status = ndesc_get_rx_status,
  23.         .get_tx_len = ndesc_get_tx_len,
  24.         .init_rx_desc = ndesc_init_rx_desc,
  25.         .init_tx_desc = ndesc_init_tx_desc,
  26.         .get_tx_owner = ndesc_get_tx_owner,
  27.     ......
  28. }
  29. static const struct stmmac_hwif_entry {
  30.         bool gmac;
  31.         bool gmac4;
  32.         bool xgmac;
  33.         ......
  34.         const void *desc;
  35.         const void *dma;
  36.         const void *mac;
  37.         const void *hwtimestamp;
  38.         const void *mode;
  39.         const void *tc;
  40.         const void *mmc;
  41.         int (*setup)(struct stmmac_priv *priv);
  42.         int (*quirks)(struct stmmac_priv *priv);
  43. } stmmac_hw[] = {
  44.      {
  45.                 .gmac = false,
  46.                 .gmac4 = false,
  47.                 .xgmac = true,
  48.                 ......
  49.                 .desc = &dwxgmac210_desc_ops,
  50.                 .dma = &dwxgmac210_dma_ops,
  51.                 .mac = &dwxgmac210_ops,
  52.                 .hwtimestamp = &stmmac_ptp,
  53.                 .mode = NULL,
  54.                 .tc = &dwmac510_tc_ops,
  55.                 .mmc = &dwxgmac_mmc_ops,
  56.                 .setup = dwxgmac2_setup,
  57.                 .quirks = NULL,
  58.      },
  59.      .....   // .gmac = true,
  60.   }
复制代码
6.stmmac_dvr_probe函数重要作用是申请netdev结构体和priv结构体,并对结构体举行赋值,包括将各个接口的回调函数赋给priv结构体。而stmmac_open 函数则对应着上层的下令(ifconfig eth up),通过调用stmmac_open函数,网卡真正能工作起来,即可以开始收发包。
  1. stmmac_open
  2. |--- stmmac_hw_setup(dev, true)
  3.    |---stmmac_init_dma_engine(priv)  /* DMA initialization and SW reset */
  4.    |---stmmac_core_init(priv, priv->hw, dev)   /* Initialize the MAC Core */
  5.    |---stmmac_mtl_configuration(priv)            /* Initialize MTL*/
  6.    |---stmmac_mac_set(priv, priv->ioaddr, true)   /* Enable the MAC Rx/Tx */
  7.    |---stmmac_set_rings_length(priv)             /* set TX and RX rings length */
  8.    |---stmmac_start_all_dma(priv)                 /* Start the ball rolling... */
  9.         |---stmmac_start_rx_dma(priv, chan)
  10.              |---stmmac_start_rx(priv, priv->ioaddr, chan)
  11.         |---stmmac_start_tx_dma(priv, chan)
  12.              |---stmmac_start_tx(priv, priv->ioaddr, chan)
  13.                   |---stmmac_do_void_callback(__priv, dma, start_tx, __args)
  14.                        //#define stmmac_do_void_callback(__priv, __module, __cname,  __arg0, __args...)
  15.                        // (__priv)->hw->__module->__cname((__arg0), ##__args);
  16.                        |--- __priv->hw->dam->start_tx((__arg0), ##__args);
  17.                            |---dwxgmac2_dma_start_tx
复制代码
2.link架构

stmmac支持两种形式的phy,一种是外置phy,phy单独存在,通过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函数的核心逻辑如下
  1. static void phylink_resolve(struct work_struct *w)
  2. {
  3.         ...
  4.         bool mac_config = false;
  5.         bool retrigger = false;
  6.         bool cur_link_state;
  7.         if (pl->netdev)   //得到当前的link状态
  8.                 cur_link_state = netif_carrier_ok(ndev);
  9.         else
  10.                 cur_link_state = pl->old_link_state;
  11.         if (pl->phylink_disable_state) {  //判断当前状态phylink_disable_state和mac_link_dropped状态
  12.                 pl->mac_link_dropped = false;
  13.                 link_state.link = false;
  14.         } else if (pl->mac_link_dropped) {
  15.                 link_state.link = false;
  16.                 retrigger = true;
  17.         } else {  //其他情况
  18.                 switch (pl->cur_link_an_mode) {
  19.                 ...
  20.                 case MLO_AN_INBAND:
  21.                 /*获取当前的link_state*/
  22.                         phylink_mac_pcs_get_state(pl, &link_state);
  23.                      
  24.                         if (!link_state.link) {
  25.                                 if (cur_link_state)
  26.                                         retrigger = true;
  27.                                 else
  28.                                         phylink_mac_pcs_get_state(pl,&link_state);
  29.                         }
  30.                         phylink_apply_manual_flow(pl, &link_state);
  31.                         break;
  32.                 }
  33.         }
  34.          ...
  35.      /*这里我的理解是如果link_state的link不等于当前状态的link,
  36.      那么如果link=0,实际是link的,所以需要link up;
  37.      如果link=1,那么实际是没link,所以需要link down.如果两者都为0和1就不需要动作了*/
  38.         if (link_state.link != cur_link_state) {
  39.                 pl->old_link_state = link_state.link;
  40.                 if (!link_state.link)
  41.                         phylink_link_down(pl);
  42.                 else
  43.                         phylink_link_up(pl, link_state);
  44.         }
  45.         if (!link_state.link && retrigger) { //如果link为0,并且需要马上retrigger,那么就重新调度resolve函数
  46.                 pl->mac_link_dropped = false;
  47.                 queue_work(system_power_efficient_wq, &pl->resolve);
  48.         }
  49.         mutex_unlock(&pl->state_mutex);
  50. }
复制代码
对于外置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。
  1. for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
  2.                 mdiodev = mdio_device_create(bus, addr);
  3.                 if (IS_ERR(mdiodev))
  4.                         continue;
  5.                 xpcs = xpcs_create(mdiodev, mode);
  6.                 if (IS_ERR_OR_NULL(xpcs)) {
  7.                         mdio_device_free(mdiodev);
  8.                        continue;
  9.                 }
  10.                 priv->hw->xpcs = xpcs;
  11.                 break;}
复制代码
与前面xgmac的硬件操纵函数一致,xpcs也必要根据差别的型号来执行差别操纵函数。
  1. //关键结构体数组
  2. xpc_id_list[] = {
  3.         {
  4.                 .id = SYNOPSYS_XPCS_ID,
  5.                 .mask = SYNOPSYS_XPCS_MASK,
  6.                 .compat = synopsys_xpcs_compat,
  7.         }, {
  8.                 .id = NXP_SJA1105_XPCS_ID,
  9.                 .mask = SYNOPSYS_XPCS_MASK,
  10.                 .compat = nxp_sja1105_xpcs_compat,
  11.         }, {
  12.                 .id = NXP_SJA1110_XPCS_ID,
  13.                 .mask = SYNOPSYS_XPCS_MASK,
  14.                 .compat = nxp_sja1110_xpcs_compat,
  15.         },
  16. };
  17. //赋值后的xpcs结构体
  18. struct dw_xpcs {
  19.         struct mdio_device *mdiodev;
  20.         const struct xpcs_id *id;
  21.         struct phylink_pcs pcs;
  22. }xpcs;
  23. xpcs.mdiodev=mdiodev;
  24. xpcs->id=&xpcs_id_list[i];  //根据id匹配得到
  25. xpcs->pcs.ops =&xpcs_phylink_ops;
  26. 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_tx
  • stammac_xmit 将当前生产的数据包位置 写入 XGMAC 的doorbell寄存器,同时flush 描述符,并开启软件定时器,举行tx方向的中断聚合
  • 硬件DMA 读取数据报文,转达给 MAC 层之后,根据描述符的IC值判定是否发送硬中断给CPU
  • CPU 执行硬中断处理函数 stmmac_msi_intr_tx
  • 硬中断处理函数 调用 napi_schedule(tx_napi)
  • Linux 内查对应当前CPU 核心的软中断线程 Ksoftirqd/N 通过net_rx_action, 调用驱动注册软中断处理函数stmmac_napi_poll_tx
  • Poll tx 调用 stmmac_tx_clean 清算 tx 描述符,tx skb buffer,tx dma mpping 等
RX方向的流程如上图所示:

  • 网卡收到数据后,产生rx中断,并发送给CPU;
  • CPU执行硬中断处理函数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 上送到协议栈。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

羊蹓狼

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表