翻译来自https://www.rocketboards.org/foswiki/Documentation/EmbeddedLinuxBeginnerSGuide

 

 

设备树是一种定义系统内部硬件(包括soc级硬件和板级硬件)的数据结构。设备树是独立于U-Boot和内核之外的一个单独的文件,可单独修改。这意味着,只要编译了这两个软件让其支持你要添加的那些设备了,那您就可以在设备树里做相应的添加或修改来说明您拥有该硬件,接下来内核将处理该过程的其余部分(加载和运行对应的驱动程序)。

 

在设备树出现之前,每个SoC和主板制造商都需要在内核中编写初始化代码,以告诉内核他们特定的平台支持什么。这样,内核将只加载系统中特定硬件的驱动程序。这些SoC或主板有一个“机器ID”跟它们绑定,U-Boot会将机器ID传递给正在启动的机器的内核。这让内核知道要运行哪些初始化例程。

 

随着基于arm的SoC设备的激增,这成为Linux内核维护者的维护噩梦。许多SoC设备具有非常相似的初始化代码或外设,但每个芯片都有自己的内存映射和中断向量。随着SoC设备的增加,在设备之间共享代码变得越来越困难,因为每个制造商必须将地址、寄存器和其他特定于设备的信息硬编码到内核代码中。最终,很多内核开发人员厌倦了处理所有这些问题,决定采用开放固件项目使用的扁平设备树(FDT)接口来描述系统中的硬件。

 

现在,可以在运行时动态确定外设配置细节,而不需要再为功能相同的硬件(也许只碰巧具有不同的内存映射)编写额外的代码。设备树不仅存储内存映射,它还包含有关所使用的中断(如果有的话)、时钟以及驱动程序可能需要了解如何使用设备的任何其他信息的详细信息。有了设备树,单个Linux映像就可以支持多种硬件变体(当您的硬件是可编程的时,这一点显然很重要)。

 

设备树以人类可读(这是有争议的)文本格式(称为设备树源(dts))编写,并编译成设备树二进制文件(dtb)。这个设备树二进制文件通过U-Boot从SD卡加载并放入RAM中。然后bootz命令将加载设备树二进制文件的地址传递给内核。当内核启动时,它加载设备树,解析它,并确定加载哪些驱动程序以适应您的平台硬件。

 

Linux内核使用设备树数据结构有三个主要目的:

  • 平台识别(它在哪个SoC/板上运行)
  • 运行时配置(传递引导参数的另一种方法)
  • 设备填充(为SoC/板上的任何外设或设备加载驱动程序)

 

 了解设备树源

正如其名所示,设备树具有层次结构。有一个由多个子节点组成的根节点。每个子节点可以有其他子节点,每个节点可以有许多不同的属性(一些是标准的,一些是特定于驱动程序的)。设备树源通常包含以下信息:

  • cpu数量
  • 各种ram的大小和位置
  • 总线和桥
  • 外围设备连接
  • 中断控制器和IRQ线路连接
  • 具体的设备驱动配置,例如:
    •   以太网MAC地址
    •   外设输入时钟
 

 下面是来自Altera SoC研讨会系列的图片,它分解了单个节点的内容:

 

 

这只是一种特定类型的节点(这里举例的是Synopsis I2C 控制器)。不同的设备需要不同的属性设定或绑定。当从设备树中读取信息时,每个驱动程序通常都有自己特定的属性集。Linux内核中所有驱动程序的“绑定”都位于内核源代码中的“Documentation/devicetree/bindings”中。在创建您的设备树源时,请确保仔细阅读您的设备将使用的驱动程序的确切绑定。

 

但是内核如何知道如何将设备树中的设备与驱动程序相匹配呢?其实是通过每个设备树节点中的“compatible”字符串。该字符串显示在设备树中的大多数节点中,以及支持设备树设备的任何驱动程序中。每个驱动程序都在一个“兼容性字符串”列表中进行硬编码,这些字符串定义了它们支持哪些设备。当内核在加载设备树时,它读取每个节点的兼容性字符串,并尝试查找具有相同兼容性字符串的驱动程序。如果它找到一个匹配的,它就加载那个驱动程序,并调用驱动程序的“probe”函数来通知它添加了一个新设备(这将在本指南的驱动程序部分进行更详细的解释)。

 

停!在进一步讨论之前,我强烈建议阅读Free electronics发布的“Device Tree for Dummies”指南。如果你想看演示,这里还有一个视频video

 

如果您仍然对设备树的功能和工作原理感到困惑,请跳到本页底部的参考资料部分去查阅更多资料。参考资料还包含一些设备树语法解释的链接。

 

