嵌入式驱动学习第四周——platform总线

前言

   platform是Linux内核抽象出来的软件代码,用于设备与驱动的连接,设备与驱动通过总线进行匹配;匹配成功后会执行驱动中的probe函数,在probe函数中可以获取到设备的信息;

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

目录

  • 前言
  • 1. platform总线介绍
    • 1.1 总线式设备驱动
    • 1.2 platform总线
  • 2. platform驱动框架
    • 2.1 相关函数与结构体
    • 2.2 platform驱动框架
  • 3. platform的匹配
  • 参考资料

1. platform总线介绍

1.1 总线式设备驱动

   外设与Soc的连接是通过接口,但是接口的不同本质上是通信协议的不同,于是内核就抽象出各种总线,将同一协议的设备连接在一起管理。

   总线分为物理总线和虚拟总线,物理总线是客观存在的,比如usb总线,有usb协议的设备连接在上面;虚拟总线式软件上模拟的,如platform总线,没有哪种设备用的platform协议,是内核为了统一用总线上方式管理设备虚拟出来的总线。

   使用总线的话,可以实现数据和方法分离,设备里包含了数据,驱动是硬件操作的方法。

   如下图所示,每个平台下的I2C控制器不同,因此主机驱动是必须的,但是对于每个SOC而言,mpu6050都是一样的,通过I2C的接口读写数据就行了,只需要一个MPU6050的驱动程序。如果再来几个 I2C 设备,比如 AT24C02、FT5206,那么只需要编写设备驱动,然后调用统一的I2C接口即可。

在这里插入图片描述
   当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。

1.2 platform总线

   并不是所有外设都有总线这个概念,比如LED,蜂鸣器,这种只是操作GPIO的,但是需要使用总线——驱动——设备模型怎么办呢,为此Linux提出platform这个虚拟总线。

   platform总线是虚拟总线,是软件层虚拟出来方便管理设备的,主要是管理类似LED灯这种比较简单,不需要专用接口的设备;

   比如LED设备,一个设备可能有好几个LED,不同的LED设备之间寄存器数目、操作方法是一样的,不一样的是寄存器的基地址不同、连接的gpio口不同,于是我们可以把不同的数据放在总线上设备信息里(描述设备的结构体里有描述资源),然后传给LED驱动,这样就可以实现一个驱动适配几个设备。

2. platform驱动框架

2.1 相关函数与结构体

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

   probe函数是当驱动与设备匹配成功后就会执行,非常重要的函数,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。

   id_table 是个表( 也就是数组) ,每个元素的类型为 platform_device_id,其定义在include/linux/mod_devicetable.h

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

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

   其中of_match_table是采用设备树时驱动使用的匹配表,其类型为of_device_id,用于将device和driver相匹配,定义在include/linux/device.h

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

   其中的compatible很重要,通过设备节点的compatible属性值和of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

   在不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。

/*
 * @description: 将设备信息注册到Linux内核中
 * @param-pdev : 要注册的platform设备
 * @return     : 负数,失败;0,成功
 */
int platform_device_register(struct platform_device *pdev)

   如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备

/*
 * @description: 注销掉对应的platform设备
 * @param-pdev : 要注销的platform设备
 * @return     : 无
 */
void platform_device_unregister(struct platform_device *pdev)

2.2 platform驱动框架

   首先要定义一个设备结构体并定义一个设备对象

struct xxx_dev {
	struct cdev cdev;
};

struct xxx_dev xxxdev;

   定义open,write等内核函数并设置file_operations结构体

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

   编写probe函数,驱动与设备匹配成功后此函数会执行,里面就是具体的驱动函数,比如字符设备驱动注册,添加cdev,创建类等。

static int xxx_probe(struct platform_device *dev)
{
	......
	cdev_init(&xxxdev.cdev, &xxx_fops);		// 注册字符设备驱动
	......		// 函数具体内容

	return 0;
}

   编写remove函数,卸载驱动的时候就会执行该函数,以前在exit函数中做的事情在此函数中,比如删除cdev,注销设备号。

static int xxx_remove(struct platform_device *dev)
{
	......
	cdev_init(&xxxdev.cdev, &xxx_fops);		// 注册字符设备驱动
	......		// 函数具体内容

	return 0;
}

   写匹配列表,即of_device_idcompatible属性,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。此匹配项的 compatible 值为“xxx”,因此当设备树中设备节点的 compatible 属性值为“xxx”的时候此设备就会与此驱动匹配。最后一行是一个标记,of_device_id 表最后一个匹配项必须是空的,因为相关的操作API会读取这个数组直到遇到一个空。

