요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
=================================
Open Firmware Devicetree Unittest
=================================
Author: Gaurav Minocha <[email protected]>
1. Introduction
===============
This document explains how the test data required for executing OF unittest
is attached to the live tree dynamically, independent of the machine's
architecture.
It is recommended to read the following documents before moving ahead.
(1) Documentation/devicetree/usage-model.rst
(2) http://www.devicetree.org/Device_Tree_Usage
OF Selftest has been designed to test the interface (include/linux/of.h)
provided to device driver developers to fetch the device information..etc.
from the unflattened device tree data structure. This interface is used by
most of the device drivers in various use cases.
2. Verbose Output (EXPECT)
==========================
If unittest detects a problem it will print a warning or error message to
the console. Unittest also triggers warning and error messages from other
kernel code as a result of intentionally bad unittest data. This has led
to confusion as to whether the triggered messages are an expected result
of a test or whether there is a real problem that is independent of unittest.
'EXPECT \ : text' (begin) and 'EXPECT / : text' (end) messages have been
added to unittest to report that a warning or error is expected. The
begin is printed before triggering the warning or error, and the end is
printed after triggering the warning or error.
The EXPECT messages result in very noisy console messages that are difficult
to read. The script scripts/dtc/of_unittest_expect was created to filter
this verbosity and highlight mismatches between triggered warnings and
errors vs expected warnings and errors. More information is available
from 'scripts/dtc/of_unittest_expect --help'.
3. Test-data
============
The Device Tree Source file (drivers/of/unittest-data/testcases.dts) contains
the test data required for executing the unit tests automated in
drivers/of/unittest.c. See the content of the folder::
drivers/of/unittest-data/tests-*.dtsi
for the Device Tree Source Include files (.dtsi) included in testcases.dts.
When the kernel is built with CONFIG_OF_UNITTEST enabled, then the following make
rule::
$(obj)/%.dtb: $(src)/%.dts FORCE
$(call if_changed_dep, dtc)
is used to compile the DT source file (testcases.dts) into a binary blob
(testcases.dtb), also referred as flattened DT.
After that, using the following rule the binary blob above is wrapped as an
assembly file (testcases.dtb.S)::
$(obj)/%.dtb.S: $(obj)/%.dtb
$(call cmd, dt_S_dtb)
The assembly file is compiled into an object file (testcases.dtb.o), and is
linked into the kernel image.
3.1. Adding the test data
-------------------------
Un-flattened device tree structure:
Un-flattened device tree consists of connected device_node(s) in form of a tree
structure described below::
// following struct members are used to construct the tree
struct device_node {
...
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
...
};
Figure 1, describes a generic structure of machine's un-flattened device tree
considering only child and sibling pointers. There exists another pointer,
``*parent``, that is used to traverse the tree in the reverse direction. So, at
a particular level the child node and all the sibling nodes will have a parent
pointer pointing to a common node (e.g. child1, sibling2, sibling3, sibling4's
parent points to root node)::
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: Generic structure of un-flattened device tree
Before executing OF unittest, it is required to attach the test data to
machine's device tree (if present). So, when selftest_data_add() is called,
at first it reads the flattened device tree data linked into the kernel image
via the following kernel symbols::
__dtb_testcases_begin - address marking the start of test data blob
__dtb_testcases_end - address marking the end of test data blob
Secondly, it calls of_fdt_unflatten_tree() to unflatten the flattened
blob. And finally, if the machine's device tree (i.e. live tree) is present,
then it attaches the unflattened test data tree to the live tree, else it
attaches itself as a live device tree.
attach_node_and_children() uses of_attach_node() to attach the nodes into the
live tree as explained below. To explain the same, the test data tree described
in Figure 2 is attached to the live tree described in Figure 1::
root ('/')
|
testcase-data
|
test-child0 -> test-sibling1 -> test-sibling2 -> test-sibling3 -> null
| | | |
test-child01 null null null
Figure 2: Example test data tree to be attached to live tree.
According to the scenario above, the live tree is already present so it isn't
required to attach the root('/') node. All other nodes are attached by calling
of_attach_node() on each node.
In the function of_attach_node(), the new node is attached as the child of the
given parent in live tree. But, if parent already has a child then the new node
replaces the current child and turns it into its sibling. So, when the testcase
data node is attached to the live tree above (Figure 1), the final structure is
as shown in Figure 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: Live device tree structure after attaching the testcase-data.
Astute readers would have noticed that test-child0 node becomes the last
sibling compared to the earlier structure (Figure 2). After attaching first
test-child0 the test-sibling1 is attached that pushes the child node
(i.e. test-child0) to become a sibling and makes itself a child node,
as mentioned above.
If a duplicate node is found (i.e. if a node with same full_name property is
already present in the live tree), then the node isn't attached rather its
properties are updated to the live tree's node by calling the function
update_node_properties().
3.2. Removing the test data
---------------------------
Once the test case execution is complete, selftest_data_remove is called in
order to remove the device nodes attached initially (first the leaf nodes are
detached and then moving up the parent nodes are removed, and eventually the
whole tree). selftest_data_remove() calls detach_node_and_children() that uses
of_detach_node() to detach the nodes from the live device tree.
To detach a node, of_detach_node() either updates the child pointer of given
node's parent to its sibling or attaches the previous sibling to the given
node's sibling, as appropriate. That is it :)
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Open Firmware Devicetree Unittest
1-8이 문서는 Gaurav Minocha가 작성한 Open Firmware Devicetree 단위 테스트 설명서이며 `GPL-2.0` 라이선스가 적용됩니다.
테스트 목적과 사전 문서
9-25이 문서는 시스템 아키텍처와 무관하게 OF 단위 테스트 실행에 필요한 테스트 데이터를 실행 중인 트리에 동적으로 연결하는 방법을 설명합니다.
계속하기 전에 `Documentation/devicetree/usage-model.rst`와 `http://www.devicetree.org/Device_Tree_Usage`를 읽는 것이 좋습니다.
OF Selftest는 장치 드라이버 개발자가 unflattened Device Tree 자료 구조에서 장치 정보 등을 가져올 때 사용하는 `include/linux/of.h` 인터페이스를 검사하도록 설계되었습니다. 이 인터페이스는 다양한 용도의 대다수 장치 드라이버에서 사용됩니다.
EXPECT를 이용한 상세 출력 구분
26-46단위 테스트가 문제를 발견하면 콘솔에 경고나 오류 메시지를 출력합니다. 또한 의도적으로 잘못 만든 테스트 데이터 때문에 다른 커널 코드의 경고와 오류도 발생시킵니다. 이 때문에 출력된 메시지가 예상된 테스트 결과인지, 단위 테스트와 무관한 실제 문제인지 혼동할 수 있습니다.
단위 테스트는 경고나 오류가 예상됨을 알리기 위해 시작 메시지 `'EXPECT \ : text'`와 종료 메시지 `'EXPECT / : text'`를 사용합니다. 시작 메시지는 경고나 오류를 일으키기 전에, 종료 메시지는 일으킨 뒤에 출력합니다.
EXPECT 메시지는 콘솔 출력을 매우 장황하고 읽기 어렵게 만듭니다. `scripts/dtc/of_unittest_expect` 스크립트는 이 출력을 걸러 내고 실제로 발생한 경고·오류와 예상한 경고·오류 사이의 불일치를 강조합니다. 자세한 내용은 `scripts/dtc/of_unittest_expect --help`에서 확인할 수 있습니다.
DTS 테스트 데이터의 빌드와 링크
47-76Device Tree Source 파일 `drivers/of/unittest-data/testcases.dts`에는 `drivers/of/unittest.c`가 자동 실행하는 단위 테스트 데이터가 들어 있습니다. `testcases.dts`에 포함되는 Device Tree Source Include 파일은 `drivers/of/unittest-data/tests-*.dtsi`에서 확인할 수 있습니다.
커널을 `CONFIG_OF_UNITTEST`가 활성화된 상태로 빌드하면 다음 Make 규칙이 `testcases.dts`를 binary blob인 `testcases.dtb`, 즉 flattened DT로 컴파일합니다.
$(obj)/%.dtb: $(src)/%.dts FORCE
$(call if_changed_dep, dtc)
그 다음 아래 규칙은 이 binary blob을 어셈블리 파일 `testcases.dtb.S`로 감쌉니다.
$(obj)/%.dtb.S: $(obj)/%.dtb
$(call cmd, dt_S_dtb)
어셈블리 파일은 object 파일 `testcases.dtb.o`로 컴파일되고 커널 이미지에 링크됩니다.
Unflattened Device Tree 자료 구조
77-94unflattened Device Tree는 트리 형태로 연결된 `device_node`들로 구성됩니다. 트리를 만드는 데 사용하는 핵심 멤버는 `parent`, `child`, `sibling` 포인터입니다.
// following struct members are used to construct the tree
struct device_node {
...
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
...
};
일반적인 child·sibling 트리
95-126그림 1은 child와 sibling 포인터만 고려한 시스템의 일반적인 unflattened Device Tree 구조입니다. `parent` 포인터는 반대 방향 순회에 사용되며, 같은 레벨의 child와 모든 sibling은 공통 부모를 가리킵니다. 예를 들어 `child1`, `sibling2`, `sibling3`, `sibling4`의 부모는 모두 root입니다.
수평 연결은 sibling 순서를, 아래 연결은 각 노드의 첫 child를 나타냅니다. 모든 sibling은 해당 레벨의 공통 parent를 가리킵니다.
테스트 blob을 unflatten하고 live tree에 연결
127-143OF 단위 테스트를 실행하기 전에 테스트 데이터를 시스템의 Device Tree가 있으면 그 트리에 연결해야 합니다. `selftest_data_add()`는 먼저 커널 이미지에 링크된 flattened Device Tree 데이터를 다음 커널 심볼을 통해 읽습니다.
__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()`를 호출하여 flattened blob을 unflatten합니다. 시스템의 Device Tree, 즉 live tree가 있으면 unflatten한 테스트 데이터 트리를 live tree에 연결하고, 없으면 테스트 데이터 트리 자체를 live Device Tree로 설정합니다.
`attach_node_and_children()`은 `of_attach_node()`를 사용해 노드를 live tree에 연결합니다. 다음 그림 2의 테스트 데이터 트리를 앞서 본 그림 1의 live tree에 연결하는 과정으로 이를 설명합니다.
연결할 테스트 데이터 트리
144-154`testcase-data` 아래에서 `test-child0`이 첫 child이고 나머지는 sibling이며, `test-child01`은 `test-child0`의 child입니다.
of_attach_node() 적용 후의 live tree
155-200이 시나리오에서는 live tree가 이미 있으므로 root `/` 노드를 다시 연결할 필요가 없습니다. 나머지 각 노드에는 `of_attach_node()`를 호출합니다.
`of_attach_node()`는 새 노드를 지정된 부모의 child로 연결합니다. 부모에게 이미 child가 있으면 새 노드가 현재 child를 대체하고, 기존 child는 새 노드의 sibling이 됩니다. 따라서 `testcase-data`를 그림 1의 live tree에 연결하면 다음 그림 3과 같은 구조가 됩니다.
root의 새 첫 child는 `testcase-data`이고 기존 `child1`부터의 트리는 그 sibling으로 유지됩니다. 테스트 child를 차례로 앞에 삽입하므로 sibling 순서는 역순이 됩니다.
sibling 순서와 중복 노드 처리
201-212그림 2와 비교하면 그림 3에서는 `test-child0`가 마지막 sibling이 됩니다. 먼저 `test-child0`을 연결한 뒤 `test-sibling1`을 연결하면, 앞에서 설명한 규칙에 따라 기존 child인 `test-child0`가 sibling으로 밀리고 `test-sibling1`이 새 child가 됩니다. 이 과정이 반복되어 순서가 뒤집힙니다.
동일한 `full_name` 속성을 가진 노드가 live tree에 이미 있어 중복 노드가 발견되면 새 노드를 연결하지 않습니다. 대신 `update_node_properties()`를 호출하여 그 속성을 live tree의 기존 노드에 갱신합니다.
테스트 데이터 제거
213-224테스트 케이스 실행이 끝나면 처음 연결한 장치 노드를 제거하기 위해 `selftest_data_remove()`를 호출합니다. leaf 노드를 먼저 분리한 다음 부모 방향으로 올라가며 노드를 제거하여 결국 전체 테스트 트리를 없앱니다.
`selftest_data_remove()`는 `detach_node_and_children()`을 호출하고, 이 함수는 `of_detach_node()`를 사용해 live Device Tree에서 노드를 분리합니다.
노드를 분리할 때 `of_detach_node()`는 상황에 따라 지정된 노드 부모의 child 포인터를 그 노드의 sibling으로 갱신하거나, 이전 sibling을 지정된 노드의 sibling에 연결합니다.
요약과 해설
of_unittest.rst:1-224OF 단위 테스트 데이터를 커널 이미지에 빌드하고 unflatten한 뒤 live tree에 연결·제거하는 child/sibling 포인터 동작을 설명합니다. 영어 원문 전체와 한국어 전문 번역을 함께 제공하며 함수명, symbol, source path, 코드와 원문 줄 좌표를 보존하고 세 ASCII 트리를 구조화 도식으로 다시 그립니다.