【Linux驱动开发】023 platform设备驱动

news2025/1/25 4:28:44

一、前言 

驱动分离目的:提高Linux代码重用性和可移植性。


二、驱动的分隔与分离

百度看了很多,大多都没讲清楚为什么使用platform驱动,为什么驱动分隔与分离可以提高代码重用性,只是在讲实现的结构体、函数接口等等,现在我们就来分析一下:

先拿stm32单片机举个例子,如果使用I2C驱动的MPU6050,我们需要写一个mpu6050.c文件对其进行初始化,包括I2C初始化和读写函数、mpu6050初始化函数,如下图所示,其中对I2C的操作是stm32的I2C主机驱动,而对于mpu6050的初始化时设备的驱动。

如果现在我们改用MSP430驱动MPU6050,那么需要重新编写上面的mpu6050.c文件,也就是需要同时对主机驱动和设备驱动进行更改,总结一下上面的操作如下:

对于不同的主机(这里是单片机),更改主机的驱动文件是必要的,因为不同的主机I2C驱动文件肯定不一样,但是对于同一个MPU6050设备而言,重新编写相应的驱动文件是没有必要的!

为了保持设备驱动文件的可重用性,我们将主机驱动和设备驱动文件进行隔离,如下图所示:

并且我们统一命名所有主机(单片机)的I2C主机驱动中函数(I2C操作init、read、write函数),也就是stm32和MSP430中的I2C初始化函数、读写函数名字相同,这样在对mpu6050_init函数中使用统一的I2C读写操作进行初始化操作。

这样从stm32移植到MSP430中就无需重写mpu6050.c,也就是只需要重写I2C主机驱动,无需重写MPU6050设备驱动,重写主机驱动是可以理解的,毕竟平台不同。主机和设备驱动分隔示意图如下:

如果有多个使用I2C的设备,每个设备驱动对于不同的平台只需要写一次即可,这样代码的重用性就非常好。多个I2C设备的主机驱动与设备驱动隔离示意图如下:

类比到Linux,传统的驱动是这样的:

驱动分隔之后是这样的:

对于多个I2C设备的驱动分隔示意图是这样的:

相当于通过驱动分隔提高了代码的重用性! (分离、分隔都是一个意思,也就是分离)

上面说的主机驱动一般由半导体厂家开发,设备驱动由设备器件厂家开发,我们只需要提供设备的信息即可,比如I2C设备连接到哪个I2C接口上,速度是多少等等。也即是说将设备信息从设备驱动中分离出来,也就是驱动只负责驱动,设备只负责设备,使用总线对两者进行匹配。这个就是linux中的驱动-总线-设备模型也就是所谓的驱动分离通过着则这种方式,我们可以进一步分离,使代码重用性变得更高!

当某个注册某个驱动的时候,总线就会在右侧设备查找匹配项,匹配成功则将两者联系起来。

OK到此为止,Linux驱动分离讲解完成!


三、驱动的分层

分层就是将一个复杂的工作分成了4层, 分而做之,降低难度。每一层只专注于自己的事情, 系统已经将其中的核心层和事件处理层写好了,所以我们只需要来写硬件相关的驱动层代码即可。


四、platform平台驱动

SOC中的某些的某些外设可能没有总线这个概念,但是又需要使用驱动-总线-设备模型,因此,提出了platform虚拟总线,对应的驱动为platform_driver,对应的设备为platforn_device。

1、platform总线

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

使用上面的结构体struct bus_type表示总线,match函数完成驱动和设备之间的匹配。

platform是结构体struct bus_type的一个实例,定义和初始化为:

struct bus_type platform_bus_type = { 
     .name         = "platform", 
     .dev_groups   = platform_dev_groups, 
     .match        = platform_match, //匹配函数
     .uevent       = platform_uevent, 
     .pm           = &platform_dev_pm_ops, 
}; 

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

我们需要学习一下驱动和设备是如何匹配的,这样才能学会配置两者匹配的方式,好的,现在看上面的代码:

