5.Linux驱动-platform设备驱动

1.前言

本文为学习正点原子Linux教程所做的笔记,无具体代码,Linux系统为了考虑到驱动的可重用性,提出了驱动的分层与分离这样的软件思路,在这个思路下诞生了最常打交道的platform 设备驱动,也叫做平台设备驱动

2.驱动的分隔与分离

在Linux内核中,常常用到软件的分层与分离的思想,比如I2C驱动框架会分为主机驱动和设备驱动和核心层,主机驱动一般由soc厂商写好,核心层提供统一的接口方法给设备驱动层调用,设备驱动一般由设备驱动厂家或者驱动开发人员完成,只需要提供设备信息即可,比如I2C设备只需在设备树中定义连接到了哪路I2C,I2C的速度,这样子将驱动与设备信息剥离开来,驱动使用标准的方法从设备树中获取设备信息来初始化设备。总线为设备与驱动牵线搭桥
在这里插入图片描述

3.驱动的分层

一些Linux驱动是分层的,这是为了在不同层做不同的事情, 如input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。 input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的。可以看出借助分层模型可以极大的简化我们的驱动编写,对于驱动编写来说非常的友好。

4. platform平台驱动模型

在前面的驱动分离中,我们提出了总线,总线,设备模型,I2C,SPC,USB都是使用这种模型,但是一些外设是没有总线这个概念的,驱动编写者又想使用这种模型时会用到platform虚拟总线,相应的就有 platform_driver 和 platform_device。

4.1 platform总线

Linux使用bus_type结构体表示总线,结构体定义在文件include/linux/device.h,bus_type 结构体内容如下:

struct bus_type {
    
    
 116        const char              *name; /*总线名称*/
 117        const char              *dev_name;
 118        struct device           *dev_root;
 119        const struct attribute_group **bus_groups;
 120        const struct attribute_group **dev_groups;
 121        const struct attribute_group **drv_groups;/*驱动属性*/
 122
 123        int (*match)(struct device *dev, struct device_driver *drv);
 124        int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 125        int (*probe)(struct device *dev);
 126        int (*remove)(struct device *dev);
 127        void (*shutdown)(struct device *dev);
 128
 129        int (*online)(struct device *dev);
 130        int (*offline)(struct device *dev);
 131
 132        int (*suspend)(struct device *dev, pm_message_t state);
 133        int (*resume)(struct device *dev);
 134
 135        int (*num_vf)(struct device *dev);
 136
 137        int (*dma_configure)(struct device *dev);
 138
 139        const struct dev_pm_ops *pm;
 140
 141        const struct iommu_ops *iommu_ops;
 142
 143        struct subsys_private *p;
 144        struct lock_class_key lock_key;
 145
 146        bool need_parent_lock;
 147};

platform总线的注册函数,在drivers/base/platform.c中

struct bus_type platform_bus_type = {
    
    
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};

int __init platform_bus_init(void)
{
    
    
...
	error =  bus_register(&platform_bus_type);
...
	return error;
}

其中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);
}
  • 第一种是采用设备树的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中,此函数会比较device_driver中的变量的of_match_table成员变量,里面保存着驱动的compatible属性,与设备树中的compatible属性做对比,如果有的话设备与驱动会进行匹配,驱动的probe函数会执行
  • 第二种方法是ACPI的匹配方式
  • 第三种方法是,通过比较id_table,每一个platform_driver结构体都有一个id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型
  • 第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功

4.2 platform驱动

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;
    bool prevent_deferred_probe;
};
  • driver成员为device_driver成员变量,Linux内核使用了面向对象的思维,device_driver相当于基类,提供了最基础的驱动框架,platform_driver继承这个基类,在编写驱动需要初始化此变量
struct device_driver {
    
    
	const char *name;
	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 */

	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;
 };

其中of_match_table为采用设备树时驱动使用的匹配表,是一个数组,每一个匹配项为of_device_id类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内
容如下:

struct of_device_id {
    
    
	char name[32];
	char type[32];
	char compatible[128];
	const void *data;
};

第 4 行的 compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功

  • id_table 表为platform_device_id类型,在设备与驱动匹配时会用到
    结构体内容:
struct platform_device_id {
    
    
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

4.3 platform驱动编写流程

在编写platform驱动时,一般步骤为先定义一个platform_driver结构体,初始化其变量,再注册驱动
注册函数为:

/*
*driver:要注册的驱动
*返回值:成功:0,失败:负数
*/
int platform_driver_register (struct platform_driver *driver)

在驱动的卸载函数中为需要卸载platform驱动,卸载函数为:

/*
*drv:要卸载的结构体
*返回值:成功:0,失败:负数
*/
void platform_driver_unregister(struct platform_driver *drv)

4.4 platform驱动编写模板

正点原子驱动例程

struct xxx_dev{
    
    
	struct cdev cdev;
	/* 设备结构体其他具体内容 */
};

struct xxx_dev xxxdev; /* 定义个设备结构体变量 */

static int xxx_open(struct inode *inode, struct file *filp)
{
    
    
	/* 函数具体内容 */
	return 0;
}

 static ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
 {
    
    
	/* 函数具体内容 */
	return 0;
 }

 /
 * 字符设备驱动操作集
 */
 static struct file_operations xxx_fops = {
    
    
	.owner = THIS_MODULE,
	.open = xxx_open,
	.write = xxx_write,
 };

 /*
 * platform 驱动的 probe 函数
 * 驱动与设备匹配成功以后此函数就会执行
 */
 static int xxx_probe(struct platform_device *dev)
 {
    
    
	......
	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
	/* 函数具体内容 */
	return 0;
 }

 static int xxx_remove(struct platform_device *dev)
 {
    
    
	......
	cdev_del(&xxxdev.cdev);/* 删除 cdev */
	/* 函数具体内容 */
	return 0;
 }

 /* 匹配列表 */
 static const struct of_device_id xxx_of_match[] = {
    
    
	{
    
     .compatible = "xxx-gpio" },
	{
    
     /* Sentinel */ }
};

 /*
 * platform 平台驱动结构体
 */
 static struct platform_driver xxx_driver = {
    
    
	.driver = {
    
    
	.name = "xxx",
	.of_match_table = xxx_of_match,
 },
	.probe = xxx_probe,
	.remove = xxx_remove,
 };

 /* 驱动模块加载 */
 static int __init xxxdriver_init(void)
 {
    
    
	return platform_driver_register(&xxx_driver);
 }

 /* 驱动模块卸载 */
 static void __exit xxxdriver_exit(void)
 {
    
    
	platform_driver_unregister(&xxx_driver);
 }

 module_init(xxxdriver_init);
 module_exit(xxxdriver_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("zuozhongkai");

5.设备树下的platform驱动简介

在没有设备树的内核下需要实现platform_device与platform_driver,分别代表设备与驱动,在使用设备树之后,设备的描述放到设备树中,只需要实现platform_driver即可

5.1在设备树中创建设备树结点

这里重点是设置结点的compatible属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!创建的结点如下

gpioled {
    
    
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "atkalpha-gpioled";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_led>;
	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
	status = "okay";
};

在编写platform驱动时,of_match_table中属性表的compatible属性要有”atkalpha-gpioled"

5.2 编写platform驱动时要注意兼容性

在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以, of_match_table 将会尤为重要,比如本例程的 platform 驱动中 platform_driver 就可以按照如下所示设置:

 static const struct of_device_id leds_of_match[] = {
    
    
	{
    
     .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
	{
    
     /* Sentinel */ }
 };

MODULE_DEVICE_TABLE(of, leds_of_match);

static struct platform_driver leds_platform_driver = {
    
    
	.driver = {
    
    
	.name = "imx6ul-led",
	.of_match_table = leds_of_match,
 },
.probe = leds_probe,
.remove = leds_remove,
 }
  • of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为 of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,也就是前面中创建的 gpioled 这个设备。第 2 行的 compatible 值为“atkalpha-gpioled”,驱动中的 compatible 属性和设备中的 compatible 属性相匹配,因此驱动中对应的 probe 函数就会执行。注意第 3 行是一个空元素,在编写 of_device_id 的时候最后一个元素一定要为空
  • 通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。
  • 设置 platform_driver 中的 of_match_table 匹配表为上面创建的 leds_of_match,至此我们就设置好了 platform 驱动的匹配表了

猜你喜欢

转载自blog.csdn.net/weixin_43824344/article/details/120121409