本文转载自:http://blog.csdn.net/KjfureOne/article/details/51972854

1、ARM Linux社区为什么要引入设备树

Linux之父Linus Torvalds闲来无事,在翻看ARM Linux代码的时候,有一天终于忍不住了。他在2011年3月17日的ARM Linux邮件列表中说道:“This whole ARM thing is a f*cking pain in the ass”。这句话迫使ARM Linux社区引入了设备树。

Linus Torvalds为什么会发飙呢?而ARM Linux社区的牛人为什么又乖乖地听话了?你得首先理解Linux设备驱动框架中一个非常好的设计:设备信息和驱动分离。

为了说明设备信息和驱动分离的概念,这里用一个简单的模拟代码来解释:

【例-1】实现一个代码,把要使用的信息简单写死在代码中:

int add() /*模拟驱动代码*/

{

return 3+5; /*模拟设备信息*/

}

优点:简单

缺点:一旦加数和被加数发生变化就得改代码

改进设计如下:

【例-2】实现一个代码,把要使用的信息和操作代码分离开来:

struct dev{

int id;

int x;

int y;

}; /*模拟设备信息结构*/

strcut drv{

int id;

int (*add)(struct dev *info);

}; /*模拟驱动结构*/

int add(struct dev *info) /*模拟驱动代码*/

{

return info->x + info->y; /*模拟设备信息-通过参数传递进来*/

}

struct drv drv = {

.id = 1,

.add = add,

};

/*模拟设备信息*/

struct dev dev = {

.id = 1,

.x = 3,

.y = 5,

};

/*模拟总线初始化匹配设备信息和驱动代码*/

int bus()

{

if(dev.id == drv.id){

return drv.add(&dev);

}

...

}

优点:不管加数和被加数怎么变化,不需要修改代码,仅需要修改信息

缺点:结构比较复杂

那这个设备信息和驱动分离的设计跟驱动有什么关系呢?熟悉硬件编程的同学都知道,硬件一般的构成可以使用下图简单表述:

操作外设的驱动代码逻辑,只要硬件是一样的,就不会变化。但是外设挂到不同的主机上,可能会存在I/O地址的变化,如果有中断也是一样的,中断号也可能不同。这些I/O地址和中断号就是设备信息,使用这些信息来操作控制硬件的代码就是驱动。

如果采用【例-1】的设计方式,那么同一个硬件外设接到不同的主机,或是换了地址线/中断线,设备信息就变化了,得去修改驱动。但是采用【例-2】的方式进行设计,问题就迎刃而解:不管同样的外设硬件接到哪里或是那个平台,其驱动代码逻辑并不需要改动,而仅仅需要改变下设备信息,主要的就是I/O地址和中断号。

说了这么半天,跟引入设备树有什么关系呢?华清教学使用的开发板(A8/A9)都使用DM9000网卡芯片。DM9000驱动是开源的,在主线内核源码中就有。我们每次基于A8/A9板子移植的时候,DM9000驱动并没有修改过,仅仅是选配了下,主要的工作是在板级文件中添加了设备信息。DM9000驱动使用的是platform框架,所以添加了一份DM9000网卡芯片的platform_device信息。问题来了,如果使用C代码的形式来描述设备信息,则在内核源码中,将会有多份DM9000的platform_device设备信息,造成了内核代码冗余。

解决这个问题的办法就是引入设备树,改造【例-2】来说明设备树的作用。

【例-3】实现一个代码,不仅把要使用的信息和操作代码分离开来,而且信息不是C代码编写的,而是文本配置文件保存的:

struct dev{

int id;

int x;

int y;

}; /*模拟设备信息结构*/

strcut drv{

int id;

int (*add)(struct dev *info);

}; /*模拟驱动结构*/

int add(struct dev *info) /*模拟驱动代码*/

{

return info->x + info->y; /*模拟设备信息-通过参数传递进来*/

}

struct drv drv = {

.id = 1,

.add = add,

};

/*模拟设备树-一个特殊的配置文件,xxx.dtbs的文本文件*/

/{

......

Dm9000{

x = 3;

y = 5;

};

......

};

