Linux如何在设备树中表示和引用设备信息

news2025/3/17 14:46:57

DTS基本知识

dts

硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts,一般在Linux源码中存在大量的dts文件,对于arm架构可以在arch/arm/boot/dts找到相应的dts,一个dts文件对应一个ARM的machie。

dtsi

值得一提的是,对于一些相同的dts配置可以抽象到dtsi文件中,然后类似于C语言的方式可以include到dts文件中,对于同一个节点的设置情况,dts中的配置会覆盖dtsi中的配置。

dtc

dtc是编译dts的工具,可以在Ubuntu系统上通过指令apt-get install device-tree-compiler安装dtc工具,不过在内核源码scripts/dtc路径下已经包含了dtc工具;

scripts/dtc/Makefile 文件内容如下:

示例代码 43.2.1 scripts/dtc/Makefile 文件代码段
1 hostprogs-y := dtc
2 always := $(hostprogs-y)
3
4 dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
5 srcpos.o checks.o util.o
6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o
......

可以看出,DTC 工具依赖于 dtc.c、flattree.c、fstree.c 等文件,最终编译并链接出 DTC 这个主机文件。如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:

make all

或者:

make dtbs

“make all”命令是编译 Linux 源码中的所有东西,包括 zImage,.ko 驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令。

dtb

dtb(Device Tree Blob),dts经过dtc编译之后会得到dtb文件,dtb通过Bootloader引导程序加载到内核。所以Bootloader需要支持设备树才行;Kernel也需要加入设备树的支持;

基于 ARM 架构的 SOC 有很多种,一种 SOC 又可以制作出很多款板子,每个板子都有一个对应的 DTS 文件,那么如何确定编译哪一个 DTS 文件呢?我们就以 I.MX6ULL 这款芯片对应的板子为例来看一下,打开 arch/arm/boot/dts/Makefile,有如下内容:

示例代码 43.2.2 arch/arm/boot/dts/Makefile 文件代码段
381 dtb-$(CONFIG_SOC_IMX6UL) += \
382 imx6ul-14x14-ddr3-arm2.dtb \
383 imx6ul-14x14-ddr3-arm2-emmc.dtb \
......
400 dtb-$(CONFIG_SOC_IMX6ULL) += \
401 imx6ull-14x14-ddr3-arm2.dtb \
402 imx6ull-14x14-ddr3-arm2-adc.dtb \
403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \
404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \
405 imx6ull-14x14-ddr3-arm2-emmc.dtb \
406 imx6ull-14x14-ddr3-arm2-epdc.dtb \
407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
409 imx6ull-14x14-ddr3-arm2-lcdif.dtb \
410 imx6ull-14x14-ddr3-arm2-ldo.dtb \
411 imx6ull-14x14-ddr3-arm2-qspi.dtb \
412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
413 imx6ull-14x14-ddr3-arm2-tsc.dtb \
414 imx6ull-14x14-ddr3-arm2-uart2.dtb \
415 imx6ull-14x14-ddr3-arm2-usb.dtb \
416 imx6ull-14x14-ddr3-arm2-wm8958.dtb \
417 imx6ull-14x14-evk.dtb \
418 imx6ull-14x14-evk-btwifi.dtb \
419 imx6ull-14x14-evk-emmc.dtb \
420 imx6ull-14x14-evk-gpmi-weim.dtb \
421 imx6ull-14x14-evk-usb-certi.dtb \
422 imx6ull-alientek-emmc.dtb \
423 imx6ull-alientek-nand.dtb \
424 imx6ull-9x9-evk.dtb \
425 imx6ull-9x9-evk-btwifi.dtb \
426 imx6ull-9x9-evk-ldo.dtb
427 dtb-$(CONFIG_SOC_IMX6SLL) += \
428 imx6sll-lpddr2-arm2.dtb \
429 imx6sll-lpddr3-arm2.dtb \
......

