很多网卡问题都有一种迷惑性:

  • MDIO 能读到 PHY ID,但接口始终 NO-CARRIER
  • ethtool 显示 1000 Mbps、Link detected: yes,却 ping 不通;
  • 百兆稳定,千兆一跑 iperf3 就出现 CRC error;
  • 开机前几秒能收包,随后 RX 停止;
  • 低流量正常,并发或大包时 TX timeout;
  • 关闭 checksum/TSO/GRO 后“神奇恢复”。

这些现象之所以难排,是因为我们口中的“网卡驱动”其实横跨多个软硬件层:RK3588 内部的 Synopsys DWMAC、Rockchip 的 GRF/clock/reset glue、外置 RTL8211F PHY、RGMII 数据总线、MDIO 管理总线、Linux stmmac、phylink/phylib、NAPI、DMA 和网络协议栈。

如果只盯着 stmmac_main.c 的某一个函数,或者只盯着 PHY 的 link 状态,就很容易在错误的层次上修问题。

本文以本地 Firefly RK3588 SDK 的 Linux 5.10.198 为样本,建立一套可迁移到其他 SoC 网卡的完整理解框架:

  1. MAC、PHY、RGMII、MDIO 各自负责什么;
  2. 一个 Linux 网卡驱动到底要实现哪些能力;
  3. dwmac-rk、stmmac、phylink、phylib 和 realtek.c 如何分工;
  4. probe、open、link、TX、RX、IRQ/NAPI 的完整调用链;
  5. descriptor、DMA buffer 与 skb 的所有权如何转移;
  6. 为什么 RGMII delay 是 RK3588 千兆网口最关键的板级边界;
  7. 面对“找不到 PHY”“link up 不通”“千兆不稳”时如何分层定位。

本文讨论的是 RK3588 + 外置 RTL8211F 千兆 PHY 的典型 RGMII 场景。不同 Firefly 板型可能连接 GMAC0 或 GMAC1,也可能使用其他 PHY;最终应以原理图和实际启动 DTB 为准。


一、先拆掉第一个误区:网卡不是一个硬件块

RK3588 与 RTL8211F 的软硬件分层

1. MAC:处理以太网帧与 DMA

RK3588 集成 Synopsys DesignWare Ethernet MAC。MAC 处于数字世界,主要负责:

  • 接收和发送以太网帧;
  • 生成、检查 FCS;
  • MAC 地址过滤、组播过滤;
  • VLAN、checksum、TSO、RSS、时间戳等硬件卸载;
  • 管理 MAC/MTL FIFO 和多队列;
  • 通过 DMA descriptor 在 FIFO 与 DDR packet buffer 之间搬运数据;
  • 产生 RX、TX completion、DMA error、LPI 等中断。

Linux stmmac 的主要工作对象就是这个 MAC、MTL 和 DMA。

2. PHY:处理双绞线上的物理信号

RTL8211F 是独立的千兆以太网 PHY。它负责:

  • 10BASE-T、100BASE-TX、1000BASE-T 的模拟前端和物理编码;
  • 自动协商、千兆主从训练;
  • 速度、双工和 pause 能力协商;
  • 均衡、回波抵消等 PHY 内部信号处理;
  • 报告 link、speed、duplex、pause;
  • PHY 私有低功耗、中断和 delay 配置。

PHY 不知道 struct sk_buff,也不管理 DMA ring。它面对的是 RGMII 数字接口和 MDI 双绞线接口。

3. RGMII:MAC 与 PHY 的数据通道

RGMII 承载实际帧数据。GMAC 与 RTL8211F 之间通过 TXD/RXD、TXC/RXC 和控制线交换数据。

千兆模式下时钟为 125 MHz,并在时钟双沿传输。正因为采样窗口很窄,RGMII 对 clock-data skew 极其敏感,这也是“百兆正常、千兆异常”的高发根因。

4. MDIO/MDC:MAC 管理 PHY 的控制通道

MDIO 是另一条独立总线:

  • MAC 内的 MDIO controller 是 bus master;
  • PHY 是具有地址的从设备;
  • Linux 通过 MDIO 读取 PHY ID、基本状态和厂商扩展寄存器。

因此必须牢记两条判断:

MDIO 能读到 PHY ID,只能证明管理通路基本正常,不能证明 RGMII 数据通路正确。

PHY 显示 link up,只能证明 PHY 与链路对端完成了协商,不能证明 MAC DMA 与 Linux 收发路径正确。


二、Linux 为什么把它拆成五层驱动

RK3588 网口不是靠一个驱动文件完成,而是多层组合:

