资讯动态

嵌入式以太网驱动实战:从MAC/PHY接口到设备树与故障排查

发布时间:2026/10/8 18:37:01 来源:尧图企业网站定制
我做嵌入式驱动开发这几年凡是跟网络沾边的项目最后十有八九都要和Ethernet 以太网驱动打交道。板子上的网口能不能稳定工作从来都不只是硬件的事SoC 里的 MAC 控制器、板子上的 PHY 芯片、MII/RMII/RGMII/SGMII 接口、MDIO 管理总线、内核里的 net_device 与 NAPI每一层都有自己的脾气。第 10 期就把这块内容一次性讲透从整体框架、接口选型、PHY 寄存器操作、DTS 设备树配置到排查链路 up 但 ping 不通、千兆降百兆、收包丢包这类常见故障内容按照我实际项目推进的顺序写。适合刚要入门嵌入式网络驱动开发的人也适合已经调了一两天网口、正被各种玄学问题困住的同行参考。1. 项目整体拆解以太网驱动到底在驱动什么1.1 先理清楚 MAC、PHY 和协议栈的分工很多新手拿到一个网口项目第一反应就是“写驱动”。但如果你直接去翻芯片手册找寄存器大概率会被一堆缩写搞晕。我习惯先画一条链路应用层 socket → TCP/IP 协议栈 → 内核网络设备层 →MAC 控制器→ 接口MII/RMII/RGMII/SGMII →PHY 芯片→ 网线/光纤 → 对端。这条链路上真正属于“驱动”要管的是网络设备层以下的部分。MAC 控制器负责的是数据链路层的活把上层下来的数据包组装成以太网帧加上 MAC 地址、CRC 校验再通过 DMA 描述符搬运到 PHY收包时反向操作还要做地址过滤和帧校验。而PHY 芯片负责物理层的信号转化把 MAC 送来的并行数字信号变成网线上能跑的模拟差分信号或者把对端传来的信号还原成数字比特流顺便完成自动协商、链路状态检测、时钟恢复这些事情。打个比方MAC 是快递分拣中心管包裹怎么封装、怎么贴面单、怎么往传送带上放PHY 是运输车队管包裹怎么装车、走哪条高速、什么时候发车。两边各干各的中间靠一条约定好的“传送带”对接这条传送带就是 MII 家族接口旁边还有一条专门用来远程管理车队的对讲通道也就是 MDIO 管理总线。1.2 常见 SoC 的 MAC 与 PHY 组合不同平台的 MAC 控制器长得完全不一样但 Linux 里的驱动框架是统一的。我这些年接触过的组合大致有这几类NXP i.MX6ULL / i.MX8M 系列FEC 或 DWMAC1000配合 Microchip LAN8720A、Realtek RTL8211F 这类常见的板级 PHY百兆到千兆都有。ST STM32F4/F7/H7内部 ETH 控制器最常见的搭档是 LAN8720A走 RMII 接口板子布局紧凑引脚少。TI AM335x / AM57xxCPSW 双端口 MAC外挂 Marvell 88E1512 或 TI DP83867工业板卡出货量很大。瑞芯微 RK/RV 系列、全志系列GMAC 控制器基本是 DesignWare 的变体配合 RTL8211F、YT8512、DP83867 等。FPGA 平台Xilinx 等厂商提供 “1G/2.5G Ethernet PCS/PMA 或 SGMII” 这类软核把 MAC 与 SerDes 之间的 PCS/PMA 逻辑在 FPGA 里实现。这种场景下“PHY” 可能不是一个独立芯片而是光模块或者另一颗交换芯片的 SerDes 接口。我的建议是先确认你手上平台的 MAC 型号再去找到对应的内核驱动文件比如fec_main.c、stmmac_main.c、cpsw.c最后看 PHY 芯片的手册。驱动框架多半不用大改真正要调的是 PHY 相关配置、设备树、时钟和复位时序。1.3 驱动工作的边界嵌入式以太网驱动的日常集中在几件事注册 net_device 并配置硬件能力、初始化 DMA 描述符环、通过 phylib 或 phylink 管理 PHY、中断处理和 NAPI 收发包、实现 ethtool 接口方便现场调试。TCP/IP 协议栈这些是内核自带的绝大多数情况下不需要你碰但在实际项目里你至少要知道 sk_buff 是怎么流转的因为驱动和协议栈的交互点就是netif_rx、napi_gro_receive和ndo_start_xmit。你不需要会写 TCP 窗口算法但必须知道驱动往协议栈塞包时sk_buff 的头部预留空间够不够。2. 接口选型与时钟设计MII、RMII、RGMII、SGMII 的取舍2.1 四种接口的核心差异板级以太网的 MAC 和 PHY 之间常见接口有 MII、RMII、GMII、RGMII更高端的平台还会走 SGMII/SerDes。我第一次选型时也纠结过其实它们的核心区别就三点数据位宽、时钟频率、引脚数量。接口数据宽度时钟频率典型引脚数用途MII4 bit2.5MHz10M/ 25MHz100M约 16 根百兆老平台常见RMII2 bit50MHz 固定参考时钟约 9 根百兆引脚紧张时首选GMII8 bit125MHz约 24 根千兆老 SoC 或 FPGARGMII4 bitDDR 采样125MHz千兆/ 25MHz百兆约 12 根千兆现在工业级 SoC 的主流SGMII1 bit 串行差分1.25Gbps / 3.125Gbps2 对差分线千兆/2.5G走背板或交换芯片选型的逻辑其实很直白引脚够不够、PCB 好不好走、速率能不能满足。如果产品只有百兆需求RMII 是性价比之王要千兆RGMII 基本是标配如果 MAC 和 PHY 距离很远或者要和交换芯片、光模块对接SGMII 这类串行接口会少很多麻烦。RGMII 这种 DDR 接口在布线时要特别注意等长和阻抗我的经验是尽量让 PCB 工程师把 RX/TX 差分对认真处理不然后面全是奇怪的偶发包问题。2.2 RMII 的 REF_CLK 到底谁给——一个经典坑RMII 比 MII 少了一半引脚代价是 MAC 和 PHY 必须共用一个 50MHz 的参考时钟 REF_CLK。这个时钟方向不同平台完全不一样有的要求 MAC 输出给 PHY有的要求 PHY 输出给 MAC还有的板子用一颗独立有源晶振同时供给两边。方向搞反最常见的现象是PHY 的寄存器能读到但链路完全协商不上或者协商成功却收不到任何数据包。我踩过一次很典型的坑某项目用 STM32F407 搭配 LAN8720A按原理图LAN8720A 的时钟由 STM32 的 MCO 引脚输出 50MHz 提供。当时为了省一个晶振硬件把 MCO 复用到了其他功能导致 PHY 没有时钟。现象非常迷惑MDIO 能正常读写 PHY 寄存器ethtool eth0也能看到 PHY 存在但 link 永远 down。查了半天最后用示波器量 REF_CLK 才发现根本没有波形。所以拿到一个新板子我的第一件事就是示波器量三个点PHY 供电、REF_CLK 波形、复位引脚电平三分钟能排除一半问题。2.3 千兆以上RGMII 的延迟和 SGMII/PCS/PMA千兆 RGMII 有个知识点必须懂数据信号在时钟的上升沿和下降沿各采样一次为了保证采到稳定数据收发双方至少有一侧要在时钟路径上加延迟补偿 PCB 走线引入的 skew。这就是设备树里rgmii-id、rgmii-txid、rgmii-rxid这些 phy-mode 的由来。如果用裸rgmii模式通常意味着 MAC 和 PHY 两侧都靠 PCB 设计保证延迟一旦板子布线不规范就会出现“百兆稳定、千兆必丢包”的诡异现象。实测中我一般优先用rgmii-id让 PHY 内部自己处理延迟省心很多。再往上走就是 SGMII 和 2.5G 的玩法。SGMII 把并行数据串行化走一对差分线常见的 1G SGMII 线速率是 1.25Gbps内部用 8B/10B 编码如果要跑 2.5G线速率会拉到 3.125Gbps这其实就是很多资料里提到的 “1G/2.5G Ethernet PCS/PMA 或 SGMII” 这类 IP 核的工作范围。PCS/PMA 负责编码、加扰、时钟恢复FPGA 上做高速以太网接口时这些逻辑要么用厂商现成软核要么自己写 Verilog。对做 SoC 板级驱动的人来说只需要关心 MAC 的 SerDes 通道配置成什么模式、速率等级是不是匹配 PHY 的能力。3. PHY 驱动与 MDIO 操作的底层细节3.1 PHY 核心寄存器导读不管 PHY 是哪家芯片IEEE 802.3 规定的标准寄存器是必须支持的。这些寄存器只有 32 个你只要把前几个读熟就基本能徒手排查链路问题。寄存器名称关键信息0BMCRbit15 软复位、bit12 自动协商使能、bit13/bit6 速率选择、bit8 全双工1BMSRbit2 链路状态、bit5 自动协商完成、低 6 位能力通告2/3PHY ID厂商 OUI 和型号信息认芯片身份用4ANAR本端自动协商能力通告比如 1000BASE-T 是否使能5ANLPAR对端链路伙伴的能力通告协商完成后一定要读它确认双方能力6ANER扩展状态偶尔查错误用实操里最常见的场景是想确认“千兆协商为什么没起来”。ethtool eth0会给你结论但它的信息就是从这些寄存器读出来的。比如要看对端能力读 ANLPAR寄存器 5的 bit9 是否置位就能知道对端是否支持 1000BASE-T。如果两边都支持但协商还是降级再看网线和硬件链路。3.2 MDIO 总线与读写稳定性MDIO也叫 MIIM就是 MAC 访问 PHY 寄存器的那条管理总线两条线MDC 是时钟MDIO 是双向数据。规范里 MDC 最高频率约 2.5MHz但很多控制器内部配了分频器你可以通过寄存器去调整实际速率。我遇到过一次 PHY 间歇性读失败最后就是把 MDC 分频调低一档解决的因为板子走线较长、上拉电阻偏大信号沿不够陡PHY 采样错位。如果控制器自带 MDIO 硬件模块驱动里直接读写寄存器即可但有时候 PHY 挂在一个不支持的 GPIO 上就只能用内核里的mdio-bitbang机制手动模拟时序。自己写 bitbang 时要特别注意的细节是读操作带一个 turnaround 位总线方向要先切换大多数自写代码翻车都翻在这一步要么读到的数据多移了一位要么把 PHY 地址和寄存器地址的位数搞错。真要手写建议对照 IEEE 802.3 第 22 章的帧格式逐步核对别凭感觉拼。读 PHY 寄存器还有一个经验叫“先复位再读”。PHY 上电后如果没有被正确复位寄存器可能一直处于异常状态读出来的 ID 全是 0xFFFF 或者 0x0000。这个时候不是驱动逻辑的问题而是复位时序的问题。我习惯在驱动 probe 之前通过 GPIO 控制 PHY 复位引脚拉低再拉高等待至少 10ms 稳定后再去访问 MDIO。3.3 自动协商失效的原因与规避自动协商是 IEEE 标准机制本端发能力通告对端回能力通告最后取两边交集。看起来自动化程度很高但在实际项目里它是最容易出问题的环节。常见原因就这几种网线质量太差特别是长距离百兆/千兆线协商链路不稳定PHY 的 strap 引脚配置不对导致速度能力被限制对端设备强制指定了速率双工两边设置不一致。强制和自动协商如果混用最容易出现的就是双工不匹配——一边全双工一边半双工表现是链路 up 正常但一旦有流量就疯狂碰撞丢包严重延迟抖动很大。我的原则是能让它自动协商就自动协商不要手动强制。只有两种情况下我会强制一是现场硬件线缆确实不行只能锁百兆二是要临时对比验证某个速率档位的行为。强制设置的时候最好记住ethtool -s eth0 speed 100 duplex full autoneg off这条命令并在 PHY 寄存器里确认修改真的生效了因为不同 PHY 对 BMCR 写操作后的内部同步逻辑不一样。4. 驱动核心代码链路从 probe 到正常收发4.1 net_device 注册与硬件能力声明Linux 网络驱动的心脏是struct net_device。驱动 probe 时通常先用alloc_etherdev分配设备然后填充netdev_ops、ethtool_ops再注册网络设备。这里面最容易犯的错误是硬件能力声明与实际硬件不符。比如 MAC 根本不支持 TCP 校验和卸载你却给netdev-features加了NETIF_F_IP_CSUM表面上看跑得挺快实际抓包发现所有 TCP 包的校验和永远不对。能力声明还有个细节hw_features和features要分开处理。hw_features表示硬件真实能力features表示当前生效的特性内核在ndo_set_features回调里可以根据实际状态做切换。常见的 VLAN offload、GRO/LRO、校验和卸载都在这一层管理调试时可以先用 ethtool -k 看每项到底是什么状态。4.2 ndo_open 里的顺序问题ndo_open是网卡被ifconfig up或ip link set up时调用的入口。它的职责是把硬件真正拉起来。我习惯的顺序是先申请并初始化 DMA 描述符然后配置 MAC 地址、过滤表接着打开硬件中断再napi_enable最后phy_start开始链路检测。这个顺序不能乱特别是 NAPI 和中断的开关顺序搞反会出现中断触发后调napi_schedule但 NAPI 还没使能丢事件导致收包永久停滞。这里要特别提醒PHY 启动后链路不会立刻 up自动协商通常需要几百毫秒甚至几秒。所以ndo_open返回后马上检查链路状态是没意义的链路 up 事件会通过 PHY 中断或定时轮询上报最终触发netif_carrier_on。很多现场脚本在网口起来后立即去 ping 网关ping 不通就上报故障其实只是没等协商完成。实操里我给现场脚本留了 3 到 5 秒的等待窗口基本没再因为这个被误报过。4.3 收包路径中断、NAPI 与 ring buffer收包路径的经典方案是网卡收到数据后 DMA 写入内存中的 ring buffer然后触发中断驱动在中断里把中断源屏蔽掉调用napi_schedule之后系统会调度 NAPI poll 函数来处理数据。NAPI 的核心思想是把中断驱动的收包转换成轮询驱动的收包在高流量下减少频繁进出中断的开销。骨架代码大概长这样static int eth_poll(struct napi_struct *napi, int budget) { struct eth_priv *priv container_of(napi, struct eth_priv, napi); int work_done 0; // 处理 TX 完成描述符释放 sk_buff eth_tx_reclaim(priv); // 从 RX ring 取描述符上限是 budget防止饿死其他软中断 work_done eth_rx_process(priv, budget); if (work_done budget) { napi_complete_done(napi, work_done); enable_irq(priv-irq); } return work_done; }这段代码是几乎所有内核网卡驱动的通用形态。实际项目里调得最多的参数就是budget和 ring buffer 的深度。预算太小高吞吐下收包会跟不上ring buffer 太浅瞬时大流量直接溢出丢包。很多 SoC 的 MAC 驱动支持通过ethtool -g eth0查看和调整 ring size我的经验是先在压力测试下观察ethtool -S里的 rx_dropped、rx_missed 计数器再决定要不要加深 ring。4.4 发送路径与 TX timeout发送路径的核心是ndo_start_xmit拿到 sk_buff把数据放进 DMA 描述符通知 MAC 开始发送然后立刻返回NETDEV_TX_OK。这里有三个经典坑。一是描述符用完了必须调用netif_stop_queue停掉上层发送队列等 TX 中断回收描述符后再netif_wake_queue恢复不然系统会无限往里塞包。二是如果底层对 sk_buff 做了暂存比如硬件的发送 FIFO 有限要尽早用skb_orphan或克隆机制防止 socket 缓冲在链路慢时阻塞上层。三是 TX 中断如果丢失描述符永远回收不回来最后触发watchdog_timeo也就是大家常说的 TX timeout。排查 TX timeout 的思路很简单先看中断计数是否在增长再看描述符头尾指针是否卡住最后确认是不是 PHY 侧把发送使能关掉了。5. 设备树与平台配置的实战要点5.1 一个典型 GMAC 节点逐行拆解以常见 ARM SoC 为例设备树里网口部分通常长这样gmac { status okay; phy-mode rgmii-id; phy-handle phy0; }; mdio { phy0: ethernet-phy3 { reg 3; reset-gpios gpio1 10 GPIO_ACTIVE_LOW; reset-assert-us 10000; reset-deassert-us 10000; }; };phy-mode前面说过决定 MAC 和 PHY 之间接口的时序模式千兆 RGMII 一般用rgmii-id。phy-handle指向 mdio 总线上具体的 PHY 子节点。reg是 PHY 地址由硬件 strap 引脚决定必须和原理图一致不然 MDIO 访问的就不是这颗 PHY。reset-gpios负责上电时序内核在访问 PHY 前先拉低复位引脚保持reset-assert-us指定的时间再释放并等待reset-deassert-us。这个等待时间千万别设太短很多 PHY 的数据手册要求复位后至少 10ms 才能稳定访问 MDIO设 1ms 就会偶发读不到 PHY ID。5.2 时钟、供电与 PHY 子节点的电源依赖设备树里最容易被忽略的是时钟和供电的依赖关系。GMAC 节点通常要引用多个时钟AHB 总线时钟、MAC 参考时钟、PTP 时钟等。时钟频率配错最常见的现象是 MAC 内部寄存器读写正常但链路速率和设置速率不符比如明明配置千兆实际跑出来的帧间隔完全不对。所以拿到一棵新设备树我第一步永远是核对 MAC 节点里的clocks属性和 SoC 手册里的时钟树是否一致再配合/sys/kernel/debug/clk/clk_summary去看实际使能和频率。另外PHY 的电源轨在设备树里也要表达清楚。工业板卡经常做多路电源PHY 的 1.0V 内核电压、2.5V/3.3V I/O 电压分别由不同 PMIC 输出如果电源轨启动顺序不对PHY 可能上电后一直处于异常状态。让phy0节点power-domains或supply属性挂在正确的电源域下内核才能在上电时序上帮你兜底。5.3 从板级以太网到车载以太网设备树配置这套东西换个场景照样成立比如车载以太网现在越来越普及。传统以太网用两对差分线车载以太网用的是单对双绞线跑 100BASE-T1 或 1000BASE-T1PHY 比如 Marvell 88Q2112、博通家的 T1 PHY。驱动框架和 phylink 的设计思路完全一样只是接口模式变成1000base-t1这类新定义链路协商机制也多了车载特有的唤醒逻辑。所以把板级以太网驱动吃透再往车载方向走是有一条相对平滑的学习曲线的。6. 常见问题与故障排查实录6.1 链路 up 但 ping 不通这个现象在调试里出现频率最高。链路已经 up说明 PHY 协商没问题问题大概率出在 MAC 之上。我整理了一套固定的排查顺序用ethtool eth0确认当前 speed、duplex确保和 PHY 协商一致。用ifconfig eth0看 MAC 地址是否正常。很多平台的 MAC 地址是出厂固化的如果读出来是00:00:00:00:00:00或全ff对端会直接丢弃这些包。抓包看 ARP。在开发板上tcpdump -i eth0或者用arping发请求看有没有 ARP 请求发出有没有应答。ARP 都没有基本是驱动收发包链路有问题。查ethtool -S里的 tx/rx 计数tx_bytes 在涨而 rx_packets 为 0说明只发不收查中断和 NAPI反之亦然。如果板子和 PC 直连注意交叉网线的问题。现在多数 PHY 支持自动翻转但老芯片或者 EEE 关闭的情况下有些 PHY 不启用 Auto-MDIX直连不通过交换机就通非常容易误判成驱动问题。这里有个踩过的坑必须单独说MAC 地址字节序。有些 SoC 的 MAC 地址寄存器是高位在前驱动里如果直接按小端往寄存器里填会导致发送的源 MAC 地址是正确的但接收过滤时目标 MAC 对不上。表象就是板子能 ping 通别人别人 ping 不通板子。后来我在ndo_set_mac_address里做了字节序交换问题立刻消失。6.2 千兆协商成百兆协商降级协商降级在排除硬件后多半是能力通告没对上。先从寄存器层面看读 BMCR 的 bit12 确认自动协商确实使能读 ANAR 看本端通告了哪些速度读 ANLPAR 看对端能力。如果本端 ANAR 里 1000BASE-T 那一位就没置位那就是寄存器初始化或者 strap 引脚配置出了问题。RTL8211F 这类 PHY 通常有速率 strap 引脚比如AN[3:0]组合决定上电默认能力。硬件为了省几个电阻可能把 strap 配成了只能百兆你再怎么在软件里改 ANAR 都没用。这种情况必须查原理图和 PHY 手册的 strap 表格。我处理过一个返修板现象就是千兆偶尔能协商上、偶尔只能百兆最后发现是 RX 差分对的串联电阻贴错规格导致信号质量和长度不稳定协商过程丢包严重。所以协商降级不要一上来就怀疑驱动先拿示波器看信号眼图再怀疑软件。6.3 MDIO 读写超时或读到全 FMDIO 读操作如果一直返回 0xFFFF原因一般就三个方向。第一看硬件复位PHY 的复位引脚被拉低没有释放或者复位时间不够可以先手动操作 GPIO 重新复位再短延时后读 PHY ID。第二看 MDC 频率分频比太高导致 MDC 超过 PHY 上限把频率调低再试。第三看地址冲突MDIO 总线上如果挂了多颗 PHY地址必须各不相同地址重复时只有一颗 PHY 能正常响应其他的就会读回全 F。还有个很有意思的问题有的平台 MAC 的 MDIO 控制器有自己的软复位位驱动如果没在初始化时做完整的 MDIO 控制器复位总线状态会残留上一次的位宽或时序配置出现“复位前读写正常跑一会儿就超时”。遇到这种问题我建议把 MDIO 控制器的复位流程单独跑一遍确认复位后再做首次 PHY 读。6.4 收包丢包、中断风暴和高 CPU压力测试下丢包先分方向。ethtool -S看 rx_dropped、rx_missed_errors、rx_fifo_errors 这些计数器。rx_missed 涨得厉害说明 MAC 内部 FIFO 接收溢出硬件设计或 flow control 的问题rx_dropped 涨更多是驱动收包没跟上NAPI 预算太小或者收包处理里做了太多耗时的操作。我优化过一次收包路径把每包里的一个无意义的校验计算挪到硬件整体丢包率直接下降一个数量级。中断风暴的表现是 CPU 占用高但吞吐也不见得好。查/proc/interrupts对应网卡中断号看计数是不是每秒几万次。如果流量不大但中断次数狂涨通常是 PHY 产生了重复事件中断比如链路翻转毛刺。解决办法是在 PHY 中断处理里做去抖或者在驱动里改成只响应我们真正关心的 PHY 事件。另外看/proc/interrupts里中断类型是电平触发还是边沿触发配错了也会产生鬼影中断。7. 写在最后的实操心得最后分享几个不写进文档但影响效率的体会。第一调试以太网驱动时别急着改代码先把ethtool、tcpdump、/proc/interrupts、/sys/kernel/debug这几样工具用熟它们能定位掉七成问题。第二任何时候都不要凭感觉把 PHY 的自动协商关掉来“解决”协商问题这只是掩盖现象规范操作是查 strap、查线缆、查对方配置。第三设备树里phy-mode、reset-gpios、clocks这三个属性要反复核对我见过太多“驱动写好了但网口就是不行”的案例最后都是设备树和原理图对不上导致的。还有一点关于 MAC 地址字节序、DMA 描述符对齐、CACHE 一致性这几个老问题现在多数平台用的是 coherent DMA 映射但如果有性能抖动可以试试把 RX 缓冲按 cacheline 对齐实测在某些 Cortex-A 平台上有明显改善。嵌入式以太网驱动不是一个“写完就完”的模块它是一个需要配合硬件、系统、现场环境不断打磨的工程。把链路状态检测、PHY 管理、收发包路径、异常恢复这些基础打扎实再往上做交换机、车载以太网、工业 EtherCAT 网关都只是换一层皮而已。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