欢迎光临
我们一直在努力

在 RK3588 上为 IgH 1.5.3 移植 Native stmmac:绕过 generic 收发路径

在 RK3588 上为 IgH 1.5.3 移植 Native stmmac:绕过 generic 收发路径

测试平台

  • 开发板:正点原子 ATK-DLRK3588
  • SoC:RK3588
  • Kernel:Rockchip Linux 5.10.160-rt89
  • EtherCAT Master:IgH EtherCAT Master 1.5.3
  • EtherCAT 网口:GMAC1,fe1c0000.ethernet
  • EtherCAT MAC:96:66:d2:da:00:62
  • 测试周期:1 ms
  • 测试从站:3 个

本文记录一次比较完整的 IgH Native 网卡驱动移植过程。

项目原本使用 ec_generic + stmmac。在已经完成 PREEMPT_RT、CPU 绑核、IRQ 亲和性和调度优化之后,我继续尝试把 EtherCAT 数据路径从 Linux 通用网络栈中拆出来,让 IgH 直接与 RK3588 的 stmmac 驱动交互。

最终 Native stmmac 成功运行,三个从站可以进入 OP,并完成了 generic/native 两种模式下的 A/B 测试。

Github: https://github.com/VictorJiaxinWang/rk3588-igh-native-stmmac


1. 为什么要做 Native stmmac

ec_generic 最大的优点是通用。

Linux 网卡驱动正常工作后,IgH 就可以通过 packet socket 使用这块网卡,不需要针对具体 MAC 驱动做适配。因此在 EtherCAT 主站开发早期,它非常适合验证协议栈、从站配置和应用程序。

但它的 EtherCAT 帧仍然要经过 Linux 通用网络路径。

Generic 发送路径大致如下:

ecrt_master_send()
-> ec_device_send()
-> ec_generic
-> kernel_sendmsg(packet socket)
-> Linux 网络发送路径
-> stmmac
-> TX DMA

接收方向:

RX DMA
-> stmmac IRQ
-> NAPI
-> skb
-> GRO / 协议分发
-> packet socket
-> ec_generic
-> ecdev_receive()

对于普通 Ethernet,这套机制非常合理;但 EtherCAT 的特点是专用网口、固定周期、小帧、高确定性,其中一些通用机制并不是必须的。

在做 Native 之前,这套系统已经完成:

  • PREEMPT_RT;
  • 周期线程绑定 CPU6;
  • SCHED_FIFO 80;
  • performance governor;
  • IRQ 亲和性优化;
  • stmmac TX 中断合并。

空闲状态下 wake_lateness 的平均值已经接近 2 μs。

所以这次优化的目标并不是继续改善 Linux 调度,而是缩短:

实时线程醒来

EtherCAT 收发

本周期处理结束

也就是主站线程醒来之后的软件执行路径。


2. Native stmmac 改变了什么

Native 的核心思路很直接:

让 IgH 直接使用 stmmac,不再经过 ec_generic 和 packet socket。

发送路径变为:

ecrt_master_send()
-> ec_device_send()
-> stmmac_xmit()
-> TX descriptor
-> DMA

接收路径变为:

ecrt_master_receive()
-> ec_device_poll()
-> ec_poll()
-> 检查 RX descriptor
-> ecdev_receive()
-> descriptor 重新交给 DMA

这样以后,周期线程在调用 ecrt_master_receive() 时直接检查 DMA ring,而不再依赖:

IRQ
NAPI
skb
GRO
packet socket

这里需要注意,“绕过 skb”并不是说 IgH 内部完全不存在 struct sk_buff。

IgH 自己仍然使用预分配的 skb 保存待发送 datagram。真正被绕开的,是 Linux 通用网络路径中的 packet socket、协议分发,以及 RX 周期中的 skb 分配和 GRO。

Native RX 还有一个特点:它不一定比 Generic RX 的 API 计时更短。

Generic 模式下,IRQ 和 NAPI 可能已经提前把数据放进队列;而 Native 模式是在周期线程中主动扫描 DMA descriptor。如果扫描时帧还没有完全返回,这部分等待时间会直接计入 RX phase。

