Zephyr Device Tree简介

Creative Commons
本作品采用知识共享署名

本文说明了Zephyr对device tree的使用方法,并分析了Qemu_cortex_m3的device tree和对应生成的#define. 本文旨在帮助理解Zephyr的代码,不说明如何添加Zephyr的Device Tree和yaml,不分析python script.

Zephyr device tree概述

linux下DTS被DTC编译为DTB,DTB被放到Flash内,启动时DTB由bootloader送给kernel使用。但是这套机制太过庞大,不适合Zephyr作为iot终端节点的目标。
Zephyr采用buildin的方法来使用DTS,主要步骤如下:

  1. 使用DTC将DTS和C头文件一起预编译组合成.dts_compiled文件。
  2. python脚本按照YAML文件指定的一组规则从.dts_compiled文件中提取信息转换为define,合并板级描述的dts.fixup内的define放置在头文件generated_dts_board.h中
  3. Zephyr编译时头文件generated_dts_board.h参与编译被buildin入zephyr

dts2h

Zephyr device tree基本元素说明

Zephyr Device Tree

Zephyr使用Device Tree来描述架构,板子,驱动信息,Device的文件分别放在

1
2
3
4
5
boards/<ARCH>/<BOARD>/
dts/
dts/common/
dts/<ARCH>/
dts/<ARCH>/<MANUFACTURER>/

Device Tree语法简述

Zephyr device tree遵循EPAPR document. 简要说明如下:

Device Tree

Device tree由node组成,包含根节点和子节点
节点node由节点名和节点内容组成,节点内容用{}扩起来

1
2
3
4
5
6
7
8
/{
node-name@unit-address{
property-name=property-value;
node-name@unit-address{
property-name=property-value;
};
}
}

节点

节点node在Device tree中以下面形式出现:

1
node-name@unit-address

只能有一个根节点root node, 可以有多个子节点sub node。
一个node的内容有属性和自己的sub node

节点名

根节点名是/
子节点名node-name为长度小于31的字符串。对不同的device,EPAPR document有推荐的node-name(非强制)
当node有寄存器时,unit-address是第一个寄存器的地址。如果node没有寄存器,则无@unit-address

节点属性

属性用表达式以下面形式出现,分为属性名和属性值

1
property-name=property-value

属性名

属性名分为标准属性名和非标属性名,对于非标属性名EPAPR document建议加前缀以示区分,例如

1
2
3
fsl,channel-fifo-len
ibm,ppc-interrupt-server#s
linux,network-index

注意以上只有3个属性名,属性名当中是允许出现 , 的

属性值

属性值有7种类型

  • <empty>
    空,没有值
  • <u32>
    big-endian 32bit符号整形

    1
    property = <0x11223344>
  • <u64>
    big-endian 64bit符号整形,由两个32bit组成

    1
    property = <0x11223344 0x55667788>
  • <string>
    字符串

    1
    device_type = "memory";
  • <prop-encoded-array>
    任意数量的array

    1
    reg = <0x3000 0x20 0xFE00 0x100>;
  • <phandle>
    节点引用

    1
    uart_0 = &uart0;
  • <stringlist>
    字符串列表

    1
    compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965";

标准属性

常用标准属性共12种:

  • compatible
  • model
  • phandle
  • status
  • #address-cells
  • #size-cells
  • reg
  • virtual-reg
  • ranges
  • dma-ranges
  • name
  • device_type

另外有6种中断用的属性

  • interrupts
  • interrupt-parent
  • #interrupt-cells
  • interrupt-controller
  • interrupt-map
  • interrupt-map-mask

属性名内容繁多,具体参考EPAPR document,后文QEMU_CORTEX_M3 Device Tree分析会做一些简要说明

YAML

YAML它是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言.
Zephyr 使用YAML来定义Device tree转化为#define的生成规则,dts/bindings/device_node.yaml.template 是一个yaml的模板, zephyr的yaml放在

1
2
dts/bindings
boards/<ARCH>/<BOARD>/

Python Script

将Dts转化为头文件的脚本放在scripts\dts下。目前也还不计划阅读这部分。

Qemu_Cortex_m3 device tree分析

DTS文件组成

由下面文件include组成

  • boards/arm/qemu_cortex_m3/qemu_cortex_m3.dts
    #include <ti/lm3s6965.dtsi>
  • dts/arm/ti/lm3s6965.dtsi
    #include <arm/armv7-m.dtsi>
  • dts/arm/armv7-m.dtsi
    #include “skeleton.dtsi”
  • dts/common/skeleton.dtsi