/*模拟总线初始化匹配设备信息和驱动代码*/

int bus()

{

/*模拟设备树初始化处理*/

读文件(xxx.dtbs);

解析文件内容(根据设备树的规则来解析);

生成struct dev设备信息;

if(dev.id == drv.id){

return drv.add(&dev);

}

...

}

如果像【例-3】这样,就可以解决大量设备信息的代码冗余问题。

推而广之,系统的软硬件信息都可以使用设备树来描述。这样的话,ARM Linux社区就不会因为支持板子和驱动越来越多造成内核源码中出现很多冗余代码(主要是板级文件),仅仅需要移植者,把系统的软硬件信息通过设备树提供出来,选配一下内核代码,就可以了。

2、设备树的概述

2.1、参考资料

内核源码目录Documentation\devicetree设备树说明文档

内核源码drivers/of/源码分析

2.2、基本概念

设备树是描述软/硬件信息的,包含节点和属性的一个树形结构。节点用以归类描述了一个硬件信息或是软件信息(好比文件系统的目录)。节点内描述了一个或多个属性,属性是键值对,描述具体的软/硬信息。简单形式如下:

/{

node{

property=value;

...

child_node{

child_property=value;

...

};

...

};

...

};

说明如下:

/:根节点,节点使用“{};”的语法描述作用范围

node:根节点下的一个子节点

child_node:node节点下的一个子节点

property:node节点内描述的属性,value就是属性的值(任意字节数据,可以是整型、字符串、数组、等等)。描述行以“;”结束

2.3、存储形式

在《ARM Linux社区为什么要引入设备树》中,已经讨论过设备树的使用方式。简而言之:内核初始化时,以配置的文件形式读取设备树文件的内容,并解析后生成相应的软/硬件信息,以供相应的内核代码使用。

编写设备树文件是以.dts的文本文件存储的,主要是为了修改、添加编辑方便。

那么问题来了,如果纯文本解析的话,显然比较慢且麻烦。譬如如果属性值是一个I/O地址:0x80000000,如果是字符串的形式存储,那么“0x80000000”就是一个字符串,内核代码解析这个信息的时候还得转换成整型数,不仅仅是慢,无形设备树文件大小还会增加不少,还得增加更多的初始化代码。

所以.dts的设备树文件,在内核使用前需要转换一次,主要是把繁复的语法形式及属性值转换成字节数据(特殊的数据结构),而非符号。.dts文件转换后是.dtb的二进制文件。

3、节点

3.1、命名

节点的命名以字母、数字、_、等等符号构成。常见的命令方式如下:

A、以“设备名”为节点名,范例:

DM9000命名如下:

/{

...

dm9000{

...

};

...

};

B、以“设备@I/O地址”为“节点名@I/O地址”,范例:

DM9000在主机端的I/O地址为0x8000 0000,可以命名如下:

/{

...

dm9000@80000000{

...

};

...

};

C、以“设备类型@I/O地址”为“节点名@I/O地址”,范例:

DM9000在主机端的I/O地址为0x8000 0000,可以命名如下:

/{

...

ethernet@80000000{

...

};

...

};

3.2、节点路径

A、

/{

...

dm9000{

...

};

...

};

节点名:dm9000

节点路径:/dm9000

B、

/{

...

dm9000@80000000{

...

};

...

};

节点名:dm9000

节点路径:/dm9000@80000000

C、

/{

...

ethernet@80000000{

...

};

...

};

节点名:ethernet

节点路径:/ethernet@80000000

3.3、节点引用

/{

aliases {

demo = &demo;

};

...

demo:demo@80000000{

...

};

...

};

节点名:demo

节点路径:/demo@80000000

引用路径:demo(等价/demo@80000000,解决路径名过程的问题)

设备树中引用节点“/demo@80000000”的范例:

&demo{

...

};

3.4、节点查找

有时候,分享内核代码或是编写内核代码的时候,可能会涉及使用查找节点函数。内核提供很多内核函数来查找(解析设备树)一个指定节点:

A、路径查找

/*

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

* 参数:

* const char *path - 节点路径,可以是路径,也可以是路径引用

* 返回值:

* 成功:得到节点对象的首地址;失败:NULL

*/

