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设备的枚举、匹配与驱动绑定。

关键点: mdio_bus是PHY设备与PHY驱动之间的通信桥梁,其核心数据结构为 struct mii_bus,每个MDIO控制器对应一个 mii_bus 实例。

mdio_bus的工作流程如下:

  1. MDIO控制器注册:MAC驱动或专用MDIO控制器驱动调用 mdiobus_register() 将 mii_bus 注册到内核。
  2. PHY设备扫描:总线注册时,内核遍历MDIO地址空间(0~31),通过 mdiobus_scan() 读取每个地址的PHY ID寄存器。
  3. 设备创建:若读取到有效的PHY ID,内核创建 phy_device 实例,并将其挂载到 mdio_bus 上。
  4. 驱动匹配: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总线的匹配、探测、移除行为
mdio_bus 总线架构与PHY设备发现流程 MDIO控制器 struct mii_bus mdio_bus 总线 mdio_bus_type PHY设备列表 phy_device 实例 ① mdiobus_register() ② 遍历MDIO地址 0~31 ③ 读取PHY ID寄存器 ID有效? ④ 创建 phy_device 匹配驱动后绑定 probe() 回调执行

3.2 phy_device结构体:PHY设备的抽象

phy_device 是内核中描述单个PHY芯片的核心数据结构,它继承自通用的 struct device,并包含了PHY的硬件属性、状态、链路参数以及回调函数指针。

调试提示: 在调试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状态机可能卡在 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
PHY状态机迁移图 PHY_DOWN PHY_READY PHY_UP PHY_AN PHY_RUNNING PHY_NOLINK PHY_HALTED phy_start() 初始化完成 config_aneg() 协商成功 协商失败 link丢失 重试协商 phy_stop() 关闭 状态机由 phy_state_machine() 工作队列驱动,默认每1秒轮询一次 中断模式下可减少轮询延迟,但需正确实现 ack_interrupt 和 config_intr

3.5 框架整合:从总线到状态机的完整链路

将上述三个结构体与状态机串联起来,PHY驱动框架的完整工作流程如下:

  1. 总线层:mdio_bus 扫描MDIO地址空间,发现PHY设备并创建 phy_device。
  2. 驱动层:phy_driver 通过 phy_id 匹配并绑定到 phy_device,调用 probe() 初始化。
  3. 状态机层:MAC驱动调用 phy_start() 启动状态机,状态机驱动自动协商与链路监控。
  4. 回调层:状态机在 PHY_AN 状态调用 config_aneg(),在 PHY_RUNNING 或 PHY_NOLINK 状态调用 read_status() 更新链路参数。
调试技巧: 在 read_status() 回调中添加 phydev_dbg() 打印 phy_device 的 state、link、speed、duplex,可以快速定位协商失败的原因。同时,通过 mdio 命令行工具(如 mdio-rw)直接读写PHY寄存器,是验证硬件通信是否正常的最直接手段。