KUnit:设备模拟&重定向

LPC中几个参考资料
How to introduce KUnit to physical device drivers?

Testing Drivers with KUnit Does Hardware have to be Hard?

设备模拟

有些驱动文件是需要device的,所以KUnit提供了一些设备模拟的方法,并且还提供了总线来管理设备的生命周期。
下面先以clock device模拟举例(drivers/clk/clk_test.c)

  1. 首先用一个struct来模拟这个clk设备。其中clk_hw是clk的描述,rate相当于模拟设备的波特率寄存器
struct clk_dummy_context { struct clk_hw hw; unsigned long rate; };
  1. 并且注册了一些用于这个fake设备的接口函数
static const struct clk_ops clk_dummy_rate_ops = { .recalc_rate = clk_dummy_recalc_rate, .determine_rate = clk_dummy_determine_rate, .set_rate = clk_dummy_set_rate, }; static unsigned long clk_dummy_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_dummy_context *ctx = container_of(hw, struct clk_dummy_context, hw); return ctx->rate; } static int clk_dummy_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { /* Just return the same rate without modifying it */ return 0; } static int clk_dummy_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_dummy_context *ctx = container_of(hw, struct clk_dummy_context, hw); ctx->rate = rate; return 0; }

到此为止一个设备就模拟完了,后续在测试中使用 clk_get_clk 就会调用到 clk_dummy_recalc_rate。从而进行后续的判断

/* * Test that the actual rate matches what is returned by clk_get_rate() */ static void clk_test_get_rate(struct kunit *test) { struct clk_dummy_context *ctx = test->priv; struct clk_hw *hw = &ctx->hw; struct clk *clk = clk_hw_get_clk(hw, NULL); unsigned long rate; rate = clk_get_rate(clk); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, rate, ctx->rate); clk_put(clk); }

为了更好的展示,我自己写了一个使用cache的测试用例(真正使用clk的时候不要开cache)。