可以看出,当选中 I.MX6ULL 这个 SOC 以后(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb。如果我们使用 I.MX6ULL 新做了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb文件。示例代码 43.2.2 中第 422 和 423 行就是我们在给正点原子的 I.MX6U-ALPHA 开发板移植Linux 系统的时候添加的设备树。

DTS 语法 

虽然我们基本上不会从头到尾重写一个.dts 文件,大多时候是直接在 SOC 厂商提供的.dts文件上进行修改。但是 DTS 文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts文件。大家不要看到要学习新的语法就觉得会很复杂,DTS 语法非常的人性化,是一种 ASCII文本文件,不管是阅读还是修改都很方便。本节我们就以 imx6ull-alientek-emmc.dts 这个文件为例来讲解一下 DTS 语法。

dtb文件是由booloader解析还是kernel解析?

DTB文件主要是由内核解析的,但Bootloader也在一定程度上参与处理DTB文件。以下是对这一过程的详细解释:

Bootloader对DTB文件的处理

加载DTB到内存:Bootloader的主要职责之一是在系统启动初期将操作系统内核和相关资源加载到内存中。在这个过程中,如果存在DTB文件(通常是由设备树源文件编译而来的二进制文件),Bootloader会将其加载到内存中的特定位置。例如,在U-Boot这样的Bootloader中,会将DTB文件存储在内存的某个预定义区域,以便后续内核能够找到并使用它。

告知内核DTB位置:Bootloader在将控制权交给内核之前,需要将DTB文件在内存中的地址或其他相关信息传递给内核。这样,内核在启动时就能够知道从哪里找到DTB文件,并开始对其进行解析。

内核对DTB文件的解析

验证DTB文件格式:内核首先会对加载的DTB文件进行格式验证,检查其头部信息是否正确,包括魔数、版本号等字段,以确保这是一个有效的设备树描述文件。

解析节点和属性:如果DTB文件格式正确,内核会进一步解析其中的节点和属性信息。节点代表了硬件设备或组件,而属性则描述了这些节点的各种特性和参数,如设备的型号、寄存器地址、中断号等。内核会将这些信息组织成内部的数据结构,以便后续的设备驱动程序能够方便地访问和使用。

创建设备模型:基于解析得到的设备树信息,内核会在内存中构建出整个系统的设备模型。这个设备模型反映了系统中所有硬件设备的层次结构和连接关系,为内核的设备管理和驱动程序的加载提供了基础。

综上所述,虽然Bootloader参与了DTB文件的加载和传递工作,但真正对DTB文件进行深入解析和利用的是Linux内核。通过解析DTB文件,内核能够获取硬件设备的信息,从而正确地初始化和管理这些设备,确保系统能够正常运行。

设备树dtb被解析后是存在内存里还是文件系统里?

设备树DTB文件在解析后主要存在于内存中,但也可能与文件系统存在一定关联。以下是对这一问题的详细解释:

内存中的存在形式

作为内核数据结构:设备树DTB文件在被内核解析后,其内容会被转化为一系列的内核数据结构,如device_nodeproperty等。这些数据结构存储在内存中,用于描述系统中的设备及其属性和配置信息。

内存中的保留区域:在某些情况下,设备树DTB文件可能会被加载到内存中的特定区域,以便内核能够直接访问和解析。例如,在ARM架构的系统中,设备树DTB文件通常会被加载到内存的起始地址处。

文件系统中的存在形式

/sys文件系统:内核会在/sys文件系统的/sys/firmware/fdt/目录下创建设备树的相关信息,包括设备树的字符串表(strings)和结构体(structure)部分。这些信息以文件的形式存在,用户可以通过读取这些文件来查看设备树的内容。

/proc文件系统:虽然/proc文件系统主要用于提供内核运行时的信息,但在某些情况下,它也可以用来查看与设备树相关的信息。例如,通过访问/proc/device-tree目录,可以获取到设备树的原始内容或经过处理后的信息。

综上所述,设备树DTB文件在解析后主要存在于内存中,但也可能与文件系统(如/sys和/proc)存在一定的关联,以便用户和开发者能够查看和管理设备树的内容。这种设计既保证了设备树信息的高效使用,又提供了方便的接口供外部访问和调试。

.dtsi 头文件

和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在 imx6ull-alientek

emmc.dts 中有如下所示内容:

示例代码 43.3.1.1 imx6ull-alientek-emmc.dts 文件代码段
12 #include <dt-bindings/input/input.h>
13 #include "imx6ull.dtsi"

第 12 行,使用“#include”来引用“input.h”这个.h 头文件。

第 13 行,使用“#include”来引用“imx6ull.dtsi”这个.dtsi 头文件。

看到这里,大家可能会疑惑,不是说设备树的扩展名是.dtsi 吗?为什么也可以直接引用 C语言中的.h 头文件呢?这里并没有错,.dts 文件引用 C 语言中的.h 文件,甚至也可以引用.dts 文件,打开 imx6ull-14x14-evk-gpmi-weim.dts 这个文件,此文件中有如下内容:

示例代码 43.3.1.2 imx6ull-14x14-evk-gpmi-weim.dts 文件代码段

#include "imx6ull-14x14-evk.dts"

可以看出,示例代码 43.3.1.2 中直接引用了.dts 文件,因此在.dts 设备树文件中,可以通过“#include”来引用.h、.dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。

一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息的,内容如下:

示例代码 43.3.1.3 中第 54~89 行就是 cpu0 这个设备节点信息,这个节点信息描述了I.MX6ULL 这颗 SOC 所使用的 CPU 信息,比如架构是 cortex-A7,频率支持 996MHz、792MHz、528MHz、396MHz 和 198MHz 等等。在 imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息,I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚,比如 ecspi1~4、uart1~8、usbphy1~2、i2c1~4等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从imx6ull.dtsi 文件中缩减出来的设备树文件内容:

第 1 行,“/”是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现,imx6ull.dtsi和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为这两个“/”根节点的内容会合并成一个根节点。

dtsi中的根结点会和包含它的dts文件中的根结点冲突吗?

在Linux内核中,设备树(Device Tree)是一种用于描述硬件拓扑结构的数据结构。设备树通常由多个文件组成,包括.dts(Device Tree Source)文件和.dtsi(Device Tree Source Include)文件。这些文件最终会被编译成一个设备树二进制文件(Device Tree Blob,简称DTB),供内核在启动时使用。

关于DTSI中的根结点是否会和包含它的DTS文件中的根结点冲突,实际上并不会冲突。原因如下:

  1. 编译时的节点合并:当包含DTSI文件的DTS文件被编译成DTB时,编译器会将DTSI中的节点合并到DTS中的相应位置。这意味着,尽管在源代码层面看起来DTSI和DTS各自有一个根结点,但在最终编译生成的设备树中,这些节点会被合并成一个统一的树状结构。

  2. 单一的根节点:设备树本质上是一棵树,因此它只能有一个根节点。在编译过程中,所有的节点都会被整合到这个单一的根节点下,形成一个完整的设备树结构。

综上所述,DTSI中的根结点不会与包含它的DTS文件中的根结点冲突。在设备树的编译和链接过程中,这些节点会被适当地合并和管理,以确保最终生成的设备树具有一致且正确的结构。

dts中的根结点会和包含它的dts文件中的根结点冲突吗?

在Linux中,DTS中的根结点和包含它的DTS文件中的根结点实际上不会冲突

在一个DTS文件中,使用#include语句可以引用其他文件(通常是.dtsi文件),这些被引用的文件可以看作是对当前DTS文件的扩展或补充。当编译器处理这种包含关系时,它会将被包含的内容合并到主DTS文件中,从而形成一个完整的设备树结构。在这个过程中,并不会因为包含操作而产生多个根结点,最终生成的设备树仍然只有一个根结点。

其实,只要最终能保证层级关系不乱就行了,这还是很容易做到的。

第 2、6 和 17 行,aliases、cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:

node-name@unit-address

其中“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是 UART1 外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。

但是我们在示例代码 43.3.2.1 中我们看到的节点命名却如下所示:

cpu0:cpu@0

上述命令并不是“node-name@unit-address”这样的格式,而是用“:”隔开成了两部分,“:”前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示:

label: node-name@unit-address

引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “intc:interrupt-controller@00a01000”,节点 label 是 intc,而节点名字就很长了,为“interrupt-controller@00a01000”。很明显通过&intc 来访问“interrupt-controller@00a01000”这个节点要方便很多!

第 10 行,cpu0 也是一个节点,只是 cpu0 是 cpus 的子节点。

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:

①、字符串

compatible = "arm,cortex-a7";

上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。

②、32 位无符号整数

reg = <0>;

上述代码设置 reg 属性的值为 0,reg 的值也可以设置为一组值,比如:

reg = <0 0x123456 100>;

③、字符串列表

属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:

compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。

compatible 属性

compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性!compatible 属性的值是一个字符串列表,compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible 属性的值格式如下所示:

"manufacturer,model"

其中manufacturer 表示厂商,model一般是模块对应的驱动名字。比如imx6ull-alientek-emmc.dts 中sound 节点是 I.MX6U-ALPHA 开发板的音频设备节点,I.MX6U-ALPHA 开发板上的音频芯片采用的欧胜(WOLFSON)出品的 WM8960,sound 节点的 compatible 属性值如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

属性值有两个,分别为“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。sound这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。

一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。比如在文件 imx-wm8960.c 中有如下内容:

示例代码 43.3.3.1 imx-wm8960.c 文件代码段
632 static const struct of_device_id imx_wm8960_dt_ids[] = {
633     { .compatible = "fsl,imx-audio-wm8960", },
634     { /* sentinel */ }
635 };
636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
637
638 static struct platform_driver imx_wm8960_driver = {
639     .driver = {
640         .name = "imx-wm8960",
641         .pm = &snd_soc_pm_ops,
642         .of_match_table = imx_wm8960_dt_ids,
643     },
644     .probe = imx_wm8960_probe,
645     .remove = imx_wm8960_remove,
646 };

第 632~635 行的数组 imx_wm8960_dt_ids 就是 imx-wm8960.c 这个驱动文件的匹配表,此匹配表只有一个匹配值“fsl,imx-audio-wm8960”。如果在设备树中有哪个节点的 compatible 属性值与此相等,那么这个节点就会使用此驱动文件。

第 642 行,wm8960 采用了 platform_driver 驱动模式,关于 platform_driver 驱动后面会讲解。此行设置.of_match_table 为 imx_wm8960_dt_ids,也就是设置这个 platform_driver 所使用的OF 匹配表。

model 属性

model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:

model = "wm8960-audio";

status 属性

status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,可选的状态如表 43.3.3.1 所示:

#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 这个数据所占用的字长,比如:

第 3,4 行,节点 spi4 的#address-cells = <1>,#size-cells = <0>,说明 spi4 的子节点 reg 属性中起始地址所占用的字长为 1,地址长度所占用的字长为 0。

第 8 行,子节点 gpio_spi: gpio_spi@0 的 reg 属性值为 <0>,因为父节点设置了#address cells = <1>,#size-cells = <0>,因此 addres=0,没有 length 的值,相当于设置了起始地址,而没有设置地址长度。

第 14,15 行,设置 aips3: aips-bus@02200000 节点#address-cells = <1>,#size-cells = <1>,说明 aips3: aips-bus@02200000 节点起始地址长度所占用的字长为 1,地址长度所占用的字长也为 1。

第 19 行,子节点 dcp: dcp@02280000 的 reg 属性值为<0x02280000 0x4000>,因为父节点设置了#address-cells = <1>,#size-cells = <1>,address= 0x02280000,length= 0x4000,相当于设置了起始地址为 0x02280000,地址长度为 0x40000。

不太明白,地址和长度不都是1个字长吗?难道还有超过1个字长的地址或者长度?

参考:终于搞懂Linux 设备树中的#address-cells,#size-cells 和reg 属性-CSDN博客

再看个例子:

  alphaled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-led";
    status = "okay";
    reg = < 0X020C406C 0X04    /* CCM_CCGR1_BAE       */
        0X020E0068 0X04    /* SW_MUX_GPIO1_IO03_BASE   */
        0X020E02F4 0X04    /* SW_PAD_GPIO1_IO03_BASE  */
        0X0209C000 0X04    /* GPIO1_DR_BASE       */
        0X0209C004 0X04>;  /* GPIO1_GDIR_BASE       */
  };

