3. Linux内核PHY驱动框架:phylib架构、phy_device与phy_driver结构体、phy_state状态机

Linux内核的PHY(Physical Layer)驱动管理经历了从分散实现到统一框架的演进。phylib(PHY Library)的出现,为网络设备驱动开发者提供了一套标准化的PHY设备抽象、驱动注册与状态管理机制。理解phylib的架构、核心数据结构以及状态机,是进行PHY寄存器配置与调试的基础。

3.1 phylib架构概述

phylib位于内核网络子系统的MAC层与物理PHY芯片之间,主要承担以下职责:

  • 抽象PHY设备:将不同厂商、不同型号的PHY芯片统一表示为phy_device结构体。
  • 管理PHY驱动:通过phy_driver结构体封装PHY芯片的初始化、配置、链路状态监测等操作。
  • 维护状态机:自动轮询PHY状态,处理链路建立、断开、协商等事件。
  • 提供通用接口:为MAC驱动提供phy_start、phy_stop、phy_read、phy_write等标准API。
架构分层示意: 网络协议栈 → MAC驱动(如stmmac、fec) → phylib核心 → PHY驱动(如marvell、broadcom) → MDIO总线 → PHY芯片寄存器。
phylib 软件架构分层图 网络协议栈 (TCP/IP) MAC 驱动 (如 stmmac, fec) phylib 核心层 phy_device / phy_driver / phy_state 状态机 PHY 驱动 (marvell, broadcom, realtek...) MDIO 总线 (SMI/MDC) PHY 芯片寄存器

3.2 phy_device 结构体

phy_device是phylib中描述一个物理PHY芯片的核心数据结构。它包含了PHY的标识、能力、状态、连接方式以及回调函数等信息。该结构体定义在include/linux/phy.h中。

字段 类型 说明
phy_id u32 PHY芯片的标识符,通过读取寄存器2和3获得
is_c45 bool 是否为Clause 45 PHY(10G及以上速率常用)
supported unsigned long * PHY支持的能力位图(速率、双工、流控等)
advertising unsigned long * PHY当前通告的能力位图(用于协商)
link int 当前链路状态:0=down, 1=up
speed int 当前协商速率(Mbps):10, 100, 1000等
duplex int 当前双工模式:DUPLEX_HALF / DUPLEX_FULL
pause int 流控协商结果
state enum phy_state PHY状态机当前状态
mdio struct mii_bus * 所属的MDIO总线
drv struct phy_driver * 指向匹配的PHY驱动
attached_dev struct net_device * 挂载该PHY的网络设备
调试提示: 在调试PHY寄存器时,phy_device中的phy_id字段是确认PHY芯片型号的关键。如果读取到的phy_id与数据手册不符,通常表明MDIO通信异常或PHY地址配置错误。

3.3 phy_driver 结构体

phy_driver结构体封装了针对特定PHY芯片或系列的操作函数。每个PHY驱动需要实现一组回调,phylib核心在状态机运行过程中会调用这些回调来配置PHY寄存器。

struct phy_driver {
    u32 phy_id;                     /* PHY芯片ID掩码匹配值 */
    char *name;                     /* 驱动名称 */
    unsigned int phy_id_mask;       /* ID掩码,用于匹配 */
    u32 features;                   /* 该驱动支持的固定能力 */

    /* 核心回调函数 */
    int (*config_init)(struct phy_device *phydev);      /* PHY初始化配置 */
    int (*soft_reset)(struct phy_device *phydev);       /* 软复位 */
    int (*config_aneg)(struct phy_device *phydev);      /* 配置自动协商 */
    int (*aneg_done)(struct phy_device *phydev);        /* 检查协商是否完成 */
    int (*read_status)(struct phy_device *phydev);      /* 读取链路状态 */
    int (*config_intr)(struct phy_device *phydev);      /* 配置中断 */
    int (*did_interrupt)(struct phy_device *phydev);    /* 检查中断源 */
    void (*link_change_notify)(struct phy_device *phydev); /* 链路变化通知 */

    /* 寄存器直接读写(通常由phylib核心提供默认实现) */
    int (*read_mmd)(struct phy_device *phydev, int devad, u32 regnum);
    int (*write_mmd)(struct phy_device *phydev, int devad, u32 regnum, u16 val);

    struct device_driver driver;    /* 内核驱动基类 */
};
回调函数 调用时机 典型寄存器操作
config_init PHY驱动绑定后、状态机启动前 配置LED模式、中断极性、节能模式等
config_aneg 进入PHY_AN状态时 写寄存器4(广告能力)、寄存器0(控制)启动协商
read_status 轮询链路状态时 读寄存器1(状态)、寄存器5(协商伙伴能力)
soft_reset PHY初始化或错误恢复时 写寄存器0的复位位(bit 15)

