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芯片寄存器。
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正在执行电缆测试 */
};
工程注意: 状态机转换由phylib核心的
phy_state_machine()工作队列驱动,默认每1秒轮询一次。在调试链路不稳定问题时,可以通过/sys/class/net/ethX/phy_state查看当前PHY状态,判断是卡在PHY_AN协商阶段还是PHY_NOLINK无链路状态。
3.5 状态机关键转换路径
在实际工程中,以下三条状态转换路径最为常见:
- 正常启动路径:
PHY_DOWN → PHY_READY → PHY_UP → PHY_AN → PHY_RUNNING- MAC驱动调用
phy_start()触发。 - 自动协商成功后直接进入
PHY_RUNNING。
- MAC驱动调用
- 链路断开恢复路径:
PHY_RUNNING → PHY_NOLINK → PHY_AN → PHY_RUNNING- 网线拔出时进入
PHY_NOLINK,插入后重新协商。 - 此路径中
read_status回调会被频繁调用。
- 网线拔出时进入
- 错误恢复路径:
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 查看基本寄存器状态。