将上面4个文件合并后如下分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/ {								//根节点的属性,如果子节点有重新定义相同属性,子节点内属性使用重新定义后的值
model = "QEMU Cortex-M3"; //板制造商设备型号,一般会用model = "st,stm32f0"说明制造商(st)和型号(stm32f0),这里是Qemu因此没有带制造商
compatible = "ti,lm3s6965evb-qemu", "ti,lm3s6965"; // 对于root node,compatible属性用来匹配machine type,用于指出板子的制造商和设备型号

#address-cells = <1>;
#size-cells = <1>;

aliases { //一个节点以全路径被引用太过复杂,aliase用短别名取代
uart_0 = &uart0; //例如“/soc/uart@4000C000”就可以用uart_0来取代
uart_1 = &uart1;
uart_2 = &uart2;
};

chosen { //chosen节点为硬件和操作系统数据传输服务,如:启动参数。Zephyr是build in形式,因此在产生header的时候用该节点指明驱动设备的配置
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &uart0;
zephyr,bt-uart = &uart2;
zephyr,uart-pipe = &uart1;
zephyr,bt-mon-uart = &uart2;
};

cpus {
#address-cells = <1>; //cpu子节点reg地址长度为32bit,中address-cells表示子节点中reg中address字段长度单位为32bit
#size-cells = <0>; //cpu子节点reg数据为0,中address-cells表示子节点中reg中数据字段长度单位为32bit,联合address-cells表示子节点reg 值为一个没有size的uint32地址

cpu@0 { //cpu寻址被id表示第0个CPU,如果节点有reg属性,节点名必须包含unit-address,并且取reg属性的第一address值(0)
device_type = "cpu"; //只有memory和cpu两种device type,为了兼容IEEE 1275
compatible = "arm,cortex-m3"; //cpu的制造商和设备型号
reg = <0>; //reg属性第一个address值为0
};
};

sram0: memory@20000000 { //内存地址在0x20000000
device_type = "memory"; //只有memory和cpu两种device type,为了兼容IEEE 1275
compatible = "mmio-sram"; //设备型号mmio-sram
reg = <0x20000000 (64*1024)>; //地址在0x20000000大小64K
};

flash0: flash@0 { //flash地址在0x00000000
reg = <0x00000000 (256*1024)>; //flash地址在0x00000000大小256K
};

soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&nvic>; //指定节点设备所依附的中断控制器的phandle,若没有此参数,则从父节点继承
ranges; //空属性, ranges本身是做地址映射,让设备地址可以被CPU访问

nvic: interrupt-controller@e000e100 { //中断控制器
compatible = "arm,v7m-nvic";
reg = <0xe000e100 0xc00>; //中断控制器的寄存器地址在0xe000e100,长度0xc00
interrupt-controller; //空属性标示是interrupt-controller
#interrupt-cells = <2>; //用2个32bit reg标示一个中断源
arm,num-irq-priority-bits = <3>;
};

systick: timer@e000e010 { //硬件timer
compatible = "arm,armv7m-systick";
reg = <0xe000e010 0x10>; //中断控制器的寄存器地址在0xe000e010,长度0x10
status = "disabled";
};

uart0: uart@4000C000 { //uart0,
compatible = "ti,stellaris-uart";
reg = <0x4000C000 0x4c>; //寄存器起始地址在0x4000c000, 大小0x4C(19个)
interrupts = <5 3>; //中断号5,触发方式3
label = "UART_0"; //绑定device描述字符串为UART_0
status = "ok"; //status指示device状态,值可以选"okay" "disabled" "fail" "fail-sss"
current-speed = <115200>;
};

uart1: uart@4000D000 { //uart1
compatible = "ti,stellaris-uart";
reg = <0x4000D000 0x4c>; //寄存器起始地址在0x4000d000, 大小0x4C(19个)
interrupts = <6 3>; //中断号6,触发方式3
label = "UART_1";
status = "ok"; //status指示device状态,值可以选"okay" "disabled" "fail" "fail-sss"
current-speed = <115200>;
};

uart2: uart@4000E000 { //uart2
compatible = "ti,stellaris-uart";
reg = <0x4000E000 0x4c>; //寄存器起始地址在0x4000e000, 大小0x4C(19个)
interrupts = <33 3>; //中断号33,触发方式3
label = "UART_2";
status = "ok"; //status指示device状态,值可以选"okay" "disabled" "fail" "fail-sss"
current-speed = <115200>;
};
};
};

Yaml组成

Qemu_cortex_m3使用的yaml有,生成规则待分析(可能要分析python script才能知道),目前能知道转换结果就不会影响Zephyr代码的分析了

  • boards/arm/qemu_cortex_m3/qemu_cortex_m3.yaml
  • dts/bindings/interrupt-controller/arm,v7m-nvic.yaml
  • dts/bindings/serial/ti,stellaris-uart.yaml
  • dts/bindings/serial/uart.yaml

转换结果

基本上可以看出就是将dts内的信息转换成device信息的#define,供Zephyr的代码使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**************************************************
* Generated include file for ti,lm3s6965evb-qemu
* DO NOT MODIFY
*/

#ifndef _DEVICE_TREE_BOARD_H
#define _DEVICE_TREE_BOARD_H