设备树生成器

 

随着可重新编程SoC的发明,只需为SoC编写一次设备树就在不需要修改的场景变少了。由于Altera SoC中的硬件可以更改,因此每次硬件更改时都需要重新创建设备树(这确保内核加载正确的驱动程序和初始化例程)。幸运的是,Altera提供了一个工具,使这个过程相对简单。

 

设备树生成器工具接受Qsys输出的.sopcinfo文件以及一个或多个描述板上设备的板XML文件(不在芯片/Qsys系统内的任何东西)。然后,它将神奇地自动为您生成设备树源。之后,您所要做的就是将设备树源代码编译成二进制文件,然后就可以开始使用了。虽然您可以让设备树为您生成二进制文件(如下图所示),但我建议您只生成源代码,然后再编译。这样,您就有机会检查生成的源代码,并验证它是否做得很好。

 

 

这些XML文件描述了SoC连接到的PCB上的设备和外设。因为这些外部设备不会出现在Qsys中,所以如果你不明确说明它们是什么,生成器就不可能知道它们并正确地将它们包含在设计中。这些文件采用altera创建的语法,将新属性和新节点附加到源中已经存在的节点(也就是从.sopcinfo文件生成的节点)。
 
Rocketboards上的Device Tree Generator页面更详细地描述了语法。它还描述了各种外设所需的板信息。对我们来说特别重要的是“必需的SDMMC外围设备属性”,这是从SD卡启动时需要的。请通读该页的其余部分,以了解一些常用外设需要哪些属性。
 
大多数GHRD都带有两个XML格式的板子文件:一个用于该板上使用的任何设计,另一个用于用户的设计。我们设计中使用的通用XML文件是位于项目根目录中的“hps_common_board_info.xml”。如果您查看这个文件,您会注意到它定义了SD卡、以太网、SPI和其他外设的属性。特定于我们设计的xml文件“soc_system_board_info.xml”只包含一些兼容性字符串,让内核知道我们正在创建Cyclone V SoC设计。如果我们要将自定义I2C或SPI设备连接到板,我们将在这里定义它。
 

生成设备树

 
一旦您设置了board XML文件(或者像本例中那样得到了它们),生成设备树源文件就非常简单了。在root目录“atlas_linux_ghrd”下运行如下命令创建设备树源文件。
 
sopc2dts --input soc_system.sopcinfo\ 
  --output soc_system.dts\ 
  --type dts\ 
  --board soc_system_board_info.xml\ 
  --board hps_common_board_info.xml\ 
  --bridge-removal all\ 
  --clocks

 

 
打开生成的“soc_system.dts”文件并浏览它以查看它如何描述硬件。还要注意它是如何为添加到Qsys系统的“Custom LEDs” IP自动生成设备节点的。下一节将解释这是如何实现的。
 
现在我们知道了设备树生成器创建了什么,接着编译源代码。
 
dtc -I dts -O dtb -o soc_system.dtb soc_system.dts

 

这时就创建了一个名为“soc_system.dtb”的设备树二进制文件。在本指南的下一部分中,这个二进制文件将被放置在SD卡上。这个名称看起来应该很熟悉,因为您已经看到如何配置U-Boot以加载具有该名称的设备树二进制文件(显然不仅仅是巧合)。
 

为自定义IP添加设备树节点信息

 
可以向自定义Qsys组件添加信息,这些信息告诉设备树生成器如何为该组件创建设备树节点。这些设置放在自定义组件的 _hw.tcl文件(请参阅硬件设计概述的参考资料部分,了解有关创建自定义Qsys组件的更多信息)。

 

上面的tcl命令(可以在“custom_leds_hw. tcl”文件中找到)在设备树源中生成了以下节点:

 

当我们为这个模块创建自定义驱动程序时,将使用上述兼容性字符串。我们的模块将在内核中注册,它可以支持任何具有“dev,custom-leds”兼容性字符串的设备。一旦内核知道了这一点,它就会告诉我们的驱动程序,它在读取设备树时找到了这样一个设备。 在那之后,我们的驱动程序将可以访问任何放置在设备树中的信息(在本例中,是寄存器映射和时钟),用于“Custom LEDs”模块的特定实例化。如果那个自定义IP有更多的实例化,那么驱动程序将为每个实例化调用一次。
 
 
 
有关在自定义IP块中添加设备树生成支持的更多信息,请访问 Device Tree Generator documentation
 
 
这应该是你需要知道的关于设备树的所有内容!在下一节中,我们将最终测试我们的系统,并确保在过去的三个步骤中没有产生问题。
 

参考资源