这里address-cells = 1,size-cells = 1 , 表示 reg属性中,地址信息的长度是1个字长,地址长度信息也是1个字长。

可见,大部分情况下,都是1个字长。

有没有不是1个字长的?

比如:

pci@1,0 {
    #address-cells = <3>;
    #size-cells = <2>;
    compatible = "intel,ce4100-pci", "pci";
    device_type = "pci";
    bus-range = <1 1>;
    reg = <0x0800 0x0 0x0 0x0 0x0>;
    ...
}

这里address-cells = 3,size-cells = 2 , 表示 reg属性中,地址信息的长度是3个字长,地址长度信息是2个字长,即是:address = 0x0800 0x0 0x0, length = 0x00 0x00,具体含义具体对待。此时,地址和长度均无法通过1个字长来描述。

在设备树(Device Tree)中,#size-cells 前面的 符号具有特定的含义和作用。以下是对其的解释:

属性标识作用

区分普通属性名与特殊属性:在设备树的语法规则中,以 # 开头的属性通常具有特殊的意义或用途,与常规的属性名有所区别。这种标识方式能够让设备树的解析器快速识别和正确处理这些特殊属性,避免与普通属性产生混淆。例如,#size-cells 表示该属性是用于描述节点地址空间中尺寸信息的特殊属性,而普通的属性如 compatiblemodel 等则用于描述设备的兼容性、型号等信息。