/* flash@0 */
#define CONFIG_FLASH_BASE_ADDRESS_0 0x0
#define CONFIG_FLASH_LOAD_OFFSET 0
#define CONFIG_FLASH_LOAD_SIZE 0
#define CONFIG_FLASH_SIZE_0 256
#define CONFIG_FLASH_BASE_ADDRESS CONFIG_FLASH_BASE_ADDRESS_0
#define CONFIG_FLASH_SIZE CONFIG_FLASH_SIZE_0

/* memory@20000000 */
#define CONFIG_SRAM_BASE_ADDRESS_0 0x20000000
#define CONFIG_SRAM_SIZE_0 64
#define CONFIG_SRAM_BASE_ADDRESS CONFIG_SRAM_BASE_ADDRESS_0
#define CONFIG_SRAM_SIZE CONFIG_SRAM_SIZE_0

/* interrupt-controller@e000e100 */
#define ARM_V7M_NVIC_E000E100_ARM_NUM_IRQ_PRIORITY_BITS 3
#define ARM_V7M_NVIC_E000E100_BASE_ADDRESS_0 0xe000e100
#define ARM_V7M_NVIC_E000E100_SIZE_0 3072
#define ARM_V7M_NVIC_E000E100_BASE_ADDRESS ARM_V7M_NVIC_E000E100_BASE_ADDRESS_0
#define ARM_V7M_NVIC_E000E100_SIZE ARM_V7M_NVIC_E000E100_SIZE_0

/* uart@4000C000 */
#define CONFIG_UART_CONSOLE_ON_DEV_NAME "UART_0"
#define TI_STELLARIS_UART_4000C000_BASE_ADDRESS_0 0x4000c000
#define TI_STELLARIS_UART_4000C000_CURRENT_SPEED 115200
#define TI_STELLARIS_UART_4000C000_IRQ_0 5
#define TI_STELLARIS_UART_4000C000_IRQ_0_PRIORITY 3
#define TI_STELLARIS_UART_4000C000_LABEL "UART_0"
#define TI_STELLARIS_UART_4000C000_SIZE_0 76
#define TI_STELLARIS_UART_4000C000_BASE_ADDRESS TI_STELLARIS_UART_4000C000_BASE_ADDRESS_0
#define TI_STELLARIS_UART_4000C000_SIZE TI_STELLARIS_UART_4000C000_SIZE_0

/* uart@4000D000 */
#define CONFIG_UART_PIPE_ON_DEV_NAME "UART_1"
#define TI_STELLARIS_UART_4000D000_BASE_ADDRESS_0 0x4000d000
#define TI_STELLARIS_UART_4000D000_CURRENT_SPEED 115200
#define TI_STELLARIS_UART_4000D000_IRQ_0 6
#define TI_STELLARIS_UART_4000D000_IRQ_0_PRIORITY 3
#define TI_STELLARIS_UART_4000D000_LABEL "UART_1"
#define TI_STELLARIS_UART_4000D000_SIZE_0 76
#define TI_STELLARIS_UART_4000D000_BASE_ADDRESS TI_STELLARIS_UART_4000D000_BASE_ADDRESS_0
#define TI_STELLARIS_UART_4000D000_SIZE TI_STELLARIS_UART_4000D000_SIZE_0

/* uart@4000E000 */
#define CONFIG_BT_MONITOR_ON_DEV_NAME "UART_2"
#define CONFIG_BT_UART_ON_DEV_NAME "UART_2"
#define TI_STELLARIS_UART_4000E000_BASE_ADDRESS_0 0x4000e000
#define TI_STELLARIS_UART_4000E000_CURRENT_SPEED 115200
#define TI_STELLARIS_UART_4000E000_IRQ_0 33
#define TI_STELLARIS_UART_4000E000_IRQ_0_PRIORITY 3
#define TI_STELLARIS_UART_4000E000_LABEL "UART_2"
#define TI_STELLARIS_UART_4000E000_SIZE_0 76
#define TI_STELLARIS_UART_4000E000_BASE_ADDRESS TI_STELLARIS_UART_4000E000_BASE_ADDRESS_0
#define TI_STELLARIS_UART_4000E000_SIZE TI_STELLARIS_UART_4000E000_SIZE_0


/* Following definitions fixup the generated include */
/* SoC level DTS fixup file */

#define CONFIG_NUM_IRQ_PRIO_BITS ARM_V7M_NVIC_E000E100_ARM_NUM_IRQ_PRIORITY_BITS

/* End of SoC Level DTS fixup file */

#endif

参考

http://docs.zephyrproject.org/devices/dts/device_tree.html
http://events17.linuxfoundation.org/sites/events/files/slides/Zephyr%20Device%20Tree%20-%20ELC2017.pdf
https://elinux.org/images/b/b8/DTWorkshop2017_Zephyr.pdf
https://www.devicetree.org/downloads/devicetree-specification-v0.1-20160524.pdf