设备树 DTS & DTB

Device Tree1

A device tree is a tree data structure with nodes that describe the physical devices in a system.

设备树是一种数据结构,用来描述硬件设备,如此一来硬件信息就无需硬编码在代码中,这样就将设备驱动和实际硬件解耦开来,有了良好的可扩展性。操作系统可以在启动运行时读取相应的文件,必要时也可以修改。当然设备树也没有完全解决所有硬件配置的问题。

Flattened Device TreeDevice Tree SourceDevice Tree Binary

Linux uses DT data for three major purposes:

  • platform identification,
  • runtime configuration, and
  • device population.

Linux 中 ARM 的 dts 文件位于 arch/arm/boot/dts/,通过相关的脚本可以在 dts 和 dtb 之间转换。

$ scripts/dtc/dtc -I dts -O dtb -o /path/my_tree.dtb /arch/arm/boot/dts/my_tree.dts
$ scripts/dtc/dtc -I dtb -O dts -o /path/my_tree.dts /path/my_tree.dtb

dts

基本的语法可以表现为节点 node 以及其中的 key-value 键值对。

label: node@xxx {
	key = value;
}

compatible: ", "

// 根据习惯,如果一个Node含有reg,那么其名称中必须含有unit-address,即reg中的第一个值。
Node: [@] 
 
 // 由 parent-node指定下面两个 property来帮助确定reg中的值长度。
Addressing: reg = 
#address-cells: reg 中 base address 由多少个 cell (32bits values) 来表示
#size-cells: reg 中的 length 由多少个 cell (32bits values) 来表示
/{
 	compatible = "nvidia,harmony", "nvidia,tegra20"; // , "
 	#address-cells = <1>; // 在它的子节点的 reg 属性中, 使用多少个 u32 整数 (cell) 来描述地址 (base address)
 	#size-cells = <1>; // 在它的子节点的 reg 属性中, 使用多少个 u32 整数 (cell) 来描述大小 (size / length)
 	
	[memory reservations]
	{
		[property definitions]
		[child node]
	};
};

// [property definitions]
[label:] node-name[@unit-address] { // 根据习惯,Node含有reg,那么其名称中必须含有unit-address,即reg中的第一个值
	[property definitions]
	[child nodes]
};

