Linux设备驱动模型之platform设备

2023-09-18 09:19:49

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设备驱动模型

更多推荐

得帆云“智改数转,非同帆响”-AIGC+低代码PaaS平台系列白皮书,正式发布!

5月16日下午,由上海得帆信息技术有限公司编写,上海市工业互联网协会指导的以“智改数转,非同帆响”为主题的《得帆云AIGC+低代码PaaS平台系列白皮书》正式在徐汇西岸国际人工智能中心发布。本次发布会受到了上海市徐汇区政府、各大媒体和业内专家的广泛关注。上海市工业互联网协会副秘书长王云、上海市徐汇区科委主任张宁、上海市

Spark【Spark SQL(四)UDF函数和UDAF函数】

UDF函数UDF是我们用户可以自定义的函数,我们通过SparkSession对象来调用udf的register(name:String,func(A1,A2,A3...))方法来注册一个我们自定义的函数。其中,name是我们自定义的函数名称,func是我们自定义的函数,它可以有很多个参数。通过UDF函数,我们可以针对某

MySQL学习6:索引

来源教学视频来源:黑马程序员MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括简介索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特点查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高价查找算法

mysql数据库基础

目录一、数据库基本概念1.数据:2.表:3.数据库:二、数据库管理系统:1、数据库管理系统介绍:2、数据库系统:3、DBMS工作模式:三、数据库分类:1、关系型数据库:2、关系型数据库的存储结构3、关系型数据库的优缺点:4、非关系型数据库4.1非关系型数据库的优点:4.2非关系型数据库的缺点:一、数据库基本概念1.数据

JavaScript-DOM实战案例

一、window定时器1.window定时器方法有时我们并不想立即执行一个函数,而是等待特定一段时间之后再执行,我们称之为“计划调用(schedulingacall)”。目前有两种方式可以实现:setTimeout允许我们将函数推迟到一段时间间隔之后再执行。setInterval允许我们重复运行一个函数,从一段时间间隔

如何通过百度SEO优化提升网站排名(掌握基础概念,实现有效优化)

随着互联网的发展,搜索引擎优化(SEO)成为了网站优化中不可或缺的一部分。在中国,百度搜索引擎占据着主导地位,因此掌握百度SEO概念和优化技巧对网站的排名和曝光非常重要。百度SEO排名的6个有效方法:首先是关键词研究。通过深入了解目标用户的搜索习惯和需求,选择合适的关键词并进行优化;其次是页面优化,蘑菇号https:/

初步了解华为的MTL(市场到线索)流程的基本概念和来龙去脉

前两天,有读者给华研荟发私信,说在学习华为资料的时候看到华为有一个MTL流程,想了解下这个MTL流程和LTC流程有什么区别?既然有了LTC流程,为什么还要MTL流程呢?为此,今天华研荟给大家简要介绍华为的MTL流程,也是非常重要的一个业务流程。如华研荟前面的文章中所提到的,华为认为一个企业运作的主业务主流程就三个:IP

3-D HANet:一种用于目标检测的柔性三维 HeatMap 辅助网络

论文背景室外场景感知使用Lidar:1.点云数据不受天气(雾、风暴、雨和雪)的影响,支持稳定的环境感知;2.点云数据在很大程度上保留了原来中物体的空间结构特征。3D目标检测是室外场景感知的重要组成部分。从一个不完整的点云空间结构中学习目标的完整尺寸和准确定位是三维目标检测的关键。空间结构不完整:受传感器、遮挡、物体材质

华为云云耀云服务器L实例评测|centos7.9在线使用cloudShell下载rpm解压包安装mysql并开启远程访问

文章目录⭐前言⭐使用华为cloudShell连接远程服务器💖进入华为云耀服务器控制台💖选择cloudShell⭐安装mysql压缩包💖wget下载💖tar解压💖安装步骤💖初始化数据库💖修改密码💖开启远程账号访问💖安全组开放3306端口💖本地navicat连接⭐总结⭐结束⭐前言大家好,我是yma16,

C++之va_start、vasprintf、va_end应用总结(二百二十六)

简介:CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.更多原创,欢迎关注:Android系统攻城狮1.前言本篇目的:C++之

第33章_瑞萨MCU零基础入门系列教程之DHT11温湿度获取实验

本教程基于韦东山百问网出的DShanMCU-RA6M5开发板进行编写,需要的同学可以在这里获取:https://item.taobao.com/item.htm?id=728461040949配套资料获取:https://renesas-docs.100ask.net瑞萨MCU零基础入门系列教程汇总:https://b

热文推荐