Linux设备驱动模型之platform设备

news2024/12/26 11:05:12

Linux设备驱动模型之platform设备

上一章节介绍了Linux字符设备驱动,它是比较基础的,让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中,个人认为完全手写一个字符设备驱动的机会比较少,更多的都是基于前人的代码修修补补过三年。在内核驱动中,你会看到比较多的platform相关的字样,他们具体是什么呢,下面我们一起来看看。

platform bus

在嵌入式Linux中,可能会听到过I2C总线、SPI总线、USB总线等,但是platform总线是什么呢,它有什么规范呢?platform总线是一个虚拟的总线,它没有像上述介绍的总线有通信协议规范,它仅仅是为了更好的管理Linux内核设备驱动而虚化出来的。

项目开发过程中,会有很多的设备驱动需要注册,但是这些设备都会具有很多共同点,比如休眠唤醒时需要注意的操作,每个设备都会有差异,但是都会有需要,开关机时也是如此。为了方便管理这些设备驱动,Linux增加了platform设备和驱动,方便驱动在开发过程做到统一管理,将设备和驱动都挂载在总线上,当有新的设备或者驱动加入时,都会进行比较,当设备和驱动信息一致时,则进行相应的操作,完成资源申请和设备注册。

Linux内核提供了注册总线的接口,下面我们来看看platform的总线都提供了那些信息:

/* 从platform_bus的定义来看,虽然我们说platform总线,实际上它还是一个设备 */
struct device platform_bus = {
        .init_name      = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

/* 定义platform设备的默认属性 */
static struct attribute *platform_dev_attrs[] = {
        &dev_attr_modalias.attr,
        &dev_attr_driver_override.attr,
        NULL,
};
ATTRIBUTE_GROUPS(platform_dev);

/* 提供设备的休眠唤醒接口函数,platform设备的休眠唤醒都通过这个接口完成调用 */
static const struct dev_pm_ops platform_dev_pm_ops = {
        .runtime_suspend = pm_generic_runtime_suspend,
        .runtime_resume = pm_generic_runtime_resume,
        USE_PLATFORM_PM_SLEEP_OPS
};

/* 定义platform总线的一些总要接口 */
struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,	/* 总线上设备的默认属性 */
        .match          = platform_match,		/* 前面我们说到注册分别注册设备和驱动之后,将会进行设备驱动匹配,platform设备就是通过该函数实现的 */
        .uevent         = platform_uevent,		/* 当添加、删除设备的时候,生成uevent添加到环境变量,实际上可以理解为有设备添加、删除时发送广播  */
        .pm             = &platform_dev_pm_ops,	/* 这个就是上面的休眠唤醒时的调用接口了 */
};
EXPORT_SYMBOL_GPL(platform_bus_type);

int __init platform_bus_init(void)
{
        int error;

        early_platform_cleanup();

    	/* 注册platform设备 */
        error = device_register(&platform_bus);
        if (error)
                return error;
    	/* 向系统注册platform总线 */
        error =  bus_register(&platform_bus_type);
        if (error)
                device_unregister(&platform_bus);
        of_platform_register_reconfig_notifier();
        return error;
}

bus_register函数主要进行下面几个操作:

  1. 将驱动自探测标志drivers_autoprobe设置为1,这样后续只要有device或driver加入bus,都将会触发设备驱动探测;
  2. 创建bus的默认属性节点;
  3. 创建bus的probe等文件节点;

向系统注册platform总线之后,又是怎么使用呢?下面我们一起看看platform设备和驱动的操作。

platform device

platform device一般在内核驱动中都是添加到各个SOC接口或者外设中出现的,作为一个对接内核设备的接口,使各自的设备驱动可更具灵活性。我们先看看内核是怎么定义这个platform device。

struct platform_device {
        const char      *name;			/* 设备属于什么名字的 */
        int             id;				/* 设备的索引,比如有多个类似的设备 */
        struct device   dev;			/* 内核设备的基础 */
        ...
        u32             num_resources;
        struct resource *resource;		/* 用于这个设备的资源保存了,比如中断和寄存器地址等 */
        ...
};