// for example
memory@30000000 {
		device_type = "memory";
		reg =  <0x30000000 0x4000000>;
};
// node reference by phandle
pic@10000000 {
	phandle = <1>;
	interrupt-controller;
};
another-device-node {
	interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

// node reference by label
PIC: pic@10000000 {
	interrupt-controller;
};
another-device-node {
	interrupt-parent = <&PIC>;   // 使用label来引用上述节点, 
	                             // 使用lable时实际上也是使用phandle来引用, 
								 // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};

Syntax

  1. 每个Module在Device Tree中都是一个Node, 根据driver的实现,可以有父节点或者子节点;
  2. Root是所有module最终的父节点;

常见的 Top-level module:

cpus: its each sub-nodes describing each CPU in the system.
memory : defines location and size of the RAM.
chosen : defines parameters chosen or defined by the system firmware at boot time. 
         In practice, one of its usage is to pass the kernel command line.
aliases : shortcuts to certain nodes.

One or more nodes defining the buses in the SoC
One or mode nodes defining on-board devices

/ :root node,device tree 从该节点开始。
property:每个 property 是一个 Key-value 键值对。

Key

model
compatible
reg:node / device 的物理地址,reg =
aliases

value

  • text-string: 双引号, "string-property = "a string";
  • cell: 32-bit无符号整型: < >, cell-property = <0xbeef 123 0xabcd1234>;
  • binary-property: [ ], binary-property = [0x01 0x23 0x45 0x67];
  • mixed-property: 不同类型的可以放一起: mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
  • string-list: 逗号也用来建立字符串列表: string-list = "red fish", "blue fish";

example

下面以 ARMfoundation v8 作为例子。

/* * ARM Ltd.
 * ARMv8 Foundation model DTS */

/dts-v1/;
/memreserve/ 0x80000000 0x00010000;
/ {
    model = "Foundation-v8A";
    compatible = "arm,foundation-aarch64", "arm,vexpress";
    interrupt-parent = <&gic>;
    #address-cells = <2>;
    #size-cells = <2>;

    chosen { };
    aliases {
    	serial0 = &v2m_serial0;
        ...
    };

    cpus {
    	#address-cells = <2>;
    	#size-cells = <0>;

    	cpu@0 {
			device_type = "cpu";
			compatible = "arm,armv8";
			reg = <0x0 0x0>;
			enable-method = "spin-table";
			cpu-release-addr = <0x0 0x8000fff8>;
			next-level-cache = <&L2_0>;
        };
    	cpu@1 { ... };
    	cpu@2 { ... };
    	cpu@3 { ... };
    	L2_0: l2-cache0 {
    		compatible = "cache";
    	};
    };


	memory@80000000 {
		device_type = "memory";
		reg = <0x00000000 0x80000000 0 0x80000000>,
		      <0x00000008 0x80000000 0 0x80000000>;
	};

	gic: interrupt-controller@2c001000 {
		compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";
		#interrupt-cells = <3>; // 所有以它作为中断接受者的设备都要用3个cell来描述中断信息。
		#address-cells = <2>;
		interrupt-controller; // 它是一个中断控制器
		reg = <0x0 0x2c001000 0 0x1000>,
		      <0x0 0x2c002000 0 0x1000>,
		      <0x0 0x2c004000 0 0x2000>,
		      <0x0 0x2c006000 0 0x2000>;
		interrupts = <1 9 0xf04>;
	};

	timer {
		compatible = "arm,armv8-timer";
		interrupts = <1 13 0xf08>,
			     <1 14 0xf08>,
			     <1 11 0xf08>,
			     <1 10 0xf08>;
		clock-frequency = <100000000>;
	};
    pmu: { ... };
    smb: { ... };

以上面的 CPU 节点为例,其拥有四个子节点和一个 l2-cache0 节点,其中 L2_0 是 l2-cache0 的 label。
cpus 节点中指定了 reg 中 address 为2个cell,构成一个u64,size为0,因此没有大小。
根据惯例,所以CPU@0的0为 unit-address 取自 reg 的第一个值 0x0 0x0,两个 cell。

interrupt2

...
  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
  interrupts.

  The 2nd cell contains the interrupt number for the interrupt type.
  SPI interrupts are in the range [0-987].  PPI interrupts are in the
  range [0-15].

  The 3rd cell is the flags, encoded as follows:
	bits[3:0] trigger type and level flags.
		1 = low-to-high edge triggered
		2 = high-to-low edge triggered (invalid for SPIs)
		4 = active high level-sensitive
		8 = active low level-sensitive (invalid for SPIs).
	bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
	the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
	the interrupt is wired to that CPU.  Only valid for PPI interrupts.
	Also note that the configurability of PPI interrupts is IMPLEMENTATION
	DEFINED and as such not guaranteed to be present (most SoC available
	in 2014 seem to ignore the setting of this flag and use the hardware
	default value).

第一个域指明中断是 SPI 还是 PPI,0是 SPI,1是PPI
第二个是中断号
第三个是中断触发类型以及中断发往的 cpu_id

以timer为例

	timer {
		compatible = "arm,armv8-timer";
		interrupts = <1 13 0xf08>,
			     <1 14 0xf08>,
			     <1 11 0xf08>,
			     <1 10 0xf08>;
		clock-frequency = <100000000>;
	};

上面的描述表明 timer 中断类型为PPI, 分别对应中断号10, 11, 13, 14, 并且对 4 个CPU与之相连, 且为 active low level-sentitive(0xf08)。

dtb

设备树源文件通过 dtc 编译得到 dtb。

struct ftd_header
free space
memory reservation block
free space
structure block
free space
strings block
free space

DTB 的数据为大端 存放。
DTB分为四个部分:struct ftd_headermemory reservation blockstructure blockstrings block
struct ftd_header:用来表明各个分部的偏移地址,整个文件的大小,版本号等;
memory reservation block:在设备树中使用 /memreservation/ 定义的保留内存信息;
structure block:保存节点的信息,节点的结构;
strings block:保存属性的名字,单独作为字符串保存;

struct fdt_header {
	uint32_t magic;
	uint32_t totalsize;
	uint32_t off_dt_struct;
	uint32_t off_dt_strings;
	uint32_t off_mem_rsvmap;
	uint32_t version;
	uint32_t last_comp_version;
	uint32_t boot_cpuid_phys;
	uint32_t size_dt_strings;
	uint32_t size_dt_struct;
};

// 节点信息结构体
struct {
	uint32_t len; // val 的长度
	uint32_t nameoff; // 在string block的偏移
}

dtc

sudo apt-get install device-tree-compiler
两种文件格式可以互相转换。

dtc -I dts -O dtb -o /path/to/xxx.dtb /path/toxxx.dts
dtc -I dtb -O dts -o /path/to/xxx.dts /path/to/xxx.dtb

  1. linux/Documentation/devicetree/usage-model.txt ↩︎

  2. https://elixir.bootlin.com/linux/v4.3.6/source/Documentation/devicetree/bindings/arm/gic.txt ↩︎

你可能感兴趣的:(ARM)