3. PHY驱动框架:mdio_bus总线、phy_device结构体、phy_driver结构体、PHY状态机
PHY(Physical Layer)驱动是Linux网络驱动中连接MAC与物理介质的核心层。理解PHY驱动框架,需要从总线模型、设备/驱动结构体以及状态机三个维度切入。本章将逐一拆解mdio_bus、phy_device、phy_driver以及PHY状态机的设计与交互逻辑。
3.1 mdio_bus总线:PHY设备的注册与发现
在Linux内核中,PHY设备并不直接挂载在platform总线或PCI总线上,而是通过专用的MDIO(Management Data Input/Output)总线进行管理。mdio_bus是内核为PHY设备抽象出的一条虚拟总线,负责PHY设备的枚举、匹配与驱动绑定。
struct mii_bus,每个MDIO控制器对应一个 mii_bus 实例。
mdio_bus的工作流程如下:
- MDIO控制器注册:MAC驱动或专用MDIO控制器驱动调用
mdiobus_register()将mii_bus注册到内核。 - PHY设备扫描:总线注册时,内核遍历MDIO地址空间(0~31),通过
mdiobus_scan()读取每个地址的PHY ID寄存器。 - 设备创建:若读取到有效的PHY ID,内核创建
phy_device实例,并将其挂载到mdio_bus上。 - 驱动匹配:PHY驱动通过
mdio_bus的匹配函数与对应PHY设备绑定。
| 组件 | 数据结构/函数 | 作用 |
|---|---|---|
| MDIO总线 | struct mii_bus |
描述MDIO控制器,包含读写回调、PHY设备列表 |
| 总线注册 | mdiobus_register() |
注册mii_bus到内核,触发PHY扫描 |
| 设备扫描 | mdiobus_scan() |
读取PHY ID寄存器,创建phy_device |
| 总线类型 | mdio_bus_type |
定义mdio总线的匹配、探测、移除行为 |
3.2 phy_device结构体:PHY设备的抽象
phy_device 是内核中描述单个PHY芯片的核心数据结构,它继承自通用的 struct device,并包含了PHY的硬件属性、状态、链路参数以及回调函数指针。
phy_device 中的 state、link、speed、duplex 字段是判断链路状态的第一手信息,可通过 sysfs 或 debugfs 导出查看。
该结构体的关键成员如下:
| 成员 | 类型 | 说明 |
|---|---|---|
phy_id |
u32 |
PHY芯片的32位标识符,由OUI+型号+版本组成 |
state |
enum phy_state |
PHY状态机当前状态(如 PHY_UP、PHY_AN) |
link |
unsigned int |
链路是否建立(1=已建立,0=未建立) |
speed |
int |
当前协商速率(单位:Mbps,如 10/100/1000) |
duplex |
int |
双工模式(DUPLEX_HALF 或 DUPLEX_FULL) |
supported |
unsigned long |
PHY支持的速率/双工能力位图 |
advertising |
unsigned long |
本端向对端通告的能力位图 |
lp_advertising |
unsigned long |
链路对端通告的能力位图 |
bus |
struct mii_bus * |
指向所属MDIO总线的指针 |
drv |
struct phy_driver * |
指向绑定PHY驱动的指针 |
在驱动开发中,phy_device 的初始化通常由 phy_device_create() 或 get_phy_device() 完成,而MAC驱动通过 phy_connect() 或 of_phy_connect() 获取关联的 phy_device 实例。
3.3 phy_driver结构体:PHY驱动的实现
phy_driver 是PHY芯片驱动的载体,它定义了PHY硬件操作的具体实现。每个PHY驱动实例对应一种或一组PHY芯片型号,通过 phy_id 和 phy_id_mask 进行匹配。
phy_driver 中的 config_aneg()、read_status()、ack_interrupt() 是驱动开发者必须实现的关键回调。
以下是一个典型的PHY驱动结构体定义示例:
static struct phy_driver my_phy_driver = {
.phy_id = 0x1234d000,
.phy_id_mask = 0xfffff000,
.name = "My Custom PHY",
.features = SUPPORTED_10baseT_Half |
SUPPORTED_10baseT_Full |
SUPPORTED_100baseT_Half |
SUPPORTED_100baseT_Full,
.config_aneg = my_phy_config_aneg,
.read_status = my_phy_read_status,
.ack_interrupt = my_phy_ack_interrupt,
.config_intr = my_phy_config_intr,
.suspend = my_phy_suspend,
.resume = my_phy_resume,
.driver = {
.owner = THIS_MODULE,
},
};
PHY驱动的注册与注销通过以下宏完成:
phy_driver_register()/phy_driver_unregister():单个驱动注册/注销。phy_drivers_register()/phy_drivers_unregister():批量注册/注销驱动数组。
| 回调函数 | 调用时机 | 典型实现 |
|---|---|---|
config_aneg() |
启动或重启自动协商 | 写入AN广告寄存器,触发AN |
read_status() |
轮询或中断时读取链路状态 | 读取BMSR、LPA等寄存器,更新link/speed/duplex |
ack_interrupt() |
PHY产生中断后确认 | 读取中断状态寄存器以清除中断 |
config_intr() |
使能或禁用PHY中断 | 写中断使能寄存器 |
suspend() |
系统进入低功耗模式 | 设置PHY进入低功耗状态 |
resume() |
系统从低功耗模式唤醒 | 恢复PHY寄存器配置 |
3.4 PHY状态机:链路协商的核心引擎
PHY状态机是驱动框架中管理链路协商与状态迁移的核心机制。内核使用 enum phy_state 定义状态,并通过 phy_state_machine() 工作队列函数驱动状态迁移。
PHY_AN 或 PHY_NOLINK 状态。此时应检查 phy_device 的 advertising 与 lp_advertising 是否匹配,以及MDIO通信是否正常。
状态机的主要状态及迁移条件如下:
| 状态 | 含义 | 进入条件 | 下一状态 |
|---|---|---|---|
PHY_DOWN |
PHY未初始化或已关闭 | 初始状态或 phy_stop() 调用 |
PHY_READY(启动后) |
PHY_READY |
PHY硬件已准备就绪 | phy_start() 调用 |
PHY_UP |
PHY_UP |
PHY已启用,等待配置 | PHY硬件初始化完成 | PHY_AN(启动AN) |
PHY_AN |
自动协商进行中 | config_aneg() 被调用 |
PHY_RUNNING 或 PHY_NOLINK |
PHY_RUNNING |
链路已建立,正常通信 | 协商成功且link up | PHY_NOLINK(link丢失) |
PHY_NOLINK |
链路断开 | link down 或协商失败 | PHY_AN(重试协商) |
PHY_HALTED |
PHY被暂停 | phy_stop() 调用 |
PHY_DOWN |
3.5 框架整合:从总线到状态机的完整链路
将上述三个结构体与状态机串联起来,PHY驱动框架的完整工作流程如下:
- 总线层:
mdio_bus扫描MDIO地址空间,发现PHY设备并创建phy_device。 - 驱动层:
phy_driver通过phy_id匹配并绑定到phy_device,调用probe()初始化。 - 状态机层:MAC驱动调用
phy_start()启动状态机,状态机驱动自动协商与链路监控。 - 回调层:状态机在
PHY_AN状态调用config_aneg(),在PHY_RUNNING或PHY_NOLINK状态调用read_status()更新链路参数。
read_status() 回调中添加 phydev_dbg() 打印 phy_device 的 state、link、speed、duplex,可以快速定位协商失败的原因。同时,通过 mdio 命令行工具(如 mdio-rw)直接读写PHY寄存器,是验证硬件通信是否正常的最直接手段。