KSZ9031_Zynq_lwIP_PHY适配指南

平台:Zynq-7000 + KSZ9031RNX | 协议栈:Xilinx lwip141_v2_0
修改文件:xemacpsif_physpeed.c(PS GEM)+ xaxiemacif_physpeed.c(AXI Ethernet)
在 Xilinx 官方 lwIP 库中新增一款 PHY 的完整流程,包含两个版本 API 的差异和一项反复验证过的禁止事项。


一、为什么需要手动适配

Xilinx SDK 自带的 lwip141_v2_0 库支持 Marvell 88E1111 和 TI DP83867 两款 PHY,通过读取 PHY 的 IEEE 标识寄存器自动选择对应的初始化函数。但 KSZ9031(Micrel/Microchip)不在官方支持列表中。

如果不在 xemacpsif_physpeed.c 中显式添加 KSZ9031 的识别和初始化代码,lwIP 初始化时将匹配不到已知 PHY 类型,退化到 Marvel 的默认路径。Marvell 和 KSZ9031 的寄存器布局差异显著——特别是 RGMII 延迟的配置和速率检测方式——导致链路行为不可预测。

本文记录在 Xilinx lwip141_v2_0 基线版本上添加 KSZ9031 支持所需的全部修改,以及调试过程中发现的一条关键限制。


二、快速参考:完整代码修改

以下给出两份文件的完整修改内容。每份文件涉及三处改动:宏定义、初始化函数、PHY 识别分支。可直接复制到对应位置。

文件一:xemacpsif_physpeed.c(PS GEM,Zynq 内置 MAC)

改动 A — 文件头部添加宏定义(放在 #include 区域之后):

// --- Add phy register for ksz9031 ---
#define MICREL_PHY_IDENTIFIER       0x22
#define MICREL_PHY_KSZ9031_MODEL    0x220
// --- Add end ---

改动 B — 添加初始化函数(放在 get_IEEE_phy_speed() 之前,与其他 get_xxx_phy_speed() 函数并列):

// --- Add get phy speed function for ksz9031 ---
static u32_t get_phy_speed_ksz9031(XEmacPs *xemacpsp, u32_t phy_addr)
{
    u16_t temp;
    u16_t control;
    u16_t status;
    u16_t status_speed;
    u32_t timeout_counter = 0;

    xil_printf("Start PHY autonegotiation \r\n");

    /* Page 2: RGMII clock delay (1.8ns on TXC+RXC) */
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 2);
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_MAC, &control);
    control |= IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK;  // 0x0030
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_MAC, control);

    /* Page 0: AN advertisement — 100M, 10M, flow control */
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);
    control |= IEEE_ASYMMETRIC_PAUSE_MASK;
    control |= IEEE_PAUSE_MASK;
    control |= ADVERTISE_100;
    control |= ADVERTISE_10;
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);

    /* 1000BASE-T advertisement */
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET, &control);
    control |= ADVERTISE_1000;
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET, control);

    /* Copper specific: gigabit attempts=7, downshift enable */
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_COPPER_SPECIFIC_CONTROL_REG, &control);
    control |= (7 << 12);    // gigabit attempts = 7
    control |= (1 << 11);    // downshift enable
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_COPPER_SPECIFIC_CONTROL_REG, control);

    /* Start autonegotiation (bit 12 + bit 9). DO NOT write bit 15 (CTRL_RESET_MASK). */
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
    control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;
    control |= IEEE_STAT_AUTONEGOTIATE_RESTART;
    XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

    /* Wait for autonegotiation complete (30s timeout) */
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
    xil_printf("Waiting for PHY to complete autonegotiation.\r\n");
    while (!(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)) {
        sleep(1);
        XEmacPs_PhyRead(xemacpsp, phy_addr,
                        IEEE_COPPER_SPECIFIC_STATUS_REG_2, &temp);
        timeout_counter++;
        if (timeout_counter == 30) {
            xil_printf("Auto negotiation error \r\n");
            return XST_FAILURE;
        }
        XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
    }
    xil_printf("autonegotiation complete \r\n");

    /* Read negotiated speed from reg 0x1F */
    XEmacPs_PhyRead(xemacpsp, phy_addr, 0x1F, &status_speed);
    if      (status_speed & 0x40) return 1000;
    else if (status_speed & 0x20) return 100;
    else if (status_speed & 0x10) return 10;
    else                           return 0;
    return XST_SUCCESS;
}
// --- Add end ---

