逸珺 · 2020年12月10日

从Linus Torvalds一封发飙的电邮开始谈设备树究竟是棵什么树?

首发:嵌入式客栈
作者:逸珺

[导读] 新版的U-Boot以及内核都引入了设备树,那么这究竟是棵什么样的树呢?长啥样?有啥用?为啥弄个这样的树?本文基于对设备树标准的理解,来学习整理一下相关的要点,供大家参考。

Linux为啥要设备树?

在Linux3.x之前的内核源码中,存在大量对板级细节信息描述的代码。这些代码充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目录,而且更严重的问题是,由于ARM商业生态模式,基于ARM IP授权模式,产生越来越多ARM核芯片。如此一来这类辣鸡代码越来越多,维护变得愈加困难。于是在2011年3月17这天,Linux之父Linus Torvalds飙了,邮件中骂到:“this whole ARM thing is a f*cking pain in the ass”。

image.png
自此之后,Linux内核引入了设备树机制以描述计算机板机底层硬件信息。

啥是设备树?

设备树(device tree)是一种描述特定计算机的硬件组件的数据结构,以便操作系统的内核或者引导程序可以使用和管理那些组件,包括一个或多个CPU,内存,总线和外围设备 。为什么说这个数据结构是树呢?
设备树是通过Open Firmware项目从基于SPARC的工作站和服务器发展而来,由https://www.devicetree.org/组...,目前的发展至V0.3版。
来看看设备树标准中的例子:该图显示了一个简单的设备树的示例表示,该树几乎完整到足以启动一个简单的操作系统,描述了平台类型,CPU,内存和1个UART。看这个图,正是一种树形数据结构。

image.png

节点

设备树数据结构,本质上由一系列带属性的节点组成,节点由节点名、单元地址以及属性组成。描述节点的语法为:

`node-name@unit-address
`

  • node-name 由1到31个ASCII字符描述,字符可取0-9,a-z,A-Z,逗号(,),点(.),加减号(+ -),以及下划线(\_)组成
  • unit-address:节点所在的总线类型。
  • / :表示树的根节点

那么上图的节点是哪些呢?移除掉属性简化一下:

image.png

节点的单元地址特定于节点所在的总线类型。单元地址必须与节点的r一般与reg属性中指定的第一个地址匹配。如果节点不具有reg属性,则必须省略@ unit-address,并且节点名称仅会将节点与树中同一级别的其他节点区分开。特定总线的绑定可以为reg和unit-address的格式指定其他更具体的要求。
节点名称,一般按照功能描述进行命令,以提升可读性,比如:

  • adc
  • atm
  • audio-codec
  • audio-controller
  • backlight
  • bluetooth
  • bus
  • .......

路径名path name

通过指定从根节点到所有后代节点到所需节点的完整路径,可以唯一标识设备树中的节点。按照下述约定进行描述:

`/node-name-1/node-name-2/node-name-N
`

比如上图中CPU1的路径为:

`/cpus/cpu@1
`

属性语法

属性用于描述节点的特征,由属性名及值组成。

  • 属性名,可由下表中字符组成:

image.png

  • <empty> 空,无值
  • <u32> <u64> ,大端16进制数,所谓大端模式就是高字节存在低地址
  • <string> 以'\0'字符串结尾的字符串
  • <phandle> 引用设备树中另一个节点的方法。见标准属性中举例。
  • <stringlist>  字符串表

标准属性

  • compatible, 其值为<stringlist>,兼容的属性值包含一个或多个字符串,这些字符串定义了设备的编程模型。使用此字符串列表选择设备驱动程序。该属性值由具有空终止字符串的串联列表组成。它们使设备可以表达与一系列类似设备的兼容性,从而可能使单个设备驱动程序与多个设备匹配。比如

`compatible = "fsl,mpc8641", "ns16550";
`

  • model, <string> 其值为字符串,用于指定设备制造商的型号。如上例中的mpc8641/ns16550
  • phandle,<u32>phandle属性为设备树内唯一的节点指定一个数字标识符。phandle属性值由其他需要引用与该属性关联的节点的节点使用。比如:

`pic@10000000 {
  phandle = <1>;
  interrupt-controller;
};
`

定义的phandle值为1。另一个设备节点可以使用一个phandle值1引用pic节点:

`another-device-node {
  interrupt-parent = <1>;
};
`

  • status,<string> status属性指示设备的运行状态。取值范围定义如下表:

image.png

  • #address-cells 及 #size-cells,可以在设备树层次结构中具有子节点的任何设备节点中使用#address-cells和#size-cells属性,它们描述如何寻址子设备节点。
  • address-cells属性指示在reg属性中需要多少个单元(即32位值)来形成address字段。

  • #size-cells属性定义子节点的reg属性中size字段需要多少个单元(即32位值)
  • reg, <prop-encoded-array>, ( address , length ) 对。
  • virtual-reg,<u32> virtual-reg属性指定一个有效地址,该地址映射到设备节点的reg属性中指定的第一个物理地址。此属性使引导程序可以为客户端程序提供已设置的虚拟到物理的映射。
  • ranges,<empty> 或 <prop-encoded-array>任意数量的(child-bus-address,parent-bus-address,length)三元组。ranges属性提供了一种定义总线的地址空间(子地址空间)和总线节点的父节点的地址空间(父地址空间)之间的映射或转换的方法。

`soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;
    serial@4600 {
        device_type = "serial";
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};
`

其中:

`<0x0 0xe0000000 0x00100000>;
`

此属性值指定对于1024 KB的地址空间范围,以物理0x0寻址的子节点映射到物理0xe0000000的父地址。通过这种映射,可以通过地址为0xe0004600的加载或存储,偏移量0x4600(在reg中指定)和在range中指定的0xe0000000映射来寻址串行设备节点。

  • dma-ranges,<empty>或<prop-encoded-array>形式的任意数量的(child-bus-address,parent-bus-address,length)三元组。dma-ranges属性用于描述内存映射总线的直接内存访问(DMA)结构,该总线可以从源于该总线的DMA操作访问其设备树父对象。它提供了一种定义总线的物理地址空间和总线父级的物理地址空间之间的映射或转换的方法。
  • child-bus-address是子总线地址空间内的物理地址。表示地址的宽度取决于总线,并且可以从此节点(出现dma-ranges属性的节点)的#address-cells中确定。
  • parent-bus-address是父总线地址空间内的物理地址。代表父级地址的cell数量取决于总线,可以从定义父级地址空间的节点的#address-cells属性中确定。
  • length指定了子地址空间中范围的大小。可以从节点(定义了dma-ranges属性的节点)的size-cells来确定表示大小的cell数。

总结一下

本文总结了Linux设备树出现的缘由,以及设备树节点常规概念:节点、属性,以及标准属性。设备树还有中断以及中断映射、设备绑定、二进制格式等概念,后续有时间在学习整理。

辛苦原创,如喜欢请点赞/在看/分享,不胜感激!

_END_—

推荐阅读

更多硬核嵌入式技术干货请关注嵌入式客栈专栏。
推荐阅读
关注数
2834
内容数
164
分享一些在嵌入式应用开发方面的浅见,广交朋友
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息