设备树
“Open Firmware Device Tree”,或简称为 Devicetree(DT),是一种用于描述硬 件的数据结构和语言。更确切地说,它是一种操作系统可读的硬件描述,这样操作系统就不 需要对机器的细节进行硬编码。
从结构上看,DT 是一棵树,或者说是带有命名节点的无环图,节点可以有任意数量的命名 属性来封装任意的数据。还存在一种机制,可以在自然的树状结构之外创建从一个节点到 另一个节点的任意链接。
从概念上讲,一套通用的使用惯例,称为 “bindings”(后文译为绑定),被定义为数据 应该如何出现在树中,以描述典型的硬件特性,包括数据总线、中断线、GPIO 连接和外围 设备。
尽可能使用现有的绑定来描述硬件,以最大限度地利用现有的支持代码,但由于属性和节 点名称是简单的文本字符串,通过定义新的节点和属性来扩展现有的绑定或创建新的绑定 很容易。然而,要警惕的是,在创建一个新的绑定之前,最好先对已经存在的东西做一些 功课。目前有两种不同的、不兼容的 i2c 总线的绑定,这是因为在创建新的绑定时没有事先 调查 i2c 设备在现有系统中是如何被枚举的。
1. 历史¶
DT 最初是由 Open Firmware 创建的,作为将数据从 Open Firmware 传递给客户程序 (如传递给操作系统)的通信方法的一部分。操作系统使用设备树在运行时探测硬件的拓 扑结构,从而在没有硬编码信息的情况下支持大多数可用的硬件(假设所有设备的驱动程 序都可用)。
由于 Open Firmware 通常在 PowerPC 和 SPARC 平台上使用,长期以来,对这些架构的 Linux 支持一直使用设备树。
2005 年,当 PowerPC Linux 开始大规模清理并合并 32 位和 64 位支持时,决定在所有 Powerpc 平台上要求 DT 支持,无论它们是否使用 Open Firmware。为了做到这一点, 我们创建了一个叫做扁平化设备树(FDT)的 DT 表示法,它可以作为一个二进制的 blob 传递给内核,而不需要真正的 Open Firmware 实现。U-Boot、kexec 和其他引导程序 被修改,以支持传递设备树二进制(dtb)和在引导时修改 dtb。DT 也被添加到 PowerPC 引导包装器(arch/powerpc/boot/*)中,这样 dtb 就可以被包裹在内核镜像中,以 支持引导现有的非 DT 察觉的固件。
一段时间后,FDT 基础架构被普及到了所有的架构中。在写这篇文章的时候,6 个主线架 构(arm、microblaze、mips、powerpc、sparc 和 x86)和 1 个非主线架构(ios) 有某种程度的 DT 支持。
1. 数据模型¶
如果你还没有读过设备树用法[1]_页,那么现在就去读吧。没关系,我等着….
2.1 高层次视角¶
最重要的是要明白,DT 只是一个描述硬件的数据结构。它没有什么神奇之处,也不会神 奇地让所有的硬件配置问题消失。它所做的是提供一种语言,将硬件配置与 Linux 内核 (或任何其他操作系统)中的板卡和设备驱动支持解耦。使用它可以使板卡和设备支持 变成数据驱动;根据传递到内核的数据做出设置决定,而不是根据每台机器的硬编码选 择。
理想情况下,数据驱动的平台设置应该导致更少的代码重复,并使其更容易用一个内核 镜像支持各种硬件。
Linux 使用 DT 数据有三个主要目的:
平台识别。
运行时配置,以及
设备数量。
2.2 平台识别¶
首先,内核将使用 DT 中的数据来识别特定的机器。在一个理想的世界里,具体的平台对 内核来说并不重要,因为所有的平台细节都会被设备树以一致和可靠的方式完美描述。 但是,硬件并不完美,所以内核必须在早期启动时识别机器,以便有机会运行特定于机 器的修复程序。
在大多数情况下,机器的身份是不相关的,而内核将根据机器的核心 CPU 或 SoC 来选择 设置代码。例如,在 ARM 上,arch/arm/kernel/setup.c 中的 setup_arch()将调 用 arch/arm/kernel/devtree.c 中的 setup_machine_fdt(),它通过 machine_desc 表搜索并选择与设备树数据最匹配的 machine_desc。它通过查看根 设备树节点中的’compatible’属性,并将其与 struct machine_desc 中的 dt_compat 列表(如果你好奇,该列表定义在 arch/arm/include/asm/mach/arch.h 中)进行比较,从而确定最佳匹配。
“compatible” 属性包含一个排序的字符串列表,以机器的确切名称开始,后面是 一个可选的与之兼容的板子列表,从最兼容到最不兼容排序。例如,TI BeagleBoard 和它的后继者 BeagleBoard xM 板的根兼容属性可能看起来分别为:
其中 “ti,map3-beagleboard-xm “指定了确切的型号,它还声称它与 OMAP 3450 SoC 以及一般的 OMP3 系列 SoC 兼容。你会注意到,该列表从最具体的(确切的板子)到最 不具体的(SoC 系列)进行排序。
聪明的读者可能会指出,Beagle xM 也可以声称与原 Beagle 板兼容。然而,我们应 该当心在板级上这样做,因为通常情况下,即使在同一产品系列中,每块板都有很高 的变化,而且当一块板声称与另一块板兼容时,很难确定到底是什么意思。对于高层 来说,最好是谨慎行事,不要声称一块板子与另一块板子兼容。值得注意的例外是, 当一块板子是另一块板子的载体时,例如 CPU 模块连接到一个载体板上。
关于兼容值还有一个注意事项。在兼容属性中使用的任何字符串都必须有文件说明它 表示什么。在 Documentation/devicetree/bindings 中添加兼容字符串的文档。
同样在 ARM 上,对于每个 machine_desc,内核会查看是否有任何 dt_compat 列表条 目出现在兼容属性中。如果有,那么该 machine_desc 就是驱动该机器的候选者。在搜索 了整个 machine_descs 表之后,setup_machine_fdt()根据每个 machine_desc 在兼容属性中匹配的条目,返回 “最兼容” 的 machine_desc。如果没有找到匹配 的 machine_desc,那么它将返回 NULL。
这个方案背后的原因是观察到,在大多数情况下,如果它们都使用相同的 SoC 或相同 系列的 SoC,一个 machine_desc 可以支持大量的电路板。然而,不可避免地会有一些例 外情况,即特定的板子需要特殊的设置代码,这在一般情况下是没有用的。特殊情况 可以通过在通用设置代码中明确检查有问题的板子来处理,但如果超过几个情况下, 这样做很快就会变得很难看和/或无法维护。
相反,兼容列表允许通用 machine_desc 通过在 dt_compat 列表中指定“不太兼容”的值 来提供对广泛的通用板的支持。在上面的例子中,通用板支持可以声称与“ti,ompa3” 或“ti,ompa3450”兼容。如果在最初的 beagleboard 上发现了一个 bug,需要在 早期启动时使用特殊的变通代码,那么可以添加一个新的 machine_desc,实现变通, 并且只在“ti,omap3-beagleboard”上匹配。
PowerPC 使用了一个稍微不同的方案,它从每个 machine_desc 中调用.probe()钩子, 并使用第一个返回 TRUE 的钩子。然而,这种方法没有考虑到兼容列表的优先级,对于 新的架构支持可能应该避免。
评论