明确属性语义类别# 符号的使用有助于将与地址、大小相关的属性归类在一起,方便管理和理解。像 #address-cells#size-cells 都属于这类描述节点地址信息的属性,通过 # 符号的统一标识,可以清晰地表明它们在语义上的关联性和特殊性。

reg 属性

reg 属性前面已经提到过了,reg 属性的值一般是(address,length)对。reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息,比如在 imx6ull.dtsi 中有如下内容:

上述代码是节点 uart1,uart1 节点描述了 I.MX6ULL 的 UART1 相关信息,重点是第 326 行的 reg 属性。其中 uart1 的父节点 aips1: aips-bus@02000000 设置了#address-cells = <1>、#sizecells = <1>,因此 reg 属性中 address=0x02020000,length=0x4000。查阅《I.MX6ULL 参考手册》可知,I.MX6ULL 的 UART1 寄存器首地址为 0x02020000,但是 UART1 的地址长度(范围)并没有 0x4000 这么多,这里我们重点是获取 UART1 寄存器首地址。

进一步思考。

假设reg里的数据是这样的

reg = <0x08000000 0x4000 0x08006000 0x5000 0x0800a000 0x4000>

你知道这里面哪些是地址哪些是长度吗?只知道用空格隔开了一些数据。而且,就算我们能从中看出来哪个是长度哪个是地址,但计算机怎判断呢?这就要我们告诉计算机,地址和长度所占的数据个数是怎么样的。

假设

#address-cells = <1>;

#size-cells = <1>;

那么上述6个数的顺序就是:<地址 长度 地址 长度 地址 长度>

假设

#address-cells = <1>;

#size-cells = <0>;

那么上述6个数的顺序就是:<地址 地址 地址 地址 地址 地址>,即没有哪个数据表示长度了

假设

#address-cells = <3>;

#size-cells = <3>;

那么上述6个数的顺序就是:<地址 长度>,前面三个数都表示地址,后面三个数都表示长度。

