1. 设备树基础:为什么嵌入式Linux离不开它
记得我第一次接触嵌入式Linux驱动开发时,最头疼的就是每次更换硬件平台都要重新修改内核代码。那时候驱动代码里充满了各种平台相关的配置信息,换个芯片就要重新编译内核,简直让人崩溃。直到设备树(Device Tree)的出现,才彻底改变了这种局面。
设备树本质上是一个描述硬件信息的数据结构,它用文本文件(.dts)的形式描述硬件配置,编译后生成二进制文件(.dtb)传递给内核。这样就把硬件描述和内核代码分离了,同一个内核镜像可以支持多种硬件平台。
举个例子,假设你正在开发一个基于i.MX6ULL的工控板,需要描述一个GPIO控制的LED设备。在没有设备树的时代,你不得不在内核的board file里这样写:
static struct gpio_led imx6ull_leds[] = {
{
.name = "system-led",
.gpio = IMX_GPIO_NR(1, 3),
.default_trigger = "heartbeat",
},
};
现在用设备树,同样的硬件可以这样描述:
leds {
compatible = "gpio-leds";
system-led {
label = "system-led";
gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
这种分离带来的好处是显而易见的:硬件变更不需要重新编译内核,只需要更新设备树文件。我在实际项目中就遇到过这样的情况——客户临时要求更换LCD屏幕,我们只用了半小时修改设备树就完成了适配,如果要改内核代码至少需要一天时间。
2. 设备树语法详解:从节点到属性的完整解析
设备树的基本组成单元是节点(node)和属性(property)。节点就像文件系统中的目录,用于组织和管理硬件信息;属性则是具体的键值对,描述硬件的详细参数。
2.1 节点命名规范与路径
每个节点都有唯一的路径标识,类似于文件路径。根节点用"/"表示,子节点通过路径分隔。比如一个I2C设备可能位于/i2c@021a0000/rtc@68,这样的路径明确表示了硬件在系统中的位置。
节点命名遵循<name>[@<unit-address>]的格式,其中unit-address通常是设备的基地址。比如serial@101f0000表示一个串口设备,基地址是0x101f0000。
2.2 属性值的多种数据类型
设备树支持丰富的数据类型,这是它能够准确描述硬件的基础:
-
字符串:用于描述兼容性、标签等文本信息
compatible = "fsl,imx6ull-i2c", "fsl,imx21-i2c"; -
32位无符号整数数组:用于描述地址、中断号等数值信息
reg = <0x021a0000 0x4000>; interrupts = <0 68 IRQ_TYPE_LEVEL_HIGH>; -
二进制数据:常用于描述MAC地址、校准数据等
local-mac-address = [00 04 9f 00 27 50]; -
混合格式:结合多种数据类型的复杂描述
clock-names = "ipg", "per"; clocks = <&clks IMX6UL_CLK_I2C1>, <&clks IMX6UL_CLK_I2C1>;
在实际开发中,我经常用混合格式来描述复杂的时钟配置。比如在一个音频项目中,需要配置多个时钟域,设备树的这种灵活性大大简化了工作。
2.3 节点引用与别名机制
设备树提供了灵活的引用机制,允许一个节点引用另一个节点的内容。这是通过phandle(指针句柄)和标签引用实现的。
// 定义节点并添加标签
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = &l


533

被折叠的 条评论
为什么被折叠?



