没有合适的资源?快使用搜索试试~ 我知道了~
首页linux/ubuntu/arm开发板 设备树详细资料
资源详情
资源评论
资源推荐

Hypersen-->Kevin_wang 最后编辑日期:2017 年 11 月 23 日星期四
第 1 页 共 28 页
一、 Device Tree:背景介绍
1、前言
作为一个多年耕耘在 linux 2.6.23 内核的开发者,各个不同项目中各种不同周边外设驱动的开发以及各种琐碎的、
扯皮的俗务占据了大部分的时间。当有机会下载 3.14 的内核并准备学习的时候,突然发现 linux kernel 对于我似乎变
得非常的陌生了,各种新的机制,各种 framework、各种新的概念让我感到阅读内核代码变得举步维艰。 还好,剖析
内核的热情还在,剩下的就交给时间的。首先进入视线的是 Device Tree 机制,这是和 porting 内核非常相关的机制,
如果想让将我们的硬件平台迁移到高版本的内核上,Device Tree 是一个必须要扫清的障碍。
我想从下面三个方面来了解 Device Tree:
1、为何要引入 Device Tree,这个机制是用来解决什么问题的?(这是本文的主题)
2、Device Tree 的基础概念(请参考 DT 基础概念)
3、ARM linux 中和 Device Tree 相关的代码分析(请参考 DT 代码分析)
阅读 linux 内核代码就像欣赏冰山,有看得到的美景(各种内核机制及其代码),也有埋在水面之下看不到的基
础(机制背后的源由和目的)。沉醉于各种内核机制的代码固然有无限乐趣,但更重要的是注入更多的思考,思考其
背后的机理,真正理解软件抽象。这样才能举一反三,并应用在具体的工作和生活中。
本文主要从下面几个方面阐述为何 ARM linux 会引入 Device Tree:
1、没有 Device Tree 的 ARM linux 是如何运转的?
2、混乱的 ARM architecture 代码和存在的问题
3、新内核的解决之道
2、没有 Device Tree 的 ARM linux 是如何运转的?
我曾经 porting 内核到两个 ARM-based 的平台上。一个是小的芯片公司的应用处理器,公司自己购买了 CPU core,
该 CPU core 使用 ARM 兼容的指令集(但不是 ARM)加上各种公司自行设计的多媒体外设整合成公司的产品进行销
售。而我的任务就是 porting 2.4.18 内核到该平台上。在黑白屏幕的手机时代,那颗 AP(application process)支持
了彩屏、camera、JPEG 硬件加速、2D/3D 加速、MMC/SD 卡、各种音频加速(内置 DSP)等等特性,功能强大到
无法直视。另外一次移植经历是让 2.6.23 内核跑在一个大公司的冷门 BP(baseband processor)上。具体 porting
的方法是很简单的:
1、自己撰写一个 bootloader 并传递适当的参数给 kernel。除了传统的 command line 以及 tag list 之类的,最重
要的是申请一个 machine type,当拿到属于自己项目的 machine type ID 的时候,当时心情雀跃,似乎自己已经是开
源社区的一份子了(其实当时是有意愿,或者说有目标是想将大家的代码并入到 linux kernel main line 的)。
2、在内核的 arch/arm 目录下建立 mach-xxx 目录,这个目录下,放入该 SOC 的相关代码,例如中断 controller
的代码,时间相关的代码,内存映射,睡眠相关的代码等等。此外,最重要的是建立一个 board specific 文件,定义
一个 machine 的宏:
MACHINE_START(project name, "xxx 公司的 xxx 硬件平台")
.phys_io = 0x40000000,
.boot_params = 0xa0000100,

