Skip to content

LV170-设备树下的平台设备驱动

前面我们学习了从 device_node 到 platform_device 的转换流程, 转换完成之后操作系统才能够识别和管理设备, 从而与 platform_driver 进行匹配 。这里我们就来学习一下吧。

一、device 如何与 driver 配对

从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个 platform_driver 时,它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的 probe 函数。 (这里具体可以看《05-设备驱动基础/17-平台总线模型/LV015-总线的匹配.md》)

这里我们简单回顾一下,我们看一下 platform_match() 函数:

c
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* (1)When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* (2)Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* (3)Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* (4)fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

1. 强制选择驱动

  • (1)最先比较:是否强制选择某个 driver 。(比较 platform_device.driver_override 和 platform_driver.driver.name)

可以设置 platform_device 的 driver_override,强制选择某个 platform_driver。

2. 设备树匹配

  • (2)设备树信息。(比较 platform_device.dev.of_node 和 platform_driver.driver.of_match_table)

由设备树节点转换得来的 platform_device 中,含有一个结构体: of_node。它的类型为 struct device_node

c
struct device_node {
	const char *name; // 节点的 name 属性
	const char *type; // 节点的 device_type 属性
	phandle phandle;
	const char *full_name;
	struct fwnode_handle fwnode;

	struct	property *properties;//含有 compatible 属性
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;
	struct	device_node *child;
	struct	device_node *sibling;
	// ......
};

如果一个 platform_driver 支持设备树,它的 platform_driver.driver.of_match_table 是一个数组,类型为 struct of_device_id

c
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

使用设备树信息来判断 dev 和 drv 是否配对时:

① 首先,如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 属性比较,若一致则成功,否则返回失败;

② 其次,如果 of_match_table 中含有 type 值,就跟 dev 的 device_type 属性比较,若一致则成功,否则返回失败;

③ 最后,如果 of_match_table 中含有 name 值,就跟 dev 的 name 属性比较,若一致则成功,否则返回失败。

而设备树中建议不再使用 devcie_type 和 name 属性,所以基本上只使用设备节点的 compatible 属性来寻找匹配的 platform_driver。

3. id 匹配

  • (3)比较 platform_device_id ( 比较 platform_device. name 和 platform_driver.id_table [i].name)

id_table 中可能有多项。 platform_driver.id_table 是“ platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data},其中的“ name”表示该 drv 支持的设备的名字, driver_data 是些提供给该 device 的私有数据。

4. name 匹配

  • (4)名称匹配,比较 platform_device.name 和 platform_driver.driver.name

platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。

5. 总结

image-20260120194030708

二、设备树匹配实例

1. 需要哪些条件?

在我们的工作中,驱动要求设备树节点提供什么,我们就得按这要求去编写设备树。匹配过程所要求的东西是固定的:

  • (1)设备树要有 compatible 属性,它的值是一个字符串
c
/{
    alpha {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";

        sdev_led {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "led";
            status = "okay";
            reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
                    0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
                    0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
                    0X0209C000 0X04 /* GPIO1_DR_BASE */
                    0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
        };
    };
};
  • (2)platform_driver 中要有 of_match_table,其中一项的.compatible 成员设置为一个字符串

我们需要定义一个设备树驱动匹配表:

c
/* 设备树匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "led" }, // 这里用于匹配设备树中的 compatible 属性节点
	{ /* Sentinel */ }
};
  • (3)上述 2 个字符串要一致。

2. 基本框架

定义 platform_driver 的时候要用上面的这个表:

c
/**
 * 定义平台驱动结构体
 */
static struct platform_driver g_sdrv_platform = {
    .probe = sdrv_probe,   // 平台设备的探测函数指针
    .remove = sdrv_remove, //  平台设备的移除函数指针
    .driver = {
        .name = "imx6ul-led-driver", // 和设备名称相同时,可以匹配成功
                                   // 会在 /sys/bus/platform/drivers 中创建对应目录,即/sys/bus/platform/drivers/driver-name
        .owner = THIS_MODULE,
		.of_match_table	= led_of_match, /* 设备树匹配表 */
    },
};

