LV170-设备树下的平台设备驱动
前面我们学习了从 device_node 到 platform_device 的转换流程, 转换完成之后操作系统才能够识别和管理设备, 从而与 platform_driver 进行匹配 。这里我们就来学习一下吧。
一、device 如何与 driver 配对
从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个 platform_driver 时,它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的 probe 函数。 (这里具体可以看《05-设备驱动基础/17-平台总线模型/LV015-总线的匹配.md》)
这里我们简单回顾一下,我们看一下 platform_match() 函数:
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
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 :
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. 总结

二、设备树匹配实例
1. 需要哪些条件?
在我们的工作中,驱动要求设备树节点提供什么,我们就得按这要求去编写设备树。匹配过程所要求的东西是固定的:
- (1)设备树要有 compatible 属性,它的值是一个字符串
/{
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 成员设置为一个字符串
我们需要定义一个设备树驱动匹配表:
/* 设备树匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "led" }, // 这里用于匹配设备树中的 compatible 属性节点
{ /* Sentinel */ }
};- (3)上述 2 个字符串要一致。
2. 基本框架
定义 platform_driver 的时候要用上面的这个表:
/**
* 定义平台驱动结构体
*/
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 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 设备树节点
我们换了设备树后就需要看一下是不是生成了正确的节点,可以到以下目录去看
ls /sys/firmware/devicetree/base
# 或者
ls /proc/device-tree/4.2 platform_device 查看
我们创建的节点是会被转化为 platform_device 的,可以看这个目录:
ls /sys/bus/platform/devices
4.3 platform_driver 的信息
以下目录含有注册进内核的所有 platform_driver:
ls /sys/bus/platform/drivers
一个 driver 对应一个目录,进入某个目录后,如果它有配对的设备,可以直接看到。例如这里我们的 imx6ul-led-driver 就匹配了 alpha:sdev_led 设备。
注意: 一个平台设备只能配对一个平台驱动,一个平台驱动可以配对多个平台设备。
三、从 platform_device 获取节点信息
1. 怎么拿到节点?
可以通过设备树节点指定资源, platform_driver 获得资源,那么 platform_drive 怎么获得资源? 我们看一下 probe 函数:
static int sdrv_probe(struct platform_device *pdev)
{
}在匹配成功后,会传入 platform_device 的指针,我们前面知道,platform_device.dev.of_node 中存储了节点的相关信息,我们完全可以根据这个来找到对应的节点:
struct device_node* np = pdev->dev.of_node;2. demo 源码
源码可以看这里:11_device_tree/05_platform_get_node,源码中是通过这行获取节点:

3. 开发板测试
我们加载驱动可以看到:
