3. PHY子系统
PHY(Physical Layer)子系统是 Linux 内核网络驱动中连接 MAC 层与物理介质的核心枢纽。它负责管理物理层芯片的状态转换、链路检测、自动协商以及寄存器读写。理解 PHY 状态机、核心数据结构 phy_device 以及驱动注册流程,是掌握网络驱动中断与链路检测协同的基础。
3.1 PHY 状态机:链路生命周期的核心
内核 PHY 层维护了一个有限状态机,用于描述 PHY 芯片从上电到链路建立、断开及错误恢复的完整过程。状态机定义在 <linux/phy.h> 中,由 phy_state 枚举表示。
核心状态一览: 状态机共包含 8 个状态,驱动在每次中断或轮询时调用
phy_state_machine() 进行状态迁移。
| 状态 | 枚举值 | 说明 | 典型触发条件 |
|---|---|---|---|
| PHY_DOWN | 0 | PHY 未初始化或已关闭 | 模块卸载、硬件复位 |
| PHY_READY | 1 | PHY 硬件就绪,等待启动自动协商 | phy_start() 调用 |
| PHY_UP | 2 | 自动协商进行中 | phy_start_aneg() 触发 |
| PHY_AN | 3 | 自动协商完成,等待链路建立 | AN 完成中断 |
| PHY_RUNNING | 4 | 链路已建立,正常收发数据 | 链路状态变为 UP |
| PHY_NOLINK | 5 | 链路断开,等待重连 | 链路丢失中断 |
| PHY_FORCING | 6 | 强制模式配置中(非自动协商) | 用户强制设置速率/双工 |
| PHY_HALTED | 7 | PHY 暂停,通常用于电源管理 | phy_stop() 调用 |
关键设计点: 状态机在
PHY_RUNNING 和 PHY_NOLINK 之间来回切换,这是中断处理与链路检测协同最频繁的路径。每次切换都会触发 netif_carrier_on/off() 通知网络层。
3.2 phy_device 结构体:PHY 设备的抽象
phy_device 是内核中描述单个 PHY 芯片的核心数据结构,定义在 <linux/phy.h>。它包含了 PHY 的硬件信息、状态、配置回调以及连接关系。
结构体关键字段分类: 以下表格按功能模块划分
phy_device 的主要成员。
| 分类 | 字段名 | 类型 | 说明 |
|---|---|---|---|
| 硬件标识 | phy_id |
u32 |
PHY 芯片的 32 位标识符(OUI + 型号 + 版本) |
is_c45 |
bool |
是否为 Clause 45 标准 PHY | |
dev_flags |
u32 |
设备特定标志,由板级代码或驱动设置 | |
| 状态管理 | state |
enum phy_state |
当前 PHY 状态机状态 |
link |
bool |
当前链路状态(true=UP, false=DOWN) | |
autoneg |
bool |
是否启用自动协商 | |
| 链路参数 | speed |
int |
当前协商速率(10/100/1000/2500 Mbps) |
duplex |
int |
双工模式(DUPLEX_HALF / DUPLEX_FULL) | |
pause |
int |
流控能力(对称/非对称/无) | |
| 驱动绑定 | drv |
struct phy_driver * |
指向匹配的 PHY 驱动 |
bus |
struct mii_bus * |
所属的 MII 总线 | |
| 中断与轮询 | irq |
int |
PHY 中断号(0 表示轮询模式) |
phy_link_change |
void (*)(struct phy_device *) |
链路变化回调函数 | |
| MAC 连接 | attached_dev |
struct net_device * |
指向绑定的网络设备(MAC 层) |
编程要点: 在中断处理中,驱动通常通过
phydev->link 判断当前链路状态,并通过 phydev->state 确认状态机是否处于 PHY_RUNNING。不要直接修改 state,应使用 phy_state_machine() 或 phy_change() 进行安全迁移。
3.3 phy_driver 注册流程:从模块加载到设备匹配
PHY 驱动的注册遵循 Linux 设备驱动模型的标准流程。一个 PHY 驱动需要实现 struct phy_driver 中的回调函数,并通过 phy_driver_register() 或 phy_drivers_register() 注册到内核。
3.3.1 phy_driver 结构体核心回调
| 回调函数 | 原型 | 调用时机 | 典型实现 |
|---|---|---|---|
config_init |
int (*)(struct phy_device *) |
PHY 设备初始化时 | 配置硬件特定寄存器、校准 |
config_aneg |
int (*)(struct phy_device *) |
启动自动协商时 | 写入 AN 广告寄存器、重启 AN |
read_status |
int (*)(struct phy_device *) |
状态机轮询或中断时 | 读取 BMSR/ANLPAR,更新 speed/duplex/link |
ack_interrupt |
int (*)(struct phy_device *) |
中断触发后 | 读取中断状态寄存器以清除中断 |
config_intr |
int (*)(struct phy_device *) |
使能/禁用中断时 | 写中断使能寄存器 |
suspend/resume |
int (*)(struct phy_device *) |
系统电源管理时 | 进入/退出低功耗模式 |
3.3.2 注册流程步骤
- 定义 phy_driver 结构体: 填充
phy_id、name、features以及上述回调函数。 - 调用注册 API: 在模块初始化函数中调用
phy_driver_register(&my_phy_driver)或批量注册phy_drivers_register()。 - 内核匹配: 内核通过
phy_id与 MII 总线上扫描到的 PHY 设备进行匹配,匹配成功后调用probe回调。 - 设备初始化: 在
probe中完成硬件初始化,设置phydev->drv指向当前驱动。 - 状态机启动: 上层调用
phy_start()后,状态机开始运行,驱动回调被逐步调用。
常见错误:
phy_id掩码设置错误导致驱动无法匹配设备。应使用PHY_ID_MATCH_EXACT(id)或PHY_ID_MATCH_MODEL(id)宏。read_status回调未正确更新phydev->link,导致状态机无法进入PHY_RUNNING。- 中断模式下未实现
ack_interrupt,导致中断风暴。