驱动开发之platform总线

1.前言

在前面的实验以及提出的各种问题中,为了提高移植性,降低模块耦合度,提让模块高内聚,分离device与driver是一个必然的趋势了。为了解决这个问题,心心念念的platform总线出来。

linux从2.6起就加入了一套新的驱动管理和注册的机制platform平台总线,是一条虚拟的总线,并不是一个物理的总线。

platform平台总线连接了平台设备(Platform Devices)和平台驱动(Platform Drivers)。平台设备是通过结构体platform_device来表示的,包含了设备的名称、资源(如内存地址、中断号等)和设备特定的属性。而平台驱动则是通过结构体platform_driver来实现,定义了与设备交互的函数指针,如探测、移除、打开、关闭、读写等操作。

2.platform总线工作原理

device与driver的注册无先后顺序,不管是device注册还是driver注册,都会触发platrom总线的检测函数,检测另一端是否已经注册,完成校验,匹配。其他匹配过程,可以看一下下面这个图,一个大佬画得,很清晰明了(链接:参考第一个)

 2.1.platform 总线介绍

2.1.1.总线结构体

此结构体定义在文件 include/linux/device.h

struct bus_type {
    const char *name; /* 总线名字 */
    const char *dev_name; 
    struct device *dev_root;
    struct device_attribute *dev_attrs;
    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 函数有 两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

2.1.2.bus实例platform 总线

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

 这些函数实现都在drivers/base/platform.c内部,不需要我们去实现,但是作为学习可以看看。

2.1.3.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. 当device设置driver_override时,只绑定到匹配的驱动程序 */
    if (pdev->driver_override)
    return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first 第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式 */
    if (of_driver_match_device(dev, drv))
    return 1;

    /* Then try ACPI style match 第二种匹配方式,ACPI 匹配方式 */
    if (acpi_driver_match_device(dev, drv))
    return 1;
    /* Then try to match against the id table . 第三种匹配方式id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型*/
    if (pdrv->id_table)
    return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match 第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功*/
    return (strcmp(pdev->name, drv->name) == 0);
 }

 3.platform 总线的使用

使用platform总线是很简单的,只需要实现的结构体的函数指针定义的函数,然后通过相应的接口注册进去即可。(使用很简单,如果使用很难,这个东西就不会那么出名了,懂的都懂

 3.1.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 函数就会执行。

remove函数,当platform_driver_unregister(&led_driver);函数调用时,会调用bus_remove_driver函数,该函数里面会再调用driver_detach函数,最后一层一层下去调用remove函数,完成资源释放, 或者在dev释放的时候,也会触发remove函数进行资源释放。测试实验见第4节。

probe ,remove函数一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。

driver 成员,为 device_driver 结构体变量,Linux 内核里面大量使用到了面向对象 的思维,device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类, 然后在此基础上又添加了一些特有的成员变量。

id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的 第四种方法,id_table 是个表( 也就是数组) ,每个元素的类型为 platform_device_id:

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

3.1.1.struct device_driver结构体


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 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下:

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

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

struct device_driver结构体里面的name就是最后一种匹配方法了。

虽然有四种匹配方法,但是只要有一种方法存在,而且匹配失败了,剩下的匹配方法就不能进行,就认为无法匹配了,请注意!!!!

3.2.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 字段相同,否则的话设
备就无法匹配到对应的驱动
driver_override成员: 就是match里面的第一种强制匹配了
num_resources 表示资源数量
resource 表示资源,也就是设备信息,resource 结构体内容如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址, name 表示资源名字, flags 表示资源类型,可选的资源类型都定义在了文件
include/linux/ioport.h 里面,如:
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000

一般是IORESOURCE_MEM与IORESOURCE_IRQ使用的比较多。

3.3.驱动注册接口

int platform_driver_register (struct platform_driver *driver)
driver :要注册的 platform 驱动。
返回值: 负数,失败; 0 ,成功。
void platform_driver_unregister(struct platform_driver *drv)
drv :要卸载的 platform 驱动。
返回值: 无。

 3.4.设备注册和注销接口

int platform_device_register(struct platform_device *pdev)
pdev :要注册的 platform 设备。
返回值: 负数,失败; 0 ,成功。
void platform_device_unregister(struct platform_device *pdev)
pdev :要注销的 platform 设备。
返回值: 无。

3.5.小结

从接口看,就两组接口(4个接口),两个结构体,就可以完成一个platform总线设备开发,是不是感觉很简单。下面进行一些实验验证。

4.测试实验

例子来源:韦东山老师的例子:

4.1.driver部分

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>


static int hello_probe(struct platform_device *pdev)
{
	int i = 0;
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	struct resource *source[2];
	for (i = 0; i < 2; i++) 
{
    source[i] = platform_get_resource(pdev, IORESOURCE_MEM, i);
    if (!source[i]) 
    {
        dev_err(&pdev->dev, "No MEM resource for always on\n");
        continue;
    }
    printk(" MEM resource i:%d, start:%x, end:%x \r\n", i, source[i]->start, source[i]->end);
}

for (i = 0; i < 2; i++) 
{
    source[i] = platform_get_resource(pdev, IORESOURCE_IRQ, i);
    if (!source[i]) 
    {
        dev_err(&pdev->dev, "No IRQ resource for always on\n");
        continue;
    }
     printk(" IRQ resource i:%d, start:%x, end:%x \r\n", i, source[i]->start, source[i]->end);
}
    return 0;
}

static int hello_remove(struct platform_device *pdev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}


static struct platform_driver hello_driver = 
{
	.probe      = hello_probe,
	.remove     = hello_remove,
	.driver     = 
	{
		.name   = "100ask_led",
	},
};

static int __init hello_drv_init(void)
{
    int err;
    
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_driver_register(&hello_driver); 
    
    return err;
}

static void __exit hello_drv_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_driver_unregister(&hello_driver);
}

