Linux平台总线机制与驱动开发实践 1. Linux平台总线机制解析在嵌入式Linux驱动开发中我们经常会遇到一些简单的片上外设比如GPIO控制的LED灯、按键等。这些设备不像I2C、SPI等标准总线设备有明确的物理总线规范Linux内核为这类设备设计了一套特殊的虚拟总线机制——platform总线。平台总线platform bus是Linux内核为那些没有物理总线的简单设备设计的虚拟总线它实现了设备与驱动的分离使得驱动代码不再直接包含硬件信息。1.1 平台总线的设计初衷传统字符设备驱动开发方式存在一个明显缺陷硬件信息与驱动代码高度耦合。在早期的LED驱动示例中我们经常看到这样的代码#define GPIO_BASE 0xFDD60000 #define GPIO_DR_H (GPIO_BASE 0x0004)这种写法直接将硬件寄存器地址硬编码在驱动中带来三个主要问题硬件变更需要修改驱动源码同一驱动难以适配不同硬件平台代码可维护性差平台总线机制通过以下方式解决这些问题将硬件信息抽象为platform_device驱动逻辑封装在platform_driver总线负责两者的匹配和管理1.2 平台总线核心组件平台总线架构包含三个关键组成部分组件类型数据结构功能描述平台总线platform_bus_type虚拟总线管理设备和驱动的匹配平台设备platform_device描述硬件资源寄存器、中断等平台驱动platform_driver实现设备操作逻辑和功能这种设计使得硬件描述与驱动实现分离符合Linux设备模型的设计哲学。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; // 匹配结果 };关键成员解析name总线匹配时使用的设备标识resource描述硬件资源寄存器、中断等dev.platform_data设备私有数据指针2.2 硬件资源描述硬件资源通过struct resource描述典型定义如下struct resource { resource_size_t start; // 起始地址 resource_size_t end; // 结束地址 const char *name; unsigned long flags; // 资源类型标识 };常用资源类型标志IORESOURCE_MEM内存映射资源最常用IORESOURCE_IRQ中断资源IORESOURCE_IOI/O端口资源资源定义示例static struct resource pdev_led_resource[] { [0] DEFINE_RES_MEM(GPIO_DR_H, 4), // 数据寄存器 [1] DEFINE_RES_MEM(GPIO_DDR_H, 4), // 方向寄存器 };2.3 设备私有数据对于无法用标准resource描述的硬件信息可以通过platform_data传递unsigned int pdev_led_hwinfo[1] {7}; // GPIO引脚偏移量 static struct platform_device pdev_led { .dev { .platform_data pdev_led_hwinfo, } };实际开发中新内核推荐使用设备树替代platform_data传递硬件信息但理解这一机制对维护旧代码很有帮助。3. 平台驱动实现细节3.1 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; };关键成员说明probe设备匹配成功后调用的初始化函数remove设备移除时调用的清理函数id_table支持的设备ID表driver.name驱动名称匹配用3.2 驱动匹配机制平台总线提供四种匹配方式按优先级排序设备树匹配通过of_match_table和compatible属性ACPI匹配用于x86体系电源管理id_table匹配平台设备ID表匹配名称匹配直接比较platform_device和platform_driver的name字段id_table匹配示例static struct platform_device_id pdev_led_ids[] { {.name pdev_led}, {} };3.3 资源获取接口驱动中获取设备资源的常用API函数接口功能描述platform_get_resource()获取指定类型和索引的资源platform_get_irq()获取中断资源dev_get_platdata()获取platform_data数据典型资源获取代码// 获取内存资源 mem_dr platform_get_resource(pdev, IORESOURCE_MEM, 0); led_cdev-va_dr devm_ioremap(pdev-dev, mem_dr-start, resource_size(mem_dr)); // 获取私有数据 pdev_led_hwinfo dev_get_platdata(pdev-dev);4. 平台总线匹配流程4.1 总线匹配函数分析platform_match()是平台总线的核心匹配函数其简化逻辑如下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_table匹配 */ if (pdrv-id_table) return platform_match_id(pdrv-id_table, pdev) ! NULL; /* 5. 名称匹配 */ return (strcmp(pdev-name, drv-name) 0); }4.2 匹配过程示例以LED驱动为例的匹配流程加载pdev_led.ko注册platform_device加载pdrv_led.ko注册platform_driver总线检测到新驱动开始匹配检查driver_override通常NULL尝试设备树匹配无设备树时跳过检查id_table存在且匹配匹配成功调用pdrv_led_probe()5. 平台设备驱动实战5.1 LED设备实现平台设备模块主要完成硬件描述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}; 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, }, };5.2 LED驱动实现平台驱动模块实现设备控制逻辑static int pdrv_led_probe(struct platform_device *pdev) { /* 1. 获取硬件资源 */ mem_dr platform_get_resource(pdev, IORESOURCE_MEM, 0); led_cdev-va_dr devm_ioremap(pdev-dev, mem_dr-start, resource_size(mem_dr)); /* 2. 初始化硬件 */ val ioread32(led_cdev-va_ddr); val | (0x1 (led_cdev-led_pin 16)); iowrite32(val, led_cdev-va_ddr); /* 3. 注册字符设备 */ alloc_chrdev_region(devno, 0, DEV_CNT, DEV_NAME); cdev_init(led_cdev-dev, pdrv_led_fops); cdev_add(led_cdev-dev, devno, DEV_CNT); /* 4. 创建设备节点 */ device_create(class, NULL, devno, NULL, DEV_NAME); return 0; }5.3 实验注意事项在实际操作中需要注意资源冲突确保注册的硬件资源未被其他驱动占用加载顺序先加载设备模块(pdev_led.ko)再加载驱动模块(pdrv_led.ko)权限问题设备节点需要正确权限才能访问系统LED驱动可能需要先关闭系统自带的LED驱动调试技巧# 查看已注册的平台设备 ls /sys/bus/platform/devices/ # 查看设备资源信息 cat /sys/bus/platform/devices/pdev_led.0/resource6. 平台总线的演进与设备树6.1 传统方式的局限性虽然平台总线实现了硬件与驱动的分离但传统方式仍有不足硬件信息仍需编码在C文件中不同硬件平台需要重新编译内核不支持动态配置6.2 设备树的优势设备树Device Tree机制的引入解决了这些问题硬件描述与内核代码完全分离单个内核镜像支持多种硬件支持动态设备配置设备树中的平台设备示例led-controller { compatible pdev-led; reg 0xFDD60000 0x10; pin-offset 7; };6.3 新旧方案对比特性传统platform方式设备树方式硬件描述位置C代码中.dts文件可维护性较差优秀跨平台支持需要重新编译单个镜像支持多硬件动态配置不支持支持学习曲线简单较陡峭在实际项目中新开发推荐使用设备树方式但理解传统platform机制对维护旧代码和深入理解Linux驱动模型非常有帮助。7. 平台总线的高级应用7.1 多设备支持通过id_table可以实现一个驱动支持多个设备static struct platform_device_id pdev_led_ids[] { {.name pdev_led_r, .driver_data LED_RED}, {.name pdev_led_g, .driver_data LED_GREEN}, {} };在probe函数中通过driver_data区分设备类型static int pdrv_led_probe(struct platform_device *pdev) { enum led_type type (enum led_type)pdev-id_entry-driver_data; switch(type) { case LED_RED: /* 红色LED初始化 */ break; case LED_GREEN: /* 绿色LED初始化 */ break; } }7.2 资源管理API内核提供了一系列devm_开头的资源管理函数可以自动释放资源函数功能传统对应函数devm_kzalloc()内存分配kzalloc()devm_ioremap()IO内存映射ioremap()devm_request_irq()中断注册request_irq()使用示例led_cdev devm_kzalloc(pdev-dev, sizeof(*led_cdev), GFP_KERNEL); led_cdev-va_dr devm_ioremap(pdev-dev, mem_dr-start, resource_size(mem_dr));这些函数可以防止驱动卸载时资源泄漏大大提高了代码的健壮性。7.3 平台数据与设备树的结合在过渡期可以采用混合模式static int pdrv_led_probe(struct platform_device *pdev) { if (pdev-dev.of_node) { /* 设备树方式获取硬件信息 */ of_property_read_u32(pdev-dev.of_node, pin-offset, pin); } else { /* 传统platform_data方式 */ pin *(unsigned int *)dev_get_platdata(pdev-dev); } }这种写法可以兼容新旧两种硬件描述方式在实际项目中很常见。