Zephyr Device Tree简介

本文说明了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的文件分别放在

boards/<ARCH>/<BOARD>/
dts/
dts/common/
dts/<ARCH>/
dts/<ARCH>/<MANUFACTURER>/

Device Tree语法简述 链接到标题

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

Device Tree 链接到标题

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

/{
	node-name@unit-address{
		property-name=property-value;
		node-name@unit-address{
			property-name=property-value;
		};
	}
}

节点 链接到标题

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

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

节点属性 链接到标题

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

property-name=property-value

属性名 链接到标题

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

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

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

属性值 链接到标题

属性值有7种类型

  • <empty> 空,没有值
  • <u32> big-endian 32bit符号整形
    property = <0x11223344>
    
  • <u64> big-endian 64bit符号整形,由两个32bit组成
    property = <0x11223344 0x55667788>
    
  • <string> 字符串
    device_type = "memory";
    
  • <prop-encoded-array> 任意数量的array
    reg = <0x3000 0x20 0xFE00 0x100>;
    
  • <phandle> 节点引用
    uart_0 = &uart0;
    
  • <stringlist> 字符串列表
    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放在

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个文件合并后如下分析:

/ {								//根节点的属性,如果子节点有重新定义相同属性,子节点内属性使用重新定义后的值
	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的代码使用

/**************************************************
 * 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