嵌入式Linux驱动——3 总线设备驱动模型

目录

1.总线设备驱动模型

1.1 总线设备驱动模型

1.2 设备树

1.3 platform_device 和 platform_driver 的匹配规则

1.3.1 最先比较

1.3.2 然后比较 

1.3.3 最后比较

2.LED 模板驱动程序的改造:总线设备驱动模型


1.总线设备驱动模型

在前面的 led 驱动程序中,我们使用分离的思想驱动程序分为了 led_resource.c 和 led_drv.c

分离思想为不同种类的硬件资源定义了不同的 resource 结构体,但是在设备有各种资源,难道每种资源都用一个结构体表示?

这并不现实,因此在分离思想中扩展出总线设备驱动模型

ps:其实并没有解决这个问题,这也是一种分离的思想方法

1.1 总线设备驱动模型

引入 platform_device platform_driver,将“资源”与“驱动”分离开来:

可以看到,一种硬件资源通过一个 platform_device 结构体表示,并通过总线 bus 与其相对的驱动 platform_driver 结构体相连接

(ps:好像并没有解决一种资源用一个结构体表示的问题,不过韦老师在视频是这样介绍的...不懂了)
答:因为这也是一种分离的思想,确实是没有解决这个问题

 特点:

  1. 代码稍微复杂,但是易于扩展。跟“分离思想”类似,可随时修改硬件资源,不像传统写法,引脚的使用和操作都写死在代码中
  2. 冗余代码太多,修改引脚时设备端的代码需要重新编译

1.2 设备树

因每个板子的硬件资源都不同,因此会存在大量的硬件资源 .c 文件存在于Linux内核中,这就会导致内核十分庞大臃肿

由此引出设备树

  1. 对于每个单板都会有其对应的 dts 文件包含它的所有硬件资源
  2. 需要使用时,将该单板的 dts 文件编译成 dtb 文件并传入内核
  3. 内核会解析dtb文件并构造出一系列的 platform_device 

因 dts 文件放在内核之外,这样就保持了 Linux 内核的干净

并不是说有了设备树文件就不用再编写驱动:设备树仅用于描述硬件资源,具体操作还得编写驱动代码

1.3 platform_device 和 platform_driver 的匹配规则

1.3.1 最先比较

若 platform_device 中定义了 driver_override

则最先比较 platform_device.driver_override platform_driver.driver.name

可以通过设置 platform_device 的 driver_override,强制选择某个 platform_driver (非某人不嫁)


1.3.2 然后比较 

platform_device. nameplatform_driver.id_table[i].name

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

通过 name 进行匹配


1.3.3 最后比较

platform_device.nameplatform_driver.driver.name

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


2.LED 模板驱动程序的改造:总线设备驱动模型

board_A_led.c:

  1. 定义 platform_device 结构体中的内容:name、num_resources、resource等成员
  2. 在入口函数和出口函数中分别注册和注销 platform_device 结构体

demo 示例代码:

#include "led_resource.h"static struct led_resource board_A_led = {.pin = GROUP_PIN(3,1),
};/*定义resource资源*/
/*flag随便拿一个IORESOURCE_IRQ用着先*/
static struct resource resource[] = {{.start = GROUP_PIN(3,1),.flags = IORESOURCE_IRQ,},{.start = GROUP_PIN(5,8),.flags = IORESOURCE_IRQ,},
}/*定义platform_device结构体*/
static struct platform_device board_A_led_dev ={.name = "100ask_led",.num_resources = ARRAY_SIZE(resource),.resource = resource,
};/*入口函数*/
static int led_dev_init(void)
{int err;/*注册*/err = platform_device_register(&board_A_led_dev);return 0;
}/*出口函数*/
static void led_dev_exit(void)
{int err;/*注销*/err = platform_device_unregister(&board_A_led_dev);return 0;
}module_init(led_dev_init);
module_exit(led_dev_exit);MODULE_LICENSE("GPL");

 chip_demo_gpio.c:

  1. 老规矩,配置 led_operations 结构体,定义实现结构体成员的函数 board_demo_led_init 和 board_demo_led_ctl(硬件操作)
  2. 配置平台驱动 platform_driver 结构体,实现成员函数:
    chip_demo_gpio_probe:用于初始化设备,只要有 platform_device 与 platform_driver 配对,就会执行这个函数,可以看出,把之前在 leddrv.c 中的创建设备节点的命令放到了这里
    chip_demo_gpio_remove:用于清除设备,就是和上面相反
  3. 最后也是老规矩,实现该驱动的入口函数和出口函数

