英语

Open Firmware 设备树单元测试

作者:Gaurav Minocha <gaurav.minocha.os@gmail.com>

1. 简介

本文档解释了如何将执行 OF 单元测试所需的测试数据动态附加到活动树,而与机器的架构无关。

建议在继续之前阅读以下文档。

  1. Linux 和设备树

  2. http://www.devicetree.org/Device_Tree_Usage

OF 自测试旨在测试提供给设备驱动程序开发人员的接口 (include/linux/of.h),以从非扁平化的设备树数据结构中获取设备信息等。大多数设备驱动程序在各种用例中使用此接口。

2. 详细输出 (EXPECT)

如果单元测试检测到问题,它将在控制台上打印警告或错误消息。由于有意使用错误的单元测试数据,单元测试还会触发来自其他内核代码的警告和错误消息。这导致了混淆,即触发的消息是测试的预期结果,还是存在独立于单元测试的真实问题。

‘EXPECT : text’(开始)和 ‘EXPECT / : text’(结束)消息已添加到单元测试中,以报告预期会出现警告或错误。开始消息在触发警告或错误之前打印,结束消息在触发警告或错误之后打印。

EXPECT 消息会导致非常嘈杂的控制台消息,难以阅读。创建了脚本 scripts/dtc/of_unittest_expect 来过滤此冗长性,并突出显示触发的警告和错误与预期的警告和错误之间的不匹配。有关更多信息,请从 ‘scripts/dtc/of_unittest_expect --help’ 中获取。

3. 测试数据

设备树源文件 (drivers/of/unittest-data/testcases.dts) 包含在 drivers/of/unittest.c 中自动执行单元测试所需的测试数据。目前,以下设备树源包含文件 (.dtsi) 包含在 testcases.dts 中

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

当内核启用 OF_SELFTEST 构建时,则使用以下 make 规则

$(obj)/%.dtb: $(src)/%.dts FORCE
        $(call if_changed_dep, dtc)

将 DT 源文件 (testcases.dts) 编译为二进制 blob (testcases.dtb),也称为扁平化 DT。

之后,使用以下规则将上面的二进制 blob 包装为汇编文件 (testcases.dtb.S)

$(obj)/%.dtb.S: $(obj)/%.dtb
        $(call cmd, dt_S_dtb)

汇编文件被编译成目标文件 (testcases.dtb.o),并链接到内核映像中。

3.1. 添加测试数据

非扁平化设备树结构

非扁平化设备树由以树结构形式连接的 device_node(s) 组成,如下所述

// 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

图 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

图 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() 要么将给定节点父节点的子指针更新为它的兄弟节点,要么将之前的兄弟节点附加到给定节点的兄弟节点,视情况而定。就是这样:)