struct device_node *of_find_node_by_path(const char *path);

B、节点名查找

/*

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

* 参数:

* struct device_node *from - 指向开始路径的节点,如果为NULL,则从根节点开始

* const char *name- 节点名

* 返回值:

* 成功:得到节点对象的首地址;失败:NULL

*/

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);

C、通过compatible属性查找

/*

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

* 参数:

* struct device_node *from - 指向开始路径的节点,如果为NULL,则从根节点开始

* const char *type - 节点类型,可以为NULL

* const char *compat - 指向节点的compatible属性的值(字符串)的首地址

* 返回值:

* 成功:得到节点对象的首地址;失败:NULL

*/

struct device_node *of_find_compatible_node(struct device_node *from,

const char *type, const char *compat);

设备ID表结构

struct of_device_id {

char name[32]; /*设备名*/

char type[32]; /*设备类型*/

char compatible[128]; /*用于与设备树compatible属性值匹配的字符串*/

const void *data; /*私有数据*/

};

/*

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

* 参数:

* 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_matching_node(struct device_node *from,

const struct of_device_id *matches);

D、查找子节点

/*

* 功能:查找指定节点的子节点

* 参数:

* const struct device_node *node - 指向要查找子节点的父节点

* const char *name - 子节点名

* 返回值:

* 成功:得到子节点对象的首地址;失败:NULL

*/

struct device_node *of_get_child_by_name(const struct device_node *node,

const char *name);

3.5、节点内容合并

有时候,一个硬件设备的部分信息不会变化,但是部分信息是可能会变化的,就出现了节点内容合并。即:先编写好节点,仅仅描述部分属性值;使用者后加一部分属性值。

在同级路径下,节点名相同的“两个”节点实际是一个节点。

/*参考板的已经编写好的node节点*/

/{

node{

item1=value;

};

};

/*移植者添加的node节点*/

/{

node{

item2=value;

};

};

等价于:

/{

node{

item1=value;

item2=value;

};

};

3.6、节点内容替换

有时候,一个硬件设备的部分属性信息可能会变化,但是设备树里面已经描述了所有的属性值,使用者可以添加已有的属性值,以替换原有的属性值,就出现了节点内容替换。

另外,节点的内容即使不会变化,但是可能不会使用。

在同级路径下,节点名相同的“两个”节点实际是一个节点。

内容替换的常见形式之一:

/*参考板的已经编写好的node节点*/

/{

node{

item=value1;

};

};

/*移植者添加的node节点*/

/{

node{

item=value2;

};

};

等价于:

/{

node{

item=value2;

};

};

内容替换的常见形式之二:

/*参考板的已经编写好的node节点*/

/{

node{

item=value;

status = "disabled";

};

};

/*移植者添加的node节点*/

/{

node{

status = "okay";

};

};

等价于:

/{

node{

item=value;

status = "okay";

};

};

3.7、节点内容引用

有时候,一个节点需要使用到别的节点的属性值,就需要引用的概念。有时候在设备树编写时,要替换节点属性值,或是合并节点的属性值,也会使用引用。

A、引用节点完成属性值的替换及合并:

/*参考板的已经编写好的node节点*/

/{

node:node@80000000{

item1=value;

status = "disabled";

};

};

/*移植者添加的node节点*/

&node{

item2=value;

status = "okay";

};

等价于:

/{

node : node@80000000{

item1=value;

item2=value;

status = "okay";

};

};

B、节点引用另一个节点:

/*参考板的已经编写好的node节点*/

/{

node:node@80000000{

item=value;

};

};

/*移植者添加的demo节点*/

/{

demo{

item=<&node>;

};

};

说明:

demo节点的属性item引用了节点的node的属性值,具体怎么使用node节点的属性值,在属性章节进行讨论。