注意:入口函数中的 register_led_operations 操作,这是给 leddrv.c 传入led_operations 结构体用的,为什么不在 leddrv.c 中调用定义好的 get_board_led_opr 函数??

        这涉及一个相互依赖问题,因为前面 chip_demo_gpio_probe 中创建和清除设备节点的函数 led_class_create_device 依赖于底层的 leddrv.c ,如果此时又在底层的 leddrv.c 中调用上层的 get_board_led_opr ,会造成的交叉依赖的问题,因此只能通过指针在上层传给leddrv.c


demo程序代码:(只打印信息,不做具体操作)

#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 "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"// 全局LED引脚数组和计数器
static int g_ledpins[100]; 
static int g_ledcnt = 0;// LED初始化函数:配置指定LED的GPIO引脚
static int board_demo_led_init(int which) 
{printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));// 根据GPIO组号执行不同的初始化操作switch(GROUP(g_ledpins[which])) {case 0: printk("init pin of group 0 ...\n"); break;case 1: printk("init pin of group 1 ...\n"); break;case 2: printk("init pin of group 2 ...\n"); break;case 3: printk("init pin of group 3 ...\n"); break;}return 0;
}// LED控制函数:设置指定LED的开关状态
static int board_demo_led_ctl(int which, char status) 
{printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));// 根据GPIO组号执行不同的控制操作switch(GROUP(g_ledpins[which])) {case 0: printk("set pin of group 0 ...\n"); break;case 1: printk("set pin of group 1 ...\n"); break;case 2: printk("set pin of group 2 ...\n"); break;case 3: printk("set pin of group 3 ...\n"); break;}return 0;
}// LED操作接口结构体
static struct led_operations board_demo_led_opr = {.init = board_demo_led_init,.ctl  = board_demo_led_ctl,
};// 获取LED操作接口
struct led_operations *get_board_led_opr(void)
{return &board_demo_led_opr;
}// 平台设备探测函数:初始化LED设备
static int chip_demo_gpio_probe(struct platform_device *pdev)
{struct resource *res;int i = 0;// 遍历并注册所有LED资源while (1) {res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);if (!res) break;g_ledpins[g_ledcnt] = res->start;led_class_create_device(g_ledcnt);g_ledcnt++;}return 0;
}// 平台设备移除函数:清理LED设备
static int chip_demo_gpio_remove(struct platform_device *pdev)
{struct resource *res;int i = 0;// 注销所有LED设备while (1) {res = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res) break;led_class_destroy_device(i);i++;g_ledcnt--;}return 0;
}// 平台驱动定义
static struct platform_driver chip_demo_gpio_driver = {.probe      = chip_demo_gpio_probe,.remove     = chip_demo_gpio_remove,.driver     = {.name   = "100ask_led",  // 驱动名称},
};// 驱动初始化
static int __init chip_demo_gpio_drv_init(void)
{int err;// 注册平台驱动和LED操作err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr);return 0;
}// 驱动退出
static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");


 

leddrv.c:

  1. 使用 EXPORT_SYMBOL() 函数将创建、删除设备号和读取 led_operations 结构体函数导出给 chip_demo_gpio.c 使用
    EXPORT_SYMBOL():使其对所有内核代码可见,从而可以在其他内核模块中直接调用。若只包含在头文件中,则只有在该模块中可以使用
  2. 其他就是正常的 led 驱动操作,配置 file_operations 结构体,实现结构体成员的各种函数

上面已经解释了为什么不能通过get_board_led_opr函数获取led_operations结构体,这里不再重复


demo程序代码:(只打印信息,不做具体操作)

#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 "led_opr.h"  // 自定义LED操作接口头文件/* 主设备号:0表示由内核动态分配 */
static int major = 0;
static struct class *led_class;          // 设备类指针
struct led_operations *p_led_opr;        // LED操作函数集指针/* 工具宏:取最小值 */
#define MIN(a, b) (a < b ? a : b)/**************************** 设备管理接口 ****************************/
/*** @brief 创建设备节点* @param minor 次设备号*/
void led_class_create_device(int minor)
{// 在/dev目录下创建设备节点,命名为100ask_led0, 100ask_led1等device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor);
}/*** @brief 销毁设备节点 * @param minor 次设备号*/
void led_class_destroy_device(int minor)
{device_destroy(led_class, MKDEV(major, minor));
}/*** @brief 注册LED操作函数集* @param opr 包含init/ctl等操作函数的指针结构体*/
void register_led_operations(struct led_operations *opr)
{p_led_opr = opr;  // 保存外部传入的操作函数集
}/* 导出符号供其他模块使用 */
EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);/**************************** 文件操作接口 ****************************/
/* read操作(未实现实际功能) */
static ssize_t led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/*** @brief write操作:控制LED状态* @param file 文件结构体* @param buf 用户空间数据缓冲区(包含控制命令)* @param size 数据大小* @param offset 文件偏移* @return 成功写入的字节数*/
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;  // 存储LED状态(0/1)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);/* 调用注册的LED控制函数 */p_led_opr->ctl(minor, status);return 1;  // 返回已处理的字节数
}/*** @brief open操作:初始化LED* @param node inode结构体* @param file 文件结构体* @return 成功返回0*/
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初始化函数 */p_led_opr->init(minor);return 0;
}/* release操作(未实现实际功能) */
static int led_drv_close(struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 文件操作结构体定义 */
static struct file_operations led_drv = {.owner   = THIS_MODULE,  // 模块所有者.open    = led_drv_open,.read    = led_drv_read,.write   = led_drv_write,.release = led_drv_close,
};/**************************** 模块初始化/退出 ****************************/
/*** @brief 模块初始化函数* @return 成功返回0,失败返回错误码*/
static int __init led_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 注册字符设备(动态分配主设备号) */major = register_chrdev(0, "100ask_led", &led_drv);/* 2. 创建设备类 */led_class = class_create(THIS_MODULE, "100ask_led_class");if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led");return -1;}return 0;
}/*** @brief 模块退出函数*/
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 1. 销毁设备类 */class_destroy(led_class);/* 2. 注销字符设备 */unregister_chrdev(major, "100ask_led");
}/* 指定模块的初始化和退出函数 */
module_init(led_init);
module_exit(led_exit);/* 模块许可证声明(必需) */
MODULE_LICENSE("GPL");

 ledtest.c:

正常的测试函数,跟之前的都一样

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./ledtest /dev/100ask_led0 on* ./ledtest /dev/100ask_led0 off*/
int main(int argc, char **argv)
{int fd;char status;/* 1. 判断参数 */if (argc != 3) {printf("Usage: %s <dev> <on | off>\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}/* 3. 写文件 */if (0 == strcmp(argv[2], "on")){status = 1;write(fd, &status, 1);}else{status = 0;write(fd, &status, 1);}close(fd);return 0;
}

Makefile:

跟以往不同,这里需要把不同的驱动.c文件各自编译成单独的ko文件

1.必须拆分为独立.ko的情况:

  • 设备信息来自硬件(如DTB或ACPI)

  • 同一总线支持多种设备(如USB键盘/鼠标共用一个USB总线驱动)

2.可合并的情况一个.ko的情况:

  • 纯软件模拟的总线(如虚拟平台设备)

  • 设备固定且无需动态匹配

KERN_DIR = all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtest# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-m += leddrv.o chip_demo_gpio.o board_A_led.o

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

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

相关文章

操作系统常用命令

逻辑卷创建及挂载步骤&#xff1a; vgcreate vg_app /dev/sda //在sda盘上创建vg_app卷组 lvcreate -L 50G -n lv_mysql vg_app //在vg_app卷组上创建逻辑卷lv_mysql mkfs.xfs /dev/vg_app/lv_mysql //对lv_mysql 逻辑卷创建文件系统 mkdir mysql //创建mysql目录 ech…

Git 的进阶功能和技巧

1、分支的概念和使用 1.1、什么是分支&#xff1f; 分支&#xff08;Branch&#xff09;是在版本控制中非常重要的概念。几乎所有版本控制系统都支持某种形式的分支。在 Git 中&#xff0c;分支是 Git 强大功能之一&#xff0c;它允许我们从主开发线分离出来&#xff0c;在不…

mapbox基础,加载F4Map二维地图

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性二、🍀F4Map 简介2.1 ☘️技术特点2.2 ☘️核…

Conda使用方法详解

Conda是一个开源的包管理和环境管理系统&#xff0c;主要用于Python/R等科学计算领域&#xff0c;可以轻松管理不同项目的依赖关系。以下是Conda的详细使用方法&#xff1a; 一、安装与配置 1.安装Miniconda/Anaconda Miniconda是精简版&#xff0c;只包含conda和Python Ana…

Unity ViewportConstraint

一、组件功能概述 ViewportConstraint是一个基于世界坐标的UI边界约束组件&#xff0c;主要功能包括&#xff1a; 将UI元素限制在父容器范围内支持自定义内边距&#xff08;padding&#xff09;可独立控制水平和垂直方向的约束 二、实现原理 1. 边界计算&#xff08;世界坐…

代码随想录-动态规划24

leetcode-300-最长递增子序列 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 dp[j]是(0,i-1)不包括i的以nums[i-1]结尾的最长递增子序列长度 int lengthOfLIS(int* nums, int numsSize) {if(numsSize < 1)return numsSize;int dp[numsSize];for(int i 0 ; i &…

银河麒麟V10 Ollama+ShellGPT打造Shell AI助手——筑梦之路

环境说明 1. 操作系统版本: 银河麒麟V10 2. CPU架构&#xff1a;X86 3. Python版本&#xff1a;3.12.9 4. 大模型&#xff1a;mistral:7b-instruct 准备工作 1. 编译安装python 3.12 # 下载python 源码wget https://www.python.org/ftp/python/3.12.9/Python-3.12.9.tg…

2025 跨平台技术如何选:KMP 与 Flutter 的核心差异

前言 在移动开发的演进历程中&#xff0c;跨平台技术始终是一个充满争议却无法回避的话题。从早期的 React Native 到如今的 Kotlin Multiplatform&#xff08;KMP&#xff09;和 Flutter&#xff0c;开发者们始终在代码复用与原生体验之间寻找平衡。本文我们从技术实现、性能…

Python Cookbook-5.10 选取序列中最小的第 n个元素

任务 需要根据排名顺序从序列中获得第n个元素(比如&#xff0c;中间的元素&#xff0c;也被称为中值)。如果序列是已经排序的状态&#xff0c;应该使用seq[n]&#xff0c;但如果序列还未被排序&#xff0c;那么除了先对整个序列进行排序之外&#xff0c;还有没有更好的方法? …

列表之链表_C

数据结构&#xff08;邓俊辉&#xff09;&#xff1a;列表及相关概念_listnodeposi-CSDN博客 #include <stdio.h> #include <stdlib.h>// 定义Rank类型为int typedef int Rank;// 定义ListNode结构体 typedef struct ListNode {int data;struct ListNode* pred;st…

0401react中使用css-react-css-仿低代码平台项目

文章目录 1、普通方式-内联使用css2、引入css文件2.1、示例2.2、classnames 3、内联css与引入css文件对比3.1、内联css3.2、 外部 CSS 文件&#xff08;External CSS&#xff09; 4、css module5、sass6、classnames组合scss modules7、css-in-js7.1、CSS-in-JS 的核心特性7.2、…

鸿蒙开发者高级认证编程题库

题目一:跨设备分布式数据同步 需求描述 开发一个分布式待办事项应用,要求: 手机与平板登录同一华为账号时,自动同步任务列表任一设备修改任务状态(完成/删除),另一设备实时更新任务数据在设备离线时能本地存储,联网后自动同步实现方案 // 1. 定义分布式数据模型 imp…

stream流Collectors.toMap(),key值重复问题

文章目录 一、问题二、问题示例三、原因四、解决方法4.1、方案一 一、问题 发现Collectors.toMap的一个坑&#xff0c;若key值重复的时候会抛异常。如&#xff1a; IllegalStateException: Duplicate key 男 二、问题示例 报错示例如下&#xff1a; import lombok.AllArgsC…

未来 AI 发展趋势与挑战(AGI、数据安全、监管政策)

从 ChatGPT 的火爆到国内 DeepSeek、通义千问、百川智能等模型的兴起,AI 正以前所未有的速度走入各行各业。而下一阶段,AI 是否会发展出真正的“通用智能”(AGI)?数据隐私、技术伦理又该如何应对?本文将带你全面洞察未来 AI 的技术趋势与落地挑战。 一、AGI 的曙光:通用…

【微服务】SpringBoot整合LangChain4j 操作AI大模型实战详解

【微服务】SpringBoot整合LangChain4j 操作AI大模型实战详解 一、前言 随着人工智能技术的飞速发展&#xff0c;AI大模型已经在众多领域展现出强大的能力&#xff0c;为业务拓展和商业价值提升带来了新的机遇。SpringBoot作为一款广受欢迎的Java微服务框架&#xff0c;以其简…

一种单脉冲雷达多通道解卷积前视成像方法【论文阅读】

一种单脉冲雷达多通道解卷积前视成像方法-李悦丽-2007 1. 论文的研究目标与实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文提出的思路、方法及模型2.1 多通道解卷积(MCD)技术的核心思想2.1.1 数学模型与公式推导2.1.2 针对单脉冲雷达的改进2.2 方法与传统技术的对比3. 实…

Codeforces Round 1016 (Div. 3)题解

题目地址 https://codeforces.com/contest/2093 锐评 在所有题意都理解正确的情况下&#xff0c;整体难度不算太难。但是偏偏存在F这么恶心的题意&#xff0c;样例都不带解释一下的&#xff0c;根本看不懂题。D题也恶心&#xff0c;在于递归过程的拆分&#xff0c;需要点数学…

【python读取并显示遥感影像】

在Python中读取并显示遥感影像&#xff0c;可以使用rasterio库读取影像数据&#xff0c;并结合matplotlib进行可视化。以下是一个完整的示例代码&#xff1a; import rasterio import matplotlib.pyplot as plt import numpy as np# 打开遥感影像文件 with rasterio.open(path…

怎样使用Python编写的Telegram聊天机器人

怎样使用Python编写的Telegram聊天机器人 代码直接运行可用 以下是对这段代码的详细解释: 1. 导入必要的库 import loggingfrom telegram import Update from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, filters, MessageHandler import log…