英語

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() 要麼將給定節點父節點的子指標更新為其同級節點,要麼將先前的同級節點附加到給定節點的同級節點,視情況而定。 就是這樣 :)