From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 620C23CF696; Mon, 27 Apr 2026 13:59:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777298377; cv=none; b=egN1vVjcvK+jH1jV7Zc7yaWxL+eNcyoQc+PY+MNmcmWc8b3ZjQmQufU/HxUegZnWpPDEg6IWQQKcjE20AuFliD/MROsk7uROEjed95xz8R1JdyedXxa1Lhg6MKFsQtb+5dMjg+mde6/gAJVAZrsnbtHV/lE5s8i5d++ky+D0hZk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777298377; c=relaxed/simple; bh=OUP8eamDpx1YV8sVjT9efSUhKDq46Hm3PUn8Yl3OXnk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fu+qgpK+IL6EcDrY4Wp+2qozf0lL+M7B1Dac8S4hO/M83V9ya44U5AHfksMSu8jaIrnuu7FV3c66BdLVbH2MJkn7tGRaBJSz7wYmKUdL7KUS8UOvKvp5ZRbVYz4kDANg1/SSRNTHbs+LbvvAcPKEWUtNvVoOzCM1w9tEAJGhXVQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=WkZ44c4S; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="WkZ44c4S" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7EE95C2BCB7; Mon, 27 Apr 2026 13:59:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777298377; bh=OUP8eamDpx1YV8sVjT9efSUhKDq46Hm3PUn8Yl3OXnk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WkZ44c4S0BgE0gSz/qInB9oWvUV9zgrBE72to2ACthPbGgNj7HwnQZk/gxq+vSuEB R01+GJP+m2geelG1NaFP/Y0JfPz4R3INZcU6J1/fBonk3ep1KEKy3f2/X5YJnBoUtH Nj34779JXrAAS8t96z3UnIC9tF8sGv6IMChtMltEEyWJlGGKqRCnSCp4dBzdHDGgtb wPf+Tc/Xt5zUIA2HBEcLdtpDsa/czWA5Es1hy/yO6Z/CpCVnKKm6G3iL8/YOp5dzCq oK67c/kBrY2WVWtboMHYek6Gb4aPJ3FtjCW3K/xDy7MySuiGNH3ltZ7QLqZPR3X/Rr YmAzl2EtWGqzA== From: Tzung-Bi Shih To: Arnd Bergmann , Greg Kroah-Hartman , Bartosz Golaszewski , Linus Walleij Cc: Benson Leung , tzungbi@kernel.org, linux-kernel@vger.kernel.org, chrome-platform@lists.linux.dev, driver-core@lists.linux.dev, linux-doc@vger.kernel.org, linux-gpio@vger.kernel.org, "Rafael J. Wysocki" , Danilo Krummrich , Jonathan Corbet , Shuah Khan , Laurent Pinchart , Wolfram Sang , Jason Gunthorpe , Johan Hovold , "Paul E . McKenney" , Dan Williams Subject: [PATCH v9 2/9] revocable: Add KUnit test cases Date: Mon, 27 Apr 2026 21:58:34 +0800 Message-ID: <20260427135841.96266-3-tzungbi@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260427135841.96266-1-tzungbi@kernel.org> References: <20260427135841.96266-1-tzungbi@kernel.org> Precedence: bulk X-Mailing-List: linux-doc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add KUnit test cases for the revocable API. The test cases cover the following scenarios: - Basic: Verifies that a consumer can successfully access the resource. - Revocation: Verifies that after the provider revokes the resource, the consumer correctly receives a NULL pointer on a subsequent access. - Try Access Macro: Same as "Revocation" but uses the macro level helpers. - Concurrent Access: Verifies multiple threads can access the resource. Signed-off-by: Tzung-Bi Shih --- v9: - Add test cases for embedded resource provider. v8: https://lore.kernel.org/all/20260213092307.858908-3-tzungbi@kernel.org - Squash: - c259cd7ea3c9 revocable: fix missing module license and description - a243f7fb11fe revocable: Add KUnit test for provider lifetime races - 988357628c2c revocable: Add KUnit test for concurrent access - Change accordingly due to its dependency "revocable: Revocable resource management" changes. v7: https://lore.kernel.org/all/20260116080235.350305-3-tzungbi@kernel.org - "2025" -> "2026" in copyright. - Rename the test name "macro" -> "try_access_macro". v6: https://lore.kernel.org/all/20251106152330.11733-3-tzungbi@kernel.org - Rename REVOCABLE_TRY_ACCESS_WITH() -> REVOCABLE_TRY_ACCESS_SCOPED(). - Add tests for new REVOCABLE_TRY_ACCESS_WITH(). v5: https://lore.kernel.org/all/20251016054204.1523139-3-tzungbi@kernel.org - No changes. v4: https://lore.kernel.org/all/20250923075302.591026-3-tzungbi@kernel.org - REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH(). - revocable_release() -> revocable_withdraw_access(). v3: https://lore.kernel.org/all/20250912081718.3827390-3-tzungbi@kernel.org - No changes. v2: https://lore.kernel.org/all/20250820081645.847919-3-tzungbi@kernel.org - New in the series. A way to run the test: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=y \ revocable_test Or $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_REVOCABLE_KUNIT_TEST=y \ --kconfig_add CONFIG_PROVE_LOCKING=y \ --kconfig_add CONFIG_DEBUG_KERNEL=y \ --kconfig_add CONFIG_DEBUG_INFO=y \ --kconfig_add CONFIG_DEBUG_INFO_DWARF5=y \ --kconfig_add CONFIG_KASAN=y \ --kconfig_add CONFIG_DETECT_HUNG_TASK=y \ --kconfig_add CONFIG_DEFAULT_HUNG_TASK_TIMEOUT="10" \ --arch=x86_64 \ --make_options="C=1 W=1" \ revocable_test --- MAINTAINERS | 1 + drivers/base/Kconfig | 5 + drivers/base/Makefile | 3 + drivers/base/revocable_test.c | 461 ++++++++++++++++++++++++++++++++++ 4 files changed, 470 insertions(+) create mode 100644 drivers/base/revocable_test.c diff --git a/MAINTAINERS b/MAINTAINERS index 6ec3a7cb5e6b..14aa035ef431 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22831,6 +22831,7 @@ L: driver-core@lists.linux.dev S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: drivers/base/revocable.c +F: drivers/base/revocable_test.c F: include/linux/revocable.h RFKILL diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index f7d385cbd3ba..921f7e812ba5 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -259,3 +259,8 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT work on. endmenu + +config REVOCABLE_KUNIT_TEST + tristate "KUnit tests for revocable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS diff --git a/drivers/base/Makefile b/drivers/base/Makefile index bdf854694e39..5fd19abbc83e 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -35,3 +35,6 @@ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) obj-$(CONFIG_TRACING) += trace.o + +# KUnit test cases +obj-$(CONFIG_REVOCABLE_KUNIT_TEST) += revocable_test.o diff --git a/drivers/base/revocable_test.c b/drivers/base/revocable_test.c new file mode 100644 index 000000000000..bdab05dfafda --- /dev/null +++ b/drivers/base/revocable_test.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 Google LLC + * + * KUnit tests for the revocable API. + * + * The test cases cover the following scenarios: + * + * - Basic: Verifies that a consumer can successfully access the resource. + * + * - Revocation: Verifies that after the provider revokes the resource, + * the consumer correctly receives a NULL pointer on a subsequent access. + * + * - Try Access Macro: Same as "Revocation" but uses the macro level + * helpers. + * + * - Concurrent Access: Verifies multiple threads can access the resource. + */ + +#include +#include +#include +#include +#include +#include + +static int get_refcount(struct revocable *rev) +{ + return refcount_read(&rev->kref.refcount); +} + +static void revocable_test_basic(struct kunit *test) +{ + struct revocable *rev; + struct revocable_consumer rc; + void *real_res = (void *)0x12345678, *res; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + revocable_init(rev, &rc); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rc); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + revocable_deinit(&rc); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +static void revocable_embed_test_basic(struct kunit *test) +{ + struct revocable rev; + struct revocable_consumer rc; + void *real_res = (void *)0x12345678, *res; + + revocable_embed_init(&rev, real_res); + KUNIT_EXPECT_EQ(test, rev.alloc_type, REVOCABLE_EMBEDDED); + + revocable_init(&rev, &rc); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rc); + + revocable_embed_destroy(&rev); +} + +static void revocable_test_revocation(struct kunit *test) +{ + struct revocable *rev; + struct revocable_consumer rc; + void *real_res = (void *)0x12345678, *res; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + revocable_init(rev, &rc); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rc); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_withdraw_access(&rc); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + revocable_deinit(&rc); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +static void revocable_embed_test_revocation(struct kunit *test) +{ + struct revocable rev; + struct revocable_consumer rc; + void *real_res = (void *)0x12345678, *res; + + revocable_embed_init(&rev, real_res); + KUNIT_EXPECT_EQ(test, rev.alloc_type, REVOCABLE_EMBEDDED); + + revocable_init(&rev, &rc); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + revocable_withdraw_access(&rc); + + revocable_revoke(&rev); + + res = revocable_try_access(&rc); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + revocable_withdraw_access(&rc); + + revocable_embed_destroy(&rev); +} + +static void revocable_test_try_access_macro1(struct kunit *test) +{ + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + { + revocable_try_access_with(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + } + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + { + revocable_try_access_with(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + } + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + revocable_put(rev); +} + +static int call_revocable_try_access_or_return_err(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_err(rev, res, -ENXIO); + return 0; +} + +static int call_revocable_try_access_or_return(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return(rev, res); + return 0; +} + +static void call_revocable_try_access_or_return_void(struct kunit *test, + struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_void(rev, res); + KUNIT_FAIL(test, "unreachable"); +} + +static void revocable_test_try_access_macro2(struct kunit *test) +{ + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + int ret; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + { + revocable_try_access_with(rev, res); + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + } + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + ret = call_revocable_try_access_or_return_err(rev); + KUNIT_EXPECT_EQ(test, ret, -ENXIO); + + ret = call_revocable_try_access_or_return(rev); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + call_revocable_try_access_or_return_void(test, rev); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +static void revocable_test_try_access_macro3(struct kunit *test) +{ + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + bool accessed; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + accessed = false; + revocable_try_access_with_scoped(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + accessed = true; + } + KUNIT_EXPECT_TRUE(test, accessed); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + accessed = false; + revocable_try_access_with_scoped(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, NULL); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + accessed = true; + } + KUNIT_EXPECT_TRUE(test, accessed); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + revocable_put(rev); +} + +static int call_revocable_try_access_or_return_err_scoped(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_err_scoped(rev, res, -ENXIO) {} + return 0; +} + +static int call_revocable_try_access_or_return_scoped(struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_scoped(rev, res) {} + return 0; +} + +static void call_revocable_try_access_or_return_void_scoped(struct kunit *test, + struct revocable *rev) +{ + void *res; + + revocable_try_access_or_return_void_scoped(rev, res) {} + KUNIT_FAIL(test, "unreachable"); +} + +static void revocable_test_try_access_macro4(struct kunit *test) +{ + struct revocable *rev; + void *real_res = (void *)0x12345678, *res; + bool accessed; + int ret; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + accessed = false; + revocable_try_access_with_scoped(rev, res) { + KUNIT_EXPECT_PTR_EQ(test, res, real_res); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 3); + accessed = true; + } + KUNIT_EXPECT_TRUE(test, accessed); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + + revocable_revoke(rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + ret = call_revocable_try_access_or_return_err_scoped(rev); + KUNIT_EXPECT_EQ(test, ret, -ENXIO); + + ret = call_revocable_try_access_or_return_scoped(rev); + KUNIT_EXPECT_EQ(test, ret, -ENODEV); + + call_revocable_try_access_or_return_void_scoped(test, rev); + + accessed = false; + revocable_try_access_or_skip_scoped(rev, res) + accessed = true; + KUNIT_EXPECT_FALSE(test, accessed); + + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + revocable_put(rev); +} + +struct test_concurrent_access_context { + struct completion started, enter; + struct task_struct *thread; + + union { + /* Used by test provider. */ + struct revocable *rev; + + /* Used by test consumer. */ + struct { + struct completion exit; + struct revocable_consumer rc; + struct kunit *test; + void *expected_res; + }; + }; +}; + +static int test_concurrent_access_provider(void *data) +{ + struct test_concurrent_access_context *ctx = data; + + complete(&ctx->started); + + wait_for_completion(&ctx->enter); + revocable_revoke(ctx->rev); + + return 0; +} + +static int test_concurrent_access_consumer(void *data) +{ + struct test_concurrent_access_context *ctx = data; + void *res; + + complete(&ctx->started); + + wait_for_completion(&ctx->enter); + res = revocable_try_access(&ctx->rc); + KUNIT_EXPECT_PTR_EQ(ctx->test, res, ctx->expected_res); + + wait_for_completion(&ctx->exit); + revocable_withdraw_access(&ctx->rc); + + return 0; +} + +static void revocable_test_concurrent_access(struct kunit *test) +{ + struct revocable *rev; + void *real_res = (void *)0x12345678; + struct test_concurrent_access_context *ctx; + int i; + + rev = revocable_alloc(real_res); + KUNIT_ASSERT_NOT_NULL(test, rev); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2); + KUNIT_EXPECT_EQ(test, rev->alloc_type, REVOCABLE_DYNAMIC); + + ctx = kunit_kmalloc_array(test, 3, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + for (i = 0; i < 3; ++i) { + ctx[i].test = test; + init_completion(&ctx[i].started); + init_completion(&ctx[i].enter); + + if (i == 0) { + /* Transfer the ownership of provider reference too. */ + ctx[i].rev = rev; + ctx[i].thread = kthread_run( + test_concurrent_access_provider, ctx + i, + "revocable_%d", i); + } else { + init_completion(&ctx[i].exit); + revocable_init(rev, &ctx[i].rc); + KUNIT_EXPECT_EQ(test, get_refcount(rev), 2 + i); + + ctx[i].thread = kthread_run( + test_concurrent_access_consumer, ctx + i, + "revocable_consumer_%d", i); + } + KUNIT_ASSERT_FALSE(test, IS_ERR(ctx[i].thread)); + + wait_for_completion(&ctx[i].started); + } + + ctx[1].expected_res = real_res; + /* consumer1 enters read-side critical section. */ + complete(&ctx[1].enter); + msleep(100); + + /* provider0 revokes the resource. */ + complete(&ctx[0].enter); + msleep(100); + /* provider0 can't exit. It's waiting for the grace period. */ + KUNIT_EXPECT_EQ(test, get_refcount(rev), 4); + + ctx[2].expected_res = NULL; + /* consumer2 enters read-side critical section. */ + complete(&ctx[2].enter); + msleep(100); + + /* consumer{1,2} exit read-side critical section. */ + for (i = 1; i < 3; ++i) { + complete(&ctx[i].exit); + kthread_stop(ctx[i].thread); + revocable_deinit(&ctx[i].rc); + } + + kthread_stop(ctx[0].thread); + /* provider0 exits as all readers exit their critical section. */ + KUNIT_EXPECT_EQ(test, get_refcount(rev), 1); + + /* Drop the caller reference. */ + revocable_put(rev); +} + +static struct kunit_case revocable_test_cases[] = { + KUNIT_CASE(revocable_test_basic), + KUNIT_CASE(revocable_embed_test_basic), + KUNIT_CASE(revocable_test_revocation), + KUNIT_CASE(revocable_embed_test_revocation), + KUNIT_CASE(revocable_test_try_access_macro1), + KUNIT_CASE(revocable_test_try_access_macro2), + KUNIT_CASE(revocable_test_try_access_macro3), + KUNIT_CASE(revocable_test_try_access_macro4), + KUNIT_CASE(revocable_test_concurrent_access), + {} +}; + +static struct kunit_suite revocable_test_suite = { + .name = "revocable_test", + .test_cases = revocable_test_cases, +}; + +kunit_test_suite(revocable_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzung-Bi Shih "); +MODULE_DESCRIPTION("KUnit tests for the revocable API"); -- 2.51.0