module_init(hello_drv_init);
module_exit(hello_drv_exit);

MODULE_LICENSE("GPL");


4.2.deivce部分

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>



static struct resource resources[] = {
	[0] =
    {
            .start = (3<<8)|(1),
            .flags = IORESOURCE_IRQ,
    },
     [1] = {
 		.start = 0x1000,
 		.end = 0x1010,
 		.flags = IORESOURCE_MEM,
	 }, 
};

static void hello_dev_release(struct device *dev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}


static struct platform_device hello_dev = 
{
	.name = "100ask_led",
	.dev = 
	{
		.release = hello_dev_release,
	},
	.num_resources = ARRAY_SIZE(resources),
        .resource = resources,

};

static int __init hello_dev_init(void)
{
    int err;
    
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&hello_dev);   
    
    return err;
}

static void __exit hello_dev_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_device_unregister(&hello_dev);
}

module_init(hello_dev_init);
module_exit(hello_dev_exit);

MODULE_LICENSE("GPL");


 4.3.测试结果

(1)先加载drv,再加载dev.然后卸载dev,再卸载drv.结果如下:

可以发现,dev与drv匹配上之后,会触发drv的probe函数。当dev卸载的时候,也会触发drv的remove函数(前提drv没有被释放)。dev里面的release函数是在dev的platform_device_unregister函数调用之后触发的,与drv没关系

数据获取也正常,当然数据是随便造的,这是普通设备的数据获取,设备树的数据是不一样的,等设备树章节,再介绍,获取数据,以及设备树下的匹配规则。

(2)先加载dev,再加载drv,然后卸载drv,再卸载dev,结果如下:

 可以发现,dev与drv匹配上之后,会触发drv的probe函数。当drv卸载的时候,platform_driver_unregister函数也会触发drv的remove函数,原理可见3.1小节。

4.4.通过id_table进行匹配

static const struct platform_device_id led_id_table[] = {
	{"hellodevice1",   1},
	{"hellodevice2", 2},
	{"100ask_led", 3},
	{ },
};

static struct platform_driver hello_driver = 
{
	.probe      = hello_probe,
	.remove     = hello_remove,
	.driver     = 
	{
		.name   = "100ask_led1",
	},
    .id_table = led_id_table,
};
id_table匹配只需要改一下drv的参数即可,dev不需要改动。测试结果和上面测试一样。