因此可能出现:

TX 明显变快
RX 略微变慢
总 execution 仍然下降

后面的实际测试也正是这个结果。


3. 移植过程中最关键的几个问题

3.1 stmmac 必须尽量留在原 BSP 内核树中构建

最开始,我把 Rockchip stmmac 源码复制到 IgH 工程中,再作为外部模块编译。

模块可以正常链接,但装板以后两个 GMAC 都出现:

dwmac4_dma_reset() timeout

即使让 ec_master 不接管任何网卡,普通管理网口也无法正常工作。

最后改成保留 Rockchip SDK 原有目录:

kernel/drivers/net/ethernet/stmicro/stmmac/

只把修改后的:

stmmac_main.c
stmmac.h
ecdev.h

写回该目录,再使用内核原生 Kbuild:

make M=drivers/net/ethernet/stmicro/stmmac modules

问题解决。

这次踩坑之后,我对 BSP 驱动有一个比较明确的认识:

驱动源码相同,不代表换一套 Kbuild 环境后得到的二进制行为仍然相同。

厂商 BSP 往往依赖特定的编译宏、Kconfig、include 顺序和 platform glue。修改这类驱动时,最好尽量保留原来的构建上下文。


3.2 两个 GMAC 必须区分普通网络和 EtherCAT

当前 RK3588 板上有两个 GMAC:

用途设备树节点GenericNative
SSH / 普通网络 fe1b0000.ethernet eth0 eth0
EtherCAT fe1c0000.ethernet eth1 + ec_generic IgH 独占

Native stmmac 在基础 DMA、MDIO 和 phylink 初始化完成后调用:

priv->ecdev = ecdev_offer(ndev, ec_poll, THIS_MODULE);

IgH 根据配置的 MAC 判断是否接管该设备。

GMAC1 被 IgH 接管后,不再注册成普通 Linux 网口;GMAC0 没被接管,继续执行 register_netdev(),所以 SSH 仍然通过 eth0 工作。

最终形成:

GMAC0 -> Linux 普通网络
GMAC1 -> IgH Native EtherCAT

这也是双网口板卡上比较实用的一种方式。


3.3 模块加载顺序必须正确

Native stmmac 会调用:

ecdev_offer()
ecdev_open()
ecdev_close()
ecdev_receive()
ecdev_set_link()
ecdev_withdraw()

因此 ec_master 必须先存在。

正确加载关系是:

ec_master
-> stmmac
-> stmmac-platform
-> dwmac-rockchip

IgH 编译后会在仓库根目录生成:

Module.symvers

构建 stmmac 时需要传给 modpost:

KBUILD_EXTRA_SYMBOLS=~/ethercat-native-stmmac/Module.symvers

如果这个路径写错,ecdev_* 会全部报 undefined symbol。

内核配置方面,stmmac 也要编译成模块:

CONFIG_STMMAC_ETH=m
CONFIG_STMMAC_PLATFORM=m
CONFIG_DWMAC_ROCKCHIP=m

这样才能先加载 ec_master,再触发两个 GMAC probe。


4. RX 和 TX 路径如何处理

Native 驱动真正麻烦的地方,不是调用 ecdev_offer(),而是 DMA buffer 和 skb 的生命周期。

RX:尽量不在实时周期分配内存

DMA 清除 OWN 位后,CPU 才可以读取 descriptor 和数据。

Native RX 中的核心处理类似:

dma_rmb();

dma_sync_single_for_cpu(...);

ecdev_receive(
priv->ecdev,
page_address(buf->page),
len
);

dma_sync_single_for_device(...);

处理完成后继续保留原来的 page 和 DMA mapping,只把 descriptor 重新交回 DMA。

因此实时周期里不会反复执行:

alloc
free
map
unmap

也不会调用:

napi_alloc_skb()
skb_put()
napi_gro_receive()

这样可以尽量避免内存分配器和通用网络栈的慢路径进入实时线程。

当前 EtherCAT 帧可以完整放入一个 RX buffer。如果以后调整 jumbo frame、buffer 大小或者 descriptor layout,需要重新验证跨 descriptor 帧处理。

TX:不能释放 IgH 的 skb

