当前位置:首页>Linux>Linux内核platform设备驱动

Linux内核platform设备驱动

  • 2026-09-21 23:55:35
Linux内核platform设备驱动

一、驱动说明

本驱动基于 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表、名字匹配,本驱动重点实现了设备树匹配,符合内核最新趋势。

六、编译与测试步骤

  1. 将驱动代码(platform_demo.c)、Makefile 放在同一目录;

  2. 修改 Makefile 中的 KERNELDIR 为实际的 Linux 6.6 内核源码路径;

  3. 执行 make 命令,编译生成 platform_demo.ko 驱动模块;

  4. 修改开发板 dts 文件,添加上述 platform 设备节点,重新编译设备树(make dtbs);

  5. 将 platform_demo.ko 拷贝到开发板,执行 insmod platform_demo.ko 加载驱动;

  6. 查看驱动加载情况:dmesg | grep platform_demo(应显示“device matched successfully”);

  7. 测试字符设备功能:通过 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 资源)。

最新文章

随机文章