摘要:是不是所有的 Linux 内核都是完美的?毕竟诸多黑客效力于此,当然不是,至少在内核 3.x 版本之前不是,之前的代码臃肿,代码利用率较低,直到设备树的引入,彻底改善这一情况;

 

一、FDT 的概念

系统启动时,Bootloader 开始加载,将内核文件,如 zImage 读取到内存中,内核按照我们的代码,逐一去配置每个寄存器,每个外设,似乎没有什么问题。但是试想一下,100 种 ARM 芯片,就要写 100 个配置文件么?当然,如果你非要这么做,我也无话可说。如果能抽象出一种数据结构,它可以直接抽象出内核需要配置的所有硬件以及硬件属性,BootLoader 预读取到内存中,在内核启动以后,可以直接配置,对于用户而言,配置 MCU 的外围时我们直接面对的就只是这个 DTS 文件,极其方便快捷。FDT 准确来讲是一种数据结构,使得硬件可以用形如 XML 的描述语言来描述。

 

二、设备树结构

 

图一 设备树结构

 

设备树一般包含以上内容:

 

    根节点“/”下的 model ,这个一般为字符串类型,它描述了厂商以及板子名称;

 

    根节点“/”下的 compitable,这个一般为字符串类型,用以匹配 model 选定的开发板对应的代码;包括后续外围驱动的匹配均是有这个 compitable 来完成;

 

    根节点“/”下的 aliases,这个设备节点只能放在根节点目录,主要用以存放外设的别名,简单讲,"/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000"其实是一个串口,但是开发人员自己看起来并不直观,我可以在 aliases 中写作:serial ="/soc/aips-bus@02000000/spba-bus@02000000/serial@02020000";serial 即可代替刚才的串口设备;

 

    根节点“/”下的 chosen:这个并非物理设备节点,而是内核启动参数的节点,类似于 uboot 阶段的 bootargs 参数;

 

当然,这个节点也可以是子节点,不一定要在根节点下;

 

实例:chosen {

        stdout-path = &uart1;

    };

    snvs@020b0000:除以上节点,剩下的我一般称之为物理设备节点(可能不准确),以 snvs 外设举例,直接举例;

 

实例:snvs@020b0000{

        conpitable = “fsl,imx6ul-snvs”;

        reg = <0x020b0000 0x4000>;

        interrupts = <0x0 0x4 0x4>;

};

 

(1)“@”后面紧跟就是该外设在 MCU 总线的地址,这个不难理解,可以理解为外设的基地址,外设模型 name@addresss;” 

 

(2)“compitable”:如上陈述,非常关键的属性,匹配外设驱动,属性模型 compitable = “[manufacture,[model]]”;

 

(3)“reg”:该属性为外设地址属性,第一个参数为该节点总线地址,后者为地址长度;

 

(4)“interrupt”:顾名思义,该外设的中断,para1 表示该中断是不是 SPI 中断(shared peripheral interrupt),注意名词区分,参数值为 1 表示为 SPI 中断,反之不是 SPI 中断;para2 是该中断号;para3 表示触发方式,参数值为 1,表示上升沿触发,为 4 表示高电平触发;如果需要低电平以及下降沿触发,硬件需要加非门; 

 

三、编译设备树与反编译

设备树编译,我们都知道使用如下命令编译:

 

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  dtbs  或者

 

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  all

 

实际上,是 dtc 这个文件在负责把 dts 解释成 dtb 文件,该文件在内核源码根目录 ./scripts/dtc

 

编译命令:

 

./scripts/dtc/dtc –I dts –O dtb /home/gyh/tmp/imx6y2c-256m.dtb  ./arch/arm/boot/dts/imx6y2c-256m.dts

 

反编译命令:

 

./scripts/dtc/dtc -I dtb -O dts -o /home/gyh/tmp/imx6y2c_asm.dts   ./arch/arm/boot/dts/imx6y2c-256m.dtb

 

对于 Linux 命令的使用,可以使用 help cmdname 或者 man cmdname,对于 dtc,非内建命令,man dtc:

 

  -I

              Input formats are:

              dts - device tree source text

              dtb - device tree blob

              fs - /proc/device-tree style directory

  -O

              Output formats are:

              dts - device tree source text

              dtb - device tree blob

              asm - assembler source

 

