Skip to content

K3 GMAC 网卡驱动调试教训

0. 背景

K3 COM260 开发板上,我需要让 StarryOS(基于 ArceOS 的通用内核)的以太网口可用, 以支持 DHCP、网络文件系统等服务。硬件为进迭时空 K3 SoC,搭载 DWMAC 5.10a 以太网 MAC IP + RTL8211F PHY,通过 RGMII 接口连接。

U-Boot 阶段 TFTP 工作正常,证明硬件没问题。但自己写的 GMAC 驱动在 StarryOS 上长期 无法工作:PHY 自协商完成、链路 UP,但 DHCP 始终超时。这个驱动我反复调试了跨多个 session、数十次真板测试,最终定位到三个独立的寄存器定义/地址错误,逐一修复后 驱动终于可用——DHCP 成功、ping 成功。

本文是我对这段调试经历的复盘:走过的弯路、最终找到的三个 bug、以及由此沉淀的教训。


1. 症状

驱动初始化日志显示一切"正常":

k3-gmac: PHY0 link state: up=true speed=1000Mbps duplex=full
k3-gmac: after start_dma: TX_ST=true chan_stat=0x84
k3-gmac: post-init: GMAC_CONFIG=0x00072203 (RE=true TE=true DM=true ...)
         TX_CTRL=0x00080011 (ST=true OSP=true) RX_CTRL=0x00081001 (SR=true RBSZ=2048)
k3-gmac: TX#0 desc[0] des3=0xb0000131(OWN=true) | tail wrote=0xfcf64040 readback=0xfcf64040
         cur_tx=0xfcf64000 chan_stat=0x84(TBU=true FBE=false) dbg0=0x1300

我在驱动里加了诊断日志,关键信息是:

  • TX 描述符 OWN=1(CPU 已交由 DMA),但 DMA 从不清除 OWN
  • cur_tx(DMA 当前读取位置)始终停在 ring base,从不前进
  • dbg0=0x1300:TX DMA 引擎状态机处于 Idle(状态 0),从未启动
  • RX 路径在特定配置下能收到包(说明 DMA AXI 总线、EAME、地址解码都正常)

核心矛盾:TX_CTRL 的 ST=1(启动位已设),tail pointer 已正确写入,描述符 OWN=1, 但 TX DMA 引擎就是不读描述符。


2. 调试历程与发现的三个 Bug

2.1 探索阶段:大量试错排除假根因

早期我走了很多弯路,靠"推测哪个寄存器可能有问题 → 改 → 上板测"的方式排查, 测了大量假设:

假设验证方式结论
DMA 软复位缺失导致引擎锁死对照 U-Boot eqos_start,恢复 reset_dma软复位成功,但 TX 仍不工作(非根因,但保留)
DMA 软复位导致引擎锁死移除软复位无变化(非根因)
update()(RMW)保留了 U-Boot 残留位改为绝对写(write让 RX 工作了(消除了残留),但 TX 仍不工作
EAME 未启用(>4GB DMA 寻址)检查 DMA_SYS_BUS_MODE bit11已启用,非根因
DMA_MASK=u32::MAX(32 位)K3 无 <4GB DRAM,改回 u64::MAX修复了 NoMemory,非 TX 根因
DMA_AXI_BUS_MODE 错误偏移合并到 DMA_SYS_BUS_MODE修正了寄存器布局,非 TX 根因
PBL=0 无突发加 PBL=8正确修复,非 TX 根因
DMA_CHAN_CONTROL(PBLX8+DSL)未写加绝对写正确修复,非 TX 根因
TX buffer cache 未 flush检查 prepare_for_device已 flush,非根因
Zicbom CMO 未真正执行追踪 feature 链已启用,非根因
描述符格式错误对照 dwmac4_descs.h 逐位检查格式正确,非根因
RISC-V IOMMU 阻塞检查 DTS iommus 属性GMAC 无 IOMMU 绑定,pass-through 模式,非根因

十几个假设全部排除了。这个阶段我反复上板测试,效率很低,而且每次测试结果都是 "TX 还是不工作",无法区分到底是哪个改动起了作用。

转折点:我决定不再猜测,而是让 AI 助手做一次 U-Boot eqos_start + Linux stmmac_hw_setup vs 我的 init_hardware逐行寄存器写入对照,系统性地列出 每一个差异。正是这次穷尽的对照找到了最终的 bug。

2.2 Bug #1:MTL_OP_MODE_TSF 位定义错误

rust
// 我的错误定义:
pub const MTL_OP_MODE_TSF: u32 = 1 << 0;  // BIT0 是 FTQ(Flush TX Queue)!

// 正确(对照 U-Boot BIT(1) + Linux BIT(1)):
pub const MTL_OP_MODE_TSF: u32 = 1 << 1;  // BIT1 才是 TSF(Store-and-Forward)

BIT0 在 MTL TX 操作模式寄存器中是 FTQ(Flush TX Queue)——我写 1 进去等于持续 flush TX 队列,DMA 写入 TX FIFO 的数据被立即丢弃。

这个 bug 是逐行对照 U-Boot dwc_eth_qos.h:114-115 时发现的:

c
#define EQOS_MTL_TXQ0_OPERATION_MODE_TSF    BIT(1)
#define EQOS_MTL_TXQ0_OPERATION_MODE_FTQ    BIT(0)

但修了它 TX 还是不工作。它是加重因素,不是终极根因。

2.3 Bug #2:DMA 软复位缺失(次要修复)

DMA 软复位(DMA_BUS_MODE bit0,写 1 后硬件自清)能重启 DMA 内部状态机。我之前 错误地移除了它——当时误以为"U-Boot 不做软复位",实际上 U-Boot 在 eqos_start 最开头就做(line 767-773),Linux 的 stmmac_hw_setup 也是。

加回软复位后,日志确认 DMA soft reset completed after 257773 iterations(~258ms), 但 TX 仍然不工作。这个修复对照参考实现是正确的,但不是 TX 不工作的根因。

2.4 Bug #3:MTL_CHAN_BASE_ADDR 错误(终极根因)⭐

rust
// 我的错误定义:
pub const MTL_CHAN_BASE_ADDR: u32 = 0xc00;

// 正确(对照 U-Boot EQOS_MTL_REGS_BASE + Linux MTL_CHAN_BASE_ADDR):
pub const MTL_CHAN_BASE_ADDR: u32 = 0xd00;

这是终极根因。我所有的 MTL 寄存器写入(TX 队列使能 TSF|TXQEN|TQS、RX 队列 RSF|RQS 等)都写到了错误地址——0xc00 而非 0xd00。MTL TX 队列从未被使能, TXQEN 位写到了不存在的地址,硬件 MTL TX 队列保持默认的禁用状态。

怎么发现的:修了 TSF 位定义后 TX 还是不工作,逐行对照报告确认"寄存器初始化 序列在所有控制 TX DMA fetch 的位上都是正确的",迫使我转向 MTL 层。最终在检查 MTL TX debug 寄存器偏移时,我注意到 U-Boot 定义 EQOS_MTL_REGS_BASE = 0xd00dwc_eth_qos.h:96),而我的代码用 0xc00——差了整整 0x100。

为什么 TX 不工作而 RX 工作:DWMAC4 硬件中,RX 队列默认使能(复位后 RXQ0EN 默认值非零),TX 队列默认禁用(复位后 TXQEN=0)。RX 不依赖我写错的 MTL 配置 (默认就开了),TX 必须显式使能但我写错了地址。这完美解释了一直困扰我的 "RX 工作、TX 不工作"的不对称现象。

DMA 引擎为何 Idle:DMA 引擎 fetch 描述符后,需要把数据写入 MTL TX FIFO。但 MTL TX 队列未被使能,MTL 拒绝接收 DMA 数据。DMA 的写入路径被阻塞,fetch engine 因此停滞在 Idle 状态——cur_tx 从不前进,dbg0 TX state=0。

2.5 修复确认

三个 Bug 修复后,真板测试结果:

k3-gmac: TX#0 desc[0] ... cur_tx=0xfcf64040  ← cur_tx 前进了!
eth0: DHCP bound: 192.168.1.100/24           ← DHCP 成功!
ping 192.168.1.1: 64 bytes, time=0.5ms       ← ping 成功!

驱动终于工作了。


3. 为什么调试这么久?——教训总结

整个调试过程跨越了多个 session、数十次真板测试。反思为何耗时如此之长:

教训 1:不要在没有参考源码的情况下猜测硬件行为

早期我大量依赖"推测"——猜哪个寄存器可能有问题,改了就上板测。这导致了大量的 试错循环(见 §2.1 的表格,列出了十几个被排除的假根因),每次真板测试 5-10 分钟, 效率极低。

正确做法:第一时间找到参考实现(U-Boot / Linux 源码),逐行对照。本次调试的 转折点正是让 AI 助手做 U-Boot eqos_start vs 我的 init_hardware 的逐行寄存器 写入对照。这种系统性的 diff 比任何猜测都有效。

对于 SoC 外设驱动移植,U-Boot 驱动(尤其是同一厂商的 SDK)是最可靠的参考,因为 U-Boot 初始化序列通常最精简、最直接,没有 Linux 的抽象层(stmmac_ops 等)。 先对照 U-Boot,再对照 Linux