只是,一般都是

#address-cells = <1>;

#size-cells = <0>;

或者

#address-cells = <1>;

#size-cells = <1>;

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 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 I.MX6ULL 来说,子地址空间和父地址空间完全相同,因此会在imx6ull.dtsi中找到大量的值为空的 ranges 属性,如下所示:

第 142 行定义了 ranges 属性,但是 ranges 属性值为空。

ranges 属性不为空的示例代码如下所示:

第 5 行,节点 soc 定义的 ranges 属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。

第 10 行,serial 是串口设备节点,reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,

寄存器长度为 0x100。经过地址转换,serial 设备可以从 0xe0004600 开始进行读写操作,

0xe0004600=0x4600+0xe0000000。

name 属性

name 属性值为字符串,name 属性用于记录节点名字,name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。

device_type 属性

device_type 属性值为字符串,IEEE 1275 会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。imx6ull.dtsi 的 cpu0 节点用到了此属性,内容如下所示:

在Linux的设备树(Device Tree)中,device_type属性是一个用于标识设备节点类型的字符串类型属性。以下是关于device_type属性的详细解释:

  1. 基本概念

    1. device_type属性用于描述设备树中各个设备节点的具体类型,它为内核或用户空间提供了一种识别和区分不同设备的机制。通过这个属性,系统可以知道某个节点所代表的是CPU、内存、I2C设备、SPI设备还是其他类型的设备。

  2. 常见取值

    1. CPU相关:对于CPU设备节点,device_type通常被设置为“cpu”,以明确表示该节点是一个CPU设备。

    2. 内存相关:内存设备节点的device_type一般被设置为“memory”,用于标识内存设备。

    3. 存储设备:如MMC(MultiMediaCard)设备节点,其device_type可能被设置为“mmc”,以表明这是一个MMC存储设备。

    4. 通信接口设备:例如I2C设备节点的device_type可以是“i2c”,SPI设备节点的device_type可以是“spi”,USB设备节点的device_type可以是“usb”等,这些设置有助于驱动程序正确地识别和处理相应的通信接口设备。

    5. 网络设备:以太网设备节点的device_type可能被设置为“ethernet”,从而让系统能够识别以太网相关的设备。

    6. 其他外设设备:像GPIO设备节点的device_type可以被设置为“gpio”,PWM设备节点的device_type可以是“pwm”等,以便系统对各种外设进行准确的识别和管理。

  3. 作用与意义

    1. 设备识别与驱动绑定:内核在启动或运行时会读取设备树中的信息,根据device_type属性来确定每个设备节点的类型,并将相应的设备与对应的驱动程序进行绑定。这样,当系统需要与某个设备进行交互时,就能够找到正确的驱动程序来操作该设备。

    2. 硬件资源管理:通过明确设备的类型,系统可以更好地管理和分配硬件资源。例如,对于不同类型的设备,系统可以根据其特性和需求分配适当的内存、中断等资源,以确保设备的正常运行。

    3. 兼容性与扩展性:使用device_type属性可以提高系统的兼容性和扩展性。在添加新的设备类型时,只需要在设备树中为新设备节点设置正确的device_type属性,并编写相应的驱动程序即可,无需对系统的核心代码进行大规模的修改。

综上所述,device_type属性在Linux的设备树中扮演着至关重要的角色,它不仅帮助内核和系统准确地识别和区分各种设备节点,还促进了设备与驱动程序的正确绑定以及硬件资源的有效管理。

注意,名称并不是强制的,是自定义的,只不过一般很多名称都是约定俗成的而已。

关于标准属性就讲解这么多,其他的比如中断、IIC、SPI 等使用的标准属性等到具体的例程再讲解。

特殊节点

/aliases 子节点

aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。例如:定义 flexcan1 和 flexcan2 的别名是 can0 和 can1。

aliases {
    can0 = &flexcan1;
    can1 = &flexcan2;
};

单词 aliases 的意思是“别名”,因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。

/memory 子节点

所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。

例如:一个64位的系统有两块内存空间:RAM1: 起始地址是0x0,地址空间是 0x80000000;RAM2: 起始地址是0x10000000,地址空间也是0x80000000;同时根节点下的 #address-cells = <2>和#size-cells = <2>,这个memory节点描述为:

memory@0 {
    device_type = "memory";
    reg = <0x00000000 0x00000000 0x00000000 0x80000000 0x00000000 0x10000000 0x00000000 0x80000000>;
};

或者:

memory@0 {
    device_type = "memory";
    reg = <0x00000000 0x00000000 0x00000000 0x80000000>;
};

memory@10000000 {
    device_type = "memory";
    reg = <0x00000000 0x10000000 0x00000000 0x80000000>;
};

 更多待补充。

/chosen 子节点

chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。例如:

chosen {
    bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};

更多待补充。

在Linux设备树中,可以自定义属性。以下是关于自定义属性的定义和引用方法的详细解释:

自定义属性的定义

