Open Firmware 设备树单元测试¶
作者:Gaurav Minocha <gaurav.minocha.os@gmail.com>
1. 简介¶
本文档解释了执行 OF 单元测试所需的测试数据是如何动态附加到活动树上的,且与机器的体系结构无关。
建议在继续阅读之前先阅读以下文档。
OF 自测试(Selftest)旨在测试提供给设备驱动开发者的接口(include/linux/of.h),以便从未展平的设备树数据结构中获取设备信息等。大多数设备驱动程序在各种用例中都会使用此接口。
2. 详细输出(EXPECT)¶
如果单元测试检测到问题,它将在控制台上打印警告或错误消息。由于故意设置了不正确的单元测试数据,单元测试还会触发来自其他内核代码的警告和错误消息。这导致人们产生困惑:触发的消息是测试的预期结果,还是与单元测试无关的真实问题。
单元测试中已添加了 ‘EXPECT : text’(开始)和 ‘EXPECT / : text’(结束)消息,用于报告预期的警告或错误。开始消息在触发警告或错误之前打印,结束消息在触发警告或错误之后打印。
EXPECT 消息会导致控制台产生大量难以阅读的冗余信息。为此创建了脚本 scripts/dtc/of_unittest_expect,用于过滤这些冗余信息,并高亮显示触发的警告/错误与预期警告/错误之间的不匹配项。有关更多信息,请参阅 ‘scripts/dtc/of_unittest_expect --help’。
3. 测试数据(Test-data)¶
设备树源文件(drivers/of/unittest-data/testcases.dtso)包含执行 drivers/of/unittest.c 中自动化单元测试所需的测试数据。请查看该文件夹
drivers/of/unittest-data/tests-*.dtsi
以获取包含在 testcases.dtso 中的设备树源包含文件(.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),并被链接到内核镜像中。
3.1. 添加测试数据¶
未展平的设备树结构
未展平的设备树由以树状结构连接的 device_node 组成,如下所述
// 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,用于反向遍历树。因此,在特定层级上,子节点和所有兄弟节点都具有指向公共节点的 parent 指针(例如,child1、sibling2、sibling3、sibling4 的 parent 指向根节点)
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
图 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() 来反展平(unflatten)展平的二进制 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
图 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
图 3:附加测试用例数据后的活动设备树结构。
敏锐的读者可能已经注意到,与先前的结构(图 2)相比,test-child0 节点变成了最后一个兄弟节点。在附加了第一个 test-child0 之后,又附加了 test-sibling1,这会把子节点(即 test-child0)推成兄弟节点,并使自身成为子节点,如上所述。
如果发现重复的节点(即活动树中已经存在具有相同 full_name 属性的节点),则不会附加该节点,而是通过调用函数 update_node_properties() 将其属性更新到活动树的节点中。
3.2. 移除测试数据¶
一旦测试用例执行完成,就会调用 selftest_data_remove 以移除最初附加的设备节点(首先分离叶节点,然后向上移除父节点,最后移除整棵树)。selftest_data_remove() 调用 detach_node_and_children(),后者使用 of_detach_node() 从活动设备树中分离节点。
为了分离节点,of_detach_node() 会视情况将给定节点父节点的子指针更新为其兄弟节点,或者将前一个兄弟节点连接到给定节点的兄弟节点。就是这样 :)