Linux驱动开发基础(总线驱动设备模型)

news2024/11/26 4:31:52

 所学来自百问网

目录

1.驱动设计的思想:面向对象/分层/分离

1.1 面向对象

1.2 分层

1.3 分离

2.总线驱动设备模型

2.1 相关函数和结构体

2.1.1 platform_device

2.1.2 platform_driver

2.1.3 相关函数

2.2 platfrom_driver和platfrom_device的注册过程

2.3 匹配规则

2.3.1 先注册驱动

2.3.2 先注册设备

2.3.3 比较顺序

2.4 driver获取device数据

2.5 总线驱动设备示例代码

2.5.1 led.drv.c

2.5.2 led_dev.c

2.5.3 led.dev2.c


1.驱动设计的思想:面向对象/分层/分离

1.1 面向对象

面向对象就是指的根据某一类事物的属性,进行抽象。

字符设备驱动程序抽象出一个file_operations结构体;

file_operations:对我们驱动程序经常需要用到的open、read、write这些公共的函数或属性封装成一个结构体,由于不同的硬件有不同的操作方法,故对这部分函数或属性进行抽象,在编写字符设备驱动程序时,只需对该结构体进行实现即可

1.2 分层

上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c

下层实现硬件相关的操作,比如board_A.c实现单板A的LED操作

如:

1.3 分离

当我们想对分层后的代码进行修改时,比如驱动不同的引脚的led灯,我们就要重新编写初始化和控制代码,这样显然很麻烦;由于每一款芯片的GPIO的操作都是类似的,故我们可以针对该芯片书写一个硬件通用代码

2.总线驱动设备模型

platform_driver结构体负责对硬件的寄存器代码的编写、初始化file_operation、映射寄存器等对硬件设备的初始化操作,即驱动

platform_device结构体负责对硬件功能的实现,即资源

在linux内核中,platform_bus_type是platform总线的数据结构,它是一个虚拟的总线,总线负责设备和驱动的匹配和绑定,用于将platform_deviceplatform_driver连接起来

2.1 相关函数和结构体

2.1.1 platform_device

platform_device包含了对描述设备所需的各种信息,如设备的名称、ID、资源(如 IO 端口、内存地址、中断号等)以及指向设备特定数据的指针。

部分字段的含义:
struct platform_device {
    const char  *name;          // 设备的名称,用于与驱动程序进行匹配
    int     id;                 // 设备的 ID 号,用于区分具有相同名称的不同设备实例。
    struct device   dev;        // 一个 device 结构体,表示设备在内核设备模型中的抽象。
    u32     num_resources;      // 设备使用的资源数量
    struct resource *resource;  // 资源描述符数组
    char *driver_override;      // 一个指向字符数组的指针,用于指定要强制匹配的驱动程序名称
};

2.1.2 platform_driver

platform_driver提供了注册和注销设备驱动程序的接口,负责对硬件驱动程序的书写

部分字段的含义:
struct platform_driver {
    int (*probe)(struct platform_device *);    //函数指针,指向驱动程序的 probe 函数 用于初始化设备并分配必要的资源
    int (*remove)(struct platform_device *);   //函数指针,指向驱动程序的 remove 函数 释放之前分配的资源并执行清理操作
    struct device_driver driver;               //device_driver 结构体,包含了驱动程序的通用信息,如驱动程序的名称、所属模块等
    const struct platform_device_id *id_table; //指向 platform_device_id 结构体数组的指针,用于基于设备 ID 的匹配
};