自定义属性允许用户根据特定硬件设备的特性和需求来定义属性名和属性值。这些属性在Linux内核和驱动程序中并不具有特殊的含义,而是由用户在设备树文件中定义,并在驱动程序中通过特定的API接口进行读取和处理。

自定义属性的引用

在设备树文件中定义自定义属性

  1. 自定义属性以键值对的形式存在,属性名由用户自定义,属性值可以是整数、字符串、布尔值等各种类型。例如:

&i2c1 {
  my-device@48 {
    compatible = "my-company,my-device";
    reg = <0x48>;
    example-config = <1 9600>; /* 工作模式为1,波特率为9600 */
  };
};

在这个例子中,example-config就是一个自定义属性,用于描述设备的特定配置选项。

在驱动程序中读取自定义属性

  1. 在Linux驱动程序中,可以使用如of_property_read_u32of_property_read_string等函数来读取设备树中的自定义属性。例如:

#include <linux>
static int my_driver_probe(struct platform_device *pdev) {
  struct device_node *np = pdev->dev.of_node;
  u32 example_config;
  int ret;
  /* 读取自定义属性example-config */
  ret = of_property_read_u32(np, "example-config", &example_config);
  if (ret) {
    dev_err(&pdev->dev, "Failed to read 'example-config' property");
    return ret;
  }
  /* 使用example_config进行设备配置 */
  // ...
  return 0;
}

在这个示例中,驱动程序在probe函数中通过of_property_read_u32函数读取了设备树中的自定义属性example-config,并根据该属性的值进行设备配置。

综上所述,Linux设备树允许用户自定义属性,并通过特定的API接口在驱动程序中读取和处理这些属性。这为用户提供了灵活的方式来描述硬件设备的特定特性和配置选项。

如何在设备树中表示和引用设备信息

先分析一个完整的设备树,是怎么表达各种外设信息的。

以imux6ull开发板为例进行说明。

待补充。。。

更多补充

中断结点

#interrupt-cells是一个整数类型的属性,用于指定在设备树的中断描述中使用多少个单元来表示一个中断。例如,如果#interrupt-cells的值为2,那么在描述每个中断时,需要使用2个单元的数据来表示相关信息。注意,不是对应的reg,具体对应着什么,暂时我也不知道,后面再看。

在设备树中,interrupt-controller属性通常是一个空属性,其作用是声明当前节点是一个中断控制器。通过设置interrupt-controller属性(即使为空),可以让设备树和系统清晰地知道该节点代表的是一个中断控制器设备。这有助于在复杂的硬件架构中快速识别和定位负责中断处理的设备,方便系统对其进行正确的配置和管理。在设备树中,存在各种不同类型的设备节点,如CPU、内存、I/O设备等。interrupt-controller属性为空可以作为一种简单的标识方式,将中断控制器与其他普通设备区分开来,避免混淆,确保系统能够准确地识别和处理中断相关的设备。

上面的#interrupt-cells应该对应的是子结点的interrupts属性的表示个数。

interrupts 属性是一个整数数组,用于指定与该设备相关联的中断请求信号的信息。每个元素通常包含两个主要部分信息,一是中断控制器的引用(通过 phandle 或设备树节点路径表示),二是中断编号或中断名称(具体取决于设备和平台的表示方式)。

例如,interrupts = <&intc 23 0x04>; 这个属性值中,&intc 是中断控制器的设备树节点标签,23 是中断编号,0x04 表示中断触发类型(如上升沿触发等)。

作用与意义

硬件中断配置:它告诉操作系统或硬件抽象层(HAL)当设备需要产生中断时,应该向哪个中断控制器发送信号,以及使用哪个中断号。这样,当设备发生特定事件(如数据接收、错误发生等)需要通知处理器时,处理器能够正确地响应并执行相应的中断服务程序。

系统资源分配和管理:系统可以根据 interrupts 属性的信息,为设备分配合适的中断处理资源,确保不同设备的中断请求能够得到及时、准确的处理,避免中断冲突和混乱。

驱动程序开发:对于设备驱动开发者来说,interrupts 属性提供了关键的信息,以便编写正确的中断处理代码。驱动程序需要根据这个属性的值来注册相应的中断处理函数,使设备能够正常工作。

常见属性值的含义

中断编号:如果 interrupts 属性值中只包含一个数字,那么这个数字通常代表中断编号。例如,在一些简单的系统中,interrupts = <5>; 表示设备使用中断编号为 5 的中断线。

中断名称:在某些情况下,可能会使用中断名称来代替中断编号。例如,interrupts = <"irq_name">; 这里的 "irq_name" 是在设备树中定义的一个中断名称标识符。这种方式可以使设备树更加易读和可维护,尤其是当中断编号较多且不易理解时。

复合值:有些复杂的设备可能会有更复杂的 interrupts 属性值,包含多个单元的复合值。这些复合值可能包括中断域、触发方式、CPU 接口信息等。例如,interrupts = <gic_ppi>; 这个属性值中,GIC_PPI 表示这是一个与通用中断控制器(GIC)相关的私有外设中断(PPI),37 是中断号,1 表示中断触发类型等信息。

