platform_bus
platform_bus是一种虚拟总线,作用就是将设备信息和驱动程序进行分离,platform_bus会维护两条线,一条是设备,一条是驱动。当一个设备被注册到总线上面的时候,总线会去搜索对应的驱动,反之如果驱动被注册到总线,总线也会去找对应的驱动。描述设备信息的方式有2种,一种是通过手动填充paltform_device结构体的方式进行,另一种是通过设备树的方式进行。
通过手动填充platform_device描述设备信息
platform_device是描述设备信息的对象,当我们描述设备信息的时候需要做的就是对应进行填充,其结构信息如下:
//include/linux/platform_device.h
struct platform_device {
const char *name; /* 设备的名称,驱动和设备匹配的桥梁 */
int id; /* 设备id,用于给插入给该总线并且具有相同name的设备编号,如果只有一个设备的话填-1 */
bool id_auto;
struct device dev; /* 结构体中内嵌的device结构体 */
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,这是和驱动匹配的重要信息,具体的匹配方式后文会说明。还有就是num_resources描述的是设备资源数量,resource描述的是设备资源信息,下面先介绍这2个成员的具体信息。
设备信息 resource
设备信息主要就是地址信息、中断信息等等,我们需要将这些信息封装,利用的就是struct resource,其结构如下:
//include/linux/ioport.h
struct resource {
resource_size_t start; /* 资源的起始地址,如果是IO资源,就是起始物理地址,如果是中断资源就是中断号 */
resource_size_t end; /* 资源结束地址,如果是IO资源,就是映射的最后一个物理地址,如果是中断资源就不填 */
const char *name; /* 资源名称 */
unsigned long flags; /* 资源类型,在ioport.h中有定义,例如IORESOURCE_IRQ表示一个中断资源 */
unsigned long desc;
struct resource *parent, *sibling, *child;
};
通过填充结构体成员的信息,来描述一个硬件资源,例如:
//IO资源信息
struct resource res= {
.start = 0x10000000,
.end = 0x20000000-1,
.flags = IORESOURCE_MEM
};
//中断资源信息
struct resource res = {
.start = 10,
.flags = IORESOURCE_IRQ,
};
在ioport.h中也有定义宏,通过宏也可填充资源描述信息
//IO资源信息宏定义描述
//#define DEFINE_RES_MEM(_start, _size)
struct resource res = DEFINE_RES_MEM(0x10000000, 1024);
//中断资源嘻嘻宏定义描述
//#define DEFINE_RES_IRQ(_irq)
struct resource res = DEFINE_RES_IRQ(10);
如果存在多个资源,可以通过写成数组的方式进行描述
struct resource res[] = {
[0] = {
.start = 0x10000000,
.end = 0x20000000-1,
.flags = IORESOURCE_MEM
},
[1] = {
.start = 10,
.flags = IORESOURCE_IRQ,
},
[2] = DEFINE_RES_IRQ(11);
}
platform_device的注册和注销
int platform_device_register(struct platform_device *pdev); /* 注册 */
void platform_device_unregister(struct platform_device *pdev); /* 注销 */
通过设备树描述设备信息
设备树(Device Tree)是一种描述设备信息的数据结构,每一个设备在设备树中是以一个节点的形式进行表现,Linux内核会将设备树中的设备信息自动构造成platform_device结构,具体的设备树格式和含义后续再谈,我们先假设有设备树文件如下:
&soc {
/* xxx */
fingerprint_goodix {
compatible = "factory,fingerprint";
/* 设备信息 */
};
/* xxx */
};
为何单要留下compatible属性呢?因为compatible是用来绑定设备驱动的关键所在!
设备和驱动的匹配
描述驱动方法的对象是platform_driver,其具体结构如下:
//include/linux/device.h
struct platform_driver {
int (*probe)(struct platform_device *); /* 探测函数,如果驱动匹配到了目标设备,总线会自动回调probe函数,必须实现 */
int (*remove)(struct platform_device *); /* 释放函数,如果匹配到的设备从总线移除了,总线会自动回调remove函数,必须实现 */
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; /* 实现 device/driver 匹配*/
const struct platform_device_id *id_table; /* 匹配 struct resource 编写的设备信息 */
bool prevent_deferred_probe;
};
device_driver结构信息如下:
//include/linux/device.h
struct device_driver {
const char *name; /* 驱动名,如果这个驱动只匹配一个 struct resource 编写的设备,那么可以通过name相同来匹配 */
struct bus_type *bus; /* 总线类型,这个成员由内核填充 */
struct module *owner; owner,通常就写THIS_MODULE
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);
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 dev_pm_ops *pm;
struct driver_private *p; /* 私有数据 */
};
设备和驱动匹配的方式一共有三种,一个驱动是可以匹配多个设备的,platform_bus使用不同的成员来进行匹配以达到对应的匹配信息能力
1、of_match_table
其结构如下:
//include/linux/mod_devicetable.h
struct of_device_id {
char name[32]; /* 设备名 */
char type[32]; /* 设备类型 */
char compatible[128]; /* compatible[128]用于与设备树compatible属性值匹配的字符串 */
const void *data;
};
我们在写驱动的时候,通过填充of_device_id结构体,设备树加载之后就可以与驱动进行匹配了,填充方法如下:
struct of_device_id of_tbl[] = {
{.compatible = "factory0,fingerprint",},
{.compatible = "factory1,fingerprint",},
{},
};
注意:最后的{},是一定要加的,这个是内核判断数组已经结束的标志!!!
2、id_table
对于使用struct resource编写的设备信息,使用id_table进行匹配,其结构如下:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE]; /* 设备名称 */
kernel_ulong_t driver_data;
};
填充方法如下:
static struct platform_device_id demo[] = {
{"factory0"},
{"factory1"},
{},
};
3、name
如果platform_driver和struct resource编码的platform_device是一一匹配的,我们还可以使用device_driver中的name来进行匹配
platform_driver的注册和注销
int platform_driver_register(struct platform_driver *drv); /* 注册 */
int platform_driver_unregister(struct platform_driver *drv); /* 注销 */
PS:3种匹配方式的优先级
///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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
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)) /* match compatible */
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_match_table > id_table > name
示例
在创建一个简单的字符设备的代码基础上,编写2个驱动分为platform_device和platform_driver。
platform_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
MODULE_LICENSE("GPL");
static struct resource csdn_resource[] = {
[0] = {
.start = 0x114000a0, /* 示例 */
.end = 0x114000a0 + 0x4,
.flags = IORESOURCE_MEM,
},
};
static void csdn_device_release(struct device *dev)
{
printk("csdn_device_release\n");
}
static struct platform_device csdn_device = {
.name = "csdn_blog",
.id = -1,
.dev.release = csdn_device_release,
.num_resources = ARRAY_SIZE(csdn_resource),
.resource = csdn_resource,
};
static __init int csdn_device_init(void)
{
pr_info("csdn_device_init\n");
return platform_device_register(&csdn_device);
}
static __exit void csdn_device_exit(void)
{
pr_info("csdn_device_exit\n");
platform_device_unregister(&csdn_device);
}
module_init(csdn_device_init);
module_exit(csdn_device_exit);
platform_driver.c
/* ohters code */
static int csdn_probe(struct platform_device *csdn_driver)
{
int result;
pr_info("csdn_probe\n");
result = csdn_register_chrdev();
if (result < 0) {
return result;
}
result = csdn_cdev_add();
if (result < 0) {
return result;
}
result = csdn_device_create();
if (result < 0) {
return result;
}
return result;
}
static int csdn_remove(struct platform_device *csdn_driver)
{
pr_info("csdn_remove\n");
device_destroy(csdn_class, csdn_dev);
class_destroy(csdn_class);
cdev_del(&csdn_cdev);
unregister_chrdev_region(csdn_dev, 1);
return 0;
}
struct platform_driver csdn_driver={
.probe = csdn_probe,
.remove = csdn_remove,
.driver = {
.name = "csdn_blog",
.owner = THIS_MODULE,
},
};
static __init int csdn_driver_init(void)
{
pr_info("csdn_driver_init\n");
platform_driver_register(&csdn_driver);
return 0;
}
static __exit void csdn_driver_exit(void)
{
pr_info("csdn_driver_exit\n");
platform_driver_unregister(&csdn_driver);
}
/* ohters code */
打印结果:
[233807.193395] csdn_driver_init
[233816.999343] csdn_device_init
[233816.999428] csdn_probe
然后在/dev下面可以查看到设备节点。
源码下载:https://gitee.com/zhangshusheng/csdn_blog/tree/master/platform_bus