Hypersen-->Kevin_wang 最后编辑日期:2017 年 11 月 23 日星期四
第 2 页 共 28 页
.io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc,
.map_io = xxx_map_io,
.init_irq = xxx_init_irq,
.timer = &xxx_timer,
.init_machine = xxx_init,
MACHINE_END
在 xxx_init 函数中,一般会加入很多的 platform device。因此,伴随这个 board specific 文件中是大量的静态 table,
描述了各种硬件设备信息。
3、调通了 system level 的 driver(timer,中断处理,clock 等)以及串口 terminal 之后,linux kernel 基本是可以
起来了,后续各种 driver 不断的添加,直到系统软件支持所有的硬件。
综上所述,在 linux kernel 中支持一个 SOC 平台其实是非常简单的,让 linux kernel 在一个特定的平台上“跑”起来
也是非常简单的,问题的重点是如何优雅的”跑”。
3、混乱的 ARM architecture 代码和存在的问题
每次正式的 linux kernel release 之后都会有两周的 merge window,在这个窗口期间,kernel 各个部分的维护者
都会提交各自的 patch,将自己测试稳定的代码请求并入 kernel main line。每到这个时候,Linus 就会比较繁忙,他
需要从各个内核维护者的分支上取得最新代码并 merge 到自己的 kernel source tree 中。Tony Lindgren,内核 OMAP
development tree 的维护者,发送了一个邮件给 Linus,请求提交 OMAP 平台代码修改,并给出了一些细节描述:
1、简单介绍本次改动
2、关于如何解决 merge conficts。有些 git mergetool 就可以处理,不能处理的,给出了详细介绍和解决方案
一切都很平常,也给出了足够的信息,然而,正是这个 pull request 引发了一场针对 ARM linux 的内核代码的争
论。我相信 Linus 一定是对 ARM 相关的代码早就不爽了,ARM 的 merge 工作量较大倒在其次,主要是他认为 ARM
很多的代码都是垃圾,代码里面有若干愚蠢的 table,而多个人在维护这个 table,从而导致了冲突。因此,在处理完
OMAP 的 pull request 之后(Linus 并非针对 OMAP 平台,只是 Tony Lindgren 撞在枪口上了),他发出了怒吼:
Gaah. Guys, this whole ARM thing is a f*cking pain in the ass.
负责 ARM linux 开发的 Russell King 脸上挂不住,进行了反驳:事情没有那么严重,这次的 merge conficts 就是
OMAP 和 IMX/MXC 之间一点协调的问题,不能抹杀整个 ARM linux 团队的努力。其他的各个 ARM 平台维护者也加
入讨论:ARM 平台如何复杂,如何庞大,对于 arm linux code 我们已经有一些思考,正在进行中……一时间,讨论的
气氛有些尖锐,但总体是坦诚和友好的。
对于一件事情,不同层次的人有不同层次的思考。这次争论涉及的人包括:
1、内核维护者(CPU 体系结构无关的代码)
2、维护 ARM 系统结构代码的人
3、维护 ARM sub architecture 的人(来自各个 ARM SOC vendor)
维护 ARM sub architecture 的人并没有强烈的使命感,作为公司的一员,他们最大的目标是以最快的速度支持自
己公司的 SOC,尽快的占领市场。这些人的软件功力未必强,对 linux kernel 的理解未必深入(有些人可能很强,但
是人在江湖身不由己)。在这样的情况下,很多 SOC specific 的代码都是通过 copy and paste,然后稍加修改代码就
提交了。此外,各个 ARM vendor 的 SOC family 是一长串的 CPU list,每个 CPU 多多少少有些不同,这时候#ifdef
就充斥了各个源代码中,让 ARM mach-和 plat-目录下的代码有些不忍直视。

