linux platform框架
驱动的分离
主机驱动和设备驱动分隔开来,比如I2C、SPI等等都会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中,一般I2C主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,只需要提供设备信息即可,比如I2C设备的话提供设备连接到了哪个I2C接口上,I2C的速度是多少等等
这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这个就是Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。
前面引出了总线(bus)、驱动(driver)和设备(device)模型,比如I2C、SPI、USB等总线。在SOC中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux提出了platform这个虚拟总线,相应的就有platfor m_driver和platform_device。
platform平台驱动模型简介
platform只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。
platform总线
在SOC中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题,Linux提出了platform这个虚拟总线
linux6.12源码\linux-6.12.1\linux-6.12.1\include\linux\device\bus.h下
struct bus_type {
const char *name;
const char *dev_name;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, const struct device_driver *drv); //匹配函数
int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
void (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
void (*dma_cleanup)(struct device *dev);
const struct dev_pm_ops *pm;
bool need_parent_lock;
};
match函数,此函数很重要,就是完成设备和驱动之间匹配的,总线就是使用match函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match函数有两个参数:dev和drv,这两个参数分别为device和device_driver类型,也就是设备和驱动。
而platform_bus_type就是platform平台总线的一个实例, 里面的成员platform_match函数在F:\linux6.12源码\linux-6.12.1\linux-6.12.1\drivers\base\platform.c下:
static int platform_match(struct device *dev, const 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 */
/*of_driver_match_device函数定义在文件include/linux/of_device.h中。device_driver结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行。*/
if (of_driver_match_device(dev, drv)) //1.第一种设备树(DTB)采用的匹配方式
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv)) //2.第二种ACPI匹配方式。
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; //3.第三种,id_table匹配,每个platform_driver结构体有一个id_table成员变量,这些id信息存放着这个platformd驱动所支持的驱动类型
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); //第四种直接比较驱动和设备的name字段匹配方式
}
对于支持设备树的Linux版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的name字段
platform驱动
platform_driver结构体表示platform驱动

probe函数,当驱动与设备匹配成功以后probe函数就会执行,非常重要的函数!!一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么probe就需要自行实现。
device_driver相当于基类,提供了最基础的驱动框架。plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量
基类里的of_match_table就是采用设备树的时候驱动使用的匹配表,同样是数组,里面成员变量compatible非常重要,因为对于设备树而言,就是通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
此结构体定义在文件linux-6.12.1\include\linux\device\driver.h中,内容如下:
struct device_driver {
const char *name;
const struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct attribute_group **dev_groups;
const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
platform驱动框架如下所示:
示例代码34.2.2.5 platform驱动框架
/* 设备结构体 */
1 struct xxx_dev{
2 struct cdev cdev;
3 /* 设备结构体其他具体内容 */
4 };
5
6 struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
7
8 static int xxx_open(struct inode *inode, struct file *filp)
9 {
10 /* 函数具体内容 */
11 return 0;
12 }
13
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
15 {
16 /* 函数具体内容 */
17 return 0;
18 }
19
20 /*
21 * 字符设备驱动操作集
22 */
23 static struct file_operations xxx_fops = {
24 .owner = THIS_MODULE,
25 .open = xxx_open,
26 .write = xxx_write,
27 };
28
29 /*
30 * platform驱动的probe函数
31 * 驱动与设备匹配成功以后此函数就会执行
32 */
33 static int xxx_probe(struct platform_device *dev)
34 {
35 ......
36 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
37 /* 函数具体内容 */
38 return 0;
39 }
40
41 static int xxx_remove(struct platform_device *dev)
42 {
43 ......
44 cdev_del(&xxxdev.cdev);/* 删除cdev */
45 /* 函数具体内容 */
46 return 0;
47 }
48
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = {
51 { .compatible = "xxx-gpio" },
52 { /* Sentinel */ }
53 };
54
55 /*
56 * platform平台驱动结构体
57 */
58 static struct platform_driver xxx_driver = {
59 .driver = {
60 .name = "xxx",
61 .of_match_table = xxx_of_match,
62 },
63 .probe = xxx_probe,
64 .remove = xxx_remove,
65 };
66
67 /* 驱动模块加载 */
68 static int __init xxxdriver_init(void)
69 {
70 return platform_driver_register(&xxx_driver);
71 }
72
73 /* 驱动模块卸载 */
74 static void __exit xxxdriver_exit(void)
75 {
76 platform_driver_unregister(&xxx_driver);
77 }
78
79 module_init(xxxdriver_init);
80 module_exit(xxxdriver_exit);
81 MODULE_LICENSE("GPL");
82 MODULE_AUTHOR("zuozhongkai");
platform设备
platform驱动已经准备好了,还需要platform设备,否则的话单单一个驱动也做不了什么。platform_device这个结构体表示platform设备,这里要注意,如果内核支持设备树的话就不要再使用platform_device来描述设备了,因为改用设备树去描述了
platform_device在 \linux-6.12.1\linux-6.12.1\include\linux\platform_device.h下定义:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
name表示设备名字,要和所使用的platform驱动的name字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的platform驱动的name字段为“xxx-gpio”,那么此name字段也要设置为“xxx-gpio”。
num_resources表示资源数量,一般为第8行resource资源的大小。
resource表示资源,也就是设备信息,比如外设寄存器等。Linux内核使用resource结构体表示资源,resource结构体定义在include/linux/ioport.h文件里面,内容为:

start和end分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name表示资源名字,flags表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h里面,如下所示:

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用platform_device_register函数将设备信息注册到Linux内核中,此函数原型如下所示:
int platform_device_register(struct platform_device *pdev)
对应着卸载函数
int platform_device_unregister(struct platform_device *pdev)
platform设备信息框架如下所示:
示例代码54.2.3.4 platform设备框架
1 /* 寄存器地址定义*/
2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设1寄存器首地址 */
3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设2寄存器首地址 */
4 #define REGISTER_LENGTH 4
5
6 /* 资源 */
7 static struct resource xxx_resources[] = {
8 [0] = {
9 .start = PERIPH1_REGISTER_BASE,
10 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
11 .flags = IORESOURCE_MEM,
12 },
13 [1] = {
14 .start = PERIPH2_REGISTER_BASE,
15 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
16 .flags = IORESOURCE_MEM,
17 },
18 };
19
20 /* platform设备结构体 */
21 static struct platform_device xxxdevice = {
22 .name = "xxx-gpio",
23 .id = -1,
24 .num_resources = ARRAY_SIZE(xxx_resources),
25 .resource = xxx_resources,
26 };
27
28 /* 设备模块加载 */
29 static int __init xxxdevice_init(void)
30 {
31 return platform_device_register(&xxxdevice);
32 }
33
34 /* 设备模块注销 */
35 static void __exit xxx_resourcesdevice_exit(void)
36 {
37 platform_device_unregister(&xxxdevice);
38 }
39
40 module_init(xxxdevice_init);
41 module_exit(xxxdevice_exit);
42 MODULE_LICENSE("GPL");
43 MODULE_AUTHOR("zuozhongkai");
实验程序编写
在编写platform驱动的时候,首先定义一个platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执行,具体的驱动程序在probe函数里面编写,比如字符设备驱动等等。当定义并初始化好platform_driver结构体变量以后,需要在驱动入口函数里面调用platform_driver_register函数向Linux内核注册一个platform驱动。对应驱动卸载函数中platform_driver_unregister函数卸载platform驱动
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com