B站 嵌入式孙老师博主个人介绍博主书籍-京东购买链接*Yocto项目实战教程加博主微信进技术交流群jerrydev从零理解 Linux 网卡驱动以 RK3576 RTL8211F 为例把 MAC、PHY、MDIO、RGMII、stmmac 和设备树一次讲清楚很多人第一次接触以太网驱动时会天然地把“网卡驱动”理解成一个单独的 C 文件写几个寄存器能发包能收包就算完成了。但真上板后很快就会发现以太网驱动根本不是这么回事。你看到的“eth0”“网线插上了”“能不能拿到 IP”“为什么只有 100M”“为什么是 169.254.x.x”“为什么日志里有 Generic PHY / RTL8211F / stmmac / MDIO / RGMII / tx_delay / rx_delay”——这些现象其实来自一整条分层链路不是一个函数决定的。如果把整个系统画成一条链大概是这样应用层 ↓ socket / TCP/IP 协议栈 ↓ Linux net_device ↓ MAC 驱动stmmac / dwmac / SoC glue ↓ MDIO 总线 PHY 驱动realtek.c ↓ RGMII / RMII / SGMII 等 MAC-PHY 接口 ↓ 外部 PHYRTL8211F ↓ 磁性器件 / RJ45 / 网线 ↓ 对端交换机 / 路由器 / 电脑理解这条链之后你再去看驱动、设备树、日志就会一下子变得非常清晰。这篇文章就围绕这个思路展开。一、先把“网卡驱动”拆开它其实不是一个驱动而是三层协作在 Linux 里以太网能工作通常至少涉及三层1. MAC 驱动MAC 是 SoC 里集成的以太网控制器。在 RK3576 这类平台上MAC 不直接等于“整个网卡”它只是数据链路层里靠近 CPU 的那一部分。它主要负责发送和接收以太网帧DMA 描述符管理中断NAPI 轮询网卡的ndo_open、ndo_stop、ndo_start_xmit和 Linux 网络子系统对接你当前这套平台里核心 MAC 驱动不是你完全手写的而是Synopsys DesignWare MACstmmac / dwmac这一套通用框架。也就是说大部分发送、接收、DMA、NAPI 逻辑实际上已经在 stmmac 里写好了。你并不是从零发明一个以太网驱动而是站在成熟内核框架上做 SoC 适配。2. PHY 驱动PHY 是外部芯片比如你这里的RTL8211F。PHY 不负责 IP不负责 socket不负责 DMA。它负责的是更底层的“电气”和“链路协商”检测网线插拔自协商速度和双工10/100/1000M 切换Link Up / Link DownRGMII 内部延时低功耗模式例如 ALDPS时钟输出读取 PHY IDMDIO/MDC 寄存器访问Linux 里这部分通常由PHYLIB框架统一管理而具体厂商的 PHY例如 Realtek就放在drivers/net/phy/realtek.c里。也就是说MAC 驱动关注“怎么收发帧”PHY 驱动关注“物理链路是否建立、速度是多少、时序怎么配”3. 设备树DTS设备树不是驱动代码但在嵌入式平台里它和驱动是同等重要的。为什么因为驱动只认识“逻辑设备”但板子到底怎么接线、PHY 地址多少、RGMII 模式是什么、reset GPIO 在哪、时钟怎么接、延时参数是多少这些都不是驱动源码能自己猜出来的必须由板级描述告诉它。所以设备树的任务是把板级硬件信息说清楚例如phy-mode rgmii-rxid;reg 0x1;snps,reset-gpio ...;tx_delay ...;rx_delay ...;phy-handle rgmii_phy0;如果设备树错了驱动再优秀也没用。二、以你当前平台为例RK3576 RTL8211F 到底是怎么工作的你当前这套板子整体结构可以这样理解RK3576 GMAC0 (MAC) │ ├── MDIO/MDC → RTL8211F 的管理接口 ├── RGMII TX/RX/CLK → RTL8211F 的数据接口 │ RTL8211F (PHY) │ ├── 磁性器件 / RJ45 │ 对端交换机 / 路由器 / PC这里最容易混淆的是两条线1. MDIO/MDC这是一条“管理总线”速度不高。它不是拿来传以太网数据的它是拿来读取 PHY ID读取 link 状态设置 10/100/1000配 RGMII delay配低功耗、时钟输出等驱动里经常出现的phy1reg 0x1PHY_ID_MATCH_EXACT(...)mdiobusphy_read()phy_write()本质都属于这条链。2. RGMIIRGMII 才是 MAC 和 PHY 之间真正传数据的高速接口。这条链上有TXD[3:0]RXD[3:0]TX_CTLRX_CTLTXCRXC如果这条线的时序、引脚、延时配置不对最常见现象不是“完全没设备”而是只能 100M千兆不稳定丢包CRC 错误10/100 能上1000 上不去Link Up 但 DHCP 拿不到地址RX 为 0TX 有值这也是为什么phy-mode、tx_delay、rx_delay、PHY 内部 delay 如此关键。三、Linux 启动后以太网驱动的完整工作流程下面按时间顺序把驱动启动的过程走一遍。第一步内核解析设备树创建平台设备内核启动后会先读取 DTB发现有一个 GMAC 节点比如gmac0 { phy-mode rgmii-rxid; clock_in_out output; snps,reset-gpio gpio2 RK_PB5 GPIO_ACTIVE_LOW; snps,reset-active-low; snps,reset-delays-us 0 20000 100000; phy-handle rgmii_phy0; status okay; };这一步内核还没有“开始发网包”只是知道“这里有个以太网控制器地址在某个寄存器区挂了一个外部 PHY。”第二步MAC 驱动 probe接着平台驱动 probe也就是 stmmac / dwmac 的 Rockchip glue 开始接管这个设备。它会做这些事情映射寄存器申请时钟申请 reset解析phy-mode解析tx_delay/rx_delay配置 pinctrl初始化 DMA注册 net_device创建或连接 MDIO 总线这一层最重要的一点是它并不直接知道板上 PHY 是谁它只负责把“MAC 控制器”先带起来。第三步创建 MDIO 总线识别 PHY然后 MAC 驱动会通过 MDIO 去扫描 PHY。例如设备树里mdio0 { rgmii_phy0: phy1 { reg 0x1; }; };这表示 PHY 地址是 1。驱动会通过 MDIO 读 PHY 的标准寄存器PHYIDR1PHYIDR2从而拿到一个唯一 PHY ID比如 RTL8211F 对应的 ID。如果识别成功内核就会在 PHY 驱动表里查找谁能匹配这个 ID。第四步PHY 驱动匹配在drivers/net/phy/realtek.c里通常会有类似这样的驱动表staticstructphy_driverrealtek_drvs[]{{PHY_ID_MATCH_EXACT(0x001cc916),.nameRTL8211F Gigabit Ethernet,.config_initrtl8211f_config_init,.config_aneggenphy_config_aneg,.read_statusgenphy_read_status,.suspendgenphy_suspend,.resumegenphy_resume,},};这段代码的含义非常重要PHY_ID_MATCH_EXACT(...)告诉内核这个驱动认哪个 PHYconfig_initPHY 初始化时要做什么config_aneg如何做自协商read_status如何读链路状态如果这里匹配不上就可能回退成Generic PHY。这时系统也许还能工作但厂商特有配置就可能丢失比如RGMII delayEEE低功耗clkoutpage 切换寄存器处理这就是为什么“能看到网口”不等于“驱动正确”。第五步PHY 初始化匹配到 RTL8211F 后会进入rtl8211f_config_init()。这一层通常干什么配置 page select配置 RGMII internal delay配置 EEE配置 ALDPS配置时钟输出配置 LED 行为做厂商寄存器的初始化这一步非常像“上电后的板级调参”。如果这里 delay 配错了就可能出现一种典型现象驱动看起来都正常PHY 也识别正确但千兆协商不上最后掉到 100M 甚至 10M所以很多人以为“驱动没问题了”其实只是 probe 通过了时序未必正确。第六步网卡被注册成 eth0前面 MAC 和 PHY 都搞定后系统就会创建eth0。这时候你用ifconfig -a、ip link就能看到它。但注意看得到eth0并不代表网络通。这只是说明net_device 已经注册了驱动对象已经存在了真正的数据路径还要看ndo_open、NAPI、DMA、PHY link 状态。第七步ifconfig up / systemd-networkd / dhcpcd 拉起接口当网络管理器把接口拉起来时会调用驱动的ndo_open。典型动作包括申请中断初始化 DMA ring启动 RX/TX engine启动 NAPIphy_start()或 phylink start允许 MAC 开始收发到这里才真正进入可收发包状态。四、网卡驱动最核心的四个入口函数很多人问从零看网卡驱动应该抓哪些核心函数我建议先盯住下面四个ndo_openndo_stopndo_start_xmitpollNAPI只要把这四个看明白整个数据平面就通了。1. ndo_open网卡启动大致伪代码是这样staticintmyeth_open(structnet_device*ndev){structmy_priv*privnetdev_priv(ndev);init_dma_desc(priv);request_irq(priv-irq,myeth_interrupt,0,ndev-name,ndev);napi_enable(priv-napi);start_rx_dma(priv);start_tx_dma(priv);enable_mac_rx_tx(priv);phy_start(ndev-phydev);netif_start_queue(ndev);return0;}逻辑很直白把 DMA ring 准备好开中断开 NAPI开 MAC 的收发启动 PHY允许上层发送队列启动2. ndo_stop网卡关闭这是反方向动作staticintmyeth_stop(structnet_device*ndev){structmy_priv*privnetdev_priv(ndev);netif_stop_queue(ndev);phy_stop(ndev-phydev);disable_mac_rx_tx(priv);stop_rx_dma(priv);stop_tx_dma(priv);napi_disable(priv-napi);free_irq(priv-irq,ndev);return0;}3. ndo_start_xmit发包主路径这是应用层发包最终到驱动的入口之一。staticnetdev_tx_tmyeth_start_xmit(structsk_buff*skb,structnet_device*ndev){structmy_priv*privnetdev_priv(ndev);dma_addr_tdma;dmadma_map_single(priv-dev,skb-data,skb-len,DMA_TO_DEVICE);fill_tx_desc(priv,dma,skb-len);kick_tx_dma(priv);returnNETDEV_TX_OK;}这段代码真正做的事只有三件把 skb 映射成 DMA 地址填 TX 描述符通知硬件开始发注意网卡驱动不是一个字节一个字节发而是通过 DMA 描述符让硬件自己搬运。4. NAPI poll收包主路径收包一般不是在中断里把所有包都处理完而是中断里只做“通知”真正收包在 poll 里做staticintmyeth_poll(structnapi_struct*napi,intbudget){structmy_priv*privcontainer_of(napi,structmy_priv,napi);intwork_done0;while(work_donebudgetrx_desc_has_packet(priv)){structsk_buff*skbbuild_skb_from_rx_desc(priv);skb-protocoleth_type_trans(skb,priv-ndev);napi_gro_receive(napi,skb);work_done;}if(work_donebudget){napi_complete_done(napi,work_done);enable_rx_irq(priv);}returnwork_done;}这里面最关键的理解是中断只是提醒“来包了”NAPI poll 才负责真正从 RX ring 取包拿到包后交给协议栈这就是 Linux 高性能网卡驱动的基本套路。五、PHY 驱动最核心的逻辑是什么相比 MAC 驱动PHY 驱动通常代码短很多但很容易看不懂。原因是它不是“完整数据通路驱动”它主要是“寄存器配置 状态机驱动”。最常见的 PHY 驱动核心函数有这几个probeconfig_initconfig_anegread_statussuspendresume其中最关键的是config_init和read_status。1. config_init初始化 PHY 的特殊功能以 RTL8211F 为例它可能需要在这里做根据phy-mode配 RGMII delay开/关 ALDPS开/关 EEE开/关 clkout配 LED例如你可以把它理解为这种伪代码staticintrtl8211f_config_init(structphy_device*phydev){switch(phydev-interface){casePHY_INTERFACE_MODE_RGMII:disable_tx_delay();disable_rx_delay();break;casePHY_INTERFACE_MODE_RGMII_ID:enable_tx_delay();enable_rx_delay();break;casePHY_INTERFACE_MODE_RGMII_RXID:disable_tx_delay();enable_rx_delay();break;casePHY_INTERFACE_MODE_RGMII_TXID:enable_tx_delay();disable_rx_delay();break;}disable_eee_if_needed();configure_clkout_if_needed();configure_low_power_if_needed();return0;}这段逻辑特别重要因为它决定的是板子是否稳定跑千兆。2. read_status读取链路状态这个函数一般做Link Up / Link DownSpeed 10 / 100 / 1000Duplex half / fullPause 能力它最后会把状态写回phydevMAC 驱动再根据这个结果调整硬件。六、设备树到底写什么才叫“把板子描述清楚了”很多初学者写网卡 DTS 时容易写成“能编译过去就行”。但真正有价值的 DTS不是能编译而是能完整描述板级事实。一个比较合理的 Ethernet DTS至少包含三部分。1. GMAC 节点gmac0 { phy-mode rgmii-rxid; clock_in_out output; snps,reset-gpio gpio2 RK_PB5 GPIO_ACTIVE_LOW; snps,reset-active-low; snps,reset-delays-us 0 20000 100000; pinctrl-names default; pinctrl-0 eth0m0_miim eth0m0_tx_bus2 eth0m0_rx_bus2 eth0m0_rgmii_clk eth0m0_rgmii_bus ethm0_clk0_25m_out; tx_delay 0x30; rx_delay 0x0; phy-handle rgmii_phy0; status okay; };这里每一项都不是装饰phy-mode定义 MAC/PHY 延时模型reset-gpioPHY 上电复位pinctrl-0把 SoC 引脚切到以太网功能tx_delay/rx_delayMAC 侧延时参数phy-handle连接到哪一个 PHY2. MDIO / PHY 节点mdio0 { rgmii_phy0: ethernet-phy1 { compatible ethernet-phy-id001c.c916, ethernet-phy-ieee802.3-c22; reg 0x1; clocks cru REFCLKO25M_GMAC0_OUT; phy-supply vcc_3v3_phy; realtek,clkout-disable; max-speed 1000; }; };这部分的作用是regPHY 的 MDIO 地址compatible帮助绑定正确驱动clocksPHY 的参考时钟phy-supply供电控制max-speed限制速度上限realtek,xxx厂商私有配置3. pinctrl 节点这是最容易被忽视的一层。驱动写得再漂亮如果 pinctrl 没配到正确复用接口根本不可能好。所以以太网 pinctrl 至少要保证MDIO/MDCTX/RX 数据线TXC/RXC参考时钟reset pin如果是 GPIO七、为什么“能看到 eth0”不等于“驱动没问题”这个误区非常常见。看到eth0只说明两件事net_device 已经注册了MAC 驱动 probe 至少没死但下面这些问题依然可能存在PHY 其实绑定成 Generic PHY 了RGMII delay 错了只有 100M没有 1000M链路一直 downshiftRX 一直 0DHCP 拿不到169.254.x.x fallback这就是为什么调试以太网时必须同时看dmesgethtoolifconfig/ip -s link设备树PHY 驱动匹配情况板级原理图单看一个现象往往会误判。八、你当前这套驱动里真正关键的代码点有哪些如果把“当前工程里的关键代码”提炼出来其实就几处。1. PHY ID 匹配这是 PHY 驱动入口。{PHY_ID_MATCH_EXACT(0x001cc916),.nameRTL8211F Gigabit Ethernet,.config_initrtl8211f_config_init,}如果这里没有或者 ID 错了就很容易退回 Generic PHY。2. rtl8211f_config_init()这是 PHY 的“板级初始化大脑”。这里通常做根据phy-mode决定延时配厂商扩展寄存器关/开 EEE配 ALDPS配 clkout这部分代码写对了日志通常会从“Generic PHY 100M”进化到“能正确识别 RTL8211F 并开始稳定协商”。3. GMAC 的设备树这个部分决定复位时钟引脚延时PHY 连接如果这个节点错了PHY 驱动未必报错但链路质量很可能不正常。4. 网络管理器配置这部分虽然不是驱动源码但在板上经常会误导判断。比如驱动已经好了但 DHCP 没回来系统给了169.254.x.x这时如果不了解网络层就会误以为“驱动还有问题”。其实169.254.x.x往往只是DHCP 失败后的 fallback或者 dhcpcd / networkd 两个管理器抢网卡所以驱动调通后还要把 rootfs 侧网络服务收敛成一套。九、如果“从零开始写驱动”到底应该怎么干这个问题必须讲现实一点。真正从零写一套网卡驱动代价非常大因为你要自己处理DMA ringcache coherentNAPIGROchecksum offloadTSOethtoolphylibPMerror recovery这对大多数项目并不划算。工业界真正的做法是复用成熟 MAC 核心只写 glue 和板级配置。对你这个平台正确策略是复用stmmac用realtek.c做 RTL8211F PHY写好 SoC glue / DTS补必要的厂商特性调板级时序也就是说真正“从零”的部分其实主要是设备树SoC glue少量 PHY 特化代码调试脚本与日志而不是自己重写一个完整以太网子系统。十、一个合理的开发顺序如果我要从零把一块新板的 Ethernet 带起来我会按下面顺序做。第一步确认硬件连接先看原理图MAC 接哪个 PHYMDIO 地址多少reset pin 在哪参考时钟怎么来RGMII 还是 RMIILED/ALDPS/clkout strap 怎么接磁性器件怎么接RJ45 是否带灯千兆四对线是否完整不做这一步后面都是猜。第二步写 DTS先把最基本的节点写出来gmac0mdio0phy1pinctrl先让系统能识别出eth0和 PHY。第三步确认 PHY 驱动是否正确匹配看dmesg是RTL8211F Gigabit Ethernet还是Generic PHY如果还是 Generic PHY先别碰 DHCP先把 PHY 驱动绑定搞定。第四步确认 Link Up / Speed / Duplex看ethtool eth0是否 Link detectedSpeed 是多少对端 advertised 什么本端 advertised 什么这一层解决的是“物理链路是否真的正常”。第五步确认 TX/RX 是否都正常看统计RX packetsTX packetserrorscrcframe如果 TX 有、RX 没有就别去想 IP先回头查物理层。第六步最后才看 DHCP / IP只有前面都正常了才看dhcpcdsystemd-networkd静态 IP路由DNS这个顺序很重要。很多人恰恰反过来一开始就盯 DHCP结果越调越乱。十一、为什么网卡驱动调试经常会卡在“100M / 10M / 169.254”这种现象里因为这几个现象非常有迷惑性。1. 只能 100M不代表驱动没加载。常见原因是千兆四对线没全通RGMII delay 不对磁性器件或 RJ45 接线有问题PHY 特殊配置没生效2. 能 Link Up但拿不到 DHCP不代表 DHCP 服务一定坏了。也可能是TX 正常但 RX 不正常对端回包收不到实际链路质量差广播包丢失3. 是 169.254.x.x这通常不是“系统给错了 IP”而是DHCP 没成功自动退回到 IPv4 link-local这时你如果把注意力全放在 IP 上就会错过真正的物理链路问题。十二、最后做一个全局总结如果只用一句话总结 Linux 以太网驱动那就是网卡驱动不是一个驱动文件而是一条从设备树、MAC、DMA、NAPI、MDIO、PHY、RGMII、RJ45 一直到网络服务的完整链路。对当前这套 RK3576 RTL8211F 而言真正关键的点有四个第一MAC 驱动不是从零写你复用的是 stmmac / dwmac。所以核心收发逻辑、DMA、NAPI已经有成熟框架不需要从零发明。第二PHY 驱动决定链路层行为realtek.c里的RTL8211F匹配和rtl8211f_config_init()决定了 PHY 是否被正确识别以及 RGMII delay、EEE、clkout、低功耗等行为。第三设备树决定板级事实设备树不是配角它是驱动能否正确工作的“前提条件”。phy-mode、reg、reset-gpio、tx_delay、rx_delay、phy-handle每一项都可能直接决定链路是否稳定。第四网络管理器只是最后一层eth0出现了、Link Up 了不代表 IP 就一定对。DHCP、169.254、networkd、dhcpcd 这些属于更上层只能在驱动和链路先正常的前提下再处理。结尾如果让我们从零做一版“最小可工作”的网卡支持最少要有这些东西最后把最小集合列出来作为一份真正可落地的清单。代码层最少要有1. MAC 驱动probeopen / stopstart_xmitinterrupt NAPIDMA 描述符管理net_device_ops2. PHY 驱动PHY ID matchconfig_initread_statusautoneg 支持3. 设备树gmac nodemdio nodephy nodepinctrlreset / clock / delay4. rootfs 网络配置选一个网络管理器DHCP 或静态 IP不要多个服务同时抢接口B站 嵌入式孙老师博主个人介绍博主书籍-京东购买链接*Yocto项目实战教程加博主微信进技术交流群jerrydev