10. Linux设备树插件

Linux4.4以后引入了动态设备树(Dynamic DeviceTree),我们这里翻译为“设备树插件”。 设备树插件可以理解为主设备树的“补丁”它动态的加载到系统中,并被内核识别。 例如我们要在系统中增加RGB驱动,那么我们可以针对RGB这个硬件设备写一个设备树插件, 然后编译、加载到系统即可,无需从新编译整个设备树。

设备树插件是在设备树基础上增加的内容,我们之前讲解的设备树语法完全适用, 甚至我们可以直接将之前编写的设备树节点复制到设备树插件里。具体使用方法介绍如下。

10.1. 设备树插件格式

设备树插件拥有相对固定的格式,甚至可以认为它只是把设备节点加了一个“壳”编译后内核能够动态加载它。 格式如下,具体节点省略。

设备树插件基本格式1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/dts-v1/;
/plugin/;

 / {
        fragment@0 {
            target-path = "/";
            __overlay__ {
                /*在此添加要插入的节点*/
                .......
            };
        };

        fragment@1 {
            target = <&XXXXX>;
            __overlay__ {
                /*在此添加要插入的节点*/
                .......
            };
        };
    .......
 };
  • 第1行: 用于指定dts的版本。

  • 第2行: 表示允许使用未定义的引用并记录它们,设备树插件中可以引用主设备树中的节点,而这些“引用的节点”对于设备树插件来说就是未定义的,所以设备树插件应该加上“/plugin/”。

  • 第6行: 指定设备树插件的加载位置,默认我们加载到根节点下,既“target-path =“/”,或者使用target = <&XXXXX>,增加节点或者属性到某个节点下。

  • 第7-8行: 我们要插入的设备及节点或者要引用(追加)的设备树节点放在__overlay__ {…}内,可以增加、修改或者覆盖主设备树的节点。

另外一种设备树插件格式:

设备树插件基本格式2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/dts-v1/;
/plugin/;

&{/} {
    /*此处在根节点"/"下,添加要插入的节点或者属性*/
};

&XXXXX {
    /*此处在节点"XXXXX"下,添加要插入的节点或者属性*/
};

以上两种格式的设备树插件都可以使用,本章的实验以第一种格式为例。

10.2. 设备树插件加载

设备树插件的加载是通过uboot,流程如下:

设备树插件加载例图
  • 编写设备树插件源文件,通过DTC工具编译生成.dtbo文件,存储在boot分区;

  • 加载boot分区的设备树插件到内存;

  • 在uboot中,合并设备树插件dtbo和设备树dtb文件为一个设备树,并得到内存指定地址;

  • 启动内核,传递设备树在内存中的地址。

10.3. 设备树插件实验

为避免冲突,需要 删除 上一章节在主设备树上添加的led_test节点,然后编译设备树并替换板卡的同名文件,此操作请参考上一章节自行操作。

10.3.1. 硬件介绍

本节实验使用Lubancat_RK系列板卡进行实验操作。

10.3.2. 设备树插件编写和加载

本章的示例代码目录为: linux_driver/10_device_tree_overlays

在内核源码/arch/arm64/boot/dts/rockchip/overlays目录下添加名为lubancat-led-overlay.dts的设备树插件文件,内容参考如下:

lubancat2板卡led灯GPIO0_C7为例,设备树插件编写如下:

led设备树插件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/";

        __overlay__ {
            led_test: led_test{
                compatible = "fire,led_test";
                #address-cells = <1>;
                #size-cells = <1>;

                led@0xfdd60000{ //RK3568的GPIO0基地址
                    reg = <0xfdd60000 0x00000004>;
                    high-low-position = <1>;    //0表示是低16位引脚,1表示高16位引脚
                    led-pin = <7>;              //引脚偏移
                };
            };
        };
    };
};

以lubancat4板卡led灯GPIO4_B5为例,设备树插件编写如下:

led设备树插件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target-path = "/";

        __overlay__ {
            led_test: led_test{
                compatible = "fire,led_test";
                #address-cells = <1>;
                #size-cells = <1>;
                led@0xfec50000{ //RK3588S的GPIO4基地址
                    reg = <0xfec50000 0x00000004>;
                    high-low-position = <0>;     //0表示是低16位引脚,1表示高16位引脚
                    led-pin = <13>;              //引脚偏移
                };
            };
        };
    };
};

以上内容和上一章节区别不大,只是根据设备树插件的编写格式进行修改。

  • 第6行: 指定设备树插件的加载位置,加载到根节点下。

  • 第8-19行: 我们要插入的设备及节点或者要引用(追加)的设备树节点放在__overlay__ {…}内,将上一章节主设备树的test_led节点添加于此。

修改内核目录/arch/arm64/boot/dts/rockchip/overlays下的Makefile文件, 添加我们编辑好的设备树插件, 并把设备树插件文件放在和Makefile文件同级目录下,以进行设备树插件的编译。

makefile添加led设备树插件编译

然后在内核源码顶层目录执行以下命令编译设备树插件:

1
2
3
#这里以rk356x系列4.19.232内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs

提示

其余系列板卡参考 使用内核的构建脚本编译设备树插件 章节进行编译。

编译出来的设备树插件位于 内核源码/arch/arm64/boot/dts/rockchip/overlay/lubancat-led-overlay.dtbo, 将设备树插件先传到板卡,再拷贝到板卡的 /boot/dtb/overlay/ 目录下。

1
2
3
4
#先传输到板卡

#再拷贝到板卡的/boot/dtb/overlay/目录下
sudo cp -f lubancat-led-overlay.dtbo /boot/dtb/overlay/

然后在 /boot/uEnv/uEnv.txt 按照格式添加我们的设备树插件,需要在#overlay_start和#overlay_end之间添加,然后重启开发板,那么系统就会加载我们编译的设备树插件。

uEnv添加led设备树插件

重启板卡可以在uboot启动信息中看到设备树插件加载。

../_images/device_tree09.jpg

10.3.3. 驱动代码

驱动部分和上一章节完全一样,此处不做过多说明,区别只是上一章节使用设备树,本章节使用设备树插件,原理是一样的。

10.3.4. 实验操作

在本节实验中,鲁班猫系列板卡,系统设备树中均默认使能了 LED 的设备功能,需要关闭设备树的leds节点,可以修改leds节点的 status = "okay";status = "disabled";,然后编译设备树进行替换,也可以在板卡中直接使用以下命令关闭系统leds驱动对LED的控制:

1
2
3
4
5
6
7
8
#心跳灯命名可能为sys_status_led或sys_led,需先确认
ls /sys/class/leds/

#如果为sys_status_led
sudo sh -c 'echo 0 > /sys/class/leds/sys_status_led/brightness'

#如果为sys_led
sudo sh -c 'echo 0 > /sys/class/leds/sys_led/brightness'

将led的亮度调为0,与此同时led的触发条件自动变为none,从而取消leds驱动对LED的控制。

重启后在目录/proc/device-tree/下,可以找到led_test,如下所示:

1
2
3
4
5
#查看设备树节点目录
ls /proc/device-tree/led_test

#信息输出如下
'#address-cells'  '#size-cells'   compatible   led@0xfdd60000   name   phandle

执行以下命令加载驱动:

1
2
3
4
5
6
7
8
#加载驱动
sudo insmod dts_led.ko

#信息输出如下
[  191.438585] led platform driver init
[  191.439040] led platform driver probe
[  191.439099] GPIO_BASE address: 0xFDD60000
[  191.439193] major=236, minor=0

可以看到驱动加载时打印了一次led platform driver probe,说明匹配成功,打印了GPIO基地址为0xFDD60000,与设备树插件配置的一致。

通过驱动代码,最后会在/dev下创建led设备,可以使用echo命令来测试我们的led驱动是否正常。 我们使用以下命令控制灯的亮灭:

1
2
3
4
5
#控制灯亮
sudo sh -c "echo 0 > /dev/dts_led"

#控制灯灭
sudo sh -c "echo 1 > /dev/dts_led"