3.4 phy_state 状态机

phylib核心维护了一个有限状态机(FSM),用于管理PHY从初始化到正常工作的完整生命周期。状态机定义在include/linux/phy.h中的enum phy_state枚举。

enum phy_state {
    PHY_DOWN = 0,       /* PHY未启动或已停止 */
    PHY_READY,          /* PHY已初始化,准备启动 */
    PHY_HALTED,         /* PHY被暂停(如电源管理) */
    PHY_ERROR,          /* PHY发生错误 */
    PHY_UP,             /* PHY已启动,开始自动协商 */
    PHY_RUNNING,        /* PHY链路已建立,正常运行 */
    PHY_NOLINK,         /* PHY已启动但无链路 */
    PHY_CABLETEST,      /* PHY正在执行电缆测试 */
};
phy_state 状态机转换图 PHY_DOWN PHY_READY PHY_UP PHY_AN (协商中) PHY_NOLINK PHY_RUNNING PHY_ERROR PHY_HALTED phy_start() 自动启动 开始协商 协商失败/无链路 协商成功 链路恢复 链路断开 严重错误 phy_stop() / 复位 暂停 恢复
工程注意: 状态机转换由phylib核心的phy_state_machine()工作队列驱动,默认每1秒轮询一次。在调试链路不稳定问题时,可以通过/sys/class/net/ethX/phy_state查看当前PHY状态,判断是卡在PHY_AN协商阶段还是PHY_NOLINK无链路状态。

3.5 状态机关键转换路径

在实际工程中,以下三条状态转换路径最为常见:

  1. 正常启动路径:PHY_DOWN → PHY_READY → PHY_UP → PHY_AN → PHY_RUNNING
    • MAC驱动调用phy_start()触发。
    • 自动协商成功后直接进入PHY_RUNNING。
  2. 链路断开恢复路径:PHY_RUNNING → PHY_NOLINK → PHY_AN → PHY_RUNNING
    • 网线拔出时进入PHY_NOLINK,插入后重新协商。
    • 此路径中read_status回调会被频繁调用。
  3. 错误恢复路径:PHY_RUNNING → PHY_ERROR → PHY_DOWN → PHY_READY → ...
    • MDIO通信超时或PHY芯片异常时触发。
    • 需要驱动层实现soft_reset回调进行恢复。

3.6 phylib核心API与驱动注册

PHY驱动的注册与注销通过以下宏完成:

/* 注册PHY驱动 */
int phy_driver_register(struct phy_driver *new_driver, struct module *owner);
void phy_driver_unregister(struct phy_driver *drv);

/* 便捷宏:模块加载/卸载时批量注册 */
module_phy_driver(phy_drivers);

一个典型的PHY驱动模块结构如下:

static struct phy_driver my_phy_driver = {
    .phy_id         = 0x01410cc0,   /* 特定PHY的ID */
    .name           = "My PHY",
    .phy_id_mask    = 0xfffffff0,   /* 掩码匹配 */
    .features       = PHY_GBIT_FEATURES | SUPPORTED_Pause,
    .config_init    = my_phy_config_init,
    .config_aneg    = genphy_config_aneg,  /* 使用通用协商配置 */
    .read_status    = my_phy_read_status,
    .soft_reset     = genphy_soft_reset,
};

module_phy_driver(my_phy_driver);
关键点: phy_id_mask用于实现一个驱动匹配多个相似PHY芯片。例如,掩码0xfffffff0表示忽略低4位,可匹配同一系列中型号略有差异的芯片。在调试时,务必确认phy_id与数据手册中的OUI+型号值一致。

3.7 调试实践:状态机与寄存器联动分析

当PHY无法建立链路时,结合状态机与寄存器值可以快速定位问题:

状态机位置 典型寄存器现象 可能原因
卡在PHY_AN 寄存器1的bit 5(Auto-Negotiation Complete)为0 协商超时、对端不匹配、MDI/MDI-X交叉问题
卡在PHY_NOLINK 寄存器1的bit 2(Link Status)为0 网线断开、对端设备未启动、PHY电源异常
进入PHY_ERROR MDIO读写返回超时或错误码 MDIO时钟极性错误、PHY地址偏移、总线竞争
状态在PHY_RUNNING但网络不通 寄存器0的bit 13(Speed)与实际不符 协商结果与MAC侧配置不一致、延迟匹配问题
调试命令: 使用mii-tool或ethtool可以快速查看PHY状态和寄存器摘要:
ethtool eth0 查看链路协商结果;
ethtool -S eth0 查看PHY统计计数器;
mii-diag eth0 查看基本寄存器状态。