Linux 驱动开发两种范式对比:platform driver 与 device tree 的优劣势分析与迁移策略

Linux 驱动开发两种范式对比:platform driver 与 device tree 的优劣势分析与迁移策略
Linux 驱动开发两种范式对比platform driver 与 device tree 的优劣势分析与迁移策略一、两种范式的历史演进Linux 内核驱动模型经历了从板级文件硬编码到设备树动态描述的演变。platform driver 模型在 Linux 2.6 时期引入解决了设备与驱动的分离问题device tree设备树自 ARM Linux 3.x 起成为主流将硬件描述从内核源码中彻底剥离。两种范式的本质区别在于硬件信息存放位置不同。platform driver 的硬件资源定义在 C 源码的platform_device结构体中device tree 则通过.dts文件以树形结构描述外设。这一差异看似微小却深刻影响了整个 BSP 开发流程。二、架构对比2.1 platform driver 的典型实现/** * platform driver 传统写法 - 硬件资源硬编码在 C 源码中 * 每新增一块板子需要复制粘贴板级文件维护成本随板卡数量线性增长 */ #include linux/platform_device.h #include linux/module.h #include linux/interrupt.h /* 硬件资源定义内存基址、中断号全部写死在代码中 */ static struct resource my_uart_resources[] { { .start 0x20200000, /* UART 寄存器基址 */ .end 0x202000FF, /* UART 寄存器结束地址 */ .flags IORESOURCE_MEM, .name uart_regs, }, { .start 32, /* 中断号硬编码 */ .end 32, .flags IORESOURCE_IRQ, .name uart_irq, }, }; /* platform device - 板级文件中定义 */ static struct platform_device my_board_uart { .name my_uart, /* 与驱动 .name 匹配 */ .id 0, .num_resources ARRAY_SIZE(my_uart_resources), .resource my_uart_resources, .dev { .platform_data NULL, }, }; /* 驱动侧 - 通过 platform_get_resource 获取硬件信息 */ static int my_uart_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(pdev-dev, [错误] 无法获取 MEM 资源\n); return -ENODEV; } base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(base)) { dev_err(pdev-dev, [错误] IO 映射失败: %ld\n, PTR_ERR(base)); return PTR_ERR(base); } /* 中断号也从资源表中提取 */ int irq platform_get_irq(pdev, 0); if (irq 0) { dev_err(pdev-dev, [错误] 无法获取 IRQ 资源: %d\n, irq); return irq; } return devm_request_irq(pdev-dev, irq, my_uart_handler, 0, my_uart, pdev); }2.2 device tree 的等效实现/** * device tree 驱动 - 硬件信息来自 dtb无需修改 C 源码 * 同一份驱动可适配不同板卡只需修改 .dts 文件 */ #include linux/of.h #include linux/of_device.h /* 兼容性列表 - 支持多个 SoC 变体 */ static const struct of_device_id my_uart_of_match[] { { .compatible vendor,my-uart-v1, }, { .compatible vendor,my-uart-v2, }, { /* 哨兵节点 */ }, }; MODULE_DEVICE_TABLE(of, my_uart_of_match); static int my_uart_probe_dt(struct platform_device *pdev) { struct device_node *np pdev-dev.of_node; void __iomem *base; int irq, ret; /* 从设备树节点自动获取 IRQ无需硬编码中断号 */ irq platform_get_irq(pdev, 0); if (irq 0) { dev_err(pdev-dev, [错误] 设备树中未定义中断: %d\n, irq); return irq; } ret devm_request_irq(pdev-dev, irq, my_uart_handler, IRQF_TRIGGER_RISING, my_uart, pdev); if (ret) { dev_err(pdev-dev, [错误] 中断注册失败: %d\n, ret); return ret; } /* 从设备树解析自定义属性 */ int baudrate; if (of_property_read_u32(np, default-baudrate, baudrate)) { dev_warn(pdev-dev, [警告] 未指定波特率使用默认值 115200\n); baudrate 115200; } dev_info(pdev-dev, UART 初始化完成IRQ%d波特率%d\n, irq, baudrate); return 0; }对应的.dts描述/* 设备树中的描述 - 独立于内核源码 */ uart1 { compatible vendor,my-uart-v1; reg 0x20200000 0x100; interrupts 32; default-baudrate 921600; /* 自定义属性 */ status okay; };三、优劣势量化对比维度Platform DriverDevice Tree差距说明板卡适配效率每板一份 C 文件每板一份 .dts文本DT 效率高 3-5 倍内核镜像通用性一变全变需重新编译内核不变换 dtb 即可DT 显著优势学习曲线直观纯 C 思维需学 dts 语法 bindingsPlatform 更友好编译时错误检测编译器能发现需额外 dtc 检查 运行时Platform 更安全上下游合作效率驱动/BSP 耦合驱动/BSP 解耦DT 大幅改善引脚复用配置代码中 pinctrlpinctrl 节点独立配置DT 更灵活运行时修改硬件配置需重新编译加载 dtbo 叠加层即可DT 支持动态加载四、迁移策略与实践建议关键迁移原则渐进式迁移先迁移新板卡老板卡保持 platform driver两套机制可以共存。pin control 先行引脚复用配置是迁移中陷阱最多的环节优先验证。双轨并行验证在 Kconfig 中保留#ifdef CONFIG_OF条件编译确保回退路径。bindings 先行如果要提交 upstream先完成 YAML schema bindings 文档再写 .dts。五、总结Device tree 已是 ARM Linux 的事实标准新项目不要再使用 platform device 硬编码方式。但迁移老项目需要理性评估如果只有 1-2 块定制板卡且无上游化计划迁移收益有限。当板卡数超过 3 块或需要与 SoC 厂商协作时device tree 带来的 BSP 解耦收益是决定性的。驱动程序本身建议保留of_match_table 传统platform_driver双兼容模式一次重构兼容新旧两种硬件描述方式。