Linux 设备树详解

目录

1、概述

2、节点( node)和属性( property)

2.1、DTS 描述键值对的语法:

2.2 节点语法规范说明

2.3节点名及节点路径

2.4 节点别名(节点引用)

2.5 合并节点内容

2.6 替换节点内容

2.8 chosen 节点

2.9 查找节点

2.10 查找办法

3 节点描述

 3.1 节点属性

3.2 compatible 属性

3.3 属性-address

3.4 CPU 地址描述

3.5 内存映射设备( Memory Mapped Devices)

3.6 非内存映射设备( Non Memory Mapped Devices)

3.7 地址转换范围 Ranges( Address Translation)

3.8 属性-interrupt

3.9 属性 gpio



1、概述

        设备树( Device Tree)是一种描述硬件的数据结构,在操作系统( OS)引导
阶段进行设备初始化的时候,数据结构中的硬件信息被检测并传递给操作系统
最早诞生于 Open Firmware, Flattened Device Tree (FDT)格式标准。
 dts 文件( Device Tree Source, dts)是以 ASCII 文本形式描述设备树内容。
 dtb 文件是二进制格式,编译工具为: Device Tree Compiler( DTC)。
 2011 年被引入 ARM Linux 内核。 ARM Linux 设备树描述了内核的软/硬件信息。

2、节点( node)和属性( property)

        节点用以归类描述一个硬件信息或是软件信息(好比文件系统的目录)
     节点内描述了一个或多个属性,属性是键值对( key/value),描述具体的
软/硬信息。
为什么 ARM Linux 社区会引入设备树呢?
    主要是想解决 ARM Linux 内核代码冗余的问题。

2.1、DTS 描述键值对的语法:


 1、字符串信息
 2、 32bits 无符号整型数组信息
 3、二进制数数组
 4、混和形式
 5、字符串哈希表