Hypersen-->Kevin_wang 最后编辑日期:2017 年 11 月 23 日星期四
第 3 页 共 28 页
作为维护 ARM 体系结构的人,其能力不容置疑。以 Russell King 为首的 team 很好的维护了 ARM 体系结构的代
码。基本上,除了 mach-和 plat-目录,其他的目录中的代码和目录组织是很好的。作为 ARM linux 的维护者,维护一
个不断有新的 SOC 加入的 CPU architecture code 的确是一个挑战。在 Intel X86 的架构一统天下的时候,任何想正
面攻击 Intel 的对手都败下阵来。想要击倒巨人(或者说想要和巨人并存)必须另辟蹊径。ARM 的策略有两个,一个
是 focus 在嵌入式应用上,也就意味着要求低功耗,同时也避免了和 Intel 的正面对抗。另外一个就是博采众家之长,
采用 license IP 的方式,让更多的厂商加入 ARM 建立的生态系统。毫无疑问,ARM 公司是成功的,但是这种模式也
给 ARM linux 的维护者带来了噩梦。越来越多的芯片厂商加入 ARM 阵营,越来越多的 ARM platform 相关的代码被加
入到内核,不同厂商的周边 HW block 设计又各不相同……
内核维护者是真正对操作系统内核软件有深入理解的人,他们往往能站在更高的层次上去观察问题,发现问题。
Linus 注意到每次 merge window 中,ARM 的代码变化大约占整个 ARCH 目录的 60%,他认为这是一个很明显的符
号,意味着 ARM linux 的代码可能存在问题。其实,60%这个比率的确很夸张,因为 unicore32 是在 2.6.39 merge
window 中第一次全新提交,它的代码是全新的,但是其代码变化大约占整个 ARCH 目录的 9.6%(需要提及的是
unicore32 是一个中国芯)。有些维护 ARM linux 的人认为这是 CPU 市场占用率的体现,不是问题,直到内核维护者
贴出实际的代码并指出问题所在。内核维护者当然想 linux kernel 支持更多的硬件平台,但是他们更愿意为 linux kernel
制定更长远的规划。例如:对于各种繁杂的 ARM 平台,用一个 kernel image 来支持。
经过争论,确定的问题如下:
1、ARM linux 缺少 platform(各个 ARM sub architecture,或者说各个 SOC)之间的协调,导致 arm linux 的代
码有重复。值得一提的是在本次争论之前,ARM 维护者已经进行了不少相关的工作(例如 PM 和 clock tree)来抽象
相同的功能模块。
2、ARM linux 中大量的 board specific 的源代码应该踢出 kernel,否则这些垃圾代码和 table 会影响 linux kernel
的长期目标。
3、各个 sub architecture 的维护者直接提交给 Linux 并入主线的机制缺乏层次。
4、新内核的解决之道
针对 ARM linux 的现状,最需要解决的是人员问题,也就是如何整合 ARM sub architecture(各个 ARM Vendor)
的资源。因此,内核社区成立了一个 ARM sub architecture 的 team,该 team 主要负责协调各个 ARM 厂商的代码(not
ARM core part),Russell King 继续负责 ARM core part 的代码。此外,建立一个 ARM platform consolidation tree。
ARM sub architecture team 负责 review 各个 sub architecture 维护者提交的代码,并在 ARM platform consolidation
tree 上维护。在下一个 merge window 到来的时候,将 patch 发送给 Linus。
针对重复的代码问题,如果不同的 SOC 使用了相同的 IP block(例如 I2C controller),那么这个 driver 的 code
要从各个 arch/arm/mach-xxx 中独立出来,变成一个通用的模块供各个 SOC specific 的模块使用。移动到哪个目录呢?
对于 I2C 或者 USB OTG 而言,这些 HW block 的驱动当然应该移动到 kernel/drivers 目录。因为,对于这些外设,可
能是 in-chip,也可能是 off-chip 的,但是对于软件而言,它们是没有差别的(或者说好的软件抽象应该掩盖底层硬件
的不同)。对于那些 system level 的 code 呢?例如 clock control、interrupt control。其实这些也不是 ARM-specific,
应该属于 linux kernel 的核心代码,应该放到 linux/kernel 目录下,属于 core-Linux-kernel frameworks。当然对于 ARM
平台,也需要保存一些和 framework 交互的 code,这些 code 叫做 ARM SoC core architecture code。OK,总结一
下:
1、ARM 的核心代码仍然保存在 arch/arm 目录下
2、ARM SoC core architecture code 保存在 arch/arm 目录下
3、ARM SOC 的周边外设模块的驱动保存在 drivers 目录下
4、ARM SOC 的特定代码在 arch/arm/mach-xxx 目录下

