• Linux下platform驱动框架描述


    如果要写复杂的外设驱动,Linux系统就要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,platform设备驱动就是在这种思路下诞生的,其也称为平台设备驱动。
    对于Linux这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则就会在Linux内核中存在大量无意义的重复代码。

    驱动的分离与分层

    最简单的思路写出来的驱动应该如下图所示。
    在这里插入图片描述
    但是设备驱动都是一样的,因此没必要每个平台都写一个,所以通过提供一个统一的接口,让每个设备通过这个接口来访问。
    在这里插入图片描述
    实际中的设备驱动类型很多,总体的架构应该如下图所示。
    在这里插入图片描述
    这就是Linux中总线、驱动和设备模型,即驱动分离,总线负责给驱动和设备牵线搭桥。当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。Linux内核中大量的驱动程序都采用总线、驱动和设备模式。
    简单介绍一下驱动的分层,input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给input核心层。input核心层会处理各种IO模型,并且提供file_operations操作集合,在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的。


    platform模型简介

    上面提到总线、驱动和设备这种模式,但是在SOC中有些外设是没有总线这个概念的,Linux提出了platform这个虚拟总线,相应
    的就有platform_driver和platform_device。
    Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h文件中。

    int (*match)(struct device *dev,struct device_driver *drv);
    
    • 1

    结构体中的match函数就是完成设备和驱动之间匹配的,总线使用match函数来根据注册的设备查找对应的驱动,或者根据注册的驱动来查找相应的设备,所以每一条总线都必须要实现此函数。
    platform总线是bus_type的一个具体实例,定义在文件drivers/base/platform.c,platform总线定义如下。

    struct bus_type platform_bus_type = {
    	.name		= "platform",
    	.dev_groups	= platform_dev_groups,
    	.match		= platform_match,
    	.uevent		= platform_uevent,
    	.pm		    = &platform_dev_pm_ops,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    platform_bus_type就是platform 平台总线,其中platform_match 就是匹配函数,该函数也定义在文件platform.c中,函数内容如下。

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    该函数中定义了四种驱动和设备匹配的方式,第一种是of类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device函数定义在文件include/linux/of_device.h中。device_driver结构体中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的compatible 属性会和of_match_table表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行。第二种是ACPI匹配方式。第三种是id_table匹配,每个platform_driver结构体有一个id_table成员变量,里面保存了很多id信息,这些id信息存放着这个platform驱动所支持的驱动类型。第四种是直接比较驱动和设备的name字段,如果相等的话就匹配成功。
    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当驱动与设备匹配成功以后probe函数就会执行,一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么probe就需要自行实现。
    driver成员为device_driver结构体变量,Linux内核里面大量使用到了面向对象的思维,device_driver相当于基类,提供了最基础的驱动框架。plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
    device_driver结构体定义在include/linux/device.h中,其原型如下。

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    其中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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对于设备树而言,就是通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
    在编写platform驱动的时候,首先定义一个platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执行,具体的驱动程序在probe函数里面编写。
    定义并初始化好platform_driver结构体变量以后,需要在驱动入口函数里面调用platform_driver_register函数向Linux内核注册一个platform驱动,platform_driver_register函数原型如下。

    int platform_driver_register (struct platform_driver *driver)
    
    • 1

    退出时卸载platform驱动,platform_driver_unregister函数原型如下。

    void platform_driver_unregister(struct platform_driver *drv)
    
    • 1

    经过上面的介绍,platform驱动框架大致如下。

    struct cdev cdev; 
    
    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,
    };
    
    static int xxx_probe(struct platform_device *dev)   //驱动和设备匹配成功之后此函数执行,原来init函数的内容放到这里
    {
    	...
    	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
    	return 0;
    }
    
    static int xxx_remove(struct platform_device *dev)  //关闭platform设备驱动的时候执行,原来exit函数的内容放到这里
    {
    	...
    	cdev_del(&xxxdev.cdev);  /* 删除cdev */
    	return 0;
    }
    
    static const struct of_device_id xxx_of_match[] = {
    	{.compatible = "xxx-gpio" },
    	{ /* Sentinel */ }
    };
    
    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)
    {
    	platform_driver_register(&xxx_driver);
    	return 0;
    }
    
    static int __exit xxxdriver_exit(void)
    {
    	platform_driver_unregister(&xxx_driver);
    	return 0;
    }
    
    module_init(xxxdriver_init);
    module_exit(xxxdriver_exit);
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    platform_device结构体定义在文件/include/linux/platform_device.h中,如果内核支持设备树的话就不用再使用platform_device来描述设备了,因为改用设备树去描述了。


    参考资料:
    I.MX6U嵌入式Linux驱动开发指南V1.5——正点原子

  • 相关阅读:
    【Homeassistant 与Passive Infrared Sensor被动红外传感器握手】
    网工内推 | 国企、上市公司,IA/IP认证即可,有年终、绩效
    【Node】核心模块
    Vue3中组件数据的传递以及更改
    LDO investigation
    【C++】“334. 队列检查”题目解析及所用vector相关用法总结
    ubuntu22.04 配置共享文件夹 找不到/mnt/hgfs
    vue---十分钟搞懂vue计算属性
    Linux 中简单的文件系统
    【PowerShell】PowerShell 7.1 之后版本的安装
  • 原文地址:https://blog.csdn.net/weixin_42570192/article/details/133577975