? ? ? ??设备树( 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 设备树描述了内核的软/硬件信息。
? ? ? ??节点用以归类描述一个硬件信息或是软件信息(好比文件系统的目录)
? ? 节点内描述了一个或多个属性,属性是键值对( key/value),描述具体的
软/硬信息。
为什么 ARM Linux 社区会引入设备树呢?
? ?主要是想解决 ARM Linux 内核代码冗余的问题。
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;
};
节点名:
语法: <name>[@<unit-address>]
规范:
名字是ASCII字符串
(字母、数字、 "-"、等等构成)
最长可以是31个字符一般的,应该以设备类型命名
unit-address一般的是设备地址
/*****示例*****/
/{
serial@101F0000{
……
};
gpio@101F3000{
……
};
interrupt-controller@10140000{
……
};
spi@10115000{
…….
};
external-bus{
……
};
};
/{
…
dm9000{
…
};
…
};
节点名:dm9000
节点路径:/dm9000
? ? 为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点
/{
aliases{
demo=&demo0;
};
…
demo:demo0@80000000{
…
};
…
};
节点名:demo0
节点路径:/demo0@80000000
节点别名:demo(等价/demo0@80000000)
/**********************************/
引用语法范例1:
&demo{
…
};
引用语法范例2:
/{
reference-node{
property=<&demo>;
…
};
…
};
? ? ?一般的, 一个硬件设备的部分信息不会变化,但是部分信息是可能会变化的,就出现了节点内容合并。即:先编写好节点,仅仅描述部分属性值;使用者后加一部分属性值。在同级路径下,节点名相同的“两个”节点实际是一个节点。
?
/{
node{
property=value;
};
};
/*移植者添加的节点*/
/{
node{
property2=value;
};
};
/***合并后的节点内容***/
/{
node{
property2=value;
};
};
? ? ? ??一个硬件设备的部分属性信息可能会变化,但是设备树里面已经描述了所有的属性值,使用者可以添加已有的属性值,以替换原有的属性值,就出现了节点内容替换。在同级路径下,节点名相同的“两个”节点实际是一个节点。
2.7?引用节点内容
? ? ?一个设备可能会使用到别的节点的内容,可以通过节点的别名来引用到其内容。 引用的目的可能是合并两个节点的内容、 替换部分内容、或是使用部分内容.
/{
node:node@80000000{
property=value;
};
};
/*移植者添加的node节点*/
&node{
property=value;
status = “okay”;
}
/*移植者添加demo节点*/
/{
demo{
property=<&node>;
};
};
说明:demo节点的属性property引用了节点的node的属性值,一般的,引用的目的是使用node节点的部分属性内容
? ? ?chosen 节点不描述一个真实设备,而是用于 firmware 传递一些数据给 OS,譬如 bootloader 传递内核启动参数给内核.
chosen {
bootargs = “root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200”;
};
? ?涉及设备、总线、驱动的概念,即所谓设备信息和驱动代码分离的驱动框架,如 platform、 i2c、 usb、spi、 pci、等等; 或是分层驱动框架( MTD 设备驱动、framebuffer 设备驱动、 input 设备驱动、 ...),则设备树中设备节点的会内核初始化时候被查找到,驱动代码将不关心节点的查找。
如果仅仅是接口驱动框架(字符设备驱动、块设备驱动、网络设备驱动) ,则需要使用内核节点查找函数查找设备树中的设备节点。
通过节点的 compatible 属性值查找指定节点
通过节点名查找指定节点
通过节点路径查找指定节点
头文件: 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
有默认意义的属性
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 = <ð0_power>;
};
compatible 属性
用于匹配设备节点和设备驱动,规则是驱动设备 ID 表中的 compatible 域的值(字符串),和设备树中设备节点中的 compatible 属性值完全一致,则节点的内容是给驱动的。
设备树中的命名规范如下
/{
node{
compatible=“厂商名,名称” ;
...
...
vcc-supply = <ð0_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)
}
}
属性-address
#address-cells:描述子节点 reg 属性值的地址表中首地址 cell 数量
#size-cells:描述子节点 reg 属性值的地址表中地址长度 cell 数量