11.1 Linux 设备树
一、什么是设备树?
设备树(Device Tree),描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息:

树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。
一般.dts 描述板级信息(也就是开发板上有哪些 IIC 设备、 SPI 设备等), .dtsi 描述 SOC 级信息(也就是 SOC 有几个 CPU、主频是多少、各个外设控制器信息等)。
Linux 内核中 ARM 架构下有太多垃圾板级信息文件,所以才引进设备树。
二、DTS、DTB 和 DTC
DTS 是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb 需要用到 DTC 工具。如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:
cd
cd /linux/atk-mpl/linux/my_linux/linux-5.4.31\make all
#或者
make dtbs# make all命令是编译 Linux 源码中的所有东西,包括 uImage,.ko 驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令,“make dtbs”会编译选中的所有设备树文件
如果只要编译指定的某个设备树,比如 ST 官方编写的“stm32mp157d-ed1.dts”,可以输入如下命令:
make stm32mp157d-ed1.dtb
每个板子都有一个对应的 DTS 文件,如何确定编译哪一个 DTS 文件呢? 就拿我们手中的 STM32MP1 这个芯片对应的板子为例,打开arch/arm/boot/dts/Makefile :
dtb-$(CONFIG_ARCH_STM32) += \ # 981 行开始stm32f429-disco.dtb \stm32f469-disco.dtb \stm32f746-disco.dtb \stm32f769-disco.dtb \stm32429i-eval.dtb \stm32746g-eval.dtb \stm32h743i-eval.dtb \stm32h743i-disco.dtb \stm32mp157a-avenger96.dtb \stm32mp157a-dk1.dtb \stm32mp157d-dk1.dtb \stm32mp157c-dk2.dtb \stm32mp157f-dk2.dtb \stm32mp157c-dk2-a7-examples.dtb \stm32mp157c-dk2-m4-examples.dtb \stm32mp157f-dk2-a7-examples.dtb \stm32mp157f-dk2-m4-examples.dtb \stm32mp157a-ed1.dtb \stm32mp157c-ed1.dtb \stm32mp157d-ed1.dtb \stm32mp157f-ed1.dtb \stm32mp157a-ev1.dtb \stm32mp157c-ev1.dtb \stm32mp157d-ev1.dtb \stm32mp157f-ev1.dtb \stm32mp157c-ev1-a7-examples.dtb \stm32mp157c-ev1-m4-examples.dtb \stm32mp157f-ev1-a7-examples.dtb \stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-atk.dtb # 这是之前加的
当选中 STM32MP1 这个 SOC 以后(CONFIG_ARCH_STM32=y),所有使用到STM32MP1 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb。如果我们使用 STM32MP1 新做了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-$( CONFIG_ARCH_STM32)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb 文件。
在哪用到了.dtb 文件呢,其实就在Uboot开启操作系统的时候用到了。

