英语

Open Firmware Devicetree 单元测试

作者: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 中自动化的单元测试所需的测试数据。 请参阅该文件夹的内容

drivers/of/unittest-data/tests-*.dtsi

对于 testcases.dts 中包含的设备树源包含文件 (.dtsi)。

当内核在启用 CONFIG_OF_UNITTEST 的情况下构建时,以下 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() 中,新节点作为给定父节点的子节点附加到实时树中。 但是,如果父节点已经有一个子节点,则新节点将替换当前子节点并将其转换为同级节点。 因此,当 testcase data 节点附加到上面的实时树(图 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:附加 testcase-data 后的实时设备树结构。

精明的读者会注意到,与之前的结构(图 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() 要么将给定节点父节点的子指针更新为其同级节点,要么将先前的同级节点附加到给定节点的同级节点,视情况而定。 就是这样 :)