改动 C — get_IEEE_phy_speed() 中添加 KSZ9031 分支(插在现有 PHY 分支中,必须在 Marvell 默认分支之前):

static u32_t get_IEEE_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
    u16_t phy_identity;
    u32_t RetStatus;

    XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG, &phy_identity);

    // === Add for ksz9031 === //
    if (phy_identity == MICREL_PHY_IDENTIFIER)
    {
        xil_printf("Phy %d is KSZ9031\n\r", phy_addr);
        RetStatus = get_phy_speed_ksz9031(xemacpsp, phy_addr);
    }
    // === Add end === //
    else if (phy_identity == PHY_TI_IDENTIFIER)
    {
        RetStatus = get_TI_phy_speed(xemacpsp, phy_addr);
    }
    else if (...)  // 其他已有分支
    {
        ...
    }
    else
    {
        RetStatus = get_Marvell_phy_speed(xemacpsp, phy_addr);
    }

    return RetStatus;
}

文件二:xaxiemacif_physpeed.c(AXI Ethernet,PL IP 核)

改动位置完全相同,仅 API 前缀不同:XEmacPs_PhyWriteXAxiEthernet_PhyWriteXEmacPs *XAxiEthernet *

改动 A — 宏定义(与文件一完全相同):

#define MICREL_PHY_IDENTIFIER       0x22
#define MICREL_PHY_KSZ9031_MODEL    0x220

改动 B — 初始化函数:

// --- Add get phy speed function for ksz9031 ---
unsigned int get_phy_speed_ksz9031(XAxiEthernet *xaxiemacp, u32 phy_addr)
{
    u16 control;
    u16 status;
    u16 partner_capabilities;

    xil_printf("Start PHY autonegotiation \r\n");

    /* Page 2: RGMII clock delay — clear bit 4 */
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 2);
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_CONTROL_REG_MAC, &control);
    control &= ~(0x10);
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_CONTROL_REG_MAC, control);

    /* Page 0: AN advertisement */
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);
    control |= IEEE_ASYMMETRIC_PAUSE_MASK;
    control |= IEEE_PAUSE_MASK;
    control |= ADVERTISE_100;
    control |= ADVERTISE_10;
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);

    /* 1000BASE-T advertisement */
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET, &control);
    control |= ADVERTISE_1000;
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET, control);

    /* Copper specific */
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_COPPER_SPECIFIC_CONTROL_REG, &control);
    control |= (7 << 12);   /* max number of gigabit attempts */
    control |= (1 << 11);   /* enable downshift */
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_COPPER_SPECIFIC_CONTROL_REG, control);

    /* Start autonegotiation. DO NOT write bit 15 (CTRL_RESET_MASK) here. */
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
    control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;
    control |= IEEE_STAT_AUTONEGOTIATE_RESTART;
    XAxiEthernet_PhyWrite(xaxiemacp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

    /* Wait for autonegotiation complete */
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
    xil_printf("Waiting for PHY to complete autonegotiation.\r\n");
    while (!(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)) {
        sleep(1);
        XAxiEthernet_PhyRead(xaxiemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
    }
    xil_printf("autonegotiation complete \r\n");

    /* Read negotiated speed */
    XAxiEthernet_PhyRead(xaxiemacp, phy_addr, 0x1F, &partner_capabilities);
    if      (partner_capabilities & 0x40) return 1000;
    else if (partner_capabilities & 0x20) return 100;
    else if (partner_capabilities & 0x10) return 10;
    else                                   return 0;
}
// --- Add end ---

⚠️ 以上给出的 AXI 版本已经是修正后的代码(CTRL_RESET_MASK 已删除)。如果你是以 BSP 中现有的 xaxiemacif_physpeed.c 为基线手动修改,注意原文件中启动自协商后还有一段 control |= IEEE_CTRL_RESET_MASK 的软复位代码(约 10 行),必须一并删除,否则所有配置会被清零。详见第九节对照表。

改动 C — get_IEEE_phy_speed() 中添加分支(与文件一完全相同,仅函数调用参数类型不同):

    // === Add for ksz9031 === //
    else if (phy_identifier == MICREL_PHY_IDENTIFIER)
    {
        xil_printf("Phy %d is KSZ9031\n\r", phy_addr);
        return get_phy_speed_ksz9031(xaxiemacp, phy_addr);
    }
    // === Add end === //

速率切换

函数体中两行代码控制千兆/百兆行为,可按需组合:

control |= ADVERTISE_1000;      // 广播千兆 → 千兆调试用
// control &= ~ADVERTISE_1000;  // 禁止千兆 → 仅 100M(调试时先验证通路)

control |= (7 << 12);           // GT 尝试 7 次 + 降速:链路差时自动退到 100M
// control |= (7 << 12) 和 (1 << 11) 都注释 → 禁用降速(诊断用)

三、PHY 识别机制

lwIP 的 PHY 检测入口是 get_IEEE_phy_speed()。它的逻辑非常直接:

  1. 通过 MDIO 读 PHY 的寄存器 2(PHY Identifier 1),获取厂商标识
  2. 根据厂商标识跳转到对应的初始化函数——Marvell、TI、或我们即将添加的 Micrel
  3. 初始化函数完成配置后返回协商速率

KSZ9031 的寄存器 2 读出为 0x0022,这是 Micrel 从 IEEE 注册管理机构获取的 Organizationally Unique Identifier 的高 6 位。至于具体型号(KSZ9021/9031 等),需要进一步读寄存器 3 的 bit 9:4,其掩码为 0x0220

整个适配工作的核心就是两步:定义标识宏,然后插入识别分支和对应的初始化函数


四、修改一:添加标识宏

在文件头部 #include 区域之后、ADVERTISE_10HALF 等寄存器宏之前,添加两行定义:

#define MICREL_PHY_IDENTIFIER       0x22
#define MICREL_PHY_KSZ9031_MODEL    0x220

这两行放在其他 PHY 的同类宏旁边(如 PHY_TI_IDENTIFIER 0x2000),保持代码风格一致。MICREL_PHY_KSZ9031_MODEL 在当前的识别逻辑中仅匹配 OUI 就跳转,未使用型号进一步区分——但保留此宏为后续可能的多型号共存预留空间。


五、修改二:KSZ9031 初始化函数

这是整个适配的核心。函数命名为 get_phy_speed_ksz9031(),放置在 get_IEEE_phy_speed() 之前,与其他 PHY 的函数并列。

以下逐段解释每一步的设计意图。

4.1 RGMII 时钟延迟(Page 2)

XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 2);
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_MAC, &control);
control |= IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK;  // 0x0030
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_MAC, control);

KSZ9031 的寄存器空间分为多个"页面"。标准 IEEE 802.3 寄存器位于 Page 0,厂商扩展寄存器(包括 RGMII Pad Skew 配置)位于 Page 2。通过写寄存器 13(IEEE_PAGE_ADDRESS_REGISTER)切换页面。

IEEE_CONTROL_REG_MAC(寄存器 21)是 KSZ9031 在 Page 2 下的 MAC 控制寄存器。向其中写入 IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK(0x0030,即 bit 4 和 bit 5 同时置位),使能 PHY 内部对 TXC 和 RXC 各添加约 1.8ns 的延迟。

RGMII 规范要求在时钟和数据之间引入约 2ns 的偏移,以确保接收端的建立/保持时间。这个延迟可以由 MAC 侧(FPGA 的 IDELAY)提供,也可以由 PHY 侧提供。本方案选择在 PHY 侧配置,简化 PL 逻辑。

4.2 自协商广播(Page 0)

XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);

切回 Page 0 后,开始配置标准自协商寄存器:

// Reg 4 — AN Advertisement
control |= IEEE_ASYMMETRIC_PAUSE_MASK;   // 声明支持非对称流控
control |= IEEE_PAUSE_MASK;              // 声明支持对称流控
control |= ADVERTISE_100;                // 声明支持 100BASE-TX
control |= ADVERTISE_10;                 // 声明支持 10BASE-T

