文章目录
- 一、简介
- 二、platform总线
- 三、platform设备和驱动的匹配过程
- 四、platrom驱动和platform设备
- 五、platform驱动设计
- 六、代码示例
🔺【linux内核系列文章】
👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章!
- 1、《linux内核数据结构分析之哈希表》
- 2、《一文总结linux内核通知链》
- 3、《linux内核中的debugfs》
- 4、《linux内核数据结构分析之链表》
- 5、《linux media子系统分析之media控制器设备》
- 6、《V4L2-PCI驱动程序样例分析(上)》
- 7、《v4l2框架分析之v4l2_fh》
- 8、《 v4l2框架分析之v4l2_subdev》
- 9、《 v4l2框架分析之v4l2_device》
- 10、《v4l2框架分析之video_device》
- 11、《linux内核重要函数 | do_initcalls》
- 12、《Linux设备驱动模型 | bus》
- 13、《linux内核裁剪随想》
- 14、《基于ARM64分析linux内核的链接脚本vmlinux.lds.S》
- 15、《linux内核start_kernel函数的早期操作》
- 16、《start_kernel函数详解系列之proc_caches_init》
- 17、《start_kernel函数详解系列之fork_init》
- 18、《start_kernel函数详解系列之rcu_init》
- 19、《start_kernel函数详解系列之proc_root_init》
- 20、《start_kernel详解系列之【setup_arch】》
- 21、《linux内核如何启动用户空间进程(上)》
- 22、《linux内核如何启动用户空间进程(下)》
- 23、《一文总结linux内核的完成量机制》
- 24、《一文总结linux内核设备驱动的注册和卸载》
- 25、《linux内核的启动加载程序的总结》
- 26、《linux内核入口:head.o》
- 27、《挂载根文件系统之rootfs》
- 28、《mount系统调用剖析》
- 29、《devtmpfs文件系统分析》
- 30、《linux内核的kthreadd线程》
- 31、《linux内核的进程调度—调度策略》
- 32、《linux系统调用实践(Arm架构)》
- 33、《对linux内核__init机制的实践》
- 34、《linux 内核中EXPORT_SYMBOL()分析与实践》
- 35、《linux内核如何挂载根文件系统》
- 36、《linux内核如何唤醒线程》
- 37、《linux内核的init线程》
- 38、《linux内核伪文件系统—sysfs分析》
- 39、《linux 内核设备模型的初始化(上)》
- 40、《linux 内核设备模型的初始化(下)》
- 41、《linux内核伪文件系统—proc分析》
- 42、《linux中断管理—workqueue工作队列》
- 43、《linux中断管理—软中断》
- 44、《linux中断管理 | tasklet》
- 45、《linux中断管理 | 中断管理框架(01)》
- 46、《linux内存管理 | 分配物理内存页面》
- 47、《linux内存管理 | 释放内存页面》
- 48、《对linux内核设备的注册机制和查找机制分析》
- 49、《linux内核设备驱动的注册机制》
一、简介
相关文件;
- /include/linux/platform_device.h
- /drivers/base/platform.c
在linux设备驱动中,有许多没有特定总线的外设驱动,在实际开发中,又需要使用到总线、驱动和设备模型这三个概念,故而linux提供了platform这个虚拟总线,挂接在platform总线上的驱动称为platform驱动,由struct platform_driver
描述,挂接在platorm总线上的设备称为platform设备,由struct platform_device
描述。
在linux内核的驱动源码中,可以看见很多基于platform驱动框架的驱动案例实现。
二、platform总线
在linux内核中,使用struct bus_type
描述一个总线,为了抽象出platform这个虚拟总线,其定义如下(/drivers/base/platform.c):
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总线的注册由platform_bus_init()
完成:
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
该函数在linux内核启动过程中,在driver_init()
中被调用,从而向linux内核注册了platform总线。
三、platform设备和驱动的匹配过程
在定义platform总线的时候就定了该总线下设备和驱动的具体匹配过程,由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);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* 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);
}
从上述代码可知,platform设备和驱动的匹配分为了四种方式处理:
- 1、基于设备树的匹配方式。
struct device_driver
结构中有个名为of_match_table
的成员变量,此成员变量保存着驱动的compatible匹配表,
在设备树中的每个设备节点的compatible
属性会和of_match_table
表中的所有成员比较,查看是否存在相同的条目,如果存在则表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行(这个过程是由linux设备驱动模型中的总线去完成)。
-
2、ACPI的匹配方式。
-
3、id_table 匹配。
每个struct platform_driver
有一个id_table
成员变量,用于保存很多id信息,这些id信息存放着这个platform驱动所支持的驱动类型。
- 4、比较
name
字段
如果第三种匹配方式的id_table
不存在,就直接比较驱动和设备的name
字段是否相等,如果相等则匹配成功;反之匹配不成功。
一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般
用的最多的还是第四种,也就是直接比较驱动和设备的name
字段,因为这种方式最简单了。
四、platrom驱动和platform设备
前文已经提到:挂接在platform总线上的驱动称为platform驱动,由struct platform_driver
描述,挂接在platorm总线上的设备称为platform设备,由struct platform_device
描述。要想开发基于platform设备驱动驱动框架的驱动程序,一定离不开这两个数据结构。首先来看看platform驱动的描述者struct platform_driver
,该结构定义如下(/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;
bool prevent_deferred_probe;
};
- probe:当驱动与设备匹配成功以后
.probe
函数就会执行,这是一个非常重要的函数,一般驱动的提供者都会设计该函数。 - remove:当platform驱动移除的时候,
.remove
指向的函数将执行。 - shutdown、suspend和resume:与电源管理相关的函数。
- driver:为device_driver结构体变量,相当于C++中的基类,提供了最基础的驱动框架。plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
- id_table:描述platform设备的id_table表,platform总线匹配驱动和设备的时候会使用。
- prevent_deferred_probe:布尔类型变量(内部参数),用于防止驱动程序请求延迟
probe
,以避免进一步的徒劳的探测尝试。
再看看platform设备的描述者struct platform_device
,定义如下(/include/linux/platform_device.h):
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
- name :name表示设备名字,该参数要和所使用的platform驱动的name字段相同,否则设备就无法匹配到对应的驱动。
- id:设备id。
- dev:linux内核面向对象的具体体现,用于描述platform_device的基类。
- num_resources:表示资源的数量。
- resource:表示资源,也就是设备的信息,比如外设寄存器等。Linux内核使用
struct resource
结构体表示资源。 - id_entry:platform设备对应的id匹配表实例,在platform总线匹配驱动和设备的时候会使用到。
五、platform驱动设计
platform驱动设计的总体思路分为两种:
- (1)使用【struct platform_device + struct platform_driver】的方式实现。
在这种实现方式中,需要实现描述设备信息的struct platform_device
结构,并需要使用platform_device
来描述具体的设备信息,然后使用platform_device_register()
函数将设备信息注册到 Linux 内核中;如果不再使用platform了,可以通过platform_device_unregister()
函数注销相应的platform设备。
这种方式在不支持设备树的linux内核中使用!
- (2)使用【struct platform_driver + 设备树】的方式来实现。
在编写 platform 驱动的时候,首先定义一个struct platform_driver
结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe
函数。当驱动和设备匹配成功以后.probe
函数就会执行,具体的驱动程序在 probe 函数里面编写。当定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register()
函数向Linux内核注册一个platform驱动。
注意,如果linux内核支持设备树,就可以不需要再使用
struct platform_device
来描述设备,直接使用设备树去描述设备的信息。当然,如果
一定要用struct platform_device
来描述设备信息也是可以的。
基于新版的linux内核的platform驱动的开发,通常是通过设备树来描述设备信息,我们只需要实现对应的platform驱动即可。
六、代码示例
本小节基于【struct platform_driver + 设备树】给出一个基本的platform驱动的设计结构。
首先使用设备树描述设备的信息:
debug_device_node {
compatible = "iriczhao_debug";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};
上述代码描述了一个名为debug_device_node
的设备节点,给出了compatible
属性值。
platform驱动设计:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
static int platform_demo_probe(struct platform_device *dev)
{ printk("\r\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r\n");
printk("do platform_demo_probe\r\n");
printk("\r\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r\n");
return 0;
}
static int platform_demo_remove(struct platform_device *dev)
{
printk("do platform_demo_remove\r\n");
return 0;
}
static const struct of_device_id platform_demo_id[] = {
{ .compatible = "iriczhao_debug" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, platform_demo_id);
static struct platform_driver platform_demo_driver = {
.probe = platform_demo_probe,
.remove = platform_demo_remove,
.driver = {
.name = "dd",
.of_match_table = platform_demo_id,
}
};
static int __init platform_demo_init(void)
{
printk("do platform_demo_init\r\n");
return platform_driver_register(&platform_demo_driver);
}
static void __exit platform_demo_exit(void)
{
printk("do platform_demo_exit\r\n");
platform_driver_unregister(&platform_demo_driver);
}
module_init(platform_demo_init);
module_exit(platform_demo_exit);
MODULE_AUTHOR("IRIC");
MODULE_LICENSE("GPL");
以模块方式构建上述代码,运行后结果如下:
从上述结果可知:
platform驱动和对应的设备匹配成功,且.probe
指向的函数得以执行,当模块退出时,platform驱动将被移除,这时候.remove
指向的函数得以执行。结果符合程序预期效果!