与Linux的设备树文件(dts)的基础知识【根节点、子节点、标签名、节点名称、`&`开头的引用结构、嵌套节点的引用、节点格式的灵活性、禁止节点、命令行基本操作、实际例子等】

目录

01-Linux的内核对设备树书写的说明文档

文档位置在哪里?

路径为:E:\Source_Insight_project\P005_driver01\Linux-4.9.88\Documentation\devicetree
在这里插入图片描述
具体的各设备和总线的设备树编写说明在上面截图的bindings目录中:
在这里插入图片描述

举例说明

比如对SPI总线的设备树描述说明就在下面的文件中:
在这里插入图片描述
再比如对I2C总线的描述说明可参考下面这个文件:
在这里插入图片描述

Linux-4.9.88内核源码中抽取出的设备树文档打包下载地址

https://pan.baidu.com/s/1Phtc1nZ0IoLo7DiwZXFEeg?pwd=amba

01-根节点和子节点的概念

问:对下面的设备树语句哪些是根节点?哪些是子节点?

/ {
    model = "Freescale i.MX6 ULL 14x14 EVK Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    chosen {
        stdout-path = &uart1;
    };

    memory {
        reg = <0x80000000 0x20000000>;
    };

    reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        linux,cma {
            compatible = "shared-dma-pool";
            reusable;
            size = <0x14000000>;
            linux,cma-default;
        };
    };

    backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm1 0 1000>;
        brightness-levels = <0 1 2 3 4 5 6 8 10>;
        default-brightness-level = <8>;
        status = "okay";
    };
}

在提供的设备树片段中,根节点是以下部分:

/ {
    model = "Freescale i.MX6 ULL 14x14 EVK Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
    ...
}

解释如下:

  1. 根节点标识

    • 根节点使用 / 来标识,它没有名字,它没有名字,它没有名字, 它是整个设备树的顶级节点。
    • 根节点包含所有其他节点(例如例子中 chosenmemoryreserved-memorybacklight 等),这些都是根节点的子节点。
  2. 根节点属性

    • modelcompatible 是根节点的属性,描述了硬件平台的信息:
      • model:硬件平台的名称(这里是 Freescale i.MX6 ULL 14x14 EVK Board)。
      • compatible:表示设备与哪些驱动或硬件配置兼容。
  3. 子节点

    • 上面代码中的根节点中的子节点包括:
      • chosen:指定启动时的配置信息,例如控制台输出路径(stdout-path)。
      • memory:描述系统内存区域。
      • reserved-memory:定义保留内存区域,通常用于特殊用途(如 DMA 或 CMA)。
      • backlight:定义背光设备的信息(如 PWM 配置)。

注意:根节点是唯一的:设备树中只有一个根节点,且位于文件的最顶层,它用符号/ 进行表示,它也没有具体的名字。上述代码中,/ 节点是根节点,而 chosenmemoryreserved-memorybacklight 都是它的子节点。

02-带标签的节点与节点名的区别

每个节点都可以通过一个标签来标识。标签紧跟在节点名的前面,使用冒号 : 分隔。
例如:

uart1: serial@2020000 {  // 定义一个名为 uart1 的标签
    compatible = "fsl,imx6ull-uart";
    reg = <0x2020000 0x1000>;
};

在这段语句中,为名称为serial@2020000的节点定义了标签,其标签名为uart1,标签名的主要作用是方便节点被引用。关于引用的详细情况,请往下看。

03-以&开头的引用结构

&开头的引用结构的详细概述

问:我看到在根节点所在的{}之外还有很多别的节点,比如下面这几个:

&flexcan1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_flexcan1>;
    xceiver-supply = <&reg_can_3v3>;
    status = "okay";
};
&gpc {
    fsl,cpu_pupscr_sw2iso = <0x1>;
    fsl,cpu_pupscr_sw = <0x0>;
    fsl,cpu_pdnscr_iso2sw = <0x1>;
    fsl,cpu_pdnscr_iso = <0x1>;
    fsl,ldo-bypass = <0>; /* DCDC, ldo-enable */
};

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";
};

这些又是什么东西?

答:在设备树中,类似 &flexcan1, &gpc, 和 &i2c1 这样的结构并不是新定义的根节点或顶层节点,而是对已有节点的引用。这些引用用于修改、补充或覆盖设备树中已有的节点内容。

详细解释如下:

1. &<节点> 是对那些带标签的节点 的引用

  • & 符号表示引用一个通过标签定义的节点。那么什么叫带标签的节点?本篇博文前面已经说明了。
  • 标签通常是在设备树的其他地方定义的,用于标记某个节点。引用标签时,设备树会定位到对应节点。
  • 例如:
    &flexcan1 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_flexcan1>;
        xceiver-supply = <&reg_can_3v3>;
        status = "okay";
    };
    
    • &flexcan1 引用的是设备树中标签名为 flexcan1 的节点,注意并不是节点名为 flexcan1,而是节点的标签名为 flexcan1
    • 这段代码的作用是为标签名为 flexcan1 的节点添加或修改属性,如引脚配置(pinctrl)、电源供应(xceiver-supply)以及状态(status)。

2. 嵌套结构中的标签也能直接被引用
即使节点定义在嵌套结构中,只要它有一个标签,就可以在任何地方引用它。
例如:

/ {
    soc {
        uart2: serial@2030000 {  // 定义标签 uart2
            compatible = "fsl,imx6ull-uart";
            reg = <0x2030000 0x1000>;
        };
    };
};

&uart2 {
    status = "okay";  // 引用嵌套结构中的节点
};

这段代码中,虽然标签uart2对应的节点是嵌套于soc节点中的节点,但是仍然可以直接被引用。

3. 修改现有节点

  • 引用语法允许你在主 DTS 文件中对其他地方定义的节点进行修改,而不必重复完整的定义。
  • 例如:
    &gpc {
        fsl,cpu_pupscr_sw2iso = <0x1>;
        fsl,cpu_pupscr_sw = <0x0>;
    };
    
    这段代码修改了标签名为 gpc(General Power Controller)节点的部分属性。

4. 典型用途

  • 启用或禁用设备
    使用 status = "okay"; 启用某设备,或 status = "disabled"; 禁用某设备。
  • 修改引脚配置
    通过 pinctrl-namespinctrl-0 等属性指定引脚复用配置。
  • 调整设备参数
    clock-frequency(时钟频率)等。

在引用结构中嵌套定义的节点也能被引用

比如有下面的引用结构:

04-设备树文件的层次结构

综合来看,设备树文件可以分为以下层次:

  • 根节点:用 / 定义,是整个设备树的顶级节点。
  • 直接定义的子节点:比如 chosenmemory
  • 引用的节点:通过 & 引用其他节点,并进行属性调整。

05-如果系统中的设备树描述文件包含了另一个设备树描述文件,那么岂不是出现了两个根节点?

在系统的设备树描述文件100ask_imx6ull-14x14.dts中包含了另一个设备树描述文件imx6ull.dtsi,我们发现文件imx6ull.dtsi中也有自己的根节点,那怎么办?

答:设备树文件即使包含多个 dtsi 文件内容,也不会导致生成的设备树有多个根节点。以下是详细原因:

  1. 设备树结构特点
    设备树的根节点是唯一的,它是用 / 表示的顶级节点。在主设备树文件中,只有一个 / 节点被定义为根节点。

  2. 文件包含的处理
    当设备树文件通过 #include 引入其他 dtsi 文件时:

  • dtsi 文件中的内容会被嵌入到主文件中,但不会定义新的根节点。
  • 这些嵌入的内容通常作为子节点或并列节点加入现有的设备树结构。

例如:
主设备树文件 100ask_imx6ull-14x14.dts

/dts-v1/;

/include/ "imx6ull.dtsi"

{
    // 其他节点定义
};

假设 imx6ull.dtsi 中包含如下内容:

{
    iomuxc: iomuxc@020e0000 {
        compatible = "fsl,imx6ull-iomuxc";
    };
};

在最终的设备树中,iomuxc 节点会被合并到唯一的根节点下。

  1. 合并规则
    设备树编译器(DTC)会将所有引用的 dtsi 文件的内容整合到一个统一的设备树结构中:
  • 如果多个文件中定义了相同路径的节点(例如 /iomuxc),它们的内容会合并。
  • 如果路径不同,则会并列存在。
  1. 编译后的结构
    编译生成的 .dtb 文件始终是单根节点的树形结构,因此不会有多个根节点。

小结
虽然从文件层面上看,主设备树文件和 dtsi 文件可能都有独立的 {} 块,但设备树编译器会处理这些文件为一个统一的树,确保只有一个根节点。您无需担心多个根节点的问题。

06-双引号中的内容表示字符串,<>中的内容表示数值

示例如下:

/ {
	swh_led@0 {
		compatible = "swh_leddrv";
		pin = <0x00030001>;
	};

.......

};