// Reg 9 — 1000BASE-T Advertisement
control |= ADVERTISE_1000;               // 声明支持 1000BASE-T

三条流控/速率声明采用读-改-写模式:先读出当前值,设置对应位,再写回。这种模式保留了寄存器中其他位域的原值,避免误改。

4.3 铜缆控制

XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_PAGE_ADDRESS_REGISTER, 0);
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_COPPER_SPECIFIC_CONTROL_REG, &control);
control |= (7 << 12);    // gigabit attempts = 7
control |= (1 << 11);    // downshift enable

寄存器 16(Copper Specific Control Register)是 KSZ9031 在 Page 0 下的一个关键厂商扩展寄存器:

  • bit 12:10 — Gigabit Attempts: 控制自协商过程中尝试千兆连接的次数。设为最大值 7,在当前链路质量下给予最充分的千兆建立机会
  • bit 11 — Downshift Enable: 使能自动降速机制。如果在千兆协商的最大尝试次数内无法建立稳定链路,PHY 自动将协商速率降至 100M 重新尝试。这个机制保证了线缆质量不佳时仍能建立百兆连接,而非直接失败

调试千兆时可以临时注释掉 control |= (7 << 12)control |= (1 << 11),强制 PHY 不广播千兆能力、不降速重试,以排除降速对故障定位的干扰。

4.4 启动自协商

XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;   // bit 12
control |= IEEE_STAT_AUTONEGOTIATE_RESTART;   // bit 9
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

寄存器 0(Basic Mode Control Register)的 bit 12 使能自协商,bit 9 触发自协商重新开始。写完之后 PHY 立即开始与链路对端交换能力信息。

此处有一项关键约束:对本寄存器不能写 bit 15(IEEE_CTRL_RESET_MASK,0x8000)。 该位是软件复位——一旦置位,PHY 将把之前所有页面中的全部配置恢复为出厂默认值,然后硬件自动将该位清零。如果在配置完成后写入此位,相当于将所有精心设置的寄存器值全部丢弃,PHY 以默认值完成自协商——自协商本身能成功,但 RGMII 延迟、流控、千兆能力广播等配置均已失效。表现为 link up 但 MAC 层无法通信。这项约束在本项目的调试过程中耗费了大量定位时间,将在第六节详细阐述。

4.5 等待自协商完成

XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
while (!(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)) {
    sleep(1);
    // 轮询直到 bit 5 置位或超时 30 秒
}

轮询寄存器 1(Basic Mode Status Register)的 bit 5。自协商是一个硬件过程,对端也需要时间响应,正常耗时在 1-3 秒之间。30 秒超时是安全余量。

4.6 读取协商速率

XEmacPs_PhyRead(xemacpsp, phy_addr, 0x1F, &status_speed);

if      (status_speed & 0x40) return 1000;
else if (status_speed & 0x20) return 100;
else if (status_speed & 0x10) return 10;
else                           return 0;

KSZ9031 将实际协商结果存储在 Page 0 的寄存器 0x1F 中:

含义
bit 6 1000Mbps 协商成功
bit 5 100Mbps 协商成功
bit 4 10Mbps 协商成功

这里没有选择 IEEE 标准的 MMD 访问方式(通过寄存器 13/14 间接访问寄存器 29/30),因为不同固件版本对 MMD 的实现存在差异,而寄存器 0x1F 在所有 KSZ9031 版本上行为一致。


六、修改三:在 PHY 检测函数中添加路由

static u32_t get_IEEE_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
    u16_t phy_identity;
    u32_t RetStatus;

    XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG, &phy_identity);

    if (phy_identity == MICREL_PHY_IDENTIFIER)       // ← 新增分支
    {
        xil_printf("Phy %d is KSZ9031\n\r", phy_addr);
        RetStatus = get_phy_speed_ksz9031(xemacpsp, phy_addr);
    }
    else if (phy_identity == PHY_TI_IDENTIFIER)      // 已有
    {
        RetStatus = get_TI_phy_speed(xemacpsp, phy_addr);
    }
    else
    {
        RetStatus = get_Marvell_phy_speed(xemacpsp, phy_addr);  // 默认
    }

    return RetStatus;
}