static const struct of_device_id xxx_of_match {
	{.compatible = "xxxx"},
	{ //Sentinel }
};

   platform平台驱动结构体,其中name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同。of_match_table 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法

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("wp");

3. platform的匹配

   学会怎么用后,来看看一些底层的东西,那就是如何将设备与驱动之间进行匹配的。

   首先我们来看Linux系统中的总线,结构体为bus_type,定义在include/linux/device.h中。

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*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);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

   其中重要的是match函数,设备与驱动件的匹配就是match函数实现的,总线就是使用match函数根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找对应的设备,因此每条总线都必须实现该函数。

   match 函数有两个参数:dev 和 drv,分别为 device 和 device_driver 类型,即设备与驱动。

   而要介绍的platform总线,是bus_type的一个具体实例,定义在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);

   其中找到match,对应的函数是platform_match,我们接下来看这个函数,其定义在drivers/base/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);
}

   可以看到函数中一共定义了4种匹配方式。

  • 第一个if,of类型的匹配,即设备树采用的匹配方式,设备树中每个设备节点的compatible会和上面我们2中device_driver结构体中of_match_table的成员变量中存储的compatible匹配表进行比较,有的话就表示匹配成功,然后执行probe 函数;
  • 第二个if,ACPI匹配方式;
  • 第三个if,id_table匹配,每个platform_driver结构体有一个id_table成员变量,保存了很多id信息,这些 id 信息存放着这个 platformd 驱动所支持的驱动类型;
  • 第四个if,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

   对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第五十四章、第五十五章
[2] platform总线
[3] 内核platform总线详解:定义、注册、匹配、使用示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/469281.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3月份后半月比赛总结

