1. BSP与设备树概述:BSP概念、设备树起源、设备树在Linux内核中的作用
1.1 什么是BSP?说白了就是板级支持包
做嵌入式开发这么多年,我经常被问到:“BSP到底是什么?”
其实很简单。BSP就是Board Support Package,板级支持包。它是一层软件,夹在硬件和操作系统之间。你想想看,一块开发板上有CPU、内存、串口、网卡、GPIO……这些东西怎么让Linux内核认识?靠的就是BSP。
我个人习惯把BSP拆成三块来看:
- 引导加载程序:比如U-Boot,负责把内核从Flash搬到内存里
- 内核驱动:让Linux内核能操作板子上的各种外设
- 板级配置:告诉内核“我这块板子用了哪个GPIO做LED,哪个I2C接了触摸屏”
我在项目中遇到过最头疼的事,就是换了一块Flash芯片,结果BSP里要改十几个地方。为什么?因为早期Linux内核把板级信息硬编码在C代码里了。改一个引脚,得翻遍arch/arm/mach-xxx目录下的文件。嗯,这就要说到设备树的由来了。
核心观点:BSP的本质是“硬件描述 + 驱动代码”的集合。设备树的出现,就是为了把“硬件描述”这部分从代码中剥离出来。
1.2 设备树的起源:为什么要有设备树?
设备树(Device Tree)的诞生,说白了是被逼出来的。
在Linux 2.6时代,ARM架构的板级信息全写在C文件里。每个厂商、每块板子都要在arch/arm/mach-xxx/下加一堆代码。我记得当时光是维护这些板级文件,内核社区就快疯了。为什么?因为代码量太大,而且重复性极高。
举个例子:
// 老式板级文件写法
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,
};
你看,这还只是一个UART。一块板子上有几十个外设,每个都要这么写。而且换一块板子,这些代码基本要重写。
2006年左右,PowerPC架构率先引入了设备树。ARM架构直到2011年才开始大规模采用。我当时参与的一个项目,正好赶上了这个过渡期。一边要维护老式的板级文件,一边要学习设备树语法。说实话,刚开始觉得设备树挺麻烦的,但用顺手之后,真香。
我的建议:如果你现在还在用老式板级文件做新项目,赶紧迁移到设备树。这不是技术偏好问题,而是生态问题。Linux内核社区已经不再接受新的板级文件补丁了。
1.3 设备树在Linux内核中的作用
设备树到底在Linux内核里扮演什么角色?我用一句话概括:它是硬件信息的“数据库”。
内核启动时,会解析设备树文件(.dtb),然后根据里面的描述去初始化硬件。设备树告诉内核:
- CPU是什么型号,有几个核
- 内存的起始地址和大小
- 各个外设挂在哪个总线上,地址是多少
- 中断号、时钟频率、DMA通道等等
来看一个实际的设备树节点:
uart0: serial@10000000 {
compatible = "my_company,my_uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clocks = <&uart_clk>;
status = "okay";
};
这个节点描述了一个UART控制器。compatible字段告诉内核用哪个驱动,reg字段告诉内核寄存器地址范围,interrupts字段告诉内核中断号。内核的驱动框架会根据这些信息自动匹配和初始化。
我曾经踩过一个坑:设备树里reg字段的地址写错了,结果内核一访问UART寄存器就死机。查了两天才发现是地址偏移了一位。嗯,设备树这东西,写的时候一定要仔细核对硬件手册。
1.4 设备树的核心文件结构
一个完整的设备树项目,通常包含这几类文件:
| 文件类型 | 扩展名 | 作用 |
|---|---|---|
| 设备树源文件 | .dts | 描述具体板子的硬件信息 |
| 设备树头文件 | .dtsi | SoC级别的通用描述,可被多个.dts包含 |
| 设备树二进制文件 | .dtb | 编译后的设备树,内核直接使用 |
| 设备树补丁文件 | .dtbo | 用于运行时覆盖设备树节点 |
我个人习惯把.dtsi文件当作“SoC级描述”,把.dts文件当作“板级描述”。比如一个芯片厂商提供imx6ull.dtsi,里面定义了CPU、内存控制器、各种外设的框架。然后具体的板子,比如“野火i.MX6ULL开发板”,再写一个imx6ull-fire.dts,去填充具体的引脚复用、设备地址等信息。
注意:设备树不是万能的。它只描述“硬件有什么”,不描述“硬件怎么用”。驱动的逻辑代码还是要写在C文件里。别指望设备树能帮你写驱动。
1.5 设备树在启动流程中的位置
为了让你更直观地理解,我画了一张设备树在Linux启动流程中的位置图:
从这张图你可以看到,设备树贯穿了整个启动流程。Bootloader把.dtb文件加载到内存,内核解压后解析设备树,然后根据设备树创建platform_device,最后驱动通过compatible字段匹配到对应的设备。
我刚开始学设备树时,总以为它只是“另一种配置文件”。后来才明白,它其实是内核驱动模型的核心一环。没有设备树,platform总线驱动模型就玩不转。
避坑指南:我曾经在调试一个I2C设备时,发现驱动死活不probe。查了半天,原来是设备树里status字段写成了"disable"而不是"disabled"。设备树语法检查很宽松,但写错了它也不会报错,只是默默忽略。所以,写完设备树一定要用dtc工具检查语法。
1.6 本章小结
这一章我们聊了三个核心概念:
- BSP:板级支持包,是硬件和操作系统之间的桥梁
- 设备树起源:为了解决ARM架构板级代码泛滥的问题
- 设备树的作用:作为硬件信息的数据库,驱动内核的设备模型
说白了,设备树就是让硬件描述和驱动代码分家。你改硬件配置,不用改C代码,改改.dts文件就行。这个思路,在大型项目中特别有用。我参与过的项目,一块SoC用在十几种板子上,每个板子就一个.dts文件,维护起来轻松多了。
下一章,我们会深入设备树的语法细节。到时候我会拿实际项目中的设备树文件来拆解,看看每个节点、每个属性到底是什么意思。