/dts-v1/;
#include "exynos4412.dtsi" //此设备树依赖于exynos4412.dtsi 文件
#include <dt-bindings/gpio/gpio.h> //gpio引脚配置文件
/ { //根节点 root node
    model = "FriendlyARM TINY4412 board based on Exynos4412";
    compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
    chosen {
    stdout-path = &serial_0;
};

2.2 节点语法规范说明

节点名:
语法: <name>[@<unit-address>]
规范:
名字是ASCII字符串
(字母、数字、 "-"、等等构成)
最长可以是31个字符一般的,应该以设备类型命名
unit-address一般的是设备地址
/*****示例*****/
/{
    serial@101F0000{
        ……
    };
    gpio@101F3000{
        ……
    };
    interrupt-controller@10140000{
        ……
    };
    spi@10115000{
        …….
    };
    external-bus{
        ……
    };
}; 

2.3节点名及节点路径

/{
    …
    dm9000{
        …
    };
    …
};
节点名:dm9000
节点路径:/dm9000

2.4 节点别名(节点引用)

    为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点

/{
    aliases{
        demo=&demo0;
    };
    …
    demo:demo0@80000000{
        …
    };
    …
};
节点名:demo0
节点路径:/demo0@80000000
节点别名:demo(等价/demo0@80000000)

/**********************************/
引用语法范例1:
&demo{
    …
};
引用语法范例2:
/{
    reference-node{
         property=<&demo>;
         …
    };
    …
};

2.5 合并节点内容

     一般的, 一个硬件设备的部分信息不会变化,但是部分信息是可能会变化的,就出现了节点内容合并。即:先编写好节点,仅仅描述部分属性值;使用者后加一部分属性值。在同级路径下,节点名相同的“两个”节点实际是一个节点。

/{
    node{
        property=value;
    };
};
/*移植者添加的节点*/
/{
    node{
        property2=value;
    };
};
/***合并后的节点内容***/
/{
    node{
        property2=value;
    };
};

2.6 替换节点内容

        一个硬件设备的部分属性信息可能会变化,但是设备树里面已经描述了所有的属性值,使用者可以添加已有的属性值,以替换原有的属性值,就出现了节点内容替换。在同级路径下,节点名相同的“两个”节点实际是一个节点。

2.7 引用节点内容

     一个设备可能会使用到别的节点的内容,可以通过节点的别名来引用到其内容。 引用的目的可能是合并两个节点的内容、 替换部分内容、或是使用部分内容.

/{
    node:node@80000000{
        property=value;
    };
};
/*移植者添加的node节点*/
&node{
    property=value;
        status = “okay”;
}
/*移植者添加demo节点*/
/{
    demo{
        property=<&node>;
    };
};
说明:demo节点的属性property引用了节点的node的属性值,一般的,引用的目的是使用node节点的部分属性内容

2.8 chosen 节点

     chosen 节点不描述一个真实设备,而是用于 firmware 传递一些数据给 OS,譬如 bootloader 传递内核启动参数给内核.

chosen {
    bootargs = “root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200”;
};

2.9 查找节点

   涉及设备、总线、驱动的概念,即所谓设备信息和驱动代码分离的驱动框架,如 platform、 i2c、 usb、spi、 pci、等等; 或是分层驱动框架( MTD 设备驱动、framebuffer 设备驱动、 input 设备驱动、 ...),则设备树中设备节点的会内核初始化时候被查找到,驱动代码将不关心节点的查找。
 如果仅仅是接口驱动框架(字符设备驱动、块设备驱动、网络设备驱动) ,则需要使用内核节点查找函数查找设备树中的设备节点。

2.10 查找办法


 通过节点的 compatible 属性值查找指定节点
 通过节点名查找指定节点
 通过节点路径查找指定节点

3 节点描述

头文件: include/of.h
struct device_node {
	const char *name; //节点名
	const char *type; //设备类型
	phandle phandle;
	const char *full_name; //全路径节点名
	struct fwnode_handle fwnode;
	struct  property *properties;
	struct  property *deadprops; /* removed properties */
	struct  device_node *parent; 父节点指针
	struct  device_node *child; //子节点指针
	struct  device_node *sibling;
	struct  kobject kobj;
	unsigned long _flags;
	void *data;
	#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
	#endif
};

功能:通过 compatible 属性查找指定节点

struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);
参数:
struct device_node *from - 指向开始路径的节点,如果为NULL,则从根节点开始
const char *type - device_type设备类型,可以为NULL
const char *compat - 指向节点的compatible属性的值(字符串)的首地址
返回值:
成功:得到节点的首地址;失败: NULL

设备 ID 表结构,用于匹配设备节点和驱动

struct of_device_id {
    char name[32]; /*设备名*/
    char type[32]; /*设备类型*/
    char compatible[128]; /*用于与设备树compatible属性值匹配的字符串*/
    const void *data; /*驱动私有数据*/
};
//注册支持设备树的设备ID表
include/module.h
MODULE_DEVICE_TABLE(of, ID表首地址)

功能:通过 compatible 属性查找指定节点

struct device_node *of_find_matching_node(struct device_node *from,
const struct of_device_id *matches);
参数:
struct device_node *from - 指向开始路径的节点,如果为NULL,则从根节点开始
const struct of_device_id *matches - 指向设备ID表,注意ID表必须以NULL结束
范例:
const struct of_device_id mydemo_of_match[] = {
	{ .compatible = "fs4412,mydemo", },
	{}
};
返回值:
成功:得到节点的首地址;失败: NULL

功能:通过路径查找指定节点

struct device_node *of_find_node_by_path(const char *path);
参数:
const char *path - 带全路径的节点名,也可以是节点的别名
返回值:
成功:得到节点的首地址;失败: NULL

功能:通过节点名查找指定节点

struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
参数:
struct device_node *from - 开始查找节点,如果为NULL,则从根节点开始
const char *name- 节点名
返回值:
成功:
得到节点的首地址;失败: NULL

 3.1 节点属性