2.1.3 相关函数

  • 用于向内核注册一个平台驱动程序

    • int platform_driver_register(struct platform_driver *drv);

    • drv 是一个指向 platform_driver 结构体的指针,该结构体包含了驱动程序的信息和函数指针

    • 返回值:注册成功,返回 0;如果失败,返回非零错误码

  • 从内核中注销一个已经注册的平台驱动程序

    • void platform_driver_unregister(struct platform_driver *drv);

    • drv 是一个指向要注销的平台驱动程序结构体的指针

  • 用于向内核注册一个平台设备

    • struct platform_device *platform_device_register(struct platform_device *pdev);

    • pdev 是一个指向 platform_device 结构体的指针,该结构体包含了设备的信息和资源

    • 返回值:注册成功,返回 pdev 指针本身;如果失败,返回 NULL

  • 用于从内核中注销一个已经注册的平台设备

    • void platform_device_unregister(struct platform_device *pdev);

    • pdev 是一个指向要注销的平台设备结构体的指针。

  • 从给定的平台设备(platform_device)中获取指定类型的资源

    • struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);

    • dev:指向要查询资源的平台设备的指针。

    • type:要查询的资源类型,如IORESOURCE_MEM(内存资源)、IORESOURCE_IRQ(中断资源)等。

    • num:如果同一类型有多个资源,此参数指定要查询的资源的编号(通常从0开始)

    • 返回值:函数返回一个指向struct resource结构体的指针,该结构体包含了资源的详细信息,如开始地址、结束地址、资源名称等。如果找不到指定的资源,则返回NULL

  • 将私有数据与平台设备(platform_device)相关联

    • void platform_set_drvdata(struct platform_device *pdev, void *data);

    • pdev:指向要设置私有数据的平台设备的指针。

    • data:指向要与平台设备关联的私有数据的指针。这个数据可以是任何类型,但通常是一个指向设备特定数据结构的指针。

  • 用于获取与平台设备(platform_device)相关联的私有数据

    • void *platform_get_drvdata(const struct platform_device *pdev);

    • pdev:指向要获取私有数据的平台设备的指针。

    • 返回值:函数返回一个指向之前通过platform_set_drvdata设置的私有数据的指针。如果没有设置私有数据,则返回NULL。

2.2 platfrom_driver和platfrom_device的注册过程

图解:即将注册的设备或驱动添加到总线的链表中,依次遍历链表,去匹配对应的驱动或设备,若匹配则调用相应的函数

2.3 匹配规则

2.3.1 先注册驱动

图解:总线的drvier链表已经构造并注册了hello_drv,系统会对device链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_dev,构造完成后,系统会对drvier链表进行一一比较,找到则直接调用

2.3.2 先注册设备

图解:总线的device链表已经构造并注册了hello_dev,系统会对drvier链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_drv,构造完成后,系统会对device链表进行一一比较,找到则直接调用

2.3.3 比较顺序

最先比较

platform_device.driver_override 和 platform_driver.driver.name 可以设置platform_device 的driver_override,强制选择某个 platform_driver。

然后比较

platform_device.name和platform_driver.id_table[i].name platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个device,它里面列出了各个device的{.name, .driver_data},其中的“name”表示该 drv 支持的设备的名字,driver_data是些提供给该device的私有数据。

最后比较

platform_device.name 和 platform_driver.driver.name platform_driver.id_table 可能为空,这时可以根据platform_driver.driver.name来寻找同名的platform_device。

2.4 driver获取device数据

通过调用

resource结构体下的参数获得数据

例如

2.5 总线驱动设备示例代码

2.5.1 led.drv.c

