一、驱动说明
本驱动基于 platform 总线、设备与驱动的核心原理,适配 Linux 6.6 内核特性,以“虚拟字符设备”为载体,实现完整的 platform 驱动框架。
Linux 6.6 内核适配要点:
优先采用设备树(DTB)匹配方式,废弃板文件硬编码注册 platform_device(Linux 3.x 后已逐步淘汰板文件注册,6.6 内核完全推荐设备树枚举);
电源管理采用 device_driver 中 dev_pm_ops 结构体(过时 suspend/resume 回调已完全弃用);
全程使用 devm_xxx 资源管理接口(自动释放资源,避免内存泄漏,6.6 内核强制推荐);
完善设备树匹配相关接口(of_device_id、MODULE_DEVICE_TABLE 规范);
适配内核最新的 platform_driver 注册宏(module_platform_driver 仍可用,补充完善驱动注销逻辑)。
二、完整驱动代码(platform_demo.c)
#include<linux/module.h>#include<linux/platform_device.h>#include<linux/cdev.h>#include<linux/fs.h>#include<linux/mutex.h>#include<linux/wait.h>#include<linux/of.h>#include<linux/of_device.h>#include<linux/pm.h>#include<linux/slab.h>// 设备私有数据(整合字符设备和platform设备信息)structplatform_demo_dev {structcdevcdev;// 字符设备核心结构体dev_t devno; // 设备号structmutexmutex;// 互斥锁,保证读写原子性wait_queue_head_t r_wait; // 读等待队列wait_queue_head_t w_wait; // 写等待队列char buf[1024]; // 数据缓冲区(类似globalfifo)int buf_len; // 缓冲区数据长度};// 全局变量(简化设计,实际开发可封装为私有数据)staticint demo_major = 0; // 主设备号(自动分配)staticstructplatform_demo_dev *demo_devp;// 设备私有数据指针// 字符设备读操作staticssize_tplatform_demo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos){structplatform_demo_dev *dev = filp->private_data;ssize_t ret = 0;// 互斥锁保护,避免并发读写 mutex_lock(&dev->mutex);// 等待队列:无数据时阻塞读进程 wait_event_interruptible(dev->r_wait, dev->buf_len > 0);// 拷贝数据到用户空间(避免内核空间直接访问用户空间) ret = copy_to_user(buf, dev->buf, min(count, (size_t)dev->buf_len));if (ret < 0) { mutex_unlock(&dev->mutex);return -EFAULT; }// 更新缓冲区状态 dev->buf_len -= ret; memmove(dev->buf, dev->buf + ret, dev->buf_len); // 数据前移// 唤醒写等待队列(有空闲空间了) wake_up_interruptible(&dev->w_wait); mutex_unlock(&dev->mutex);return ret;}// 字符设备写操作staticssize_tplatform_demo_write(struct file *filp, constchar __user *buf, size_t count, loff_t *ppos){structplatform_demo_dev *dev = filp->private_data;ssize_t ret = 0; mutex_lock(&dev->mutex);// 等待队列:缓冲区满时阻塞写进程 wait_event_interruptible(dev->w_wait, dev->buf_len < sizeof(dev->buf));// 从用户空间拷贝数据到内核缓冲区 ret = copy_from_user(dev->buf + dev->buf_len, buf, min(count, (size_t)(sizeof(dev->buf) - dev->buf_len)));if (ret < 0) { mutex_unlock(&dev->mutex);return -EFAULT; }// 更新缓冲区长度 dev->buf_len += ret;// 唤醒读等待队列(有数据可读了) wake_up_interruptible(&dev->r_wait); mutex_unlock(&dev->mutex);return ret;}// 字符设备打开操作staticintplatform_demo_open(struct inode *inode, struct file *filp){structplatform_demo_dev *dev = container_of(inode->i_cdev, structplatform_demo_dev, cdev); filp->private_data = dev; // 将私有数据绑定到文件指针return0;}// 字符设备释放操作staticintplatform_demo_release(struct inode *inode, struct file *filp){// 无需额外操作,devm接口会自动释放资源return0;}// 字符设备操作集(驱动核心入口)staticconststructfile_operationsplatform_demo_fops = { .owner = THIS_MODULE, .open = platform_demo_open, .release = platform_demo_release, .read = platform_demo_read, .write = platform_demo_write,};// -------------------------- platform驱动核心接口 --------------------------// probe函数:设备与驱动匹配成功后执行// 功能:初始化字符设备、申请资源、初始化等待队列和互斥锁staticintplatform_demo_probe(struct platform_device *pdev){int ret;structdevice *dev = &pdev->dev; pr_info("platform demo probe: device matched successfully!\n");// 1. 分配设备私有数据(devm_kzalloc:内核自动释放,避免内存泄漏) demo_devp = devm_kzalloc(dev, sizeof(struct platform_demo_dev), GFP_KERNEL);if (!demo_devp) { pr_err("devm_kzalloc failed: no memory for device private data\n");return -ENOMEM; }// 2. 申请设备号(devm_alloc_chrdev_region:自动释放设备号) ret = devm_alloc_chrdev_region(&demo_devp->devno, 0, 1, "platform_demo");if (ret < 0) { pr_err("devm_alloc_chrdev_region failed: %d\n", ret);return ret; } demo_major = MAJOR(demo_devp->devno); pr_info("platform demo: major = %d\n", demo_major);// 3. 初始化字符设备(cdev_init + cdev_add) cdev_init(&demo_devp->cdev, &platform_demo_fops); demo_devp->cdev.owner = THIS_MODULE; ret = devm_cdev_add(dev, &demo_devp->cdev, demo_devp->devno, 1);if (ret < 0) { pr_err("devm_cdev_add failed: %d\n", ret);return ret; }// 4. 初始化互斥锁和等待队列 mutex_init(&demo_devp->mutex); init_waitqueue_head(&demo_devp->r_wait); init_waitqueue_head(&demo_devp->w_wait);// 5. (可选)从设备树获取资源和platform_data(适配Linux 6.6设备树优先原则)// 示例:获取中断资源structresource *irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);if (irq_res) { pr_info("platform demo: get irq resource: start = %lu, end = %lu\n", irq_res->start, irq_res->end); }// (可选)获取platform_data// struct demo_plat_data *pdata = dev_get_platdata(dev);// if (pdata) {// pr_info("platform demo: platform_data config: %d\n", pdata->config);// }return0;}// remove函数:设备与驱动解绑时执行(释放资源,与probe函数对应)staticintplatform_demo_remove(struct platform_device *pdev){ pr_info("platform demo remove: device unbind successfully!\n");// 无需手动释放资源:devm_xxx接口会自动释放设备号、cdev、私有数据等return0;}// -------------------------- 电源管理适配(Linux 6.6规范) --------------------------// 使用dev_pm_ops结构体staticintplatform_demo_suspend(struct device *dev){ pr_info("platform demo suspend: enter low power mode\n");// 此处可添加电源管理逻辑(如关闭设备时钟、保存设备状态)return0;}staticintplatform_demo_resume(struct device *dev){ pr_info("platform demo resume: exit low power mode\n");// 此处可添加恢复逻辑(如恢复设备时钟、恢复设备状态)return0;}// 电源管理操作集(绑定到device_driver)staticconststructdev_pm_opsplatform_demo_pm_ops = { .suspend = platform_demo_suspend, .resume = platform_demo_resume,};// -------------------------- 设备树匹配(Linux 6.6核心匹配方式) --------------------------// of_device_id:设备树匹配表,与dts中的compatible属性对应staticconststructof_device_idplatform_demo_of_match[] = { { .compatible = "demo,platform-demo" }, // 必须与dts中的compatible一致 { /* sentinel */ } // 哨兵,标识匹配表结束};MODULE_DEVICE_TABLE(of, platform_demo_of_match); // 向内核注册匹配表// -------------------------- platform_driver结构体(驱动核心) --------------------------staticstructplatform_driverplatform_demo_driver = { .driver = { .name = "platform_demo", // 驱动名(备用匹配方式,优先级低于设备树) .owner = THIS_MODULE, // 驱动所属模块 .of_match_table = of_match_ptr(platform_demo_of_match), // 设备树匹配表 .pm = &platform_demo_pm_ops, // 电源管理操作集(适配6.6内核) }, .probe = platform_demo_probe, // 匹配成功回调 .remove = platform_demo_remove, // 解绑回调};// 注册platform驱动(module_platform_driver宏:自动实现module_init/exit)// 等价于:module_init(platform_driver_register) + module_exit(platform_driver_unregister)module_platform_driver(platform_demo_driver);// 模块信息(必须添加,否则内核编译警告)MODULE_LICENSE("GPL"); // 开源协议(GPL兼容内核)MODULE_AUTHOR("Linux Driver Developer");MODULE_DESCRIPTION("Linux 6.6 Platform Device Driver Demo");MODULE_VERSION("1.0");
三、设备树配置
在对应开发板的dts文件(如arch/arm/boot/dts/xxx.dts)中添加如下节点,实现platform_device的枚举(无需手动调用platform_add_devices):
// platform设备节点(与驱动中of_device_id的compatible对应)platform_demo@0 { compatible = "demo,platform-demo"; // 必须与驱动中of_match_table的compatible一致 reg = <0x12340000 0x1000>; // 示例:设备内存资源(start=0x12340000, end=0x12340fff) interrupt-parent = <&gpio1>; // 示例:中断父控制器 interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; // 示例:中断号5,高电平触发 // (可选)platform_data配置 // demo-config = <0x01>;};
四、Makefile(Linux 6.6内核编译配置)
# 内核源码路径(需根据实际开发环境修改,如开发板内核路径)KERNELDIR ?= /home/user/linux-6.6.y# 当前驱动目录PWD := $(shell pwd)# 驱动模块名obj-m += platform_demo.o# 编译规则modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules# 清理编译产物clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
五、关键适配说明
1. 设备注册方式(核心区别)
Linux 2.6/3.x 采用“板文件硬编码注册 platform_device”,而 Linux 6.6 完全采用设备树枚举,通过 dts 节点定义 platform_device,驱动通过 of_device_id 匹配,无需修改板文件,可移植性更强。
2. 资源管理(适配6.6内核规范)
使用普通的 kzalloc、register_chrdev_region 等接口,需手动释放资源;本驱动全部使用 devm_xxx 接口(devm_kzalloc、devm_alloc_chrdev_region、devm_cdev_add),内核会在驱动卸载时自动释放资源,避免内存泄漏和资源残留,符合 Linux 6.6 内核的资源管理规范。
3. 电源管理(废弃过时接口)
“直接填充 platform_driver 的 suspend/resume 已过时”,Linux 6.6 内核完全弃用该方式,改为通过 device_driver 的 pm 成员(dev_pm_ops 结构体)实现电源管理,本驱动已完整适配。
4. 匹配方式(设备树优先)
参考 platform_match 函数,Linux 6.6 内核优先采用“设备树风格匹配”(of_driver_match_device),其次是 ACPI、ID表、名字匹配,本驱动重点实现了设备树匹配,符合内核最新趋势。
六、编译与测试步骤
将驱动代码(platform_demo.c)、Makefile 放在同一目录;
修改 Makefile 中的 KERNELDIR 为实际的 Linux 6.6 内核源码路径;
执行 make 命令,编译生成 platform_demo.ko 驱动模块;
修改开发板 dts 文件,添加上述 platform 设备节点,重新编译设备树(make dtbs);
将 platform_demo.ko 拷贝到开发板,执行 insmod platform_demo.ko 加载驱动;
查看驱动加载情况:dmesg | grep platform_demo(应显示“device matched successfully”);
测试字符设备功能:通过 cat、echo 命令操作 /dev/platform_demo(需先创建设备节点:mknod /dev/platform_demo c 主设备号 0)。
七、注意事项
驱动中的 compatible 属性必须与 dts 节点中的 compatible 完全一致,否则无法匹配;
若不需要中断、内存等资源,可删除 dts 中的 reg、interrupts 节点,同时删除驱动中 platform_get_resource 相关代码;
Linux 6.6 内核编译时,需确保 CONFIG_PLATFORM_DEVICE 选项已开启(默认开启);
实际开发中,可根据 resource、platform_data 的用法,扩展驱动的资源获取和配置功能(如添加 GPIO、DMA 资源)。