1. 课程导论:什么是 Platform 驱动?为什么需要设备树?课程目标与学习路径
1.1 从“字符设备”到“Platform 设备”的演进
在嵌入式 Linux 开发中,驱动程序是连接硬件与操作系统的桥梁。初学者往往从 字符设备驱动 入手,通过 register_chrdev 注册设备号、实现 file_operations 来操作硬件。然而,这种模式在 SoC(片上系统)时代遇到了瓶颈:
- 硬件不挂在传统总线上:SoC 内部集成的 UART、I2C、SPI、DMA、GPIO 等控制器,并不像 PCI 或 USB 设备那样具有可枚举的总线协议。
- 资源描述硬编码:驱动中直接写死中断号、内存地址、时钟频率,导致代码与特定板级硬件强耦合,更换硬件平台时必须修改驱动源码。
- 无法动态匹配:系统启动时,内核无法自动识别这些“片上外设”,需要手动注册设备信息。
为了解决上述问题,Linux 内核引入了 Platform 驱动模型(也称为“平台驱动”或“伪总线驱动”)。它将 SoC 内部集成的外设抽象为 Platform 设备,并通过一条虚拟的 Platform 总线 实现设备与驱动的自动匹配。
1.2 为什么需要设备树?
在 Linux 早期(2.6 内核时代),Platform 设备的描述是通过 板级文件(board file) 中的 C 语言结构体完成的。例如在 arch/arm/mach-xxx 目录下,每个开发板都有一个 board-xxx.c 文件,里面充斥着 platform_device 的静态定义:
static struct resource uart_resources[] = {
{
.start = 0x10000000,
.end = 0x10000FFF,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_UART0,
.end = IRQ_UART0,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device uart_device = {
.name = "my-uart",
.id = 0,
.num_resources = ARRAY_SIZE(uart_resources),
.resource = uart_resources,
};
这种方式的弊端随着 ARM 生态的爆发式增长而暴露:
| 问题 | 描述 | 影响 |
|---|---|---|
| 代码膨胀 | 每个开发板都需要独立的 board-xxx.c 文件 | arch/arm 目录下包含数百个板级文件,内核体积臃肿 |
| 硬件变更困难 | 修改硬件配置(如更换 UART 引脚)需要重新编译内核 | 开发效率低,无法实现“一次编译,多处运行” |
| 碎片化严重 | 不同厂商的板级文件风格各异,难以统一维护 | 社区维护成本急剧上升,Linus Torvalds 曾多次批评 |
| 无法支持多平台 | 一个内核镜像只能支持一种开发板 | 与通用 Linux 发行版理念背道而驰 |
设备树(Device Tree) 是一种描述硬件拓扑结构的数据结构,它以 .dts(Device Tree Source)文件的形式存在,通过 dtc 编译器生成 .dtb(Device Tree Blob)二进制文件,在系统启动时由 Bootloader 传递给内核。设备树的核心价值在于:
- 硬件描述与内核代码分离:驱动不再包含硬件资源信息,只负责操作逻辑。
- 支持多平台:同一份内核镜像,搭配不同的
.dtb文件即可适配不同硬件。 - 标准化接口:设备树提供统一的
compatible属性匹配机制,驱动通过of_match_table与设备树节点建立联系。
1.3 Platform 驱动与设备树的匹配机制
当内核启动并解析设备树后,会为每个设备树节点创建一个 Platform 设备。随后,Platform 总线开始遍历已注册的 Platform 驱动,通过 匹配机制 将驱动与设备绑定。匹配的核心依据是设备树节点的 compatible 属性与驱动中 of_match_table 的对应关系。
匹配流程可以概括为以下步骤:
- 设备注册:内核解析 DTB,为每个设备树节点创建
platform_device结构体。 - 驱动注册:驱动模块加载时,调用
platform_driver_register注册platform_driver。 - 总线匹配:Platform 总线调用
platform_match函数,依次尝试多种匹配方式。 - 驱动绑定:匹配成功后,调用驱动的
probe函数,驱动开始初始化硬件。
| 匹配方式 | 匹配依据 | 优先级 | 典型场景 |
|---|---|---|---|
| OF 匹配 | 设备树 compatible 属性 vs 驱动 of_match_table |
最高 | 现代 ARM/ RISC-V 平台,设备树是主流 |
| ACPI 匹配 | ACPI 表 HID/CID vs 驱动 acpi_match_table |
高 | x86 平台,如 Intel/AMD SoC |
| 名称匹配 | 设备 name 字段 vs 驱动 id_table 中的名称 |
中 | 传统板级文件或简单设备 |
| ID 匹配 | 设备 name 字段 vs 驱动 driver.name |
低 | 兼容旧版驱动,不推荐在新代码中使用 |
of_match_table 中声明支持的 compatible 字符串,设备树开发者确保节点属性正确,两者即可自动匹配。
1.4 课程目标与学习路径
本课程旨在帮助嵌入式开发者系统掌握 Platform 驱动与设备树匹配机制,从原理到实战,构建完整的知识体系。课程分为以下四个阶段:
| 阶段 | 章节范围 | 核心目标 | 实战产出 |
|---|---|---|---|
| 基础篇 | 第 1-6 章 | 理解 Platform 驱动模型、设备树语法、匹配原理 | 手写第一个 Platform 驱动 + 设备树节点 |
| 进阶篇 | 第 7-14 章 | 掌握设备树资源获取 API、中断、DMA、时钟等复杂资源 | 实现一个完整的 UART/GPIO 控制器驱动 |
| 实战篇 | 第 15-24 章 | 多设备树文件、overlay、pinmux、电源管理等高级主题 | 在 QEMU/真实开发板上验证完整驱动栈 |
| 调试与优化篇 | 第 25-30 章 | 动态调试、trace、性能分析、兼容性处理 | 解决实际项目中的匹配失败、资源冲突问题 |
/sys/bus/platform/devices/ 和 /sys/bus/platform/drivers/ 下的文件,这是最直观的调试入口。
通过本课程的学习,你将能够:
- 独立编写符合规范的 Platform 驱动,并正确编写对应的设备树节点。
- 深入理解
platform_match的四种匹配机制及其优先级。 - 熟练使用
of_系列 API 从设备树中获取内存、中断、GPIO、时钟等资源。 - 掌握设备树 overlay 动态加载技术,实现硬件功能的运行时切换。
- 具备分析和解决实际项目中驱动匹配失败、资源冲突等问题的能力。
file_operations 和 module_init 等概念还不熟悉,建议先阅读 Linux 字符设备驱动的入门教程。