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 操作函数。

总结:设备树就是查询板子信息的,我们驱动开发所需要的信息都会在上面,比如设备名字、设备地址之类的都在里面,简而言之,设备树其实就是类似于查询手册一样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/231894.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

飞天使-jumpserver-docker跳板机安装

文章目录 jumpserverdocker 更新到最新下载安装包mysql启动mysql 命令 验证字符集,创建数据库使用jumpserver 进行连接测试 redis部署jumpserver 写入变量建jumpserver 容器正确输出登录验证 jumpserver 基础要求 硬件配置: 2 个 CPU 核心, 4G 内存, 50G 硬盘&#xff08;最低…

【改进YOLOv8】桑叶病害检测系统:减少通道的空间对象注意力RCS-OSA改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义&#xff1a; 随着农业科技的不断发展&#xff0c;农作物病害的快速检测和准确诊断成为了农业生产中的重要问题。其中&#xff0c;桑叶病害对于桑树的生长和产量具…

电脑监测微信聊天记录丨用黑科技能查到别人聊天记录吗

最近有企业网管来咨询我们&#xff0c;用什么黑科技可以查看到别人的聊天记录吗&#xff1f; 先说答案吧&#xff1a;是可以的 下面是一位访客咨询我们的记录↓ 2023年都要结束了&#xff0c;电脑监测微信聊天记录也已经不再是什么稀奇的事情了。在市面上这样的软件也很普遍了…

pytorch——豆瓣读书评价分析

任务目标 基于给定数据集&#xff0c;采用三层bp神经网络方法&#xff0c;编写程序并构建分类模型&#xff0c;通过给定特征实现预测的书籍评分的模型。 选取数据 在各项指标中&#xff0c;我认为书籍的评分和出版社、评论数量还有作者相关&#xff0c;和其他属性的关系并大。…

MongoDB的数据库引用

本文主要介绍MongoDB的数据库引用。 目录 MongoDB的数据库引用 MongoDB的数据库引用 MongoDB是一种面向文档的NoSQL数据库&#xff0c;它使用BSON&#xff08;Binary JSON&#xff09;格式存储和查询数据。在MongoDB中&#xff0c;数据库引用是一种特殊的数据类型&#xff0c;…

利用gradio快速搭建AI应用

引言 Gradio 是一个用于快速创建交互式界面的Python库&#xff0c;这些界面可以用于演示和测试机器学习模型。使用Gradio&#xff0c;开发者可以非常轻松地为他们的模型构建一个前端界面&#xff0c;而不需要任何Web开发经验。 与类似产品的对比 TensorBoard&#xff1a;主…

【Python从入门到进阶】44、Scrapy的基本介绍和安装

接上篇《43.验证码识别工具结合requests的使用》 上一篇我们学习了如何使用验证码识别工具进行登录验证的自动识别。本篇我们开启一个新的章节&#xff0c;来学习一下快速、高层次的屏幕抓取和web抓取框架Scrapy。 一、Scrapy框架的背景和特点 Scrapy框架是一个为了爬取网站数…

C++内存布局

温故而知新&#xff0c;本文浅聊和回顾下C内存布局的知识。 一、c内存布局 C的内存布局主要包括以下几个部分&#xff1a; 代码段&#xff1a;存储程序的机器代码。.数据段&#xff1a;存储全局变量和静态变量。数据段又分为初始化数据段&#xff08;存储初始化的全局变量和…

python与机器学习2,激活函数

目录 1 什么是激活函数&#xff1f; activation function 1.1 阈值 1.2 激活函数a(x) &#xff0c;包含偏置值θ 1.3 激活函数a(x) &#xff0c;包含偏置值b 2 激活函数1: 单位阶跃函数 2.1 函数形式 2.2 函数图形 2.3 函数特点 2.4 代码实现这个 单位阶跃函数 3 激活…

Convolutional Neural Network(CNN)——卷积神经网络

