Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,37 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/changesets.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
============
设备树变更集
============
设备树变更集是一种方法,它允许人们以这样一种方式在实时树中使用变化,即要么使用全部的
变化,要么不使用。如果在使用变更集的过程中发生错误,那么树将被回滚到之前的状态。一个
变更集也可以在使用后被删除。
当一个变更集被使用时,所有的改变在发出OF_RECONFIG通知器之前被一次性使用到树上。这是
为了让接收者在收到通知时看到一个完整的、一致的树的状态。
一个变化集的顺序如下。
1. of_changeset_init() - 初始化一个变更集。
2. 一些DT树变化的调用,of_changeset_attach_node(), of_changeset_detach_node(),
of_changeset_add_property(), of_changeset_remove_property,
of_changeset_update_property()来准备一组变更。此时不会对活动树做任何变更。所有
的变更操作都记录在of_changeset的 `entries` 列表中。
3. of_changeset_apply() - 将变更使用到树上。要么整个变更集被使用,要么如果有错误,
树会被恢复到之前的状态。核心通过锁确保正确的顺序。如果需要的话,可以使用一个解锁的
__of_changeset_apply版本。
如果一个成功使用的变更集需要被删除,可以用of_changeset_revert()来完成。
@@ -0,0 +1,31 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/dynamic-resolution-notes.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
========================
Devicetree动态解析器说明
========================
本文描述了内核内DeviceTree解析器的实现,它位于drivers/of/resolver.c中。
解析器如何工作?
----------------
解析器被赋予一个任意的树作为输入,该树用适当的dtc选项编译,并有一个/plugin/标签。这就产
生了适当的__fixups__和__local_fixups__节点。
解析器依次通过以下步骤工作:
1. 从实时树中获取最大的设备树phandle值 + 1.
2. 调整树的所有本地 phandles,以解决这个量。
3. 使用 __local__fixups__ 节点信息以相同的量调整所有本地引用。
4. 对于__fixups__节点中的每个属性,找到它在实时树中引用的节点。这是用来标记该节点的标签。
5. 检索fixup的目标的phandle。
6. 对于属性中的每个fixup,找到节点:属性:偏移的位置,并用phandle值替换它。
@@ -0,0 +1,45 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/index.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
=============================
Open Firmware 和 Devicetree
=============================
该文档是整个设备树文档的总目录,标题中多是业内默认的术语,初见就翻译成中文,
晦涩难懂,因此尽量保留,后面翻译其子文档时,可能会根据语境,灵活地翻译为中文。
内核Devicetree的使用
=======================
.. toctree::
:maxdepth: 1
usage-model
of_unittest
kernel-api
Devicetree Overlays
===================
.. toctree::
:maxdepth: 1
changesets
dynamic-resolution-notes
overlay-notes
Devicetree Bindings
===================
.. toctree::
:maxdepth: 1
Todolist:
* bindings/index
@@ -0,0 +1,58 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/kernel-api.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
=================
内核中的设备树API
=================
核心函数
--------
该API在以下内核代码中:
drivers/of/base.c
include/linux/of.h
drivers/of/property.c
include/linux/of_graph.h
drivers/of/address.c
drivers/of/irq.c
drivers/of/fdt.c
驱动模型函数
------------
该API在以下内核代码中:
include/linux/of_device.h
drivers/of/device.c
include/linux/of_platform.h
drivers/of/platform.c
覆盖和动态DT函数
----------------
该API在以下内核代码中:
drivers/of/resolver.c
drivers/of/dynamic.c
drivers/of/overlay.c
@@ -0,0 +1,192 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/of_unittest.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
=================================
Open Firmware Devicetree 单元测试
=================================
作者: Gaurav Minocha <gaurav.minocha.os@gmail.com>
1. 概述
=======
本文档解释了执行 OF 单元测试所需的测试数据是如何动态地附加到实时树上的,与机器的架构无关。
建议在继续读下去之前,先阅读以下文件。
(1) Documentation/devicetree/usage-model.rst
(2) http://www.devicetree.org/Device_Tree_Usage
OF Selftest被设计用来测试提供给设备驱动开发者的接口(include/linux/of.h),以从未扁平
化的设备树数据结构中获取设备信息等。这个接口被大多数设备驱动在各种使用情况下使用。
2. 测试数据
===========
设备树源文件(drivers/of/unittest-data/testcases.dtso)包含执行drivers/of/unittest.c
中自动化单元测试所需的测试数据。目前,以下设备树源包含文件(.dtsi)被包含在testcases.dtso中::
drivers/of/unittest-data/tests-interrupts.dtsi
drivers/of/unittest-data/tests-platform.dtsi
drivers/of/unittest-data/tests-phandle.dtsi
drivers/of/unittest-data/tests-match.dtsi
drivers/of/unittest-data/tests-address.dtsi
drivers/of/unittest-data/tests-overlay.dtsi
drivers/of/unittest-data/tests-lifecycle.dtsi
当内核在启用CONFIG_OF_UNITTEST的情况下被构建时,那么下面的make规则::
$(obj)/%.dtbo: $(src)/%.dtso $(DTC) FORCE
$(call if_changed_dep,dtc)
用于将DT源文件(testcases.dtso)编译成二进制blob(testcases.dtbo),也被称为扁平化的DT。
之后,使用以下规则将上述二进制blob包装成一个汇编文件(testcases.dtbo.S)::
$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE
$(call if_changed,wrap_S_dtb)
汇编文件被编译成一个对象文件(testcases.dtbo.o),并被链接到内核镜像中。
2.1. 添加测试数据
-----------------
未扁平化的设备树结构体:
未扁平化的设备树由连接的设备节点组成,其树状结构形式如下所述::
// following struct members are used to construct the tree
struct device_node {
...
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
...
};
图1描述了一个机器的未扁平化设备树的通用结构,只考虑了子节点和同级指针。存在另一个指针,
``*parent`` ,用于反向遍历该树。因此,在一个特定的层次上,子节点和所有的兄弟姐妹节点将
有一个指向共同节点的父指针(例如,child1、sibling2、sibling3、sibling4的父指针指向
根节点)::
root ('/')
|
child1 -> sibling2 -> sibling3 -> sibling4 -> null
| | | |
| | | null
| | |
| | child31 -> sibling32 -> null
| | | |
| | null null
| |
| child21 -> sibling22 -> sibling23 -> null
| | | |
| null null null
|
child11 -> sibling12 -> sibling13 -> sibling14 -> null
| | | |
| | | null
| | |
null null child131 -> null
|
null
Figure 1: 未扁平化的设备树的通用结构
在执行OF单元测试之前,需要将测试数据附加到机器的设备树上(如果存在)。因此,当调用
selftest_data_add()时,首先会读取通过以下内核符号链接到内核镜像中的扁平化设备树
数据::
__dtb_testcases_begin - address marking the start of test data blob
__dtb_testcases_end - address marking the end of test data blob
其次,它调用of_fdt_unflatten_tree()来解除扁平化的blob。最后,如果机器的设备树
(即实时树)是存在的,那么它将未扁平化的测试数据树附加到实时树上,否则它将自己作为
实时设备树附加。
attach_node_and_children()使用of_attach_node()将节点附加到实时树上,如下所
述。为了解释这一点,图2中描述的测试数据树被附加到图1中描述的实时树上::
root ('/')
|
testcase-data
|
test-child0 -> test-sibling1 -> test-sibling2 -> test-sibling3 -> null
| | | |
test-child01 null null null
Figure 2: 将测试数据树附在实时树上的例子。
根据上面的方案,实时树已经存在,所以不需要附加根('/')节点。所有其他节点都是通过在
每个节点上调用of_attach_node()来附加的。
在函数of_attach_node()中,新的节点被附在实时树中给定的父节点的子节点上。但是,如
果父节点已经有了一个孩子,那么新节点就会取代当前的孩子,并将其变成其兄弟姐妹。因此,
当测试案例的数据节点被连接到上面的实时树(图1)时,最终的结构如图3所示::
root ('/')
|
testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null
| | | | |
(...) | | | null
| | child31 -> sibling32 -> null
| | | |
| | null null
| |
| child21 -> sibling22 -> sibling23 -> null
| | | |
| null null null
|
child11 -> sibling12 -> sibling13 -> sibling14 -> null
| | | |
null null | null
|
child131 -> null
|
null
-----------------------------------------------------------------------
root ('/')
|
testcase-data -> child1 -> sibling2 -> sibling3 -> sibling4 -> null
| | | | |
| (...) (...) (...) null
|
test-sibling3 -> test-sibling2 -> test-sibling1 -> test-child0 -> null
| | | |
null null null test-child01
Figure 3: 附加测试案例数据后的实时设备树结构。
聪明的读者会注意到,与先前的结构相比,test-child0节点成为最后一个兄弟姐妹(图2)。
在连接了第一个test-child0节点之后,又连接了test-sibling1节点,该节点推动子节点
(即test-child0)成为兄弟姐妹,并使自己成为子节点,如上所述。
如果发现一个重复的节点(即如果一个具有相同full_name属性的节点已经存在于实时树中),
那么该节点不会被附加,而是通过调用函数update_node_properties()将其属性更新到活
树的节点中。
2.2. 删除测试数据
-----------------
一旦测试用例执行完,selftest_data_remove被调用,以移除最初连接的设备节点(首先是
叶子节点被分离,然后向上移动父节点被移除,最后是整个树)。selftest_data_remove()
调用detach_node_and_children(),使用of_detach_node()将节点从实时设备树上分离。
为了分离一个节点,of_detach_node()要么将给定节点的父节点的子节点指针更新为其同级节
点,要么根据情况将前一个同级节点附在给定节点的同级节点上。就这样吧。 :)
@@ -0,0 +1,140 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/overlay-notes.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
==============
设备树覆盖说明
==============
本文档描述了drivers/of/overlay.c中的内核内设备树覆盖功能的实现,是
Documentation/devicetree/dynamic-resolution-notes.rst[1]的配套文档。
覆盖如何工作
------------
设备树覆盖的目的是修改内核的实时树,并使修改以反映变化的方式影响内核的状态。
由于内核主要处理的是设备,任何新的设备节点如果导致一个活动的设备,就应该创建它,
而如果设备节点被禁用或被全部删除,受影响的设备应该被取消注册。
让我们举个例子,我们有一个foo板,它的基本树形图如下::
---- foo.dts ---------------------------------------------------------------
/* FOO平台 */
/dts-v1/;
/ {
compatible = "corp,foo";
/* 共享的资源 */
res: res {
};
/* 芯片上的外围设备 */
ocp: ocp {
/* 总是被实例化的外围设备 */
peripheral1 { ... };
};
};
---- foo.dts ---------------------------------------------------------------
覆盖bar.dtso,
::
---- bar.dtso - 按标签覆盖目标位置 ---------------------------
/dts-v1/;
/插件/;
&ocp {
/* bar外围 */
bar {
compatible = "corp,bar";
... /* 各种属性和子节点 */
};
};
---- bar.dtso --------------------------------------------------------------
当加载(并按照[1]中描述的方式解决)时,应该产生foo+bar.dts::
---- foo+bar.dts -----------------------------------------------------------
/* FOO平台 + bar外围 */
/ {
compatible = "corp,foo";
/* 共享资源 */
res: res {
};
/* 芯片上的外围设备 */
ocp: ocp {
/* 总是被实例化的外围设备 */
peripheral1 { ... };
/* bar外围 */
bar {
compatible = "corp,bar";
... /* 各种属性和子节点 */
};
};
};
---- foo+bar.dts -----------------------------------------------------------
作为覆盖的结果,已经创建了一个新的设备节点(bar),因此将注册一个bar平台设备,
如果加载了匹配的设备驱动程序,将按预期创建设备。
如果基础DT不是用-@选项编译的,那么“&ocp”标签将不能用于将覆盖节点解析到基础
DT中的适当位置。在这种情况下,可以提供目标路径。通过标签的目标位置的语法是比
较好的,因为不管标签在DT中出现在哪里,覆盖都可以被应用到任何包含标签的基础DT上。
上面的bar.dtso例子被修改为使用目标路径语法,即为::
---- bar.dtso - 通过明确的路径覆盖目标位置 -------------------
/dts-v1/;
/插件/;
&{/ocp} {
/* bar外围 */
bar {
compatible = "corp,bar";
... /* 各种外围设备和子节点 */
}
};
---- bar.dtso --------------------------------------------------------------
内核中关于覆盖的API
-------------------
该API相当容易使用。
1) 调用of_overlay_fdt_apply()来创建和应用一个覆盖的变更集。返回值是一个
错误或一个识别这个覆盖的cookie。
2) 调用of_overlay_remove()来删除和清理先前通过调用of_overlay_fdt_apply()
而创建的覆盖变更集。不允许删除一个被另一个覆盖的覆盖变化集。
最后,如果你需要一次性删除所有的覆盖,只需调用of_overlay_remove_all(),
它将以正确的顺序删除每一个覆盖。
你可以选择注册在覆盖操作中被调用的通知器。详见
of_overlay_notifier_register/unregister和enum of_overlay_notify_action。
OF_OVERLAY_PRE_APPLY、OF_OVERLAY_POST_APPLY或OF_OVERLAY_PRE_REMOVE
的通知器回调可以存储指向覆盖层中的设备树节点或其内容的指针,但这些指针不能持
续到OF_OVERLAY_POST_REMOVE的通知器回调。在OF_OVERLAY_POST_REMOVE通
知器被调用后,包含覆盖层的内存将被kfree()ed。请注意,即使OF_OVERLAY_POST_REMOVE
的通知器返回错误,内存也会被kfree()ed。
drivers/of/dynamic.c中的变更集通知器是第二种类型的通知器,可以通过应用或移除
覆盖层来触发。这些通知器不允许在覆盖层或其内容中存储指向设备树节点的指针。当包含
覆盖层的内存因移除覆盖层而被释放时,覆盖层代码并不能防止这类指针仍然有效。
任何其他保留指向覆盖层节点或数据的指针的代码都被认为是一个错误,因为在移除覆盖层
后,该指针将指向已释放的内存。
覆盖层的用户必须特别注意系统上发生的整体操作,以确保其他内核代码不保留任何指向覆
盖层节点或数据的指针。任何无意中使用这种指针的例子是,如果一个驱动或子系统模块在
应用了覆盖后被加载,并且该驱动或子系统扫描了整个设备树或其大部分,包括覆盖节点。
@@ -0,0 +1,330 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/devicetree/usage-model.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
===================
Linux 和 Devicetree
===================
Linux对设备树数据的使用模型
:作者: Grant Likely <grant.likely@secretlab.ca>
这篇文章描述了Linux如何使用设备树。关于设备树数据格式的概述可以在
devicetree.org\ [1]_ 的设备树使用页面上找到。
.. [1] https://www.devicetree.org/specifications/
"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数据有三个主要目的:
1) 平台识别。
2) 运行时配置,以及
3) 设备数量。
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板的根兼容属性可能看起来分别为::
compatible = "ti,omap3-beagleboard", "ti,omap3450", "ti,omap3";
compatible = "ti,omap3-beagleboard-xm", "ti,omap3450", "ti,omap3";
其中 "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的钩子。然而,这种方法没有考虑到兼容列表的优先级,对于
新的架构支持可能应该避免。
2.3 运行时配置
--------------
在大多数情况下,DT是将数据从固件传递给内核的唯一方法,所以也被用来传递运行
时和配置数据,如内核参数字符串和initrd镜像的位置。
这些数据大部分都包含在/chosen节点中,当启动Linux时,它看起来就像这样::
chosen {
bootargs = "console=ttyS0,115200 loglevel=8";
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>;
};
bootargs属性包含内核参数,initrd-\*属性定义initrd blob的地址和大小。注
意initrd-end是initrd映像后的第一个地址,所以这与结构体资源的通常语义不一
致。选择的节点也可以选择包含任意数量的额外属性,用于平台特定的配置数据。
在早期启动过程中,架构设置代码通过不同的辅助回调函数多次调用
of_scan_flat_dt()来解析设备树数据,然后进行分页设置。of_scan_flat_dt()
代码扫描设备树,并使用辅助函数来提取早期启动期间所需的信息。通常情况下,
early_init_dt_scan_chosen()辅助函数用于解析所选节点,包括内核参数,
early_init_dt_scan_root()用于初始化DT地址空间模型,early_init_dt_scan_memory()
用于确定可用RAM的大小和位置。
在ARM上,函数setup_machine_fdt()负责在选择支持板子的正确machine_desc
后,对设备树进行早期扫描。
2.4 设备数量
------------
在电路板被识别后,在早期配置数据被解析后,内核初始化可以以正常方式进行。在
这个过程中的某个时刻,unflatten_device_tree()被调用以将数据转换成更有
效的运行时表示。这也是调用机器特定设置钩子的时候,比如ARM上的machine_desc
.init_early()、.init_irq()和.init_machine()钩子。本节的其余部分使用
了ARM实现的例子,但所有架构在使用DT时都会做几乎相同的事情。
从名称上可以猜到,.init_early()用于在启动过程早期需要执行的任何机器特定设
置,而.init_irq()则用于设置中断处理。使用DT并不会实质性地改变这两个函数的
行为。如果提供了DT,那么.init_early()和.init_irq()都能调用任何一个DT查
询函数(of_* in include/linux/of*.h),以获得关于平台的额外数据。
DT上下文中最有趣的钩子是.init_machine(),它主要负责将平台的数据填充到
Linux设备模型中。历史上,这在嵌入式平台上是通过在板卡support .c文件中定
义一组静态时钟结构、platform_devices和其他数据,并在.init_machine()中
大量注册来实现的。当使用DT时,就不用为每个平台的静态设备进行硬编码,可以通过
解析DT获得设备列表,并动态分配设备结构体。
最简单的情况是,.init_machine()只负责注册一个platform_devices。
platform_device是Linux使用的一个概念,用于不能被硬件检测到的内存或I/O映
射的设备,以及“复合”或 “虚拟”设备(后面会详细介绍)。虽然DT没有“平台设备”的
术语,但平台设备大致对应于树根的设备节点和简单内存映射总线节点的子节点。
现在是举例说明的好时机。下面是NVIDIA Tegra板的设备树的一部分::
/{
compatible = "nvidia,harmony", "nvidia,tegra20";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
chosen { };
aliases { };
memory {
device_type = "memory";
reg = <0x00000000 0x40000000>;
};
soc {
compatible = "nvidia,tegra20-soc", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
intc: interrupt-controller@50041000 {
compatible = "nvidia,tegra20-gic";
interrupt-controller;
#interrupt-cells = <1>;
reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >;
};
serial@70006300 {
compatible = "nvidia,tegra20-uart";
reg = <0x70006300 0x100>;
interrupts = <122>;
};
i2s1: i2s@70002800 {
compatible = "nvidia,tegra20-i2s";
reg = <0x70002800 0x100>;
interrupts = <77>;
codec = <&wm8903>;
};
i2c@7000c000 {
compatible = "nvidia,tegra20-i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x7000c000 0x100>;
interrupts = <70>;
wm8903: codec@1a {
compatible = "wlf,wm8903";
reg = <0x1a>;
interrupts = <347>;
};
};
};
sound {
compatible = "nvidia,harmony-sound";
i2s-controller = <&i2s1>;
i2s-codec = <&wm8903>;
};
};
在.init_machine()时,Tegra板支持代码将需要查看这个DT,并决定为哪些节点
创建platform_devices。然而,看一下这个树,并不能立即看出每个节点代表什么
类型的设备,甚至不能看出一个节点是否代表一个设备。/chosen、/aliases和
/memory节点是信息节点,并不描述设备(尽管可以说内存可以被认为是一个设备)。
/soc节点的子节点是内存映射的设备,但是codec@1a是一个i2c设备,而sound节
点代表的不是一个设备,而是其他设备是如何连接在一起以创建音频子系统的。我知
道每个设备是什么,因为我熟悉电路板的设计,但是内核怎么知道每个节点该怎么做?
诀窍在于,内核从树的根部开始,寻找具有“兼容”属性的节点。首先,一般认为任何
具有“兼容”属性的节点都代表某种设备;其次,可以认为树根的任何节点要么直接连
接到处理器总线上,要么是无法用其他方式描述的杂项系统设备。对于这些节点中的
每一个,Linux都会分配和注册一个platform_device,它又可能被绑定到一个
platform_driver。
为什么为这些节点使用platform_device是一个安全的假设?嗯,就Linux对设备
的建模方式而言,几乎所有的总线类型都假定其设备是总线控制器的孩子。例如,每
个i2c_client是i2c_master的一个子节点。每个spi_device都是SPI总线的一
个子节点。类似的还有USB、PCI、MDIO等。同样的层次结构也出现在DT中,I2C设
备节点只作为I2C总线节点的子节点出现。同理,SPI、MDIO、USB等等。唯一不需
要特定类型的父设备的设备是platform_devices(和amba_devices,但后面会
详细介绍),它们将愉快地运行在Linux/sys/devices树的底部。因此,如果一个
DT节点位于树的根部,那么它真的可能最好注册为platform_device。
Linux板支持代码调用of_platform_populate(NULL, NULL, NULL, NULL)来
启动树根的设备发现。参数都是NULL,因为当从树的根部开始时,不需要提供一个起
始节点(第一个NULL),一个父结构设备(最后一个NULL),而且我们没有使用匹配
表(尚未)。对于只需要注册设备的板子,除了of_platform_populate()的调用,
.init_machine()可以完全为空。
在Tegra的例子中,这说明了/soc和/sound节点,但是SoC节点的子节点呢?它们
不应该也被注册为平台设备吗?对于Linux DT支持,一般的行为是子设备在驱动
.probe()时被父设备驱动注册。因此,一个i2c总线设备驱动程序将为每个子节点
注册一个i2c_client,一个SPI总线驱动程序将注册其spi_device子节点,其他
总线类型也是如此。根据该模型,可以编写一个与SoC节点绑定的驱动程序,并简单
地为其每个子节点注册platform_device。板卡支持代码将分配和注册一个SoC设
备,一个(理论上的)SoC设备驱动程序可以绑定到SoC设备,并在其.probe()钩
中为/soc/interruptcontroller、/soc/serial、/soc/i2s和/soc/i2c注
册platform_devices。很简单,对吗?
实际上,事实证明,将一些platform_device的子设备注册为更多的platform_device
是一种常见的模式,设备树支持代码反映了这一点,并使上述例子更简单。
of_platform_populate()的第二个参数是一个of_device_id表,任何与该表
中的条目相匹配的节点也将获得其子节点的注册。在Tegra的例子中,代码可以是
这样的::
static void __init harmony_init_machine(void)
{
/* ... */
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
}
“simple-bus”在Devicetree规范中被定义为一个属性,意味着一个简单的内存映射
的总线,所以of_platform_populate()代码可以被写成只是假设简单总线兼容的节
点将总是被遍历。然而,我们把它作为一个参数传入,以便电路板支持代码可以随时覆
盖默认行为。
[需要添加关于添加i2c/spi/etc子设备的讨论] 。
附录A:AMBA设备
---------------
ARM Primecell是连接到ARM AMBA总线的某种设备,它包括对硬件检测和电源管理
的一些支持。在Linux中,amba_device和amba_bus_type结构体被用来表示
Primecell设备。然而,棘手的一点是,AMBA总线上的所有设备并非都是Primecell,
而且对于Linux来说,典型的情况是amba_device和platform_device实例都是同
一总线段的同义词。
当使用DT时,这给of_platform_populate()带来了问题,因为它必须决定是否将
每个节点注册为platform_device或amba_device。不幸的是,这使设备创建模型
变得有点复杂,但解决方案原来并不是太具有侵略性。如果一个节点与“arm,primecell”
兼容,那么of_platform_populate()将把它注册为amba_device而不是
platform_device。