Linux 平台总线驱动开发实战

  • 一、平台总线驱动基础概念
  • 二、核心数据结构解析
  • 2.1 设备结构体 struct platform_device
    • 2.2 驱动结构体 struct platform_driver
    • 2.3 资源结构体 struct resource
  • 三、驱动开发完整流程
    • 3.1 设备注册
    • 3.2 驱动注册
    • 3.3 设备与驱动匹配
  • 四、编译与测试
    • 4.1 编译模块
    • 4.2 加载与验证
  • 五、总结

在 Linux 驱动开发领域,平台总线驱动是连接硬件设备与内核的重要桥梁,它通过将设备和驱动分离管理,极大提升了驱动的复用性和系统的可维护性。本文将深入剖析平台总线驱动的工作原理,并结合完整代码示例,帮助开发者掌握其核心开发流程。

一、平台总线驱动基础概念

Linux 平台总线驱动基于设备、驱动和总线三者的协同工作,形成了一套高效的设备管理机制:

设备(Device): 代表具体的硬件实体,描述设备名称、资源占用(如内存地址、中断号)等信息。
驱动(Driver): 包含操作硬件的核心代码,通过probe、remove等回调函数完成设备初始化与资源释放。
总线(Bus): 负责两者的注册、匹配与通信,确保驱动能正确识别并控制设备。

平台总线的核心优势在于分离设备与驱动,同一驱动可适配多种同类设备,设备升级时仅需修改设备描述,无需改动驱动代码,显著提升开发效率。

二、核心数据结构解析

2.1 设备结构体 struct platform_device

struct platform_device {const char  *name;      // 设备名称,用于驱动匹配int         id;         // 设备ID(-1表示自动分配)struct device dev;      // 通用设备结构u32         num_resources;  // 资源数量struct resource *resource;  // 硬件资源数组// 其他字段
};

通过填充resource数组,可指定设备的内存、中断等资源。例如:

static struct resource my_gpio_resources[] = {[0] = {.start = 0xFDD60000,       // GPIO控制器基地址.end   = 0xFDD60004,.flags = IORESOURCE_MEM,},[1] = {.start = 15,       .end   = 15,.flags = IORESOURCE_IRQ,}};

2.2 驱动结构体 struct platform_driver

struct platform_driver {int (*probe)(struct platform_device *);  // 设备匹配成功时调用int (*remove)(struct platform_device *); // 设备移除时调用struct device_driver driver;             // 通用驱动结构const struct platform_device_id *id_table; // 支持的设备ID表// 其他字段
};

probe函数是驱动核心,用于初始化设备;id_table定义驱动支持的设备列表,辅助总线完成匹配。

2.3 资源结构体 struct resource

struct resource {resource_size_t start;  // 资源起始地址resource_size_t end;    // 资源结束地址unsigned long flags;    // 资源类型(如`IORESOURCE_MEM`、`IORESOURCE_IRQ`)// 其他字段
};

三、驱动开发完整流程

3.1 设备注册

定义设备资源: 通过struct resource数组描述硬件资源。
初始化设备结构体: 填充struct platform_device,指定名称、ID 和资源。
注册设备到总线: 调用platform_device_register函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>// 设备名称(用于匹配驱动)
#define DEVICE_NAME "my_gpio_controller"// 定义设备资源(内存区域和中断)
static struct resource my_gpio_resources[] = {[0] = {.start = 0xFDD60000,       // GPIO控制器基地址.end   = 0xFDD60004,.flags = IORESOURCE_MEM,},[1] = {.start = 15,       .end   = 15,.flags = IORESOURCE_IRQ,}};static void my_gpio_release(struct device *dev){printk(KERN_ERR "my_gpio_release\n");
}// 平台设备结构体
static struct platform_device my_gpio_device = {.name           = DEVICE_NAME,.id             = -1,          // 自动分配ID.num_resources  = ARRAY_SIZE(my_gpio_resources),.resource       = my_gpio_resources,.dev            = {.release    = my_gpio_release},
};// 模块初始化函数
static int __init platform_device_init(void) {int ret;// 注册平台设备ret = platform_device_register(&my_gpio_device);if (ret) {printk(KERN_ERR "Failed to register platform device: %d\n", ret);return ret;}printk(KERN_INFO "Platform device registered: %s\n", DEVICE_NAME);return 0;
}// 模块退出函数
static void __exit platform_device_exit(void) {// 注销平台设备platform_device_unregister(&my_gpio_device);printk(KERN_INFO "Platform device unregistered\n");
}module_init(platform_device_init);
module_exit(platform_device_exit);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Platform Device");
MODULE_AUTHOR("cmy");

3.2 驱动注册

实现驱动回调函数: 编写probe、remove等核心函数。
初始化驱动结构体: 指定驱动名称、支持的设备列表等。
注册驱动到总线: 调用platform_driver_register函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/device.h>// 驱动支持的设备名称
#define DRIVER_NAME "my_gpio_controller"// 寄存器偏移量
#define GPIO_SWPORT_DDR 0x0008 //输入输出偏移量
#define GPIO_SWPORT_DR 0x0000 //高低电平偏移量// 设备私有数据结构
struct gpio_dev {struct device *dev;dev_t dev_num;void __iomem *regs;         // 映射后的寄存器基址int irq;                    // 中断号struct class *class;        // 设备类struct cdev cdev;			// 字符设备结构
};// 文件操作函数
static ssize_t gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {struct gpio_dev *gdev = filp->private_data;int value = 0;if (!gdev->regs) {printk(KERN_ERR "gpio_read gdev->regs can't be null\n");return -EFAULT;}u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DR);value = (gp_cfg >> 15) & 1;//获取第15位printk(KERN_INFO "gpio_read value = %d\n", value);if (copy_to_user(buf, &value, sizeof(int))) {return -EFAULT;}return 0;
}static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {struct gpio_dev *gdev = filp->private_data;int value = 0;if (copy_from_user(&value, buf, sizeof(int))) {return -EFAULT;}if (!gdev->regs) {printk(KERN_ERR "gpio_write gdev->regs can't be null\n");return -EFAULT;}u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DR);printk(KERN_INFO "gpio_write GPIO_SWPORT_DR gp_cfg = %llx\n", gp_cfg);if(value){gp_cfg |= 1 << 31;//Write access enablegp_cfg |= 1 << 15;//high}else{gp_cfg |= 1 << 31;//Write access enableprintk(KERN_INFO " gpio_write Write access gp_cfg = %llx\n", gp_cfg);gp_cfg &= ~(1 << 15);//lowprintk(KERN_INFO " gpio_write low gp_cfg = %llx\n", gp_cfg);}iowrite64(gp_cfg, gdev->regs + GPIO_SWPORT_DR);printk(KERN_INFO " gpio_write gp_cfg = %llx\n", gp_cfg);return 0;
}static int gpio_open(struct inode *inode, struct file *filp) {struct gpio_dev *gdev = container_of(inode->i_cdev, struct gpio_dev, cdev);filp->private_data = gdev;return 0;
}static int gpio_release(struct inode *inode, struct file *filp) {return 0;
}// 文件操作表
static const struct file_operations gpio_fops = {.owner = THIS_MODULE,.open = gpio_open,.read = gpio_read,.write = gpio_write,.release = gpio_release,
};// 驱动probe函数(设备匹配成功时调用)
static int gpio_probe(struct platform_device *pdev) {struct gpio_dev *gdev;struct resource *res;int ret;// 分配并初始化设备结构体gdev = devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL);if (!gdev) {dev_err(&pdev->dev, "Failed to allocate memory\n");return -ENOMEM;}gdev->dev = &pdev->dev;platform_set_drvdata(pdev, gdev);// 获取内存资源并映射res = platform_get_resource(pdev, IORESOURCE_MEM, 0);printk(KERN_INFO "gpio_probe res->start = %llx, res->end = %llx\n", res->start, res->end);/*if (!res) {dev_err(&pdev->dev, "Missing memory resource\n");return -ENODEV;}gdev->regs = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(gdev->regs)) {dev_err(&pdev->dev, "Failed to map memory resource\n");return PTR_ERR(gdev->regs);}*/// 注册字符设备ret = alloc_chrdev_region(&gdev->dev_num, 0, 1, pdev->name);if (ret) {dev_err(&pdev->dev, "Failed to allocate char device region\n");return ret;}cdev_init(&gdev->cdev, &gpio_fops);gdev->cdev.owner = THIS_MODULE;ret = cdev_add(&gdev->cdev, gdev->dev_num, 1);if (ret) {dev_err(&pdev->dev, "Failed to add char device\n");unregister_chrdev_region(gdev->dev_num, 1);return ret;}// 创建设备类gdev->class = class_create(THIS_MODULE, pdev->name);if (IS_ERR(gdev->class)) {dev_err(&pdev->dev, "Failed to create class\n");cdev_del(&gdev->cdev);unregister_chrdev_region(gdev->dev_num, 1);return PTR_ERR(gdev->class);}// 创建设备节点gdev->dev = device_create(gdev->class, NULL, gdev->dev_num, NULL, pdev->name);if (IS_ERR(gdev->dev)) {dev_err(&pdev->dev, "Failed to create device\n");class_destroy(gdev->class);cdev_del(&gdev->cdev);unregister_chrdev_region(gdev->dev_num, 1);return PTR_ERR(gdev->dev);}// 映射GPIO寄存器gdev->regs = ioremap(res->start, res->end - res->start);if (!gdev->regs) {printk(KERN_ERR "gpio_probe Failed to ioremap\n");cdev_del(&gdev->cdev);unregister_chrdev_region(gdev->dev_num, 1);return -ENOMEM;}printk(KERN_INFO "gpio_probe gdev->regs = %llx\n", gdev->regs);u64 gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DDR);printk(KERN_INFO "gpio_probe GPIO_SWPORT_DDR gp_cfg = %llx\n", gp_cfg);gp_cfg |= 1 << 31;// Write access enablegp_cfg |= 1 << 15;//Output//设置输出模式iowrite64(gp_cfg, gdev->regs + GPIO_SWPORT_DDR);gp_cfg = ioread64(gdev->regs + GPIO_SWPORT_DDR);printk(KERN_INFO "gpio_probe initialized successfully gp_cfg = %llx\n", gp_cfg);dev_info(&pdev->dev, "GPIO driver initialized\n");return 0;
}// 驱动remove函数(设备移除时调用)
static int gpio_remove(struct platform_device *pdev) {struct gpio_dev *gdev = platform_get_drvdata(pdev);if (gdev->regs) {iounmap(gdev->regs);  // 解除映射gdev->regs = NULL;}// 清理资源device_destroy(gdev->class, gdev->dev_num);class_destroy(gdev->class);cdev_del(&gdev->cdev);unregister_chrdev_region(gdev->dev_num, 1);dev_info(&pdev->dev, "GPIO driver removed\n");return 0;
}// 驱动支持的设备ID表const struct platform_device_id gpio_device_id = {.name = DRIVER_NAME,
};// 平台驱动结构体
static struct platform_driver gpio_driver = {.probe      = gpio_probe,.remove     = gpio_remove,.driver     = {.name   = DRIVER_NAME,.owner  = THIS_MODULE,},.id_table   = &gpio_device_id,
};// 模块初始化
static int __init gpio_driver_init(void) {return platform_driver_register(&gpio_driver);
}// 模块退出
static void __exit gpio_driver_exit(void) {platform_driver_unregister(&gpio_driver);
}module_init(gpio_driver_init);
module_exit(gpio_driver_exit);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example Platform Driver");
MODULE_AUTHOR("cmy");

3.3 设备与驱动匹配

总线通过设备和驱动的name字段进行匹配,匹配成功后自动调用驱动的probe函数初始化设备。若id_table存在,总线会优先检查设备是否在支持列表中。

四、编译与测试

4.1 编译模块

创建Makefile:

export ARCH=arm64export CROSS_COMPILE=/home/chenmy/rk356x/RK356X_Android11.0/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-obj-m += platform_device_example.oobj-m += platform_driver_example.oKERNEL_DIR:=/home/chenmy/rk356x/RK356X_Android11.0/kernelall:make -C $(KERNEL_DIR) M=$(PWD) modules
clean:make -C $(KERNEL_DIR) M=$(PWD) clean

执行make生成.ko模块文件。

4.2 加载与验证

 # 加载设备模块
insmod platform_device_example.ko
# 加载驱动模块
insmod platform_driver_example.ko# 查看设备与驱动状态
ls /sys/bus/platform/devices | grep "my"
ls /sys/bus/platform/drivers | grep "my"

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、总结

Linux 平台总线驱动通过标准化的设备与驱动分离模型,显著提升了驱动开发的效率与代码复用性。本文通过原理解析与完整代码示例,展示了从设备注册、驱动实现到匹配测试的全流程。在实际开发中,开发者可根据硬件需求灵活调整资源配置与回调函数逻辑,构建稳定高效的驱动程序。

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

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

相关文章

LabVIEW液位上升图像识别 附件有源码

源程序在这里https://www.bjcyck.com/nd.jsp?fromColId101&id2675#_np101_331 本LabVIEW 程序实现基于图像灰度特征的液位上升监测与控制&#xff0c;通过读取序列液位上升图像&#xff0c;分析指定区域灰度变化获取液位斜率&#xff0c;依据设定标记位置实现液位上升到目…

git安装使用详细教程

git高速下载 macOS 系统 # 方法1&#xff1a;Homebrew&#xff08;推荐&#xff09; brew install git# 方法2&#xff1a;官方安装包 下载地址&#xff1a;https://sourceforge.net/projects/git-osx-installer/Linux 系统 # Debian/Ubuntu sudo apt update && sudo…

玛哈特机械矫平机:精密制造的“应力消除师”与“平整度雕刻家”

机械矫平机&#xff0c;作为金属板材加工链中的关键一环&#xff0c;其价值远不止于“压平”那么简单。它是材料科学、精密机械与控制技术的结晶&#xff0c;是确保高端制造品质的幕后功臣。本文将深入探讨其核心机理、进阶应用及未来方向。 一、 矫平机理再探&#xff1a;超越…

四色(定理/猜想)染色算法小软件Version1.11 2025.6.24 开发者:孝感动天/卧冰求鲤

四色(定理/猜想)染色算法小软件Version1.11 2025.6.24 开发者&#xff1a;孝感动天/卧冰求鲤 开发者&#xff1a;路人甲/打酱油 开发者&#xff1a;四色定要治理/四邻不安/相邻必反/草木皆兵/围棋紧箍/不是我~干的/和我无关 开发者&#xff1a;不是我/不是我干的&#xff0c…

SQL 分页方法全解析:从基础到高级应用

一、引言 在 Web 应用和数据分析中&#xff0c;分页是处理大量数据的必备功能。想象一下&#xff0c;如果没有分页&#xff0c;社交媒体的动态流、电商平台的商品列表都将变成无穷无尽的长页面&#xff0c;用户体验和系统性能都会受到严重影响。本文将深入探讨 SQL 中各种分页方…

STM32 adc采集数据存到SD卡中

F1板子实现adc采集模拟信号存储到SD卡中 STM32 adc采集数据存到SD卡中/STM32SD文件系统ADC采集/AD/adc_dma.c , 10291 STM32 adc采集数据存到SD卡中/STM32SD文件系统ADC采集/AD/adc_dma.h , 661 STM32 adc采集数据存到SD卡中/STM32SD文件系统ADC采集/CMSIS/core_cm3.c , 17273…

redis8.0新特性:布谷鸟过滤器(Cuckoo Filter)详解

文章目录 一、写在前面二、使用1、CF.RESERVE 创建布谷鸟过滤器2、CF.ADD 添加元素3、CF.ADDNX 不存在才添加4、CF.COUNT 判断元素添加次数5、CF.DEL 删除一次元素6、CF.EXISTS 判断元素是否存在7、CF.MEXISTS 批量判断元素是否存在8、CF.INFO 查看布谷鸟过滤器信息9、CF.INSER…

2025 Java秋招『面试避坑指南』:牛客网高频题分类精讲

前言 今天为大家整理了目前互联网出现率最高的大厂面试题&#xff0c;所谓八股文也就是指文章的八个部分&#xff0c;文体有固定格式:由破题、承题、起讲、入题、起股、中股、后股、束股八部分组成&#xff0c;题目一律出自四书五经中的原文。 初中级和中高级都有&#xff0c…

git安装使用和git命令大全

Git高速下载 程序员面试资料大全&#xff5c;各种技术书籍等资料-1000G Git 命令大全 一、基础操作 1. 初始化与克隆 命令说明示例git init初始化本地仓库git initgit clone克隆远程仓库git clone https://github.com/user/repo.gitgit remote add添加远程仓库git remote ad…

非常好用的markdown转pdf工具

在文档处理和知识管理中&#xff0c;Markdown因其简洁易读的特性而广受欢迎&#xff0c;而PDF格式则因其广泛的兼容性和稳定性而被广泛用于文档分享和存档。然而&#xff0c;将Markdown文档高效地转换为PDF格式&#xff0c;同时保留格式和样式&#xff0c;一直是许多用户的需求…

八股文——JAVA基础:基本数据类型与包装类的区别

基本数据类型包含八种&#xff0c; 1.用途不同&#xff0c;在目前编程而言&#xff0c;基本除了使用局部变量会使用基本数据类型外&#xff0c;都会去使用包装类。包装类能够适用泛型是目前企业编程使用包装类的主要原因&#xff0c;而基本类型不行。除此之外&#xff0c;包装…

从0开始学习R语言--Day30--函数型分析

在研究离散变量之间的影响时&#xff0c;我们往往只能获取类似中位数&#xff0c;平均数点来额外数据特点&#xff1b;但如果数据本身具有时间特性的话&#xff0c;我们可以尝试运用函数型分析&#xff0c;将静态的离散点转为动态过程来分析&#xff0c;即若本来是分析离散点对…

Agent轻松通-P3:分析我们的Agent

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录 1 引言2 使用工具分析Agent&#xff1a;”日志“…

如何将FPGA设计验证效率提升1000倍以上(1)

我们将以三个设计样例&#xff0c;助力您提升设计开发效率。 对于FPGA应用开发来说&#xff0c;代码是写出来的&#xff0c;更是调试出来的。软件仿真拥有最佳的信号可见性和调试灵活性&#xff0c;被大多数工程师熟练使用&#xff0c;能够高效捕获很多显而易见的常见错误。 …

RabbitMQ 利用死信队列来实现延迟消息

RabbitMQ 利用死信队列来实现延迟消息 基于 TTL&#xff08;Time-To-Live&#xff09; 死信队列&#xff08;DLX&#xff09;的方式来实现延迟消息 首先消息会被推送到普通队列中&#xff0c;该消息设置了TTL&#xff0c;当TTL到期未被消费掉&#xff0c;则会自动进入死信队列…

Keepalived+Haproxy+Redis三主三从

一、集群部署 1、案例拓扑 2、资源列表 主从节点是随机分配的&#xff0c;下属列表只是框架&#xff1a; 操作系统主机名配置IP应用OpenEuler24master12C4G192.168.10.101RedisOpenEuler24master22C4G192.168.10.102RedisOpenEuler24master32C4G192.168.10.103RedisOpenEule…

Modbus转IEC104网关:电力自动化系统的桥梁

现代电力系统中&#xff0c;变电站、发电厂以及配电网络中存在大量采用不同通信协议的设备。Modbus协议因其简单易用在现场设备中广泛部署&#xff0c;而电力行业主流监控系统则普遍采用IEC 60870-5-104&#xff08;简称IEC104&#xff09;协议。协议差异导致的数据孤岛现象&am…

@annotation:Spring AOP 的“精准定位器“

想象你是一位快递员&#xff0c;负责给一个大型社区送快递。社区里有几百户人家&#xff0c;但只有特定家庭需要特殊服务&#xff1a; 普通快递&#xff1a;直接放快递柜生鲜快递&#xff1a;需要冷藏处理贵重物品&#xff1a;需要本人签收药品快递&#xff1a;需要优先配送 …

Web Worker使用指南 解锁浏览器多线程 ,提升前端性能的利器

文章目录 前言一、什么是 Web Worker二、适用场景1、CPU 密集型计算2、图像/视频处理3、实时数据流处理&#xff08;高频场景&#xff09;4、后台文件操作5、复杂状态机/AI逻辑&#xff08;游戏开发&#xff09;6、长轮询与心跳检测7、WebAssembly 加速8、WebGL 与 Canvas 渲染…

React 18.2.0 源码打包

一、React源码地址 GitHub&#xff1a;React 二、参考文章 sourcemap实战-生成react源码sourcemap Rollup中文文档 JavaScript Source Map 详解 全网最优雅的 React 源码调试方式 三、打包操作 安装依赖 // 全局安装yarn npm i -g yarn // 源码项目目录下执行yarn安装依赖…