在嵌入式 Linux 开发中设备树是一个绕不开的核心概念。它的出现从根本上改变了驱动开发的方式让“一套内核多套硬件”成为可能。本篇博客只讲最基础的内容包括什么是设备树为什么要有设备树dtsdtsidtb 是什么设备树里最基本的节点、属性怎么写驱动和设备树是怎么对应起来的1.设备树基本概念1.1什么是设备树在设备树出现之前ARM 架构下的 Linux 开发曾面临一个严重的困境硬件信息被硬编码在 C 源码中每块电路板都需要定制独立的内核导致内核镜像泛滥维护成本极高这与 Linux“一内核多平台”的目标严重冲突。设备树的本质就是一种树状数据结构用于描述硬件平台上那些不能动态探测到的设备如 CPU、内存、片内外设等。它将硬件信息从内核源码中分离出来保存在独立的配置文件中内核启动时动态读取这些信息自动加载适配驱动。简单理解设备树就是一份“硬件说明书”告诉 Linux 内核这块板子上有哪些设备以及它们的地址、中断号等配置参数。设备树可以先简单理解成一句话设备树就是用来描述硬件信息的数据结构。Linux 内核启动时需要知道板子上有哪些外设比如LED 接在哪个 GPIO按键接在哪个 GPIOUART 的寄存器地址是多少I2C 控制器的中断号是多少这些硬件信息不能写死在每个驱动里否则同一个内核换一块板子就要改源码、重新编译非常麻烦。所以 Linux 引入了设备树把这些和硬件板子相关的信息单独描述出来。1.2设备树文件格式与编译工具设备树涉及三种主要文件格式理解它们的区别是入门的第一步文件类型扩展名说明DTS.dts设备树源文件人类可读的 ASCII 文本一个 .dts 通常对应一个硬件平台DTSI.dtsi设备树包含文件描述 SoC 级别的公共信息如 CPU 架构、外设寄存器地址等可被多个 DTS 通过 #include 引用DTB.dtb设备树二进制文件由 DTS 编译生成供 U-Boot 和 Linux 内核使用从 DTS 到 DTB 的编译工具是DTCDevice Tree Compiler。典型用法如下# 编译 DTS 为 DTB dtc -I dts -O dtb -o output.dtb input.dts # 反编译 DTB 为 DTS调试常用 dtc -I dtb -O dts -o output.dts input.dtbDTC 工具源码位于内核的 scripts/dtc 目录也可通过 apt install device-tree-compiler 单独安装2.设备树的基本语法设备树文件由节点Node和属性Property两种基本元素构成。2.1节点节点的基本格式为标签: 节点名单元地址 { 属性... 子节点... };例如i2c1: i2c1c2b400 { compatible vendor,i2c-controller; reg 0x1c2b400 0x400; /* 子节点和属性 */ };/ 表示根节点根节点下面可以挂很多子节点/ { model my board; compatible myvendor,myboard; led0 { compatible myvendor,myled; }; key0 { compatible myvendor,mykey; }; };这里 led0 和 key0 就是两个子节点。2.2属性属性以键值对的形式描述设备特性常见的数据类型有1身份与状态类这类属性决定了 “你是谁” 以及 “当前系统用不用你” 是内核平台总线Platform Bus进行设备与驱动匹配的绝对核心。属性名称 (Property)物理硬件含义 (硬件的世界)内核与驱动行为 (软件的世界)compatible设备的“身份证号”。标明了具体的芯片制造商和器件的精确型号。平台总线拿着这串字符与驱动代码里的.of_match_table逐一比对。一旦字符串完全匹配内核就会唤醒并执行该驱动的probe函数。status电路的“物理开关”。描述该硬件模块在当前的电路板上是否实际存在是否贴了芯片、是否飞了线。okay 表示启用内核会为其分配内存并尝试挂载驱动disable 表示禁用内核解析器会直接忽略该节点节省系统资源。2硬件资源类这类属性描述了设备在物理世界中的 “坐标与连线” 。驱动程序被唤醒后必须靠这些属性才能真正找到硬件并对其发号施令。属性名称 (Property)物理硬件含义 (硬件的世界)内核与驱动行为 (软件的世界)reg设备的“门牌号”。硬件模块在芯片内部总线上的绝对物理基地址或者在外部通讯总线如 I2C/SPI上的从机地址。驱动在 probe 中通过 platform_get_resource 获取该值如果是内存地址则进一步使用 ioremap 映射为可操作的虚拟地址指针。interrupt-parent中断的“直属上司”。该模块的物理中断报警线具体接在了哪一个上级中断控制器比如某组 GPIO的身上。告知内核中断的级联路由关系让内核知道去哪个控制器那里为该设备申请虚拟中断号。interrupts中断的“触发条件”。具体接在第几号引脚以及物理电平是如何跳变的高/低电平或上升/下降沿。内核解析此信息后在内存中生成一个唯一的虚拟中断号 (VIRQ)。驱动程序随后使用 request_irq(virq, ...) 将中断处理函数注册到该号码上。clocks设备的“心脏起搏器”。硬件内部逻辑电路运行所依赖的物理振荡脉冲频率源。驱动程序在读写任何 reg 寄存器之前必须先调用 clk_enable() 开启这个时钟源。否则一旦访问未通时钟的寄存器会导致总线死锁和内核崩溃。示例/* 必须包含头文件才能使用硬件对应的文本宏如 RK_PB5, GPIO_ACTIVE_LOW */ #include dt-bindings/gpio/gpio.h #include dt-bindings/interrupt-controller/irq.h #include dt-bindings/pinctrl/rockchip.h /* 使用 符号引用在 .dtsi 中已经定义好的 i2c1 控制器节点 */ i2c1 { /* 【身份与状态类】把 I2C1 控制器总闸打开 */ status okay; /* 在 I2C1 总线下定义触摸屏节点38 只是节点命名习惯表示地址 */ touchscreen38 { /* 身份与状态类 */ /* 身份证号内核拿着 edt,edt-ft5206 去驱动代码里找对应的 .c 文件 */ compatible edt,edt-ft5206; /* 设备开关表示板子上确实焊了这个触摸屏请内核加载它 */ status okay; /* 硬件资源类 */ /* 门牌号I2C 通讯时的从机地址 (十六进制 0x38) */ reg 0x38; /* 汇报线这块屏幕的中断引脚物理上连到了 gpio0 控制器上 */ interrupt-parent gpio0; /* 触发源具体连在 gpio0 的 PB5 引脚且硬件设计为下降沿触发中断 */ interrupts RK_PB5 IRQ_TYPE_EDGE_FALLING; /* 物理连线这块屏幕的复位引脚物理上连到了 gpio0 的 PB6 引脚低电平复位 */ reset-gpios gpio0 RK_PB6 GPIO_ACTIVE_LOW; /* (注触摸屏自带内部振荡器通常不需要系统给它分配 clocks 属性) */ }; };3.中断实例分析作为驱动工程师我们撰写 ft5x06 触摸屏的中断的设备树时要根据以下步骤撰写1.查看 ft5x06 原理图找到中断对应的 gpio口可以看到是GPIO3_A52.查找gpio的设备树源码了解gpio对下规则对设备树代码的理解硬件连接过程外设-gpio-gpio控制器-GIC控制器-cpu内核所以这个地方对gpio0的定义分为两部分上半部分说明的是gpio作为普通外设对上对GIC的连接下半部分是说明gpio作为gpio控制器或者中断控制器的作用是对连接引脚的外设而言的所以我们作为驱动开发者的话我们要开发外设的驱动肯定是要看下半部分的详细解释见3.找 ft5x06 对应的driver程序确认name最终得到的设备树代码如下/dts-v1/ /include /home/topeet/source/linux/rk3568_linux_5.10/kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi /{ model This is touchscreen dt ft5x0638{ compatible edt,edt-ft5206 ; //name需要在ft5x06的驱动源码中看 interrupt-parent gpio0; interrupts 13,1; //等价于interruptsRK PB5 IRQ_TYPE EDGE_RISING; 用宏需要包含头文件 } }4.零碎知识点1.设备树中的设备在哪里查看如果你的开发板已经烧录了系统并进入了 Linux 命令行1.查看设备树节点(/proc/device-tree或/sys/firmware/devicetree/base)2.查看已加载的驱动与设备绑定 (/sys/bus/)查看驱动是否与设备绑定# 查看某个总线下的设备 ls /sys/bus/platform/devices/ # 或者直接通过设备名称查找假设你的节点名是 i2c0 ls /sys/bus/i2c/devices/2./dev/下的节点与设备树节点的区别它们分别处于驱动开发的两端一个是硬件描述一个是用户接口设备树是硬件工程师写给内核看的。它以树状结构描述了电路板上有哪些硬件、它们的基地址Register Address、中断号、引脚配置等。/dev下的设备节点是内核给应用程序看的。这种架构的优势在于解耦当硬件修改如更换传感器时只需要改设备树文件而不需要修改应用程序的代码甚至不需要修改驱动程序如果是通用驱动。
Linux内核与驱动:11.设备树
在嵌入式 Linux 开发中设备树是一个绕不开的核心概念。它的出现从根本上改变了驱动开发的方式让“一套内核多套硬件”成为可能。本篇博客只讲最基础的内容包括什么是设备树为什么要有设备树dtsdtsidtb 是什么设备树里最基本的节点、属性怎么写驱动和设备树是怎么对应起来的1.设备树基本概念1.1什么是设备树在设备树出现之前ARM 架构下的 Linux 开发曾面临一个严重的困境硬件信息被硬编码在 C 源码中每块电路板都需要定制独立的内核导致内核镜像泛滥维护成本极高这与 Linux“一内核多平台”的目标严重冲突。设备树的本质就是一种树状数据结构用于描述硬件平台上那些不能动态探测到的设备如 CPU、内存、片内外设等。它将硬件信息从内核源码中分离出来保存在独立的配置文件中内核启动时动态读取这些信息自动加载适配驱动。简单理解设备树就是一份“硬件说明书”告诉 Linux 内核这块板子上有哪些设备以及它们的地址、中断号等配置参数。设备树可以先简单理解成一句话设备树就是用来描述硬件信息的数据结构。Linux 内核启动时需要知道板子上有哪些外设比如LED 接在哪个 GPIO按键接在哪个 GPIOUART 的寄存器地址是多少I2C 控制器的中断号是多少这些硬件信息不能写死在每个驱动里否则同一个内核换一块板子就要改源码、重新编译非常麻烦。所以 Linux 引入了设备树把这些和硬件板子相关的信息单独描述出来。1.2设备树文件格式与编译工具设备树涉及三种主要文件格式理解它们的区别是入门的第一步文件类型扩展名说明DTS.dts设备树源文件人类可读的 ASCII 文本一个 .dts 通常对应一个硬件平台DTSI.dtsi设备树包含文件描述 SoC 级别的公共信息如 CPU 架构、外设寄存器地址等可被多个 DTS 通过 #include 引用DTB.dtb设备树二进制文件由 DTS 编译生成供 U-Boot 和 Linux 内核使用从 DTS 到 DTB 的编译工具是DTCDevice Tree Compiler。典型用法如下# 编译 DTS 为 DTB dtc -I dts -O dtb -o output.dtb input.dts # 反编译 DTB 为 DTS调试常用 dtc -I dtb -O dts -o output.dts input.dtbDTC 工具源码位于内核的 scripts/dtc 目录也可通过 apt install device-tree-compiler 单独安装2.设备树的基本语法设备树文件由节点Node和属性Property两种基本元素构成。2.1节点节点的基本格式为标签: 节点名单元地址 { 属性... 子节点... };例如i2c1: i2c1c2b400 { compatible vendor,i2c-controller; reg 0x1c2b400 0x400; /* 子节点和属性 */ };/ 表示根节点根节点下面可以挂很多子节点/ { model my board; compatible myvendor,myboard; led0 { compatible myvendor,myled; }; key0 { compatible myvendor,mykey; }; };这里 led0 和 key0 就是两个子节点。2.2属性属性以键值对的形式描述设备特性常见的数据类型有1身份与状态类这类属性决定了 “你是谁” 以及 “当前系统用不用你” 是内核平台总线Platform Bus进行设备与驱动匹配的绝对核心。属性名称 (Property)物理硬件含义 (硬件的世界)内核与驱动行为 (软件的世界)compatible设备的“身份证号”。标明了具体的芯片制造商和器件的精确型号。平台总线拿着这串字符与驱动代码里的.of_match_table逐一比对。一旦字符串完全匹配内核就会唤醒并执行该驱动的probe函数。status电路的“物理开关”。描述该硬件模块在当前的电路板上是否实际存在是否贴了芯片、是否飞了线。okay 表示启用内核会为其分配内存并尝试挂载驱动disable 表示禁用内核解析器会直接忽略该节点节省系统资源。2硬件资源类这类属性描述了设备在物理世界中的 “坐标与连线” 。驱动程序被唤醒后必须靠这些属性才能真正找到硬件并对其发号施令。属性名称 (Property)物理硬件含义 (硬件的世界)内核与驱动行为 (软件的世界)reg设备的“门牌号”。硬件模块在芯片内部总线上的绝对物理基地址或者在外部通讯总线如 I2C/SPI上的从机地址。驱动在 probe 中通过 platform_get_resource 获取该值如果是内存地址则进一步使用 ioremap 映射为可操作的虚拟地址指针。interrupt-parent中断的“直属上司”。该模块的物理中断报警线具体接在了哪一个上级中断控制器比如某组 GPIO的身上。告知内核中断的级联路由关系让内核知道去哪个控制器那里为该设备申请虚拟中断号。interrupts中断的“触发条件”。具体接在第几号引脚以及物理电平是如何跳变的高/低电平或上升/下降沿。内核解析此信息后在内存中生成一个唯一的虚拟中断号 (VIRQ)。驱动程序随后使用 request_irq(virq, ...) 将中断处理函数注册到该号码上。clocks设备的“心脏起搏器”。硬件内部逻辑电路运行所依赖的物理振荡脉冲频率源。驱动程序在读写任何 reg 寄存器之前必须先调用 clk_enable() 开启这个时钟源。否则一旦访问未通时钟的寄存器会导致总线死锁和内核崩溃。示例/* 必须包含头文件才能使用硬件对应的文本宏如 RK_PB5, GPIO_ACTIVE_LOW */ #include dt-bindings/gpio/gpio.h #include dt-bindings/interrupt-controller/irq.h #include dt-bindings/pinctrl/rockchip.h /* 使用 符号引用在 .dtsi 中已经定义好的 i2c1 控制器节点 */ i2c1 { /* 【身份与状态类】把 I2C1 控制器总闸打开 */ status okay; /* 在 I2C1 总线下定义触摸屏节点38 只是节点命名习惯表示地址 */ touchscreen38 { /* 身份与状态类 */ /* 身份证号内核拿着 edt,edt-ft5206 去驱动代码里找对应的 .c 文件 */ compatible edt,edt-ft5206; /* 设备开关表示板子上确实焊了这个触摸屏请内核加载它 */ status okay; /* 硬件资源类 */ /* 门牌号I2C 通讯时的从机地址 (十六进制 0x38) */ reg 0x38; /* 汇报线这块屏幕的中断引脚物理上连到了 gpio0 控制器上 */ interrupt-parent gpio0; /* 触发源具体连在 gpio0 的 PB5 引脚且硬件设计为下降沿触发中断 */ interrupts RK_PB5 IRQ_TYPE_EDGE_FALLING; /* 物理连线这块屏幕的复位引脚物理上连到了 gpio0 的 PB6 引脚低电平复位 */ reset-gpios gpio0 RK_PB6 GPIO_ACTIVE_LOW; /* (注触摸屏自带内部振荡器通常不需要系统给它分配 clocks 属性) */ }; };3.中断实例分析作为驱动工程师我们撰写 ft5x06 触摸屏的中断的设备树时要根据以下步骤撰写1.查看 ft5x06 原理图找到中断对应的 gpio口可以看到是GPIO3_A52.查找gpio的设备树源码了解gpio对下规则对设备树代码的理解硬件连接过程外设-gpio-gpio控制器-GIC控制器-cpu内核所以这个地方对gpio0的定义分为两部分上半部分说明的是gpio作为普通外设对上对GIC的连接下半部分是说明gpio作为gpio控制器或者中断控制器的作用是对连接引脚的外设而言的所以我们作为驱动开发者的话我们要开发外设的驱动肯定是要看下半部分的详细解释见3.找 ft5x06 对应的driver程序确认name最终得到的设备树代码如下/dts-v1/ /include /home/topeet/source/linux/rk3568_linux_5.10/kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi /{ model This is touchscreen dt ft5x0638{ compatible edt,edt-ft5206 ; //name需要在ft5x06的驱动源码中看 interrupt-parent gpio0; interrupts 13,1; //等价于interruptsRK PB5 IRQ_TYPE EDGE_RISING; 用宏需要包含头文件 } }4.零碎知识点1.设备树中的设备在哪里查看如果你的开发板已经烧录了系统并进入了 Linux 命令行1.查看设备树节点(/proc/device-tree或/sys/firmware/devicetree/base)2.查看已加载的驱动与设备绑定 (/sys/bus/)查看驱动是否与设备绑定# 查看某个总线下的设备 ls /sys/bus/platform/devices/ # 或者直接通过设备名称查找假设你的节点名是 i2c0 ls /sys/bus/i2c/devices/2./dev/下的节点与设备树节点的区别它们分别处于驱动开发的两端一个是硬件描述一个是用户接口设备树是硬件工程师写给内核看的。它以树状结构描述了电路板上有哪些硬件、它们的基地址Register Address、中断号、引脚配置等。/dev下的设备节点是内核给应用程序看的。这种架构的优势在于解耦当硬件修改如更换传感器时只需要改设备树文件而不需要修改应用程序的代码甚至不需要修改驱动程序如果是通用驱动。