Linux平台总线驱动开发与设备树实践
1. Linux平台总线驱动概述在嵌入式Linux开发中我们经常需要为各种外设编写驱动程序。传统的字符设备驱动虽然简单直接但存在一个明显的问题硬件信息如寄存器地址、中断号等与驱动代码高度耦合。这种设计会导致驱动代码难以复用当硬件发生变化时需要大量修改代码。平台总线platform bus是Linux内核为解决这个问题而引入的一种虚拟总线机制。它专门用于管理那些没有物理总线的简单设备如LED、按键、蜂鸣器等。通过平台总线我们可以将硬件信息与驱动代码分离实现更灵活的驱动开发模式。2. 平台总线核心组件解析2.1 platform_device结构体platform_device用于描述平台设备其核心定义如下struct platform_device { const char *name; // 设备名称用于总线匹配 int id; // 设备ID用于区分同名设备 struct device dev; // 继承自设备模型的基类 u32 num_resources; // 资源数量 struct resource *resource; // 硬件资源数组 const struct platform_device_id *id_entry; // 匹配结果 };关键成员解析resource描述硬件资源内存、中断等dev.platform_data可存放任意软件配置信息name必须与驱动中的名称匹配才能成功绑定2.2 platform_driver结构体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函数在probe中完成设备初始化和资源获取设备移除时调用remove进行清理2.3 平台总线匹配机制平台总线的匹配过程由platform_match函数实现支持四种匹配方式按优先级排序设备树匹配通过compatible属性匹配ACPI匹配用于电源管理相关设备ID表匹配比较platform_device_id中的name字段名称匹配直接比较platform_device和platform_driver的name字段典型匹配函数实现static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev to_platform_device(dev); struct platform_driver *pdrv to_platform_driver(drv); /* 1. 检查driver_override */ if (pdev-driver_override) return !strcmp(pdev-driver_override, drv-name); /* 2. 尝试设备树匹配 */ if (of_driver_match_device(dev, drv)) return 1; /* 3. 尝试ACPI匹配 */ if (acpi_driver_match_device(dev, drv)) return 1; /* 4. 尝试ID表匹配 */ if (pdrv-id_table) return platform_match_id(pdrv-id_table, pdev) ! NULL; /* 5. 回退到名称匹配 */ return (strcmp(pdev-name, drv-name) 0); }3. 平台设备驱动开发实战3.1 定义平台设备以LED设备为例我们需要先定义硬件资源/* GPIO寄存器定义 */ #define GPIO_BASE 0xfdd60000 #define GPIO_DR_H (GPIO_BASE 0x0004) #define GPIO_DDR_H (GPIO_BASE 0x000C) /* 资源定义 */ static struct resource pdev_led_resource[] { [0] DEFINE_RES_MEM(GPIO_DR_H, 4), // 数据寄存器 [1] DEFINE_RES_MEM(GPIO_DDR_H, 4), // 方向寄存器 }; /* 私有数据引脚偏移量 */ static unsigned int pdev_led_hwinfo[1] {7}; // GPIO0_C7 /* 平台设备定义 */ static struct platform_device pdev_led { .name pdev_led, .id 0, .num_resources ARRAY_SIZE(pdev_led_resource), .resource pdev_led_resource, .dev { .platform_data pdev_led_hwinfo, }, };3.2 实现平台驱动驱动需要实现probe/remove函数并与设备绑定static int pdrv_led_probe(struct platform_device *pdev) { /* 1. 获取平台资源 */ struct resource *mem_dr platform_get_resource(pdev, IORESOURCE_MEM, 0); struct resource *mem_ddr platform_get_resource(pdev, IORESOURCE_MEM, 1); /* 2. 映射寄存器 */ void __iomem *va_dr devm_ioremap(pdev-dev, mem_dr-start, resource_size(mem_dr)); void __iomem *va_ddr devm_ioremap(pdev-dev, mem_ddr-start, resource_size(mem_ddr)); /* 3. 获取私有数据 */ unsigned int *pin dev_get_platdata(pdev-dev); /* 4. 初始化GPIO方向 */ u32 val ioread32(va_ddr); val | (1 (*pin 16)) | (1 *pin); iowrite32(val, va_ddr); /* 5. 注册字符设备 */ // ...字符设备注册代码... return 0; } static struct platform_driver pdrv_led { .probe pdrv_led_probe, .remove pdrv_led_remove, .driver { .name pdev_led, }, }; module_platform_driver(pdrv_led);3.3 资源管理API详解平台驱动常用资源获取函数函数作用示例platform_get_resource获取指定类型资源platform_get_resource(pdev, IORESOURCE_MEM, 0)platform_get_irq获取中断号platform_get_irq(pdev, 0)dev_get_platdata获取私有数据dev_get_platdata(pdev-dev)devm_ioremap映射寄存器devm_ioremap(dev, res-start, resource_size(res))资源类型宏定义IORESOURCE_MEM内存映射资源IORESOURCE_IRQ中断资源IORESOURCE_IOIO端口资源4. 平台总线与设备树结合现代Linux驱动开发推荐使用设备树描述硬件平台总线完美支持设备树绑定。4.1 设备树节点示例led-controller { compatible vendor,pdev-led; reg 0xfdd60000 0x1000; led-pin 7; };4.2 驱动适配设备树static const struct of_device_id led_of_match[] { { .compatible vendor,pdev-led }, {}, }; static struct platform_driver pdrv_led { .driver { .name pdev-led, .of_match_table led_of_match, }, .probe pdrv_led_probe, .remove pdrv_led_remove, };设备树资源获取方法struct resource *res; void __iomem *base; res platform_get_resource(pdev, IORESOURCE_MEM, 0); base devm_ioremap_resource(pdev-dev, res); /* 获取自定义属性 */ int pin; of_property_read_u32(pdev-dev.of_node, led-pin, pin);5. 平台驱动开发实践技巧5.1 资源管理最佳实践使用devm_系列函数自动释放资源// 传统方式 res request_irq(...); // 退出时需要free_irq(res); // 推荐方式 res devm_request_irq(...); // 自动释放错误处理模板int probe(struct platform_device *pdev) { res1 devm_alloc(...); if (!res1) return -ENOMEM; res2 devm_get_resource(...); if (IS_ERR(res2)) return PTR_ERR(res2); // ... }5.2 调试技巧查看已注册设备ls /sys/bus/platform/devices/查看驱动匹配情况dmesg | grep platform手动绑定/解绑驱动echo driver_name /sys/bus/platform/drivers/.../bind echo device_name /sys/bus/platform/drivers/.../unbind5.3 常见问题排查probe函数未调用检查设备/驱动名称是否匹配确认设备树compatible属性正确检查模块依赖关系资源获取失败确认设备树reg属性设置正确检查资源索引号是否正确验证物理地址是否有效内存映射问题检查ioremap返回的指针确认资源大小足够验证寄存器访问权限6. 平台总线高级应用6.1 多设备支持通过id_table支持多个设备static const struct platform_device_id led_ids[] { {led-ver1, 0}, {led-ver2, 1}, {} }; static struct platform_driver led_driver { .id_table led_ids, // ... };6.2 平台数据与设备树结合struct led_platform_data { int pin; bool active_low; }; static int pdrv_led_probe(struct platform_device *pdev) { struct led_platform_data *pdata; if (pdev-dev.of_node) { // 从设备树解析 pdata devm_kzalloc(pdev-dev, sizeof(*pdata), GFP_KERNEL); of_property_read_u32(np, led-pin, pdata-pin); pdata-active_low of_property_read_bool(np, active-low); } else { // 从platform_data获取 pdata dev_get_platdata(pdev-dev); } // ... }6.3 平台总线与字符设备结合典型架构平台总线负责硬件抽象字符设备提供用户接口probe函数中完成字符设备注册static int led_probe(struct platform_device *pdev) { // 1. 获取硬件资源 // 2. 初始化硬件 // 3. 注册字符设备 alloc_chrdev_region(devno, 0, 1, led); cdev_init(led_cdev, led_fops); cdev_add(led_cdev, devno, 1); // ... }7. 性能优化建议延迟加载对非关键驱动使用module_platform_driver宏实现按需加载资源共享多个设备实例共享相同资源时使用devm函数管理热插拔支持实现完整的probe/remove函数逻辑电源管理实现pm_ops结构体支持系统休眠唤醒static const struct dev_pm_ops led_pm_ops { .suspend led_suspend, .resume led_resume, }; static struct platform_driver led_driver { .driver { .pm led_pm_ops, }, };通过平台总线机制Linux内核实现了硬件资源与驱动代码的有效分离大大提高了驱动的可移植性和可维护性。在实际项目中结合设备树使用平台总线可以构建更加灵活、易于维护的驱动架构。