最近工作的时候遇到了要用到覆盖设备树的驱动,刚开始不知道有这东西,所有被折腾了好久。由于这东西平时用得也比较少,所有网上能查到的资料也比较有限。我这里就整理一下我学习到的关于Devices-tree overlays的一些东西。
一、设备树插件基本概念
一个常规的设备树主要由源文件 .dts 和头文件 .dtsi 以及一些 .h 文件共同编译出可以由Linux系统加载的 .dtb二进制文件,内核就会在初始化后根据 uboot 加载这个 .dtb 的二进制文件,然后根据设备树的节点把相关的硬件设备对应的驱动注册好。这个我们可以称之为 live tree。
传统的设备树一般用于硬件资源已经确定的情况,但如果要添加或者删除硬件资源,需要找出已经在设备中使用的设备树源文件然后在源文件上修改,内核为了解决这个问题,就提出了Device Tree Overlays,中文上可以理解为“设备树插件”。其核心是通过传统的设备树语法,使得各个硬件模块的信息可以独立地用新的设备树语法来描述。在系统实际使用时,根据实际应用情景,需要用到哪些硬件模块就把对应的设备树插件加入到主设备树即可。
设备树插件拥有相对固定的格式,可以认为它只是把设备节点加了一个“壳”编译后内核能够动态加载它。我们只需要根据格式编写即可,以IMX8 下载FPGA 为例,其编写格式如下:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&fpga_region0>;
#address-cells = <1>;
#size-cells = <1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <1>;
firmware-name = "test1.bit";
};
};
在新版的内核中,为了更便于原设备树的读写,取消了fragment 和 overlay 两个关键字,上述代码改写成了:
/dts-v1/;
/plugin/;
&fpga_region0 {
#address-cells = <1>;
#size-cells = <1>;
firmware-name = "test1.bit";
};
在原设备树中存在fpga_region0的节点:
fpga-region0 {
compatible = "fpga-region";
fpga-mgr = <&fpga_mgr_spi>;
#address-cells = <0x1>;
#size-cells = <0x1>;
};
可以看到,插件设备树的语法与普通设备树并没有任何差别,只是在开头增加了 /plugin/;
语句来标识这是一个插件设备树。
设备树插件中各个关键字的解释如下:
/dts-v1/; | 用于指定设备树的版本 |
---|---|
/plugin/; | 标识这个设备树是一个插件,可以引用未在这个设备树中定义的在主设备树上存在的节点 |
fragment | 允许开发人员创建一个部分性质的设备树文件。这个“部分”可以被其他设备树文件包含和重用,在包含过程中,原始的设备树文件可以被覆盖或者替换。 |
target = <&fpga_region0>; | 定义要将片段添加到主设备树上的目标节点。在这里,目标节点是名为"fpga_region0"的节点。 |
overlay | 用于在设备树中定义一个覆盖层(overlay)。覆盖层是一种动态修改设备树的方法,可以在运行时添加、删除或修改设备节点。使用覆盖层可以方便地进行设备树的调试和修改,而不需要重新编译整个设备树。 |
二、设备树的编译
一般来说,我们可以使用如下命令将dts
文件编译成dtb
文件:
dtb -I dts -O dtb -o test.dtb test.dts
但是,一般情况下,我们这样编译成的dtb文件是不能对其打dtbo
补丁的。要判断一个dtb
文件是否可以打dtbo
补丁,可以将该dtb
文件反编译成dts
文件,在反编译生成的dts
文件的最后如果包含有 __symbols__
字段,则该dtb可以打dtbo
补丁,否则不可以打dtbo
补丁。
__symbols__ {
cpu_pd_wait = "/cpus/idle-states/cpu-pd-wait";
A53_0 = "/cpus/cpu@0";
A53_1 = "/cpus/cpu@1";
A53_2 = "/cpus/cpu@2";
A53_3 = "/cpus/cpu@3";
A53_L2 = "/cpus/l2-cache0";
a53_opp_table = "/opp-table";
ddr_pmu0 = "/ddr_pmu@3d800000";
edacmc = "/memory-controller@3d400000";
gic = "/interrupt-controller@38800000";
……
};
为此,我们可以用如下命令将dts
文件编译成可以打dtbo
补丁的dtb
文件:
dtc -O dtb -o OK8MP-C.dtb -b 0 -@ OK8MP-C.dts
使用这个命令编译出来的.dtb
文件就是包含了__symbols__
字段的二进制设备树文件。
然而对于很多dts
设备树文件,其往往又包含有一些dtsi
文件和.h
文件,对于 include
的这些文件,dtc
工具是无法识别其内容的,如果直接使用上面的命令去编译,则会在 #include
处报错语法错误。
对于使用#include
指令链接将多个文件在一起的dts
文件。在将设备树源馈送到编译器 (DTC) 之前,必须对顶级 DTS
进行预处理,以将所有源合并到单个 DTS
中。这可以使用标准的GNU C
编译器来完成。我们可以在源代码根目录下使用下面的命令对设备树源文件进行预处理:
gcc -I include/ -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o OK8MP-C.dts arch/arm64/boot/dts/freescale/OK8MP-C.dts
下面是每个参数的解释:
- -I include/: 添加 include 文件夹路径 include/ 使得预处理器可以找到相关文件(头文件)
- -E: 指示编译器只运行预处理器,并将处理结果输出到 stdout。
- -nostdinc: 这个选项表明不要在标准 include 路径上搜索头文件。
- -undef: 不处理 #undef 指令。
- -D__DTS__: 定义名为 DTS 的宏,该选项可以作为编译时常量使用。
- -x assembler-with-cpp: 告诉 GCC 将输入文件视为一种混合语言,其中包含了汇编和 C 代码。
- -o OK8MP-C.dts : 将输出存储到 OK8MP-C.dts 文件中。
- arch/arm64/boot/dts/freescale/OK8MP-C.dts: 输入的 DTS 文件的名称。
通过预处理将#include
的多个设备树文件整合到一起后,就可以通过上述命令生成支持dtbo
补丁的二进制dtb
文件了。
将dtsi
文件编译成dtbo
文件的命令与上面的命令也类似,命令如下:
dtc -I dtsi-O dtb -o test.dtbo -b 0 -@ test.dtsi
三、设备树插件的加载
要使用设备树插件,首先我们要在内核中启用dtbo
的支持。
同时配置 CONFIG_OF_OVERLAY = y
。
然后将dtb
文件和dtbo
文件拷到开发板上,按照如下步骤配置 uboot
:
# 1. 找到基本设备树和设备树插件的位置,设置地址
=> setenv fdtaddr 0x87f00000
=> setenv fdtovaddr 0x87fc0000
# 2. 加载基本设备树和设备树插件的二进制文件
=> load ${devtype} ${bootpart} ${fdtaddr} ${bootdir}/base.dtb
=> load ${devtype} ${bootpart} ${fdtovaddr} ${bootdir}/overlay.dtbo
# 3. 将基础二进制设备树设置为工作fdt树。
=> fdt addr $fdtaddr
# 4. Grow it enough so it can encompass all applied overlays
=> fdt resize 8192
# 5. apply the overlay.
=> fdt apply $fdtovaddr
# 6. boot
=> bootm ${kerneladdr} - ${fdtaddr}
通过如下命令应用dtbo
的属性。
mkdir /configfs
mount -t configfs configfs /configfs
cd /configfs/device-tree/overlays/
mkdir full
echo -n "pl.dtbo" > full/path
参考文献: