Linux以太网之MDIO扫描注册phy及C22和C45

MDIO扫描注册phy

在 Linux 内核中,MDIO 子系统对 PHY 设备的扫描和注册,从GMAC驱动中对mdiobus注册开始,主要涉及到几个关键文件中:

  • 硬件拓扑信息(如扫描、ID匹配) → 主要代码在 drivers/net/phy/mdio_bus.c
  • 设备树(Device Tree) → 主要代码在 drivers/of/of_mdio.c
  • ACPI(高级配置与电源接口) → 主要代码在 drivers/net/mdio/acpi_mdio.c
  • 平台特定板级信息 → 主要代码在 drivers/net/phy/mdio-boardinfo.c

📊 说明:

场景 主要代码来源 核心触发函数 创建原理与示例
自动扫描 (通用) drivers/net/phy/mdio_bus.c __mdiobus_register 注册 mii_bus 时自动扫描硬件创建节点。例如:stmmac-0:01
设备树 drivers/of/of_mdio.c of_mdiobus_register 依据设备树子节点信息创建节点,覆盖自动扫描。例如:stmmac-0:01
ACPI drivers/net/mdio/acpi_mdio.c __acpi_mdiobus_register 依据ACPI表中子节点信息创建节点,覆盖自动扫描。例如:stmmac-0:01
板级信息 drivers/net/phy/mdio-boardinfo.c mdiobus_setup_mdiodev_from_board_info 在注册 mii_bus 过程中,使用预定义的平台数据创建设备。例如:stmmac-0:01

📝 1. 关键函数:__mdiobus_register

无论是走通用的mdiobus_register方式,还是设备树或 ACPI 方式去注册,最终都会调用此函数,它负责将一个 mii_bus 实例注册到内核设备模型中。

  • 注册 mii_bus:调用 device_register(&bus->dev)。当内核的设备模型核心处理这个调用时,会在 sysfs 中创建该总线的根节点,例如 /sys/bus/mdio_bus/devices/stmmac-0
  • 记录设备和驱动:将当前正在运行的模块设置为该总线的所有者 (bus->owner = owner),并将总线驱动指针设置为 MDIO_bus_driver。这个驱动负责匹配和管理总线上的设备。
  • 扫描并创建设备
    • 处理预声明信息:调用 mdiobus_setup_mdiodev_from_board_info(bus),该函数会查找是否有平台预先为这条总线声明的设备信息。如果有,会基于这些信息立即调用 mdio_device_register 创建设备节点。
    • 触发硬件扫描:如果未指定 phy_mask 屏蔽所有地址的自动扫描,则会调用 mdiobus_create_device,遍历 PHY 地址(0-31)调用 get_phy_device。一旦在某个地址成功探测到硬件,就立即调用 phy_device_register 注册它。phy_device_register 内部最终会调用 device_add,随之在 /sys/bus/mdio_bus/devices/ 下创建该 PHY 的节点(如 stmmac-0:01)。

🌳 2. of_mdio.c:设备树 (Device Tree) 的实现

当硬件信息由设备树描述时,会禁用自动扫描,转而根据设备树子节点创建 PHY 设备。
关键函数of_mdiobus_register 注册 mii_bus 本身,遍历设备树相关的节点,解析 reg 属性获取 PHY 地址,为每个子节点调用 get_phy_devicephy_device_registerphy_device_register 内部会调用 device_add,从而在 sysfs 中生成设备节点。

💻 3. acpi_mdio.c:ACPI 的实现

对于使用 ACPI 进行固件描述的系统,drivers/net/mdio/acpi_mdio.c 提供了类似设备树的实现,主要不同在于它从 ACPI 表中获取信息。

关键函数__acpi_mdiobus_register 注册总线,遍历 ACPI 固件节点的子节点 (fwnode_for_each_child_node),获取每个子节点的地址,并对每个有效地址来创建具体的 PHY 设备。

📋 4. mdio-boardinfo.c:板级信息的实现

