4. 链路检测机制:PHY中断 vs 轮询、phy_interrupt()处理流程、链路状态变更通知

在嵌入式Linux网络驱动中,PHY(物理层)设备负责检测链路的连接与断开状态。内核提供了两种链路检测机制:PHY中断和轮询。本章深入分析这两种机制的差异、phy_interrupt()的处理流程以及链路状态变更通知的完整路径。

4.1 PHY中断 vs 轮询:机制对比

PHY中断和轮询是两种互补的链路检测策略,它们在实时性、CPU占用率和实现复杂度上存在显著差异。

特性 PHY中断 轮询
触发方式 PHY硬件引脚电平变化触发 内核定时器周期性查询
实时性 高(微秒级响应) 低(取决于轮询间隔,通常10~100ms)
CPU占用 低(仅状态变化时唤醒) 高(周期性占用CPU)
实现复杂度 高(需处理中断共享、边缘触发) 低(简单定时器回调)
适用场景 对链路变化敏感的高速网络 低功耗或中断资源受限的系统
内核配置 CONFIG_PHYLIB=y + 硬件中断线 CONFIG_PHYLIB=y + 无中断线
设计建议:在大多数嵌入式系统中,推荐优先使用PHY中断机制。仅在PHY芯片不支持中断引脚、或系统中断资源极度紧张时,才退而使用轮询方式。中断方式能显著降低CPU空闲时的功耗。

下图展示了两种机制在链路状态变更时的触发路径差异:

PHY中断 vs 轮询 触发路径对比 PHY硬件中断触发 phy_interrupt() 处理 链路状态变更通知 内核定时器到期 phy_state_machine() 轮询 链路状态变更通知 中断路径:事件驱动 → 低延迟;轮询路径:时间驱动 → 周期性延迟

4.2 phy_interrupt() 处理流程

当PHY芯片检测到链路状态变化(如Link Up/Down、速率或双工模式变更)时,会通过中断引脚通知MAC控制器或SoC。内核PHY层的中断处理函数phy_interrupt()是这一流程的核心入口。

4.2.1 中断处理函数调用链

PHY中断的完整处理路径涉及硬件抽象层、PHY设备驱动和网络协议栈。以下为典型的调用链:

  1. 硬件中断触发:PHY芯片拉低/拉高中断引脚,触发MAC或SoC的GPIO中断。
  2. 中断控制器分发:内核中断子系统调用注册的中断处理函数(通常为phy_mac_interrupt()或自定义包装函数)。
  3. phy_interrupt() 调用:通用PHY层函数读取中断状态寄存器,判断中断源。
  4. phy_handle_interrupt() 处理:根据中断类型(链路变更、错误等)调用对应的PHY驱动回调。
  5. 链路状态更新:更新phy_device结构体中的link、speed、duplex等字段。
  6. 通知上层:通过phy_change_work()工作队列或直接回调adjust_link通知MAC驱动。
关键数据结构:struct phy_device中的irq字段保存中断号,phy_interrupt()通过该字段确认中断来源。中断处理必须在原子上下文中快速完成,耗时操作需推迟到工作队列。

下图展示了phy_interrupt()内部的详细处理流程:

phy_interrupt() 处理流程 phy_interrupt(irq, phydev) 1. 读取中断状态寄存器 (MII_INT_STATUS) 2. 判断中断源类型 链路变更? 是 3a. 更新链路状态 4a. 触发工作队列通知 否 3b. 处理其他中断类型 4b. 清除中断标志 返回 IRQ_HANDLED

4.2.2 核心代码实现

以下为phy_interrupt()在drivers/net/phy/phy.c中的简化实现,展示了中断状态读取和链路变更处理的核心逻辑:

/* drivers/net/phy/phy.c */
irqreturn_t phy_interrupt(int irq, void *phy_dat)
{
    struct phy_device *phydev = phy_dat;
    int err;

    /* 1. 读取中断状态寄存器,确认中断源 */
    err = phy_read(phydev, MII_INT_STATUS);
    if (err < 0) {
        dev_err(&phydev->mdio.dev, "Error reading interrupt status\n");
        return IRQ_NONE;
    }

    /* 2. 清除中断状态(写1清除) */
    phy_write(phydev, MII_INT_STATUS, err);

    /* 3. 判断是否为链路状态变更中断 */
    if (err & PHY_INT_LINK_CHANGED) {
        /* 更新链路状态 */
        phydev->link = phy_read_status(phydev);
        /* 触发工作队列,通知上层协议栈 */
        schedule_work(&phydev->phy_change_work);
    }

    /* 4. 处理其他中断类型(如错误计数、远程故障等) */
    if (err & PHY_INT_ERROR) {
        phydev->errors++;
        dev_warn(&phydev->mdio.dev, "PHY error interrupt\n");
    }

    return IRQ_HANDLED;
}
EXPORT_SYMBOL(phy_interrupt);
注意事项:
  • 中断状态寄存器必须使用“写1清除”方式,否则会导致中断风暴。
  • phy_read()和phy_write()通过MDIO总线操作,可能引入延迟,因此中断处理函数应避免长时间持有自旋锁。
  • 链路状态变更后的通知工作(如调整MAC速率)应推迟到phy_change_work工作队列中执行,以符合原子上下文要求。

4.3 链路状态变更通知

当PHY检测到链路状态变化后,需要将变更通知到MAC驱动和网络协议栈。这一过程涉及多个层次的回调机制。

4.3.1 通知路径

