嵌入式Linux设备树(DTS)深度解析——从硬件描述到驱动绑定
文章目录每日一句正能量一、引言为什么需要设备树二、设备树架构与编译流程2.1 设备树源文件类型2.2 编译工具链2.3 内核驱动绑定流程三、设备树节点层次结构3.1 节点命名规则3.2 节点引用与别名3.3 常见标准节点四、属性详解与数据类型4.1 标准属性4.2 属性数据类型五、compatible 属性与驱动绑定5.1 匹配机制详解5.2 匹配优先级六、设备树覆盖层 (Overlay)6.1 覆盖层源码结构6.2 编译与加载6.3 覆盖层应用场景七、中断与时钟属性深度解析7.1 中断属性7.2 时钟属性八、设备树调试与验证工具链8.1 常用调试命令8.2 驱动调试技巧九、设备树绑定文档 (Binding) 规范9.1 YAML 绑定文档结构9.2 验证绑定文档十、实战从零编写一个完整设备树10.1 硬件假设10.2 SoC 级定义 (soc.dtsi)10.3 板级定义 (board.dts)10.4 驱动实现十一、总结与最佳实践11.1 设备树编写原则11.2 常见错误排查每日一句正能量个人选择的路自己扛不跟世界要安慰只跟自身要答案。这是成年人的底色你选的路结果好坏都由自己承担。向外索求同情或理解往往徒增失望向内追问“我该如何调整、如何负责”才是解决之道。自立从停止扮演受害者开始。一、引言为什么需要设备树在早期的嵌入式 Linux 开发中硬件信息寄存器地址、中断号、引脚配置等直接硬编码在内核源码中如arch/arm/mach-xxx/board-xxx.c。这种架构存在严重问题内核膨胀每新增一块开发板都需要修改内核源码维护困难硬件变更需要重新编译整个内核可移植性差同一 SoC 的不同板级配置无法复用设备树Device Tree的引入彻底解决了这些问题。它通过一种独立的、声明式的数据结构描述硬件实现了内核与硬件的解耦SoC 厂商提供.dtsi文件描述通用硬件CPU、中断控制器、时钟等板级厂商提供.dts文件描述具体外设连接内核解析设备树动态加载匹配的驱动本文将从设备树架构、节点与属性、驱动绑定机制、覆盖层技术、调试工具等方面深入解析设备树的完整技术栈。二、设备树架构与编译流程2.1 设备树源文件类型文件类型扩展名用途示例设备树源码.dts板级硬件描述board.dts设备树包含.dtsiSoC 通用定义soc.dtsi设备树二进制.dtb编译后的内核加载格式output.dtb覆盖层源码.dts(含/plugin/)运行时动态修改overlay.dts覆盖层二进制.dtbo覆盖层编译输出output.dtbo2.2 编译工具链# 安装设备树编译器sudoapt-getinstalldevice-tree-compiler# DTS → DTB (编译)dtc-Idts-Odtb-ooutput.dtb board.dts# DTB → DTS (反编译用于调试)dtc-Idtb-Odts-oboard.dts output.dtb# 语法检查dtc-Idts-Odtb board.dts21|head-20# 查看 DTB 信息fdtdump output.dtb|head-502.3 内核驱动绑定流程设备树与内核驱动的绑定遵循严格的匹配机制/** * 内核启动时设备树解析流程 */// 1. 早期启动展开 DTBvoid__initsetup_arch(char**cmdline){// unflatten_device_tree() 将扁平 DTB 展开为 device_node 树unflatten_device_tree();}// 2. 平台设备注册void__initof_platform_populate(structdevice_node*root,conststructof_device_id*matches,structdevice_node*parent){// 遍历所有设备节点为每个节点创建 platform_devicefor_each_child_of_node(root,child){of_platform_device_create(child,NULL,parent);}}// 3. 驱动匹配staticintplatform_match(structdevice*dev,structdevice_driver*drv){structplatform_device*pdevto_platform_device(dev);structplatform_driver*pdrvto_platform_driver(drv);// 3.1 尝试 OF (Open Firmware) 风格匹配if(of_driver_match_device(dev,drv))return1;// 3.2 回退到 id_table 匹配if(pdrv-id_table)returnplatform_match_id(pdrv-id_table,pdev)!NULL;// 3.3 最后尝试名称匹配returnstrcmp(pdev-name,drv-name)0;}三、设备树节点层次结构3.1 节点命名规则/* 节点命名: name[unit-address] */ /soc/i2c40020000/eeprom50 /* 名称 (name): 设备类型标识 */ i2c, uart, gpio, eeprom /* 单元地址 (unit-address): 寄存器基地址或设备地址 */ 40020000 /* 寄存器地址 */ 50 /* I2C 设备地址 */ 0 /* SPI CS 片选号 */3.2 节点引用与别名/ { /* 别名简化路径引用 */ aliases { serial0 uart0; i2c0 i2c0; spi0 spi0; }; /* 标签 (label)用于 引用 */ i2c0: i2c40020000 { compatible vendor,i2c; reg 0x40020000 0x1000; status disabled; }; }; /* 使用 引用并修改节点 */ i2c0 { status okay; clock-frequency 400000; eeprom50 { compatible atmel,24c256; reg 0x50; pagesize 64; }; };3.3 常见标准节点节点路径用途典型属性/根节点#address-cells,#size-cells,model,compatible/cpusCPU 描述device_type cpu,reg,clock-frequency/memory内存布局device_type memory,reg/chosen启动参数bootargs,stdout-path/aliases别名定义serial0,i2c0等/socSoC 外设包含所有片上外设节点四、属性详解与数据类型4.1 标准属性/ { /* 模型标识 */ model MyCompany MyBoard; /* 兼容字符串用于驱动匹配 */ compatible mycompany,myboard, mycompany,myboard-v1; /* 地址单元格大小 */ #address-cells 2; /* 64位地址需要2个cells */ #size-cells 2; /* 64位大小需要2个cells */ soc { /* 子节点继承或覆盖 */ #address-cells 1; #size-cells 1; uart0: serial40020000 { /* 寄存器地址: base length */ reg 0x40020000 0x1000; /* 中断: type number flags */ interrupts GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH; /* 时钟引用 */ clocks clk 12; clock-names uartclk; /* 状态控制 */ status okay; /* okay 或 disabled */ }; }; };4.2 属性数据类型类型表示示例读取 API字符串...compatible vendor,deviceof_property_read_string()32位整数valuereg 0x40020000of_property_read_u32()数组v1 v2 v3reg 0x4000 0x1000of_property_read_u32_array()布尔属性存在即真interrupt-controllerof_property_read_bool()引用labelclocks clk 12of_parse_phandle()字符串列表a, bcompatible a, bof_property_read_string_array()五、compatible 属性与驱动绑定compatible是设备树中最重要的属性它是连接硬件描述与软件驱动的桥梁。5.1 匹配机制详解/* 设备树中的 compatible */ eeprom50 { /* 优先匹配第一个失败则回退到第二个 */ compatible atmel,24c256, atmel,24c; reg 0x50; };/* 驱动中的 of_match_table */staticconststructof_device_idat24_of_match[]{/* 精确匹配: 设备树 atmel,24c256 → 驱动 at24_data_24c256 */{.compatibleatmel,24c256,.dataat24_data_24c256},{.compatibleatmel,24c512,.dataat24_data_24c512},{.compatibleatmel,24c,.dataat24_data_generic},{/* sentinel */}};MODULE_DEVICE_TABLE(of,at24_of_match);staticstructi2c_driverat24_driver{.driver{.nameat24,.of_match_tableat24_of_match,},.probeat24_probe,.removeat24_remove,};staticintat24_probe(structi2c_client*client){conststructof_device_id*match;conststructat24_chip_data*chip;/* 获取匹配信息 */matchof_match_device(at24_of_match,client-dev);if(!match)return-ENODEV;/* 获取设备特定数据 */chipmatch-data;dev_info(client-dev,Matched: %s, pagesize%d\n,match-compatible,chip-page_size);/* 继续初始化... */}5.2 匹配优先级设备树: compatible vendor,device-v2, vendor,device; 匹配顺序: 1. 驱动 of_match_table 中 vendor,device-v2 → 精确匹配 2. 驱动 of_match_table 中 vendor,device → 回退匹配 3. 驱动 id_table 匹配 4. 驱动 name 匹配 最佳实践: - 新设备: vendor,device-v2 - 兼容旧驱动: vendor,device六、设备树覆盖层 (Overlay)覆盖层允许在运行时动态修改设备树无需重新编译完整 DTB。6.1 覆盖层源码结构// myoverlay.dts /dts-v1/; /plugin/; /* 声明为覆盖层 */ i2c0 { /* 引用已有节点 */ status okay; clock-frequency 400000; /* 新增子节点 */ eeprom50 { compatible atmel,24c256; reg 0x50; pagesize 64; }; }; /* 也可以新增根节点 */ / { leds { compatible gpio-leds; led0 { gpios gpio0 5 GPIO_ACTIVE_LOW; label status; }; }; };6.2 编译与加载# 编译覆盖层dtc-Idts-Odtb-omyoverlay.dtbo myoverlay.dts# 方法一: 通过 configfs 加载 (Linux 4.10)mkdir-p/sys/kernel/config/device-tree/overlays/myoverlaycatmyoverlay.dtbo/sys/kernel/config/device-tree/overlays/myoverlay/dtbo# 方法二: 通过 U-Boot 加载 (启动时)setenv bootargs${bootargs}dtoverlaymyoverlay# 或在 U-Boot 命令行:fdt addr${fdt_addr}fdt resize8192load mmc0:1${loadaddr}myoverlay.dtbo fdt apply${loadaddr}6.3 覆盖层应用场景场景说明HAT/扩展板Raspberry Pi 等平台的硬件扩展** cape/子卡**BeagleBone 的 cape 扩展动态外设热插拔设备如 USB 转 I2C产品变体同一主板的不同 SKU有无 WiFi/蓝牙七、中断与时钟属性深度解析7.1 中断属性/* GIC 中断控制器定义 */ gic: interrupt-controller40041000 { compatible arm,cortex-a7-gic; #interrupt-cells 3; /* 声明后续 cells 数量 */ interrupt-controller; /* 标记为中断控制器 */ reg 0x40041000 0x1000, 0x40042000 0x2000; }; /* 设备使用中断 */ serial0: serial40020000 { compatible ns16550a; reg 0x40020000 0x1000; /* interrupts 类型 中断号 触发方式 */ interrupts GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH; /* 中断父节点 */ interrupt-parent gic; };中断 cells 含义ARM GICCell值说明[0]GIC_SPI (0)共享外设中断GIC_PPI (1)私有外设中断[1]0-1019中断号[2]IRQ_TYPE_LEVEL_HIGH高电平触发IRQ_TYPE_EDGE_RISING上升沿触发IRQ_TYPE_EDGE_FALLING下降沿触发IRQ_TYPE_LEVEL_LOW低电平触发7.2 时钟属性/* 时钟控制器 */ clk: clock-controller40000000 { compatible vendor,clk; #clock-cells 1; /* 声明时钟 ID 需要 1 个 cell */ reg 0x40000000 0x1000; }; /* 设备使用时钟 */ serial0: serial40020000 { compatible ns16550a; /* clocks 控制器 时钟ID */ clocks clk 12; clock-names uartclk; };驱动中获取时钟staticintserial_probe(structplatform_device*pdev){structclk*clk;/* 方法一: 通过名称获取 */clkdevm_clk_get(pdev-dev,uartclk);if(IS_ERR(clk))returnPTR_ERR(clk);/* 方法二: 通过索引获取 */clkdevm_clk_get(pdev-dev,NULL);/* 第一个时钟 *//* 使能时钟 */clk_prepare_enable(clk);/* 获取时钟频率 */unsignedlongrateclk_get_rate(clk);dev_info(pdev-dev,Clock rate: %lu Hz\n,rate);/* 禁用时钟 */clk_disable_unprepare(clk);return0;}八、设备树调试与验证工具链8.1 常用调试命令# 编译时调试 # 检查 DTS 语法dtc-Idts-Odtb board.dts21# 查看展开后的完整设备树包含所有 includecpp-nostdinc-Iinclude-Iarch/arm/boot/dts-xassembler-with-cpp\-D__DTS__board.dts|dtc-Idts-Odts -# 运行时调试 # 查看设备树节点ls/proc/device-tree/ls/proc/device-tree/soc/# 查看节点属性cat/proc/device-tree/soc/i2c0/compatiblecat/proc/device-tree/soc/i2c0/statuscat/proc/device-tree/soc/i2c0/reg|xxd# 查找特定设备find/proc/device-tree-name*eeprom*# 查看设备绑定情况ls/sys/bus/platform/devices/cat/sys/bus/platform/devices/soc\:i2c0/uevent# U-Boot 调试 # 查看设备树fdt print /soc/i2c0 fdt print /soc/i2c0/eeprom50# 修改设备树内存中fdtset/soc/i2c0 statusokayfdtset/soc/i2c0 clock-frequency400000# 保存修改后的设备树fdt addr${fdt_addr}fdt save8.2 驱动调试技巧/* 打印设备树节点信息 */staticvoiddebug_device_node(structdevice_node*np){structproperty*prop;pr_info(Node: %s\n,np-full_name);for_each_property_of_node(np,prop){pr_info( Property: %s\n,prop-name);}}/* 检查属性存在性 */if(of_property_read_bool(np,optional-feature)){/* 启用可选功能 */}/* 安全读取属性带默认值 */u32 value1000;/* 默认值 */of_property_read_u32(np,custom-delay,value);九、设备树绑定文档 (Binding) 规范从 Linux 5.2 开始内核引入 YAML 格式的绑定文档用于自动化验证设备树。9.1 YAML 绑定文档结构# Documentation/devicetree/bindings/i2c/atmel,24c.yaml$schema:http://devicetree.org/meta-schemas/core.yaml#title:Atmel AT24 EEPROMmaintainers:-Bartosz Golaszewski bgolaszewskibaylibre.comdescription:|This binding describes the Atmel AT24 series of I2C EEPROMs.properties:compatible:enum:-atmel,24c00-atmel,24c01-atmel,24c02-atmel,24c04-atmel,24c08-atmel,24c16-atmel,24c32-atmel,24c64-atmel,24c128-atmel,24c256-atmel,24c512-atmel,24c1024reg:maxItems:1description:I2C device addresspagesize:$ref:/schemas/types.yaml#/definitions/uint32enum:[1,8,16,32,64,128,256]description:EEPROM page size for page writesize:$ref:/schemas/types.yaml#/definitions/uint32description:Total EEPROM size in bytesaddress-width:$ref:/schemas/types.yaml#/definitions/uint32enum:[8,16]default:8description:Number of address bitsrequired:-compatible-regadditionalProperties:falseexamples:-|i2c { #address-cells 1; #size-cells 0;eeprom50{compatible atmel,24c256; reg 0x50; pagesize 64;};};9.2 验证绑定文档# 使用 dt-schema 工具验证pip3installdtschema dt-validate Documentation/devicetree/bindings/# 验证具体设备树dt-validate board.dts-pDocumentation/devicetree/bindings/十、实战从零编写一个完整设备树10.1 硬件假设假设我们有一块基于 ARM Cortex-A7 的板卡包含1 个 UART (0x40020000)1 个 I2C (0x40030000)连接 AT24C256 EEPROM1 个 GPIO 控制的 LED10.2 SoC 级定义 (soc.dtsi)// arch/arm/boot/dts/vendor-soc.dtsi / { #address-cells 1; #size-cells 1; cpus { #address-cells 1; #size-cells 0; cpu0 { device_type cpu; compatible arm,cortex-a7; reg 0; clock-frequency 800000000; /* 800MHz */ }; }; soc { compatible simple-bus; #address-cells 1; #size-cells 1; ranges; uart0: serial40020000 { compatible ns16550a; reg 0x40020000 0x1000; interrupts GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH; clocks clk 12; clock-names uartclk; status disabled; }; i2c0: i2c40030000 { compatible vendor,i2c; reg 0x40030000 0x1000; interrupts GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH; clocks clk 13; clock-names i2cclk; #address-cells 1; #size-cells 0; status disabled; }; gpio0: gpio40040000 { compatible vendor,gpio; reg 0x40040000 0x1000; interrupts GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH; clocks clk 14; clock-names gpioclk; gpio-controller; #gpio-cells 2; interrupt-controller; #interrupt-cells 2; status disabled; }; }; };10.3 板级定义 (board.dts)// arch/arm/boot/dts/vendor-board.dts /dts-v1/; #include vendor-soc.dtsi / { model Vendor MyBoard; compatible vendor,myboard, vendor,myboard-v1; chosen { stdout-path serial0:115200n8; bootargs root/dev/mmcblk0p2 rw rootfstypeext4 consolettyS0,115200n8; }; memory80000000 { device_type memory; reg 0x80000000 0x10000000; /* 256MB */ }; leds { compatible gpio-leds; led0: status { gpios gpio0 5 GPIO_ACTIVE_LOW; label status; default-state on; }; }; }; uart0 { status okay; }; i2c0 { status okay; clock-frequency 400000; eeprom50 { compatible atmel,24c256; reg 0x50; pagesize 64; }; }; gpio0 { status okay; };10.4 驱动实现// drivers/i2c/eeprom/my_eeprom.c#includelinux/module.h#includelinux/i2c.h#includelinux/of.h#includelinux/of_device.hstructmy_eeprom_data{intpage_size;intsize;};staticconststructof_device_idmy_eeprom_of_match[]{{.compatibleatmel,24c256,.data(structmy_eeprom_data){64,32768}},{.compatibleatmel,24c512,.data(structmy_eeprom_data){128,65536}},{}};MODULE_DEVICE_TABLE(of,my_eeprom_of_match);staticintmy_eeprom_probe(structi2c_client*client){conststructmy_eeprom_data*data;structdevice_node*npclient-dev.of_node;u32 pagesize0;/* 获取匹配数据 */dataof_device_get_match_data(client-dev);if(!data)return-EINVAL;/* 读取设备树属性覆盖默认值 */of_property_read_u32(np,pagesize,pagesize);if(pagesize0)pagesizedata-page_size;dev_info(client-dev,EEPROM: size%d, pagesize%d\n,data-size,pagesize);return0;}staticintmy_eeprom_remove(structi2c_client*client){return0;}staticstructi2c_drivermy_eeprom_driver{.driver{.namemy_eeprom,.of_match_tablemy_eeprom_of_match,},.probemy_eeprom_probe,.removemy_eeprom_remove,};module_i2c_driver(my_eeprom_driver);MODULE_LICENSE(GPL);MODULE_AUTHOR(Your Name);MODULE_DESCRIPTION(My EEPROM Driver);十一、总结与最佳实践11.1 设备树编写原则DRY 原则公共定义放在.dtsi板级差异放在.dts兼容字符串使用vendor,model格式包含版本信息状态控制默认禁用外设 (status disabled)板级启用文档同步修改设备树时同步更新绑定文档11.2 常见错误排查现象原因解决方法驱动未加载compatible 不匹配检查字符串拼写寄存器访问错误reg 地址错误核对 datasheet中断不触发interrupts 配置错误检查中断号/触发方式时钟不工作clocks 未指定确认时钟控制器和 ID设备树编译失败语法错误使用dtc检查转载自https://blog.csdn.net/u014727709/article/details/162641210欢迎 点赞✍评论⭐收藏欢迎指正