三、DTS 语法
一般情况不会从头到尾写一个 .dts 文件,大多时候都是去修改 SOC 厂商提供的 .dts 文件上修改。学习一遍 DTS 语法可以让我们修改 .dts 文件。
1. dtsi 头文件
在设备树中可以引用.h、.dtsi 和 .dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。
前 STM32MP1 系列里有 stm32mp151、 stm32mp153和 stm32mp157 这三款 SOC,其中 151 是外设最少的, 153 和 157 的外设是在 151 的基础上逐渐增加的。因此 151 就相当于“基类”, 153 和 157 是在 151 基础上得到的“派生类”。因此 ST就把最基本的外设资源都写在 stm32mp151.dtsi 文件里。 stm32mp151.dtsi 就是描述 151、 153 和 157 共有的外设信息的。
/ {#address-cells = <1>;#size-cells = <1>;cpus {#address-cells = <1>;#size-cells = <0>;cpu0: cpu@0 {compatible = "arm,cortex-a7";...nvmem-cell-names = "part_number";#cooling-cells = <2>;};};cpu0_opp_table: cpu0-opp-table {compatible = "operating-points-v2";opp-shared;};...spi2: spi@4000b000 {#address-cells = <1>;#size-cells = <0>;compatible = "st,stm32h7-spi";reg = <0x4000b000 0x400>;interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;clocks = <&rcc SPI2_K>;resets = <&rcc SPI2_R>;dmas = <&dmamux1 39 0x400 0x01>,<&dmamux1 40 0x400 0x01>;dma-names = "rx", "tx";power-domains = <&pd_core>;status = "disabled";};
}
顶层节点是根节点"/",下面包含了两个子节点:cpus和spi2。每个节点都使用花括号括起来,并包含一系列属性和属性值。在cpus节点中定义了一个子节点cpu0,用于描述第一个CPU的配置,spi2节点中定义了SPI控制器的配置信息。
2. 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息, 属性就是键—值对,每个属性由一个键和一个值组成。以下是缩减后的设备树模板:
/ { # "/" 是根节点,每个设备树文件都只有一个根节点。#address-cells = <1>;#size-cells = <1>;aliases { # 子节点serial0 = &uart4;};cpus { # 子节点#address-cells = <1>;#size-cells = <0>;# 节点标签:label:node-name@unit-addresscpu0: cpu@0 { # 引入节点标签是为了可以直接通过 &label来访问,比如cpu0,可以直接用&cpu0访问compatible = "arm,cortex-a7";device_type = "cpu";reg = <0>;clocks = <&scmi0_clk CK_SCMI0_MPU>;clock-names = "cpu";operating-points-v2 = <&cpu0_opp_table>;nvmem-cells = <&part_number_otp>;nvmem-cell-names = "part_number";#cooling-cells = <2>;};};soc { # 子节点compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;interrupt-parent = <&intc>;ranges;sram: sram@10000000 { # sram是soc的子节点compatible = "mmio-sram";reg = <0x10000000 0x60000>;#address-cells = <1>;#size-cells = <1>;ranges = <0 0x10000000 0x60000>;};};
}# 节点格式:node-name@unit-address
# 其中 node-name 是节点名字,比如"uart1"表示UART1外设,"unit-address"表示设备地址或寄存器首地址,没有地址可以不要,比如 cpu@0,soc
① 字符串
compatible = "arm,cortex-a7";
设置compatible 属性为字符串 "arm,cortex-a7,"。
② 32位无符号整数
reg = <0>;
设置 reg 属性的值为 0, reg 的值也可以设置为一组值。reg = <0 0x123456 100>;
③ 字符串列表
compatible = "st,stm32mp157d-atk", "st,stm32mp157";
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开。
3. 标准属性
① compatible 属性
compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序, compatible 属性的值格式如下所示:
"manufacturer,model"
# 其中 manufacturer 表示厂商, model 一般是模块对应的驱动名字。
比如 stm32mp15xx-dkx.dtsi中有一个音频设备节点,这个节点的音频芯片采用的Cirrus Logic公司出品的cs42l51:
compatible = "cirrus,cs42l51";
# 属性值为“cirrus,cs42l51”,其中‘cirrus’表示厂商是 Cirrus Logic,“cs42l51”表示驱动模块名字
compatible 也可以多个属性值比如:
compatible = "cirrus,my_cs42l51","cirrus,cs42l51";
# 这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查,以此类推,直到查找完 compatible 属性中的所有值。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在文件cs42l51.c 中有如下内容:
const struct of_device_id cs42l51_of_match[] = {{ .compatible = "cirrus,cs42l51", },{ }
};# 数组 cs42l51_of_match 就是 cs42l51.c 这个驱动文件的匹配表,此匹配表只有一个匹配值“cirrus,cs42l51”。如果在设备树中有哪个节点的 compatible 属性值与此相等,那么这个节点就会使用此驱动文件。
② model 属性
model 属性值也是一个字符串,一般 model 属性描述开发板的名字或者设备模块信息,比如:
model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";
③ status 属性
status 属性值也是字符串,字符串是设备的状态信息:
| “okay” | 表明设备是可操作的。 |
| “disabled” | 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。 |
| “fail” | 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。 |
| “fail-sss” | 含义和“fail”相同,后面的 sss 部分是检测到的错误内容。 |
④ #address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形, #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。 #address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式为:
reg = <address1 length1 address2 length2 address3 length3……># 每个“address length”组合表示一个地址范围,其中 address 是起始地址, length 是地址长度
#address-cells 表明 address 这个数据所占用的字长, #size-cells 表明 length 这个数据所占用的字长,比如:
cpus {#address-cells = <1>;#size-cells = <0>;# 说明 cpus 的子节点 reg 属性中起始地址所占用的字长为 1,地址长度所占用的字长为 0cpu0: cpu@0 {compatible = "arm,cortex-a7";device_type = "cpu";reg = <0>; # 因为父节点设置了 address-cells和size-cells ,因此 addres=0,没有 length 的值,相当于设置了起始地址,而没有设置地址长度clocks = <&scmi0_clk CK_SCMI0_MPU>;clock-names = "cpu";operating-points-v2 = <&cpu0_opp_table>;nvmem-cells = <&part_number_otp>;nvmem-cell-names = "part_number";#cooling-cells = <2>;};
};scmi_sram: sram@2ffff000 {compatible = "mmio-sram";reg = <0x2ffff000 0x1000>;#address-cells = <1>;#size-cells = <1>;ranges = <0 0x2ffff000 0x1000>;scmi0_shm: scmi_shm@0 {reg = <0 0x80>; # 设置了起始地址为0x0,地址长度为 0x80};
};
⑤ reg 属性
reg 属性的值一般是(address, length) 。reg 属性一般用于描述设备地址空间资源信息或者设备地址信息,比如某个外设的寄存器地址范围信息或者IIC期间的设备地址等。
⑥ ranges 属性
ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 stm32mp157 来说,子地址空间和父地址空间完全相同,因此会在stm32mp151.dtsi 中找到大量的值为空的 ranges 属性。
soc {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;interrupt-parent = <&intc>;ranges = <0 0x10000000 0x100000>;
# 节点soc定义了ranges属性,此属性值指定了一个 1024KB(0x100000)的地址范围,子地址空间的物理起始地址为 0,父地址空间的物理起始地址为 0x10000000sram: sram@10000000 { compatible = "mmio-sram";reg = <0x0 0x60000>; # reg定义了sram设备起始地址为0,寄存器长度为0x60000,经过地址转换,sram设备可以从0x10000000开始读写操作#address-cells = <1>;#size-cells = <1>;ranges = <0 0x10000000 0x60000>;};
};
4. 根节点下的 compatible 属性
每个节点都有 compatible 属性,根节点“/”也不例外,在我们新建的 stm32mp157d-atk.dts文件中根节点的 compatible 属性内容如下:
/ {model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";compatible = "st,stm32mp157d-atk", "st,stm32mp157"; # 匹配Linux内核中的驱动程序# 描述设备 描述设备使用的SOC....
};
通过根节点的 compatible 属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“stm32mp157d-atk”这个设备,第二个值描述了设备所使用的 SOC,比如这里使用的是“stm32mp157”这颗 SOC, Linux内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux内核。
当 Linux 内核引入设备树后就是换成 DT_MACHINE_START。
static const char *const stm32_compat[] __initconst = {"st,stm32f429","st,stm32f469","st,stm32f746","st,stm32f769","st,stm32h743","st,stm32mp151","st,stm32mp153","st,stm32mp157",NULL
};# st,stm32mp157”与 stm32_compat 中的“ st,stm32mp157”匹配,因此 STM32MP157 开发板可以正常启动 Linux 内核。
只要某个设备(板子)根节点“/”的compatible 属性值与 stm32_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备。 stm32mp157d-atk.dts 中根节点的 compatible 属性值如下 :
compatible = "st,stm32mp157d-atk", "st,stm32mp157";
总体来说:
内核启动时加载设备树。设备树描述了硬件设备的结构和特性,包括各个设备节点及其属性。
内核根据设备树中的根节点开始解析。根节点是设备树中的顶层节点,通常以/表示。
内核读取根节点的compatible属性。该属性包含了设备的类型和兼容性信息。
内核遍历已加载的驱动程序列表,尝试与根节点的compatible属性进行匹配。
如果找到匹配的驱动程序,内核通过调用驱动程序中的初始化函数来初始化设备。
驱动程序的初始化函数会读取设备树中与设备相关的属性,进行设备的初始化和配置。
5. 向节点追加或修改内容
什么时候需要追加和修改内容呢?如果硬件被修改了,那么我们需要同步修改设备树文件。假设现在有个六轴芯片fxls8471, fxls8471 要接到 STM32MP157D-ATK 开发板的 I2C1 接口上,那么相当于需要在 i2c1这个节点上添加一个 fxls8471 子节点。先看一下 I2C1 接口对应的节点,打开文件 stm32mp157.dtsi 文件:
i2c1: i2c@40012000 {compatible = "st,stm32mp15-i2c";reg = <0x40012000 0x400>;interrupt-names = "event", "error";interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,<&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;clocks = <&rcc I2C1_K>;resets = <&rcc I2C1_R>;#address-cells = <1>;#size-cells = <0>;dmas = <&dmamux1 33 0x400 0x80000001>,<&dmamux1 34 0x400 0x80000001>;dma-names = "rx", "tx";power-domains = <&pd_core>;st,syscfg-fmp = <&syscfg 0x4 0x1>;wakeup-source;status = "disabled";};
};
打开 stm32mp157d-atk.dts,在根节点后添加以下代码:
/*
追加的方法:
&i2c1{// 要追加或修改的内容
}
*/&i2c1 {pinctrl-names = "default", "sleep"; // 引脚控制名称pinctrl-0 = <&i2c1_pins_b>; // 默认引脚控制配置pinctrl-1 = <&i2c1_pins_sleep_b>; // 睡眠模式下的引脚控制配置status = "okay";clock-frequency = <100000>; // I2C时钟频率fxls8471@1e {compatible = "fsl,fxls8471";reg = <0x1e>; // 指定设备寄存器的地址position = <0>; interrupt-parent = <&gpioh>; // 定义中断信号的父节点interrupts = <6 IRQ_TYPE_EDGE_FALLING>; // 指定中断引脚为6,并且为下降沿触发};
};
最重要的其实就是:通过 &label 来访问节点,然后直接在里面编写要追加或者修改的内容。
四、创建小型模板设备树
编写的这个设备树没有意义,只让我们去掌握设备树的语法。
在编写设备树之前要先定义一个设备,我们就以 STM32MP157 这个 SOC 为例,我们需要在设备树里面描述的内容如下:
1、这个芯片是由两个 Cortex-A7 架构的 32 位 CPU 和 Cortex-M4 组成。
2、STM32MP157 内部 sram,起始地址为 0x10000000,大小为 384KB(0x60000)。
3、STM32MP157 内部 timers6,起始地址为 0x40004000,大小为 25.6KB(0x400)。
4、STM32MP157 内部 spi2,起始地址为 0x4000b000,大小为 25.6KB(0x400)。
5、STM32MP157 内部 usart2,起始地址为 0x4000e000,大小为 25.6KB(0x400)。
6、STM32MP157 内部 i2c1,起始地址为 0x40012000,大小为 25.6KB(0x400)。
首先在 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 路径下去修改或者创造.dts/.dtsi 文件,一般情况都是在dts目录下。首先,搭建一个仅含有根节点“/”的基础的框架并创建一个 myfirstdevicetree.dts,输入以下内容:
/ {compatible = "st,stm32mp157d-atk", "st,stm32mp157";
};
1. 添加 cpus 节点
/* 此节点用于描述 SOC 内部的所有 CPU,因为 STM32MP157 有两个 CPU,所以在 cpus 下添加两个子节点分别为 cpu0 和 cpu1。*//* cpu 节点 */cpus {#address-cells = <1>;#size-cells = <0>;/* CPU0 节点 */cpu0:cpu@0 {compatible = "arm,cortex-a7";device_type = "cpu";reg = <0>;};/* CPU1 节点 */ cpu1:cpu@1 {compatible = "arm,cortex-a7";device_type = "cpu";reg = <1>;};}
2. 添加 soc 节点
像 uart, iic 控制器等等这些都属于 SOC 内部外设,因此一般会创建一个叫做 soc 的父节点来管理这些 SOC 内部外设的子节点:
/* soc 节点 */soc {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>; // soc 子节点的 reg 属性中起始地占用一个字长,地址空间长度也占用一个字长ranges; // ranges 属性为空,说明子空间和父空间地址范围相同};
3. 添加 sram 节点
sram 是 STM32MP157 内部 RAM, M4 内核会用到 SRAM4。 sram是soc节点的子节点。sram起始地址为0x10000000,大小为384KB:
/* soc 节点 */soc {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;ranges;/* sram 节点 */sram: sram@10000000 { // 0x10000000 就是 sram 的起始地址compatible = "mmio-sram";reg = <0x10000000 0x60000> // sram 内存的起始地址为 0x10000000,大小为 0x60000ranges = <0 0x10000000 0x60000>;};};
4. 添加 timers6、spi2、usart2 和 i2c1 节点
/* soc 节点 */soc {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;ranges;/* sram 节点 */sram: sram@10000000 {compatible = "mmio-sram";reg = <0x10000000 0x60000>ranges = <0 0x10000000 0x60000>;};/* timers6 节点 */timers6: timer@40004000 {#address-cells = <1>;#size-cells = <0>;compatible = "st,stm32-timers";reg = <0x40004000 0x400>;};/* spi2 节点 */spi2: spi@4000b000 {#address-cells = <1>;#size-cells = <0>;compatible = "st,stm32h7-spi";reg = <0x4000b000 0x400>;};/* usart2 节点 */usart2: serial@4000e000 {compatible = "st,stm32h7-uart";reg = <0x4000e000 0x400>;};/* i2c1 节点 */i2c1: i2c@40012000 {compatible = "st,stm32mp15-i2c";reg = <0x40012000 0x400>;};};
五、设备树在系统中的体现
Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹:

1.根节点 "/" 各个属性
“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设备树中就是根节点的5 个属性,用命令 cat 可以看 model 和 compatible 文件内容:

其实这些都是 "/" 根节点 model 和 compatible 属性值。
2.根节点 "/" 各子节点
进入/proc/device-tree/soc 目录中就可以看到 soc 节点的所有子节点:

总结:无论根节点还是子节点,白色的都是该节点的属性,蓝色的都是该节点的子节点,进入soc节点,也是一样。
六、特殊节点
在根节点“/”中有两个特殊的子节点: aliases 和 chosen。
1. aliases 子节点
打开 stm32mp157d-atk.dts 文件, aliases 节点内容如下:
aliases {serial0 = &uart4;
};
aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上 label,然后通过 &label 来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。
2. chosen 子节点
chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少。进入 chosen 节点发现 bootargs文件,它内容为:

这个跟我们在 Uboot 设置 bootargs 一样的值。 uboot 在启动 Linux 内核的时候会将 bootargs 的值传递给 Linux内核, bootargs 会作为 Linux 内核的命令行参数, Linux 内核启动的时候会打印出命令行参数(也就是 uboot 传递进来的 bootargs 的值) 。其实就是 uboot 中的 fdt_chosen 函数在设备树的 chosen 节点中加入了 bootargs 属性,并且还设置了 bootargs 属性值。
七、设备树常用 OF 操作函数
设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。比如设备树使用 reg 属性描述了某个外设的寄存器地址为 0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg 属性的0X02005482 和 0X400 这两个值,然后初始化外设。 Linux 内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做 OF 函数。
1. 查找节点的 OF 函数
设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。
① of_find_node_by_name 函数
/** @description : 通过节点名字查找指定的节点* @param - from : 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树* @param - name : 查找的节点名字* @return : 找到的节点,如果为 NULL 表示查找失败*/
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
② of_find_node_by_type 函数
/** @description : 通过 device_type 属性查找指定的节点* @param - from : 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树* @param - type : 查找的节点对应的 type 字符串,也就是 device_type 属性值* @return : 找到的节点,如果为 NULL 表示查找失败*/
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
③ of_find_compatible_node 函数
/** @description : 根据 device_type 和 compatible 这两个属性查找指定的节点* @param - from : 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树* @param - type : 查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示忽略掉 device_type 属性* @param - compatible : 要查找的节点所对应的 compatible 属性列表* @return : 找到的节点,如果为 NULL 表示查找失败*/
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
④ of_find_matching_node_and_match 函数
/** @description : 通过 of_device_id 匹配表来查找指定的节点* @param - from : 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树* @param - matches : of_device_id 匹配表,也就是在此匹配表里面查找节点* @param - match : 找到的匹配的 of_device_id* @return : 找到的节点,如果为 NULL 表示查找失败*/
struct device_node *of_find_matching_node_and_match(struct device_node *from,const struct of_device_id *matches,const struct of_device_id **match);
⑤ of_find_node_by_path 函数
/** @description : 通过路径来查找指定的节点* @param - path : 带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径* @return : 找到的节点,如果为 NULL 表示查找失败*/
inline struct device_node *of_find_node_by_path(const char *path);
2. 查找父/子节点的 OF 函数
查找节点对应的父节点或者子节点的 OF 函数。
① of_get_parent 函数
/** @description : 获取指定节点的父节点(如果有父节点的话)* @param - node : 要查找的父节点的节点* @return : 找到的父节点*/
struct device_node *of_get_parent(const struct device_node *node);
② of_get_next_child 函数
/** @description : 迭代的查找子节点* @param - node : 父节点* @param - prev : 前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始* @return : 找到的下一个子节点*/
struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);
3. 提取属性值的 OF 函数
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux 内核中使用结构体 property 表示属性。
① of_find_property 函数
/** @description : 查找指定的属性* @param - np : 设备节点* @param - name : 设备名字* @param - lenp : 属性值的字节数* @return : 找到的属性*/
property *of_find_property(const struct device_node *np,const char *name,int *lenp);
② of_property_count_elems_of_size 函数
/** @description : 获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小* @param - np : 设备节点* @param - proname : 需要统计元素数量的属性名字* @param - elem_size : 元素长度* @return : 得到的属性元素数量*/
int of_property_count_elems_of_size(const struct device_node *np,const char *propname,int elem_size);
③ of_property_read_u32_index 函数
/** @description : 从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 u32 类型的值* @param - np : 设备节点* @param - proname : 要读取的属性名字* @param - index : 要读取的值标号* @param - out_value : 读取到的值* @return : 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小*/
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index,u32 *out_value);
④ of_property_read_u8_array 函数 (还有u16/u32/u64)
/** @description : 读取属性中 u8、 u16、 u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据* @param - np : 设备节点* @param - proname : 要读取的属性名字* @param - out_value : 读取到的数组值,分别为 u8、 u16、 u32 和 u64* @param - sz : 要读取的数组元素数量* @return : 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小*/
int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values,size_t sz);
⑤ of_property_read_u8 函数 (还有u16/u32/u64)
/** @description : 有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性* @param - np : 设备节点* @param - proname : 要读取的属性名字* @param - out_value : 读取到的数组值* @return : 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小*/
int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value);
⑥ of_property_read_string 函数
/** @description : 用于读取属性中字符串值* @param - np : 设备节点* @param - proname : 要读取的属性名字* @param - out_string : 读取到的字符串值* @return : 0,读取成功,负值,读取失败*/
int of_property_read_string(struct device_node *np,const char *propname,const char **out_string);
⑦ of_n_addr_cells 函数
/** @description : 用于获取#address-cells 属性值* @param - np : 设备节点* @return : 获取到的#address-cells 属性值*/
int of_n_addr_cells(struct device_node *np);
⑧ of_n_size_cells 函数
/** @description : 用于获取#size-cells 属性值* @param - np : 设备节点* @return : 获取到的#size-cells 属性值*/
int of_n_size_cells(struct device_node *np);
4. 其他常用的 OF 函数
① of_device_is_compatible 函数
/** @description : 用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性* @param - device : 设备节点* @param - compat : 要查看的字符串* @return : 点的 compatible 属性中不包含 compat 指定的字符串;正数,节点的 compatible属性中包含 compat 指定的字符串*/
int of_device_is_compatible(const struct device_node *device,const char *compat);
② of_get_address 函数
/** @description : 用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值* @param - dev : 设备节点* @param - index : 要读取的地址标号* @param - size : 地址长度* @param - flags : 参数,比如 IORESOURCE_IO、 IORESOURCE_MEM 等* @return : 读取到的地址数据首地址,为 NULL 的话表示读取失败*/
const __be32 *of_get_address(struct device_node *dev,int index,u64 *size,unsigned int *flags);
③ of_translate_address 函数
/** @description : 将从设备树读取到的地址转换为物理地址* @param - dev : 设备节点* @param - in_addr : 要转换的地址* @return : 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败*/
u64 of_translate_address(struct device_node *dev,const __be32 *addr);
④ of_address_to_resource 函数
/** @description : 将 reg 属性值,然后将其转换为 resource 结构体类型* @param - dev : 设备节点* @param - index : 地址资源标号* @param - r : 得到的 resource 类型的资源值* @return : 0,成功;负值,失败*/
int of_address_to_resource(struct device_node *dev,int index,struct resource *r);struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;unsigned long desc;struct resource *parent, *sibling, *child;
};
/* 对于 32 位的 SOC 来说, resource_size_t 是 u32 类型的。其中 start 表示开始地址, end 表示
结束地址, name 是这个资源的名字, flags 是资源标志位,一般表示资源类型 */
⑤ of_iomap 函数
of_iomap 函数用于直接内存映射,在采用设备树以后,大部分的驱动都使用 of_iomap 函数。of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段。
/** @description : 用于直接内存映射* @param - np : 设备节点* @param - index : reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0* @return : 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败*/
void __iomem *of_iomap(struct device_node *np,int index);
设备树重点:设备树语法、设备树的 OF 操作函数。
总结:设备树就是查询板子信息的,我们驱动开发所需要的信息都会在上面,比如设备名字、设备地址之类的都在里面,简而言之,设备树其实就是类似于查询手册一样。
相关文章:
11.1 Linux 设备树
一、什么是设备树? 设备树(Device Tree),描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息: 树的主干就是系统总线, IIC 控制器、 GPIO 控制…...
万宾科技管网水位监测助力智慧城市的排水系统
以往如果要了解城市地下排水管网的水位变化,需要依靠人工巡检或者排查的方式,这不仅加大了人员的工作量,而且也为市政府带来了更多的工作难题。比如人员监管监测不到位或无法远程监控等情况,都会降低市政府对排水管网的管理能力&a…...
Glide transform CircleCrop()圆图,Kotlin
Glide transform CircleCrop()圆图,Kotlin import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.load.resource.bitmap.CircleCropclass MainActivity : AppCompatActivity() {o…...
从NetSuite Payment Link杂谈财务自动化、数字化转型
最近在进行信息化的理论学习,让我有机会跳开软件功能,用更加宏大的视野,来审视我们在哪里,我们要到哪去。 在过去20多年,我们的财务软件经历了电算化、网络化、目前处于自动化、智能化阶段。从NetSuite这几年的功能发…...
1.UML面向对象类图和关系
文章目录 4种静态结构图类图类的表示类与类之间的关系依赖关系(Dependency)关联关系(Association)聚合(Aggregation)组合(Composition)实现(Realization)继承/泛化(Inheritance/Generalization)常用的UML工具reference欢迎访问个人网络日志🌹🌹知行空间🌹🌹 4种静态结构…...
JAVA小说小程序系统是怎样开发的
随着移动互联网的普及,小说阅读已经成为人们休闲娱乐的重要方式之一。为了满足广大读者的需求,我们开发了一款基于JAVA编程语言的小说小程序系统。本系统旨在提供一种便捷、高效、有趣的阅读体验,让用户能够随时随地阅读最新、最热门的小说。…...
【深度学习】pytorch——Tensor(张量)详解
笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~ pytorch——Tensor 简介创建Tensortorch.Tensor( )和torch.tensor( )的区别torch.Tensor( )torch.tensor( ) tensor可以是一个数(标量)、一维数组(向量)、二维数组&…...
装修服务预约小程序的内容如何
大小装修不断,市场中大小品牌也比较多,对需求客户来说,可以线下咨询也可以线上寻找品牌,总是可以找到满意的服务公司,而对装修公司来说如今线下流量匮乏,很多东西也难以通过线下方式承载,更需要…...
easypoi 导出Excel 使用总结
easypoi 导出Excel 导出Excel需要设置标题,且标题是多行,标题下面是列表头 设置表格标题 ExportParams headExportParams new ExportParams();StringBuilder buffer new StringBuilder("");buffer.append("1、课程名称:....…...
MySQL性能优化的最佳20条经验
概述 关于数据库的性能,这并不只是DBA才需要担心的事。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。下面讲下MySQL性能优化的一些点。 1. 为查询缓存优化你的查询 大多数的MySQL服务器…...
【Liunx基础】之指令(一)
【Liunx基础】之指令(一) 1.ls指令2.pwd命令3.cd指令4.touch指令5.mkdir指令(重要)6.rmdir指令与rm指令(重要)7.man指令(重要)8.cp指令(重要) 📃博客主页: 小…...
jQuery案例专题
jQuery案例专题 本学期主要担任的课程是js和jQuery,感觉用到的有一些案例挺有意思的,就对其进行了一下整理。 目录: 电影院的幕帘特效 手风琴特效 星光闪烁 网页轮播图 1.电影院的幕帘特效代码如下 html <!DOCTYPE html > <html…...
【Linux】服务器间免登陆访问
准备两台服务器,服务器A,服务器B 在服务器A中实现免登陆服务器B 进入服务器A操作 进入目录/root/.ssh cd /root/.ssh秘钥对使用默认文件名 生成秘钥对,在输入秘钥文件时直接回车则会使用默认文件名:id_rsa ssh-keygen -t rsa…...
【信息安全原理】——IP及路由安全(学习笔记)
目录 🕒 1. IPv4协议及其安全性分析🕒 2. IPsec(IP Security)🕘 2.1 IPsec安全策略🕤 2.1.1 安全关联(Security Association, SA)🕤 2.1.2 安全策略(Security…...
【jvm】虚拟机之本地方法栈
目录 一、说明二、注意 一、说明 1. Java虚拟机栈用于管理Javaj法的调用,而本地方法栈用于管理本地方法的调用。 2. 本地方法栈,也是线程私有的。 3. 允许被实现成固定或者是可动态扩展的内存大小。 (在内存溢出方面是相同) 4. 如果线程请求分…...
『CV学习笔记』图像超分辨率等图像处理任务中的评价指标PSNR(峰值信噪比)
图像超分辨率等图像处理任务中的评价指标PSNR(峰值信噪比) 文章目录 一. PSNR(峰值信噪比)1.1. 定义1.2. 作用1.3. 例子1.4 . PSNR评价标准二. 参考文献一. PSNR(峰值信噪比) 1.1. 定义 峰值信噪比(Peak Signal-to-Noise Ratio, PSNR)是图像超分辨率等图像处理任务中常用的一…...
【51nod 连续区间】 题解(序列分治)
题目描述 区间内的元素元素排序后 任意相邻两个元素值差为 1 1 1 的区间称为“连续区间”。 如 3 , 1 , 2 3,1,2 3,1,2 是连续区间, 3 , 1 , 4 3,1,4 3,1,4 不是连续区间。 给出一个 1 ∼ n 1 \sim n 1∼n 的排列,问有多少连续区间。 …...
10.30校招 实习 内推 面经
绿*泡*泡: neituijunsir 交流裙 ,内推/实习/校招汇总表格 1、校招|极目智能2024届校招 校招|极目智能2024届校招 2、校招|杭州极弱磁场国家重大科技基础设施研究院2024秋季校园招聘正式启动! 校招&…...
相比typescript,python的动态类型有什么优缺点?
以下是Python的动态类型相对于TypeScript的静态类型的一些优缺点: 1、Python的动态类型优点: 更灵活:Python的动态类型允许你在运行时更灵活地改变变量的类型,这对于快速原型设计和快速开发非常有帮助。 代码更简洁:…...
高效处理文件:批量顺序编号重命名方法
每个人都面临着文件管理的挑战,特别是那些需要处理大量文件的人。如何高效地管理这些文件一直是一个难题。为了解决这个问题,我向大家推荐一款强大的文件管理工具——固乔文件管家。这个工具可以帮助你快速有效地给文件进行批量重命名和编号,…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