#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>
#include <linux/mod_devicetable.h>
​
#define LED_MAX_CNT 10
// 结构体记录led信息
struct led_desc {
    int pin;
    int minor;
};
​
/* 1. 确定主设备号 */ 
static int major = 0;
static struct class *led_class;
​
static int g_ledcnt = 0;
static struct led_desc leds_desc[LED_MAX_CNT];
​
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    int err;
    char status;
    // 记录次设备号
    struct inode *inode = file_inode(file);
    int minor = iminor(inode);   
    
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = copy_from_user(&status, buf, 1);
​
    /* 根据次设备号和status控制LED */
    printk("set led pin 0x%x as %d\n", leds_desc[minor].pin, status);
    
    return 1;
}
​
static int led_drv_open (struct inode *node, struct file *file)
{
    int minor = iminor(node);
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    /* 根据次设备号初始化LED */
    printk("init led pin 0x%x as output\n", leds_desc[minor].pin);
    
    return 0;
}
​
/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
    .owner   = THIS_MODULE,
    .open    = led_drv_open,
    .write   = led_drv_write,
};
​
/* B.1 实现platform_driver的probe函数  */
static int led_probe(struct platform_device *pdev)
{   
    int minor;
    int i = 0;
​
    struct resource *res;
        
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
    if (!res)
        return -EINVAL;
​
    /* 记录引脚和次设备号 */
    minor = g_ledcnt;
    leds_desc[minor].pin = res->start;
    leds_desc[minor].minor = minor;
​
    /* 7.2 辅助信息 */
    /* 创建设备节点 */
    device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
    
    platform_set_drvdata(pdev, &leds_desc[minor]);
    g_ledcnt++;
    
    return 0;
}
​
/* B.2 实现platform_driver的remove函数  */
static int led_remove(struct platform_device *pdev)
{
    struct led_desc *led = platform_get_drvdata(pdev);
    device_destroy(led_class, MKDEV(major, led->minor)); /* /dev/100ask_led0,1,... */
​
    return 0;
}
// 记录可与driver相匹配的device设备
static const struct platform_device_id led_id_table[] = {
    {"100ask_led",   1},
    {"100ask_led_3", 2},
    {"100ask_led_4", 3},
    { },
};
​
​
/* A. 实现platform_driver  */
static struct platform_driver led_driver = {
    .probe      = led_probe,
    .remove     = led_remove,
    .driver     = {
        .name   = "100ask_led",
    },
    .id_table = led_id_table, // 获取匹配的device
};
​
/* 4. 把file_operations结构体告诉内核:注册驱动程序register_chrdev                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
    int err;
    
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
​
    /* 7.1 辅助信息 */
    led_class = class_create(THIS_MODULE, "100ask_led_class");
    err = PTR_ERR(led_class);
    if (IS_ERR(led_class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(major, "led");
        return -1;
    }
    /* C. 注册platform_driver */
    err = platform_driver_register(&led_driver); 
    
    return err;
}
​
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
​
    /* C. 反注册platform_driver    */
    platform_driver_unregister(&led_driver); 
​
    class_destroy(led_class);
    unregister_chrdev(major, "100ask_led");
}
​
/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

2.5.2 led_dev.c

#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>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
    {
            .start = (3<<8)|(1),            
            .flags = IORESOURCE_IRQ,
    },
};
​
static void led_dev_release(struct device *dev)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
​
static struct platform_device led_dev = {
        .name = "100ask_led",   // 名称匹配
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};
​
static int __init led_dev_init(void)
{
    int err;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&led_dev);   
    
    return err;
}
​
static void __exit led_dev_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_device_unregister(&led_dev);
}
​
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

2.5.3 led.dev2.c

#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>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
    {
            .start = (3<<8)|(2),            
            .flags = IORESOURCE_IRQ,
    },
};
​
static void led_dev_release(struct device *dev)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
​
static struct platform_device led_dev = {
        .name = "100ask_led_second",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
        .driver_override = "100ask_led", // 强制匹配
};
​
static int __init led_dev_init(void)
{
    int err;
    
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_device_register(&led_dev);   
    
    return err;
}
​
static void __exit led_dev_exit(void)
{
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_device_unregister(&led_dev);
}
​
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

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

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

相关文章

解决麒麟 V10 SP1 升级 Python 后 Yum 不可用问题

目录 一、前提概要 二、解决办法 1、卸载原有的 python 2、安装 Python 3.7.9 rpm 3、安装一系列 yum 相关 rpm 4、rpm 包下载 一、前提概要 在部署 gaussDB 的时候&#xff0c;安装代理时要求 python 版本满足 3.7.9&#xff0c;但已安装的麒麟 V10 内集成的 python 版…