将 KSZ9031 分支放在 Marvell 默认分支之前。顺序上 KSZ9031 分支必须出现在 Marvell(0x0141)之前,否则不会被匹配到。添加识别日志 "Phy %d is KSZ9031",在调试时可以直接从串口输出判断 PHY 识别是否成功——如果未见此消息,说明 MDIO 通信异常或 PHY 硬件地址不匹配。

当前实现仅匹配 OUI 高 6 位(0x0022)。如果未来一块板上同时存在两款 Micrel PHY(如 KSZ9021 和 KSZ9031),需进一步通过寄存器 3 的型号掩码 MICREL_PHY_KSZ9031_MODEL 进行二级识别。


七、CTRL_RESET_MASK:一条铁律

这是本项目 PHY 调试中定位时间最长的问题,值得单独写一节。

在从社区获取的 KSZ9031 参考代码中,初始化函数末尾有一段"等待复位完成"的逻辑:

// 错误示例 — 来自参考代码
control |= IEEE_CTRL_RESET_MASK;  // 0x8000
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

while (1) {
    XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
    if (control & IEEE_CTRL_RESET_MASK) continue;
    else break;
}

这段代码的语义是"等待 PHY 软复位完成"——注释也确实是这么写的。但它在时序上的位置——初始化流程的最末尾——使得它实际做的是"把所有配置清除后等待 PHY 以默认值重启"。

IEEE_CTRL_RESET_MASK 是 register 0 的 bit 15。此位是自清除位:写入 1 触发软件复位,PHY 硬件将所有内部寄存器恢复为芯片出厂默认值,然后将该位清零。整个过程约需几百微秒。

关键约束:在任何 PHY 的初始化代码中,不要将 IEEE_CTRL_RESET_MASK 写在配置流程之后。 如果确有需求使用软复位(例如需要以确定的默认状态开始配置),应将其置于初始化流程的最前端——先复位到干净状态,再进行配置——而非末尾。

定位此问题的线索是二分现象:link up 但 ping 不通。Link up 源于 IEEE 标准寄存器的默认值仍支持基础自协商;ping 不通源于 RGMII 延迟等厂商扩展配置被清零。这两个症状的组合高度指向"配置被撤销"类的问题。


八、配套修改

lwipopts.h

#define CONFIG_LINKSPEED_AUTODETECT 1

get_IEEE_phy_speed() 及其调用的所有 PHY 初始化函数由 CONFIG_LINKSPEED_AUTODETECT 宏控制编译。如果未定义此宏,整个 xemacpsif_physpeed.c 中的自动检测逻辑不会被编译进最终固件,PHY 初始化将走固定速率路径而非自协商。在 lwip141_v2_0 中,此宏通常由 BSP 根据 Vivado 中的 PS GEM 配置自动生成,但建议在 lwipopts.h 中显式确认。

xparameters.h(仅 EMIO 路径需要)

#define XPAR_GMII2RGMIICON_0N_ETH1_ADDR 8

如果使用 GEM1 的 EMIO GMII 路径(经 gmii_to_rgmi IP 转 RGMII),SDK 生成的 xparameters.h 不包含此宏。gmii_to_rgmi 驱动依赖此宏来配置 RGMII 接口速率。MIO 直连的 GEM0 不受影响。


九、第二份文件:AXI Ethernet 版本

lwIP 库中同时存在两个 physpeed 文件,分别对应两种 MAC 控制器:

文件 MAC 类型 API 前缀 本项目使用
xemacpsif_physpeed.c PS GEM (Zynq 内置) XEmacPs_PhyWrite/Read
xaxiemacif_physpeed.c AXI Ethernet (PL IP 核) XAxiEthernet_PhyWrite/Read

即使本项目只用 PS GEM,为了保持 lwIP 库的一致性,两份文件都进行了 KSZ9031 适配。三处修改的位置和逻辑完全对应,差异仅在于 API 前缀和函数签名:

差异点 PS 版本 AXI 版本
设备句柄类型 XEmacPs *xemacpsp XAxiEthernet *xaxiemacp
MDIO 读写 API XEmacPs_PhyWrite(xemacpsp, ...) XAxiEthernet_PhyWrite(xaxiemacp, ...)
函数返回类型 static u32_t unsigned int (非 static)
RGMII 延迟配置 control |= IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK (0x0030) control &= ~(0x10) (仅清 bit 4)
CTRL_RESET_MASK ★ 已修复(删除) 仍保留在参考代码中

关于 RGMII 延迟配置的差异: PS 版本使用 IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK(0x0030,同时设置 bit 4 和 bit 5),在 KSZ9031 的 Page 2 寄存器 21 中同时使能 TXC 和 RXC 各约 1.8ns 的 PHY 内部延迟。AXI 版本使用 control &= ~(0x10)(清除 bit 4,保留 bit 5),仅使能单一方向的延迟。两个版本的寄存器操作差异来自不同的参考来源(doc/ref.mddoc/ref1.md),对于 PS GEM 的实际应用场景,PS 版本的 IEEE_RGMII_TXRX_CLOCK_DELAYED_MASK 经过 100M/1000M 实测验证。

关于 CTRL_RESET_MASK: AXI 版本的 get_phy_speed_ksz9031() 函数末尾仍然保留了软件复位代码(control |= IEEE_CTRL_RESET_MASK,位于第 770 行附近)。这是因为本项目全程使用 PS GEM,AXI 版本未曾实际运行和调试,因此未被修正。如果后续切换到 AXI Ethernet MAC(PL 侧 IP 核),需要将第六节所述的修复同样应用到 AXI 版本中。

适配 AXI 版本时将以下三处修改直接移植即可——宏定义、函数体(修改 API 前缀)、get_IEEE_phy_speed() 分支。如需实际使用,必须同时删除函数末尾的 CTRL_RESET_MASK 写入。


十、常见问题速查

问题 原因 验证方法
PHY 识别后走 Marvell 分支 get_IEEE_phy_speed() 中未添加 Micrel 分支,或分支顺序在 Marvell 之后 串口无 "Phy X is KSZ9031" 输出;用 XEmacPs_PhyRead(reg 2) 验证标识寄存器值
Link up 但无法通信 配置末尾误写了 CTRL_RESET_MASK 搜索 get_phy_speed_ksz9031 函数体中 IEEE_CTRL_RESET_MASK 出现的位置
自协商始终超时 MDIO 地址错误,或 PHY 硬件未复位 确认 C_PHYADDR 与硬件 strap 一致;确认 PHY 复位脉冲已发出
百兆正常,千兆失败 GMII-to-RGMII IDELAY 未校准(EMIO 路径特有) 检查 XDC 中 IODELAY_GROUP 是否已设置
千兆自协商成功但降速为百兆 网线质量差或对端不支持千兆 检查 reg 0x1F 的 bit 6;暂时注释 downshift 排除降速干扰
Clean BSP 后 KSZ9031 失效 SDK 静默覆盖了 xemacpsif_physpeed.c 在 SDK 目录外保存 patched 版本,Clean 后恢复

十一、总结

在 Xilinx lwip141_v2_0 中适配 KSZ9031 涉及三处文件内修改和两条配套配置:

  1. 定义 MICREL_PHY_IDENTIFIER
  2. 编写 get_phy_speed_ksz9031() 初始化函数,顺序为:扩展页配置 → 标准页广播 → 铜缆控制 → 启动自协商 → 等待完成 → 读取速率
  3. get_IEEE_phy_speed() 中添加 Micrel 识别分支
  4. 确认 lwipopts.hCONFIG_LINKSPEED_AUTODETECT 已使能
  5. 如果使用 EMIO GMII 路径,确认 xparameters.h 中有 XPAR_GMII2RGMIICON_0N_ETH1_ADDR

整个过程的技术核心在于理解 KSZ9031 的页面机制和寄存器布局,而非代码量本身。而最大的经验教训在技术细节之外:不要信任参考代码的正确性——对 PHY 芯片而言,必须对照数据手册逐位验证每个寄存器写入操作的副作用。一条 CTRL_RESET_MASK 的误用,足以让所有精心配置失效,且症状与配置无关,极难定位。

posted @ 2026-07-24 16:35  dMing`  阅读(2)  评论(0)    收藏  举报