对于无法通过设备树或 ACPI 自动发现的特殊硬件,drivers/net/phy/mdio-boardinfo.c 提供了一种板级文件预先声明设备信息的机制。

  • 收集信息:平台初始化代码可以调用 mdiobus_register_board_info 函数,将 mdio_board_info 结构体(包含总线ID、设备地址、驱动名等)添加到一个全局的 mdio_board_list 链表中。
  • 创建节点:当总线控制器驱动调用 __mdiobus_register 注册时,会同步调用 mdiobus_setup_mdiodev_from_board_info来遍历全局链表,查找匹配的设备。一旦找到匹配项,会立即调用 mdio_device_register 向设备模型注册设备。整个过程不涉及硬件自动扫描。

主要流程路径

这里主要说说常用的两个初始化流程路径,主要区别在于是否对phy的自动扫描。

🔍 路径一:基于设备树 (Device Tree) 的注册

设备树明确地描述了硬件连接,因此内核无需“猜测”或“扫描”总线上有哪些设备。

  1. 禁用自动扫描
    当 MAC 驱动调用 of_mdiobus_register() 来注册 MDIO 总线时,内核会首先执行一个关键操作:将总线的 phy_mask 设置为 ~0(所有位都为1)。这个掩码的作用是告诉内核:“不要自动扫描总线上的任何地址”。这确保了 PHY 设备的创建完全由设备树的描述来决定。

  2. 解析设备树节点
    接着,of_mdiobus_register() 函数会遍历设备树中 MDIO 控制器节点下的所有子节点。

  3. 识别并创建 PHY 设备
    对于每一个子节点,内核会调用 of_mdiobus_child_is_phy() 来判断它是否描述了一个 PHY 设备。判断的依据主要是节点的 compatible 属性,例如 "ethernet-phy-ieee802.3-c22"。一旦确认是 PHY 节点,内核就会:

    • 从节点的 reg 属性中读取 PHY 的地址。
    • 调用 of_mdiobus_register_phy() 来创建并注册一个 phy_device 结构体,将这个软件对象与硬件设备关联起来。

这种方式非常精确,避免了总线扫描可能带来的不确定性。

🔍 路径二:传统自动扫描 (Legacy Auto-Scanning)

在不使用设备树或为了兼容旧系统的场景下,内核会采用主动扫描的方式来发现 PHY 设备。

  1. 启动扫描
    当 MDIO 总线通过 __mdiobus_register() 注册时,如果 phy_mask 没有被设置为屏蔽所有地址,内核就会启动一个扫描循环。

  2. 遍历地址空间
    内核会遍历所有可能的 PHY 地址,范围是 0 到 31。对于每一个地址,它会调用 mdiobus_scan() 函数进行探测。

  3. 探测与识别
    mdiobus_scan() 的核心任务是读取该地址上 PHY 的标识符寄存器(通常是寄存器 2 和 3,即 MII_PHYSID1MII_PHYSID2)。

    • 读取 ID:通过 MDIO 总线读取 PHY ID。
    • 验证有效性:如果读取到的值是 0xFFFF0x0000,通常表示该地址上没有有效的 PHY 设备,内核会跳过这个地址。
    • 创建设备:如果读取到一个有效的 PHY ID,内核就会调用 get_phy_device()phy_device_register() 来创建一个 phy_device 实例,并将其注册到系统中。

这个过程可以形象地理解为内核在挨个“敲门”,看哪个房间(地址)里有“人”(PHY 设备)回应。

🤝 驱动匹配

注册成功后,就会在/sys/bus/mdio_bus/devices 目录下生成对应的节点。
无论 PHY 设备是通过设备树创建还是通过扫描发现的,最终都会进入驱动匹配阶段。内核会拿着 phy_device 中的 ID 信息,去和所有已注册的 phy_driver 进行匹配,找到最合适的驱动程序来管理这个 PHY 设备。

💡流程图

