设备模拟
有些驱动文件是需要device的,所以KUnit提供了一些设备模拟的方法,并且还提供了总线来管理设备的生命周期。
下面先以clock device模拟举例(drivers/clk/clk_test.c)
- 首先用一个struct来模拟这个clk设备。其中
clk_hw
是clk的描述,rate
相当于模拟设备的波特率寄存器
struct clk_dummy_context {
struct clk_hw hw;
unsigned long rate;
};
- 并且注册了一些用于这个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的方法,都很值得看一看。
重定向
示例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)
标签:重定向,clk,ctx,KUnit,rate,static,test,模拟,struct
From: https://www.cnblogs.com/alanli07/p/18399767