platform设备驱动

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值