软件层 本地源码 解决的问题
设备树 arch/arm64/boot/dts/rockchip/ 板子到底怎么连
Rockchip glue drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c 通用 DWMAC 如何接入 RK3588
stmmac core drivers/net/ethernet/stmicro/stmmac/ MAC/DMA 如何作为 Linux netdev 工作
phylink/phylib drivers/net/phy/ MAC 与 PHY 如何协商能力和链路状态
Realtek PHY drivers/net/phy/realtek.c RTL8211F 有哪些私有初始化和寄存器

1. Rockchip glue 不负责逐包收发

dwmac-rk.c 的价值在于处理 SoC 差异:

  • GRF/PHP_GRF 接口 mux;
  • GMAC0/GMAC1 选择;
  • RGMII/RMII 模式;
  • 参考时钟输入/输出;
  • 10/100/1000 Mbps 分频;
  • SoC 侧 TX/RX delay line;
  • clock、reset、power domain;
  • suspend/resume 包装。

它完成板级准备后调用通用的 stmmac_dvr_probe()

2. stmmac 是 MAC 数据路径主体

stmmac 负责:

  • 创建和注册 net_device
  • ndo_openndo_stopndo_start_xmit
  • DMA descriptor ring;
  • RX buffer 与 TX DMA mapping;
  • IRQ 和 NAPI;
  • 多 TX/RX queue;
  • checksum、TSO、VLAN、RSS、PTP、EEE;
  • ethtool、统计、TX timeout、PM;
  • 注册 MDIO bus;
  • 通过 phylink 接收链路变化。

PHY 能发现链路协商到了 1000 Mbps 全双工,但它不知道 RK3588 应该把哪个 GRF 分频位改成 DIV1;MAC 知道怎样配置自己,却不应该依赖某一个 Realtek 型号。

phylink 负责把双方隔离并协调:

  • MAC 支持哪些 interface 和速率;
  • PHY/PCS 当前解析出了什么状态;
  • link up/down 时调用哪个 MAC 回调;
  • speed、duplex、pause 变化如何传递。

4. phylib 提供 PHY 通用状态机

phylib 负责:

  • 创建 phy_device
  • 读取 PHY ID 并匹配 phy_driver
  • 通用自动协商;
  • 定时状态机或 PHY IRQ;
  • link/speed/duplex 状态更新;
  • suspend/resume 和通用 Clause 22/45 操作。

5. Realtek driver 只实现芯片差异

RTL8211F 的驱动无需重新实现整个自动协商协议。它主要补充:

  • 芯片私有初始化;
  • RGMII internal delay;
  • PHY 中断 enable/ack;
  • paged register 访问;
  • 低功耗或硬件 quirk。

这种分层不是“复杂化”,而是让一个 stmmac core 可以服务许多 SoC,让同一个 Realtek PHY driver 可以连接许多 MAC。


三、从设备树读懂一块板的网口

1. SoC 节点声明 DWMAC 实例

RK3588 的 GMAC0 定义在:

1
firefly_rk3588_SDK/kernel/arch/arm64/boot/dts/rockchip/rk3588.dtsi:690

GMAC1 定义在:

1
firefly_rk3588_SDK/kernel/arch/arm64/boot/dts/rockchip/rk3588s.dtsi:5500

两个节点都包含:

1
compatible = "rockchip,rk3588-gmac", "snps,dwmac-4.20a";

这两个 compatible 分别选择:

  • RK3588 平台 glue;
  • Synopsys DWMAC 4.20a 通用能力模型。

SoC 节点还描述 MMIO、MAC IRQ、wake IRQ、GRF、clock、reset、power domain、TSO 和 MDIO 子节点。

2. 板级 DTS 决定 PHY 和 RGMII 怎么接

Firefly SDK 的 rk3588-firefly-port.dtsi:179 提供了一个典型配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&gmac1 {
phy-mode = "rgmii-rxid";
clock_in_out = "output";

snps,reset-gpio = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>;
snps,reset-active-low;
snps,reset-delays-us = <0 20000 100000>;

pinctrl-0 = <&gmac1_miim
&gmac1_tx_bus2
&gmac1_rx_bus2
&gmac1_rgmii_clk
&gmac1_rgmii_bus>;

tx_delay = <0x42>;
phy-handle = <&rgmii_phy1>;
};

&mdio1 {
rgmii_phy1: phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <0x1>;
};
};

这段 DTS 同时回答了八个问题:

  1. 使用 GMAC1;
  2. 数据接口是 RGMII;
  3. PHY 负责插入 RX internal delay;
  4. 参考时钟由 SoC 输出;
  5. PHY reset 使用哪个 GPIO;
  6. reset 前后延迟多长;
  7. RK3588 SoC 侧 TX delay tap 是多少;
  8. PHY 的 MDIO 地址是 1。