IgH 的发送 skb 是可复用缓冲区。

普通 stmmac 的 TX clean 会认为 skb 属于网络栈,因此可能执行 skb free、BQL 和 netdev queue 管理。

Native 模式不能这么做。

IgH 仍然持有 skb 的所有权,所以 TX clean 只负责:

回收 DMA descriptor
解除 DMA mapping

不能释放 skb。

否则下一周期 IgH 再次使用这个缓冲区时,就可能产生 use-after-free。


5. 构建、部署与运行验证

完整构建最终被收敛到一个脚本:

LAB="$HOME/native-package-in-tree"
SDK="$HOME/rk3588_linux_sdk"
IGH="$HOME/ethercat-native-stmmac"
OUT="$HOME/native-stmmac-in-tree-artifacts"

"$LAB/tools/build_in_tree_native_stmmac_vm.sh" \\
"$SDK" \\
"$IGH" \\
"$OUT"

最终生成:

ec_master.ko
stmmac.ko
stmmac-platform.ko
dwmac-rockchip.ko

检查 stmmac.ko:

modinfo stmmac.ko

本次结果:

depends: phylink,ec_master
vermagic: 5.10.160-rt89 SMP preempt_rt mod_unload aarch64

部署时没有直接覆盖旧环境,而是保留了完整回退机制。

先验证包:

./tools/install_native_artifacts.sh \\
/userdata/native-stmmac-in-tree-deployment \\
–verify-only

确认无误后再执行:

./tools/install_native_artifacts.sh \\
/userdata/native-stmmac-in-tree-deployment \\
–apply

./tools/select_stmmac_mode.sh native –apply

sync
reboot

另外增加了一个 fallback 机制:如果启动后 eth0 在限定时间内无法正常工作,就恢复 stock 模块。

对于依赖 SSH 调试的开发板,这一点很有必要。网卡驱动一旦出问题,没有串口的话很容易直接失联。

Native 启动成功后,可以从以下信息确认驱动路径:

stmmac depends: phylink,ec_master

ec_generic 未加载

eth0 UP

EtherCAT:
Accepting 96:66:D2:DA:00:62 as main device

fe1c0000.ethernet:
claimed by IgH native EtherCAT master

同时三个从站能够进入 OP。

至此,Native 收发路径已经真正跑通。


6. Generic 与 Native A/B 测试

为了避免不同内核、不同程序导致结果失真,A/B 测试使用:

  • 同一块 RK3588;
  • 同一个 5.10.160-rt89 PREEMPT_RT 内核;
  • 同一份测试程序;
  • 三个相同从站;
  • 1 ms EtherCAT 周期;
  • CPU6;
  • SCHED_FIFO 80;
  • 相同 IRQ 配置。

唯一变化是网卡驱动路径。

Generic:

stock stmmac
+
ec_generic

Native:

ec_master
+
native stmmac

测试重点记录四个时间:

指标含义
wake_lateness 周期线程实际唤醒比计划晚多久
receive_phase RX、domain process 等接收阶段
send_phase domain queue 到 ecrt_master_send() 完成
execution 线程醒来到本周期发送完成

同时检查:

deadline miss
WKC incomplete

每组测试运行:

30 s × 3

因此每组约有:

90,003 cycles

压力测试时,在其他 CPU 上加入计算和内存负载。

最终结果如下,时间单位为 μs:

负载驱动wake mean/P99/maxRX mean/P99/maxTX mean/P99/maxexecution mean/P99/maxmissedWKC bad
空闲 generic 1.847 / 3 / 4.764 1.953 / 3 / 6.711 3.310 / 4 / 7.878 5.529 / 7 / 14.006 0 5
空闲 native 2.020 / 3 / 7.184 2.212 / 3 / 5.544 1.810 / 3 / 2.918 4.275 / 5 / 7.878 0 0
压力 generic 1.838 / 2.667 / 12.717 1.973 / 3 / 9.337 3.101 / 5 / 16.048 5.364 / 7 / 21.592 0 0
压力 native 1.860 / 3 / 8.508 2.328 / 4 / 12.255 2.015 / 3 / 4.960 4.620 / 6.667 / 16.923 0 0