教训 2:寄存器地址和位定义必须逐个对照源码,不能凭记忆/猜测

三个 Bug 都是常量定义错误MTL_CHAN_BASE_ADDR 差 0x100、MTL_OP_MODE_TSF 差 1 位)。这类错误编译器无法发现,运行时的症状又很模糊("DMA 不工作"),极难定位。

正确做法:新建驱动时,每一个寄存器偏移量和位定义都应该从参考头文件直接对照 (dwmac4.hdwmac4_dma.hdwc_eth_qos.h),而不是凭记忆或"差不多"写。建一个 对照表:

| 我的常量 | 值 | Linux/U-Boot 对应 | 参考值 | 一致? |
|-----------|-----|-------------------|--------|--------|
| MTL_CHAN_BASE_ADDR | 0xc00 | EQOS_MTL_REGS_BASE | 0xd00 | ❌ |
| MTL_OP_MODE_TSF | BIT(0) | EQOS_..._TSF | BIT(1) | ❌ |

在驱动开发的寄存器定义阶段就做这种对照,能避免后期数天的调试。

教训 3:不对称现象(部分工作)是重要线索,要深入解释

"RX 工作但 TX 不工作"这个现象在调试早期就出现了,但我没有深入追问"为什么"。 如果早一点追问"什么硬件机制能让 RX 默认工作但 TX 不工作",可能更快定位到 MTL 队列 使能的问题。

正确做法:当观察到不对称现象(一个方向工作、另一个不工作)时,列出所有 "RX 和 TX 的不同点",包括:

  • 硬件默认状态(RX 队列默认开 vs TX 队列默认关)
  • 配置依赖(RX 需要什么、TX 需要什么)
  • 代码路径差异(submit_rx 有 SR doorbell,submit_tx 没有)

不对称现象通常指向配置差异或默认值差异,值得专门深入。

教训 4:诊断日志要包含"引擎状态"而不只是"配置状态"

早期诊断日志主要打印配置寄存器的值(TX_CTRL、CHAN_CTRL 等),这些值看起来都正确, 但无法反映 DMA 引擎的实际运行状态。

正确做法:加入 DMA 引擎状态寄存器的诊断:

  • DMA_DEBUG_STATUS_0(0x100c):TX/RX DMA 引擎状态机状态
  • DMA_CHAN_CUR_TX_DESC(0x1144):DMA 当前正在处理的描述符地址
  • MTL_CHAN_TX_DEBUG:MTL TX 队列状态

这些寄存器直接反映引擎"在做什么"而非"配了什么"。dbg0=0x1300(TX state=Idle)和 cur_tx=base(从不前进)是定位问题的关键信号。

诊断日志应该回答"引擎为什么不工作",而不只是"配置是否正确"。

教训 5:逐行对照时要警惕确认偏差

人眼对照源码容易"看到自己期望看到的"——确认偏差。我多次"确认"过寄存器定义"正确", 但实际上 MTL base 地址和 TSF 位都是错的。

正确做法:让 AI 助手做自动化的逐行对照,输出结构化的 diff 表格。机器不受确认 偏差影响,会忠实地报告每一个差异。对于复杂的源码对照任务,穷尽的 diff 比人工 对照更可靠。

教训 6:真板测试成本高,改代码前要做足推演

每次真板测试需要刷写固件、重启开发板、观察串口日志,耗时 5-10 分钟。大量"试错式" 修改导致测试效率极低——这也是我在调试中途明确要求"测试之前严格进行代码推演, 保证测试有必要的进展"的原因。

正确做法:每次提交测试前,应该明确:

  1. 当前修改的假设是什么
  2. 这个假设有什么源码证据
  3. 如果假设错误,诊断日志能否区分

尊重真板测试的成本,把调试的"不确定性"消化在代码推演阶段,而不是测试阶段。


4. 调试方法论沉淀

基于本次经验,我总结出 SoC 外设驱动调试的标准流程:

第一阶段:参照源码(动手前)

  1. 找到参考实现:U-Boot SDK 驱动 > Linux mainline 驱动 > 数据手册
  2. 逐行对照初始化序列:列出每一个寄存器写入的对照表
  3. 特别关注常量定义:寄存器偏移量、位位置、位掩码——这些是最容易出错且最难发现的地方

第二阶段:诊断设计(第一次测试前)

  1. 打印引擎状态:不只是配置寄存器,还要有状态机状态、当前指针位置
  2. 打印不对称信息:如果 TX 和 RX 路径类似,对比两者的诊断输出
  3. 设计可区分的诊断:每次测试应该能排除至少一个假设