3. 不要只看某个 dtsi

这个示例文件中还出现了:

1
status = "disbaled";

对设备树来说,通常只有 okay/ok 表示可用,其他值都等价于不可用。真实板级文件可能在后面覆盖它,也可能没有。

所以判断运行配置时,证据优先级应当是:

1
实际启动 DTB > 最终板级 DTS 展开结果 > 某个被 include 的 dtsi

可以反编译实际 DTB:

1
2
dtc -I dtb -O dts -o /tmp/running.dts /sys/firmware/fdt
rg -n 'ethernet@fe1b0000|ethernet@fe1c0000|phy-mode|tx_delay|rx_delay|phy@1' /tmp/running.dts

四、最危险的板级参数:RGMII delay

1. 为什么 RGMII 需要 delay

RGMII 为减少引脚数量,在时钟上升沿和下降沿都发送数据。接收端需要在数据稳定窗口中间采样,因此时钟与数据之间需要大约 1.5~2 ns 的偏移。

这个偏移可以来自:

  • PCB 走线;
  • PHY 内部 delay;
  • SoC/GMAC 内部 delay line。

问题在于三者都可能“帮忙”。如果同一方向重复加入 delay,或者没有任何一方加入,就会把采样点推到窗口边缘之外。

2. phy-mode 是从 PHY 视角定义的

模式 PHY TX delay PHY RX delay
rgmii
rgmii-rxid
rgmii-txid
rgmii-id

这里最容易发生方向混淆:

  • PHY TX:PHY → MAC,对应 MAC RX;
  • PHY RX:MAC → PHY,对应 MAC TX。

3. RTL8211F 会真的读取 phy-mode

rtl8211f_config_init() 位于:

1
firefly_rk3588_SDK/kernel/drivers/net/phy/realtek.c:181

函数按 phydev->interface 选择:

  • RGMII:PHY 不加 delay;
  • RGMII_RXID:PHY 加 RX delay;
  • RGMII_TXID:PHY 加 TX delay;
  • RGMII_ID:PHY 两边都加。

所以 phy-mode 不是一个只供 MAC 参考的标签,它会改变 RTL8211F 私有寄存器。

4. RK3588 自己也能加 delay

RK3588 专用代码位于:

1
firefly_rk3588_SDK/kernel/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c:1881-2018

rk3588_set_to_rgmii() 会:

  • 选择 GMAC0/1 的 RGMII mux;
  • 配置 RGMII clock mode;
  • 打开或关闭 SoC 侧 TX/RX delay;
  • 写入 tx_delay/rx_delay tap。

Firefly 示例选择 rgmii-rxid,PHY 负责 RX delay,DTS 又给 SoC 配 tx_delay = <0x42>。这是一种明确分工,而不是“两个地方都随便填一个值”。

5. RGMII 时序问题的典型症状

  • 10/100 Mbps 正常,1000 Mbps 不稳定;
  • ping 小包正常,iperf3 大流量丢包;
  • 单向正常、反向异常;
  • ethtool -S 中 CRC、alignment、receive error 增长;
  • 不同温度、不同线缆或不同对端表现不一致。

调试时不要问“delay 应该填多大”,而要先问:

  1. 原理图和 PCB 由哪一侧提供 skew?
  2. PHY internal delay 开了哪一边?
  3. SoC internal delay 开了哪一边?
  4. 最终 DTB 的配置是否和设想一致?
  5. 经过双向压力测试后,稳定窗口在哪里?

五、probe:把一个 DWMAC IP 注册成 Linux 网卡

RK3588 stmmac probe 与 link 建立流程

1. 从 compatible 进入 rk_gmac_probe()

RK3588 match 表在 dwmac-rk.c:2879-2940,其中:

1
{ .compatible = "rockchip,rk3588-gmac", .data = &rk3588_ops },

platform driver 的 probe 是 rk_gmac_probe(),位于 dwmac-rk.c:2774

它的主干是:

1
2
3
4
5
6
7
rk_gmac_probe()
→ stmmac_get_platform_resources()
→ stmmac_probe_config_dt()
→ rk_gmac_setup()
→ rk_gmac_clk_init()
→ rk_gmac_powerup()
→ stmmac_dvr_probe()

2. 平台层准备什么

stmmac_get_platform_resources() 取得:

  • MMIO resource;
  • macirq
  • 可选 wake/LPI IRQ。

stmmac_probe_config_dt() 解析:

  • MAC 地址;
  • phy-modephy-handle
  • MDIO 子节点;
  • FIFO/ring/queue 参数;
  • DMA burst;
  • TSO、RSS 等能力;
  • AXI/MTL 队列配置。

