拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux KUnit 设备树(OF)API:用测试托管的 Overlay 与节点机制测试 of_* 依赖代码

Linux KUnit 设备树OFAPI用测试托管的 Overlay 与节点机制测试 of_* 依赖代码【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文围绕 Linux 内核 KUnit 框架的 Device Tree (OF) API 展开完整解析 Documentation/dev-tools/kunit/api/of.rst 所定义的接口体系如何在内核单元测试中动态向设备树注入 overlay、如何在测试结束后自动清理资源以及如何把.dtsooverlay 文件编译进内核镜像供测试直接加载。读完后你将掌握编写依赖of_*设备树接口的驱动单元测试的完整方法包括 Makefile 组织方式、Kconfig 依赖和真实测试用例drivers/of/overlay_test.c的逐项解读。API 定位KUnit 设备树 API 解决什么问题KUnit 是内核自带的轻量级单元测试框架。对于依赖设备树of_*系列接口如of_find_node_by_name()、of_property_read_string()的驱动代码测试的最大障碍是运行环境中往往不存在被测驱动对应的设备树节点。KUnit OF API 正是为此设计的测试托管test managed接口层其价值可以用一句话概括资源由测试用例统一托管——测试用例结束无论成功还是失败时overlay 自动移除、设备树节点自动释放开发者不再手动配对调用of_node_put()与of_overlay_remove()。文档 Documentation/dev-tools/kunit/api/of.rst 本身只有两行kernel-doc指令实际 API 文档由内核文档系统从两个源文件自动抽取生成.. kernel-doc:: include/kunit/of.h :internal: .. kernel-doc:: drivers/of/of_kunit_helpers.c :export::internal:表示只抽取 include/kunit/of.h 中的内联static inline函数:export:表示同时抽取 drivers/of/of_kunit_helpers.c 中EXPORT_SYMBOL_GPL导出的符号。该文档位于 KUnit API 参考的 api/index.rst 中Driver KUnit API分类下与 clk API、platform device API 并列。三个核心接口逐个解析of_node_put_kunit()测试托管的设备树节点引用释放struct device_node的引用计数遵循of_node_get()/of_node_put()配对原则。KUnit 的托管版本声明在 include/kunit/of.h#ifdef CONFIG_OF void of_node_put_kunit(struct kunit *test, struct device_node *node); #else static inline void of_node_put_kunit(struct kunit *test, struct device_node *node) { kunit_skip(test, requires CONFIG_OF); } #endif /* !CONFIG_OF */注意这里的优雅降级设计当内核未启用CONFIG_OF时该函数不会被跳过整个测试文件而是在具体调用处执行kunit_skip()使测试以跳过状态退出而非编译失败。其实现位于 drivers/of/of_kunit_helpers.cKUNIT_DEFINE_ACTION_WRAPPER(of_node_put_wrapper, of_node_put, struct device_node *); void of_node_put_kunit(struct kunit *test, struct device_node *node) { if (kunit_add_action(test, of_node_put_wrapper, node)) { KUNIT_FAIL(test, Cant allocate a kunit resource to put of_node\n); } }从源码结构看机制是典型的 KUnit 资源动作模式KUNIT_DEFINE_ACTION_WRAPPER宏生成一个包装of_node_put()的回调of_node_put_wrapper随后kunit_add_action()将该回调注册到测试的资源列表。KUnit 框架保证在每个测试用例收尾时逆序执行所有已注册动作因此即使测试中途断言失败KUNIT_ASSERT_*触发提前返回节点的put也必然发生避免引用计数泄漏。若注册动作时内存分配失败则直接KUNIT_FAIL()让整个用例失败——这比静默泄漏更符合测试语义。of_overlay_fdt_apply_kunit()应用原始 FDT 格式的 overlayinclude/kunit/of.h#L25-L37 声明了基于原始 FDT 字节流应用 overlay 的接口#if defined(CONFIG_OF) defined(CONFIG_OF_OVERLAY) defined(CONFIG_OF_EARLY_FLATTREE) int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt, u32 overlay_fdt_size, int *ovcs_id); #else static inline int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt, u32 overlay_fdt_size, int *ovcs_id) { kunit_skip(test, requires CONFIG_OF and CONFIG_OF_OVERLAY and CONFIG_OF_EARLY_FLATTREE for root node); return -EINVAL; } #endif返回值语义与普通of_overlay_fdt_apply()一致成功返回 0失败返回负值 errno。未满足编译条件时同样退化为kunit_skip()。实现见 drivers/of/of_kunit_helpers.c#L47-L68static void of_overlay_fdt_apply_kunit_exit(void *ovcs_id) { of_overlay_remove(ovcs_id); } int of_overlay_fdt_apply_kunit(struct kunit *test, void *overlay_fdt, u32 overlay_fdt_size, int *ovcs_id) { int ret; int *copy_id; of_root_kunit_skip(test); copy_id kunit_kmalloc(test, sizeof(*copy_id), GFP_KERNEL); if (!copy_id) return -ENOMEM; ret of_overlay_fdt_apply(overlay_fdt, overlay_fdt_size, ovcs_id, NULL); if (ret) return ret; *copy_id *ovcs_id; return kunit_add_action_or_reset(test, of_overlay_fdt_apply_kunit_exit, copy_id); }这条调用链有三个值得注意的细节of_root_kunit_skip(test)前置检查drivers/of/of_kunit_helpers.c#L19-L24在 arm64/riscv 平台启用CONFIG_ACPI且of_root未填充时跳过测试。因为 overlay 必须以 root 节点为锚点解析没有 root node 时无法应用 overlayvoid of_root_kunit_skip(struct kunit *test) { if ((IS_ENABLED(CONFIG_ARM64) || IS_ENABLED(CONFIG_RISCV)) IS_ENABLED(CONFIG_ACPI) !of_root) kunit_skip(test, arm64/riscvacpi doesnt populate a root node); }ovcs_id的拷贝overlay 控制器状态 id 先通过kunit_kmalloc()分配一个由测试托管的堆空间copy_id再把*ovcs_id拷贝进去。这样即使调用者传入的是栈上的ovcs_id测试结束时动作回调读取到的也是有效的副本。kunit_add_action_or_reset()注册退出动作of_overlay_fdt_apply_kunit_exit即调用of_overlay_remove()。使用_or_reset变体意味着若注册失败框架会先把该用例状态置为失败再走正常退出路径杜绝overlay 已应用但无人清理的悬挂状态。of_overlay_apply_kunit() 宏加载编译进内核的内置 overlay大多数单元测试场景下overlay 不需要运行时从外部获取而是把.dtso文件直接编译成内核或测试模块的一部分再用宏按名字加载。include/kunit/of.h#L54-L119 给出了完整的宏体系static inline int __of_overlay_apply_kunit(struct kunit *test, u8 *overlay_begin, const u8 *overlay_end) { int unused; return of_overlay_fdt_apply_kunit(test, overlay_begin, overlay_end - overlay_end, /* 实际为 overlay_end - overlay_begin */ unused); } #define of_overlay_begin(overlay_name) __dtbo_##overlay_name##_begin #define of_overlay_end(overlay_name) __dtbo_##overlay_name##_end #define OF_OVERLAY_DECLARE(overlay_name) \ extern uint8_t of_overlay_begin(overlay_name)[]; \ extern uint8_t of_overlay_end(overlay_name)[] #define of_overlay_apply_kunit(test, overlay_name) \ ({ \ OF_OVERLAY_DECLARE(overlay_name); \ \ __of_overlay_apply_kunit((test), \ of_overlay_begin(overlay_name), \ of_overlay_end(overlay_name)); \ })各组件职责组件作用OF_OVERLAY_DECLARE(name)声明 overlay 对象在链接器中的起止符号__dtbo_name_begin/__dtbo_name_end使测试代码不必#include生成的.S文件__of_overlay_apply_kunit()内部 API用指针差overlay_end - overlay_begin计算 overlay 大小转发给of_overlay_fdt_apply_kunit()文档注释标注mostly internal APIof_overlay_apply_kunit(test, name)对外主入口测试代码唯一需要调用的宏。ovcs_id用局部unused承接清理完全交给框架头文件注释明确了 overlay 的来源约束它必须是通过构建系统中cmd_dt_S_dtbo规则文档注释引自 include/kunit/of.h#L77-L80从.dtso编译出来的产物compiled into the kernel image or KUnit test module。在当前源码树中该构建链定义于 scripts/Makefile.dtbs其推导链为%.dtbo.o - %.dtbo.S - %.dtbo - %.dtso即 DTC 先将.dtso汇编为.dtbo再转成.S汇编源其中定义__dtbo_name_begin/end符号最终链接成.dtbo.o参与模块或 vmlinux 链接。实操一个内置 overlay 单元测试的完整组织方式KUnit 官方文档include/kunit/of.h#L88-L108 的 kernel-doc 注释给出的标准组织方式是Makefile 测试代码两件套第一步在 Makefile 中把 overlay 与测试源一起编译文档示例obj-$(CONFIG_OF_OVERLAY_KUNIT_TEST) overlay_test.o kunit_overlay_test.dtbo.o内核树中真实采用的形式在 drivers/of/Makefile#L22-L25通过modname-y语法把 overlay 并入模块obj-$(CONFIG_KUNIT) of_kunit_helpers.o obj-$(CONFIG_OF_KUNIT_TEST) of_test.o obj-$(CONFIG_OF_OVERLAY_KUNIT_TEST) overlay-test.o overlay-test-y : overlay_test.o kunit_overlay_test.dtbo.o第二步准备.dtsooverlay 文件drivers/of/kunit_overlay_test.dtso 全部内容如下展示了最小 overlay 的形态——/plugin/;声明这是可热插拔的 plugin 节点{/}锚定到 root 节点/dts-v1/; /plugin/; {/} { kunit-test { compatible test,empty; }; };第三步在测试代码中按名字加载并断言drivers/of/overlay_test.c 是这套 API 的参考测试套件包含三个用例/* Test that of_overlay_apply_kunit() adds a node to the live tree */ static void of_overlay_apply_kunit_apply(struct kunit *test) { struct device_node *np; KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, kunit_overlay_test)); np of_find_node_by_name(NULL, kunit_node_name); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, np); of_node_put(np); }这里of_overlay_apply_kunit(test, kunit_overlay_test)中的kunit_overlay_test即.dtso文件名去掉扩展名与 Makefile 里的kunit_overlay_test.dtbo.o对应。宏展开后先extern声明__dtbo_kunit_overlay_test_begin/end符号再计算大小并调用托管版本。第二个用例验证 overlay 节点能进一步创建 platform devicedrivers/of/overlay_test.c#L36-L52static void of_overlay_apply_kunit_platform_device(struct kunit *test) { struct platform_device *pdev; struct device_node *np; KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, kunit_overlay_test)); np of_find_node_by_name(NULL, kunit_node_name); of_node_put_kunit(test, np); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, np); pdev of_find_device_by_node(np); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, pdev); if (pdev) put_device(pdev-dev); }注意np使用了of_node_put_kunit()而非手动of_node_put()——这正是托管接口的典型用法断言失败提前返回时也不会泄漏引用。第三个用例专门验证测试结束后自动清理这一核心承诺drivers/of/overlay_test.c#L59-L99它构造了一个独立的struct kunit fake测试上下文在其中应用 overlay、找到节点和 platform device然后主动调用kunit_cleanup(fake)触发资源释放随后断言of_find_node_by_name()返回 NULL、bus_find_device()在 platform 总线上按compatible test,empty也找不到设备。该用例开头还显式检查CONFIG_OF_OVERLAY与CONFIG_OF_EARLY_FLATTREE并调用of_root_kunit_skip()展示了在更细粒度场景下的手动跳过写法of_root_kunit_skip(test); if (!IS_ENABLED(CONFIG_OF_OVERLAY)) kunit_skip(test, requires CONFIG_OF_OVERLAY to apply overlay); if (!IS_ENABLED(CONFIG_OF_EARLY_FLATTREE)) kunit_skip(test, requires CONFIG_OF_EARLY_FLATTREE for root node);三个用例通过标准 KUnit 套件机制注册drivers/of/overlay_test.c#L101-L118static struct kunit_case of_overlay_apply_kunit_test_cases[] { KUNIT_CASE(of_overlay_apply_kunit_apply), KUNIT_CASE(of_overlay_apply_kunit_platform_device), KUNIT_CASE(of_overlay_apply_kunit_cleanup), {} }; static struct kunit_suite of_overlay_apply_kunit_suite { .name of_overlay_apply_kunit, .test_cases of_overlay_apply_kunit_test_cases, }; kunit_test_suites( of_overlay_apply_kunit_suite, );编译依赖Kconfig 选项与 kunitconfigoverlay 测试的开关在 drivers/of/Kconfig#L116-L124config OF_OVERLAY_KUNIT_TEST tristate Device Tree overlay KUnit tests if !KUNIT_ALL_TESTS depends on KUNIT default KUNIT_ALL_TESTS select DTC help This option builds KUnit unit tests for the device tree overlay code. If unsure, say N here, but this option is safe to enable.关键依赖depends on KUNIT且select DTC——因为.dtso需要 DTCDevice Tree Compiler在构建期编译。而 overlay 运行时的完整能力来自三个配置的组合CONFIG_OF设备树基础设施、CONFIG_OF_OVERLAYoverlay 子系统drivers/of/Makefile#L13 中对应overlay.o和CONFIG_OF_EARLY_FLATTREE提供of_root作为 overlay 应用锚点。三者任一缺失对应 API 都会以kunit_skip()优雅跳过而非报错这是整个 OF API 的设计基调。仓库还为该目录提供了独立的 kunitconfig 预设 drivers/of/.kunitconfig用kunittool等工具可以只跑 OF 相关测试CONFIG_KUNITy CONFIG_OFy CONFIG_OF_KUNIT_TESTy CONFIG_OF_OVERLAYy CONFIG_OF_OVERLAY_KUNIT_TESTy在自己的模块中复用这套 API完整模式综合文档与参考实现在一个自己的驱动目录下为of_*依赖代码写 KUnit 测试最小可行模板如下示意代码路径为示意实际放入你的驱动目录# Makefile obj-$(CONFIG_MYDRIVER_KUNIT_TEST) mydriver-test.o mydriver-test-y : mydriver_test.o mydriver_overlay.dtbo.o// mydriver_test.c #include kunit/of.h #include kunit/test.h static void apply_overlay_case(struct kunit *test) { struct device_node *np; /* 0 表示成功测试结束时 overlay 被框架自动移除 */ KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, mydriver_overlay)); np of_find_node_by_name(NULL, my-driver-test-node); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, np); of_node_put_kunit(test, np); /* 测试结束自动 put */ } static struct kunit_case mydriver_cases[] { KUNIT_CASE(apply_overlay_case), {} }; static struct kunit_suite mydriver_suite { .name mydriver_of_test, .test_cases mydriver_cases, }; kunit_test_suites(mydriver_suite); MODULE_LICENSE(GPL);// mydriver_overlay.dtso /dts-v1/; /plugin/; {/} { my-driver-test-node { compatible vendor,test-device; reg 0x4000 0x100; /* 被测驱动需要读取的其他属性 */ }; };使用约束汇总overlay 文件名不含扩展名必须与of_overlay_apply_kunit()的第二个参数一致Makefile 中.dtbo.o必须以modname-y或obj-$(CONFIG_...)形式与测试目标一起编译若 overlay 需要reg等属性注意of_address_*解析依赖CONFIG_OF_ADDRESS测试在 arm64/riscv ACPI 且无of_root的环境中会自动跳过这是预期行为而非缺陷测试失败不影响 overlay 清理——这正是kunit_add_action_or_reset()与资源列表存在的原因。小结KUnit OF API 的设计可以用三个词概括托管资源生命周期绑定测试用例、降级编译条件缺失时 skip 而非 fail、内置overlay 直接编进镜像无外部文件依赖。接口面很小——include/kunit/of.h 对外只有of_node_put_kunit()、of_overlay_fdt_apply_kunit()和of_overlay_apply_kunit()三个入口——但覆盖了设备树驱动测试的两个核心痛点节点引用计数管理drivers/of/of_kunit_helpers.c#L73-L90与运行时设备树注入drivers/of/of_kunit_helpers.c#L47-L69。结合 scripts/Makefile.dtbs 的.dtso → .dtbo.o编译链和 drivers/of/overlay_test.c 中的三个参考用例即可在自己的模块中复制这套经过内核自测验证的测试模式。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门