嵌入式Linux设备树(Device Tree)编写:从节点到驱动绑定
扫描二维码
随时随地手机看文章
在嵌入式Linux开发中,设备树(Device Tree)已成为描述硬件的标准。它让内核与硬件解耦,实现“一份内核,适配万千板卡”。本文将跳过繁杂的理论,直击设备树节点编写与内核驱动匹配的核心流程。
一、设备树基础:从“硬件图纸”到DTS
设备树的核心结构是节点(Node)和属性(Property)。一个节点代表一个硬件设备。
1. 标准节点结构
// 在 arch/arm/boot/dts/my_board.dts
/ {
model = "My Custom Board";
compatible = "myvendor,my-board-v1";
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 起始地址 + 长度 (512MB)
};
};
关键点:根节点/下的model和compatible用于内核识别板卡;reg属性是地址描述的核心。
二、添加自定义外设节点(I2C设备为例)
假设板上有一个挂载在I2C1控制器下的EEPROM。
1. 查找控制器节点
首先,在SoC的DTSI文件(如soc.dtsi)中找到I2C控制器节点,获取其#address-cells和#size-cells。
2. 编写设备节点
// 在 my_board.dts 中
&i2c1 { // 引用已有的i2c1控制器节点
status = "okay"; // 确保控制器使能
clock-frequency = <100000>; // 100kHz
eeprom@50 {
compatible = "atmel,24c08"; // 关键:用于匹配驱动
reg = <0x50>; // I2C从设备地址
pagesize = <16>; // 设备特有属性
read-only; // 布尔属性(无值)
};
};
注意:&i2c1表示追加或修改原有节点;reg的格式和长度必须与父节点的#address-cells匹配。
三、驱动与设备树的“联姻”:Of Match Table
设备树节点如何找到对应的驱动?答案是compatible字符串。
1. 内核驱动中的匹配表
在驱动代码中,定义一个of_device_id结构体数组。
// drivers/misc/my_eeprom.c
static const struct of_device_id eeprom_of_match[] = {
{ .compatible = "atmel,24c08" }, // 必须与DTS中的字符串完全一致
{ .compatible = "atmel,24c256" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, eeprom_of_match);
static struct platform_driver eeprom_driver = {
.probe = eeprom_probe,
.remove = eeprom_remove,
.driver = {
.name = "my_eeprom",
.of_match_table = eeprom_of_match, // 绑定匹配表
},
};
2. 驱动获取设备树属性
当compatible匹配成功后,内核会调用驱动的probe函数,此时可以通过of_系列API读取DTS属性。
static int eeprom_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
u32 reg_addr;
int ret;
// 获取reg属性
ret = of_property_read_u32(np, "reg", ®_addr);
if (ret) {
dev_err(&pdev->dev, "Failed to get reg property\n");
return ret;
}
// 判断布尔属性是否存在
if (of_property_read_bool(np, "read-only")) {
dev_info(&pdev->dev, "Device is read-only\n");
}
// 获取pagesize
of_property_read_u32(np, "pagesize", &priv->page_size);
// ... 初始化硬件 ...
return 0;
}
四、Pinctrl与GPIO:复杂硬件的绑定
对于GPIO或引脚复用,设备树需要与pinctrl子系统配合。
// 定义引脚配置
&iomuxc {
pinctrl_eeprom: eepromgrp {
fsl,pins = <
MX6QDL_PAD_EIM_D17__GPIO3_IO17 0x80000000
>;
};
};
// 在设备节点中引用
&i2c1 {
eeprom@50 {
compatible = "atmel,24c08";
reg = <0x50>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_eeprom>; // 绑定引脚配置
wp-gpios = <&gpio3 17 GPIO_ACTIVE_LOW>; // 写保护GPIO
};
};
驱动中通过gpiod_get()获取wp-gpios,无需关心具体引脚号。
五、调试与验证技巧
1. 编译与反编译
# 编译DTB
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- my_board.dtb
# 反编译DTB查看最终节点(非常有用!)
dtc -I dtb -O dts my_board.dtb > decompiled.dts
很多时候DTS写对了,但被DTSI覆盖了,反编译能看到最终生效的配置。
2. 内核启动后验证
# 查看设备树节点
ls /proc/device-tree/
# 查看特定属性
hexdump -C /proc/device-tree/soc/i2c@21a0000/eeprom@50/compatible
# 查看GPIO分配
cat /sys/kernel/debug/gpio
六、结语
编写设备树的关键在于“描述准确”与“匹配无误”。遵循三步法:定义节点与属性 → 驱动中实现of_match_table → 使用of_property_read_* API读取数据。掌握设备树,你就掌握了嵌入式Linux硬件适配的钥匙。