Rockchip 私有路径再取得 GRF/PHP_GRF、clock、reset、power 和 delay 属性。

3. rk3588_ops 是 SoC 差异接口

1
2
3
4
5
6
rk3588_ops
├─ set_to_rgmii
├─ set_to_rmii
├─ set_rgmii_speed
├─ set_rmii_speed
└─ set_clock_selection

链路速度变化时,stmmac 会经平台 fix_mac_speed 回调进入 rk3588_set_gmac_speed()

链路速率 RGMII clock divider
1000 Mbps DIV1
100 Mbps DIV5
10 Mbps DIV50

PHY 协商到 1 Gbps 后,如果 SoC clock 仍停留在百兆分频,link 灯可以亮,数据仍然无法正确传输。

4. stmmac_dvr_probe() 创建 Linux netdev

通用 probe 位于 stmmac_main.c:5061-5332,主要完成:

  • devm_alloc_etherdev_mqs()
  • 初始化 stmmac_priv 与多队列;
  • 识别 DWMAC 版本和硬件 feature;
  • 创建 phylink;
  • 注册每个 channel 的 RX/TX NAPI;
  • 安装 netdev ops 和 ethtool ops;
  • register_netdev()

probe 成功只代表接口被内核管理。真正启动 ring、DMA 和 PHY 要等 ip link set ethX up 调用 ndo_open

5. net_device_ops 是阅读入口

stmmac_netdev_ops 位于 stmmac_main.c:4789-4822,最关键的三个回调是:

1
2
3
ndo_open       → stmmac_open
ndo_start_xmit → stmmac_xmit
ndo_stop → stmmac_release

如果要系统学习任意 Linux 网卡驱动,先找到这三个入口,再找 IRQ/NAPI 注册位置,通常就能搭起主骨架。


六、MDIO、PHY ID 与 RTL8211F 驱动绑定

1. stmmac 注册一个 mii_bus

MDIO 实现在 stmmac_mdio.c

1
2
3
4
stmmac_mdio_read()      :222
stmmac_mdio_write() :289
stmmac_mdio_reset() :354
stmmac_mdio_register() :406

stmmac_mdio_register() 创建 mii_bus,把 read/write/reset 回调指向 DWMAC MDIO controller,然后由 phylib 枚举 PHY。

2. DTS reg 必须与 strap 地址一致

1
phy@1 { reg = <1>; };

地址通常由 PHY strap 电阻决定。DTS 写 1、硬件实际 strap 为 0 时,常见现象是:

  • MDC 有时钟;
  • MDIO 读回 0xffff0x0000
  • /sys/bus/mdio_bus/devices/ 没有期望设备;
  • open 报找不到 PHY。

3. phylib 用真实 PHY ID 匹配 Realtek driver

RTL8211F 的 PHY ID 是 0x001cc916。驱动表位于 realtek.c:691-700,安装:

  • rtl8211f_config_init()
  • rtl8211f_ack_interrupt()
  • rtl8211f_config_intr()
  • genphy suspend 与 Realtek resume;
  • paged register access。

板级 DTS 使用通用的:

1
compatible = "ethernet-phy-ieee802.3-c22";

并不妨碍匹配 Realtek driver,因为最终依据是 MDIO 读出的 PHY ID。

4. PHY IRQ 和 MAC IRQ 完全不同

RTL8211F 的 link interrupt enable/ack 位于 realtek.c:93-150。这个中断只用于通知 PHY 链路状态变化。

MAC/DMA IRQ 则通知:

  • RX descriptor 已完成;
  • TX completion;
  • DMA error;
  • MAC/MTL/LPI/PTP 事件。

把 PHY IRQ 配错,通常影响 link 变化检测;把 MAC IRQ 配错,数据路径会停止。两者不能混为一谈。


stmmac_init_phy() 位于 stmmac_main.c:1145,优先通过:

1
phylink_of_phy_connect()

phy-handle 连接 PHY。如果没有标准 handle,则尝试从 MDIO bus 按地址连接。

stmmac 提供的 phylink MAC ops 位于 stmmac_main.c:865-1110

  • stmmac_validate():限制 MAC 支持的接口和速率;
  • stmmac_mac_config():配置接口状态;
  • stmmac_mac_link_down():关闭 MAC TX/RX;
  • stmmac_mac_link_up():配置 speed/duplex/pause,并通知平台改速率。

完整链路是:

1
2
3
4
5
6
7
8
9
网线插入
→ RTL8211F 自动协商
→ PHY IRQ 或 phylib delayed work
→ 读取 link/speed/duplex/pause
→ phylink resolve
→ stmmac_mac_link_up()
→ rk_fix_speed()
→ rk3588_set_gmac_speed()
→ MAC 按协商结果工作