总之,设备树中的interrupts属性至关重要,它不仅明确了设备的中断请求信号细节,还为系统资源分配、驱动程序开发提供了关键依据,确保了硬件中断的有效管理和设备的正常运行。

具体还要看程序里是怎么获取的。

#clock-cells

通过指定 #clock-cells 的值,可以明确子节点的时钟信息在设备树中应该如何被表示和解析。例如,如果 #clock-cells = <2>,则表示该节点下的子节点时钟信息将使用 2 个单元的数据来描述。这有助于系统准确地理解和处理不同节点的时钟配置信息,确保硬件设备的时钟设置能够被正确地识别和应用。

芯片上的外设通常都作为soc结点的子结点。

设备树的语法规定了属性值可以是整数、字符串、数组等多种形式。当属性值为数组时,尖括号用于标识数组的开始和结束。分开写尖括号并不影响设备树编译器对属性值的解析,因为编译器会按照设备树的语法规则正确处理这种写法。例如,对于一个包含多个数值的数组属性reg = <0x1000 0x2000 0x3000>;,无论是连续写还是分开写尖括号,如reg = <0x1000>,<0x2000>,<0x3000>;,中间以逗号或者空格隔开应该都可以,设备树编译器都能识别并将其解析为一个包含三个元素的数组。也可以分行写

reg = <0x1000>,

  <0x2000>,

  <0x3000>;

  比如:

gpio-controller

在设备树(Device Tree)中,gpio-controller 属性通常用于标识某个节点代表一个 GPIO(通用输入输出)控制器。这个属性告诉系统该节点下的硬件资源是与 GPIO 控制相关的,使得操作系统能够正确地识别和使用这些 GPIO 引脚。

使用pinctrl子系统时,我们通常都会将所有的pin引脚信息都放在一起;然后具体被哪个外设使用时,需要再在对应外设节点中引用pin信息。

目前还是没太搞明白,pin,gpio和具体外设之间的设备树的关系,所有的引脚都是pin,需要先设置好pin的复用功能、驱动能力以及上下拉等基本属性;如果是复用为gpio,那么就作为gpio这种外设功能;但是,gpio也需要依附其他外设来使用的。这里要搞清楚,gpio是一种片上外设,一般都定义在soc节点中吧?然后片外的外设,就不放在soc里面?这块还得再研究研究,捋一捋。

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

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

相关文章

Matlab 汽车振动多自由度非线性悬挂系统和参数研究

1、内容简介 略 Matlab 169-汽车振动多自由度非线性悬挂系统和参数研究 可以交流、咨询、答疑 2、内容说明 略 第二章 汽车模型建立 2.1 汽车悬架系统概述 2.1.1 悬架系统的结构和功能 2.1.2 悬架分类 2.2 四分之一车辆模型 对于车辆动力学&#xff0c;一般都是研究其悬…

生活中的可靠性小案例11:窗户把手断裂

窗户把手又断了&#xff0c;之前也断过一次&#xff0c;使用次数并没有特别多。上方的图是正常的把手状态&#xff0c;断的形状如下方图所示。 这种悬臂梁结构&#xff0c;没有一个良好的圆角过渡&#xff0c;导致应力集中。窗户的开关&#xff0c;对应的是把手的推拉&#xff…

[oeasy]python074_ai辅助编程_水果程序_fruits_apple_banana_加法_python之禅

074_ai辅助编程_水果程序_fruits_加法 回忆上次内容 上次直接从模块中导入变量、函数 from my_file import pi 导入my_file.pi 并作为 pi 使用 from my_file import pi as my_pi 导入变量 并 重命名 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; …

【图论】并查集的学习和使用

目录 并查集是什么&#xff1f; 举个例子 组成 父亲数组&#xff1a; find函数&#xff1a; union函数&#xff1a; 代码实现&#xff1a; fa[] 初始化code: find code&#xff1a; 递归实现: 非递归实现: union code : 画图模拟&#xff1a; 路径压缩&#xff1a…

欢乐力扣:反转链表

文章目录 1、题目描述2、思路 1、题目描述 反转链表。  给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 2、思路 借助cur指针和pre双指针来调整链表的前后指向。 # Definition for singly-linked list. # class ListNode: # def __i…

什么是大带宽服务器

什么是大带宽服务器&#xff1f; 在深入探讨大带宽之前&#xff0c;让我们先明确带宽的概念。带宽与我们日常所说的宽带有所不同&#xff0c;宽带是运营商为满足家庭或商业上网需求所提供的服务&#xff0c;而带宽则特指数据的传输速度&#xff0c;尤其是上行速度。大带宽服务…

【TCP】三次挥手,四次挥手详解--UDP和TCP协议详解

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…

SSM基础专项复习4——Maven项目管理工具(1)