GitHub Actions 遭利用,14个热门开源项目令牌泄露风险激增

近日&#xff0c;有攻击者通过 CI/CD 工作流中的 GitHub Actions 工具窃取了谷歌、微软、AWS 和 Red Hat 等多个知名开源项目的 GitHub 身份验证令牌。 窃取这些令牌的攻击者可在未经授权的情况下访问私有存储库、窃取源代码或向项目中注入恶意代码。 Palo Alto Networks Un…

【STM32 Blue Pill编程】-STM32CubeIDE开发环境搭建与点亮LED

开发环境搭建与点亮LED 文章目录 开发环境搭建与点亮LED1、STM32F103C8T6及STM32 Blue Pill 介绍2、下载并安装STM32CubeIDE3、编程并点亮LED3.1 在Stm32CubeIDE中编写第一个STM32程序3.1.1 创建项目3.1.2 设备配置3.1.2.1 系统时钟配置3.1.2.2 系统调试配置3.1.2.3 GPIO配置3.…

饲料粉碎加工:玉米豆粕小麦秸秆破碎机械设备

饲料粉碎机是一种专门用于将各种原料如玉米、小麦、豆粕、秸秆等物料进行破碎、细化的机械设备。其工作原理主要依赖于旋转的刀盘或锤片&#xff0c;在高速旋转过程中产生强大的冲击力和剪切力&#xff0c;将物料粉碎至所需粒度。这一过程不仅提高了饲料的利用率&#xff0c;还…

鸿蒙环境和模拟器安装

下载华为开发者工具套件&#xff0c;并解压 https://developer.harmonyos.com/deveco-developer-suite/enabling/kit?currentPage1&pageSize10 双击dmg安装ide 复制并解压sdk 安装模拟器 https://yuque.antfin-inc.com/ainan.lsd/cm586u/po19k1mi9b2728da?singleDoc#…

Unity大场景切换进行异步加载时,如何设计加载进度条,并配置滑动条按照的曲线给定的速率滑动

一、异步加载场景的过程 1、异步加载场景用到的API LoadSceneAsync 2、异步加载的参数说明 &#xff08;1&#xff09;默认参数&#xff1a;SceneManagement.LoadSceneAsync(“SceneName”); AsyncOperation task SceneManager.LoadSceneAsync("SceneName");&a…

James Forshaw的.NET Remoting反序列化升级版之TypeFilterLevel.Low模式无文件payload任意代码执行

引用 这篇文章的目的是介绍一款基于James Forshaw的.NET Remoting反序列化工具升级版在TcpServerChannel的TypeFilterLevel.Low模式无文件payload任意代码执行poc的开发心得 文章目录 引用简介.NET Remoting的应用程序通道介绍.NET Remoting的应用程序利用场景介绍扩展ysoseria…

【卫星影像地图API】常见地图服务_WMS_WFS_WCS_ WMTS

地图服务作为一种展现数据集的良好方式&#xff0c;为地理信息的共享起到重要作用。本文将介绍常见地图服务的相关内容。 网络地图服务&#xff08;WMS&#xff09; &#xff08;1&#xff09;概念 网络地图服务 (Web Map Service&#xff0c;WMS)指从地理信息动态产生具有地…

Docker最佳实践进阶(二):Docker Compose容器编排

大家好&#xff0c;在上篇文章中博主演示了Dockerfile常用的命令&#xff0c;以及如何利用Dockerfile构建镜像&#xff0c;生成容器服务&#xff0c;但是在实际应用环境中&#xff0c;特别是在微服务架构中&#xff0c;一个应用系统可能包含多个微服务&#xff0c;每个微服务可…

遇到的基本问题

遇到的基本问题 Linux常用操作 1、关闭防火墙、配置本地yum源、添加静态网卡 systemctl stop firewalld if [ getenforce "Enforcing" ];thensetenforce 0 fi sleep 3 echo "防火墙和selinux高级权限管理已关闭" ############ #添加静态网卡 #########…