phylib 的 phy_state_machine() 位于 drivers/net/phy/phy.c:1197,它运行在 delayed work 上下文,可以执行 MDIO 事务;这与收包的 hardirq/NAPI 路径是两套不同上下文。


八、open:什么时候 ring、IRQ 和 DMA 真正开始工作

stmmac_open() 位于 stmmac_main.c:2901。将接口置为 UP 后,典型流程是:

1
2
3
4
5
6
7
8
9
stmmac_open()
├─ stmmac_init_phy()
├─ 分配 descriptor ring 和 RX buffer
├─ stmmac_hw_setup()
├─ request_irq()
├─ napi_enable()
├─ 启动 DMA RX/TX
├─ phylink_start()
└─ netif_tx_start_all_queues()

其中 stmmac_hw_setup() 位于 stmmac_main.c:2753-2898,负责把软件配置落到硬件:

  • DMA reset 和 channel 初始化;
  • descriptor base/tail;
  • MAC core 与 packet filter;
  • RX/TX queue 和 DMA channel 映射;
  • MTL scheduling;
  • checksum、TSO、RSS、CBS;
  • 中断合并;
  • PTP 与 EEE。

顺序非常重要:ring 未完成前不能启动 DMA;NAPI 未 enable 前不应开放 RX 中断;关闭路径也必须按相反方向阻止新工作后再释放 DMA memory。


九、TX:一个 skb 如何变成网线上的帧

stmmac TX/RX 与执行上下文

1. 调用链

1
2
3
4
5
6
7
8
9
10
11
send()/sendmsg()
→ TCP/UDP/IP
→ qdisc
→ dev_hard_start_xmit()
→ stmmac_xmit()
→ TX descriptor ring
→ GMAC DMA 读取 DDR
→ MAC/MTL
→ RGMII
→ RTL8211F
→ 网线

stmmac_xmit() 位于 stmmac_main.c:3468

2. ndo_start_xmit 必须完成什么

  1. 根据 skb->queue_mapping 选 TX queue;
  2. 计算 linear data 和 frags 需要多少 descriptor;
  3. 检查 ring 空间,不足则停止 netdev queue;
  4. 处理 checksum、TSO、VLAN 和 timestamp metadata;
  5. 用 DMA API 映射 skb 数据;
  6. 写 descriptor 地址、长度、first/last segment;
  7. 保存 descriptor 到 skb/DMA mapping 的软件关系;
  8. 用内存屏障保证描述符字段先发布;
  9. 设置 OWN,把 descriptor 交给 DMA;
  10. 更新 tail pointer/doorbell。

3. TX completion 不是可选项

DMA 完成发送后,TX NAPI 调用 stmmac_tx_clean(),入口在 stmmac_main.c:2087

  • 检查 descriptor 已归 CPU;
  • 解析发送错误;
  • dma_unmap_*()
  • 释放 skb;
  • 更新统计;
  • ring 空间恢复后唤醒 queue。

TX 所有权变化可以概括为:

1
2
3
4
5
协议栈拥有 skb
→ ndo_start_xmit 接管 skb
→ DMA 拥有 descriptor 和读取 buffer 的权利
→ completion 将 descriptor 归还 CPU
→ TX clean 解除 mapping 并释放 skb

如果 ndo_start_xmit 返回 NETDEV_TX_OK,驱动已经消费 skb;如果返回 NETDEV_TX_BUSY,驱动不能偷偷释放或修改 skb。

4. 常见 TX bug

  • descriptor 数量估算不足;
  • DMA map 失败后回滚不完整;
  • OWN 设置过早,设备读到半写 descriptor;
  • 忘记更新 tail pointer;
  • completion 漏掉 unmap/free;
  • queue stop 后没有 wake;
  • close 时 DMA 仍访问已释放 ring。

十、RX:DMA buffer 如何变成 skb

1. 调用链

1
2
3
4
5
6
7
8
9
10
11
网线
→ RTL8211F
→ RGMII
→ GMAC MAC/MTL
→ DMA 写 RX buffer
→ RX IRQ
→ NAPI poll
→ stmmac_rx()
→ 构造 skb 与 metadata
→ napi_gro_receive()
→ IP/TCP/UDP/socket

2. RX 先有 buffer,后有数据

驱动必须提前为 RX ring 准备 buffer:

1
2
3
4
5
分配 page
→ DMA map
→ 把 DMA address 写进 RX descriptor
→ 设置 OWN
→ DMA 可向该 buffer 写包