compatible的具体内容为字符串swh_leddrv,而pin的值为数值0x00030001

07-设备树的节点格式是很灵活的,其最终目标是与驱动程序的解析逻辑无缝对接

精华总结

设备树节点的格式其实是很灵活的,它的目的是为了适应驱动程序的相关函数的解析,所以我们甚至可以说设备树节点的书写是为了去适应驱动程序中的相关函数,其格式很大程序上取决于驱动程序中需要解析它内容的的相关函数。

所以离开具体的驱动程序中的相关操作函数去谈设备树文件的具体内容,都是不实际的。

在实际操作中,我们如果要写设备树文件,都是照着人家的设备树文件并对照别人的驱动程序来写的。
如果是BSP包,我们通常不会去分析BSP中的那些函数对设备树文件是如何解析的,所以这个时候就需要去参考官方提供的设备树文件。一般芯片生产商发布某一款芯片的BSP后,也会有相应的工具去生成需要的设备树语句(比如IMX6ULL生产商提供的工具Pins_Tool_for_i.MX_Processors_v6_x64),或者提供相关的文档或示例dts文件,如果需要自己写或修改,那么照着官方提供的这些照着修改就行了。


具体说明

设备树(Device Tree)的书写是非常灵活的,具体的设计和结构通常是为了与目标驱动程序中的实现相匹配【与驱动程序的操作函数对接】。以下是设备树节点书写灵活性和驱动关联的几个关键点:

1. 灵活性来源

  • 设备树本质是硬件描述
    设备树的主要目的是描述硬件配置和资源,但它的写法并不完全固定。如何组织节点、属性的命名,以及层次结构都可以根据需求调整,只要设备树与驱动程序的解析逻辑匹配即可。

  • 命名自由度
    设备树中的节点名、属性名是可以自定义的。例如,你可以命名一个节点为led-controllermy-led,只要驱动程序能够识别它即可。

  • 节点层次结构
    节点的层级组织方式(嵌套深度和关联关系)也没有硬性要求。可以根据硬件的复杂程度选择扁平化或分层的设计。


2. 与驱动程序的关系

设备树的灵活性很大程度上来源于需要与驱动程序的操作函数对接。驱动程序根据设备树的内容来解析和初始化硬件资源,因此设备树需要满足以下几点:

(1) compatible 属性的匹配

设备树中最重要的属性之一是compatible,驱动程序通过这个属性来识别设备。例如:

compatible = "fsl,imx6ull-iomuxc";

驱动中通常会有一个设备匹配表:

static const struct of_device_id iomuxc_dt_ids[] = {
    { .compatible = "fsl,imx6ull-iomuxc", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, iomuxc_dt_ids);

如果设备树中的compatible和驱动的匹配表一致,驱动程序就会加载并处理该设备。


(2) 资源属性的解析和解析函数

驱动程序会通过设备树中的资源描述(如reginterruptsclocks等)初始化硬件。例如:

reg = <0x020e0000 0x4000>;

驱动程序通常会用of开头的函数,比如函数of_spi_register_masterof_property_read_u32()去解析这些硬件资源的描述,函数of_spi_register_master的详情如下:
来源:\Linux-4.9.88\drivers\spi\spi.c

static int of_spi_register_master(struct spi_master *master)
{
	int nb, i, *cs;
	struct device_node *np = master->dev.of_node;

	if (!np)
		return 0;

	nb = of_gpio_named_count(np, "cs-gpios");
	master->num_chipselect = max_t(int, nb, master->num_chipselect);

	/* Return error only for an incorrectly formed cs-gpios property */
	if (nb == 0 || nb == -ENOENT)
		return 0;
	else if (nb < 0)
		return nb;

	cs = devm_kzalloc(&master->dev,
			  sizeof(int) * master->num_chipselect,
			  GFP_KERNEL);
	master->cs_gpios = cs;

	if (!master->cs_gpios)
		return -ENOMEM;

	for (i = 0; i < master->num_chipselect; i++)
		cs[i] = -ENOENT;

	for (i = 0; i < nb; i++)
		cs[i] = of_get_named_gpio(np, "cs-gpios", i);

	return 0;
}

(3) 引脚复用与功能分配

如这段设备树中:

MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0

驱动会通过解析设备树来配置相应的引脚复用寄存器。通常使用函数如:

of_property_read_variable_u32_array()

3. 实际案例:灵活的书写示例

(1) 同一硬件,不同书写方式
led-controller {
    compatible = "my,led";
    gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};

led0: led@5 {
    compatible = "my,led";
    gpio = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};

以上两种方式都可以描述一个连接到GPIO1的LED设备,关键是驱动程序如何匹配compatible并解析gpio属性。

(2) 子节点结构的灵活性
i2c@1 {
    compatible = "fsl,imx6ull-i2c";
    reg = <0x21a0000 0x1000>;
    clock-frequency = <100000>;

    sensor@40 {
        compatible = "my,sensor";
        reg = <0x40>;
    };
};

这里的sensor@40子节点可以用来描述挂载在I2C总线上的一个传感器设备。只要驱动程序设计合理,节点结构可以灵活调整。


4. 灵活性与规范性平衡

虽然设备树书写很灵活,但也需要遵循一定的规范:

  • 标准属性与约定:某些属性(如compatiblereg)是约定俗成的,便于与驱动匹配。
  • 保持清晰和一致性:灵活性不应该导致混乱,应尽量遵循芯片厂商或社区的设备树风格。
  • 文档支持:设备树节点的结构和属性应在驱动或硬件文档中有明确说明。

小结

设备树节点的书写需要在灵活性和规范性之间找到平衡点。其最终目标是与驱动程序的解析逻辑无缝对接,为硬件初始化和管理提供准确的描述。

08-如何禁止一个节点的使用

在节点的描述中加上status = "disabled";即可,比如下面的代码:

    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_leds>;
        
        status = "disabled";

        led0: cpu {
            label = "cpu";
            gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
            default-state = "on";
            linux,default-trigger = "heartbeat";
        };
    };