配置访问权限|预防数据泄漏

IT行业正在以闪电般速度发展&#xff0c;而网络攻击也随之激增。在今年4月份的IT数据泄漏报告中&#xff0c;教育行业数据泄漏事件数量最多&#xff0c;其次是医疗保健行业、IT服务和软件行业。 为什么有许多数据泄漏事件&#xff1f; 通常是由于缺乏访问权限的认证&#xff0…

渗透实战——为喜欢的游戏“排忧解难”

本文仅用于技术研究学习&#xff0c;请遵守相关法律&#xff0c;禁止使用本文所提及的相关技术开展非法攻击行为&#xff0c;由于传播、利用本文所提供的信息而造成任何不良后果及损失&#xff0c;与本账号及作者无关。 资料查询来源- 安全社区与AI模型结合探索【文末申请免费…

了解数据库中常用存储引擎数据结构(4)

目录 深入了解LSM树及其发展 一条数据的整体写入过程 读操作&#xff08;Bloom Filter优化&#xff09; 合并策略&#xff08;Merging Policy&#xff09; LSM-Tree并发控制机制 一些Compaction优化方案 深入了解LSM树及其发展 LSM Tree 的概念起源于 1996年的论文《The…

【网络工程师模拟面试题】(2)OSPF区域划分与LSA细节

一、OSPF中心结点与周围200个路由器节点建立邻居&#xff0c;该如何划分区域&#xff0c;为什么&#xff1f; 这个问题其实没有标准答案&#xff0c;因为据OSPF RFC标准文档(RFC 2328 OSPF Version 2)来看&#xff0c;其中内容并没有规定OSPF单区域的邻居数量限制&#xff0c;…

【鸿蒙学习】HarmonyOS应用开发者基础 - 从网络获取数据

学完时间&#xff1a;2024年8月15日 一、前言叨叨 学习HarmonyOS的第七课&#xff0c;人数居然反超到了3735名了&#xff0c;难道前面的那一课&#xff0c;这么多人挂科了吗。不过这一节的内容稍微简单一些&#xff0c;都是网络相关知识。 <HarmonyOS第一课>从网络获取…

【Angular18】封装自定义组件

1. 准备组件 2. 创建打包文件夹及部分配置文件 创建 文件夹app-legalentities-root拷贝组件源文件到新的文件夹app-legalentities中创建文件 .npmrc registry发布地址always-authtrue创建文件 ng-package.json {"$schema": "./node_modules/ng-packagr/ng-pac…

FChen的408学习日记--三次握手和四次握手

一、三次握手 在建立连接的过程中&#xff0c;首先SYN1&#xff0c;随机发送sqex。服务器接受后要反过来对客户端发送连接请求&#xff0c;SYN1&#xff0c;随机发送sqey&#xff0c;ackx1。然后客户端还要发送连接确认报文&#xff0c;原因如下 例题&#xff1a; 二、四次…

WeiXin Bill Record

WeiXin Bill Record 微信账单记录查询流程 【我】 【钱包】 【账单】 选中一笔【查询对象】的交易记录 【查看往来转账】 【导出凭证】 选择查询开始时间&#xff0c;结束时间&#xff1b;【申请】 【验证】 【同意】协议 进入人脸识别 【申请】 【查看详情】 【确定】

SaaS中如何应用AIGC:智能赋能,引领未来

引言 在数字化浪潮的推动下&#xff0c;SaaS&#xff08;软件即服务&#xff09;已成为企业数字化转型的重要工具。而 AIGC 作为人工智能领域的前沿技术&#xff0c;正逐步渗透到SaaS的各个环节&#xff0c;为企业带来前所未有的智能化体验。 一、智能客服与自动化服务 在Saa…

Docker-命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Docker架构二、Docker进程相关命令&#xff08;一&#xff09;启动 docker 服务&#xff08;二&#xff09;重启 docker 服务&#xff08;三&#xff09;停…