xref: /openbmc/linux/lib/kunit/static_stub.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
1*e047c5eaSDavid Gow // SPDX-License-Identifier: GPL-2.0
2*e047c5eaSDavid Gow /*
3*e047c5eaSDavid Gow  * KUnit function redirection (static stubbing) API.
4*e047c5eaSDavid Gow  *
5*e047c5eaSDavid Gow  * Copyright (C) 2022, Google LLC.
6*e047c5eaSDavid Gow  * Author: David Gow <davidgow@google.com>
7*e047c5eaSDavid Gow  */
8*e047c5eaSDavid Gow 
9*e047c5eaSDavid Gow #include <kunit/test.h>
10*e047c5eaSDavid Gow #include <kunit/static_stub.h>
11*e047c5eaSDavid Gow #include "hooks-impl.h"
12*e047c5eaSDavid Gow 
13*e047c5eaSDavid Gow 
14*e047c5eaSDavid Gow /* Context for a static stub. This is stored in the resource data. */
15*e047c5eaSDavid Gow struct kunit_static_stub_ctx {
16*e047c5eaSDavid Gow 	void *real_fn_addr;
17*e047c5eaSDavid Gow 	void *replacement_addr;
18*e047c5eaSDavid Gow };
19*e047c5eaSDavid Gow 
__kunit_static_stub_resource_free(struct kunit_resource * res)20*e047c5eaSDavid Gow static void __kunit_static_stub_resource_free(struct kunit_resource *res)
21*e047c5eaSDavid Gow {
22*e047c5eaSDavid Gow 	kfree(res->data);
23*e047c5eaSDavid Gow }
24*e047c5eaSDavid Gow 
25*e047c5eaSDavid Gow /* Matching function for kunit_find_resource(). match_data is real_fn_addr. */
__kunit_static_stub_resource_match(struct kunit * test,struct kunit_resource * res,void * match_real_fn_addr)26*e047c5eaSDavid Gow static bool __kunit_static_stub_resource_match(struct kunit *test,
27*e047c5eaSDavid Gow 						struct kunit_resource *res,
28*e047c5eaSDavid Gow 						void *match_real_fn_addr)
29*e047c5eaSDavid Gow {
30*e047c5eaSDavid Gow 	/* This pointer is only valid if res is a static stub resource. */
31*e047c5eaSDavid Gow 	struct kunit_static_stub_ctx *ctx = res->data;
32*e047c5eaSDavid Gow 
33*e047c5eaSDavid Gow 	/* Make sure the resource is a static stub resource. */
34*e047c5eaSDavid Gow 	if (res->free != &__kunit_static_stub_resource_free)
35*e047c5eaSDavid Gow 		return false;
36*e047c5eaSDavid Gow 
37*e047c5eaSDavid Gow 	return ctx->real_fn_addr == match_real_fn_addr;
38*e047c5eaSDavid Gow }
39*e047c5eaSDavid Gow 
40*e047c5eaSDavid Gow /* Hook to return the address of the replacement function. */
__kunit_get_static_stub_address_impl(struct kunit * test,void * real_fn_addr)41*e047c5eaSDavid Gow void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr)
42*e047c5eaSDavid Gow {
43*e047c5eaSDavid Gow 	struct kunit_resource *res;
44*e047c5eaSDavid Gow 	struct kunit_static_stub_ctx *ctx;
45*e047c5eaSDavid Gow 	void *replacement_addr;
46*e047c5eaSDavid Gow 
47*e047c5eaSDavid Gow 	res = kunit_find_resource(test,
48*e047c5eaSDavid Gow 				  __kunit_static_stub_resource_match,
49*e047c5eaSDavid Gow 				  real_fn_addr);
50*e047c5eaSDavid Gow 
51*e047c5eaSDavid Gow 	if (!res)
52*e047c5eaSDavid Gow 		return NULL;
53*e047c5eaSDavid Gow 
54*e047c5eaSDavid Gow 	ctx = res->data;
55*e047c5eaSDavid Gow 	replacement_addr = ctx->replacement_addr;
56*e047c5eaSDavid Gow 	kunit_put_resource(res);
57*e047c5eaSDavid Gow 	return replacement_addr;
58*e047c5eaSDavid Gow }
59*e047c5eaSDavid Gow 
kunit_deactivate_static_stub(struct kunit * test,void * real_fn_addr)60*e047c5eaSDavid Gow void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr)
61*e047c5eaSDavid Gow {
62*e047c5eaSDavid Gow 	struct kunit_resource *res;
63*e047c5eaSDavid Gow 
64*e047c5eaSDavid Gow 	KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
65*e047c5eaSDavid Gow 				"Tried to deactivate a NULL stub.");
66*e047c5eaSDavid Gow 
67*e047c5eaSDavid Gow 	/* Look up the existing stub for this function. */
68*e047c5eaSDavid Gow 	res = kunit_find_resource(test,
69*e047c5eaSDavid Gow 				  __kunit_static_stub_resource_match,
70*e047c5eaSDavid Gow 				  real_fn_addr);
71*e047c5eaSDavid Gow 
72*e047c5eaSDavid Gow 	/* Error out if the stub doesn't exist. */
73*e047c5eaSDavid Gow 	KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL,
74*e047c5eaSDavid Gow 				"Tried to deactivate a nonexistent stub.");
75*e047c5eaSDavid Gow 
76*e047c5eaSDavid Gow 	/* Free the stub. We 'put' twice, as we got a reference
77*e047c5eaSDavid Gow 	 * from kunit_find_resource()
78*e047c5eaSDavid Gow 	 */
79*e047c5eaSDavid Gow 	kunit_remove_resource(test, res);
80*e047c5eaSDavid Gow 	kunit_put_resource(res);
81*e047c5eaSDavid Gow }
82*e047c5eaSDavid Gow EXPORT_SYMBOL_GPL(kunit_deactivate_static_stub);
83*e047c5eaSDavid Gow 
84*e047c5eaSDavid Gow /* Helper function for kunit_activate_static_stub(). The macro does
85*e047c5eaSDavid Gow  * typechecking, so use it instead.
86*e047c5eaSDavid Gow  */
__kunit_activate_static_stub(struct kunit * test,void * real_fn_addr,void * replacement_addr)87*e047c5eaSDavid Gow void __kunit_activate_static_stub(struct kunit *test,
88*e047c5eaSDavid Gow 				  void *real_fn_addr,
89*e047c5eaSDavid Gow 				  void *replacement_addr)
90*e047c5eaSDavid Gow {
91*e047c5eaSDavid Gow 	struct kunit_static_stub_ctx *ctx;
92*e047c5eaSDavid Gow 	struct kunit_resource *res;
93*e047c5eaSDavid Gow 
94*e047c5eaSDavid Gow 	KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
95*e047c5eaSDavid Gow 				"Tried to activate a stub for function NULL");
96*e047c5eaSDavid Gow 
97*e047c5eaSDavid Gow 	/* If the replacement address is NULL, deactivate the stub. */
98*e047c5eaSDavid Gow 	if (!replacement_addr) {
99*e047c5eaSDavid Gow 		kunit_deactivate_static_stub(test, replacement_addr);
100*e047c5eaSDavid Gow 		return;
101*e047c5eaSDavid Gow 	}
102*e047c5eaSDavid Gow 
103*e047c5eaSDavid Gow 	/* Look up any existing stubs for this function, and replace them. */
104*e047c5eaSDavid Gow 	res = kunit_find_resource(test,
105*e047c5eaSDavid Gow 				  __kunit_static_stub_resource_match,
106*e047c5eaSDavid Gow 				  real_fn_addr);
107*e047c5eaSDavid Gow 	if (res) {
108*e047c5eaSDavid Gow 		ctx = res->data;
109*e047c5eaSDavid Gow 		ctx->replacement_addr = replacement_addr;
110*e047c5eaSDavid Gow 
111*e047c5eaSDavid Gow 		/* We got an extra reference from find_resource(), so put it. */
112*e047c5eaSDavid Gow 		kunit_put_resource(res);
113*e047c5eaSDavid Gow 	} else {
114*e047c5eaSDavid Gow 		ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
115*e047c5eaSDavid Gow 		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
116*e047c5eaSDavid Gow 		ctx->real_fn_addr = real_fn_addr;
117*e047c5eaSDavid Gow 		ctx->replacement_addr = replacement_addr;
118*e047c5eaSDavid Gow 		res = kunit_alloc_resource(test, NULL,
119*e047c5eaSDavid Gow 				     &__kunit_static_stub_resource_free,
120*e047c5eaSDavid Gow 				     GFP_KERNEL, ctx);
121*e047c5eaSDavid Gow 	}
122*e047c5eaSDavid Gow }
123*e047c5eaSDavid Gow EXPORT_SYMBOL_GPL(__kunit_activate_static_stub);
124