树莓派 RaspberryPi - 设备树,覆盖和参数
Raspberry Pi内核和固件使用设备树(DT)来描述Pi中存在的硬件。这些设备树可能包含DT参数,这些参数提供了对某些板载功能部件的控制程度。DT覆盖层允许描述和配置可选的外部硬件,并且它们还支持参数以实现更好的控制。
固件加载器(start.elf
及其变体)负责加载DTB(Device Tree Blob-机器可读的DT文件)。它根据电路板修订版号选择加载哪个,并进行某些修改以进一步调整它(内存大小,以太网地址等)。这种运行时自定义避免了仅需细微差别的大量DTB的需求。
config.txt
扫描用户提供的参数以及所有叠加层及其参数,然后将其应用。加载程序检查结果以了解(例如)哪个UART(如果有)将用于控制台。最后,它启动内核,将指针传递给合并的DTB。
设备树(Device Trees)
设备树(DT)是对系统中硬件的描述。它应包括基本CPU的名称,其内存配置以及所有外围设备(内部和外部)。DT不应用于描述软件,尽管通过列出硬件模块通常会导致加载驱动程序模块。记住DT应该与操作系统无关,因此不应该有任何特定于Linux的东西。
设备树将硬件配置表示为节点的层次结构。每个节点都可以包含属性和子节点。属性被命名为字节数组,可以包含字符串,数字(大端),任意字节序列及其任意组合。类似于文件系统,节点是目录,属性是文件。可以使用路径描述树中节点和属性的位置,并使用斜杠作为分隔符,并使用单个斜杠(/
)表示根。
1.1:基本DTS语法
设备树通常以称为设备树源(Device Tree Source - DTS)的文本形式编写,并存储在带有.dts
后缀的文件中。DTS语法类似于C,每行末尾都有用于分组的分号和分号。请注意,DTS在右花括号后需要分号:考虑C struct
而不是函数。编译后的二进制格式称为扁平化设备树(FDT)或设备树Blob(DTB),并存储在.dtb
文件中。
以下是.dts
格式简单的树:
/dts-v1/;
/include/ "common.dtsi";
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
cousin: child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
my-cousin = <&cousin>;
};
};
};
/node2 {
another-property-for-node2;
};
该树包含:
- 必填标头:
/dts-v1/
。 - 包含另一个DTS文件,该文件通常按惯例命名
*.dtsi
并类似于.h
C中的头文件-参见下面的/ include /。 - 单个根节点:
/
- 几个子节点:
node1
和node2
- node1的一些子代:
child-node1
和child-node2
- 标签(
cousin
)和对该标签的引用(&cousin
):请参见下面的标签和引用。 - 分散在树上的几个属性
- 重复的节点(
/node2
)-参见下面的/ include /。
属性是简单的键/值对,其中值可以为空或包含任意字节流。虽然数据类型未在数据结构中编码,但是可以在设备树源文件中表达一些基本数据表示形式。
文本字符串(NUL终止)用双引号表示:
string-property = "a string";
单元格是由尖括号分隔的32位无符号整数:
cell-property = <0xbeef 123 0xabcd1234>;
任意字节数据用方括号定界,并以十六进制输入:
binary-property = [01 23 45 67 89 ab cd ef];
可以使用逗号将不同表示形式的数据连接起来:
mixed-property = "a string", [01 23 45 67], <0x12345678>;
逗号还用于创建字符串列表:
string-list = "red fish", "blue fish";
1.2:关于/ include /
该/include/
指令导致简单的文本包含,与C的#include
指令非常相似,但是Device Tree编译器的功能导致了不同的使用模式。给定节点的名称(可能带有绝对路径),则同一个节点可能在DTS文件(及其包含文件)中出现两次。发生这种情况时,将节点和属性组合在一起,并根据需要对属性进行交织和覆盖(后面的值会覆盖前面的值)。
在上面的示例中,第二次出现/node2
导致将新属性添加到原始属性:
/node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
another-property-for-node2;
child-node1 {
my-cousin = <&cousin>;
};
};
因此,有可能.dtsi
覆盖一棵树中的多个位置或为其提供默认值。
1.3:标签和参考
通常需要树的一部分引用另一部分,并且有四种方法可以做到这一点:
-
路径字符串
类似于文件系统,路径应该是不言自明的-
/soc/i2s@7e203000
是BCM2835和BCM2836中I2S设备的完整路径。请注意,尽管构造属性的路径很容易(例如/soc/i2s@7e203000/status
),但是标准API却不这样做。您首先找到一个节点,然后选择该节点的属性。 -
把手
phandle是在其
phandle
属性中分配给节点的唯一32位整数。由于历史原因,您可能还会看到一个多余的匹配项linux,phandle
。把手从1开始顺序编号;0不是有效的个人。当它们在整数上下文中遇到对节点的引用时,通常由DT编译器分配它们,通常以标签的形式(请参见下文)。使用句柄对节点的引用被简单地编码为相应的整数(单元)值。没有标记指示应将它们解释为phandles,因为这是应用程序定义的。 -
标签
就像C中的标签为代码中的位置提供名称一样,DT标签也为层次结构中的节点分配名称。编译器获取对标签的引用,并将其在字符串上下文(
&node
)中使用时转换为路径,而在整数上下文(<&node>
)中使用处理。原始标签不会出现在编译输出中。请注意,标签不包含任何结构。它们只是一个统一的全局命名空间中的令牌。 -
别名
别名与标签类似,不同的是别名确实以索引形式出现在FDT输出中。它们存储为
/aliases
节点的属性,每个属性将别名映射到路径字符串。尽管别名节点出现在源中,但路径字符串通常显示为对标签(&node
)的引用,而不是全部写出。将节点的路径字符串解析为DT API时,通常会查看路径的第一个字符,将不以斜杠开头的路径作为别名,必须先使用/aliases
表将其转换为路径。
1.4:设备树语义
如何构造设备树,以及如何最好地使用它来捕获某些硬件的配置是一个大而复杂的主题。有很多可用的资源,下面列出了其中的一些资源,但是在本文档中有几点需要提及:
compatible
属性是硬件描述和驱动程序软件之间的链接。当操作系统遇到具有compatible
属性的节点时,它将在其设备驱动程序数据库中查找该节点以找到最佳匹配项。在Linux中,如果已正确标记驱动程序模块且未将其列入黑名单,这通常会导致驱动程序模块自动加载。
该status
属性指示设备是启用还是禁用。如果status
是ok
,okay
或不存在,则该设备已启用。否则,status
应为disabled
,以便禁用设备。将设备放置在.dtsi
状态为的文件中可能很有用disabled
。然后,派生的配置可以包括该配置,.dtsi
并设置所需设备的状态okay
。
第2部分:设备树覆盖
现代的SoC(片上系统)是非常复杂的设备。完整的设备树可能长达数百行。再往前走一步,将SoC与其他组件一起放在板上,只会使情况变得更糟。为了保持可管理性,特别是在有共享组件的相关设备的情况下,将公共元素放入.dtsi
文件中是很有意义的,以便可能包含多个.dts
文件。
当像Raspberry Pi这样的系统也支持可选的插件附件(例如HAT)时,问题就越来越大。最终,每种可能的配置都需要一个设备树来描述它,但是一旦您考虑了所有不同的基本模型和大量可用的附件,组合的数量就会开始迅速增加。
所需要的是一种使用部分设备树描述这些可选组件的方法,然后通过采用基本DT并添加许多可选元素来构建完整的树。您可以执行此操作,这些可选元素称为“叠加层”。
除非您想学习如何为Raspberry Pi编写覆盖图,否则您可能更喜欢跳到第3部分:在Raspberry Pi上使用设备树。
2.1:片段
DT覆盖包括多个片段,每个片段都针对一个节点及其子节点。尽管这个概念听起来很简单,但语法一开始似乎很奇怪:
// Enable the i2s interface
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&i2s>;
__overlay__ {
status = "okay";
test_ref = <&test_label>;
test_label: test_subnode {
dummy;
};
};
};
};
该compatible
字符串将其标识为BCM2835,这是Raspberry Pi SoC的基本体系结构。如果覆盖层利用Pi 4的功能,brcm,bcm2711
则该值是要使用的正确值,否则brcm,bcm2835
可以用于所有Pi覆盖层。然后是第一个(仅在这种情况下)片段。片段应从零开始顺序编号。不遵守此规定可能会导致丢失部分或全部片段。
每个片段由两部分组成:一个target
属性,标识要对其应用叠加层的节点;以及其__overlay__
本身,其主体将添加到目标节点。可以将上面的示例解释为如下所示:
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
};
&i2s {
status = "okay";
test_ref = <&test_label>;
test_label: test_subnode {
dummy;
};
};
(实际上,有了一个足够新的版本,dtc
您可以完全像这样编写它并获得相同的输出,但是某些本地开发的工具尚不了解这种格式,因此,您可能希望包含在标准Raspbian内核中的任何覆盖都应该是暂时以旧格式编写)。
如果bcm2708-rpi-b-plus.dtb
覆盖层是在以后加载的,那么将覆盖层与标准的Raspberry Pi基本设备树(例如)合并,将会通过将I2S接口的状态更改为okay
来启用I2S接口。但是,如果您尝试使用以下命令编译此叠加层:
dtc -I dts -O dtb -o 2nd.dtbo 2nd-overlay.dts
你会得到一个错误:
Label or path i2s not found
这不应太出乎意料,因为没有引用基础.dtb
或.dts
文件以允许编译器找到i2s
标签。
再试一次,这次使用原始示例,并添加-@
选项以允许未解析的引用(并-Hepapr
消除一些混乱):
dtc -@ -Hepapr -I dts -O dtb -o 1st.dtbo 1st-overlay.dts
如果dtc
返回有关第三行的错误,则它没有覆盖工作所需的扩展名。运行并重sudo apt install device-tree-compiler
试-这次,编译应成功完成。请注意,在内核树中还可以使用合适的编译器,该编译器是在使用make目标scripts/dtc/dtc
时构建的dtbs
:
make ARCH=arm dtbs
转储DTB文件的内容以查看编译器生成的内容很有趣:
$ fdtdump 1st.dtbo
/dts-v1/;
// magic: 0xd00dfeed
// totalsize: 0x207 (519)
// off_dt_struct: 0x38
// off_dt_strings: 0x1c8
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0x3f
// size_dt_struct: 0x190
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <0xffffffff>;
__overlay__ {
status = "okay";
test_ref = <0x00000001>;
test_subnode {
dummy;
phandle = <0x00000001>;
};
};
};
__symbols__ {
test_label = "/fragment@0/__overlay__/test_subnode";
};
__fixups__ {
i2s = "/fragment@0:target:0";
};
__local_fixups__ {
fragment@0 {
__overlay__ {
test_ref = <0x00000000>;
};
};
};
};
在文件结构的详细描述之后,是我们的片段。但是请仔细观察-我们&i2s
现在写的位置说0xffffffff
,这暗示着发生了奇怪的事情(旧版本的dtc可能会说0xdeadbeef
)。编译器还添加了一个phandle
属性,该属性包含一个唯一的(对此覆盖图)小整数以指示该节点具有标签,并用相同的小整数替换了对该标签的所有引用。
片段之后有三个新节点:
-
__symbols__
列出叠加层中使用的标签(test_label
此处),以及标记节点的路径。该节点是如何处理未解析符号的关键。 -
__fixups__
包含一个属性列表,该属性列表将未解析符号的名称映射到片段中需要用目标节点的模型修补的单元格路径列表。在这种情况下,路径是的0xffffffff
值target
,但是片段可以包含其他未解析的引用,这将需要其他修复。 -
__local_fixups__
保存覆盖层中存在的对标签的所有引用的位置-test_ref
属性。这是必需的,因为执行合并的程序将必须确保角色编号是连续且唯一的。
在第1.3节中,它说“原始标签未出现在编译输出中”,但这在使用-@
开关时并非如此。相反,每个标签都会在__symbols__
节点中产生一个属性,将标签映射到与aliases
节点完全相同的路径。实际上,该机制是如此相似,以至于在解析符号时,Raspberry Pi加载器将在不存在节点的情况下搜索“别名” __symbols__
节点。这一次很有用,因为提供足够的别名允许使用非常老的版本dtc
来构建基本DTB文件,但幸运的是,这已经是古老的历史了。
2.2:设备树参数
为了避免需要大量的设备树覆盖,并减少外围设备用户修改DTS文件的需求,Raspberry Pi加载程序支持一项新功能-设备树参数。这允许使用命名参数对DT进行少量更改,类似于内核模块从modprobe
内核命令行接收参数的方式。基本DTB和覆盖(包括HAT覆盖)可以暴露参数。
在DTS中,通过将__overrides__
节点添加到根来定义参数。它包含一些属性,这些属性的名称是所选的参数名称,其值是一个序列,该序列包括目标节点的phandle(对标签的引用)和指示目标属性的字符串;支持字符串,整数(单元格)和布尔属性。
2.2.1:字符串参数
字符串参数声明如下:
name = <&label>,"property";
其中label
和property
被适当的值替换。字符串参数可以导致其目标属性增长,缩小或创建。
注意,被调用status
的属性会被特别对待;非零/ true / yes / on值转换为字符串"okay"
,而零/ false / no / off变为"disabled"
。
2.2.2:整数参数
整数参数的声明如下:
name = <&label>,"property.offset"; // 8-bit
name = <&label>,"property;offset"; // 16-bit
name = <&label>,"property:offset"; // 32-bit
name = <&label>,"property#offset"; // 64-bit
其中label
,property
和offset
被适当的值替换;相对于属性开始的偏移量以字节为单位(默认为十进制),并且前面的分隔符指定参数的大小。与以前的实现方式相比,整数参数可以引用不存在的属性,也可以引用超出现有属性结尾的偏移量。
2.2.3:布尔参数
设备树将布尔值编码为零长度属性;如果存在,则该属性为true,否则为false。它们的定义如下:
boolean_property; // Set 'boolean_property' to true
请注意,false
通过未定义属性来为属性分配值。布尔参数声明如下:
name = <&label>,"property?";
其中label
和property
被适当的值替换。
反转的布尔值在应用输入值之前,先将其转换为与常规布尔值相同的值。它们的声明方式类似,但是用于!
表示反转:
name = <&label>,"property!";
布尔参数可以导致创建或删除属性,但是它们不能删除基本DTB中已经存在的属性。
2.2.4:字节字符串参数
字节字符串属性是字节的任意序列,例如MAC地址。它们接受十六进制字节的字符串,字节之间带有或不带有冒号。
mac_address = <ðernet0>,"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
叠加层)
请注意,甚至可以使用单个参数来定位不同类型的属性。您可以合理地将“启用”参数连接到status
字符串,包含零或一的单元格以及适当的布尔属性。
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
bytes = <&target>,"bytestr[=b8:27:eb:01:23:45"; // 4
第1、2和4行相当明显,但是第3行更有趣,因为该值显示为整数(单元格)值。DT编译器在编译时评估整数表达式,这可能很方便(特别是如果使用了宏值),但是单元格还可以包含对标签的引用:
// Force an LED to use a GPIO on the internal GPIO controller.
exp_led = <&led1>,"gpios:0=",<&gpio>,
<&led1>,"gpios:4";
当应用叠加时,将以通常的方式针对基本DTB解析标签。请注意,将多部分参数分割成多行是一个好主意,这样可以使它们更易于阅读-随着像这样的单元格值分配,这变得更加必要。
请记住,除非应用参数,否则它们将不执行任何操作-除非使用参数名称而不分配值,否则查找表中的默认值将被忽略。
2.2.7:查找表
查找表允许在使用参数输入值之前对其进行转换。它们充当关联数组,就像switch / case语句一样:
phonetic = <&node>,"letter{a=alpha,b=bravo,c=charlie,d,e,='tango uniform'}";
bus = <&fragment>,"target:0{0=",<&i2c0>,"1=",<&i2c1>,"}";
不能=value
使用该键作为值的=
键,在不匹配的情况下,键之前没有键是默认值,并且以逗号开头(或在任何地方为空的键=值对)表示列表结束不匹配的输入值应原样使用;否则,找不到匹配项是错误的。
请注意,单元格整数值后的表字符串中的逗号分隔符是隐式的-添加一个显式创建一个空对(请参见上文)。
注意:由于查找表对输入值进行操作,而文字分配忽略它们,因此无法将两者合并- }
在将查找声明中的结束符视为错误后,这些字符将变为错误。
2.2.8叠加/片段参数
所描述的DT参数机制具有许多限制,包括当使用参数时无法更改节点名称以及无法将任意值写入任意属性。克服其中一些限制的一种方法是有条件地包括或排除某些片段。
通过将__overlay__
节点重命名为,可以将片段从最终合并过程中排除(禁用)__dormant__
。参数声明语法已得到扩展,以允许否则为非法的零目标对象指示以下字符串包含片段或覆盖范围内的操作。到目前为止,已实施了四个操作:
+ // Enable fragment
- // Disable fragment
= // Enable fragment if the assigned parameter value is true, otherwise disable it
! // Enable fragment if the assigned parameter value is false, otherwise disable it
例子:
just_one = <0>,"+1-2"; // Enable 1, disable 2
conditional = <0>,"=3!4"; // Enable 3, disable 4 if value is true,
// otherwise disable 3, enable 4.
该i2c-rtc
叠加使用这种技术。
2.2.9特殊属性
当某些属性名称由参数作为目标时,将得到特殊处理。您可能已经注意到一个status
--它将布尔值转换okay
为true和disabled
false。
分配给bootargs
属性会附加到该属性,而不是覆盖它-这是将设置添加到内核命令行的方式。
该reg
属性用于指定设备地址-内存映射的硬件块的位置,I2C总线上的地址等。子节点的名称应使用其地址以十六进制表示,并@
用作分隔符:
bmp280@76 {
reg = <0x77>;
...
};
分配给reg
属性时,父节点名称的地址部分将替换为分配的值。当多次使用相同的叠加层时,这可以用来防止节点名称冲突-叠加层使用的一种技术i2c-gpio
。
该name
属性是伪属性-不应在DT中显示,但对其进行分配会导致其父节点的名称更改为已分配的值。像该reg
属性一样,此属性可用于为节点赋予唯一名称。
2.2.10例子
以下是一些不同类型的属性的示例,并带有用于修改它们的参数:
/ {
fragment@0 {
target-path = "/";
__overlay__ {
test: test_node {
string = "hello";
status = "disabled";
bytes = /bits/ 8 <0x67 0x89>;
u16s = /bits/ 16 <0xabcd 0xef01>;
u32s = /bits/ 32 <0xfedcba98 0x76543210>;
u64s = /bits/ 64 < 0xaaaaa5a55a5a5555 0x0000111122223333>;
bool1; // Defaults to true
// bool2 defaults to false
mac = [01 23 45 67 89 ab];
spi = <&spi0>;
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
frag1;
};
};
fragment@2 {
target-path = "/";
__dormant__ {
frag2;
};
};
__overrides__ {
string = <&test>,"string";
enable = <&test>,"status";
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>,"english",
<&test>,"french{hello=bonjour,goodbye='au revoir',weekend}";
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的仓库托管覆盖源文件在这里。
2.3:导出标签
固件和运行时覆盖应用程序中使用该dtoverlay
实用程序进行的覆盖处理将覆盖中定义的标签视为该覆盖的私有标签。这避免了为标签发明全局唯一名称的需要(这使得标签简短),并且允许多次使用同一叠加层而不会发生冲突(使用了一些技巧-参见Special properties)。
但是,有时候,能够创建一个覆盖并从另一个覆盖使用标签非常有用。自2020年2月14日以来发布的固件具有将某些标签声明为全局标签的能力- __export__
节点:
...
public: ...
__exports__ {
public; // Export the label 'public' to the base DT
};
};
应用此叠加层后,加载程序会剥离除已导出的符号以外的所有符号(在本例中为)public
,并重写路径以使其相对于包含标签的片段目标。然后可以参考参考资料&public
。
2.4:覆盖申请顺序
在大多数情况下,片段的应用顺序无关紧要,但是对于自己打补丁的叠加层(片段的目标是叠加层中的标签,称为叠加层内片段),它变得很重要。在较旧的固件中,严格按照从上到下的顺序应用片段。自2020年2月14日发布固件以来,分两步应用片段:
一世。首先,应用和隐藏针对其他片段的片段。ii。然后应用常规片段。
此拆分对于运行时覆盖尤为重要,因为步骤(i)发生在dtoverlay
实用程序中,而步骤(ii)由内核执行(无法处理重叠内片段)。
第3部分:在Raspberry Pi上使用设备树
3.1:DTB,覆盖和config.txt
在Raspberry Pi上,加载程序(start.elf
图像之一)的工作是将覆盖层与适当的基本设备树结合在一起,然后将完全解析的设备树传递给内核。基本设备树位于start.elf
FAT分区(/从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的情况下才能运行。您可以通过添加device_tree=
config.txt 来覆盖它,这会强制使用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
在Raspbian安装在的固件分区中寻找/boot
。然后它将搜索参数foo
和level
,并为它们分配指示的值。
加载程序还将搜索带有已编程EEPROM的附加HAT,并从那里直接或通过“ overlays”目录中的名称加载支持的覆盖图。这是在没有任何用户干预的情况下发生的。
有几种方法可以表明内核正在使用设备树:
- 在启动过程中,“ Machine model:”内核消息具有特定于板的值,例如“ Raspberry Pi 2 Model B”,而不是“ BCM2709”。
-
/proc/device-tree
存在,并且包含与DT的节点和属性完全相同的子目录和文件。
使用设备树,内核将自动搜索并加载支持指定的已启用设备的模块。结果,通过为设备创建适当的DT覆盖,您可以节省设备用户的编辑时间/etc/modules
;所有配置都进入config.txt
,对于HAT,甚至不需要该步骤。但是请注意,诸如此类的分层模块i2c-dev
仍需要显式加载。
不利的一面是,除非DTB要求,否则不会创建平台设备,因此不再需要将由于板支持代码中定义的平台设备而被加载的模块列入黑名单。实际上,当前的Raspbian映像没有附带黑名单文件(某些WLAN设备中有多个驱动程序可用除外)。
3.2:DT参数
如上所述,DT参数是对设备配置进行小的更改的便捷方法。当前的基本DTB支持无需启用专用覆盖即可启用和控制板载音频,I2C,I2S和SPI接口的参数。在使用中,参数如下所示:
dtparam=audio=on,i2c_arm=on,i2c_arm_baudrate=400000,spi=on
请注意,可以将多个分配放在同一行上,但请确保您不超过80个字符的限制。
如果您有一个定义了一些参数的覆盖,则可以在后续行中指定它们,如下所示:
dtoverlay=lirc-rpi
dtparam=gpio_out_pin=16
dtparam=gpio_in_pin=17
dtparam=gpio_in_pull=down
或附加到叠加线,如下所示:
dtoverlay=lirc-rpi,gpio_out_pin=16,gpio_in_pin=17,gpio_in_pull=down
覆盖参数仅在作用域中,直到加载下一个覆盖。如果覆盖层和基础都导出了具有相同名称的参数,则覆盖层中的参数优先;为了清楚起见,建议您避免这样做。要公开由基本DTB导出的参数,请使用以下命令结束当前覆盖范围:
dtoverlay=
3.3:特定于电路板的标签和参数
Raspberry Pi板具有两个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 Camera或7英寸DSI显示器正常工作。
对于编写覆盖图的人员,相同的别名已应用于I2C DT节点上的标签。因此,您应该写:
fragment@0 {
target = <&i2c_arm>;
__overlay__ {
status = "okay";
};
};
任何使用数字变量的覆盖都将被修改为使用新的别名。
3.4:HAT和设备树
Raspberry Pi HAT是具有嵌入式EEPROM的附加板,专为具有40针接头的Raspberry Pi设计。EEPROM包括使电路板启用所需的任何DT覆盖层(或从归档系统加载的覆盖层名称),并且该覆盖层还可以公开参数。
HAT覆盖图由基本DTB之后的固件自动加载,因此可以访问其参数,直到加载任何其他覆盖图或使用结束覆盖图作用域为止dtoverlay=
。如果出于某种原因要禁止加载HAT覆盖,请dtoverlay=
在其他命令dtoverlay
或dtparam
指令之前放置。
3.5:动态设备树
从Linux 4.4开始,RPi内核支持动态加载覆盖和参数。兼容的内核管理一叠叠加层,这些叠加层应用在基本DTB的顶部。更改会立即反映出来,/proc/device-tree
并可能导致模块加载,平台设备创建和销毁。
上面的“堆栈”一词非常重要-叠加层只能在堆栈的顶部添加和删除;在堆栈的更下方进行更改需要首先删除其顶部的所有内容。
有一些用于管理覆盖图的新命令:
3.5.1 dtoverlay命令
dtoverlay
是一个命令行实用程序,可在系统运行时加载和删除覆盖,以及列出可用的覆盖并显示其帮助信息:
pi@raspberrypi ~ $ dtoverlay -h
Usage:
dtoverlay [=...]
Add an overlay (with parameters)
dtoverlay -D [] Dry-run (prepare overlay, but don't apply -
save it as dry-run.dtbo)
dtoverlay -r [] Remove an overlay (by name, index or the last)
dtoverlay -R [] Remove from an overlay (by name, index or all)
dtoverlay -l List active overlays/params
dtoverlay -a List all overlays (marking the active)
dtoverlay -h Show this usage message
dtoverlay -h Display help on an overlay
dtoverlay -h .. Or its parameters
where is the name of an overlay or 'dtparam' for dtparams
Options applicable to most variants:
-d Specify an alternate location for the overlays
(defaults to /boot/overlays or /flash/overlays)
-v Verbose operation
与config.txt
等效项不同,覆盖层的所有参数都必须包含在同一命令行中-dtparam命令仅适用于基本DTB的参数。
需要注意两点:
更改内核状态(添加和删除内容)的命令变体需要root特权,因此您可能需要在命令前加上
sudo
。只能卸载在运行时应用的叠加层和参数-固件应用的叠加层或参数会被“烘焙”,因此不会被列出
dtoverlay
,也无法删除。
3.5.2 dtparam命令
dtparam
创建并加载与在中使用dtparam指令效果大致相同的叠加层config.txt
。在用法上,它等效于dtoverlay
的覆盖名称-
,但有一些区别:
dtparam
将列出基本DTB的所有已知参数的帮助信息。仍然可以使用dtparam命令的帮助dtparam -h
。当指示要删除的参数时,只能使用索引号(不能使用名称)。
并非所有的Linux子系统都在运行时对设备的添加做出响应-I2C,SPI和声音设备可以工作,但有些则不能。
3.5.3编写具有运行时能力的叠加层的准则
该区域的文档很少,但是这里有一些累积的技巧:
设备对象的创建或删除由添加或删除的节点触发,或者由节点的状态从禁用变为启用,反之亦然。当心-缺少“状态”属性意味着该节点已启用。
不要在将覆盖基本DTB中现有节点的片段中创建节点-内核将重命名新节点以使其唯一。如果要更改现有节点的属性,请创建一个针对它的片段。
ALSA不会阻止其编解码器和其他组件在使用时被卸载。如果删除覆盖,则删除声卡仍在使用的编解码器会导致内核异常。实验发现,设备会按照覆盖中的片段顺序相反的顺序删除,因此将卡的节点放置在组件的节点之后可以有序地关闭。
3.5.4注意事项
在运行时加载叠加层是内核的新增功能,到目前为止,尚无从用户空间执行此操作的方法。通过将这种机制的细节隐藏在命令后面,目的是在不同的内核接口变得标准化的情况下,使用户免受更改的影响。
一些覆盖在运行时比其他覆盖要好。设备树的部分仅在引导时使用-使用覆盖更改它们不会有任何影响。
应用或删除某些覆盖可能会导致意外的行为,因此应谨慎操作。这是它要求的原因之一
sudo
。如果某些东西正在使用ALSA,则卸载ALSA卡的覆盖层可能会停顿-LXPanel音量滑块插件演示了这种效果。为了能够删除声卡的叠加层,该
lxpanelctl
实用程序提供了两个新选项-alsastop
和alsastart
-,并且分别从辅助脚本dtoverlay-pre
以及dtoverlay-post
在加载或卸载叠加层之前和之后调用这些选项。删除覆盖不会导致已加载的模块被卸载,但是可能导致某些模块的引用计数降至零。运行
rmmod -a
两次将导致未使用的模块被卸载。覆盖层必须以相反的顺序除去。这些命令将允许您删除较早的命令,但是所有中间的命令将被删除并重新应用,这可能会产生意想不到的后果。
/clocks
在运行时在节点下添加时钟不会导致注册新的时钟提供程序,因此devm_clk_get
对于在叠加层中创建的时钟将失败。
3.6:支持的叠加层和参数
由于在此处记录单个覆盖图太耗时,因此请参阅中的覆盖图文件旁边的自述文件。它通过添加和更改保持最新状态。.dtbo``/boot/overlays
第4部分:故障排除和专业提示
4.1:调试
加载程序将跳过缺少的覆盖和错误的参数,但是如果存在严重错误,例如基本DTB丢失或损坏或覆盖合并失败,则加载程序将退回到非DT引导。如果发生这种情况,或者您的设置不符合预期,则值得检查加载程序中的警告或错误:
sudo vcdbg log msg
额外的调试可以通过添加启用dtdebug=1
到config.txt
。
您可以这样创建人类可读的DT当前状态的表示形式:
dtc -I fs /proc/device-tree
这对于查看将覆盖层合并到基础树上的效果很有用。
如果内核模块未按预期加载,请检查它们是否未列入黑名单/etc/modprobe.d/raspi-blacklist.conf
;使用设备树时,不必将其列入黑名单。如果那什么都没有显示出来,您还可以通过搜索/lib/modules/
该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)从以下位置的实时DT状态生成它/proc/device-tree
:
dtc -I fs -O dtb -o base.dtb /proc/device-tree
这将包括您到目前为止应用的所有叠加层和参数,无论是在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>;
bus-width = <0x4>;
clocks = <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,pins = <0x12 0x11>;
phandle = <0x3e>;
};
@@ -725,7 +725,7 @@
#size-cells = <0x0>;
clocks = <0x13 0x1>;
compatible = "brcm,bcm2835-aux-spi";
- cs-gpios = <0xc 0x12 0x1>;
+ cs-gpios = <0xc 0x12 0x1 0xc 0x11 0x1>;
interrupts = <0x1 0x1d>;
linux,phandle = <0x30>;
phandle = <0x30>;
@@ -743,6 +743,16 @@
spi-max-frequency = <0x7a120>;
status = "okay";
};
+
+ spidev@1 {
+ #address-cells = <0x1>;
+ #size-cells = <0x0>;
+ compatible = "spidev";
+ phandle = <0x41>;
+ reg = <0x1>;
+ spi-max-frequency = <0x7a120>;
+ status = "okay";
+ };
};
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的更多详细信息,请参见此处
overlay_prefix
指定从中加载覆盖的子目录/前缀-默认为“ overlays /”。注意尾随“ /”。如果需要,您可以在最后的“ /”之后添加一些内容,以便为每个文件添加一个前缀,尽管这不是必需的。
DT可以控制其他端口,有关更多详细信息,请参见第3节。
4.7进一步的帮助
如果您已通读本文档,但未找到设备树问题的答案,则可以获取帮助。通常可以在Raspberry Pi论坛(尤其是Device Tree论坛)上找到作者。