如果 refill 断掉,网口可能在启动时收几个包,耗尽 ring 后完全停止。

3. stmmac_rx() 的核心职责

stmmac_rx() 位于 stmmac_main.c:3862-4069,它会:

  • 检查 descriptor ownership;
  • 解析 first/last segment、长度和错误;
  • 做 DMA sync;
  • 构造 skb linear area 和 frags;
  • 处理 checksum、VLAN、RSS hash、timestamp;
  • 调用 eth_type_trans() 设置协议和 MAC header 语义;
  • 记录来源 RX queue;
  • 调用 napi_gro_receive()
  • 保存跨 descriptor 或跨 budget 的半包状态;
  • refill 新 buffer。

关键提交点在:

1
2
3
stmmac_main.c:4048  skb_record_rx_queue()
stmmac_main.c:4049 napi_gro_receive()
stmmac_main.c:4064 stmmac_rx_refill()

调用 napi_gro_receive() 后,skb 所有权已交给 GRO/协议栈,驱动不能继续访问或释放它。

4. page 所有权比 skb 更容易出错

RX 中必须区分两种情况:

  • 数据被 copy 到 skb linear head:原 RX page 可较早回收;
  • page 被挂到 skb frag:page 生命周期转交 skb。

如果已经挂入 frag 的 page 又立即 refill 给 DMA,设备可能覆盖协议栈仍在读取的数据,造成 use-after-free、数据随机损坏或难以复现的 checksum error。


十一、IRQ 与 NAPI:为什么中断里不直接把包收完

stmmac 主 ISR 是 stmmac_interrupt(),位于 stmmac_main.c:4254-4307。它负责:

  • 检查设备是否 DOWN;
  • 读取 MAC/MTL 状态;
  • 处理 LPI、PCS、安全特性等事件;
  • 进入 DMA interrupt handler;
  • 对活跃 channel 屏蔽 IRQ 并调度 NAPI。

RX/TX NAPI 入口分别是:

1
2
stmmac_napi_poll_rx()  stmmac_main.c:4071
stmmac_napi_poll_tx() stmmac_main.c:4093

NAPI 的设计目标不是“减少一次函数调用”,而是避免高包速率下每个包都打断 CPU:

1
2
3
4
5
第一个包触发 IRQ
→ 驱动 mask 当前队列 IRQ
→ NAPI 在 softirq 中批量处理多个包/completion
→ work_done < budget 时 complete
→ 驱动重新打开 IRQ

执行上下文必须分清

路径 上下文 能否睡眠
probe/open/close/PM 进程上下文 可以
ndo_start_xmit 网络发送 softirq 上下文 不可以
MAC ISR hardirq 不可以
NAPI poll NET_RX softirq 不可以
PHY state machine workqueue 可以

因此:

  • hardirq 中不能做长时间 MDIO;
  • NAPI 中不能调用可能睡眠的 clock/regulator API;
  • open/close 要和 IRQ、NAPI、DMA 并发退出同步;
  • IRQ/NAPI 共享短状态时通常使用 spinlock;
  • PHY paged register 应走 phylib 的锁与 helper。

十二、DMA 驱动最核心的不是寄存器,而是所有权

理解网卡驱动时,建议始终追踪三套 owner:

  1. descriptor 属于 CPU 还是 DMA;
  2. DMA buffer/page 属于 ring、DMA、驱动还是 skb;
  3. skb 属于协议栈、qdisc、驱动还是 GRO/socket。

1. TX 所有权

时点 skb descriptor mapping
进入 xmit 驱动接管 CPU 建立中
发布 descriptor 驱动保留回收责任 DMA DMA 可读
completion 待释放 CPU 待 unmap
clean 完成 已释放 free 已解除

2. RX 所有权

时点 page/buffer descriptor skb
refill DMA/ring DMA
DMA 写完 驱动 CPU
构造 skb 驱动或 skb CPU 驱动
GRO 提交 skb/协议栈 待 refill 协议栈

3. 为什么需要屏障

CPU 写 descriptor 的普通字段后,必须保证设备先看见地址和长度,再看见 OWN:

1
2
3
4
write address/length/control
→ dma_wmb()
→ set OWN
→ write tail/doorbell

这不是编译器风格问题,而是 CPU、互连、cache 和 DMA 对内存观察顺序的问题。

4. 为什么不能绕过 DMA API

即使 arm64 平台常见 cache coherent,DMA API 仍负责:

  • DMA address translation;
  • IOMMU;
  • DMA mask;
  • mapping 生命周期;
  • 非一致性平台的 cache sync。

把 CPU virtual address 直接写到 descriptor,是不可移植也不可靠的。


十三、一个网卡驱动还要照顾哪些“非收发”能力