第二个if语句:OF匹配形式,即设备树匹配方式of_driver_match_device(dev, drv)函数形参drv表示platform驱动,也就是一个形参变量,该变量中有个of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树节点compatible属性值和此匹配表进行比较,查看是否匹配,成功匹配probe函数(platform设备驱动中的一个函数,下面讲解)就会执行。

第三个if语句:ACPI匹配方式。

第四个if语句:id_table匹配,每个platform_driver(platform驱动结构体)有一个id_table成员变量,保存着这个platform所支持的设备类型。

最后一个return语句:如果id_table不存在的话,直接比较驱动和设备的name字段,相当表明匹配成功。

2、platform驱动

platform_driver结构体表示platform驱动,定义如下:

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函数就会执行。非常重要的函数!!

driver成员:device_driver 结构体变量,相当于C++中的基类, device_driver 结构体是驱动最基础的框架。

id_table:上面讲解第三种驱动和设备匹配方法(非设备树)。

上方 struct device_driver 结构体是 struct platform_driver 的基类结构体,其中:

  • name 是前面说的第四种方式,直接比较驱动和设备的name字段,就是这里基类里面的name。
  • of_match_table 为采用设备树的时候使用的匹配表。
struct device_driver {
    const char          *name;// 非设备树匹配方式
    ...

    const struct of_device_id      *of_match_table;// 设备树匹配方式
    ...
}

上面的设备树匹配方式 struct of_device_id  结构体如下,其中 compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。 

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

3、platform 驱动API 函数

当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动:

int platform_driver_register (struct platform_driver    *driver) 

driver:要注册的 platform 驱动。 
返回值:负数,失败;0,成功。

还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,

void platform_driver_unregister(struct platform_driver *drv) 
drv:要卸载的 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");

4、不使用设备树的 platform 设备

platform 驱动已经准备好了,我们还需要 platform 设备,驱动写了一个文件了,设备还要写一个文件嘛?是的,不使用设备树是要将设备信息写为一个文件的,总要描述设备信息吧,还不能和驱动写在同一个文件中,只能单独写一个文件喽。

platform_device 这个结构体表示 platform 设备:

struct platform_device {
    const char  *name;
    int         id;
    bool        id_auto;
    struct device   dev;
    u32     num_resources;
    struct resource *resource;

    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

name:设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动;

num_resources 表示资源数量,一般为第 resource 资源的大小; 

resource 表示资源,也就是设备信息,比如外设寄存器等。

Linux 内核使用 struct resource 表示资源:

struct resource {
    resource_size_t   start;// 资源的起始和终止信息
    resource_size_t   end;
    const char        *name;// 资源名字
    unsigned long     flags;// 资源类型, 可选的资源类型都定义在了文件 include/linux/ioport.h 里面
    struct resource   *parent, *sibling, *child;
};

5、不使用设备树的 platform 设备API 函数

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

int platform_device_register(struct platform_device *pdev) 

pdev:要注册的 platform 设备。 
返回值:负数,失败;0,成功。 

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

void platform_device_unregister(struct platform_device *pdev) 
pdev:要注销的 platform 设备。 
返回值:无。 

platform 设备信息框架如下所示:

/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE    (0X20000000) /* 外设 1 寄存器首地址 */
#define PERIPH2_REGISTER_BASE    (0X020E0068) /* 外设 2 寄存器首地址 */
#define REGISTER_LENGTH           4

/* 资源 */
static struct resource xxx_resources[] = {
    [0] = {
        start  = PERIPH1_REGISTER_BASE,
        end    = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
        flags  = IORESOURCE_MEM,
    },
    [1] = {
        start  = PERIPH2_REGISTER_BASE,
        end    = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
        flags  = IORESOURCE_MEM,
    },
};

/* platform 设备结构体 */
static struct platform_device xxxdevice = {
    name = "xxx-gpio",
    id = -1,
    num_resources = ARRAY_SIZE(xxx_resources),
    resource = xxx_resources,
};

/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
    return platform_device_register(&xxxdevice);
}

/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{
    platform_device_unregister(&xxxdevice);
}

module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");

6、设备树下的platform驱动

在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。

在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。

Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成platform_device 形式,至于设备树到 platform_device 的具体过程就不去详细的追究了。

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

