-
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
社区
的牛人为什么又乖乖地听话了
?
你得首先理解
< br>Linux
设备驱动框架中一个非常好的设计:设备信息和驱动分离。
为了说明设备信息和驱动分离的概念,这里用一个简单的模拟代码来解释
:
【例
-1
】实现一个代码,把要使用的信息简单写死在代码中:
int
add() /*
模拟驱动代码
*/
{
return 3+5;
/*
模拟设备信息
*/
}
优点:简单
缺点:一旦加数和被加数发生变化就得改代码
改进设计如下:
【例
-2
】实现一个代码,把要使用的信息和操作代码分离开来:
< br>
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; /*
模拟设备信息
-
< br>通过参数传递进来
*/
}
struct drv drv
= {
.id = 1,
.add = add,
};
/*
模
拟设备信息
*/
struct
dev dev = {
.id =
1,
.x = 3,
.y = 5,
};
/*
模
拟总线初始化匹配设备信息和驱动代码
*/
int bus()
{
if( ==
){
return (&dev);
}
...
}
优点:不管加数和被加数怎么变化
,不需要修改代码,仅需要修改信息
缺点:结构比较复杂
那这个设备信息
和驱动分离的设计跟驱动有什么关系呢
?
熟悉硬件编程的同学都
知道,硬件一般的构
成可以使用下图简单表述:
操作外设的驱动代码逻辑,只要硬件是一样的,就不会变化。但是外设挂到不同的主机上,可能会
p>
存在
I/O
地址的变化,
< br>如果有中断也是一样的,
中断号也可能不同。
这些
I/O
地址和中断号就是设备信息,
使用这些
信息来操作控制硬件的代码就是驱动。
如果采用【例
-1
】的设计方式,那么同一个硬件外设接到不同的主机,或是换了地址
线
/
中断线,设
备信息就变化了,得去
修改驱动。但是采用【例
-2
】的方式进行设计,问题就迎刃而
解:不管同样的外设
硬件接到哪里或是那个平台,
其驱动代码逻
辑并不需要改动,
而仅仅需要改变下设备信息,
主要的就是
p>
I/O
地址和中断号。
< br>说了这么半天,跟引入设备树有什么关系呢
?
华清教学使
用的开发板
(A8/A9)
都使用
DM
9000
网卡芯
片。
DM9000
p>
驱动是开源的,在主线内核源码中就有。我们每次基于
A8/A9<
/p>
板子移植的时候,
DM9000
驱
动并没有修改过,仅仅是选配了下,主要的工作是在板级文件中添加了设备信息。
DM9000
驱动使用的是
platform
框架,所以添加了一份
DM9000
网卡芯片
的
platform_device
信息。问题来了,如果使用
C
代码
的形式来描述设备信息,则在内
核源码中,将会有多份
DM9000
的
platform_device
设备信息,造成了内
核代码冗
余。
解决这个问题的办法就是引入设备树,改造【例
-2
】来说明设备树的作用。
【例
-3
】实现一个代码,不仅把要使用的信息和操作
代码分离开来,而且信息不是
C
代码编写的,
< br>而是文本配置文件保存的:
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; /*
模拟设备信息
-
< br>通过参数传递进来
*/
}
struct drv drv
= {
.id = 1,
.add = add,
};
/*
模
拟设备树
-
一个特殊的配置文件,
p>
的文本文件
*/
/{
......
Dm9000{
x =
3;
y = 5;
};
......
};
/*
模
拟总线初始化匹配设备信息和驱动代码
*/
int bus()
{
/*
模拟
设备树初始化处理
*/
读文件
();
解析文件内容
(
根据设备树的规则来解析
);
生成
struct
dev
设备信息
;
if( == ){
return
(&dev);
}
...
}
如果像【例
-3
】这样,就可以解决大
量设备信息的代码冗余问题。
推而广之,系统的软硬件信息都
可以使用设备树来描述。这样的话,
ARM Linux
社区就
不会因为支
持板子和驱动越来越多造成内核源码中出现很多冗余代码
(
主要是板级文件
)
,仅仅需要移
植者,把系统的
软硬件信息通过设备树提供出来,选配一下内核代码,就可以了。
2
、设备树的概述
2.1
、参考资料
< br>内核源码目录
Documentationdevicetree
设备树说明文档
内核源码
d
rivers/of/
源码分析
2.2
、基本概念
< br>设备树是描述软
/
硬件信息的,包含节点和属性的一个树
形结构。节点用以归类描述了一个硬件信息
或是软件信息
(
p>
好比文件系统的目录
)
。节点内描述了一个
或多个属性,属性是键值对,描述具体的软
/
硬信
息。简单形式如下:
/{
node{
property=value;
...
child_node{
child_property=value;
...
};
...
};
...
};
说明如下:
/
:根节点,节点使用
“{};”
的语法描述作用范围
node
:根节点下的一个子节点
<
/p>
child_node
:
node
节点下的一个子节点
property
p>
:
node
节点内描述的属性,
value
就是属性的值
(
任意字节数据,可以是整型、字符串、数
组、等等
)
。描述行以
“;”
结束
2.3
、存储形式
在《
ARM Linux
社区为什么要
引入设备树》中,已经讨论过设备树的使用方式。简而言之:内核初
始化时,以配置的文
件形式读取设备树文件的内容,并解析后生成相应的软
/
硬件信
息,以供相应的内核代
码使用。
编写
设备树文件是以
.dts
的文本文件存储的,主要是为了修改、
添加编辑方便。
那么问题来了,如果纯文本解析的话,显然比
较慢且麻烦。譬如如果属性值是一个
I/O
地址:
0x80000000
,如果是字符串的形式存储,那么
“0x80000000”
就是一个字符串,内核代码解析这个信息的
时候还得转换成整型数,不仅仅是慢,无形设备树文件大小还会增加不少,还得增加更多的初始化代码
。
所以
.dts
的设备树文件,
在内核使用前需要转换一次,
主要是把繁复
的语法形式及属性值转换成字节
数据
(
特殊的数据结构
)
,而非符号。
.dt
s
文件转换后是
.dtb
的二进制文件
。
3
、节点
3.1
、命名
节点的命名以字母、数字、
_
、等等符号构成。常见的命令方
式如下:
A
、以
“
设备名
”
为节点名,范例:
p>
DM9000
命名如下:
/{
...
dm9000{
...
};
...
};
B
、以
“
设备
@I/O
地址
”
为
“
节点名
@I/O
地址
”
,范例:
DM9000
在主机端的
I/O
< br>地址为
0x8000
0000
,可以命名如下:
/{
...
dm9000@80000000{
...
};
...
};
C
、以
“
设备
类型
@I/O
地址
”
< br>为
“
节点名
@I/O
地址
”
,范例:
DM9000
在主机端的
I/O
地址为
0x8000
0000
,可以命名如下:
/{
...
ethernet@80000000{
...
};
...
};
3.2
、节点路径
A
、
/{
...
dm9000{
...
};
...
};
节点名:
dm9000
节点路径:
/dm9000
B
、
/{
...
dm9000@80000000{
...
};
...
};
节点名:
dm9000
节点路径:
/dm9000@80000000
C
、
/{
...
ethernet@80000000{
...
};
...
};
-
-
-
-
-
-
-
-
-
上一篇:班组建设会议记录(完整版)
下一篇:H3C路由器常用基本配置命令