Hypersen-->Kevin_wang 最后编辑日期:2017 年 11 月 23 日星期四
第 4 页 共 28 页
5、ARM SOC board specific 的代码被移除,由 Device Tree 机制来负责传递硬件拓扑和硬件资源信息。
OK,终于来到了 Device Tree 了。本质上,Device Tree 改变了原来用 hardcode 方式将 HW 配置信息嵌入到内
核代码的方法,改用 bootloader 传递一个 DB 的形式。对于基于 ARM CPU 的嵌入式系统,我们习惯于针对每一个
platform 进行内核的编译。但是随着 ARM 在消费类电子上的广泛应用(甚至桌面系统、服务器系统),我们期望 ARM
能够象 X86 那样用一个 kernel image 来支持多个 platform。在这种情况下,如果我们认为 kernel 是一个 black box,
那么其输入参数应该包括:
1、识别 platform 的信息
2、runtime 的配置参数
3、设备的拓扑结构以及特性
对于嵌入式系统,在系统启动阶段,bootloader 会加载内核并将控制权转交给内核,此外,还需要把上述的三个
参数信息传递给 kernel,以便 kernel 可以有较大的灵活性。在 linux kernel 中,Device Tree 的设计目标就是如此。
二、 Device Tree:基本概念
1、前言
一些背景知识(例如:为何要引入 Device Tree,这个机制是用来解决什么问题的)请参考引入 Device Tree 的
原因,本文主要是介绍 Device Tree 的基础概念。
简单的说,如果要使用 Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成
Device Tree source file。通过 DTC(Device Tree Compiler),可以将这些适合人类阅读的 Device Tree source file
变成适合机器处理的 Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。在系统启动的时候,
boot program(例如:firmware、bootloader)可以将保存在 flash 中的 DTB copy 到内存(当然也可以通过其他方式,
例如可以通过 bootloader 的交互式命令加载 DTB,或者 firmware 可以探测到 device 的信息,组织成 DTB 保存在内
存中),并把 DTB 的起始地址传递给 client program(例如 OS kernel,bootloader 或者其他特殊功能的程序)。对
于计算机系统(computer system),一般是 firmware->bootloader->OS,对于嵌入式系统,一般是 bootloader->OS。
本文主要描述下面两个主题:
1、Device Tree source file 语法介绍
2、Device Tree binaryfile 格式介绍
2、Device Tree 的结构
在描述 Device Tree 的结构之前,我们先问一个基础问题:是否 Device Tree 要描述系统中的所有硬件信息?答
案是否定的。基本上,那些可以动态探测到的设备是不需要描述的,例如 USB device。不过对于 SOC 上的 usb host
controller,它是无法动态识别的,需要在 device tree 中描述。同样的道理,在 computer system 中,PCI device 可
以被动态探测到,不需要在 device tree 中描述,但是 PCI bridge 如果不能被探测,那么就需要描述之。
为了了解 Device Tree 的结构,我们首先给出一个 Device Tree 的示例:
/ o device-tree
|- name = "device-tree"
|- model = "MyBoardName"
|- compatible = "MyBoardFamilyName"
|- #address-cells = <2>
|- #size-cells = <2>

Hypersen-->Kevin_wang 最后编辑日期:2017 年 11 月 23 日星期四
第 5 页 共 28 页
|- linux,phandle = <0>
|
o cpus
| | - name = "cpus"
| | - linux,phandle = <1>
| | - #address-cells = <1>
| | - #size-cells = <0>
| |
| o PowerPC,970@0
| |- name = "PowerPC,970"
| |- device_type = "cpu"
| |- reg = <0>
| |- clock-frequency = <0x5f5e1000>
| |- 64-bit
| |- linux,phandle = <2>
|
o memory@0
| |- name = "memory"
| |- device_type = "memory"
| |- reg = <0x00000000 0x00000000 0x00000000 0x20000000>
| |- linux,phandle = <3>
|
o chosen
|- name = "chosen"
|- bootargs = "root=/dev/sda2"
|- linux,phandle = <4>
从上图中可以看出,device tree 的基本单元是 node。这些 node 被组织成树状结构,除了 root node,每个 node
都只有一个 parent。一个 device tree 文件中只能有一个 root node。每个 node 中包含了若干的 property/value 来描述
该 node 的一些特性。每个 node 用节点名字(node name)标识,节点名字的格式是 node-name@unit-address。如
果该 node 没有 reg 属性(后面会描述这个 property),那么该节点名字中必须不能包括@和 unit-address。unit-address
的具体格式是和设备挂在那个 bus 上相关。例如对于 cpu,其 unit-address 就是从 0 开始编址,以此加一。而具体的
设备,例如以太网控制器,其 unit-address 就是寄存器地址。root node 的 node name 是确定的,必须是“/”。
在一个树状结构的 device tree 中,如何引用一个 node 呢?要想唯一指定一个 node 必须使用 full path,例如
/node-name-1/node-name-2/node-name-N。在上面的例子中,cpu node 我们可以通过/cpus/PowerPC,970@0 访问。
属性(property)值标识了设备的特性,它的值(value)是多种多样的:
1、可能是空,也就是没有值的定义。例如上图中的 64-bit ,这个属性没有赋值。
2、可能是一个 u32、u64 的数值(值得一提的是 cell 这个术语,在 Device Tree 表示 32bit 的信息单位)。例如
#address-cells = <1> 。当然,可能是一个数组。例如<0x00000000 0x00000000 0x00000000 0x20000000>
4、可能是一个字符串。例如 device_type = "memory" ,当然也可能是一个 string list。例如"PowerPC,970"
3、Device Tree source file 语法介绍
了解了基本的 device tree 的结构后,我们总要把这些结构体现在 device tree source code 上来。在 linux kernel
中,扩展名是 dts 的文件就是描述硬件信息的 device tree source file,在 dts 文件中,一个 node 被定义成:
剩余27页未读,继续阅读














安全验证
文档复制为VIP权益,开通VIP直接复制

评论0