第三阶段:迭代调试

  1. 一次只改一个变量:不要同时改多个地方
  2. 解释所有现象:特别是"部分工作"的不对称现象
  3. 用穷尽对照代替猜测:当人眼对照无法定位时,做自动化逐行 diff

分支完整代码分析和工作内容总结见周报 《第三十三周:K3 GMAC 网卡驱动调试成功 + AMP IPC 链路修复》


5. 附录:关键寄存器速查

DMA 块(基址偏移 0x1000)

寄存器偏移说明
DMA_BUS_MODE0x1000bit0=SWR(软复位)
DMA_SYS_BUS_MODE0x1004EAME(bit11) + BLEN + OSR
DMA_DEBUG_STATUS_00x100cbits[3:0]=TX state, [19:16]=RX state

DMA Channel 0(基址 0x1100,stride 0x80)

寄存器偏移说明
DMA_CHAN_CONTROL0x1100PBLX8(bit16) + DSL(bits[20:18])
DMA_CHAN_TX_CONTROL0x1104ST(bit0) + OSP(bit4) + PBL(bits[21:16])
DMA_CHAN_RX_CONTROL0x1108SR(bit0) + RBSZ(bits[14:1]) + RXPBL(bits[21:16])
DMA_CHAN_TX_BASE_HI0x1110描述符环基址高 32 位
DMA_CHAN_TX_BASE0x1114描述符环基址低 32 位
DMA_CHAN_RX_BASE_HI0x1118
DMA_CHAN_RX_BASE0x111c
DMA_CHAN_TX_END0x1120TX 尾指针(doorbell)
DMA_CHAN_RX_END0x1128RX 尾指针
DMA_CHAN_TX_RING_LEN0x112cTX 环长度(N-1)
DMA_CHAN_RX_RING_LEN0x1130RX 环长度(N-1)
DMA_CHAN_INTR_ENA0x1134中断使能
DMA_CHAN_CUR_TX_DESC0x1144DMA 当前 TX 描述符(只读)
DMA_CHAN_STATUS0x1160TI(bit0) TBU(bit2) RI(bit6) RBU(bit7)

MTL Channel 0(基址 0xd00,stride 0x40)

寄存器偏移说明
MTL_CHAN_TX_OP_MODE0xd00TSF(bit1) FTQ(bit0!) TXQEN(bits[3:2]) TQS(bits[24:16])
MTL_CHAN_TX_DEBUG0xd08TX 队列状态
MTL_CHAN_RX_OP_MODE0xd30RSF(bit5) RQS(bits[29:20])

MAC(基址 0x0000)

寄存器偏移说明
GMAC_CONFIG0x0000RE(bit0) TE(bit1) DM(bit13) FES(bit14) PS(bit15)
GMAC_PACKET_FILTER0x0008PR(bit0) PM(bit1)
GMAC_RXQ_CTRL00x00a0RXQ0EN(bits[1:0]),DCB=2
GMAC_ADDR_HIGH00x0300MAC 地址高 16 位 + AE(bit31)
GMAC_ADDR_LOW00x0304MAC 地址低 32 位
GMAC_VERSION0x0310synopsys_id(0x54 = DWMAC 5.10a)
GMAC_HW_FEATURE0-30x0358+硬件特性(FIFO 大小、地址宽度等)

6. 参考源码索引

参考源路径关键函数
U-Bootdrivers/net/dwc_eth_qos.ceqos_start()(初始化)、eqos_send()(TX)、eqos_recv()(RX)
U-Bootdrivers/net/dwc_eth_qos.h寄存器定义 + 位定义
Linuxdrivers/net/ethernet/stmicro/stmmac/stmmac_main.cstmmac_hw_setup()stmmac_xmit()
Linuxdrivers/net/ethernet/stmicro/stmmac/dwmac4_dma.cdwmac4_dma_init()dwmac4_dma_reset()
Linuxdrivers/net/ethernet/stmicro/stmmac/dwmac4_lib.cdwmac4_dma_start_tx()dwmac4_set_tx_tail_ptr()
Linuxdrivers/net/ethernet/stmicro/stmmac/dwmac4_descs.cdwmac4_rd_prepare_tx_desc()dwmac4_set_tx_owner()
Linuxdrivers/net/ethernet/stmicro/stmmac/dwmac4.hMAC/MTL 寄存器定义
Linuxdrivers/net/ethernet/stmicro/stmmac/dwmac4_dma.hDMA 寄存器 + 位定义

文档完成于 2026-08-14。基于 K3 COM260 开发板真板调试。