注意第 4 行的 compatible 属性值为“atkalpha-gpioled”,因此一会在编写 platform 驱动的时候 of_match_table 属性表中要有“atkalpha-gpioled”。 

2、编写 platform 驱动的时候要注意兼容属性 

上一章已经详细的讲解过了,在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 将会尤为重要:

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 的时候最后一个元素一定要为空!
  • 通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。

3、编写 platform 驱动 

基于设备树的 platform 驱动和上一章无设备树的 platform 驱动基本一样,都是当驱动和设备匹配成功以后就会执行 probe 函数。

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

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

相关文章

npm、pnpm、yarn的常用命令

npm、pnpm、yarn的常用命令 文章目录npm、pnpm、yarn的常用命令一、常用命令1、npm命令2、pnpm命令&#xff1a;3、yarn命令二、对比一、常用命令 1、npm命令 npm init: 初始化一个新的npm包。 npm install: 安装项目依赖项。 npm install : 安装指定的包。 npm install --sa…

【Java数据结构】链表(Linked List)-双向链表

双向链表&#xff08;Linked List&#xff09;是一种常用的数据结构&#xff0c;它允许在所有节点中快速添加或删除元素&#xff0c;并且可以有效地实现反向遍历。本篇文章将介绍双向链表的基础知识&#xff0c;并提供使用Java语言实现该数据结构的示例代码。 一、双向链表的基…

mysql数据库事务脏读、不可重复度、幻读详解

文章目录1 事务隔离级别2 脏读3 不可重复度3.1 解决了脏读的问题。3.2 有不可重复度的问题4 幻读4.1 没有脏读和不可重复读的问题4.2 有幻读的问题5 serializable1 事务隔离级别 read-uncommitted&#xff1a;脏读、不可重复度、幻读&#xff0c;均可出现。安全性低&#xff0…

HBase架构篇 - Hadoop家族的天之骄子HBase

HBase的基本组成结构 表&#xff08;table&#xff09; HBase 的数据存储在表中。表名是一个字符串。表由行和列组成。 行&#xff08;row&#xff09; HBase 的行由行键&#xff08;rowkey&#xff09;和 n 个列&#xff08;column&#xff09;组成。行键没有数据类型&…

《花雕学AI》06:抢先体验ChatGPT的九个国内镜像站之试用与综合评测

最近ChatGPT持续大火&#xff0c;大家们是不是在网上看到各种和ChatGPT有趣聊天的截图&#xff0c;奈何自己实力不够&#xff0c;被网络拒之门外&#xff0c;只能眼馋别人的东西。看别人在体验&#xff0c;看别人玩&#xff0c;肯定不如自己玩一把舒服的啊。 上一期&#xff0…

2.5d风格的游戏模式如何制作

文章目录一、 介绍二、 绘制瓦片地图三、 添加场景物体&#xff0c;添加碰撞器四、 创建玩家五、 创建玩家动画六、 玩家脚本七、 2d转换成2.5d八、 “Q”键向左转动视角、“E”键向右转动视角九、 下载工程文件一、 介绍 制作一个类似饥荒风格的2.5d游戏模板。 2.5D游戏是指以…

Spring之循环依赖

什么事循环依赖 很简单的定义就是就如有两个对象A类&#xff0c;B类&#xff0c;其中两个类中的属性都有对方。 A类 public class A{private B b;}B类 public class B{ private A a; }在Spring中&#xff0c;什么情况下会出现循环依赖 如果要了解循环依赖&#xff0c;首先…

基于matlab进行雷达信号模拟

一、前言此示例说明如何将基本工具箱工作流应用于以下方案&#xff1a;假设有一个工作频率为 4 GHz 的各向同性天线。假设天线位于全局坐标系的原点。有一个目标&#xff0c;其非波动雷达横截面为0.5平方米&#xff0c;最初位于&#xff08;7000&#xff0c;5000&#xff0c;0&…

Linux下使用ClamAV病毒查杀