有默认意义的属性
 1、设备树语法中已经定义好的,具有通用规范意义的属性。
 如果是设备信息和驱动分离框架的设备节点,则能够在内核初始化找到节点时候,自动解析生成相应的设备信息。
 常见属性的有: compatible、地址 address、中断 interrupt
 ARM Linux 内核定义好的,一类设备通用的有默认意义的属性
 一般的,不能被内核自动解析生成相应的设备信息,但是内核已经编写了相应的解析提取函数。
 常见属性的有: MAC 地址、 GPIO 口、 clock、 power、 regulator、等等

驱动自定义属性

针对具体设备,有部分属性很难通用,需要驱动自己定义好,通过内核的属性提取解析函数进行值的获得。

ethernet@18000000 {
	compatible = “davicom,dm9000”;
	reg = <0x18000000 0x2 0x18000004 0x2>;
	interrupt = <7 4>;
	local-mac-address = [00 00 de ad be ef];
	davicom,no-eeprom;
	reset-gpios = <&gpf 12 GPIO_ACTIVE_LOW>;
	vcc-supply = <&eth0_power>;
};

3.2 compatible 属性

用于匹配设备节点和设备驱动,规则是驱动设备 ID 表中的 compatible 域的值(字符串),和设备树中设备节点中的 compatible 属性值完全一致,则节点的内容是给驱动的。
 设备树中的命名规范如下