系统提供的 dts 一般引用 dtsi 这个母设备树,所以大量外设都是直接引用 dtsi 中的,因此很难理解这些字符串是怎样的匹配驱动程序的,但是一旦将已经生产的 dtb 文件反编译,生产的 dts 文件将更直观;但是易读性也更差。这并不矛盾;我选择,” /”  ,”chosen” ,”aliases”三个节点来对比。

 

图二 BSP 提供的 dts 文件

 

图三 反编译的 dts 文件

 

对同一个 chosen 节点:BSP 中 dts 描述为 stdout-path = &uart1;这样很难想象它是怎样把该外设定义为标准输出的,但是如果看反编译文件可以较好的理解,标准输出被重定向到某个可以作为输出的外设地址;

 

四、设备树节点添加与验证

(1)直接在 dts 文件中查找,是否已经存在你需要的外设节点;如果有,且该外设支持多从机或者多节点,直接在该节点下面,添加子节点,以 GPIO_LED 为例。

 

图四 GPIO_LEDS 节点

2)假设,你需要添加一个黄色的 LED,那么仿照已经存在的节点,复制一个节点在母节点下,命名为 green-led,同时用 GPIO3_4 为该 LED 驱动引脚;你希望在 arm 板上叫他,My_Cute(这个名字不好),那么最后修改如下:

 

图五 增加 yellow-led 节点

 

(3)节点添加完成,引用了 GPIO3_4,所以你需要确认该 MCU 引脚已经配置为 GPIO 功能,这里直接贴出配置代码:MX6UL_PAD_LCD_RESET__GPIO3_IO04 0x40017059

 

图六 引脚配置为 GPIO

 

该宏定义 MX6UL_PAD_LCD_RESET__GPIO3_IO04 在 ./arch/arm/boot/dts/imx6ull-pinfunc.h 中;针对同一个引脚的全部复用,均定义了宏,可以直接调用;该 dts 并未直接包含 imx6ull-pinfunc.h,在其他 dtsi 中已经包含该头文件;

 

(4)如果之前已经完全编译过内核,可以直接编译 dtb,注意不要 make menuconfig 或者 defconfig,否则会覆盖 zImage 的配置文件 .config; 

 

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  dtbs

 

(5)编译完成后,开发板直接进入 uboot 模式,tftp 网络烧写 dtb,reset 重启生效;

 

run updtb 

 

(备注:updtb 为组合命令 updtb=if tftp ${fdt_file}; then nand erase.part dtb; nand write ${loadaddr} dtb ${filesize}; fi;) 

 

(6)如果 dtb 按照我们理解修改是正确的,那么我们将在开发板的 /sys/class/leds 下面看到我们的 My_Cute 这个 LED 节点;结果如下:

 

图七 开发板设备截图

 

其实,可以看到 /sys/class/leds 下面的设备节点都是指向 /devices/platfome/leds 目录的连接文件,也就是这里仅仅是这个设备的“快捷方式”,我们也可以进行文件 IO 操作;

 

(7)文件 IO 操作:打开 My_Cute 节点,可以看到以下接口可以操作,但是我们在添加 GPIO_LEDS 并没有添加这些属性。Brightness, trigger—led 亮度以及触发方式比较常用,那么问题来了,为什么会有这些接口。因为它们继承了母节点的属性,所以我们需要找到母节点设备的定义。

 

图八 yellow-led 的操作接口

 

(8)讲道理,所有的内核驱动你都可以尝试在 ./driver/ 下面去找,针对 led 类,我们直接进入 leds 文件夹,发现 leds 的驱动 leds-gpio.c 在,在这里就可以理解 led 的接口为什么是这样;当然优秀的驱动应该还有一份清晰的文档,你同样可也尝试去源码根目录的 . /Documentation 中查找 leds-gpio 的使用文档;这里也会解释,我为什么会去开发板的 /sys/class/leds 下面去查看我增加的 My_Cute 节点;

 

图九 驱动使用文档

 

(9)增加一个驱动或者一个设备节点到设备树中,你可以先查看内核源码的 / Documentation 目录,其中包含了几乎所有驱动的使用说明以及设备树属性的解释,同时也包括大量优秀的内核调试技巧;再去写节点,也可以先模仿,针对不懂的地方再来看文档,印象更为深刻。

 

五、结语

设备树相比于传统的配置文件,无疑是降低了 Linux 外设开发与使用的门槛,但是也隐藏了大量的细节,难以了解其底层的驱动原理;对于 LINUX 内核的了解,我所认识的还不及冰山一角,单希望对你有一点帮助。