设备树基础:DTS语法、节点与属性
设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构的数据结构,它替代了传统板级文件(board-*.c)中硬编码的硬件信息。设备树源文件(.dts)通过编译器生成设备树二进制文件(.dtb),由Bootloader传递给内核解析。理解设备树语法是编写Platform驱动的前提。
核心概念:设备树将硬件描述与内核驱动分离,驱动通过
compatible属性匹配设备节点,probe函数在匹配成功后自动执行。
2.1 设备树语法基础
设备树源文件采用树形结构,以节点(node)和属性(property)描述硬件。每个节点代表一个设备或总线,属性描述设备的特性。
2.2 节点与属性详解
设备树节点由节点名和单元地址组成,格式为 node-name@unit-address。属性是键值对,用于描述设备的硬件特征。
| 语法元素 | 格式 | 说明 | 示例 |
|---|---|---|---|
| 节点 | node-name@unit-address |
节点名标识设备类型,单元地址通常为寄存器基址 | uart@1f000 |
| 标签 | label: node-name |
为节点创建引用别名,便于其他节点引用 | uart0: serial@1f000 |
| 属性 | property-name = value; |
描述设备特性,值可为字符串、数字、数组等 | status = "okay"; |
| 包含文件 | #include "file.dtsi" |
引入公共设备树头文件,复用硬件定义 | #include "imx6ull.dtsi" |
实战提示:节点名应尽量使用标准名称(如
uart、i2c、spi),便于内核驱动匹配。单元地址必须与 reg 属性的第一个地址一致。
2.3 compatible属性——驱动匹配的关键
compatible 属性是设备树与驱动之间建立联系的桥梁。驱动通过 of_match_table 中定义的 compatible 字符串与设备树节点匹配。
// 设备树节点定义
i2c@1c000 {
compatible = "vendor,i2c-controller", "simple-bus";
reg = <0x01c000 0x1000>;
interrupts = <0 32 4>;
status = "okay";
};
// 驱动匹配表
static const struct of_device_id my_i2c_dt_ids[] = {
{ .compatible = "vendor,i2c-controller", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_i2c_dt_ids);
compatible 属性支持多个字符串,内核按优先级依次匹配。第一个字符串通常为精确型号,后续为兼容型号或总线类型。
| 匹配优先级 | compatible字符串 | 匹配场景 |
|---|---|---|
| 1(最高) | "vendor,i2c-controller" |
精确匹配特定厂商的I2C控制器 |
| 2 | "simple-bus" |
回退匹配,适用于简单总线设备 |
| 3 | "vendor,generic-i2c" |
通用兼容匹配,覆盖同系列设备 |
常见错误:compatible 字符串必须与驱动中
of_match_table 完全一致(包括大小写和标点)。例如 "vendor,i2c" 与 "vendor,i2c-controller" 不匹配。
2.4 reg属性——地址与内存映射
reg 属性描述设备的寄存器地址空间,由一组或多组 (address, length) 对组成。内核通过 platform_get_resource() 函数获取这些资源。
// 设备树定义:多组reg地址
dma-controller@1e000 {
compatible = "vendor,dma-controller";
reg = <0x01e000 0x1000>, // 控制寄存器
<0x01f000 0x0200>; // 描述符表
interrupts = <0 33 4>;
};
// 驱动中获取reg资源
static int my_dma_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *ctrl_base, *desc_base;
// 获取第一个reg区域(索引0)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ctrl_base = devm_ioremap_resource(&pdev->dev, res);
// 获取第二个reg区域(索引1)
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
desc_base = devm_ioremap_resource(&pdev->dev, res);
return 0;
}
2.5 interrupts属性——中断资源描述
interrupts 属性描述设备的中断请求信息,包括中断控制器、中断号、触发类型等。内核通过 platform_get_irq() 函数获取中断号。
| 中断描述格式 | 字段含义 | 示例 |
|---|---|---|
<中断号 触发类型> |
简单中断控制器,直接指定中断号和触发方式 | interrupts = <31 4>; |
<中断控制器 中断号 触发类型> |
多中断控制器,指定父控制器、中断号和触发方式 | interrupts = <0 31 4>; |
<中断号 触发类型 优先级> |
扩展格式,增加优先级字段 | interrupts = <31 4 0>; |
触发类型宏定义:
| 宏名称 | 值 | 触发方式 |
|---|---|---|
IRQF_TRIGGER_NONE |
0x00000000 | 无触发(默认) |
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 |
// 设备树中断定义
ethernet@1c000 {
compatible = "vendor,eth-controller";
reg = <0x01c000 0x2000>;
interrupts = <0 35 4>; // 中断控制器0,中断号35,高电平触发
interrupt-parent = <&gic>; // 指定中断父控制器
};
// 驱动中获取中断号
static int my_eth_probe(struct platform_device *pdev)
{
int irq_num;
irq_num = platform_get_irq(pdev, 0);
if (irq_num < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return irq_num;
}
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq_num, my_eth_isr,
IRQF_TRIGGER_HIGH, "my_eth", dev);
return 0;
}
关键总结:设备树通过
compatible 属性实现驱动与设备的匹配,reg 属性提供地址资源,interrupts 属性提供中断资源。驱动在 probe 函数中通过 platform_get_resource() 和 platform_get_irq() 获取这些资源,完成硬件初始化。
2.6 完整设备树节点示例
以下是一个综合示例,展示如何在一个设备节点中组合使用上述属性:
// 完整设备树节点
spi@1d000 {
compatible = "vendor,spi-controller", "vendor,spi-v2";
reg = <0x01d000 0x1000>;
interrupts = <0 36 4>;
interrupt-parent = <&gic>;
status = "okay";
clock-frequency = <50000000>;
#address-cells = <1>;
#size-cells = <0>;
// 子设备(SPI从设备)
spi-device@0 {
compatible = "vendor,temperature-sensor";
reg = <0>; // SPI片选号
spi-max-frequency = <10000000>;
};
};
该节点展示了设备树的多层嵌套结构:SPI控制器节点包含子节点(SPI从设备),每个节点都有自己的 compatible 和 reg 属性。驱动通过解析这些属性,能够完整获取设备的硬件拓扑信息。