一、介绍Clam AntiVirus 是一款 UNIX 下开源的 (GPL) 反病毒工具包&#xff0c;专为邮件网关上的电子邮件扫描而设计。该工具包提供了包含灵活且可伸缩的监控程序、命令行扫描程序以及用于自动更新数据库的高级工具在内的大量实用程序。该工具包的核心在于可用于各类场合的反病…

CompletableFuture使用详解(IT枫斗者)

CompletableFuture使用详解 简介 概述 CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口&#xff0c;并在此基础上进行了丰富的扩展&#xff0c;完美弥补了Future的局限性&#xff0c;同时CompletableFuture实现了对任务编排的能力。借助这项能力…

2023最新快速单机创建三主三从Redis集群

单机搭建Redis集群 本次采用Redis的5.0.14版本在单机centos8上搭建Redis三主三从集群. 1.创建6个文件夹 一个文件夹代表一个节点,同时也代表每个节点的端口号. 2.下载Redis文件并解压 使用命令: #下载Redis 可以将5.0.14替换成自己想要的版本 wget http://download.redis…

JavaScript面向对象编程再讲

JavaScript面向对象编程再讲 JavaScript支持的面向对象比较复杂&#xff0c;和其他编程语言又有其独特之处。本文是对以前博文 JavaScript的面向对象编程 https://blog.csdn.net/cnds123/article/details/109763357 补充。 概述 这部分是JavaScript面向对象的概括&#xff0c…

计算机网络微课堂1-3节

目录 1. TCP/TP协议​编辑 2. 3.调制解调器 4.因特网的组成 5.电路交换 6.分组交换 重要常用 7.报文交换 8.总结电路交换 报文交换和分组交换 9. 1. TCP/TP协议 2. ISP 网络提供商 ISP的三层 国际 国家 和本地 3.调制解调器 什么是调制解调器&#xff0c;它存在的…

稳压二极管工作原理、重要参数意义和典型电路参数计算

稳压二极管的工作原理&#xff1a;稳压二极管也叫稳压管&#xff0c;它在电路中一般起到稳定电压的作用&#xff0c;也可以为电路提供基准电压值。稳压二极管使用特殊工艺制造&#xff0c;这种工艺使它在反向击穿时仍然可以长时间稳定工作&#xff0c;不损坏&#xff0c;而工作…

macbook触摸板怎么按右键

苹果MacBook电脑触摸板如何右键&#xff0c;对于初次使用MacBook电脑的朋友&#xff0c;是一个小难题&#xff0c;其实MacBook电脑右键打开快捷辅助菜单的方法很简单。我们在MacBook电脑的【系统设置】—【触控板】中对触控板进行设置后可使用不同方式实现鼠标右键。 方法一&am…

形式与语言与自动机总结-----图灵机

图灵机的设计 图灵机的组成&#xff1a; 图灵机包括三部分:输入输出表带 &#xff0c;上面包括一些空格和输入字符&#xff0c;读写头可以向两个方向移动&#xff0c;每一次可以读取一个字符并对他进行改写&#xff0c;改变状态根据状态转移函数来确定。 状态转移函数: 图灵机…

【树】你真的会二叉树了嘛? --二叉树LeetCode专题Ⅳ

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

C# 文件操作

一 File\FileInfo类 在.NETFramework提供的文件操作类基本上都位于System.IO的命名空间下。操作硬盘文件常用的有两个类File\FileInfo. File类主要是通过静态方法实现的&#xff0c;FileInfo类是通过实例方法。 File类核心成员&#xff1a; FileInfo类的实例成员提供了与Fil…

Redis实现分布式锁的7种方案,及正确使用姿势!

redis学习笔记 7种方案前言 日常开发中&#xff0c;秒杀下单、抢红包等等业务场景&#xff0c;都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开&#xff0c;跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方&#xff0c;欢迎大家指出…

c盘如何扩展分区?C盘满了这么处理就对了

案例分享&#xff1a;“c盘如何扩展分区&#xff1f;我的电脑C盘前几天都还有50GB&#xff0c;这几天发现越来越小了&#xff0c;电脑也越来越卡顿了&#xff0c;为什么我的C盘突然就满了呢&#xff1f;那么我该怎么解决这个问题&#xff1f;请求大神的帮助&#xff01;” 在使…