基本框架如下:

c
static int sdrv_probe(struct platform_device *pdev)
{
    PRT("probing platform device & driver!pdev->name=%s\n", pdev->name);
    return 0;
}

static int sdrv_remove(struct platform_device *pdev)
{
    PRT("removing platform driver!pdev->name=%s\n", pdev->name);
    return 0;
}

/* 设备树匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "led" }, // 这里用于匹配设备树中的 compatible 属性节点
	{ /* Sentinel */ }
};

/**
 * 定义平台驱动结构体
 */
static struct platform_driver g_sdrv_platform = {
    .probe = sdrv_probe,   // 平台设备的探测函数指针
    .remove = sdrv_remove, //  平台设备的移除函数指针
    .driver = {
        .name = "imx6ul-led-driver", // 和设备名称相同时,可以匹配成功
                                   // 会在 /sys/bus/platform/drivers 中创建对应目录,即/sys/bus/platform/drivers/driver-name
        .owner = THIS_MODULE,
		.of_match_table	= led_of_match, /* 设备树匹配表 */
    },
};

static __init int sdrv_demo_init(void)
{
    int ret = 0;
	printk("*** [%s:%d]Build Time: %s %s, git version:%s LINUX_VERSION=%d.%d.%d ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION, 
           (LINUX_VERSION_CODE >> 16) & 0xff, (LINUX_VERSION_CODE >> 8) & 0xff, LINUX_VERSION_CODE & 0xff);

	// 注册平台驱动
    ret = platform_driver_register(&g_sdrv_platform);
    if (ret) 
    {
        PRT("Failed to register platform driver!ret=%d\n", ret);
        goto err_platform_driver_register;
    }

    //PRT("sdrv_demo module init success!\n");
	return 0;

err_platform_driver_register:
    return ret;
}

static __exit void sdrv_demo_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&g_sdrv_platform);
    //PRT("sdrv_demo module exit!\n");
}

module_init(sdrv_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo xxx.ko 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

3. demo 源码

demo 源码可以看这里:11_device_tree/04_platform_dtb_match

需要注意,这个 demo 中完成了平台设备驱动的设备树匹配过程,但是获取节点的信息的时候还是通过 of 相关函数获取,下一个 demo 我们再研究通过 platform_device 来获取节点信息。

4. 调试技巧

4.1 设备树节点

我们换了设备树后就需要看一下是不是生成了正确的节点,可以到以下目录去看

shell
ls /sys/firmware/devicetree/base
# 或者 
ls /proc/device-tree/

4.2 platform_device 查看

我们创建的节点是会被转化为 platform_device 的,可以看这个目录:

shell
ls /sys/bus/platform/devices
image-20250309193144637

4.3 platform_driver 的信息

以下目录含有注册进内核的所有 platform_driver:

shell
ls /sys/bus/platform/drivers
image-20250309193337157

一个 driver 对应一个目录,进入某个目录后,如果它有配对的设备,可以直接看到。例如这里我们的 imx6ul-led-driver 就匹配了 alpha:sdev_led 设备。

注意: 一个平台设备只能配对一个平台驱动,一个平台驱动可以配对多个平台设备。

三、从 platform_device 获取节点信息

1. 怎么拿到节点?

可以通过设备树节点指定资源, platform_driver 获得资源,那么 platform_drive 怎么获得资源? 我们看一下 probe 函数:

c
static int sdrv_probe(struct platform_device *pdev)
{
    
}

在匹配成功后,会传入 platform_device 的指针,我们前面知道,platform_device.dev.of_node 中存储了节点的相关信息,我们完全可以根据这个来找到对应的节点:

c
struct device_node* np = pdev->dev.of_node;

2. demo 源码

源码可以看这里:11_device_tree/05_platform_get_node,源码中是通过这行获取节点:

image-20260120194241830

3. 开发板测试

我们加载驱动可以看到:

image-20250309195026404