首页 > 其他分享 >KUnit:设备模拟&重定向

KUnit:设备模拟&重定向

时间:2024-09-06 10:46:51浏览次数:12  
标签:重定向 clk ctx KUnit rate static test 模拟 struct

设备模拟

有些驱动文件是需要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的方法,都很值得看一看。

重定向

示例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

相关文章

  • KUnit
    基本介绍KUnitSuiteMemory每一个kUnit都有自己的上下文,所以一个崩溃了,不会影响到其他的suit。开关目前可以有下面几种方法控制case测试。menuconfig控制使用.kunitconfig控制编译的测试文件使用宏选择性开启某些case使用debugfskunit_get_current_test总开关CONFIG_......
  • 【自由能系列(中级),代码模拟】预测编码的核心:三个关键方程式的详解
    预测编码的核心:三个关键方程式的详解——探索预测编码背后的数学原理与应用核心结论:预测编码是一种基于贝叶斯定理的理论框架,它通过三个关键方程式描述了大脑如何处理和解释来自环境的信号。这些方程式分别建立了贝叶斯定理的简化形式、生成模型以及观察者模型,共同揭示了......
  • LeetCode 3174. 清除数字(字符串、模拟)
    题目:3174.清除数字思路:用字符串t模拟操作要求,当x是数字时,删除t的最后一个字符。不是的话,直接插入xclassSolution{public:stringclearDigits(strings){stringt="";for(autox:s){if('0'<=x&&x<='9'){......
  • 基于SA-BP模拟退火算法优化BP神经网络实现数据预测Python实现
        在数据分析和机器学习领域,时间序列预测和多输入单输出系统的预测是重要且复杂的问题。传统的BP(反向传播)神经网络虽然具有强大的非线性函数逼近能力,但在处理这些问题时容易陷入局部极小值、训练速度慢以及过拟合等问题。为了克服这些不足,我们引入了SA-BP(模拟退火算法......
  • 9.5 上午 becoder 模拟赛总结 & 题解
    T1文本编辑器说实话,看到题目的第一瞬间,我还以为gm第一道就放了平衡树。一道链表的模板题,当然愿意也可以用平衡树写,不多说了,直接放代码(100pts):#defineN1000005chars[N],t[N];intnow,pre[N],nxt[N];intmain(){scanf("%s%s",s+1,t+1);intn=strlen(s+1);......
  • 3293. 风险人群筛查 来源:第二十次CCF-CSP计算机软件能力认证 模拟枚举
    #include<iostream>#include<cstring>#include<algorithm>#definexfirst#defineysecondusingnamespacestd;intn,k,t,x1,y1,x2,y2;intmain(){cin>>n>>k>>t>>x1>>y1>>x2......
  • 2024.8.10模拟赛17
    模拟赛今天是七夕耶!哦,今天是七夕呀。。。T1Non-decreasing题目背景先拿部分分,当全正或全负时很显然,只需要\(n\)次操作:正:如果\(a_i\gta_{i+1},a_{i+1}\gets(a_i+a_{i+1})\)。负:如果\(a_i\lta_{i-1},a_{i-1}\gets(a_i+a_{i-1})\)。然后开始想有正有负的情......
  • 2024.8.7 模拟赛 15
    模拟赛。。。T1绿绿和串串学习manacher。先说求回文串,manacher算法,每次记录向右能延伸最长的回文串和回文中心。这样对于新扩展的字符,按已有的回文中心对称过去,会得到一个已经求出的回文长度,在这个基础上向两端扩展就好了。对于普通的回文串,有奇回文和偶回文两种,为了方便......
  • 2024.8.8模拟赛16
    模拟赛重拾题解(刚刚写过一版忘保存了)T1其实就是个最长公共子序列的变形。把一样的数才匹配换成有倍数关系就匹配。最长公共子序列:一般转化为最长上升子序列,即在一个串中的数\(a\),找到它在另一个串中的位置\(j\),从\(1\dotsj-1\)转移即可,取最大值可用树状数组维护前缀最......
  • Day85 APP攻防-资产收集篇&反证书检验&XP框架&反代理VPN&数据转发&反模拟器
    知识点1、APP资产-抓包突破&反模拟器2、APP资产-抓包突破&反证书检验3、APP资产-抓包突破&反代理VPN没有限制过滤的抓包问题:1、抓不到-工具证书没配置好2、抓不到-app走的不是http/s不走http/s协议的数据包抓不到可以用封包抓有限制过滤的抓包问题:3、抓不到-......