大家好,我是王鸽,今天这篇文章承接总线驱动设备,具体化实例,好了进入主题。Linux发明了一种虚拟的总线, 称为platform总线,platform 总线是 bus_type 的一个具体实例,相应的设备称为platform_device,而驱动成为platform_driver。物理总线 vs 虚拟总线
- 物理总线:实际存在的硬件总线(如 I2C、SPI、PCI、USB),设备和驱动通过物理总线通信;
- Platform 总线:Linux 内核定义的 虚拟总线(
bus_type platform_bus_type),专为 SoC 中没有标准物理总线的外设(如片上定时器、GPIO、UART 控制器)设计,本质是内核的软件抽象。
定义在文件 drivers/base/platform.c, platform 总线定义如下:struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops,};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:staticintplatform_match(struct device *dev, struct device_driver *drv){struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* 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;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);}
驱动和设备的匹配有四种方法,
第一种匹配方式:of_driver_match_device(dev, drv),OF 类型的匹配,也就是设备树采用的匹配方式,device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
第二种匹配方式:ACPI 匹配方式,
Linux ACPI 匹配是内核通过 _HID(硬件 ID)、_CID(兼容 ID) 与驱动的 acpi_match_table 进行字符串比对。
比如:
#include<linux/acpi.h>static const struct acpi_device_id my_acpi_ids[] = { { "ACME0001", 0 }, // 匹配 _HID { "ACME0002", 0 }, // 匹配 _CID { "PRP0001", 0 }, // 兼容 DT 模式 { } // 结束标记};MODULE_DEVICE_TABLE(acpi, my_acpi_ids); // 导出给模块自动加载
另外调试与查看
- 查看系统 ACPI 设备:
cat /sys/bus/acpi/devices/*/hid - 查看驱动 ACPI 匹配表:
modinfo my_driver.ko | grep alias - 内核日志:
dmesg | grep -i acpi 查看匹配与 probe 过程
第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
platform_match_id(pdrv->id_table, pdev) != NULL
具体函数如下:
static const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev){while (id->name[0]) {if (strcmp(pdev->name, id->name) == 0) { pdev->id_entry = id;return id; } id++; }return NULL;}
第四种匹配方式
直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
(strcmp(pdev->name, drv->name) == 0);
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。
platform _driver 结构体表示platform驱动,该结构体定义在文件include/linux/platform_device.h,内容如下:struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;};
probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。device_driver 结构体定义在 include/linux/device.h
of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中.
/* * Struct used for matching a device */struct of_device_id{char name[32];char type[32];char compatible[128];const void *data;};
compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
const struct platform_device_id *id_table;
另外id_table 表,之前 platform 总线匹配驱动和设备的时候采用的第三种匹配方法, id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:
struct platform_device_id {char name[PLATFORM_NAME_SIZE];kernel_ulong_t driver_data;};
其实platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张“platform” 的壳,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。
典型示例
设备注册(设备树方式,主流)
在设备树(.dts)中定义设备:
my_platform_device { compatible = "my_driver,platform_dev"; // 匹配关键字 reg = <0x12340000 0x1000>; // 寄存器地址 interrupts = <IRQ_TYPE_EDGE_RISING 5>; // 中断号};
内核解析设备树后,自动创建 device 结构体并注册到 platform 总线。
驱动注册(匹配 + probe)
#include<linux/module.h>#include<linux/platform_device.h>// 匹配表(和设备树的compatible对应)static const struct of_device_id my_dev_of_match[] = { { .compatible = "my_driver,platform_dev" }, { }, // 结束符};MODULE_DEVICE_TABLE(of, my_dev_of_match);// 驱动的probe函数(匹配成功后执行)staticintmy_platform_probe(struct platform_device *pdev){ printk(KERN_INFO "设备匹配成功,初始化硬件!\n"); // 1. 获取设备树参数(寄存器地址、中断号) struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 2. 映射寄存器地址 void __iomem *reg_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); // 3. 初始化硬件(比如设置寄存器) writel(0x1234, reg_base + 0x00); return 0;}// 驱动移除函数staticintmy_platform_remove(struct platform_device *pdev){ printk(KERN_INFO "驱动移除,释放资源!\n"); return 0;}// 定义platform驱动结构体static struct platform_driver my_platform_driver = { .driver = { .name = "my_platform_driver", .of_match_table = my_dev_of_match, // 设备树匹配表 }, .probe = my_platform_probe, .remove = my_platform_remove,};// 模块加载/卸载module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");
- 加载驱动模块后,platform 总线调用
match 函数,对比驱动的 of_match_table 和设备树的 compatible; - 匹配成功后,调用
my_platform_probe 初始化硬件; - 卸载驱动时,调用
my_platform_remove 释放资源。
注册总线→注册设备 / 驱动→总线匹配→执行 probe 初始化硬件。dmesg|grep my_dev
关键调试技巧
- 查看 Platform 设备列表:
ls /sys/bus/platform/devices/ - 查看 Platform 驱动列表:
ls /sys/bus/platform/drivers/ ls /sys/bus/platform/drivers/my_platform_driver/- 查看内核匹配日志:
dmesg | grep -i platform
接下来讲一下platform设备,这个因为设备树出现导致使用少了。platform 设备
platform 驱动已经准备好了,我们还需要 platform 设备,否则的话单单一个驱动也做不了什么。 platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。设备树(DTB)解析后,内核将硬件设备封装成 device 结构体,挂到对应总线上;当然了,你如果一定要用 platform_device 来描述设备信息的话也是可以的。 platform_device 结构体定义在文件include/linux/platform_device.h 中struct platform_device {constchar *name;int id;bool id_auto;struct device dev;u32 num_resources;struct resource *resource;conststruct platform_device_id *id_entry;/* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata archdata;};
| |
| 要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-led”,那么此 name字段也要设置为“xxx-led” |
| |
| - 指向 struct resource 数组,描述设备的硬件资源:- DMA 通道(IORESOURCE_DMA);- 驱动匹配后,可通过 platform_get_resource() 获取具体资源。 |
| 通用设备结构体(基类)- 所有 Linux 设备的通用父结构体,包含设备的核心属性:- platform_data:设备私有数据(旧版方式,现代推荐用 of_device_id/ 设备树);- kobj:设备的内核对象(sysfs 节点);- 是 platform 设备融入 Linux 设备模型的核心。 |
structresource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent, *sibling, *child; };
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址, name 表示资源名字, flags 表示资源类型。
在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。
intplatform_device_register(struct platform_device *pdev){ device_initialize(&pdev->dev); arch_setup_pdev_archdata(pdev); return platform_device_add(pdev);}EXPORT_SYMBOL_GPL(platform_device_register);
函数参数和返回值含义如下:pdev:要注册的 platform 设备。返回值: 负数,失败; 0:成功。
如果要注销掉platform设备,对应调用函数不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉。
举一个例子
#include<linux/module.h>#include<linux/platform_device.h>#include<linux/init.h>#include<linux/kernel.h>// 定义设备私有数据(可选,用于存储设备相关的自定义信息)struct demo_dev_data { int id; char name[32];};// 设备私有数据初始化static struct demo_dev_data demo_data = { .id = 1001, .name = "platform_demo_device",};// 设备资源定义(可选,比如外设的寄存器地址、中断号等)static struct resource demo_resource[] = { // 示例:定义一个内存资源(寄存器基地址) [0] = { .start = 0x12340000, // 模拟的寄存器起始地址 .end = 0x123400FF, // 模拟的寄存器结束地址 .flags = IORESOURCE_MEM, // 资源类型:内存 }, // 示例:定义一个中断资源 [1] = { .start = 50, // 模拟的中断号 .end = 50, .flags = IORESOURCE_IRQ, // 资源类型:中断 },};// platform 设备结构体定义static struct platform_device demo_platform_device = { .name = "demo_platform_dev", // 设备名(需与驱动的 name 匹配才能成功匹配) .id = PLATFORM_DEVID_AUTO, // 自动分配设备 ID .dev = { .platform_data = &demo_data, // 挂载私有数据 }, .num_resources = ARRAY_SIZE(demo_resource), // 资源数量 .resource = demo_resource, // 资源数组};// 模块初始化函数:注册 platform 设备staticint __init demo_platform_device_init(void){ int ret; // 注册 platform 设备 ret = platform_device_register(&demo_platform_device); if (ret) { pr_err("Failed to register platform device: %d\n", ret); return ret; } pr_info("Platform device registered successfully!\n"); return 0;}// 模块退出函数:注销 platform 设备staticvoid __exit demo_platform_device_exit(void){ // 注销 platform 设备 platform_device_unregister(&demo_platform_device); pr_info("Platform device unregistered successfully!\n");}// 模块入口和出口宏module_init(demo_platform_device_init);module_exit(demo_platform_device_exit);// 模块信息(必须)MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("Demo for Platform Device Registration");MODULE_VERSION("1.0");
用于存储设备的自定义信息(如设备 ID、名称等),通过 platform_data 挂载到 platform_device 的 dev 成员中,驱动匹配后可读取这些数据。platform_data 挂载自定义数据,是驱动获取硬件信息的重要方式.name设备名称,必须与 platform 驱动的名称一致 才能完成总线匹配。.id设备 ID,PLATFORM_DEVID_AUTO 表示自动分配。.dev.platform_data.num_resources 和 .resource:指定设备资源的数量和数组。
# 查看已注册的 platform 设备cat /sys/bus/platform/devices/demo_platform_dev/name# 查看内核日志dmesg |grep "Platform device"
在现在设备树方式开发中(主流),只要编写底层驱动就可以了!总线注册 → 设备注册(入列→遍历驱动匹配→未绑定) → 驱动注册(入列→遍历设备匹配)→ 匹配成功→probe初始化→已绑定 → 卸载→remove清理→解绑 | | |
|---|
| platform_bus_init() | |
| platform_device_register() | |
| platform_driver_register() | |
| platform_match() | |
| | |
| | |
| platform_driver_unregister() | |
| platform_device_unregister() | |
注意
- 设备树定义的 Platform 设备,其
/sys/bus/platform/devices/ 下的目录名是节点名 + @reg 起始地址(如 my_platform_dev@12340000),而非 compatible 字符串; driver 软链接仅在驱动通过 of_match_table 匹配设备树 compatible 成功后生成,指向 /sys/bus/platform/drivers/驱动名;
// 加入设备树的根节点(如 /soc 下)my_platform_dev@12340000 { compatible = "my_driver,platform_dev"; reg = <0x12340000 0x1000>; interrupts = <IRQ_TYPE_EDGE_RISING 5>; status = "okay"; // 启用节点};//驱动代码// 设备树匹配表static const struct of_device_id my_dev_of_match[] = { { .compatible = "my_driver,platform_dev" }, { },};MODULE_DEVICE_TABLE(of, my_dev_of_match);//........static struct platform_driver my_platform_drv = { .probe = my_dev_probe, .remove = my_dev_remove, .driver = { .name = "my_platform_driver", .of_match_table = of_match_ptr(my_dev_of_match), },};
编译设备树并烧录,启动系统
# 1. 查看设备目录(节点名+@地址)ls /sys/bus/platform/devices/my_platform_dev@12340000/# 2. 加载驱动前:无 driver 软链接ls /sys/bus/platform/devices/my_platform_dev@12340000/driver # 报错:无此文件# 3. 加载驱动insmod my_platform_driver.ko# 4. 加载后:查看 driver 软链接ls -l /sys/bus/platform/devices/my_platform_dev@12340000/driver# 输出示例:# lrwxrwxrwx 1 root root 0 Jan 1 00:00 driver -> ../../../../bus/platform/drivers/my_platform_driver# 5. 查看驱动目录ls /sys/bus/platform/drivers/my_platform_driver/
谢谢点赞阅读收藏!