一个可用于产品的驱动不能只实现 ping:

  • MAC 地址管理和 RX filter;
  • MTU 与 jumbo frame;
  • multicast/promiscuous;
  • checksum offload;
  • scatter-gather、GSO/TSO;
  • VLAN insert/strip/filter;
  • RSS 与多队列;
  • interrupt coalescing;
  • ethtool feature/statistics/ring/coalesce;
  • PTP hardware timestamp;
  • EEE/LPI;
  • WoL;
  • tc/CBS;
  • TX timeout 和 DMA error recovery;
  • system/runtime suspend/resume。

RK3588 DTS 的 DWMAC 节点声明了 snps,tso。如果怀疑 offload 路径,可以先用最小配置缩小范围:

1
2
ethtool -k eth0
ethtool -K eth0 rx off tx off tso off gro off gso off

如果关闭某项后恢复正常,下一步应检查 descriptor bit、skb metadata、MTU/frags 和 feature advertisement,而不是永久关闭功能后结束分析。


十四、关闭与挂起:先阻止新工作,再释放资源

stmmac_release() 位于 stmmac_main.c:3060。关闭路径的核心顺序是:

1
2
3
4
5
6
7
停止 TX queue
→ 停止 phylink/PHY
→ mask/disable IRQ
→ napi_disable() 并等待 poll 退出
→ stop DMA TX/RX
→ free IRQ
→ unmap/free packet buffer 和 descriptor

最危险的错误是先释放 ring,后停止 DMA。此时设备仍可能按旧 DMA address 写内存。

RK3588 的 PM 包装位于 dwmac-rk.c:2848-2877

  • 不需要 WoL 时,suspend 会关闭 Rockchip GMAC 电源/时钟;
  • 需要 WoL 时,必须保留足够的 PHY、MAC 和唤醒 IRQ 资源;
  • resume 先恢复平台资源,再恢复 stmmac。

WoL 经常跨越 PHY 电源、MAC wake IRQ、clock domain、pinctrl sleep state 和系统电源策略,不能只看一个 ethtool -s wol g


十五、故障定位:先判断坏在哪一层

场景 1:驱动没有 probe

1
2
dmesg | grep -Ei 'stmmac|dwmac|gmac|ethernet'
ls -l /sys/bus/platform/drivers/rk_gmac-dwmac/

检查:

  • 最终 DT 节点是否 okay
  • compatible 是否正确;
  • clock/reset/power-domain;
  • pinctrl 冲突;
  • 内核配置。

本地 BSP .config 中已启用:

1
2
3
4
5
CONFIG_STMMAC_ETH=y
CONFIG_STMMAC_PLATFORM=y
CONFIG_DWMAC_ROCKCHIP=y
CONFIG_PHYLINK=y
CONFIG_REALTEK_PHY=y

场景 2:probe 成功,但找不到 PHY

1
2
dmesg | grep -Ei 'mdio|phy|no phy|attached PHY'
ls /sys/bus/mdio_bus/devices/

检查:

  • MDC/MDIO pinmux;
  • PHY 电源;
  • reset GPIO 与延时;
  • strap 地址与 DTS reg
  • 读取 ID 是否为 0x001cc916
1
2
ethtool eth0
cat /sys/class/net/eth0/carrier

优先检查:线缆、网络变压器、PHY 模拟供电、晶振、自动协商、PHY reset 和对端能力。此时 DMA ring 通常还不是首要矛盾。

1
2
3
4
ip -s link show eth0
ethtool -S eth0
cat /proc/interrupts | grep -Ei 'eth|gmac'
tcpdump -ni eth0 -e

分层判断:

  • 没有 RX IRQ:看 RGMII、pinmux、clock、MAC enable;
  • 有 IRQ、无 RX packet:看 descriptor ownership、NAPI、RX buffer;
  • CRC/error 增长:看 RGMII delay 与信号完整性;
  • ARP 可以、大包不行:看 MTU、checksum、TSO、DMA/cache;
  • 单向不通:分别检查 TX 与 RX 时序。

场景 5:百兆正常、千兆异常

优先级通常是:

  1. RGMII delay 重复或缺失;
  2. link up 后 RK3588 clock divider 没切换;
  3. 125 MHz clock;
  4. PCB 等长和信号完整性;
  5. PHY 供电、变压器和四对线;
  6. 对端兼容性。

可短暂强制百兆做对照:

1
2
3
ethtool -s eth0 speed 100 duplex full autoneg off
# 测试完成后恢复
ethtool -s eth0 autoneg on

场景 6:高负载丢包、hang 或 TX timeout

1
2
3
4
5
6
ethtool -S eth0
cat /proc/net/softnet_stat
cat /proc/interrupts | grep -Ei 'eth|gmac'
mpstat -P ALL 1
iperf3 -c <peer> -P 4 -t 300
iperf3 -c <peer> -R -P 4 -t 300

重点检查:

  • RX missed/overflow、buffer unavailable;
  • NAPI budget 和 softnet backlog;
  • IRQ affinity、RPS/XPS;
  • ring size、coalescing;
  • RX refill;
  • TX queue stop/wake;
  • DMA mapping/IOMMU error;
  • 温度、clock 和电源管理。

十六、推荐的真实开发板 bring-up 路线

第一步:静态硬件闭环

  • 原理图确认 GMAC0/GMAC1;
  • PHY 型号、strap 地址、reset、IRQ、clock source;
  • RGMII delay 由 PCB、PHY、SoC 哪一方承担;
  • DTS status、pinmux、phy-mode、delay、phy-handle;
  • Kconfig。

第二步:只验证控制面

  • platform probe;
  • clock/reset/power;
  • MDIO 稳定读取 PHY ID;
  • ethtool 能显示 PHY 能力。

第三步:验证链路状态机

  • 10/100/1000 自动协商;
  • 网线插拔 carrier 变化;
  • speed change 时 SoC 分频变化;
  • PHY IRQ 或轮询稳定。

第四步:最小数据路径

  • ARP、ping;
  • 临时关闭复杂 offload;
  • 观察 RX/TX packets、IRQ、NAPI、DMA error。

第五步:性能、温度和电源回归

  • 单向与双向 iperf3;
  • 小包、大包、并发流;
  • 长时间压力;
  • suspend/resume;
  • 网线热插拔和对端速率变化;
  • 逐项恢复 offload。

QEMU virt 无法真实模拟 RK3588 GRF、RGMII 时序、外置 RTL8211F、变压器和板级信号完整性。这类问题可以在 QEMU 中学习通用网络栈,却必须在真实开发板上完成最终验证。


十七、源码阅读地图

如果希望沿本文继续读代码,推荐按这个顺序:

  1. rk3588.dtsi:690rk3588s.dtsi:5500:SoC 资源;
  2. rk3588-firefly-port.dtsi:179:板级 RGMII 与 PHY;
  3. dwmac-rk.c:1881-2018:RK3588 interface/clock/delay;
  4. dwmac-rk.c:2774:平台 probe;
  5. stmmac_main.c:5061:通用 probe;
  6. stmmac_main.c:4789:netdev ops;
  7. stmmac_main.c:2901:open;
  8. stmmac_main.c:3468:TX;
  9. stmmac_main.c:4254:4071:IRQ/NAPI;
  10. stmmac_main.c:3862:RX;
  11. stmmac_mdio.c:406:MDIO bus;
  12. stmmac_main.c:865-1110:phylink MAC callbacks;
  13. realtek.c:181:RTL8211F internal delay;
  14. drivers/net/phy/phy.c:1197:PHY 状态机。

把这些入口串起来后,一个 SoC 网卡驱动的全景就会从“很多不相关的文件”变成四条清晰主线:

1
2
3
4
板级资源线:DTS → dwmac-rk → clock/reset/GRF/delay
链路控制线:MDIO → phylib → RTL8211F → phylink → MAC link up
发送数据线:skb → stmmac_xmit → TX DMA → TX clean
接收数据线:RX DMA → IRQ → NAPI → stmmac_rx → GRO → 协议栈

小结

RK3588 + RTL8211F 网卡驱动最值得掌握的,不是某个寄存器魔数,而是分层边界和状态闭环:

  • MAC 与 PHY 是两个硬件世界,RGMII 传数据,MDIO 做管理;
  • dwmac-rk 解决 SoC 接入问题,stmmac 解决通用 MAC/DMA 数据路径;
  • phylink 协调 MAC 与 PHY,Realtek driver 只实现 RTL8211F 差异;
  • probe 与 open 不同:前者注册设备,后者才启动 ring、IRQ、DMA 和 PHY;
  • IRQ 只负责快速调度,NAPI 批量处理数据
  • TX/RX 的核心是不丢失 descriptor、DMA mapping、page 和 skb 的所有权
  • RGMII delay 是板级时序设计,不是可以盲目复制的经验值
  • 排错要先判断层次:probe、MDIO、PHY link、RGMII、DMA/NAPI、协议栈,不能看到 link up 就跳过中间所有环节。

一旦用这套模型分析问题,link up but no traffic100M works but 1G failsRX stops after seconds 这类现象就不再是随机故障,而会变成可以逐层收集证据、逐层排除的工程问题。