| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit function redirection (static stubbing) API. |
| * |
| * Copyright (C) 2022, Google LLC. |
| * Author: David Gow <davidgow@google.com> |
| */ |
| |
| #include <kunit/test.h> |
| #include <kunit/static_stub.h> |
| #include "hooks-impl.h" |
| |
| |
| /* Context for a static stub. This is stored in the resource data. */ |
| struct kunit_static_stub_ctx { |
| void *real_fn_addr; |
| void *replacement_addr; |
| }; |
| |
| static void __kunit_static_stub_resource_free(struct kunit_resource *res) |
| { |
| kfree(res->data); |
| } |
| |
| /* Matching function for kunit_find_resource(). match_data is real_fn_addr. */ |
| static bool __kunit_static_stub_resource_match(struct kunit *test, |
| struct kunit_resource *res, |
| void *match_real_fn_addr) |
| { |
| /* This pointer is only valid if res is a static stub resource. */ |
| struct kunit_static_stub_ctx *ctx = res->data; |
| |
| /* Make sure the resource is a static stub resource. */ |
| if (res->free != &__kunit_static_stub_resource_free) |
| return false; |
| |
| return ctx->real_fn_addr == match_real_fn_addr; |
| } |
| |
| /* Hook to return the address of the replacement function. */ |
| void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr) |
| { |
| struct kunit_resource *res; |
| struct kunit_static_stub_ctx *ctx; |
| void *replacement_addr; |
| |
| res = kunit_find_resource(test, |
| __kunit_static_stub_resource_match, |
| real_fn_addr); |
| |
| if (!res) |
| return NULL; |
| |
| ctx = res->data; |
| replacement_addr = ctx->replacement_addr; |
| kunit_put_resource(res); |
| return replacement_addr; |
| } |
| |
| void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr) |
| { |
| struct kunit_resource *res; |
| |
| KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL, |
| "Tried to deactivate a NULL stub."); |
| |
| /* Look up the existing stub for this function. */ |
| res = kunit_find_resource(test, |
| __kunit_static_stub_resource_match, |
| real_fn_addr); |
| |
| /* Error out if the stub doesn't exist. */ |
| KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL, |
| "Tried to deactivate a nonexistent stub."); |
| |
| /* Free the stub. We 'put' twice, as we got a reference |
| * from kunit_find_resource() |
| */ |
| kunit_remove_resource(test, res); |
| kunit_put_resource(res); |
| } |
| EXPORT_SYMBOL_GPL(kunit_deactivate_static_stub); |
| |
| /* Helper function for kunit_activate_static_stub(). The macro does |
| * typechecking, so use it instead. |
| */ |
| 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); |
| 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); |