上面基本了解了platform device的类型之后,那么它又是怎么登记注册到Linux内核的呢?主要是通过下面的两个接口函数。

/* 登记注册platform device */
int platform_device_register(struct platform_device *pdev);
/* 登记注册num个platform device */
int platform_add_devices(struct platform_device **devs, int num);

那么platform_device_register()函数的操作主要是什么呢?

  1. 通过 device_initialize 初始化 pdev->dev,也就是初始化上述的struct device;
  2. 设置platform device的 DMA mask;
  3. 初始化pdev->dev的parent为platform_bus以及bus为platform_bus_type;
  4. 更新platform device资源;
  5. 通过device_add()向内核添加pdev->dev设备;

似乎完成了注册,现在只有设备,还没有驱动,接着看驱动又是怎样的。

platform driver

内核就是将设备与驱动分离的,设备可以是看得见、摸得着的实体,也可以是一些虚拟的、远端的设备,而驱动则是使设备可以正常工作的代码。platform driver的定义如下:

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

struct platform_driver {
    	/* probe函数用于探测是否存在和该驱动一致的platform device,由各自driver实现,驱动加载时调用 */
        int (*probe)(struct platform_device *);
    	/* remove函数则是在卸载模块时调用,释放相关资源 */
        int (*remove)(struct platform_device *);
    	/* 系统关机时将会调用shutdown */
        void (*shutdown)(struct platform_device *);
    	/* suspend和resume则是系统进入休眠和唤醒时调用 */
        int (*suspend)(struct platform_device *, pm_message_t state);
        int (*resume)(struct platform_device *);
    	/* 内核驱动的基础 */
        struct device_driver driver;
    	/* platform device和driver匹配信息 */
        const struct platform_device_id *id_table;
};

上面罗列了platform driver的基本成员,各自驱动将会实现,基础需要完成probe、remove和id_table信息的填充。而向内核注册驱动则是通过platform_driver_register完成驱动的注册,而platform_driver_register又主要是进行几个操作:

  1. 将driver的bus设置为platform_bus_type;
  2. 而driver为platform driver,所以device_driver的probe、remove、shutdown函数设置为plaform driver的函数;
  3. 通过driver_register函数向内核注册驱动;

probe的意义

单纯只有硬件机器而没有软件,或许它就是一堆废铜烂铁;而只有软件驱动,也是毫无用处,没有载体,发挥不出它们的作用。虽然我们有注册device、driver,但是如果没有匹配,也是不行,就像用一个ARM64的固件放在mips架构的芯片上运行失败一样,所以probe的意义就是通过driver确认当前的device是否与自身一致,一致则可进行系统节点的注册等。

上面在介绍platform device时有name成员,而platform driver中有id_table,什么时候会触发设备和驱动进行匹配呢?

回头看通过platform_driver_register()函数注册驱动时,driver_register()中会调用bus_add_driver()向总线注册driver,在bus_add_driver()中由因为总线已经设置了drivers_autoprobe,将会通过driver_attach()进行device、driver的匹配,这个过程如下:

platform_driver_register
	driver_register
		bus_add_driver
			driver_attach
				__driver_attach
					driver_match_device
						bus->match

而driver_attach中,将从bus的klist_devices(总线上的所有设备信息都在该链表上)逐个device与driver进行匹配,匹配是通过__driver_attach()函数完成,在__driver_attach中,通过driver_match_device()调用bus的match函数,platform bus的match函数为platform_match()。platform_match将会依次进行以下的对比:

  1. 如果platform device的driver_override置位,则对比pdev的driver_override和drv的name是否一致;
  2. 进行设备树的匹配,将对比drv的of_match_table和dev的of_node信息是否一致;
  3. 进行acpi的信息对比,这点有点不是很清晰,平常没有使用;
  4. 进行pdrv->id_table与pdev->name的对比,平常这个使用较多;
  5. 最后则是直接对比pdev->name和drv->name;

