1. 设备树基础从硬件描述到内核识别的桥梁第一次接触设备树时我完全被那些嵌套的节点和奇怪的属性搞懵了。直到在某个嵌入式项目中被硬件兼容性问题折磨得死去活来才真正理解设备树的价值。简单来说设备树Device Tree就是描述硬件配置的电路板蓝图它用文本文件.dts定义CPU、内存、外设等硬件信息编译后生成二进制文件.dtb由Bootloader传递给内核。传统的内核开发方式需要为每块开发板编写大量板级代码比如arch/arm/mach-xxx目录下的硬件描述。我在旧版内核移植时就遇到过这样的麻烦——每次硬件改动都要重新编译内核。而设备树机制将硬件描述从内核中剥离形成了独立的.dts文件。这种变化带来的最大好处是同一套内核镜像可以适配不同硬件只需更换dtb文件即可。设备树的核心组成包括DTS人类可读的文本描述文件位于arch/arm/boot/dts目录DTSI类似C语言的头文件包含SOC的通用定义DTB编译后的二进制文件由Bootloader加载到内存举个例子下面是描述两个UART设备的典型代码片段/ { compatible vendor,board; serial0: serial101f0000 { compatible ns16550a; reg 0x101f0000 0x1000; interrupts 1 0; }; serial1: serial101f2000 { status disabled; compatible ns16550a; reg 0x101f2000 0x1000; interrupts 2 0; }; }这个例子展示了设备树的几个关键特征节点路径如/serial101f0000、兼容性标识compatible、寄存器地址reg和中断号interrupts。其中status属性特别实用可以通过设置为disabled临时禁用某个设备。2. 设备树语法深度解析2.1 节点与属性的艺术设备树本质上是一棵由节点和属性组成的硬件描述树。每个节点就像文件系统中的目录可以包含子节点和属性。在我调试过的多个项目中最常打交道的节点类型包括/cpus描述CPU核心数量和特性/memory定义内存大小和地址范围/soc包含各类片上外设UART、GPIO等/chosen由Bootloader动态填充的运行时参数属性则是键值对形式的具体描述常见的有/* 字符串类型 */ compatible arm,cortex-a9; /* 32位整数数组 */ reg 0x101f0000 0x1000; /* 二进制数据 */ local-mac-address [00 0a 35 00 1e 53]; /* 字符串列表 */ compatible fsl,imx6q-gpio, fsl,imx35-gpio;2.2 地址映射的奥秘设备树中最容易出错的部分莫过于地址映射。通过#address-cells和#size-cells这两个属性可以灵活定义不同总线上的地址格式。记得有一次调试PCIe设备时就因为没搞清楚地址转换规则浪费了两天时间。典型的内存映射示例如下/ { #address-cells 2; #size-cells 1; external-bus { #address-cells 2; #size-cells 1; ranges 0 0 0x10100000 0x10000 1 0 0x10160000 0x10000 2 0 0x30000000 0x1000000; ethernet0,0 { reg 0 0 0x1000; }; }; };这里ranges属性建立了三级地址转换第一级是CPU视角的地址2 cells地址 1 cell大小第二级是总线桥的片选号2 cells第三级是设备在总线上的偏移地址2.3 中断处理的精妙设计现代SoC的中断系统越来越复杂设备树提供了清晰的描述方式。以GPIO中断为例gpio-keys { compatible gpio-keys; button { label Power; gpios gpio 3 1; linux,code 116; /* KEY_POWER */ gpio-key,wakeup; }; }; interrupt-controller10140000 { compatible arm,pl190; interrupt-controller; #interrupt-cells 2; };关键点在于interrupt-controller属性标记中断控制器#interrupt-cells定义中断描述符的格式interrupts属性指定中断号和触发方式3. 内核API实战指南3.1 设备树操作基础API在驱动代码中我们主要通过of_开头的API与设备树交互。最常用的几个函数包括// 获取兼容设备节点 struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat); // 读取属性值 int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value); // 解析中断 unsigned int irq_of_parse_and_map(struct device_node *dev, int index);比如获取GPIO编号的标准做法int gpio; struct device_node *np dev-of_node; if (!np) return -ENODEV; gpio of_get_named_gpio(np, enable-gpio, 0); if (!gpio_is_valid(gpio)) { dev_err(dev, invalid GPIO\n); return -EINVAL; }3.2 平台设备注册流程内核在启动时会自动将设备树节点转换为platform_device。这个转换过程主要涉及解析dtb文件生成设备树结构匹配具有compatible属性的节点为节点创建对应的platform_device注册到平台总线驱动开发者需要实现platform_driverstatic const struct of_device_id my_drv_ids[] { { .compatible vendor,my-device }, { /* sentinel */ } }; static struct platform_driver my_driver { .probe my_probe, .remove my_remove, .driver { .name my-device, .of_match_table my_drv_ids, }, };3.3 资源获取最佳实践在probe函数中获取设备资源的标准模式static int my_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; int irq; /* 获取内存资源 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(base)) return PTR_ERR(base); /* 获取中断资源 */ irq platform_get_irq(pdev, 0); if (irq 0) return irq; /* 获取GPIO */ int reset_gpio of_get_named_gpio(pdev-dev.of_node, reset-gpio, 0); if (gpio_is_valid(reset_gpio)) { gpio_request(reset_gpio, my_reset); gpio_direction_output(reset_gpio, 1); } return 0; }4. 高级技巧与调试方法4.1 设备树覆盖技术在嵌入式开发中经常需要在不修改原始dtsi的情况下覆盖某些配置。这可以通过设备树覆盖Overlay实现// 原始定义在板级dtsi中 i2c1 { status disabled; }; // 在覆盖文件中修改 /dts-v1/; /plugin/; i2c1 { status okay; touchscreen38 { compatible edt,edt-ft5x06; reg 0x38; }; };加载覆盖文件的命令fdtoverlay -o new.dtb -i base.dtb overlay.dtbo4.2 调试技巧大全当设备树配置出错时这些调试方法可能会救你一命查看解析后的设备树cat /proc/device-tree/* # 或者更直观的 dtc -I fs /proc/device-tree检查平台设备ls /sys/devices/platform/ cat /sys/devices/platform/device/of_node/*内核日志分析[ 0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000 [ 0.000000] OF: reserved mem: initialized node linux,cma, compatible id shared-dma-poolGPIO状态检查cat /sys/kernel/debug/gpio4.3 常见问题解决方案问题1驱动probe函数未被调用检查compatible字符串是否完全匹配确认status属性是否为okay查看/sys/firmware/devicetree/base下对应节点是否存在问题2资源获取失败确认reg属性格式正确检查#address-cells和#size-cells定义验证中断号在interrupt-controller中定义问题3设备树修改未生效确保Bootloader加载的是新dtb检查内核是否启用了CONFIG_OF选项确认没有其他dtsi覆盖了你的修改在某个车载项目调试CAN控制器时我就遇到过reg属性定义错误导致ioremap失败的情况。最终通过对比芯片手册和设备树地址映射发现是#size-cells值设置不当。这种硬件相关的bug往往最难排查需要耐心分析每一层地址转换。