static int clk_cached_test_init(struct kunit *test) { struct clk_dummy_context *ctx; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; test->priv = ctx; ctx->rate = DUMMY_CLOCK_INIT_RATE; //开启cache ctx->hw.init = CLK_HW_INIT_NO_PARENT("test-clk", &clk_dummy_rate_ops, CLK_SET_RATE_NO_REPARENT); ret = clk_hw_register(NULL, &ctx->hw); if (ret) return ret; return 0; } /* * Test cached rate */ static void clk_test_cached_get_rate(struct kunit *test) { struct clk_dummy_context *ctx = test->priv; struct clk_hw *hw = &ctx->hw; struct clk *clk = clk_hw_get_clk(hw, NULL); unsigned long rate; rate = clk_get_rate(clk); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_INIT_RATE);//这里正常得到最开始的初值 /* We change the rate behind the clock framework's back */ ctx->rate = DUMMY_CLOCK_RATE_1;//相当于物理设备rate寄存器的值改了 rate = clk_get_rate(clk); KUNIT_ASSERT_GT(test, rate, 0); KUNIT_EXPECT_EQ(test, rate, DUMMY_CLOCK_INIT_RATE);//由于有cache,可以发现读取的还是初值 clk_put(clk); } static struct kunit_case clk_cached_test_cases[] = { KUNIT_CASE(clk_test_cached_get_rate), {} }; /* * Test suite for a basic, uncached, rate clock, without any parent. * * These tests exercise the rate API with simple scenarios */ static struct kunit_suite clk_cached_test_suite = { .name = "clk-cached-test", .init = clk_cached_test_init, .exit = clk_test_exit, .test_cases = clk_cached_test_cases, };

几种注册设备的方法

sound/soc/soc-card-test.c:注册fake device

sound/pci/hda/cirrus_scodec_test.c:直接创建了一套假gpio。这源码值得读一读

其实还有很多用到mock device的方法,都很值得看一看。

重定向

使用方法

int real_func(int n) { KUNIT_STATIC_STUB_REDIRECT(real_func, n); return 0; } int replacement_func(int n) { return 42; } void example_test(struct kunit *test) { kunit_activate_static_stub(test, real_func, replacement_func); KUNIT_EXPECT_EQ(test, real_func(1), 42); }

示例sound/soc/codecs/cs-amp-lib-test.c

其实就是为了导出static函数使用,static函数只是在链接阶段看不见,并不是在kernel启动后也看不见,启动后这些函数都有自己的地址的,所以可以通过hook的方式得到这些static函数地址,然后通过 `kunit_activate_static_stub' 连接static函数和fake函数的地址。
1.

//测试文件 static void cs_amp_lib_test_cal_data_too_short_test(struct kunit *test) { struct cs_amp_lib_test_priv *priv = test->priv; struct cirrus_amp_cal_data result_data; int ret; /* Redirect calls to get EFI data */ //这对static函数和fake函数做了关联 kunit_activate_static_stub(test, cs_amp_test_hooks->get_efi_variable, cs_amp_lib_test_get_efi_variable_nohead); ret = cs_amp_get_efi_calibration_data(&priv->amp_pdev.dev, 0, 0, &result_data); KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW); kunit_deactivate_static_stub(test, cs_amp_test_hooks->get_efi_variable); }
//sound/soc/codecs/cs-amp-lib.c static const struct cs_amp_test_hooks cs_amp_test_hook_ptrs = { .get_efi_variable = cs_amp_get_efi_variable, .write_cal_coeff = cs_amp_write_cal_coeff, }; //hook导出 const struct cs_amp_test_hooks * const cs_amp_test_hooks = PTR_IF(IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST), &cs_amp_test_hook_ptrs); EXPORT_SYMBOL_NS_GPL(cs_amp_test_hooks, SND_SOC_CS_AMP_LIB);
//sound/soc/codecs/cs-amp-lib.c //在被重定向的函数中插桩子 static efi_status_t cs_amp_get_efi_variable(efi_char16_t *name, efi_guid_t *guid, unsigned long *size, void *buf) { u32 attr; //插桩 由于这个语句才可以在Kunit框架中重定向到1中指定的地址 KUNIT_STATIC_STUB_REDIRECT(cs_amp_get_efi_variable, name, guid, size, buf); //然后本函数后续的代码就不执行了 if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) return efi.get_variable(name, guid, &attr, size, buf); return EFI_NOT_FOUND; }

这样在后续调用到 cs_amp_get_efi_variable 函数的时候就会跳转到fake函数中执行。

重定向原理

总的来说一句话,就是在static函数中插了桩,然后在Kunit的test中先连接好目标的函数地址,这样在Kunit上下文中就可以跳转过去执行.

//首先看一下 kunit_active_static_stub的实现 void __kunit_activate_static_stub(struct kunit *test, void *real_fn_addr, void *replacement_addr) { struct kunit_static_stub_ctx *ctx; struct kunit_resource *res; KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL, "Tried to activate a stub for function NULL"); /* If the replacement address is NULL, deactivate the stub. */ if (!replacement_addr) { kunit_deactivate_static_stub(test, replacement_addr); return; } /* Look up any existing stubs for this function, and replace them. */ res = kunit_find_resource(test, __kunit_static_stub_resource_match, real_fn_addr); // 将real_fn_addr 和 replacement_addr 做关联,放到上下文的resource中 if (res) { ctx = res->data; ctx->replacement_addr = replacement_addr; /* We got an extra reference from find_resource(), so put it. */ kunit_put_resource(res); } else { ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); ctx->real_fn_addr = real_fn_addr; ctx->replacement_addr = replacement_addr; res = kunit_alloc_resource(test, NULL, &__kunit_static_stub_resource_free, GFP_KERNEL, ctx); } } EXPORT_SYMBOL_GPL(__kunit_activate_static_stub);

下面再来看一下 KUNIT_STATIC_STUB_REDIRECT 这个宏,可以看到就是一个简单的获取到 replacement_addr地址然后直接跳过去。

#define KUNIT_STATIC_STUB_REDIRECT(real_fn_name, args...) \ do { \ typeof(&real_fn_name) replacement; \ struct kunit *current_test = kunit_get_current_test(); \ \ if (likely(!current_test)) \ break; \ \ replacement = kunit_hooks.get_static_stub_address(current_test, \ &real_fn_name); \ \ if (unlikely(replacement)) \ return replacement(args); \ } while (0)

__EOF__

本文作者alan
本文链接https://www.cnblogs.com/alanli07/p/18399767.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   LIalan  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示