VCTF web hackjs 就直接给了源码审计 const express require(express) const fs require(fs) var bodyParser require(body-parser); const app express() app.use(bodyParser.urlencoded({extended: true })); app.use(bodyParser.json());app.post(/plz, (req, res) …

VR历史建筑漫游介绍|虚拟现实体验店|VR设备购买

VR历史建筑漫游是一种利用虚拟现实技术,让用户可以身临其境地参观和探索历史建筑的体验。通过VR头显和相关设备,用户可以在虚拟环境中自由移动和互动,感受历史建筑的真实氛围和文化内涵。 在VR历史建筑漫游中,您可以选择不同的历史…

Android 之 GMS 认证知多少?

GMS认证 1.什么是GMS GMS全称Google Mobile Service,谷歌移动服务。 为什么要通过GMS认证 Android 系统是开源的,但是 Google 针对GMS所提供的服务却是收费的,比如Google Map,Google Play,Youtube,Gmai…

堆(数据结构)

堆的概念及结构 如果有一个关键码的集合K { &#xff0c; &#xff0c; &#xff0c;…&#xff0c; }&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中&#xff0c;并满足&#xff1a; < 且 < ( > 且 > ) i 0&#xff0c;1&#xff…

Python爬虫案例-爬取主题图片(可以选择自己喜欢的主题)

2024年了&#xff0c;你需要网络资源不能还自己再慢慢找吧&#xff1f; 跟着博主一块学习如何利用爬虫获取资源&#xff0c;从茫茫大海中寻找那个她到再妹子群中找妹子&#xff0c;闭着眼睛都可以找到合适的那种。文章有完整示例代码&#xff0c;拿过来就可以用&#xff0c;欢迎…

【C语言】数据在内存中的存储(包含大小端字节序问题)~

一、前言 我们在刚开始学习C语言的时候&#xff0c;就接触到了很多数据的不同类型。我们也知道&#xff0c;数据是存储在一块内存空间的&#xff0c;且我们只知道数据的类型决定着&#xff0c;该数据在内存中所占内存空间的大小&#xff0c;且超过一个字节的数据在内存中存储的…

【项目实践Day06】异步请求与同步请求+Ajax+微信小程序上实现发送异步请求

什么是同步和异步 同步 在主线程上排队执行的任务&#xff0c;只有前一个任务执行完毕&#xff0c;才能继续执行下一个任务。也就是一旦调用开始&#xff0c;就必须等待其返回结果&#xff0c;程序的执行顺序和任务排列顺序一致。客户端必须等待服务器端的响应。在等待的期间客…

Android源码阅读 SharedPreferences - 1

目录 前言 正文 SharedPreferences.java PreferenceManager.java ContextImpl.java 前言 由于笔者目前水平限制&#xff0c;表达能力有限&#xff0c;尽请见谅。 SharedPreferences提供了一种轻量级的数据存储方式&#xff0c;允许保存和获取简单的键值对。它适用于保存少…

转座子插入序列分析1-GENE-IS分析管道

如果你使用 GENE-IS: Saira Afzal et al。 &#xff0c;2016请引用这篇研究文章。GENE-IS: time-efficient and accurate analysis of viral integration events in large-scale gene therapy data. Molecular Therapy - Nucleic Acids 2016, vol. 6:133-139. DOI:https://doi.…

做好外贸网站SEO优化,拓展海外市场

随着全球贸易的发展和互联网的普及&#xff0c;越来越多的外贸企业将目光投向了网络&#xff0c;希望通过建立网站来拓展海外市场。然而&#xff0c;在竞争激烈的外贸市场中&#xff0c;要让自己的网站脱颖而出&#xff0c;吸引更多的目标客户&#xff0c;就需要进行有效的SEO优…

StarRocks 记录

《实时数仓StarRocks集群部署》

提升Spring Boot应用性能的秘密武器:揭秘@Async注解的实用技巧

引言 在日常业务开发中&#xff0c;异步编程已成为应对并发挑战和提升应用程序性能的关键策略。传统的同步编程方式&#xff0c;由于会阻碍主线程执行后续任务直至程序代码执行结束&#xff0c;不可避免地降低了程序整体效率与响应速度。因此&#xff0c;为克服这一瓶颈&#…

win11环境安装VmwareLinux

VMware 安装Vmware 操作系统&#xff1a; win11 VM版本&#xff1a; 重启系统 输入许可证秘钥 安装centos finalshell连接linux服务 配置虚拟机运行状态 查询linux服务器的ip地址 下载finalshell 访问FinalShell官网 (hostbuf.com)

Spring6入门到高级-动力节点老杜

文章目录 OCP开闭原则依赖倒置原则控制反转依赖注入DISet方法注入构造注入 Sping特点代理模式代理模式中的角色动态代理JDK动态代理newProxyInstance() 的三个参数 JDK实现代理的步骤第一步&#xff1a;创建目标对象第二步&#xff1a;创建代理对象第三步&#xff1a;调用代理对…

C语言学习--八种排序算法

目录 排序的概念 1.直接插入排序 基本思想 代码实现 算法分析 2.希尔排序 基本思想 代码实现 算法分析 3.冒泡排序 基本思想 代码实现 算法分析 4.快速排序 基本思想 代码实现 算法分析 5.简单选择排序 基本思想 代码实现 算法分析 6.堆排序 基本思想 代…

合合信息扫描全能王亮相静安区3·15活动,AI扫描带来绿色消费新体验

保护消费者的合法权益&#xff0c;是全社会的共同责任。为优化消费环境、促进品质消费高地建设&#xff0c;打造安全优质和谐的消费环境&#xff0c;上海静安区消保委于3月15日举办静安区2024年“315”国际消费者权益日活动。 “激发消费活力&#xff0c;绿色低碳同行”是本次3…

Vue+SpringBoot打造民宿预定管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色2.2.2 房主角色2.2.3 系统管理员角色 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿4.3 新增民宿评价4.4 查询留言4.5 新增民宿订单 五、免责说明 一、摘要 1.1 项目介绍 基于…

傻傻分不清目标检测、语义分割和实例分割,看这篇就够了

⭐️ 导言 随着深度学习技术的飞速发展&#xff0c;计算机视觉领域取得了巨大的进步。目标检测、语义分割和实例分割是计算机视觉中的重要任务&#xff0c;它们在图像理解和视频分析等方面发挥着关键作用。本文将深入探讨这三个任务的概念、原理、常用算法以及在实际应用中的案…

(css)vue 自定义背景 can‘t resolve

(css)vue 自定义背景 can’t resolve 旧写法&#xff1a; background-image: url(/assets/images/step-bg.jpg);background-size: 100% 100%; 新写法&#xff1a; background-image: url(~/assets/images/step-bg.jpg);background-size: 100% 100%; 解决参考&#xff1a;https…

shopee无货源出单了怎么发货?shopee怎么做无货源?

在Shopee的电商大舞台上&#xff0c;“无货源出单”就像是一场神奇的魔术表演。你的店铺是舞台&#xff0c;买家的订单是观众的掌声&#xff0c;而你&#xff0c;就是那位神秘的魔术师。订单来了&#xff0c;你却没有货&#xff1f;这可不是什么障碍&#xff0c;因为你有着更为…