如果上述对比下来都不一致,则没有匹配,否则匹配成功。设备驱动匹配成功,将会通过device_driver_attach()函数进行匹配。

device_driver_attach将会进行以下的操作:

  1. 获取device所属的引脚配置信息;
  2. 在/sys目录下建立driver与device的链接;
  3. 优先调用设备所在bus的probe函数,如果没有则调用driver的probe函数;
  4. 将设备所有的引脚配置为默认状态;

像platform bus是没有实现probe函数的,所以将会调用在注册驱动时赋值的platform_drv_probe()函数,而在这里,实际上就是调用platform driver的probe函数,也就是驱动自己实现的probe函数了。

当调用到probe函数时,证明整个Linux内核的设备驱动模型已经跑通,但实际上这个设备是否存在,需要probe函数实现,probe函数确认device存在之后,将会可以进行资源的申请以及相关设备节点的注册操作。

设备驱动模型

platform设备驱动模型

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3+移动端适配屏幕+默认横屏展示

效果图展示区: 1. 想要把px自动转换单位为vw需要项目根目录.postcssrc.js中进行配置以下代码 module.exports {plugins: {autoprefixer: {}, // 用来给不同的浏览器自动添加相应前缀,如-webkit-,-moz-等等"postcss-px-to-viewport": {unitTo…

C/C++计算表达式的值 2023年5月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C计算表达式的值 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C计算表达式的值 2023年5月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定整数x,y的值&#xff0c…

视频号挂公众号链接引流到公众号还能加,好消息来了

视频号挂公众号链接要求在八月初出来了新规则,相信玩视频号的人大家都应该清楚,这两个新规则第一个看似简单,但是第二个却堵住了99%的人 接着看看视频号挂链接发展的来龙去脉 要点一:早在前两年,视频号链接直接显示在视…

基于springboot的流沐商城的设计与实现(前后端分离)

博主主页:一季春秋博主简介:专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发,远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容:毕业设计(Java项目、小程序等)、简历模板、学习资料、面试题…

【JavaSE笔记】运算符

一、前言 作为Java编程中最基本的语法元素之一,运算符在编写程序时扮演着至关重要的角色。 运算符被用于执行各种数学和逻辑运算,以及比较操作,运算符的使用可以使代码更加简洁、易读和高效。在本文中,我们将会详细介绍Java中常…

nginx反向代理,用户访问服务器1的80端口,请求转发至服务器2,3的8882端口

两台应用服务器,一台nginx,用户访问nginx服务器80端口,将请求转发至服务器2和服务器3的8882端口。 1、修改nginx配置文件 upstream backend {server 10.60.16.187:8882;server 10.60.16.188:8882;}server {listen 80;server_name 10.6…

Git(9)——Git多人协同开发之创建初始项目

目录 一、简介 二、创建新项目 三、本地Git接入 四、创建远端仓库——Gitee 五、推送代码至远端仓库 一、简介 前面8章都是我们一个人独自开发,如果现在项目新增两名同事,我们就需要使用Git来实现多人协同开发,从第九章开始将介绍从零到…

Atlas VPN 曝零日漏洞,允许查看用户真实 IP 地址

Atlas VPN 已确认存在一个零日漏洞,该漏洞允许网站所有者查看 Linux 用户的真实 IP 地址。不久前,发现该漏洞的人在Reddit上公开发布了有关该零日漏洞的详细信息以及漏洞利用代码。 关于 Atlas VPN 零日漏洞 Atlas VPN提供 "免费 "和付费的 …

GemBox.Bundle 47.0.1227 Crack

GemBox.Document 35.0.1480 GemBox.Email 17.0.1147 GemBox.Imaging 10.0.1096 GemBox.Pdf 17.0.1404 GemBox.Presentation 25.0.1526 GemBox.Spreadsheet 49.0.1454 GemBox.Spreadsheet 从 .NET 应用程序读取、写入、转换和打印 XLSX、XLS、XLSB、CSV、HTML 和 ODS 电子表…

算法题笔记 1-5

目录 week 11. 找出数组中重复的数字题目数据范围样例题解(数组遍历) O(n) 2. 不修改数组找出重复的数字题目数据范围样例题解(分治,抽屉原理) O(nlogn) 3. 二维数组中的查找题目题解(单调性扫描) O(nm) 4.替换空格题目题解(线性扫描) O(n)(双指针扫描) O(n) 5.从尾…

一阶滞后低通滤波器(支持采样频率设置 博途SCL代码)

一阶低通滤波器算法介绍这篇博客不再赘述,专栏有很多的文章讲过。之前的低通滤波器都是没有采样频率接口的,低通滤波器的采样频率都等于定时中断周期,实际滤波效果和信号采样频率、滤波系数、信号采样频率都有关系,所以这里我们将…

【C语言】指针的进阶(二)—— 回调函数的讲解以及qsort函数的使用方式

目录 1、函数指针数组 1.1、函数指针数组是什么? 1.2、函数指针数组的用途:转移表 2、扩展:指向函数指针的数组的指针 3、回调函数 3.1、回调函数介绍 3.2、回调函数的案例:qsort函数 3.2.1、回顾冒泡排序 3.2.1、什么是qso…

Python 10之异常模块包

😀前言 在Python编程中,我们时常会遇到各种异常和错误,同时我们也会使用多个模块和包来组织和结构化我们的代码。理解如何有效地处理异常和组织我们的代码是成为一个成功的Python程序员的关键。 . 在本教程中,我们将深入探讨Pytho…

10.3 滤波电路

整流电路的输出电压虽然是单一方向的,但是含有较大的交流成分,不能适应大多数电子电路及设备的需要。因此,一般在整流后,还需利用滤波电路将脉动的直流电压变为平滑的直流电压。与用于信号处理的滤波电路相比,直流电源…

Friend.tech和Tip Coin爆火!去中心化社交热度再起?

在Web2.0时代,用户对于大型中心化社交平台的信任逐渐降低,于是,去中心化的Web3社交应用也开始如雨后春笋般冒出。其中,像Friend.tech和Tip Coin这样的项目一经推出便在Twitter等平台刷爆了热榜。 Friend.tech基于Coinbase Layer 2…

SAP FI之自动付款程序运行 F110

简介 付款流程包括以下步骤 输入发票分析未结发票的到期日准备应付发票付款被批准或修改发票已付款 始终需要处理大量的发票。 必须按时支付应付帐款发票才能获得可能的折扣。 会计部门希望自动执行此发票处理。 自动付款程序是一种可以帮助用户管理应付帐款的工具。 SAP 为用…

Python 自定义模块

视频版教程 Python3零基础7天入门实战视频教程 Python中已经有很多的内置模块,以及也有很多的第三方优秀模块,我们直接导入使用即可。 当然我们有时候也需要自己定义一些自定义模块,来实现我们项目的功能。 看下案例: 先定义s…

基于Java的大学生在线租房平台的设计与实现(亮点:合理的租房流程、房屋报修、多角色、在线评论回复)

校园点餐小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序(小蔡coding)2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1 前面界…

SpringBoot实战(二十四)集成 LoadBalancer

目录 一、简介1.定义2.取代 Ribbon3.主要特点与功能4.LoadBalancer 和 OpenFeign 的关系 二、使用场景一:Eureka LoadBalancer服务A:loadbalancer-consumer 消费者1.Maven依赖2.application.yml配置3.RestTemplateConfig.java4.DemoController.java 服务…

浏览器事件机制详解

目录 前言 事件类型 鼠标事件 表单事件 窗口事件 DOM事件 多媒体事件 拖拽与放置事件 移动设备事件 剪切板事件 错误事件 过渡、动画事件 事件监听 onevent addEventListener(event) 事件触发 事件流程 捕获阶段 目标阶段 冒泡阶段 事件对象 总结 相关代…