[Linux] Linux PHY驱动注册与调用流程详解
本文详细解析了Linux内核中PHY驱动的注册与调用流程,分为两个主要阶段: 驱动注册阶段:通过module_phy_driver宏展开为模块初始化函数,调用phy_driver_register将驱动注册到全局链表中。 驱动调用阶段:当MAC驱动触发PHY查找时,通过phy_probe函数完成关键操作: 创建phy_device(通过设备树或MDIO扫描) 进行驱动匹配(优先OF匹配表,其次PH
Linux PHY驱动注册与调用流程详解
今天来详细拆解一下使用
module_phy_driver()注册的 PHY 驱动是如何被调用到的整个流程。这个过程可以分为两大阶段:驱动注册 和 驱动调用(匹配与探测)。
文章目录
1. 驱动调用第一阶段:驱动注册 (模块加载时)
这一阶段发生在你执行 insmod 或 modprobe 加载驱动模块的时候。
-
宏展开 (
module_phy_driver)module_phy_driver是一个定义在include/linux/phy.h中的宏,它的目的是简化 PHY 驱动作者的编码工作。它的定义类似于:#define module_phy_driver(__phy_drivers) \ module_driver(__phy_drivers, phy_driver_register, phy_driver_unregister)其中
module_driver是另一个更通用的宏,它会展开成以下代码:static int __init phy_driver_mod_init(void) { return phy_driver_register(&(__phy_drivers)); } module_init(phy_driver_mod_init); static void __exit phy_driver_mod_exit(void) { phy_driver_unregister(&(__phy_drivers)); } module_exit(phy_driver_mod_exit);所以,当你写:
static struct phy_driver my_phy_drv[] = { { ... }, }; module_phy_driver(my_phy_drv);它实际上为你创建了模块的
init和exit函数。 -
调用
phy_driver_register
在模块初始化函数 (phy_driver_mod_init) 被调用时,它会调用phy_driver_register函数(或循环注册数组中的每个驱动)。这个函数是 PHY 子系统的核心注册函数。 -
添加到全局链表
phy_driver_register函数内部会调用driver_register,这是 Linux 设备模型的核心函数。这个函数会做几件关键事情:- 将
phy_driver中内嵌的struct device_driver对象注册到 Linux 设备模型中。 - 将这个驱动添加到 PHY 子系统维护的一个全局驱动链表中。
- 将
至此,你的 PHY 驱动已经安静地躺在内核的某个全局列表里,等待与它匹配的设备出现。
2. 驱动第二阶段:驱动调用 (匹配与探测)
这一阶段发生在系统启动或热插拔时,当有网络设备(如 MAC)去获取和初始化其 PHY 设备的时候。这个过程要复杂得多,是“驱动调用”的核心。
-
MAC 驱动触发 PHY 查找
你的以太网 MAC 控制器驱动(例如drivers/net/ethernet/stmicro/stmmac/stmmac_main.c)在 probe 过程中,会需要配置它的 PHY。它通常会调用phy_connect或类似的函数。 -
PHY 子系统查找 PHY 设备
phy_connect会引导内核去查找并初始化对应的 PHY 设备。关键函数调用链是:phy_connect->phy_attach->phy_probe -
phy_probe:匹配和调用的核心phy_probe函数是整个环节的枢纽,它负责:
a. 遍历设备树(或 MDIO 总线)创建phy_device:
首先,内核需要知道物理上存在什么 PHY 芯片。这是通过读取 PHY 芯片的 PHY ID(两个标准寄存器)来完成的。
* 如果使用设备树(Device Tree),of_mdiobus_register会解析设备树节点,创建phy_device并设置其phy_id和c45_ids等字段。
* 如果是旧式的板级文件,MDIO 总线扫描过程会读取每个可能的地址上的 PHY ID 寄存器,来发现存在的 PHY 设备并创建phy_device。b. 驱动匹配:
有了代表物理硬件的phy_device后,phy_probe会遍历我们在第一阶段注册到全局链表里的所有phy_driver。
对于每一个phy_driver,它调用phy_driver_matches函数来检查是否匹配。匹配的依据优先级如下:
1. OF 匹配表 (最高优先级): 如果设备树节点里有compatible属性,并且phy_driver提供了of_match_table,内核会使用of_driver_match_device来匹配设备树节点的compatible和驱动of_match_table中的值。
2. PHY ID 匹配: 如果设备树没有提供compatible属性,则回退到传统的 PHY ID 匹配。内核会检查phy_device->phy_id和phy_driver->phy_id,并通过phy_driver->match_phy_device函数(如果驱动定义了)或者通过掩码(phy_id & phy_driver->phy_id_mask) == phy_driver->phy_id来判断是否匹配。c. 调用驱动的
probe函数:
一旦找到匹配的phy_driver,phy_probe就会调用该驱动结构体中定义的.probe函数指针,也就是你编写驱动时填充的probe函数。c static struct phy_driver my_phy_drv = { .phy_id = MY_PHY_ID, .phy_id_mask = MY_PHY_ID_MASK, .name = "My Awesome PHY", .probe = my_phy_probe, // <--- 这里被调用 .config_init = my_phy_config_init, ... };
你的my_phy_probe函数就此被执行!它负责对 PHY 芯片进行任何必要的初始化配置。 -
连接与启动
phy_probe成功后,控制权返回到phy_attach和phy_connect。phy_connect最终会调用phy_start,启动 PHY 的状态机轮询(或中断),PHY 驱动开始正常工作,链接状态改变、数据包收发等功能全部就绪。
3. 驱动调用总结与流程图
整个调用过程可以概括为:
注册 (insmod):insmod -> module_init() -> phy_driver_register() -> 驱动被加入全局链表
调用 (系统运行时):
MAC驱动Probe -> phy_connect() -> phy_attach() -> phy_probe() ->
- 创建phy_device (通过设备树或MDIO扫描)
- 匹配 (遍历全局驱动链表,用OF或PHY ID匹配)
- 调用Probe (找到后,调用匹配驱动的
.probe函数)

希望这个详细的描述能帮助你彻底理解 module_phy_driver() 背后的完整调用流程。
4. OF匹配
设备树(OF)的匹配发生在 PHY 设备探测 (phy_probe) 阶段,并且是匹配流程中的最高优先级。
4.1 匹配时机:在 phy_probe 函数中
具体来说,当内核需要初始化一个 PHY 设备时,会调用 drivers/net/phy/phy_device.c 中的 phy_probe 函数。在这个函数内部,会遍历所有已注册的 phy_driver,并针对每一个驱动调用 phy_driver_matches 函数来判断它是否与当前 phy_device 匹配。
4.2 具体匹配的代码
匹配的核心逻辑位于 drivers/net/phy/phy_device.c 的 phy_driver_matches 函数中。我们来看一下它的代码(基于 Linux 内核 6.x 版本的精简和解释):
static bool phy_driver_matches(struct phy_device *phydev,
struct phy_driver *drv)
{
// 1. 最高优先级:OF 匹配表 (of_match_table) 匹配
if (drv->of_match_table) {
// 调用通用的OF匹配函数,比较设备树节点的'compatible'和驱动of_match_table中的值
if (of_driver_match_device(phydev->mdio.dev.of_node, drv->mdiodrv.driver))
return true;
}
// 2. 次优先级:ACPI 匹配 (在x86等平台,此处不讨论)
if (drv->mdiodrv.driver.acpi_match_table) {
// ... ACPI 匹配逻辑 ...
}
// 3. 最低优先级:传统的 PHY ID 匹配
if (drv->match_phy_device) {
// 如果驱动提供了自定义的匹配函数,则使用它
return drv->match_phy_device(phydev);
} else if (drv->phy_id != 0) {
// 否则,使用标准的 (phy_id & phy_id_mask) == drv->phy_id 进行匹配
return (phydev->phy_id & drv->phy_id_mask) == drv->phy_id;
}
// 如果没有任何匹配条件,则默认不匹配
return false;
}
从这个函数可以清晰地看到匹配的优先级顺序:
- OF 匹配 (
of_match_table) - ACPI 匹配
- PHY ID 匹配 (传统方式)
4.3 核心中的核心:of_driver_match_device 函数
phy_driver_matches 函数中的 of_driver_match_device 是执行实际OF匹配的通用内核函数(位于 include/linux/of_device.h)。它的工作很简单:
- 输入:
phydev->mdio.dev.of_node:指向代表该PHY设备的设备树节点(struct device_node)的指针。drv->mdiodrv.driver:指向phy_driver内嵌的通用device_driver结构的指针。
- 过程:它会提取设备树节点的
compatible属性,然后与drv->mdiodrv.driver.of_match_table中定义的of_device_id数组进行比对。 - 输出:如果节点的
compatible属性值与of_match_table中的任何一个条目匹配,则返回true,否则返回false。
4.4 一个驱动代码示例
一个支持OF匹配的 PHY 驱动会这样定义自己:
static const struct of_device_id my_phy_of_match[] = {
{ .compatible = "vendor,super-phy-1234g" }, // 与设备树中的 compatible 字符串对应
{ .compatible = "vendor,super-phy-5678k" },
{ } // 终止条目
};
MODULE_DEVICE_TABLE(of, my_phy_of_match); // 允许模块通过OF名称自动加载
static struct phy_driver my_phy_drv[] = {
{
.phy_id = MY_PHY_ID, // 传统ID,用于非OF或回退匹配
.phy_id_mask = MY_PHY_ID_MASK,
.name = "Vendor Super PHY",
.of_match_table = my_phy_of_match, // 关键:指向OF匹配表
.probe = my_phy_probe,
.config_init = my_phy_config_init,
.suspend = my_phy_suspend,
.resume = my_phy_resume,
}
};
module_phy_driver(my_phy_drv);
4.5 OF匹配总结与流程
整个OF匹配的调用链可以总结如下:
所以,OF匹配发生在 phy_probe 过程中,由 phy_driver_matches 函数实现,其核心是调用通用的 of_driver_match_device 函数来比对设备树节点的 compatible 属性和驱动中定义的 of_match_table。
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)
更多推荐



所有评论(0)