4. 链路检测机制
在嵌入式Linux网络驱动中,PHY(物理层)设备负责检测链路的连接与断开状态。内核提供了两种链路检测机制:PHY中断和轮询。本章深入分析这两种机制的差异、phy_interrupt()的处理流程以及链路状态变更通知的完整路径。
4.1 PHY中断 vs 轮询:机制对比
PHY中断和轮询是两种互补的链路检测策略,它们在实时性、CPU占用率和实现复杂度上存在显著差异。
| 特性 | PHY中断 | 轮询 |
|---|---|---|
| 触发方式 | PHY硬件引脚电平变化触发 | 内核定时器周期性查询 |
| 实时性 | 高(微秒级响应) | 低(取决于轮询间隔,通常10~100ms) |
| CPU占用 | 低(仅状态变化时唤醒) | 高(周期性占用CPU) |
| 实现复杂度 | 高(需处理中断共享、边缘触发) | 低(简单定时器回调) |
| 适用场景 | 对链路变化敏感的高速网络 | 低功耗或中断资源受限的系统 |
| 内核配置 | CONFIG_PHYLIB=y + 硬件中断线 | CONFIG_PHYLIB=y + 无中断线 |
下图展示了两种机制在链路状态变更时的触发路径差异:
4.2 phy_interrupt() 处理流程
当PHY芯片检测到链路状态变化(如Link Up/Down、速率或双工模式变更)时,会通过中断引脚通知MAC控制器或SoC。内核PHY层的中断处理函数phy_interrupt()是这一流程的核心入口。
4.2.1 中断处理函数调用链
PHY中断的完整处理路径涉及硬件抽象层、PHY设备驱动和网络协议栈。以下为典型的调用链:
- 硬件中断触发:PHY芯片拉低/拉高中断引脚,触发MAC或SoC的GPIO中断。
- 中断控制器分发:内核中断子系统调用注册的中断处理函数(通常为
phy_mac_interrupt()或自定义包装函数)。 - phy_interrupt() 调用:通用PHY层函数读取中断状态寄存器,判断中断源。
- phy_handle_interrupt() 处理:根据中断类型(链路变更、错误等)调用对应的PHY驱动回调。
- 链路状态更新:更新
phy_device结构体中的link、speed、duplex等字段。 - 通知上层:通过
phy_change_work()工作队列或直接回调adjust_link通知MAC驱动。
struct phy_device中的irq字段保存中断号,phy_interrupt()通过该字段确认中断来源。中断处理必须在原子上下文中快速完成,耗时操作需推迟到工作队列。
下图展示了phy_interrupt()内部的详细处理流程:
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 通知路径
链路状态变更通知的完整路径如下:
- PHY驱动层:
phy_interrupt()或phy_state_machine()更新phy_device->link、phy_device->speed、phy_device->duplex。 - PHY状态机:
phy_change_work()工作队列函数调用phy_state_machine(),执行状态迁移。 - MAC驱动回调:状态机调用
phydev->adjust_link回调函数,通知MAC驱动调整硬件参数。 - 网络协议栈:MAC驱动通过
netif_carrier_on()/netif_carrier_off()更新网络设备载波状态。 - 用户空间:协议栈通过
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硬件中断到用户空间应用程序的完整通知路径:
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()的处理流程和链路状态变更通知路径,对于开发稳定可靠的嵌入式网络驱动至关重要。在实际项目中,应根据硬件能力和功耗要求选择合适的检测机制,并确保中断处理函数遵循原子上下文约束,将耗时操作推迟到工作队列中执行。