目录
3.5 内存映射设备( Memory Mapped Devices)
3.6 非内存映射设备( Non Memory Mapped Devices)
3.7 地址转换范围 Ranges( Address Translation)
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 = <ð0_power>;
};
3.2 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)
}
}
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>;
};
};