flowchart TD
    A[GMAC驱动加载] --> B[调用对应的mdiobus注册函数];
    B --> C{__mdiobus_register};
    
    C --> D[注册mii_bus到设备模型];
    D --> E{扫描策略选择-phy_mask};
    
    E -- 有设备树信息 --> F[处理设备树子节点<br>由of_mdiobus_register驱动];
    F --> F1[遍历子节点];
    F1 --> F2[根据compatible创建设备];
    F2 --> F3{是否为C45 PHY?};
    F3 -- 是 --> F4[构建C45 PHY设备];
    F3 -- 否 --> F5[构建C22 PHY设备];
    
    E -- 无设备树信息 --> G[执行硬件自动扫描];
    
    G --> H[阶段一: C22扫描<br>mdiobus_scan_bus_c22];
    H --> H1[遍历地址31->0];
    H1 --> H2[调用mdiobus_scan_c22];
    H2 --> H3["get_phy_device(..., is_c45=false)"];
    H3 --> H4{PHY ID有效?};
    H4 -- 是 --> H5[phy_device_register];
    
    G --> I[阶段二: C45扫描<br>mdiobus_scan_bus_c45];
    I --> I1[遍历地址31->0];
    I1 --> I2[调用mdiobus_scan_c45];
    I2 --> I3["get_phy_device(..., is_c45=true)"];
    I3 --> I4{PHY ID有效?};
    I4 -- 是 --> I5[phy_device_register];
    
    H5 --> J[设备注册完成];
    I5 --> J;
    F4 --> J;
    F5 --> J;
    
    J --> K[Linux设备模型触发驱动匹配];
    K --> L[调用PHY驱动的probe函数];
    L --> M[PHY设备初始化并可用];

C22和C45

为了规范怎么通过 MDIO 这两根线“说话”,IEEE 802.3 标准定义了两个主要条款:Clause 22Clause 45, 它们是两种不同的 PHY 管理协议。

Clause 22:经典的老规矩

这是最早定义的 MDIO 协议标准(IEEE 802.3u),适用于 10M/100M/1000M 的以太网 PHY。

  • 特点:简单直接,一帧搞定。
  • 寻址限制
    • PHY 地址:5 bit,意味着一条 MDIO 总线上最多挂 32 个 PHY 芯片。
    • 寄存器地址:5 bit,意味着每个 PHY 内部最多只有 32 个 寄存器(地址 0-31)。
  • 帧结构
    一次读写操作就是一个完整的 32-bit 帧(不含前导码):
    ST(01) + OP(读/写) + PHY地址 + 寄存器地址 + TA(切换位) + DATA(16bit)

它的局限性
随着技术发展,千兆/万兆 PHY 需要配置的参数越来越多(比如温度监控、LED 控制、高级节能模式等),32 个寄存器根本不够用!虽然厂商可以通过“页切换”(Page Register)来扩展,但这非常麻烦且不统一。

Clause 45:万兆时代的新规矩

为了解决 Clause 22 寄存器不够用的问题,IEEE 802.3ae (10GbE) 引入了 Clause 45。

  • 特点:复杂,分两步走(先定地址,再读写数据)。
  • 寻址扩展
    • **设备类型 (DEVAD)**:5 bit,用来区分 PHY 内部的不同功能模块(如 PMA、PCS、PHYXS 等)。
    • 寄存器地址:扩展到 16 bit,意味着每个设备类型下可以有 65,536 个寄存器!
  • 操作流程
    一次完整的操作需要发 两帧 数据:
    1. 地址帧:告诉 PHY 我要操作哪个设备类型(DEVAD)和哪个寄存器地址(16bit)。
    2. 数据帧:执行实际的读或写操作。

📊 核心对比:Clause 22 vs Clause 45

特性 Clause 22 (C22) Clause 45 (C45)
寻址能力 5位地址,最多 32 个寄存器 16位地址,最多 65536 个寄存器,分为不同的MMD设备
帧格式 定长帧,结构简单 变长帧,支持间接访问和批量操作
电气特性 帧结构固定 帧结构更复杂,支持更高效率的访问
典型应用 1Gbps 及以下速率的传统 PHY 2.5G、5G、10G 等高速 PHY
寄存器空间 单一的 32 个寄存器空间 分为多个 MMD(如 PMA/PMD, PCS 等)的独立 65536 寄存器空间
兼容性 MDIO 总线的基础协议 可通过 “C45 over C22” 机制在 C22 总线上进行隧道封装访问

注意:许多现代 PHY 同时实现 C22 和 C45,例如 1G 以下的功能在 C22 空间,而 2.5G 及以上的功能在 C45 的 MMD 空间。

在内核设备树的标识

在设备树中,通过 compatible 属性来明确告知内核 PHY 的类型:

  • ethernet-phy-ieee802.3-c22:明确指示这是一个 C22 PHY。
  • ethernet-phy-ieee802.3-c45:明确指示这是一个 C45 PHY。

如果没有指定兼容性字符串,内核会默认将其视为 C22 PHY。