linux 设备树及节点引用【转】的更多相关文章

  1. 我眼中的Linux设备树(二 节点)

    二 节点(node)的表示首先说节点的表示方法,除了根节点只用一个斜杠"/"表示外,其他节点的表示形式如"node-name@unit-address".@前边 ...

  2. Linux设备树(二 节点)

    二 节点(node)的表示 首先说节点的表示方法,除了根节点只用一个斜杠“/”表示外,其他节点的表示形式如“node-name@unit-address”.@前边是节点名字,后边是节点地址.节点名字的 ...

  3. Linux设备树语法详解

    概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写.引入了设备树之后,驱动代 ...

  4. Linux设备树语法详解【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备 ...

  5. linux 设备树【转】

    转自:http://blog.csdn.net/chenqianleo/article/details/77779439 [-] linux 设备树 为什么要使用设备树Device Tree 设备树的 ...

  6. linux设备树语法

    设备树语法及绑定 概述 Device Tree是一种用来描述硬件的数据结构,类似板级描述语言,起源于OpenFirmware(OF). 就ARM平台来说,设备树文件存放在arch/arm/boot/d ...

  7. 【转载】Linux设备树(Device Tree)机制

    转:Linux设备树(Device Tree)机制   目录 1. 设备树(Device Tree)基本概念及作用2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DT ...

  8. Linux设备树学习

    1.概念 设备树用于实现驱动代码与设备信息相分离.驱动代码只负责处理驱动的逻辑而关于设备的具体信息存放到设备树文件中.(dts文件,编译后为dtb文件).一个dts文件对应一个ARM的machine, ...

  9. 我眼中的Linux设备树(四 中断)

    四 中断中断一般包括中断产生设备和中断处理设备.中断控制器负责处理中断,每一个中断都有对应的中断号及触发条件.中断产生设备可能有多个中断源,有时多个中断源对应中断控制器中的一个中断,这种情况中断产生设 ...

随机推荐

  1. Python之字符串的特性及常用方法

    字符串的特性 索引: (索引是从0开始) s='hello'print(s[0])print(s[4])print(s[-1]) #拿出最后一个字符 hoo12345678截取s[start:stop ...

  2. Vue指令1:v-text及v-html

    v-text: //插入一段文本<div id="app"> <p v-text="message"></p></di ...

  3. 【Redis】四、Redis设计原理及相关问题

    (六)Redis设计原理及相关问题   通过前面关于Redis五种数据类型.相关高级特性以及一些简单示例的使用,对Redis的使用和主要的用途应该有所掌握,但是还有一些原理性的问题我们在本部分做一个探 ...

  4. 关闭的连接: next

    1.最近做了一个项目,扫描读取了第三方数据库的数据,结果本来在公司测试没有问题的程序在客户那边一直报如下错误: java.sql.SQLException: 关闭的连接: next 代码如下: //第 ...

  5. 让元素div消失在视野中

    让元素div消失在视野中1.position:absolute/relative/fixed + 方位 top/bottom/left/right: -9999px2.display:none3.vi ...

  6. WebBrowser之获取跳转页面的Document接口源码

    问题由来是这样的,今天帮一个网友解决问题,说从VC驿站下载了一个源码,程序的功能主要是在对话框上面放置了一个WebBrowser控件,程序启动的时候默认调用这句代码: m_web.Navigate(_ ...

  7. 十二届 - CSU 1803 :2016(同余定理)

    题目地址:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1803 Knowledge Point: 同余定理:两个整数a.b,若它们除以整数m所 ...

  8. 数列分块入门1-9 By hzwer

    声明 持续更新,因为博主也是正在学习分块的知识,我很菜的,菜的抠$jio$ 写在前面 分块是个很暴力的算法,但却比暴力优秀的多,分块算法的时间复杂度一般是根号的,他的主要思想是将一个长度是$n$的数列 ...

  9. HDU - 4810 - Wall Painting (位运算 + 数学)

    题意: 从给出的颜料中选出天数个,第一天选一个,第二天选二个... 例如:第二天从4个中选出两个,把这两个进行异或运算(xor)计入结果 对于每一天输出所有异或的和 $\sum_{i=1}^nC_{n ...

  10. ecshop笔记

    ***ecshop在线入门手册***:http://book.ecmoban.com/ 解决ecshop与jquery冲突问题1.修改文件:/js/transport.js在文件最底部增加代码: if ...