Linux以太网之MDIO扫描注册phy及C22和C45
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_device 和 phy_device_register。phy_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) 的注册
设备树明确地描述了硬件连接,因此内核无需“猜测”或“扫描”总线上有哪些设备。
禁用自动扫描
当 MAC 驱动调用of_mdiobus_register()来注册 MDIO 总线时,内核会首先执行一个关键操作:将总线的phy_mask设置为~0(所有位都为1)。这个掩码的作用是告诉内核:“不要自动扫描总线上的任何地址”。这确保了 PHY 设备的创建完全由设备树的描述来决定。解析设备树节点
接着,of_mdiobus_register()函数会遍历设备树中 MDIO 控制器节点下的所有子节点。识别并创建 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 设备。
启动扫描
当 MDIO 总线通过__mdiobus_register()注册时,如果phy_mask没有被设置为屏蔽所有地址,内核就会启动一个扫描循环。遍历地址空间
内核会遍历所有可能的 PHY 地址,范围是 0 到 31。对于每一个地址,它会调用mdiobus_scan()函数进行探测。探测与识别
mdiobus_scan()的核心任务是读取该地址上 PHY 的标识符寄存器(通常是寄存器 2 和 3,即MII_PHYSID1和MII_PHYSID2)。- 读取 ID:通过 MDIO 总线读取 PHY ID。
- 验证有效性:如果读取到的值是
0xFFFF或0x0000,通常表示该地址上没有有效的 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 22 和 Clause 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 个寄存器!
- 操作流程:
一次完整的操作需要发 两帧 数据:- 地址帧:告诉 PHY 我要操作哪个设备类型(DEVAD)和哪个寄存器地址(16bit)。
- 数据帧:执行实际的读或写操作。
📊 核心对比: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。