/{
node{
	compatible=“厂商名,名称” ;
		...
   
	    ...
    vcc-supply = <&eth0_power>;
};

设备树示例
/{
	…
	mydemo{
		compatible = “fs4412,mydemo”;
		…
	}
}

/*platform 框架的探测函数*/
static int demo_probe(struct platform_device *devices)
{
//设备树对应节点的信息已经被内核构造成struct platform_devic
…
}
static const struct of_device_id demo_of_matches[] = {
	{.compatible = “fs4412,mydemo”,},
	{}
}
MODULE_DEVICE_TABLE(of,demo_of_matches);
	static struct platform_driver demo_drv = {
		.driver = {
			.name = DEMONAME,
			.owner = THIS_MODULE,
			.of_match_table = of_match_ptr(demo_of)
		}
}

3.3 属性-address

#address-cells:描述子节点 reg 属性值的地址表中首地址 cell 数量
#size-cells:描述子节点 reg 属性值的地址表中地址长度 cell 数量

reg:描述地址表
/{
	parent-node{
		#address-cell = <1>;
		#size-cells= <1>;
		…
		son-node{
			reg=<addr1 len1 [addr2 len2] […]>;
			…
		};
	};
};
说明:
父节点#address-cells值为1,#size-cells值为1,则子节点中reg的值就是一个首地址紧接着一个地址上都为一个单元。

3.4 CPU 地址描述

每个 CPU 都分配了唯一的一个 ID,描述没有大小的 CPU ids

cpus {
	#address-cells = <1>;
	#size-cells = <0>;
	cpu@0 {
		compatlibe = “arm,cortex-a9”;
		reg = <0>;
	};
	cpu@1 {
		compatible = “arm,cortex-a9”;
		reg = <1>;
	};
};

3.5 内存映射设备( Memory Mapped Devices)

描述一个设备的内存地址的时候,一般使用 1 个 cell( 32bits)描述地址,紧接着 1 一个 cell
( 32bits)描述地址长度

/ {
#address-cells = <1>;
	#size-cells = <1>;
	…
	serial@101f0000 {
		compatible = “arm, p1011”;
		reg = <0x101f0000 0x1000>;
	};
	gpio@101f3000{
		compatible = “arm,p1061”;
		reg = <0x101f3000 0x1000
		0x101f4000 0x0010>;
	};
	spi@10115000 {
		compatible = “arm,p1022”;
		reg = <0x10115000 0x1000 >;
	};
	…
};

3.6 非内存映射设备( Non Memory Mapped Devices)

譬如 i2c 设备,有一个寻址地址,没有内存地址那样的地址长度和范围,一般使用 1 个 cell(32bits)描述该地址,而没有描述地址长度的 cell。

i2c@1,0{
	compatible = “acme, a1234-i2c-bus”;
	#address-cells = <1>;
	#size-cells = <0>;
	reg = <1 0 0x1000>;
	rtc@58{
		compatible = “maxim,ds1338”;
		reg = <58>;
	};
};

3.7 地址转换范围 Ranges( Address Translation)

有些设备是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地
址映射范围。

/{
	compatible = “acme,coyotes-revenge”;
	#address-cells = <1>;
	#size-cells = <1>;
	…
	external-bus {
		#address-cells = <2>;
		#size-cells = <1>;
		ranges = < 0 0 0x10100000 0x10000 //片选1,ethernet
				   1 0 0x10160000 0x10000 //片选2,i2c控制器
				   2 0 0x30000000 0x1000000>; //片选3 NOR FLASH
		ehternet@0,0 {
			compatible = “smc,smc91c1111”;
			reg = <0 0 0x1000>;
		};
	};
};

说明:片选0,偏移0(选中了网卡),被映射到CPU地址空间的0x10100000~0x10110000中,地址长度为0x10000

3.8 属性-interrupt

/{
compatible = “acme,coyotes-revenge”;
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;
	interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
	serial@101f0000 {
		compatible = “arm,p1011”;
		reg = <0x101f0000 0x1000 >;
		interrupt = <1 0>;
		interrupts 一个中断标识符列表,表示每一个中断输出信号
	};
	intc: interrupt-controller@10140000{
		compatible = “arm,p1190”;
		reg = <0x10140000 0x1000>;
		interrupt-controller;
		#interrupt-cells = <2>;
		interrupt-controller 一个空属性用来声明这个node接收中断信号;
		#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;
	};
};

如果父节点的#interrupt-cells 的值是 3,则子节点的 interrupts 一个 cell 三个 32bits 整型值: <中断域 中断触发方式>实际解析情况,得根据实际使用内核的设备树参加资料来决定。

/{
	gic: interrupt-controller@10490000 {
		compatible = “arm,cortex-a9-gic”;
		#interrupt-cells = <3>;
		interrupt-controller;
		cpu-offset = <0x4000>;
		reg = <0x10490000 0x10000>,<0x10480000 0x10000>;
	};
	pinctl@11000000 {
		gpx0:gpx0{
			gpio-controller;
			#gpio-cells = <2>;S
			interrupt-controller;
			interrupt-parent = <&gic>;
			interrupts = <0 16 0>,<0 17 0 >,<0 18 0>,<0 19 0>,
						 <0 20 0>,<0 21 0>,<0 22 0>,<0 23 0>;
			#interrupt-cells = <2>;
		};
		…
	};
	ethernet@5000000 {
		compatible = “davicom,dm9000”;
		reg = <0x5000000 0x2 0x5000004 0x2>;
		interrupt-parent = <&pgx0>;
		interrupts = <6 4>;
		davicom,no-eeprom;
		mac-address = [00 0a 2d a6 55 a2];
	};
	…
};

如果父节点的#interrupt-cells的值是2,则子节点的interrupts一个cell两个32bits整型值:
中断和触发方式。实际解析情况,得根据实际使用内核的设备树参加资料来决定。

3.9 属性 gpio

常用的属性如下:
gpio-controller:说明该节点描述的是一个 gpio 控制器
#gpio-cells:描述 gpio 使用节点的属性一个 cell 的内容
 属性名=<&引用 GPIO 节点别名 GPIO 标号 工作模式>;

/{
	gpx1:gpx1 {
		gpio-controller;
		#gpio-celslls = <2>;
	};
	key@11400C24{
		compatible = “fs4412,key”;
		reg = <0x11400C24 0x4>;
		intn-key = <&gpx1 2 2>;
	};
};

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑吧撸码兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值