Linux PHY驱动注册与调用流程详解

今天来详细拆解一下使用 module_phy_driver() 注册的 PHY 驱动是如何被调用到的整个流程。这个过程可以分为两大阶段:驱动注册驱动调用(匹配与探测)

1. 驱动调用第一阶段:驱动注册 (模块加载时)

这一阶段发生在你执行 insmodmodprobe 加载驱动模块的时候。

  1. 宏展开 (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);
    

    它实际上为你创建了模块的 initexit 函数。

  2. 调用 phy_driver_register
    在模块初始化函数 (phy_driver_mod_init) 被调用时,它会调用 phy_driver_register 函数(或循环注册数组中的每个驱动)。这个函数是 PHY 子系统的核心注册函数。

  3. 添加到全局链表
    phy_driver_register 函数内部会调用 driver_register,这是 Linux 设备模型的核心函数。这个函数会做几件关键事情:

    • phy_driver 中内嵌的 struct device_driver 对象注册到 Linux 设备模型中。
    • 将这个驱动添加到 PHY 子系统维护的一个全局驱动链表中。

至此,你的 PHY 驱动已经安静地躺在内核的某个全局列表里,等待与它匹配的设备出现。


2. 驱动第二阶段:驱动调用 (匹配与探测)

这一阶段发生在系统启动或热插拔时,当有网络设备(如 MAC)去获取和初始化其 PHY 设备的时候。这个过程要复杂得多,是“驱动调用”的核心。

  1. MAC 驱动触发 PHY 查找
    你的以太网 MAC 控制器驱动(例如 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c)在 probe 过程中,会需要配置它的 PHY。它通常会调用 phy_connect 或类似的函数。

  2. PHY 子系统查找 PHY 设备
    phy_connect 会引导内核去查找并初始化对应的 PHY 设备。关键函数调用链是:
    phy_connect -> phy_attach -> phy_probe

  3. phy_probe:匹配和调用的核心
    phy_probe 函数是整个环节的枢纽,它负责:
    a. 遍历设备树(或 MDIO 总线)创建 phy_device
    首先,内核需要知道物理上存在什么 PHY 芯片。这是通过读取 PHY 芯片的 PHY ID(两个标准寄存器)来完成的。
    * 如果使用设备树(Device Tree),of_mdiobus_register 会解析设备树节点,创建 phy_device 并设置其 phy_idc45_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_idphy_driver->phy_id,并通过 phy_driver->match_phy_device 函数(如果驱动定义了)或者通过掩码 (phy_id & phy_driver->phy_id_mask) == phy_driver->phy_id 来判断是否匹配。

    c. 调用驱动的 probe 函数
    一旦找到匹配的 phy_driverphy_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 芯片进行任何必要的初始化配置。

  4. 连接与启动
    phy_probe 成功后,控制权返回到 phy_attachphy_connectphy_connect 最终会调用 phy_start,启动 PHY 的状态机轮询(或中断),PHY 驱动开始正常工作,链接状态改变、数据包收发等功能全部就绪。

3. 驱动调用总结与流程图

整个调用过程可以概括为:

注册 (insmod):
insmod -> module_init() -> phy_driver_register() -> 驱动被加入全局链表

调用 (系统运行时):
MAC驱动Probe -> phy_connect() -> phy_attach() -> phy_probe() ->

  1. 创建phy_device (通过设备树或MDIO扫描)
  2. 匹配 (遍历全局驱动链表,用OF或PHY ID匹配)
  3. 调用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.cphy_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;
}

从这个函数可以清晰地看到匹配的优先级顺序

  1. OF 匹配 (of_match_table)
  2. ACPI 匹配
  3. 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匹配的调用链可以总结如下:

Yes
No
Yes
No
phy_probe(phydev)
遍历所有已注册的 phy_driver
对每个驱动 drv 调用 phy_driver_matches(phydev, drv)
drv->of_match_table 存在?
调用 of_driver_match_device(phydev->dev.of_node, drv->driver)
尝试ACPI或PHY ID匹配
内核比较设备树节点的 compatible 属性
与 drv->of_match_table 中的字符串
匹配成功?
phy_driver_matches 返回 true
尝试ACPI或PHY ID匹配
phy_probe 选择此驱动并调用其 .probe 函数

所以,OF匹配发生在 phy_probe 过程中,由 phy_driver_matches 函数实现,其核心是调用通用的 of_driver_match_device 函数来比对设备树节点的 compatible 属性和驱动中定义的 of_match_table


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