因为其中有status = "disabled";,所以相当于leds这个设备节点不会被使用。

09-设备树文件(dts文件)和编译生成的dtb文件在哪里?

请参考下面两篇博文:
https://blog.youkuaiyun.com/wenhao_ir/article/details/144421577
https://blog.youkuaiyun.com/wenhao_ir/article/details/145056170

10-Linux系统运行时命令行中的相关基本操作

如何查看系统中有哪些设备节点?如何查看设备节点的属性?

用下面的命令查看是否有相应的设备树节点对应的目录是否存在:

ls /sys/firmware/devicetree/base/

在这里插入图片描述
上图白框中的两个是我自己添加的。

还可以进入相应的目录查看下设备节点的属性。

cd /sys/firmware/devicetree/base/swh_led@0
ls

在这里插入图片描述
可以对着咱们的设备文件源码来理解三个属性文件:
在这里插入图片描述
从上图中可以看出,name这个文件的名字是系统为我们自动加上的,我们在源码中定义设备文件时是不需加上的。

compatible文件的具体内容查看命令如下:

cat compatible

在这里插入图片描述
pin文件的具体内容查看命令如下【因为它是一个十六进制数,所以要用特殊的命令】:

hexdump pin

在这里插入图片描述
以上面这个结果的解释如下:
hexdump 命令的输出中,结果由两部分组成:

  1. 数据内容部分(如 0000000 0300 0100):显示从设备树节点文件中读取的实际内容。
  2. 偏移地址部分(如 0000004):表示读取到的最后一个字节的文件偏移地址。

让我们具体分析:

数据内容部分

0000000 0300 0100
  • 0000000: 文件的起始偏移地址,表示数据从文件的第 0 字节开始。
  • 0300 0100: 文件中实际存储的数据内容。解释如下:
    • 03 00: 高 16 位(组号 3)。
    • 01 00: 低 16 位(引脚号 1)。
    • 数据以小端字节序存储,因此高位和低位的字节顺序颠倒了。

偏移地址部分

0000004
  • 这个数字表示 文件中数据读取到的最后一个字节的偏移量,以十六进制表示。
  • 在你的情况下:
    • 设备树中存储的 pin 是一个 32 位整数,占用 4 个字节。
    • 因此偏移量是 0x4,表示读取到文件的第 4 个字节位置(实际数据范围为 0x00x3)。

小结

  • 数据内容部分显示的是 pin 属性的值(即 GROUP_PIN(3, 1) 的 32 位表示)。
  • 偏移地址部分仅是 hexdump 的输出格式,显示文件读取的结束位置,用于帮助定位文件中的数据,不是数据内容本身的一部分。

查看设备树节点生成的platform_device结构体的属性文件

这部分内容请访问博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145056170 【搜索“查看设备树节点生成”】查看相应的内容。

11-大家惯用的属性名字积累

01-名字中带“pinctrl”

名字中带“pinctrl”,意味着这与引脚复用的选择有关,相关的例子如下:
第1个例子:

&uart6 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart6>;
    status = "okay";
};


        pinctrl_uart6: uart6grp {
             fsl,pins = <
                 MX6UL_PAD_CSI_MCLK__UART6_DCE_TX      0x1b0b1
                 MX6UL_PAD_CSI_PIXCLK__UART6_DCE_RX    0x1b0b1
             >;
         };

这个例子是把与串口6相关的两个引脚(TX和RX)配置为串口的TX信号和RX信号。

第2个例子:
来源:https://blog.youkuaiyun.com/wenhao_ir/article/details/145119224

    swhled {
        compatible = "swh,leddrv";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_swh_led>;
        led2-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
    };

        pinctrl_swh_led: suwenhao_led {        /*!< Function assigned for the core: Cortex-A7[ca7] */
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };

这个例子是把与GPIO5_IO03相关的引脚配置为GPIO5_IO03信号。

02-address-cells属性的常用含义

以下面这个SPI节点为例:
来源:\Linux-4.9.88\Documentation\devicetree\bindings\spi\spi-bus.txt

	spi@f00 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "fsl,mpc5200b-spi","fsl,mpc5200-spi";
		reg = <0xf00 0x20>;
		interrupts = <2 13 0 2 14 0>;
		interrupt-parent = <&mpc5200_pic>;

		ethernet-switch@0 {
			compatible = "micrel,ks8995m";
			spi-max-frequency = <1000000>;
			reg = <0>;
		};

		codec@1 {
			compatible = "ti,tlv320aic26";
			spi-max-frequency = <100000>;
			reg = <1>;
		};
	};

在文档spi-bus.txt的上面有对它address-cells属性介绍,如下:
在这里插入图片描述
它的意思是定义挂接在这个SPI总线上的设备的地址值用几位表示,比如上面的设备树语句中的reg = <0>;reg = <1>;都是1位的地址值。
在这里插入图片描述

12-为什么会出现一个节点下有子节点的情况?

比如I2C控制器节点、SPI控制器节点,这些控制器上会挂载多个设备,这个挂载在I2C总线和SPI总线上的设备,就以子节点的形式存在。

关于I2C控制器节点下的子节点的情况,请参考博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/146417363 【搜索“利用设备树生成i2c_client”】

关于SPI控制器节点下的子节点的情况,请百度网盘搜索“1-4_04_SPI设备树处理过程”,然后从头开始看。

13-注意:节点取名字的时候名字不要太短,否则容易冲突

节点名取得太短,有可能造成莫名的冲突,而且编译时不会报错,启动系统时才发现无法正常启动系统。详情见 https://blog.youkuaiyun.com/wenhao_ir/article/details/146455604 【搜索“增加的子节点的名字不能为”】

14-如何查看一个dtb文件是否有某个节点?

直接用Notepad++打开设备树dtb文件,搜索相关的节点名就行了,比如下面这个:
在这里插入图片描述
在这里插入图片描述

15-如何查看内核中有没有某个节点?

详见 https://blog.youkuaiyun.com/wenhao_ir/article/details/146455604 【搜索“看下内核中有没有相关的设备树节点”】

16-如何在内核中查看某个节点的各属性值?

详见 https://blog.youkuaiyun.com/wenhao_ir/article/details/146455604 【搜索“看下内核中有没有相关的设备树节点”】

167-一些有设备树文件书写的具体例子

https://blog.youkuaiyun.com/wenhao_ir/article/details/145056170

https://blog.youkuaiyun.com/wenhao_ir/article/details/145119224

https://blog.youkuaiyun.com/wenhao_ir/article/details/145176361

https://blog.youkuaiyun.com/wenhao_ir/article/details/145176361

https://blog.youkuaiyun.com/wenhao_ir/article/details/145662136【这篇博文涉及到的是u-boot的设备树,不仅Linux在用设备树文件,u-boot也在用】

https://blog.youkuaiyun.com/wenhao_ir/article/details/146268977 【串口5的启用:这篇博文让我真正明白了设备树中“pinctrl”的含义】

https://blog.youkuaiyun.com/wenhao_ir/article/details/146393239 【I2C的控制器】

https://blog.youkuaiyun.com/wenhao_ir/article/details/146417363 【I2C的控制器节点下添加挂接在I2C总上的I2C设备,搜索“利用设备树生成i2c_client”】

https://blog.youkuaiyun.com/wenhao_ir/article/details/146524645 【搜索“设备树节点的书写、分析和设备树文件的编译”】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值