4.5.driver_override强制匹配

static struct platform_device hello_dev =
{
        .name = "100ask_led1",
        .dev =
        {
                .release = hello_dev_release,
        },
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .driver_override = "100ask_led1",
};

强制匹配,只需要修改dev里面的参数,这个名字必须得与platform_driver结构体下面的.driver成员里面的name匹配上,才可以触发probe函数。

ok,目前platform总线就这样了,与设备树的使用,将在下一章进行介绍。

参考:

一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN博客

手把手教Linux驱动10-platform总线详解_linux驱动中platform总线驱动的ppt-CSDN博客

Linux驱动开发(二)---驱动与设备的分离设计_linux driver device 分离-CSDN博客

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

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

相关文章

区块链--Ubuntu上搭建以太坊私有链

1、搭建私链所需环境 操作系统&#xff1a;ubuntu16.04&#xff0c;开虚拟机的话要至少4G&#xff0c;否则会影响测试挖矿时的速度 软件&#xff1a; geth客户端 Mist和Ethereum Wallet&#xff1a;Releases ethereum/mist GitHub 2、安装geth客户端 sudo apt-get update …

ssm音乐网站-计算机毕业设计源码87184

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采SSM技术和mysql数据库来完成对系统的设计。整个开发过程首…

linux系统下,mysql增加用户

首先&#xff0c;在linux进入mysql mysql -u root -p 然后查看当前用户&#xff1a; select user,host from user; 增加用户语句&#xff1a; CREATE USER 用户名host范围 IDENTIFIED BY 密码;

渗透测试报告生成工具

目录 1.前言 1.1 渗透测试报告是什么? 1.2 渗透测试报告的编写需要考虑以下几点&#xff1a; 1.3 一份优秀的渗透测试报告应该具备以下特点&#xff1a; 1.4 在编写渗透测试报告之前&#xff0c;需要进行一些准备工作&#xff1a; 1.5 渗透测试报告一般包括以下部分&…

【避坑全攻略】如何让私人的LLM学GPT4o一样说话——ChatTTS

OpenAI 发布 GPT4o 之后&#xff0c;使得越来越多的人都开始幻想属于自己的AI“伴侣”&#xff0c;这最让人惊艳的就是他们出色的TTS技术。而在此之前&#xff0c;主流的开源TTS有 XTTS 2 和 Bark。而近日&#xff0c;一个名为 ChatTTS 文本转语音项目爆火出圈&#xff0c;引来…

Vue3集成Phaser-飞机大战游戏(设计与源码)

文章目录 引言项目初始化游戏设计和结构游戏程序实现Vue页面嵌入PhaserPreloader 场景加载游戏场景功能实现功能类定义Boom爆炸类Bullet子弹类Enemy敌军类Player玩家类End游戏结束类 总结 更多相关内容可查看 引言 飞机大战&#xff08;也被称为射击游戏或空战游戏&#xff09…

PID算法入门

文章目录 122.12.22.3 344.14.24.3 1 e(t) 是偏差 实 和 目u(t) 是运算结果 2 层层叠加 得出完整的离散公式 2.1 kp 越大 系统偏差 减小的越快kp大的时候 会出现过冲现象&#xff1f; 0.5 那个会快他解释过冲 &#xff1a; 0.2的 5分钟正好到了 那0.5的五分钟 升的就比20多 就…

springboot 自带的定时任务

启用springboot 定时任务 在springboot 启动类上增加EnableScheduling 注解 如下 SpringBootApplication EnableScheduling public class SpringApplication {public static void main(String[] args) {SpringApplication.run(SpringApplication.class, args);} }编写定时逻辑…

WHAT - 容器化系列(三)- Kubernetes - k8s

目录 一、前言二、Kubernetes 架构图三、KubernetesKubernetes和Docker的关系最小调度单元Pod 四、基本概念容器生态和标准化资源Workload资源&#xff1a;控制器对象服务担保Service&Ingress1. 两者的介绍以及与 OSI 七层模型关系2. 常见的 Service 类型3. Ingress 和 Ing…