1.NN的局限性 拓展性差 NN的计算量大性能差&#xff0c;不利于在不同规模的数据集上有效运行若输入维度发生变化&#xff0c;需要修改并重新训练网络容易过拟合 全连接导致参数量特别多&#xff0c;容易过拟合如果增加更多层&#xff0c;参数量会翻倍无法有效利用局部特征 输入…

结构型设计模式(三)享元模式 代理模式 桥接模式

享元模式 Flyweight 1、什么是享元模式 享元模式的核心思想是共享对象&#xff0c;即通过尽可能多地共享相似对象来减少内存占用或计算开销。这意味着相同或相似的对象在内存中只存在一个共享实例。 2、为什么使用享元模式 减少内存使用&#xff1a;通过共享相似对象&#…

汽车UDS诊断——SecureDataTransmission 加密数据传输(0x84)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍诊断和通讯管理功能单元下的84服务SecureDataTransmission,在常规诊断通信中,数据极易被第三方获取,所以在一些特殊的数据传输时,标准定义了加密数据传输的服务。 简而言之,就是在发送诊断数据时,发送方先把数…

fragstats:景观指数的时间序列分析框架

作者&#xff1a;CSDN _养乐多_ 本文将介绍景观指数的时间序列分析计算的软件使用方法和 python 代码&#xff0c;该框架可用于分析景观指数时间序列图像的趋势分析、突变分析、机器学习&#xff08;分类/聚类/回归&#xff09;、相关性分析、周期分析等方面。 文章目录 一、…

智能优化算法应用:基于人工电场算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工电场算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工电场算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工电场算法4.实验参数设定5.算法结果6.…

04-Revision和流量管理

1 Revision 关于Revision 应用程序代码及相关容器配置某个版本的不可变快照KService上的spec.template的每次变动&#xff0c;都会自动生成一个新的Revision通常不需要手动创建及维护 Revision的使用场景 将流量切分至不同版本的应用程序间&#xff08;Canary Deployment、Blu…

静态路由及动态路由

文章目录 静态路由及动态路由一、静态路由基础1. 静态路由配置2. 负载分担3. 路由备份4. 缺省路由5. 静态路由实操 二、RIP 动态路由协议1. RIP 协议概述2. RIP 协议版本对比2.1 有类路由及无类路由 3. RIP 路由协议原理4. RIP 计时器5. 度量值6. 收敛7. 示例 静态路由及动态路…

Kafka基本原理及使用

目录 基本概念 单机版 环境准备 基本命令使用 集群版 消息模型 成员组成 1. Topic&#xff08;主题&#xff09;&#xff1a; 2. Partition&#xff08;分区&#xff09;&#xff1a; 3. Producer&#xff08;生产者&#xff09;&#xff1a; 4. Consumer&#xff08;…

使用TensorRT对Yolov5进行部署【基于Python】

如果还未配置TensorRT&#xff0c;请看这篇博文&#xff1a;Win11下TensorRT环境部署 这里使用TensorRT对Yolov5进行部署流程比较固定&#xff1a;先将pt模型转换为onnx&#xff0c;再将onnx模型转为engine&#xff0c;所以在执行export.py时要将onnx、engine给到include。 P…

Linear Regression线性回归(一元、多元)

目录 介绍&#xff1a; 一、一元线性回归 1.1数据处理 1.2建模 二、多元线性回归 2.1数据处理 2.2数据分为训练集和测试集 2.3建模 介绍&#xff1a; 线性回归是一种用于预测数值输出的统计分析方法。它通过建立自变量&#xff08;也称为特征变量&#xff09;和因变…

【Redis】五、Redis持久化、RDB和AOF

文章目录 Redis持久化一、RDB&#xff08;Redis DataBase&#xff09;触发机制如何恢复rdb文件 二、AOF&#xff08;Append Only File&#xff09;三、扩展 Redis持久化 面试和工作&#xff0c;持久化都是重点&#xff01; Redis 是内存数据库&#xff0c;如果不将内存中的数据…