链路状态变更通知的完整路径如下:

  1. PHY驱动层:phy_interrupt()或phy_state_machine()更新phy_device->link、phy_device->speed、phy_device->duplex。
  2. PHY状态机:phy_change_work()工作队列函数调用phy_state_machine(),执行状态迁移。
  3. MAC驱动回调:状态机调用phydev->adjust_link回调函数,通知MAC驱动调整硬件参数。
  4. 网络协议栈:MAC驱动通过netif_carrier_on()/netif_carrier_off()更新网络设备载波状态。
  5. 用户空间:协议栈通过netlink或sysfs通知用户空间应用程序(如ifplugd、NetworkManager)。

4.3.2 adjust_link 回调机制

adjust_link是PHY层与MAC驱动之间的关键接口。MAC驱动在初始化时注册该回调,PHY状态机在链路状态变化时调用它。

/* MAC驱动注册 adjust_link 回调示例 */
static void my_mac_adjust_link(struct net_device *ndev)
{
    struct my_priv *priv = netdev_priv(ndev);
    struct phy_device *phydev = priv->phydev;
    int new_state = 0;

    if (phydev->link) {
        /* 链路已建立,配置MAC控制器 */
        if (phydev->speed != priv->old_speed ||
            phydev->duplex != priv->old_duplex) {
            /* 调整MAC的速率和双工模式 */
            my_mac_set_speed_duplex(priv, phydev->speed, phydev->duplex);
            priv->old_speed = phydev->speed;
            priv->old_duplex = phydev->duplex;
        }
        /* 通知协议栈链路已建立 */
        if (!netif_carrier_ok(ndev)) {
            netif_carrier_on(ndev);
            new_state = 1;
        }
    } else {
        /* 链路断开 */
        if (netif_carrier_ok(ndev)) {
            netif_carrier_off(ndev);
            new_state = 1;
        }
    }

    if (new_state)
        dev_info(&ndev->dev, "Link %s (%dMbps/%s)\n",
                 phydev->link ? "UP" : "DOWN",
                 phydev->speed,
                 phydev->duplex == DUPLEX_FULL ? "Full" : "Half");
}

/* 在MAC驱动 probe 中注册 */
static int my_mac_probe(struct platform_device *pdev)
{
    struct net_device *ndev;
    struct phy_device *phydev;

    /* ... 分配 net_device 等操作 ... */

    phydev = phy_connect(ndev, dev_name(&pdev->dev),
                         my_mac_adjust_link, 0, PHY_INTERFACE_MODE_RGMII);
    if (IS_ERR(phydev)) {
        dev_err(&pdev->dev, "phy_connect failed\n");
        return PTR_ERR(phydev);
    }

    priv->phydev = phydev;
    return 0;
}

4.3.3 通知路径示意图

下图展示了从PHY硬件中断到用户空间应用程序的完整通知路径:

链路状态变更通知路径 PHY硬件层:中断触发 / 状态寄存器变更 PHY驱动层:phy_interrupt() / phy_state_machine() MAC驱动层:adjust_link 回调 → netif_carrier_on/off 网络协议栈:载波状态更新 → 路由表 / 邻居子系统 用户空间:netlink 事件 / sysfs 状态 / ifplugd 守护进程

4.4 轮询机制:phy_state_machine()

当PHY设备未使用中断时,内核通过phy_state_machine()定时轮询PHY状态寄存器。该函数由phy_start_machine()启动,默认轮询间隔为1秒(可通过phydev->polling_interval调整)。

4.4.1 轮询状态机

phy_state_machine()实现了一个有限状态机,管理PHY的初始化、协商、链路检测和错误恢复:

状态 描述 触发条件
PHY_DOWN PHY未启动或已关闭 phy_stop() 调用
PHY_READY PHY已准备就绪,等待启动 phy_start() 调用
PHY_AN 自动协商进行中 phy_start_aneg() 调用
PHY_RUNNING 链路已建立,正常运行 自动协商完成
PHY_NOLINK 链路断开 轮询检测到链路丢失
PHY_HALTED PHY被暂停 phy_stop() 或错误

4.4.2 轮询与中断的协同

在实际系统中,中断和轮询可以协同工作。例如,在PHY中断不可用或异常时,内核可以回退到轮询模式:

/* 中断与轮询的协同示例 */
static int phy_poll_or_interrupt(struct phy_device *phydev)
{
    if (phydev->irq >= 0) {
        /* 使用中断模式 */
        return phy_request_interrupt(phydev);
    } else {
        /* 回退到轮询模式 */
        dev_info(&phydev->mdio.dev, "No IRQ, falling back to polling\n");
        phydev->polling_interval = HZ; /* 1秒轮询间隔 */
        return phy_start_machine(phydev);
    }
}
性能优化:在轮询模式下,可以通过调整phydev->polling_interval来平衡实时性和CPU占用。对于高速网络(如千兆以太网),建议将轮询间隔设置为100~200ms;对于低速或低功耗场景,可延长至2~5秒。

4.5 本章小结

链路检测机制是PHY驱动的核心功能之一。中断模式提供高实时性和低CPU占用,适用于对链路变化敏感的场景;轮询模式实现简单,适用于中断资源受限的系统。理解phy_interrupt()的处理流程和链路状态变更通知路径,对于开发稳定可靠的嵌入式网络驱动至关重要。在实际项目中,应根据硬件能力和功耗要求选择合适的检测机制,并确保中断处理函数遵循原子上下文约束,将耗时操作推迟到工作队列中执行。