系列文章 1、SSM基础专项复习1——SSM项目整合-CSDN博客 2、SSM基础专项复习2——Spring 框架&#xff08;1&#xff09;-CSDN博客 3、SSM基础专项复习3——Spring框架&#xff08;2&#xff09;-CSDN博客 文章目录 系列文章 1. Maven 的概念 1.1. 什么是 Maven 1.2. 什…

使用c#进行串口通信

一、串口通信协议 1.串口通信协议简介 串口通信&#xff08;serial communication&#xff09;是一种设备间非常常用的串行通信方式&#xff0c;大部分电子设备都支持&#xff0c;电子工程师再调试设备时也经常使用该通信方式输出调试信息。讲到某一种通信协议&#xff0c;离…

Web开发-PHP应用鉴别修复AI算法流量检测PHP.INI通用过滤内置函数

知识点&#xff1a; 1、安全开发-原生PHP-PHP.INI安全 2、安全开发-原生PHP-全局文件&单函数 3、安全开发-原生PHP-流量检测&AI算法 一、演示案例-WEB开发-修复方案-PHP.INI配置 文章参考&#xff1a; https://www.yisu.com/ask/28100386.html https://blog.csdn.net/…

蓝桥模拟+真题讲解

今天谁一篇文章哈 &#xff01; 由于本篇文章有些的题目只有图片&#xff0c;因此还望各位见谅。 目录 第一题 题目解析 代码原理 代码编写 填空技巧---巧用python 第二题 题目解析 ​编辑 填空技巧---巧用python 第三题 题目链接 题目解析 必备知识 解题技巧 …

C语言【数据结构】:时间复杂度和空间复杂度.详解

引言 详细介绍什么是时间复杂度和空间复杂度。 前言&#xff1a;为什么要学习时间复杂度和空间复杂度 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏&#xff0c;一般是从时间和空间两个维度来衡量的&#xff0c;即时…

基于Python的selenium入门超详细教程(第2章)--单元测试框架unittest

学习路线 自动化测试介绍及学习路线-CSDN博客 ​自动化测试之Web自动化&#xff08;基于pythonselenium&#xff09;-CSDN博客 基于Python的selenium入门超详细教程(第1章)--WebDriver API篇-CSDN博客 目录 前言&#xff1a; 一、单元测试 1. 单元测试的定义 2. 单元测…

日志、类加载器、XML(配置文件)

目录 一、日志1.日志技术的概述2.日志技术的体系a. Logback 3.日志的级别 二、类加载器1.概述2.类加载时机3.类加载过程3.类加载器的分类4.常用方法 三、XML&#xff08;配置文件&#xff09;1.概述2.XML的基本语法3.XML的文档约束a.DTD约束b.schema约束 4.XML文档解析a.Dom4jb…

AI大白话(一):5分钟了解AI到底是什么?

&#x1f31f;引言&#xff1a; 在这个信息爆炸的时代&#xff0c;“人工智能”、“AI”、“机器学习”、"深度学习"等词汇频繁出现在我们的生活中。 从手机里的语音助手&#xff0c;到网购平台的个性化推荐&#xff0c;再到最近大火的AI绘画和ChatGPT&#xff0c;人…

蓝桥与力扣刷题(蓝桥 字符统计)

题目&#xff1a;给定一个只包含大写字母的字符出 S, 请你输出其中出现次数最多的字符。如果有多个字母均出现了最多次, 按字母表顺序依次输出所有这些字母。 输入格式 一个只包含大写字母的字等串 S. 输出格式 若干个大写字母&#xff0c;代表答案。 样例输入 BABBACAC样…

AtCoder Beginner Contest 397(ABCDE)

目录 A - Thermometer 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; B - Ticket Gate Log 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; C - Variety Split Easy 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; D - Cubes 翻译&#xff1a…

Profinet转Profinet以创新网关模块为核心搭建西门子和欧姆龙PLC稳定通讯架构案例​

你是否有听过PROFINET主站与PROFINET主站之间需要做数据通讯有需求&#xff1f; 例如西门子1500与霍尼韦尔DCS系统两个主站之间的通讯。应用于PROFINET为主站设备还有欧姆龙、基恩士、罗克韦尔、施耐德、GE、ABB等品牌的PLC或DCS、FCS等平台。在生产或智能领域有通讯需求。两头…

计算机视觉|Swin Transformer:视觉 Transformer 的新方向

一、引言 在计算机视觉领域的发展历程中&#xff0c;卷积神经网络&#xff08;CNN&#xff09; 长期占据主导地位。从早期的 LeNet 到后来的 AlexNet、VGGNet、ResNet 等&#xff0c;CNN 在图像分类、目标检测、语义分割等任务中取得了显著成果。然而&#xff0c;CNN 在捕捉全…

C++单例模式精解

单例模式&#xff08;重点*&#xff09; 单例模式是23种常用设计模式中最简单的设计模式之一&#xff0c;它提供了一种创建对象的方式&#xff0c;确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例&#xff0c;即一个类只有一个对象。 将单…