自然资源-农村土地流转知识全解

自然资源-农村土地流转知识全解 随着农村经济的发展和城市化进程的加快&#xff0c;农村土地面临着多方面的压力&#xff0c;如人口增长、城市扩张、环境恶化等。这些压力导致了农村土地利用率低、经济效益差、农民收入水平低、农村社会经济不发达等问题。因此&#xff0c;改变…

Rust 第三方库创建和导入(cargo --lib)

前言 日常开发过程中&#xff0c;难免会有一些工具方法&#xff0c;多个项目之间可能会重复使用。 所以将这些方法集成到一个第三方包中方便后期维护和管理&#xff0c; 比如工具函数如果需要修改&#xff0c;多个项目可能每个都需要改代码&#xff0c; 抽离到单独的包中只需要…

AI办公自动化:用kimi批量提取音频中的标题并重命名

很多音频文件&#xff0c;文件名很乱&#xff0c;需要根据音频信息中的标题聪明吗 在kimi中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;一步步的思考&#xff0c;完成以下脚本的撰写&#xff1a; 打开文件夹&#xff1a;E:\有声\a16z播客 读取里面所有的mp3格…

关于vlookup的第一个参数的个人理解

VLOOKUP&#xff08;查阅值&#xff0c;包含查阅值和返回值的查找区域&#xff0c;查找区域中返回值的列号&#xff0c;精确查找或近似查找&#xff09; 我个人理解&#xff0c;第一个参数应该叫线索值&#xff0c;因为我们要通过它去找与其对应的&#xff08;也就是与其同行的…

Soulmask灵魂面甲服务器一键开服联机教程

1、购买后登录服务器&#xff08;百度莱卡云&#xff09; 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、创建端口 点击网络➡创建新的网络设置 需要创建两个端口&#xff0c;一个 查询端口…

SAP_SD模块-销售交货并开票后发现物料没维护价格的完整处理方法(含POD功能)

销售流程完结后&#xff0c;发现物料价格没维护时&#xff0c;如何处理 一、业务背景&#xff1a; 1、问题发现时间&#xff1a;2024年6月2日&#xff1b; 2、问题描述&#xff1a; 2024年5月份的单据业务存在交货成本和开票成本为0的单据&#x…

【Hive SQL 每日一题】统计指定范围内的有效下单用户

文章目录 测试数据需求说明需求实现 前言&#xff1a;本题制作参考牛客网进阶题目 —— SQL128 未完成试卷数大于1的有效用户 测试数据 -- 创建用户表 DROP TABLE IF EXISTS users; CREATE TABLE users (user_id INT,name STRING,age INT,gender STRING,register_date STRING…

cocos入门5:编辑器界面介绍

Cocos Creator是一款功能强大的跨平台游戏开发工具&#xff0c;其编辑器界面设计直观易用&#xff0c;提供了从资源管理、场景编辑到脚本编写等一站式解决方案。下面是对Cocos Creator编辑器界面的详细介绍&#xff1a; 一、界面布局 Cocos Creator编辑器界面通常包含以下几个…

【计算机毕设】基于SpringBoot的医院管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 本项目旨在设计并实现一个基于SpringBoot的医院管理系统&#xff0c;以提高医院管理效率&#xff0c;优化医疗服务流程&#xff0c;提升患者就诊体验…

FL Studio Producer Edition 21.2.2.3914 所有插件版安装教程指南

FL Studio Producer Edition 21.2.2.3914 所有插件版是一款功能强大的软件音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。FL Studio 中文学习版可以帮助你制作出色的音乐&#xff0c;为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您…

2.4 操作系统死锁(死锁的概念、产生、防止、预防、避免)

文章目录 一、死锁的概念1.1 死锁、饥饿、死循环对比1.1.1 死锁&#xff08;Deadlock&#xff09;1.1.2 饥饿&#xff08;Starvation&#xff09;1.1.3 死循环&#xff08;Infinite Loop&#xff09; 1.2 死锁产生的条件 二、预防死锁三、避免死锁四、死锁的检测和解除4.1 资源…