P99 为直方图统计得到的近似值,max 只是有限测试时间中的最大观测值,不能直接等同于 WCET。


7. 测试结果说明了什么

结果里最明显的是 TX。

空闲状态:

TX mean
3.310 μs -> 1.810 μs
下降 45.3%

execution:

5.529 μs -> 4.275 μs
下降 22.7%

P99:

7 μs -> 5 μs

压力状态下:

TX mean
3.101 μs -> 2.015 μs
下降约 35%

execution:

5.364 μs -> 4.620 μs
下降约 13.9%

最坏 execution:

21.592 μs -> 16.923 μs

结果基本符合预期:Native 最大的收益来自 TX。

Generic TX 需要经过 packet socket 和 Linux 通用发送路径,而 Native 可以直接进入 stmmac 和 DMA,所以这一段缩短最明显。

但 RX 的结果正好相反。

空闲状态:

1.953 μs -> 2.212 μs

压力状态:

1.973 μs -> 2.328 μs

Native RX 平均时间反而增加了约 13%~18%。

原因就是前面提到的同步 DMA poll。

Generic 模式下,IRQ/NAPI 可能已经提前处理了部分数据;Native 模式则由周期线程主动检查 DMA ring,所以一部分等待时间被直接计入 receive phase。

不过 TX 节省的时间明显大于 RX 增加的时间,所以最终 execution 仍然下降。

另一个值得注意的是 wake_lateness 基本没有改善。

这也是合理的,因为它主要由:

Linux timer
scheduler
PREEMPT_RT
CPU isolation
IRQ 干扰

决定。

Native 驱动优化的是线程醒来之后的 EtherCAT 收发,并不是 Linux 调度器。

因此不能用“wake mean 有没有继续低于 2 μs”来评价 Native 是否有效。


8. 目前结论和验证边界

这次移植已经完成:

Native stmmac 修改
内核树内构建
RK3588 部署
双 GMAC 共存
IgH 接管
三从站 OP
Generic / Native A/B 测试

从当前测试数据来看,Native stmmac 成功绕过 ec_generic 和 packet socket 后,收益主要集中在发送路径。

三个从站、1 ms 周期下:

  • TX mean 下降约 35%~45%;
  • execution mean 下降约 14%~23%;
  • 压力状态最坏 execution 从 21.592 μs 降至 16.923 μs;
  • 所有测试均没有 deadline miss。

同时也发现两个不能忽略的问题。

第一,Generic 空闲第一轮出现了 5 个 WKC incomplete,其余 Generic 和所有 Native 测试均为 0。

目前样本不足,不能据此认为 Native 消除了 WKC 异常,只能作为后续长时间测试需要关注的现象。

第二,实验期间曾出现两次 PREEMPT_RT Kernel Oops,一次处于 Native 环境,一次已经回退 Generic。由于当时没有获取完整调用栈,目前无法归因到 Native stmmac。

因此现阶段更准确的结论是:

Native stmmac 已经证明可以降低 RK3588 + IgH 主站线程醒来之后的网络发送开销,并改善整个 EtherCAT 处理阶段的平均时间和部分长尾指标,但目前的测试规模还不足以证明产品级长期可靠性。

后续如果真正准备进入产品环境,还需要继续验证:

  • 小时级、天级压力测试;
  • EtherCAT 反复启停;
  • 网线断开和恢复;
  • 从站掉电重连;
  • 主站异常重启;
  • 不同 PDO 数据规模;
  • 500 Hz、1 kHz 以及更高频率;
  • CPU 和内存高负载;
  • Kernel Oops 完整串口和 pstore 抓取。

对于实时 EtherCAT 主站,平均值只是一个指标。

最终真正决定系统是否成熟的,还是:

deadline
WKC
P99 / max
链路恢复
异常恢复
长期稳定性

Native 能不能用于产品,也应该由这些数据来决定,而不是仅仅因为它比 ec_generic 的路径更短。

赞(0)
未经允许不得转载:171主机测评 » 在 RK3588 上为 IgH 1.5.3 移植 Native stmmac:绕过 generic 收发路径
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址