Skip to content

设备树,覆盖和参数

树莓派内核和固件使用设备树(DT)来描述 Pi 中存在的硬件。这些设备树可能包括 DT 参数,这些参数提供了对某些板载功能部件的控制程度。 DT覆盖层允许描述和配置可选的外部硬件,并且它们还支持参数以实现更好的控制。

固件加载器(" start.elf"及其变体)负责加载 DTB (设备树 Blob -机器可读的 DT 文件)。它根据电路板修订版号选择加载哪个,并进行某些修改以进一步调整它(内存大小,以太网地址等)。这种运行时自定义避免了只需要很小差异的大量 DTB 的需求。

会在 config .txt文件中扫描用户提供的参数以及所有叠加层及其参数,然后将其应用。加载程序检查结果以了解(例如)哪个 UART (如果有)将用于控制台。最后,它启动内核,将指针传递到合并的 DTB 。

设备树

设备树(DT)是对系统中硬件的描述。它应包括基本 CPU 的名称,其内存配置以及所有外围设备(内部和外部)。 DT不应用于描述软件,尽管通过列出硬件模块通常会导致加载驱动程序模块。记住 DT 应该与操作系统无关,因此可能不应该存在任何特定于 Linux 的东西。

设备树将硬件配置表示为节点的层次结构。每个节点都可以包含属性和子节点。属性被命名为字节数组,其中可以包含字符串,数字(大端),字节的任意序列及其任意组合。类似于文件系统,节点是目录,属性是文件。可以使用路径描述树中节点和属性的位置,并使用斜杠作为分隔符,并使用单个斜杠(/)表示根。

1.1:基本 DTS 语法

设备树通常以称为设备树源(DTS)的文本形式编写,并存储在带有后缀" .dts"的文件中。 DTS语法类似于 C ,每行末尾都有用于分组的分号和分号。请注意,DTS在右花括号后需要分号:考虑 C struct而不是函数。编译后的二进制格式称为"扁平化设备树(FDT)"或"设备树 Blob (DTB)",并存储在.dtb文件中。

以下是.dts格式的简单树:

/dts-v1 /;
/include /" common.dtsi";

/{
    节点 1  {
        a-string-property =" A字符串";
        a-string-list-property ="第一字符串","第二字符串";
        a字节数据属性= [0x01 0x23 0x34 0x56];
        表弟:child-node1 {
            第一胎财产
            第二个孩子的属性= <1>;
            a-string-property ="你好,世界";
        };
        child-node2 {
        };
    };
    节点 2  {
        空财产
        a-cell-property = <1 2 3 4>;/*每个数字(单元格)都是一个 uint32  * /
        child-node1 {
            my-cousin = <&cousin>;
        };
    };
};

/node2 {
    node2的另一个属性;
};

该树包含:

  • 必需的标头:/dts-v1 /
  • 包含另一个 DTS 文件,该文件通常称为* .dtsi,类似于 C 中的.h头文件-参见以下有关/include/_的说明。
  • 单个根节点:/
  • 几个子节点:node1和 node2
  • node1的一些子代:child-node1child-node2
  • 标签(cousin)和对该标签的引用(&cousin):请参见下面的_Labels和 References _。
  • 分散在树上的几个属性
  • 重复的节点(/node2)-参见_关于/include/_的补充。

属性是简单的键/值对,其中值可以为空或包含任意字节流。尽管数据类型未在数据结构中编码,但是可以在设备树源文件中表达一些基本数据表示形式。

文本字符串(NUL终止)用双引号表示:

string-property ="一个字符串";

单元格是由尖括号分隔的 32 位无符号整数:

单元属性= <0xbeef 123 0xabcd1234>;

任意字节数据用方括号定界,并以十六进制输入:

二进制属性= [01 23 45 67 89 ab cd ef];

可以使用逗号将不同表示形式的数据连接起来:

混合属性="字符串",[01 23 45 67],<0x12345678>;

逗号还用于创建字符串列表:

string-list ="红色鱼","蓝色鱼";

1.2:关于/include //include /`指令可导致简单的文本包含,就像 C 的#include指令一样,但是 Device Tree编译器的功能导致使用模式不同。给定节点的名称(可能带有绝对路径),则同一个节点可能在 DTS 文件(及其包含文件)中出现两次。发生这种情况时,将节点和属性组合在一起,并根据需要对属性进行交织和覆盖(后面的值会覆盖前面的值)。

在上面的示例中,/node2的第二次出现导致将新属性添加到原始属性:

/node2 {
    空财产
    a-cell-property = <1 2 3 4>;/*每个数字(单元格)都是一个 uint32  * /
    node2的另一个属性;
    child-node1 {
        my-cousin = <&cousin>;
    };
};

因此,一个.dtsi可能覆盖一棵树中的多个位置,或为其提供默认值。

1.3:标签和参考

通常需要树的一部分引用另一部分,并且有四种方法可以做到这一点:

1.路径字符串

类似于文件系统,路径应该是不言自明的-`/soc/i2s @ 7e203000`是 BCM2835 和 BCM2836 中 I2S 设备的完整路径。请注意,尽管构造属性的路径很容易(例如,"/soc/i2s @ 7e203000/status"),但标准的 API 并没有这样做;您首先找到一个节点,然后选择该节点的属性。

1.把手

phandle是在其 phandle 属性中分配给节点的唯一 32 位整数。由于历史原因,您可能还会看到一个冗余的,匹配的" linux,phandle"。把手从 1 开始顺序编号; 0不是有效的个人。当它们在整数上下文中遇到对节点的引用时,通常由 DT 编译器分配它们,通常以标签的形式(请参见下文)。使用句柄对节点的引用被简单地编码为相应的整数(单元)值。没有标记指示应将它们解释为 phandles ,因为这是应用程序定义的。

1.标签

就像 C 中的标签为代码中的位置提供名称一样,DT标签也为层次结构中的节点分配名称。编译器获取对标签的引用,并将其在字符串上下文(&node)中使用时转换为路径,在整数上下文(<&node>)中使用句柄。原始标签不会出现在编译输出中。请注意,标签不包含任何结构。它们只是一个统一的全局命名空间中的令牌。

1.别名

别名与标签类似,不同的是别名确实以索引形式出现在 FDT 输出中。它们被存储为/aliases节点的属性,每个属性都将别名映射到路径字符串。尽管别名节点出现在源中,但是路径字符串通常作为对标签(&node)的引用而出现,而不是全部写出来。将节点的路径字符串解析为 DT 的 DT API通常会查看路径的第一个字符,将不以斜杠开头的路径视为别名,必须先使用/aliases表将其转换为路径。

1.4:设备树语义

如何构造设备树,以及如何最好地使用它来捕获某些硬件的配置是一个大而复杂的主题。有很多可用的资源,下面列出了其中的一些资源,但是在本文档中有几点需要提及:

" compatible"属性是硬件描述和驱动程序软件之间的链接。当操作系统遇到具有" compatible"属性的节点时,它将在其设备驱动程序数据库中查找该节点以找到最佳匹配项。在 Linux 中,只要已正确标记驱动程序模块且未将其列入黑名单,这通常会导致驱动程序模块自动加载。

状态属性指示设备是启用还是禁用。如果"状态"为"正常","正常"或不存在,则表示设备已启用。否则,应禁用"状态",以便禁用设备。将设备放置在状态为"禁用"的.dtsi文件中可能会很有用。然后,派生的配置可以包含该.dtsi并将所需设备的状态设置为"好"。

第 2 部分:设备树覆盖

现代的 SoC (片上系统)是非常复杂的设备。完整的设备树可能长达数百行。再往前走一步,将 SoC 与其他组件一起放置在板上,只会使情况变得更糟。为了保持可管理性,特别是在有共享组件的相关设备的情况下,将公共元素放入.dtsi文件中是很有意义的,以便可能包含在多个.dts文件中。

当像树莓派这样的系统还支持可选的插件附件(例如 HAT )时,问题就越来越大。最终,每种可能的配置都需要一个设备树来描述它,但是一旦您考虑了所有不同的基本模型和大量可用的附件,组合的数量就会开始迅速增加。所需要的是一种使用部分设备树描述这些可选组件的方法,然后通过采用基本 DT 并添加许多可选元素来构建完整的树。您可以执行此操作,这些可选元素称为"叠加层"。 除非您想学习如何为树莓派编写覆盖图,否则您可能更喜欢跳过第 3 部分:在树莓派上使用设备树

2.1:片段

DT覆盖包括多个片段,每个片段都针对一个节点及其子节点。尽管这个概念听起来很简单,但语法一开始似乎很奇怪:

//启用 i2s 接口
/dts-v1 /;
/插入/;

/{
    兼容=" brcm,bcm2835";

    片段@ 0 {
        目标= <&i2s>;
        __over____ {
            状态="好";
            test_ref = <&test_label>;
            test_label:test_subnode {
                假;
            };
        };
    };
};

" compatible"字符串将其标识为用于 BCM2835 ,这是树莓派 SoC的基本体系结构。如果覆盖层利用 Pi 4的功能,则`brcm,bcm2711'是正确的值,否则,'brcm,bcm2835'可以用于所有 Pi 覆盖层。然后是第一个(仅在这种情况下)片段。片段应从零开始顺序编号。不遵守此规定可能会导致丢失部分或全部片段。

每个片段由两部分组成:" target"属性,标识要对其应用叠加层的节点;以及__overlay__本身,其主体将添加到目标节点。可以将上面的示例解释为如下所示:

/dts-v1 /;
/插入/;

/{
    兼容=" brcm,bcm2835";
};

&i2s {
    状态="好";
    test_ref = <&test_label>;
    test_label:test_subnode {
        假;
    };
};

(实际上,使用足够新版本的dtc,您可以完全像这样编写并获得相同的输出,但是某些本地化工具尚不了解此格式,因此您可能希望将其包含在标准 Raspberry 中。目前应以旧格式编写 Pi OS内核)。

如果覆盖层是在以后加载的,那么将覆盖层与标准的树莓派基本设备树(例如," bcm2708-rpi-b-plus.dtb")合并,将会通过将 I2S 接口的状态更改为"好"来启用 I2S 接口。 `。但是,如果您尝试使用以下命令编译此叠加层:

dtc -I dts -O dtb -o 2nd.dtbo 2nd-overlay.dts

你会得到一个错误:

找不到标签或路径

这应该不会太出乎意料,因为没有引用基本的.dtb或.dts文件来允许编译器找到 i2s 标签。

再试一次,这次使用原始示例,并添加-@选项以允许未解析的引用(和-Hepapr消除一些混乱):

dtc-@ -Hepapr -I dts -O dtb -o 1st.dtbo 1st-overlay.dts

如果dtc返回有关第三行的错误,则它没有覆盖工作所需的扩展名。运行sudo apt install device-tree-compiler并重试-这次,编译应该成功完成。请注意,内核树中还提供了一个合适的编译器,如scripts/dtc/dtc,该编译器是在使用dtbs make target时构建的:

使 ARCH  = arm dtbs

转储 DTB 文件的内容以查看编译器生成的内容很有趣:

$ fdtdump 1st.dtbo
/dts-v1 /;
//魔法:0xd00dfeed
//总大小:0x207(519)
//off_dt_struct:0x38
//off_dt_strings:0x1c8
//off_mem_rsvmap:0x28
//版本:17
//last_comp_version:16
//boot_cpuid_phys:0x0
//size_dt_strings:0x3f
//size_dt_struct:0x190

/{
    兼容=" brcm,bcm2835";
    片段@ 0 {
        目标= <0xffffffff>;
        __over____ {
            状态="好";
            test_ref = <0x00000001>;
            test_subnode {
                假;
                phandle = <0x00000001>;
            };
        };
    };
    __符号__ {
        test_label ="/fragment @ 0/__ overlay __/test_subnode";
    };
    __fixups__ {
        i2s ="/fragment @ 0:target:0";
    };
    __local_fixups__ {
        片段@ 0 {
            __over____ {
                test_ref = <0x00000000>;
            };
        };
    };
};

在文件结构的详细描述之后,是我们的片段。但是请仔细观察-在我们写&i2s的地方,现在说的是0xffffffff,这暗示发生了奇怪的事情(旧版本的 dtc 可能会说0xdeadbeef)。编译器还添加了一个" phandle"属性,该属性包含一个唯一的(对此覆盖图)小整数以指示该节点具有标签,并用相同的小整数替换了对该标签的所有引用。

片段之后有三个新节点: __symbols__列出了叠加层中使用的标签(此处为test_label),以及标记节点的路径。该节点是如何处理未解析符号的关键。__fixups__包含一个属性列表,该属性列表将未解析符号的名称映射到片段中的单元格的路径列表,这些片段中的单元格一旦确定了目标就需要用目标节点的模范进行修补。在这种情况下,路径是目标值的" 0xffffffff"值,但是片段可以包含其他未解析的引用,这些引用需要进行其他修复。*__local_fixups__保留了对覆盖物中存在的标签的所有引用的位置- test_ref属性。这是必需的,因为执行合并的程序将必须确保角色编号是连续且唯一的。

回到第 1 .3节中,它说"原始标签未出现在编译输出中",但这在使用-@开关时并非如此。相反,每个标签都会在" symbols"节点中产生一个属性,将标签映射到路径,就像" aliases"节点一样。实际上,该机制是如此相似,以至于当解析符号时,树莓派加载器将在没有" symbols"节点的情况下搜索" aliases"节点。这一次很有用,因为提供足够的别名允许使用非常老的dtc版本来构建基本 DTB 文件,但是幸运的是,这已经是古老的历史了。

2.2:设备树参数

为了避免需要大量的设备树覆盖,并减少外围设备用户修改 DTS 文件的需求,树莓派加载程序支持一项新功能-设备树参数。这允许使用命名参数对 DT 进行少量更改,类似于内核模块从" modprobe"和内核命令行接收参数的方式。基本 DTB 和覆盖(包括 HAT 覆盖)可以暴露参数。

通过在根目录中添加一个__overrides__节点在 DTS 中定义参数。它包含一些属性,这些属性的名称是所选的参数名称,其值是一个序列,该序列包括目标节点的 phandle (对标签的引用)和指示目标属性的字符串;支持字符串,整数(单元格)和布尔属性。

2.2.1:字符串参数

字符串参数的声明如下:

名称= <&label>,"属性";

其中,"标签"和"属性"被替换为适当的值。字符串参数可以导致其目标属性增长,缩小或创建。

注意,名为" status"的属性会被特别对待;非零/true/yes/on值将转换为字符串" okay",而零/false/no/off将变为" disabled"。

2.2.2:整数参数

整数参数的声明如下:

名称= <&label>," property.offset"; //8位
名称= <&label>,"属性;偏移量"; //16位
名称= <&label>,"属性:偏移量"; //32位
名称= <&label>,"属性#offset"; //64位

用适当的值代替"标签","属性"和"偏移";相对于属性的开头以字节为单位指定偏移量(默认为十进制),并且前面的分隔符指示参数的大小。与以前的实现方式不同的是,整数参数可以引用不存在的属性或超出现有属性结尾的偏移量。

2.2.3:布尔参数

设备树将布尔值编码为零长度属性;如果存在,则该属性为 true ,否则为 false 。它们的定义如下:

boolean_property; //将'boolean_property'设置为 true 

请注意,通过未定义属性,为属性分配了值" false"。布尔参数声明如下:

名称= <&label>,"属性?";

其中,"标签"和"属性"被替换为适当的值。

反转的布尔值在应用输入值之前,将其反转为与常规布尔值相同的值。它们的声明方式相似,但是使用!表示反转:

名称= <&label>,"属性!";

布尔参数可以导致创建或删除属性,但是它们不能删除基本 DTB 中已经存在的属性。

2.2.4:字节字符串参数

字节字符串属性是字节的任意序列,例如 MAC 地址。它们接受十六进制字节的字符串,字节之间带有或不带有冒号。

mac_address = <&ethernet0>," local_mac_address [";

选择了[[以匹配 DT 语法以声明字节字符串:

local_mac_address = [aa bb cc dd ee ff];

2.2.5:具有多个目标的参数

在某些情况下,能够在设备树中的多个位置设置相同的值很方便。可以使用串联多个目标的方法来将多个目标添加到单个参数,而不是笨拙地创建多个参数的方法:

    __overrides__ {
        gpiopin = <&w1>," gpios:4",
                  <&w1_pins>," brcm,pins:0";
        ...
    };

(示例取自" w1-gpio"覆盖图)请注意,甚至可以使用单个参数来定位不同类型的属性。您可以合理地将"启用"参数连接到"状态"字符串,包含零或一的单元格以及适当的布尔属性。

2.2.6:文字分配

2.2.5中所述,DT参数机制允许从同一参数修补多个目标,但是该实用程序受到以下限制:必须将相同的值写入所有位置(除了格式转换和取反(布尔取反)。嵌入的文字分配的增加使参数可以写入任意值,而与用户提供的参数值无关。

赋值出现在声明的末尾,并以=表示:

str_val = <&target>," strprop = value"; //1
int_val = <&target>," intprop:0 = 42 //2
int_val2 = <&target>," intprop:0 =",<42>; //3
字节= <&target>," bytestr [= b8:27:eb:01:23:45"; //4

第 1 、 2 和 4 行相当明显,但是第 3 行更有趣,因为该值显示为整数(单元格)值。 DT编译器在编译时对整数表达式求值,这可能很方便(特别是如果使用了宏值),但是单元格还可以包含对标签的引用:

//强制 LED 使用内部 GPIO 控制器上的 GPIO 。
exp_led = <&led1>," gpios:0 =",<&gpio>,
          <&led1>," gpios:4";

当应用叠加时,将以通常的方式针对基本 DTB 解析标签。请注意,将多部分参数分割成这样的多行是一个好主意,以使它们更易于阅读-随着像这样的单元格值分配,这变得更加必要。

请记住,除非应用参数,否则它们将不执行任何操作-除非使用参数名称而不分配值,否则查找表中的默认值将被忽略。

2.2.7:查找表

查找表允许在使用参数输入值之前对其进行转换。它们充当关联数组,就像 switch /case语句一样:

语音= <&node>,"字母{a = alpha,b = bravo,c = charlie,d,e,='探戈制服'}";
bus = <&fragment>," target:0 {0 =",<&i2c0>," 1 =",<&i2c1>,"}";

没有= value的键意味着使用键作为值,如果没有匹配,则在没有默认值的情况下将=用作键,并且以逗号开头(或以空结束)键=值对在任何地方)表示不匹配的输入值应原样使用;否则,找不到匹配项是错误。

请注意,单元格整数值后的表字符串中的逗号分隔符是隐式的-添加一个显式会创建一个空对(请参见上文)。

N.B.由于查找表对输入值进行操作并且文字分配忽略它们,因此无法将两个-字符合并在一起,因为查找声明中的结束}被视为错误。

2.2.8叠加/片段参数

所描述的 DT 参数机制具有许多限制,包括当使用参数时无法更改节点名称以及无法将任意值写入任意属性。克服其中一些限制的一种方法是有条件地包括或排除某些片段。

通过将__overlay__节点重命名为__dormant__,可以将片段从最终合并过程中排除(禁用)。参数声明语法已得到扩展,以允许否则为非法的零目标对象指示以下字符串包含片段或覆盖范围内的操作。到目前为止,已实施了四个操作:

+ <n> //启用片段<n>
- <n> //禁用片段<n>
= <n> //如果分配的参数值为 true ,则启用片段<n>,否则将其禁用
!<n> //如果分配的参数值为 false ,则启用片段<n>,否则将其禁用

例子:

just_one = <0>," + 1-2"; //启用 1 ,停用 2 
条件= <0>," = 3!4"; //启用 3 ,如果值为 true ,则禁用 4 ,
                          //否则禁用 3 ,启用 4 。

i2c-rtc叠加层使用此技术。

2.2.9特殊属性

当某些属性名称被参数作为目标时,将得到特殊处理。您可能已经注意到了一个-status-将布尔值转换为 true 表示" okay",将 false 表示为" disabled"。

分配给bootargs属性是对其的追加,而不是覆盖它-这是将设置添加到内核命令行的方式。

reg属性用于指定设备地址-内存映射的硬件块的位置,I2C总线上的地址等。子节点的名称应使用十六进制的地址进行限定,并以@表示。分隔符:

        bmp280 @ 76 {
            reg = <0x77>;
            ...
        };
```分配给 reg 属性时,父节点名称的地址部分将替换为分配的值。当多次使用相同的覆盖图时,这可以用来防止节点名称冲突-这是 i2c -gpio覆盖图使用的一种技术。
名称属性是伪属性-不应在 DT 中显示,但对其进行分配会导致其父节点的名称更改为分配的值。就像`reg`属性一样,它可以用来为节点赋予唯一的名称。

<a name="part2.2.10"> </a>
#### 2.2.10叠加图文件

围绕 BCM2711  SoC构建的 Pi  4的引入带来了许多变化。其中一些更改是附加接口,而某些则是对现有接口的修改(或删除)。有一些专门用于 Pi  4的新覆盖层,这些覆盖层在较旧的硬件上没有任何意义,例如支持新的 SPI ,I2C和 UART 接口的叠加层,但其他叠加层不能正确应用,即使它们控制的功能仍与新设备相关。

因此,需要一种将覆盖物定制为具有不同硬件的多个平台的方法。在单个.dtbo文件中支持所有这些文件将需要大量使用隐藏的("休眠")片段,并切换到按需符号解析机制,以便不需要的丢失符号不会导致失败。一个更简单的解决方案是添加一个功能,以根据当前平台将覆盖名称映射到多个实现文件之一。

随切换到 Linux  5.4一起推出的覆盖图是由固件在启动时加载的文件。它以 DTS 源格式" overlay_map.dts"编写,编译为" overlay_map.dtb"并存储在 overlays 目录中。

这是当前地图文件的编辑版本(完整版本位于[此处](https://github.com/raspberrypi/linux/blob/rpi-5.4.y/arch/arm/boot/dts/overlays/overlay_map.dts)):

/{ vc4-kms-v3d { bcm2835; bcm2711 =" vc4-kms-v3d-pi4"; };

vc4-kms-v3d-pi4 {
    bcm2711;
};

uart5 {
    bcm2711;
};

pi3-disable-bt {
    重命名=" disable-bt";
};

lirc-rpi {
    不推荐使用=" use gpio-ir";
};

};


每个节点都有一个需要特殊处理的覆盖名称。每个节点的属性要么是平台名称,要么是少数特殊指令之一。当前支持的平台是" bcm2835",其中包括围绕 BCM2835 ,BCM2836和 BCM2837  SoC构建的所有 Pi ,以及用于 Pi  4B的" bcm2711"。

没有值的平台名称(空属性)表示当前覆盖层与平台兼容;例如," vc4-kms-v3d"与" bcm2835"平台兼容。平台的非空值是用于替代请求的覆盖层的名称;在 BCM2711 上要求 vc4 -kms-v3d会导致加载 vc4 -kms-v3d-pi4。覆盖节点中未包括的任何平台均与该覆盖不兼容。

可以从 vc4 -kms-v3d的内容中推断出第二个示例节点 vc4 -kms-v3d-pi4,但是这种智能关系到文件的构造,而不是文件的解释。

如果未列出叠加平台,则可以使用以下特殊指令之一:

*`renamed`指令指示叠加层的新名称(应该与原始名称基本兼容),但还会记录有关重命名的警告。

*`deprecated`指令包含一个简短的解释性错误消息,该消息将在不赞成使用公共前缀`overlay'...'之后记录:

请记住:仅需列出例外情况-覆盖图缺少节点意味着所有平台均应使用默认文件。

[调试](#part4.1)中介绍了从固件访问诊断消息。

扩展了 dtoverlay 和 dtmerge 实用程序以支持映射文件:
*`dtmerge`从基本 DTB 中的兼容字符串中提取平台名称。
*`dtoverlay`从`/proc/device-tree`的实时设备树中读取兼容的字符串,但是您可以使用`-p`选项来提供备用平台名称(对于在不同平台上的空运行很有用)。

它们都向 STDERR 发送错误,警告和任何调试输出。

<a name="part2.2.11"> </a>
#### 2.2.11示例

以下是一些不同类型的属性的示例,并带有用于修改它们的参数:

/{ 片段@ 0 { target-path =" /"; __over____ {

        测试:test_node {
            字符串="你好";
            状态="已禁用";
            字节=/bits/8 <0x67 0x89>;
            u16s =/bits/16 <0xabcd 0xef01>;
            u32s =/bits/32 <0xfedcba98 0x76543210>;
            u64s =/bits/64 <0xaaaaa5a55a5a5555 0x0000111122223333>;
            bool1; //默认为 true 
                   //bool2默认为 false 
            mac = [01 23 45 67 89 ab];spi = <&spi0>; };
    };
};

片段@ 1 {
    target-path =" /";
    __over____ {
        frag1;
    };
};

片段@ 2 {
    target-path =" /";
    __休眠__ {
        frag2;
    };
};

__overrides__ {
    字符串= <&test>,"字符串";
    enable = <&test>,"状态";
    byte_0 = <&test>," bytes.0";
    byte_1 = <&test>," bytes.1";
    u16_0 = <&test>," u16s; 0";
    u16_1 = <&test>," u16s; 2";
    u32_0 = <&test>," u32s:0";
    u32_1 = <&test>," u32s:4";
    u64_0 = <&test>," u64s#0";
    u64_1 = <&test>," u64s#8";
    bool1 = <&test>," bool1!";
    bool2 = <&test>," bool2?";
    entofr = <&test>,"英语",
                  <&test>,"法语{hello = bonjour,再见='au revoir',周末}";
    pi_mac = <&test>," mac [{1 = b8273bfedcba,2 = b8273b987654}";"
    spibus = <&test>," spi:0 [0 =",<&spi0>," 1 =",<&spi1>," 2 =",<&spi2>;

    only1 = <0>," + 1-2";
    only2 = <0>,"-1 + 2";
    enable1 = <0>," = 1";
    disable2 = <0>,"!2";
};

};


对于其他示例,树莓派 Linux GitHub存储库中托管了大量叠加源文件[here](https://github.com/raspberrypi/linux/tree/rpi-5.4.y/arch/arm/boot/dts /叠加层)。

<a name="part2.3"> </a>
### 2.3:导出标签

固件和运行时覆盖应用程序中使用" dtoverlay"实用程序的覆盖处理将覆盖中定义的标签视为该覆盖的私有标签。这样可以避免发明标签的全局唯一名称(使标签简短),并且允许多次使用同一叠加层而不会发生冲突(使用了一些技巧-请参阅[特殊属性](#part2.2.9)) 。

但是,有时候,能够创建一个覆盖并从另一个覆盖使用标签非常有用。自 2020 年 2 月 14 日以来发布的固件具有将某些标签声明为全局标签的能力-__export__节点:
...
上市: ...

__exports__ {
    上市; //将标签" public"导出到基本 DT 
};

};

应用此叠加层后,加载程序会去除除已导出的符号(在本例中为" public")以外的所有符号,并重写路径以使其相对于包含标签的片段目标。此后加载的叠加层可以引用`&public`。

<a name="part2.4"> </a>
### 2.4:覆盖申请顺序

在大多数情况下,片段的应用顺序无关紧要,但是对于自己打补丁的叠加层(片段的目标是叠加层中的标签,称为叠加层内片段),它变得很重要。在较旧的固件中,严格按照从上到下的顺序应用片段。自 2020 年 2 月 14 日发布固件以来,分两步应用片段:

一世。首先,应用和隐藏针对其他片段的片段。
ii。然后应用常规片段。

这种分割对运行时叠加尤为重要,因为步骤(i)发生在`dtoverlay`实用程序中,而步骤(ii)由内核执行(无法处理叠加内片段)。

<a name="part3"> </a>
## 第 3 部分:在树莓派上使用设备树

<a name="part3.1"> </a>
### 3.1:DTB,覆盖和 config .txt

在树莓派上,加载程序(" start.elf"映像之一)的工作是将覆盖层与适当的基本设备树结合起来,然后将完全解析的设备树传递给内核。基本设备树位于 FAT 分区中的 start .elf旁边(从 Linux 启动),名称为 bcm2711 -rpi-4-b.dtb,bcm2710-rpi-3-b-plus.dtb,注意,某些模型(3A +,A,A +)将分别使用等效的" b"(3B +,B,B +)。此选择是自动的,并允许在各种设备中使用相同的 SD 卡映像。

请注意,DT和 ATAG 是互斥的,并且将 DT  blob传递给不了解它的内核会导致引导失败。固件将始终尝试加载 DT 并将其传递给内核,因为 rpi -4.4.y之后的所有内核都必须在没有 DTB 的情况下才能运行。您可以在 config .txt中添加`device_tree =`来覆盖它,这会强制使用 ATAG ,这对于简单的"裸机"内核很有用。

[该固件过去一直在寻找`mkknlimg`实用程序附加到内核的预告片,但是已经取消了对此的支持。 ]

加载程序现在支持使用 bcm2835 _defconfig进行的构建,该版本选择上游的 BCM2835 支持。此配置将导致构建" bcm2835-rpi-b.dtb"和" bcm2835-rpi-b-plus.dtb"。如果这些文件是与内核一起复制的,则加载程序将默认尝试加载其中一个 DTB 。

为了管理设备树和覆盖,加载器支持许多`config.txt`指令:

dtoverlay = acme-board dtparam = foo = bar,level = 42 ```这将导致加载程序在固件分区中查找" overlays/acme-board.dtbo",树莓派 OS将该固件分区安装在"/boot"中。然后它将搜索参数" foo"和" level",并为其指定指示值。 加载程序还将搜索带有已编程 EEPROM 的附加 HAT ,并从那里直接或通过" overlays"目录中的名称加载支持的覆盖图。这种情况无需任何用户干预即可发生。

有几种方法可以表明内核正在使用设备树:

1.引导过程中的" Machine model:"内核消息具有特定于板的值,例如" 树莓派 2 Model B",而不是" BCM2709"。 2./proc/device-tree存在,并且包含与 DT 的节点和属性完全相同的子目录和文件。

使用设备树,内核将自动搜索并加载支持指定的已启用设备的模块。结果,通过为设备创建适当的 DT 覆盖层,可以节省设备用户不必编辑`/etc/modules'的麻烦。所有配置都放在" config.txt"中,如果是 HAT ,则甚至不需要该步骤。但是请注意,诸如 i2c -dev之类的分层模块仍需要显式加载。

不利的一面是,除非 DTB 要求,否则不会创建平台设备,因此不再需要将由于板支持代码中定义的平台设备而被加载的模块列入黑名单。实际上,当前的树莓派 OS映像附带没有黑名单文件(某些具有多个驱动程序的 WLAN 设备除外)。

3.2:DT参数

如上所述,DT参数是对设备配置进行较小更改的便捷方法。当前的基本 DTB 支持无需启用专用覆盖即可启用和控制板载音频,I2C,I2S和 SPI 接口的参数。在使用中,参数如下所示:

dtparam =音频=开,i2c_arm =开,i2c_arm_baudrate = 400000,spi =开

请注意,可以将多个分配放在同一行上,但请确保您不超过 80 个字符的限制。

如果您有一个定义了一些参数的叠加层,则可以在后续行中指定它们,如下所示:

dtoverlay = lirc-rpi
dtparam = gpio_out_pin = 16
dtparam = gpio_in_pin = 17
dtparam = gpio_in_pull =向下

或附加到叠加线,如下所示:

dtoverlay = lirc-rpi,gpio_out_pin = 16,gpio_in_pin = 17,gpio_in_pull =向下

覆盖参数仅在作用域中,直到下一个覆盖被加载。如果覆盖层和基础都导出了具有相同名称的参数,则覆盖层中的参数具有优先权;为了清楚起见,建议您避免这样做。要公开由基本 DTB 导出的参数,请使用以下命令结束当前覆盖范围:

dtoverlay =

3.3:特定于电路板的标签和参数

树莓派板具有两个 I2C 接口。它们名义上是分开的:一个用于 ARM ,另一个用于 VideoCore (" GPU")。在几乎所有型号上," i2c1"属于 ARM ," i2c0"属于 VC ,用于控制摄像机和读取 HAT EEPROM。但是,模型 B 有两个早期的修订版本,它们的作用相反。

为了使所有 Pi 都可以使用一组叠加层和参数,固件会创建一些特定于板的 DT 参数。这些是:

i2c/i2c_arm
i2c_vc
i2c_baudrate/i2c_arm_baudrate
i2c_vc_baudrate

这些是" i2c0"," i2c1"," i2c0_baudrate"和" i2c1_baudrate"的别名。建议仅在确实需要时使用" i2c_vc"和" i2c_vc_baudrate",例如,如果您正在编程 HAT EEPROM(最好使用带有" i2c-gpio"覆盖的软件 I2C 总线来完成)。启用" i2c_vc"可以停止 Pi 摄像机或 7 英寸 DSI 显示器的正常运行。

对于编写覆盖图的人,相同的别名已应用于 I2C DT节点上的标签。因此,您应该写:

片段@ 0 {
    目标= <&i2c_arm>;
    __over____ {
        状态="好";
    };
};

任何使用数字变量的覆盖都将被修改为使用新的别名。

3.4:HAT和设备树

树莓派 HAT是具有嵌入式 EEPROM 的附加板,专为具有 40 针接头的树莓派设计。 EEPROM包括使电路板启用所需的任何 DT 覆盖层(或从归档系统加载的覆盖层名称),并且该覆盖层还可以公开参数。

HAT覆盖由固件在基本 DTB 之后自动加载,因此在加载任何其他覆盖或使用dtoverlay ='终止覆盖范围之前,可以访问其参数。如果出于某种原因要抑制 HAT 叠加层的加载,请将dtoverlay =放在任何其他dtoverlaydtparam`指令之前。

3.5:动态设备树从 Linux 4.4开始,RPi内核支持动态加载覆盖和参数。兼容的内核管理一叠叠加层,这些叠加层应用在基本 DTB 的顶部。更改会立即反映在/proc/device-tree中,并可能导致模块被加载以及平台设备的创建和销毁。

上面的"堆栈"一词非常重要-叠加层只能在堆栈的顶部添加和删除;在堆栈的更下方进行更改需要首先删除堆栈顶部的所有内容。

有一些用于管理覆盖图的新命令:

3.5.1 dtoverlay命令

dtoverlay是一个命令行实用程序,可在系统运行时加载和删除覆盖,以及列出可用的覆盖并显示其帮助信息:

pi @ raspberrypi〜$ dtoverlay -h
用法:
  dtoverlay <覆盖> [<参数> = <val> ...]
                           添加叠加层(带有参数)
  dtoverlay -D [<idx>]空运行(准备覆盖,但不适用-
                           将其保存为 dry -run.dtbo)
  dtoverlay -r [<overlay>]删除覆盖(按名称,索引或最后一个)
  dtoverlay -R [<overlay>]从叠加层中删除(按名称,索引或全部)
  dtoverlay -l列出活动的叠加层/参数
  dtoverlay -a列出所有覆盖图(标记为活动)
  dtoverlay -h显示此用法消息
  dtoverlay -h <overlay>在叠加层上显示帮助
  dtoverlay -h <overlay> <param> ..或其参数
    其中<overlay>是 dtparams 的覆盖名称或'dtparam'
适用于大多数变体的选项:
    - d <dir>指定覆盖的替代位置
                (默认为/boot/overlays或/flash/overlays)
    - v详细操作

与" config.txt"等效项不同,覆盖层的所有参数都必须包含在同一命令行中-dtparam命令仅适用于基本 DTB 的参数。

注意两点: 1.更改内核状态(添加和删除内容)的命令变体需要 root 特权,因此您可能需要在命令前加上 sudo 前缀。

2.只有在运行时应用的叠加层和参数才能被卸载-固件应用的叠加层或参数将被"烘焙",因此不会在dtoverlay中列出并且无法删除。

3.5.2 dtparam命令

dtparam创建并加载一个覆盖层,其效果与在config.txt中使用 dtparam 指令基本相同。在用法上,它等效于覆盖名称为-的 dtoverlay ,但有一些区别:

1.dtparam将列出基本 DTB 的所有已知参数的帮助信息。仍然可以使用 dtparam -h来获得 dtparam 命令的帮助。

2.当指示要删除的参数时,只能使用索引号(不能使用名称)。

3.并不是所有的 Linux 子系统在运行时都对设备的添加做出响应-I2C,SPI和声音设备可以工作,但有些则不能。

3.5.3编写支持运行时的覆盖的准则

该区域的文档很少,但是这里有一些累积的技巧:

*设备对象的创建或删除是通过添加或删除节点,或者节点的状态从禁用更改为启用来触发的,反之亦然。当心-缺少"状态"属性意味着该节点已启用。

*不要在片段内创建一个会覆盖基本 DTB 中现有节点的节点-内核将重命名新节点以使其唯一。如果要更改现有节点的属性,请创建一个针对它的片段。

  • ALSA不会阻止其编解码器和其他组件在使用时被卸载。如果删除覆盖,则删除声卡仍在使用的编解码器时,可能会导致内核异常。实验发现,设备会按照覆盖中的片段顺序相反的顺序删除,因此将卡的节点放置在组件的节点之后可以有序关闭。

3.5.4注意事项

在运行时加载叠加层是内核的一项新功能,到目前为止,尚无从用户空间进行此操作的方法。通过将这种机制的细节隐藏在命令后面,目的是在不同的内核接口变得标准化的情况下,使用户免受更改的影响。

*有些叠加层在运行时比其他叠加层效果更好。设备树的部分仅在引导时使用-使用覆盖更改它们不会有任何影响。

应用或移除某些覆盖层可能会导致意外的行为,因此应格外小心。这是它需要使用 sudo 的原因之一。如果某些东西正在使用 ALSA ,则卸载 ALSA 卡的覆盖层可能会停止-LXPanel音量滑块插件演示了这种效果。为了移除声卡的覆盖,lxpanelctl实用程序被赋予了两个新的选项-alsastopalsastart-这些在辅助脚本 dtoverlay -pre和 dtoverlay -post之前被调用。以及分别加载或卸载叠加层之后。 *移除覆盖不会导致已加载的模块被卸载,但是可能导致某些模块的引用计数降至零。两次运行 rmmod -a将导致未使用的模块被卸载。

*必须以相反的顺序除去覆盖层。这些命令将允许您删除较早的命令,但是所有中间的命令将被删除并重新应用,这可能会导致意想不到的后果。

*仅探测在树的顶层的设备树节点和总线节点的子节点。对于在运行时添加的节点,存在进一步的限制,即总线必须注册以获取有关添加和删除子代的通知。但是,有一些例外情况会破坏该规则并引起混乱:内核明确扫描整个树以查找某些设备类型-时钟和中断控制器是两个主要类型-以便(对于时钟)尽早初始化它们和/或(对于中断控制器)。该搜索机制仅在引导期间发生,因此不适用于运行时叠加层添加的节点。因此,建议覆盖将固定时钟节点放置在树的根中,除非可以确保运行时不会使用覆盖。

3.6:受支持的叠加层和参数

由于在此处记录单个覆盖图太耗时,因此请参考覆盖图旁边的[README](https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README)文件。 .dtbo文件位于/boot/overlays中。它通过添加和更改保持最新状态。

第 4 部分:故障排除和专业提示

4.1:调试

加载程序将跳过缺少的覆盖和错误的参数,但是如果存在严重错误,例如基本 DTB 丢失或损坏或覆盖合并失败,则加载程序将退回到非 DT 引导。如果发生这种情况,或者您的设置不符合预期,则值得检查加载程序中的警告或错误:

sudo vcdbg日志消息

可以通过在 config .txt文件中添加 dtdebug = 1来启用额外的调试功能。

您可以像这样创建人类可读的 DT 当前状态表示:

dtc -I fs/proc/device-tree

这对于查看将覆盖层合并到基础树上的效果很有用。

如果内核模块没有按预期加载,请检查它们是否未在/etc/modprobe.d/raspi-blacklist.conf中列入黑名单。使用设备树时,不必将其列入黑名单。如果没有任何问题,您还可以通过在/lib/modules/<version>/modules.alias中搜索compatible值来检查模块是否导出了正确的别名。否则,您的驱动程序可能会丢失:

.of_match_table = xxx_of_match,

要么:

MODULE_DEVICE_TABLE(of,xxx_of_match);

失败的是,depmod失败或目标文件系统上未安装更新的模块。

4.2:使用 dtmerge ,dtdiff和 ovmerge 测试覆盖

除了 dtoverlay 和 dtparam 命令之外,还有一个实用程序,用于将覆盖层应用于 DTB -dtmerge。要使用它,您首先需要获取基本的 DTB ,可以通过以下两种方式之一来获取它:

a)从/proc/device-tree中的实时 DT 状态生成它:

dtc -I fs -O dtb -o base.dtb/proc /设备树

这将包括您到目前为止已应用的所有叠加层和参数,无论是在 config .txt中还是通过在运行时加载它们,都可能是您想要的,也可能不是。或者...

b)从/boot中的源 DTB 复制它。这将不包括覆盖和参数,但也将不包括固件的任何其他修改。为了允许测试所有覆盖,dtmerge实用程序将创建一些特定于板的别名(" i2c_arm"等),但这意味着合并的结果将比原始 DTB 包含更多的差异。期望。解决方案是使用 dtmerge 进行复制:

dtmerge /boot/bcm2710-rpi-3-b.dtb base.dtb-

("-"表示缺少覆盖名称)。

现在,您可以尝试应用叠加层或参数:

dtmerge base.dtb merged.dtb-sd_overclock = 62
dtdiff base.dtb merged.dtb

它将返回:

---/dev/fd/63 2016-05-16 14:48:26.396024813 +0100
+++/dev/fd/62 2016-05-16 14:48:26.396024813 +0100
@@ -594,7 +594,7 @@
                };

                sdhost @ 7e202000 {
- brcm,overclock-50 = <0x0>;
+ brcm,overclock-50 = <0x3e>;brcm,pio-limit = <0x1>;总线宽度= <0x4>;
                        时钟= <0x8>;

您还可以比较不同的叠加层或参数。

dtmerge base.dtb merged1.dtb /boot/overlays/spi1-1cs.dtbo
dtmerge base.dtb merged2.dtb /boot/overlays/spi1-2cs.dtbo
dtdiff merged1.dtb merged2.dtb

要得到:

---/dev/fd/63 2016-05-16 14:18:56.189634286 +0100
+++/dev/fd/62 2016-05-16 14:18:56.189634286 +0100
@@ -453,7 +453,7 @@

                        spi1_cs_pins {
                                brcm,function = <0x1>;
- brcm,pins = <0x12>;
+ brcm,引脚= <0x12 0x11>;
                                phandle = <0x3e>;
                        };

@@ -725,7 +725,7 @@
                        #size-cells = <0x0>;
                        时钟= <0x13 0x1>;
                        兼容=" brcm,bcm2835-aux-spi";
- cs-gpios = <0xc 0x12 0x1>;
+ cs-gpios = <0xc 0x12 0x1 0xc 0x11 0x1>;
                        中断= <0x1 0x1d>;
                        linux,phandle = <0x30>;
                        phandle = <0x30>;
@@ -743,6 +743,16 @@
                                spi-max-frequency = <0x7a120>;
                                状态="好";
                        };
+
+ spidev @ 1 {
+#address-cells = <0x1>;
+#size-cells = <0x0>;
+兼容=" spidev";
+ phandle = <0x41>;
+ reg = <0x1>;
+ spi-max-frequency = <0x7a120>;
+状态="好";
+};
                };

                spi @ 7e2150C0 {

Utils存储库包含另一个 DT 实用程序-" ovmerge"。与dtmerge不同,ovmerge合并文件并以源格式应用叠加层。由于覆盖图从未编译过,因此标签会保留下来,并且结果通常更具可读性。它还具有许多其他技巧,例如列出文件包含顺序的能力。

4.3:强制使用特定的设备树

如果您有默认 DTB 所不支持的非常特定的需求,或者您只是想尝试编写自己的 DT ,则可以告诉加载程序像这样加载另一个 DTB 文件:

device_tree = my-pi.dtb

4.4:禁用设备树使用

由于切换到 4 .4内核并使用更多的上游驱动程序,因此在 Pi Linux内核中需要使用设备树。但是,对于裸机和其他操作系统,禁用 DT 使用的方法是添加:

device_tree =

config.txt

4.5:快捷方式和语法变体

加载程序了解一些快捷方式:

dtparam = i2c_arm = on
dtparam = i2s = on

可以缩短为:

dtparam = i2c,i2s

(" i2c"是" i2c_arm"的别名,并且假定" = on")。它也仍然接受长版本:device_tree_overlay和 device _tree_param。

4.6:其他 DT 命令可在 config .txt中使用

device_tree_address 这用于覆盖固件加载设备树的地址(不是 dt -blob)。默认情况下,固件将选择合适的位置。

device_tree_end 这为加载的设备树设置了(独占)限制。默认情况下,设备树可以增长到可用内存的末尾,几乎可以肯定这是必需的。

dtdebug 如果非零,请打开一些额外的日志记录以进行固件的设备树处理。

enable_uart 启用主/控制台 UART (Pi 3上为 ttyS0 ,否则为 ttyAMA0 -除非与诸如 miniuart -bt的覆盖层交换)。如果主 UART 是 ttyAMA0 ,则 enable _uart默认为 1 (启用),否则默认为 0 (禁用)。这是因为有必要阻止核心频率的变化,这会使 ttyS0 无法使用,因此`enable_uart = 1'意味着 core _freq = 250(除非 force _turbo = 1)。在某些情况下,这会影响性能,因此默认情况下处于禁用状态。有关 UART 的更多详细信息,请参见here

overlay_prefix 指定从中加载覆盖的子目录/前缀-默认为" overlays /"。注意尾随" /"。如果需要,您可以在最后的" /"之后添加一些内容,以便为每个文件添加前缀,尽管这可能不是必需的。

DT可以控制其他端口,有关更多详细信息,请参见第 3 节

4.7进一步的帮助

如果您已通读本文档,但未找到设备树问题的答案,则可以获取帮助。通常可以在树莓派论坛上找到作者,特别是在Device Tree论坛上。