* [PATCH v8 0/3] drivers/base: Introduce revocable
@ 2026-02-13 9:23 Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 1/3] revocable: Revocable resource management Tzung-Bi Shih
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Tzung-Bi Shih @ 2026-02-13 9:23 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Bartosz Golaszewski, Wolfram Sang, Jason Gunthorpe, Johan Hovold,
Paul E . McKenney, Dan Williams, Linus Walleij, chrome-platform,
tzungbi, driver-core, linux-doc, linux-kernel, linux-kselftest
The series is separated from [1] to show the independency and compare
potential use cases easier. This is the revocable core part. Use cases
are in other series.
The 1st patch introduces the revocable which is an implementation of ideas
from the talk [2].
The 2nd and 3rd patches add test cases for revocable in KUnit and selftest.
[1] https://lore.kernel.org/all/20251016054204.1523139-1-tzungbi@kernel.org
[2] https://lpc.events/event/17/contributions/1627/
---
v8:
- Rework on the revocable APIs. See changelog in [PATCH v8 1/3] for details.
v7: https://lore.kernel.org/all/20260116080235.350305-1-tzungbi@kernel.org
- Rebase onto next-20260115.
v6: https://lore.kernel.org/chrome-platform/20251106152330.11733-1-tzungbi@kernel.org
- Rebase onto next-20251106.
- Separate revocable core and use cases.
v5: https://lore.kernel.org/chrome-platform/20251016054204.1523139-1-tzungbi@kernel.org
- Rebase onto next-20251015.
- Add more context about the PoC.
- Support multiple revocable providers in the PoC.
v4: https://lore.kernel.org/chrome-platform/20250923075302.591026-1-tzungbi@kernel.org
- Rebase onto next-20250922.
- Remove the 5th patch from v3.
- Add fops replacement PoC in 5th - 7th patches.
v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-1-tzungbi@kernel.org
- Rebase onto https://lore.kernel.org/chrome-platform/20250828083601.856083-1-tzungbi@kernel.org
and next-20250912.
- The 4th patch changed accordingly.
v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-1-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".
- Add test cases in Kunit and selftest.
v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-1-tzungbi@kernel.org
Tzung-Bi Shih (3):
revocable: Revocable resource management
revocable: Add KUnit test cases
selftests: revocable: Add kselftest cases
.../driver-api/driver-model/index.rst | 1 +
.../driver-api/driver-model/revocable.rst | 256 +++++++++++
MAINTAINERS | 11 +
drivers/base/Kconfig | 5 +
drivers/base/Makefile | 5 +-
drivers/base/revocable.c | 228 ++++++++++
drivers/base/revocable_test.c | 410 ++++++++++++++++++
include/linux/revocable.h | 194 +++++++++
tools/testing/selftests/revocable/Makefile | 7 +
.../selftests/revocable/revocable_test.c | 177 ++++++++
.../selftests/revocable/revocable_test.h | 20 +
.../selftests/revocable/test-revocable.sh | 34 ++
.../selftests/revocable/test_modules/Makefile | 10 +
.../revocable/test_modules/revocable_test.c | 234 ++++++++++
14 files changed, 1591 insertions(+), 1 deletion(-)
create mode 100644 Documentation/driver-api/driver-model/revocable.rst
create mode 100644 drivers/base/revocable.c
create mode 100644 drivers/base/revocable_test.c
create mode 100644 include/linux/revocable.h
create mode 100644 tools/testing/selftests/revocable/Makefile
create mode 100644 tools/testing/selftests/revocable/revocable_test.c
create mode 100644 tools/testing/selftests/revocable/revocable_test.h
create mode 100755 tools/testing/selftests/revocable/test-revocable.sh
create mode 100644 tools/testing/selftests/revocable/test_modules/Makefile
create mode 100644 tools/testing/selftests/revocable/test_modules/revocable_test.c
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v8 1/3] revocable: Revocable resource management
2026-02-13 9:23 [PATCH v8 0/3] drivers/base: Introduce revocable Tzung-Bi Shih
@ 2026-02-13 9:23 ` Tzung-Bi Shih
2026-02-16 16:37 ` Bartosz Golaszewski
2026-02-13 9:23 ` [PATCH v8 2/3] revocable: Add KUnit test cases Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 3/3] selftests: revocable: Add kselftest cases Tzung-Bi Shih
2 siblings, 1 reply; 6+ messages in thread
From: Tzung-Bi Shih @ 2026-02-13 9:23 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Bartosz Golaszewski, Wolfram Sang, Jason Gunthorpe, Johan Hovold,
Paul E . McKenney, Dan Williams, Linus Walleij, chrome-platform,
tzungbi, driver-core, linux-doc, linux-kernel, linux-kselftest
The "revocable" mechanism is a synchronization primitive designed to
manage safe access to resources that can be asynchronously removed or
invalidated. Its primary purpose is to prevent Use-After-Free (UAF)
errors when interacting with resources whose lifetimes are not
guaranteed to outlast their consumers.
This is particularly useful in systems where resources can disappear
unexpectedly, such as those provided by hot-pluggable devices like
USB. When a consumer holds a reference to such a resource, the
underlying device might be removed, causing the resource's memory to
be freed. Subsequent access attempts by the consumer would then lead
to UAF errors.
Revocable addresses this by providing a form of "weak reference" and
a controlled access method. It allows a resource consumer to safely
attempt to access the resource. The mechanism guarantees that any
access granted is valid for the duration of its use. If the resource
has already been revoked (i.e., freed), the access attempt will fail
safely, typically by returning NULL, instead of causing a crash.
It uses a provider/consumer model built on Sleepable RCU (SRCU) to
guarantee safe memory access:
- A resource provider, such as a driver for a hot-pluggable device,
allocates a struct revocable and initializes it with a pointer
to the resource.
- A resource consumer that wants to access the resource allocates a
struct revocable_consumer containing a reference to the provider.
- To access the resource, the consumer uses revocable_try_access().
This function enters an SRCU read-side critical section and returns
the pointer to the resource. If the provider has already freed the
resource, it returns NULL. After use, the consumer calls
revocable_withdraw_access() to exit the SRCU critical section. There
are some macro level helpers for doing that.
The API provides the following contract:
- revocable_try_access() can be safely called from both process and
atomic contexts.
- It is permitted to sleep within the critical section established
between revocable_try_access() and revocable_withdraw_access().
- revocable_try_access() and the matching revocable_withdraw_access()
must occur in the same context. For example, it is illegal to
invoke revocable_withdraw_access() in an irq handler if the matching
revocable_try_access() was invoked in process context.
- When the provider needs to remove the resource, it calls
revocable_revoke(). This function sets the internal resource
pointer to NULL and then calls synchronize_srcu() to wait for all
current readers to finish before the resource can be completely torn
down.
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v8:
- Squash:
- fdeb3ca3cca8 revocable: Remove redundant synchronize_srcu() call
- 4d7dc4d1a62d revocable: Fix races in revocable_alloc() using RCU
- 377563ce0653 revocable: fix SRCU index corruption by requiring caller-provided storage
- Rename macro names:
- REVOCABLE_TRY_ACCESS_WITH() -> revocable_try_access_with().
- REVOCABLE_TRY_ACCESS_SCOPED() -> revocable_try_access_with_scoped().
- Rename terminologies as now normal users should only "see" provider
handles, using a shorter name for provider handle to echo the main
concept.
- struct revocable -> struct revocable_consumer.
- struct revocable_provider -> struct revocable.
- revocable_provider_alloc() -> revocable_alloc().
- revocable_provider_revoke() -> revocable_revoke().
- New APIs:
- revocable_get().
- revocable_put().
- revocable_try_access_or_return_err().
- revocable_try_access_or_return().
- revocable_try_access_or_return_void().
- revocable_try_access_or_return_err_scoped().
- revocable_try_access_or_return_scoped().
- revocable_try_access_or_void_scoped().
- revocable_try_access_or_skip_scoped().
- Add API contract that revocable_try_access() works from process and
atomic context while also allowing sleeping inside the critical
sections.
- Add revocable.h to the DRIVER CORE entry in MAINTAINERS.
v7: https://lore.kernel.org/all/20260116080235.350305-2-tzungbi@kernel.org
- "2025" -> "2026" in copyright.
- Documentation/
- Rephrase section "Revocable vs. Devres (devm)".
- Include sections for struct revocable_provider and struct revocable.
- Minor rename: "revocable" -> "access_rev" for DEFINE_FREE().
- Add Acked-by tag.
v6: https://lore.kernel.org/chrome-platform/20251106152330.11733-2-tzungbi@kernel.org
- Rename REVOCABLE_TRY_ACCESS_WITH() -> REVOCABLE_TRY_ACCESS_SCOPED().
- Add new REVOCABLE_TRY_ACCESS_WITH().
- Remove Acked-by tags as the API names changed a bit.
v5: https://lore.kernel.org/chrome-platform/20251016054204.1523139-2-tzungbi@kernel.org
- No changes.
v4: https://lore.kernel.org/chrome-platform/20250923075302.591026-2-tzungbi@kernel.org
- Rename:
- revocable_provider_free() -> revocable_provider_revoke().
- REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
- revocable_release() -> revocable_withdraw_access().
- rcu_dereference() -> srcu_dereference() to fix a warning from lock debugging.
- Move most docs to kernel-doc, include them in Documentation/, and modify the
commit message accordingly.
- Fix some doc errors.
- Add Acked-by tags.
v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-2-tzungbi@kernel.org
- No changes.
v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-2-tzungbi@kernel.org
- Rename "ref_proxy" -> "revocable".
- Add introduction in kernel-doc format in revocable.c.
- Add MAINTAINERS entry.
- Add copyright.
- Move from lib/ to drivers/base/.
- EXPORT_SYMBOL() -> EXPORT_SYMBOL_GPL().
- Add Documentation/.
- Rename _get() -> try_access(); _put() -> release().
- Fix a sparse warning by removing the redundant __rcu annotations.
- Fix a sparse warning by adding __acquires() and __releases() annotations.
v1: https://lore.kernel.org/chrome-platform/20250814091020.1302888-2-tzungbi@kernel.org
A way to verify Documentation/:
- `make O=build SPHINXDIRS=driver-api/driver-model/ htmldocs`.
.../driver-api/driver-model/index.rst | 1 +
.../driver-api/driver-model/revocable.rst | 256 ++++++++++++++++++
MAINTAINERS | 9 +
drivers/base/Makefile | 2 +-
drivers/base/revocable.c | 228 ++++++++++++++++
include/linux/revocable.h | 194 +++++++++++++
6 files changed, 689 insertions(+), 1 deletion(-)
create mode 100644 Documentation/driver-api/driver-model/revocable.rst
create mode 100644 drivers/base/revocable.c
create mode 100644 include/linux/revocable.h
diff --git a/Documentation/driver-api/driver-model/index.rst b/Documentation/driver-api/driver-model/index.rst
index 4831bdd92e5c..8e1ee21185df 100644
--- a/Documentation/driver-api/driver-model/index.rst
+++ b/Documentation/driver-api/driver-model/index.rst
@@ -14,6 +14,7 @@ Driver Model
overview
platform
porting
+ revocable
.. only:: subproject and html
diff --git a/Documentation/driver-api/driver-model/revocable.rst b/Documentation/driver-api/driver-model/revocable.rst
new file mode 100644
index 000000000000..25f7ce82fa05
--- /dev/null
+++ b/Documentation/driver-api/driver-model/revocable.rst
@@ -0,0 +1,256 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==============================
+Revocable Resource Management
+==============================
+
+Overview
+========
+
+.. kernel-doc:: drivers/base/revocable.c
+ :doc: Overview
+
+Revocable vs. Devres (devm)
+===========================
+
+Revocable and Devres address different problems in resource management:
+
+* **Devres:** Primarily addresses **resource leaks**. The lifetime of the
+ resources is tied to the lifetime of the device. The resource is
+ automatically freed when the device is unbound. This cleanup happens
+ irrespective of any potential active users.
+
+* **Revocable:** Primarily addresses **invalid memory access**,
+ such as Use-After-Free (UAF). It's an independent synchronization
+ primitive that decouples consumer access from the resource's actual
+ presence. Consumers interact with a "revocable object" (an intermediary),
+ not the underlying resource directly. This revocable object persists as
+ long as there are active references to it from consumer handles.
+
+**Key Distinctions & How They Complement Each Other:**
+
+1. **Reference Target:** Consumers hold a reference to the *revocable object*,
+ not the encapsulated resource itself.
+
+2. **Resource Lifetime vs. Access:** The underlying resource's lifetime is
+ independent of the number of references to the revocable object. The
+ resource can be freed at any point. A common scenario is the resource
+ being freed by `devres` when the providing device is unbound.
+
+3. **Safe Access:** Revocable provides a safe way to attempt access. Before
+ using the resource, a consumer uses the Revocable API (e.g.,
+ revocable_try_access()). This function checks if the resource is still
+ valid. It returns a pointer to the resource only if it hasn't been
+ revoked; otherwise, it returns NULL. This prevents UAF by providing a
+ clear signal that the resource is gone.
+
+4. **Complementary Usage:** `devres` and Revocable work well together.
+ `devres` can handle the automatic allocation and deallocation of a
+ resource tied to a device. The Revocable mechanism can be layered on top
+ to provide safe access for consumers whose lifetimes might extend beyond
+ the provider device's lifetime. For instance, a userspace program might
+ keep a character device file open even after the physical device has been
+ removed. In this case:
+
+ * `devres` frees the device-specific resource upon unbinding.
+ * The Revocable mechanism ensures that any subsequent operations on the
+ open file handle, which attempt to access the now-freed resource,
+ will fail gracefully (e.g., revocable_try_access() returns NULL)
+ instead of causing a UAF.
+
+In summary, `devres` ensures resources are *released* to prevent leaks, while
+the Revocable mechanism ensures that attempts to *access* these resources are
+done safely, even if the resource has been released.
+
+API and Usage
+=============
+
+For Resource Providers
+----------------------
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_get
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_put
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_alloc
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_revoke
+
+For Resource Consumers
+----------------------
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_consumer
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_init
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_deinit
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_try_access
+
+.. kernel-doc:: drivers/base/revocable.c
+ :identifiers: revocable_withdraw_access
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_with
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ revocable_try_access_with(rp, res);
+ // Always check if the resource is valid.
+ if (!res) {
+ pr_warn("Resource is not available\n");
+ return -EAGAIN;
+ }
+
+ // 'res' is guaranteed to be valid until this function exits.
+ do_something_with(res);
+ return 0;
+ } // revocable_withdraw_access() is automatically called here.
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_or_return_err
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ // Returns -ENXIO if access fails.
+ revocable_try_access_or_return_err(rp, res, -ENXIO);
+
+ // 'res' is guaranteed to be valid if we reach here.
+ do_something_with(res);
+ return 0;
+ } // revocable_withdraw_access() is automatically called here.
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_or_return
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ // Returns -ENODEV if access fails.
+ revocable_try_access_or_return(rp, res);
+
+ // 'res' is guaranteed to be valid if we reach here.
+ do_something_with(res);
+ return 0;
+ } // revocable_withdraw_access() is automatically called here.
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_with_scoped
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ revocable_try_access_with_scoped(rp, res) {
+ // Always check if the resource is valid.
+ if (!res) {
+ pr_warn("Resource is not available\n");
+ return -EAGAIN;
+ }
+
+ // 'res' is valid for the rest of this block.
+ do_something_with(res);
+ }
+ // revocable_withdraw_access() is automatically called here.
+
+ return 0;
+ }
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_or_return_err_scoped
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ // Returns -ENXIO if access fails.
+ revocable_try_access_or_return_err_scoped(rp, res, -ENXIO) {
+ // 'res' is guaranteed to be valid in this block.
+ do_something_with(res);
+ }
+ // revocable_withdraw_access() is automatically called here.
+
+ return 0; // Only reached if resource was accessed.
+ }
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_or_return_scoped
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ // Returns -ENODEV if access fails.
+ revocable_try_access_or_return_scoped(rp, res) {
+ // 'res' is guaranteed to be valid in this block.
+ do_something_with(res);
+ }
+ // revocable_withdraw_access() is automatically called here.
+
+ return 0; // Only reached if resource was accessed.
+ }
+
+.. kernel-doc:: include/linux/revocable.h
+ :identifiers: revocable_try_access_or_skip_scoped
+
+Example Usage
+~~~~~~~~~~~~~
+
+.. code-block:: c
+
+ int consumer_use_resource(struct revocable *rp)
+ {
+ struct foo_resource *res;
+
+ revocable_try_access_or_skip_scoped(rp, res) {
+ // This block is ONLY entered if 'res' is not NULL.
+ do_something_with(res);
+ }
+ // revocable_withdraw_access() is automatically called here.
+
+ return 0;
+ }
diff --git a/MAINTAINERS b/MAINTAINERS
index a638a9ddb0ff..01670bc4db8d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7702,6 +7702,7 @@ F: include/linux/device.h
F: include/linux/fwnode.h
F: include/linux/kobj*
F: include/linux/property.h
+F: include/linux/revocable.h
F: include/linux/sysfs.h
F: lib/kobj*
F: rust/kernel/debugfs.rs
@@ -22407,6 +22408,14 @@ F: include/uapi/linux/rseq.h
F: kernel/rseq.c
F: tools/testing/selftests/rseq/
+REVOCABLE RESOURCE MANAGEMENT
+M: Tzung-Bi Shih <tzungbi@kernel.org>
+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: include/linux/revocable.h
+
RFKILL
M: Johannes Berg <johannes@sipsolutions.net>
L: linux-wireless@vger.kernel.org
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 8074a10183dc..bdf854694e39 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -6,7 +6,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
cpu.o firmware.o init.o map.o devres.o \
attribute_container.o transport_class.o \
topology.o container.o property.o cacheinfo.o \
- swnode.o faux.o
+ swnode.o faux.o revocable.o
obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
diff --git a/drivers/base/revocable.c b/drivers/base/revocable.c
new file mode 100644
index 000000000000..aeda9f46351f
--- /dev/null
+++ b/drivers/base/revocable.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Revocable resource management
+ */
+
+#include <linux/kref.h>
+#include <linux/revocable.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+
+/**
+ * DOC: Overview
+ *
+ * The "revocable" mechanism is a synchronization primitive designed to
+ * manage safe access to resources that can be asynchronously removed or
+ * invalidated. Its primary purpose is to prevent Use-After-Free (UAF)
+ * errors when interacting with resources whose lifetimes are not
+ * guaranteed to outlast their consumers.
+ *
+ * This is particularly useful in systems where resources can disappear
+ * unexpectedly, such as those provided by hot-pluggable devices like
+ * USB. When a consumer holds a reference to such a resource, the
+ * underlying device might be removed, causing the resource's memory to
+ * be freed. Subsequent access attempts by the consumer would then lead
+ * to UAF errors.
+ *
+ * Revocable addresses this by providing a form of "weak reference" and
+ * a controlled access method. It allows a resource consumer to safely
+ * attempt to access the resource. The mechanism guarantees that any
+ * access granted is valid for the duration of its use. If the resource
+ * has already been revoked (i.e., freed), the access attempt will fail
+ * safely, typically by returning NULL, instead of causing a crash.
+ *
+ * It uses a provider/consumer model built on Sleepable RCU (SRCU) to
+ * guarantee safe memory access:
+ *
+ * - A resource provider, such as a driver for a hot-pluggable device,
+ * allocates a struct revocable and initializes it with a pointer
+ * to the resource.
+ *
+ * - A resource consumer that wants to access the resource allocates a
+ * struct revocable_consumer containing a reference to the provider.
+ *
+ * - To access the resource, the consumer uses revocable_try_access().
+ * This function enters an SRCU read-side critical section and returns
+ * the pointer to the resource. If the provider has already freed the
+ * resource, it returns NULL. After use, the consumer calls
+ * revocable_withdraw_access() to exit the SRCU critical section. There
+ * are some macro level helpers for doing that.
+ *
+ * The API provides the following contract:
+ *
+ * - revocable_try_access() can be safely called from both process and
+ * atomic contexts.
+ * - It is permitted to sleep within the critical section established
+ * between revocable_try_access() and revocable_withdraw_access().
+ * - revocable_try_access() and the matching revocable_withdraw_access()
+ * must occur in the same context. For example, it is illegal to
+ * invoke revocable_withdraw_access() in an irq handler if the matching
+ * revocable_try_access() was invoked in process context.
+ *
+ * - When the provider needs to remove the resource, it calls
+ * revocable_revoke(). This function sets the internal resource
+ * pointer to NULL and then calls synchronize_srcu() to wait for all
+ * current readers to finish before the resource can be completely torn
+ * down.
+ */
+
+static void revocable_release(struct kref *kref)
+{
+ struct revocable *rp = container_of(kref, struct revocable, kref);
+
+ cleanup_srcu_struct(&rp->srcu);
+ kfree(rp);
+}
+
+/**
+ * revocable_get() - Increase a reference count to the provider handle.
+ * @rp: The pointer of resource provider.
+ *
+ * This holds a refcount to the resource provider.
+ */
+void revocable_get(struct revocable *rp)
+{
+ kref_get(&rp->kref);
+}
+EXPORT_SYMBOL_GPL(revocable_get);
+
+/**
+ * revocable_put() - Decrease a reference count to the provider handle.
+ * @rp: The pointer of resource provider.
+ *
+ * This drops a refcount to the resource provider. If it is the final
+ * reference, revocable_release() will be called to free the struct.
+ */
+void revocable_put(struct revocable *rp)
+{
+ kref_put(&rp->kref, revocable_release);
+}
+EXPORT_SYMBOL_GPL(revocable_put);
+
+/**
+ * revocable_alloc() - Allocate struct revocable.
+ * @res: The pointer of resource.
+ *
+ * This allocates a resource provider handle and holds 2 initial reference
+ * counts to the handle. If revocable_alloc() succeed:
+ *
+ * - The provider should call revocable_revoke() for dropping a reference.
+ * - The caller should call revocable_put() for dropping another reference.
+ *
+ * Return: The pointer of struct revocable. NULL on errors.
+ */
+struct revocable *revocable_alloc(void *res)
+{
+ struct revocable *rp;
+
+ rp = kzalloc(sizeof(*rp), GFP_KERNEL);
+ if (!rp)
+ return NULL;
+
+ init_srcu_struct(&rp->srcu);
+ RCU_INIT_POINTER(rp->res, res);
+ kref_init(&rp->kref);
+ revocable_get(rp);
+ return rp;
+}
+EXPORT_SYMBOL_GPL(revocable_alloc);
+
+/**
+ * revocable_revoke() - Revoke the managed resource.
+ * @rp: The pointer of resource provider.
+ *
+ * This sets the resource `(struct revocable *)->res` to NULL to indicate
+ * the resource has gone.
+ *
+ * This drops a refcount to the resource provider. If it is the final
+ * reference, revocable_release() will be called to free the struct.
+ */
+void revocable_revoke(struct revocable *rp)
+{
+ rcu_assign_pointer(rp->res, NULL);
+ synchronize_srcu(&rp->srcu);
+ revocable_put(rp);
+}
+EXPORT_SYMBOL_GPL(revocable_revoke);
+
+/**
+ * revocable_init() - Initialize struct revocable_consumer.
+ * @rp: The pointer of resource provider.
+ * @rev: The pointer of resource consumer.
+ *
+ * This holds a refcount to the resource provider.
+ */
+void revocable_init(struct revocable *rp, struct revocable_consumer *rev)
+{
+ revocable_get(rp);
+ rev->rp = rp;
+}
+EXPORT_SYMBOL_GPL(revocable_init);
+
+/**
+ * revocable_deinit() - Deinitialize struct revocable_consumer.
+ * @rev: The pointer of resource consumer.
+ *
+ * This drops a refcount to the resource provider. If it is the final
+ * reference, revocable_release() will be called to free the struct.
+ */
+void revocable_deinit(struct revocable_consumer *rev)
+{
+ struct revocable *rp = rev->rp;
+
+ revocable_put(rp);
+}
+EXPORT_SYMBOL_GPL(revocable_deinit);
+
+/**
+ * revocable_try_access() - Try to access the resource.
+ * @rev: The pointer of resource consumer.
+ *
+ * This tries to de-reference to the resource and enters a SRCU critical
+ * section.
+ *
+ * The function is safe to be called from both process and atomic contexts.
+ * While holding the access (i.e. before calling revocable_withdraw_access()),
+ * the caller is allowed to sleep.
+ *
+ * Note that revocable_try_access() and the matching
+ * revocable_withdraw_access() must occur in the same context. For example, it
+ * is illegal to invoke revocable_withdraw_access() in an irq handler if the
+ * matching revocable_try_access() was invoked in process context.
+ *
+ * Return: The pointer to the resource. NULL if the resource has gone.
+ */
+void *revocable_try_access(struct revocable_consumer *rev)
+ __acquires(&rev->rp->srcu)
+{
+ struct revocable *rp = rev->rp;
+
+ rev->idx = srcu_read_lock(&rp->srcu);
+ return srcu_dereference(rp->res, &rp->srcu);
+}
+EXPORT_SYMBOL_GPL(revocable_try_access);
+
+/**
+ * revocable_withdraw_access() - Stop accessing to the resource.
+ * @rev: The pointer of resource consumer.
+ *
+ * Call this function to indicate the resource is no longer used. It exits
+ * the SRCU critical section.
+ *
+ * The function is safe to be called from both process and atomic contexts.
+ *
+ * Note that revocable_try_access() and the matching
+ * revocable_withdraw_access() must occur in the same context. For example, it
+ * is illegal to invoke revocable_withdraw_access() in an irq handler if the
+ * matching revocable_try_access() was invoked in process context.
+ */
+void revocable_withdraw_access(struct revocable_consumer *rev)
+ __releases(&rev->rp->srcu)
+{
+ struct revocable *rp = rev->rp;
+
+ srcu_read_unlock(&rp->srcu, rev->idx);
+}
+EXPORT_SYMBOL_GPL(revocable_withdraw_access);
diff --git a/include/linux/revocable.h b/include/linux/revocable.h
new file mode 100644
index 000000000000..90a03b80db49
--- /dev/null
+++ b/include/linux/revocable.h
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2026 Google LLC
+ */
+
+#ifndef __LINUX_REVOCABLE_H
+#define __LINUX_REVOCABLE_H
+
+#include <linux/cleanup.h>
+#include <linux/compiler.h>
+#include <linux/kref.h>
+#include <linux/srcu.h>
+
+/**
+ * struct revocable - A handle for resource provider.
+ * @srcu: The SRCU to protect the resource.
+ * @res: The pointer of resource. It can point to anything.
+ * @kref: The refcount for this handle.
+ */
+struct revocable {
+ struct srcu_struct srcu;
+ void __rcu *res;
+ struct kref kref;
+};
+
+/**
+ * struct revocable_consumer - A handle for resource consumer.
+ * @rp: The pointer of resource provider.
+ * @idx: The index for the SRCU critical section.
+ */
+struct revocable_consumer {
+ struct revocable *rp;
+ int idx;
+};
+
+void revocable_get(struct revocable *rp);
+void revocable_put(struct revocable *rp);
+struct revocable *revocable_alloc(void *res);
+void revocable_revoke(struct revocable *rp);
+void revocable_init(struct revocable *rp, struct revocable_consumer *rev);
+void revocable_deinit(struct revocable_consumer *rev);
+void *revocable_try_access(struct revocable_consumer *rev)
+ __acquires(&rev->rp->srcu);
+void revocable_withdraw_access(struct revocable_consumer *rev)
+ __releases(&rev->rp->srcu);
+
+DEFINE_FREE(access_rev, struct revocable_consumer *, {
+ revocable_withdraw_access(_T);
+ revocable_deinit(_T);
+})
+
+#define _revocable_try_access_with(_rp, _rev, _res) \
+ struct revocable_consumer _rev; \
+ struct revocable_consumer *__UNIQUE_ID(name) __free(access_rev) = &_rev;\
+ revocable_init(_rp, &_rev); \
+ _res = revocable_try_access(&_rev)
+
+/**
+ * revocable_try_access_with() - A helper for accessing revocable resource
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * The macro simplifies the access-release cycle for consumers, ensuring that
+ * corresponding revocable_withdraw_access() and revocable_deinit() are called,
+ * even in the case of an early exit.
+ *
+ * It creates a local variable in the current scope. @_res is populated with
+ * the result of revocable_try_access(). Callers **must** check if @_res is
+ * ``NULL`` before using it. The revocable_withdraw_access() function is
+ * automatically called when the scope is exited.
+ *
+ * Note: It shares the same issue with guard() in cleanup.h. No goto statements
+ * are allowed before the helper. Otherwise, the compiler fails with
+ * "jump bypasses initialization of variable with __attribute__((cleanup))".
+ */
+#define revocable_try_access_with(_rp, _res) \
+ _revocable_try_access_with(_rp, __UNIQUE_ID(name), _res)
+
+/**
+ * revocable_try_access_or_return_err() - Variant of revocable_try_access_with()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ * @_err: The error code to return if resource is revoked.
+ *
+ * Similar to revocable_try_access_with() but returns from the current function
+ * with @_err if the resource is revoked. Callers don't need to check @_res for
+ * ``NULL`` as this handles the revocation case by returning early.
+ */
+#define revocable_try_access_or_return_err(_rp, _res, _err) \
+ _revocable_try_access_with(_rp, __UNIQUE_ID(name), _res); \
+ if (!_res) \
+ return _err
+
+/**
+ * revocable_try_access_or_return() - Variant of revocable_try_access_with()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_or_return_err() but returns -ENODEV if the
+ * resource is revoked.
+ */
+#define revocable_try_access_or_return(_rp, _res) \
+ revocable_try_access_or_return_err(_rp, _res, -ENODEV)
+
+/**
+ * revocable_try_access_or_return_void() - Variant of revocable_try_access_with()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_or_return_err() but returns void if the
+ * resource is revoked.
+ */
+#define revocable_try_access_or_return_void(_rp, _res) \
+ revocable_try_access_or_return_err(_rp, _res, )
+
+#define _revocable_try_access_with_scoped(_rp, _rev, _label, _res) \
+ for (struct revocable_consumer _rev, \
+ *__UNIQUE_ID(name) __free(access_rev) = &_rev; \
+ ({ revocable_init(_rp, &_rev); \
+ _res = revocable_try_access(&_rev); \
+ true; }); \
+ ({ goto _label; })) \
+ if (0) { \
+_label: \
+ break; \
+ } else
+
+/**
+ * revocable_try_access_with_scoped() - Variant of revocable_try_access_with()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_with() but with an explicit scope from a
+ * temporary ``for`` loop.
+ */
+#define revocable_try_access_with_scoped(_rp, _res) \
+ _revocable_try_access_with_scoped(_rp, __UNIQUE_ID(name), \
+ __UNIQUE_ID(label), _res)
+
+/**
+ * revocable_try_access_or_return_err_scoped() - Variant of revocable_try_access_with_scoped()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ * @_err: The error code to return if resource is revoked.
+ *
+ * Similar to revocable_try_access_with_scoped() but returns from the current
+ * function with @_err if the resource is revoked. Callers don't need to check
+ * @_res for ``NULL`` as this handles the revocation case by returning early.
+ */
+#define revocable_try_access_or_return_err_scoped(_rp, _res, _err) \
+ _revocable_try_access_with_scoped(_rp, __UNIQUE_ID(name), \
+ __UNIQUE_ID(label), _res) \
+ if (!_res) { \
+ return _err; \
+ } else
+
+/**
+ * revocable_try_access_or_return_scoped() - Variant of revocable_try_access_with_scoped()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_or_return_err_scoped() but returns -ENODEV
+ * if the resource is revoked.
+ */
+#define revocable_try_access_or_return_scoped(_rp, _res) \
+ revocable_try_access_or_return_err_scoped(_rp, _res, -ENODEV)
+
+/**
+ * revocable_try_access_or_return_void_scoped() - Variant of revocable_try_access_with_scoped()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_or_return_err_scoped() but returns void
+ * if the resource is revoked.
+ */
+#define revocable_try_access_or_return_void_scoped(_rp, _res) \
+ revocable_try_access_or_return_err_scoped(_rp, _res, )
+
+/**
+ * revocable_try_access_or_skip_scoped() - Variant of revocable_try_access_with_scoped()
+ * @_rp: The pointer of resource provider.
+ * @_res: A pointer variable that will be assigned the resource.
+ *
+ * Similar to revocable_try_access_with_scoped() but skips the following code
+ * block if the resource is revoked.
+ */
+#define revocable_try_access_or_skip_scoped(_rp, _res) \
+ _revocable_try_access_with_scoped(_rp, __UNIQUE_ID(name), \
+ __UNIQUE_ID(label), _res) \
+ if (!_res) { \
+ break; \
+ } else
+
+#endif /* __LINUX_REVOCABLE_H */
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v8 2/3] revocable: Add KUnit test cases
2026-02-13 9:23 [PATCH v8 0/3] drivers/base: Introduce revocable Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 1/3] revocable: Revocable resource management Tzung-Bi Shih
@ 2026-02-13 9:23 ` Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 3/3] selftests: revocable: Add kselftest cases Tzung-Bi Shih
2 siblings, 0 replies; 6+ messages in thread
From: Tzung-Bi Shih @ 2026-02-13 9:23 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Bartosz Golaszewski, Wolfram Sang, Jason Gunthorpe, Johan Hovold,
Paul E . McKenney, Dan Williams, Linus Walleij, chrome-platform,
tzungbi, driver-core, linux-doc, linux-kernel, linux-kselftest
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 <tzungbi@kernel.org>
---
v8:
- 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/chrome-platform/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/chrome-platform/20251016054204.1523139-3-tzungbi@kernel.org
- No changes.
v4: https://lore.kernel.org/chrome-platform/20250923075302.591026-3-tzungbi@kernel.org
- REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
- revocable_release() -> revocable_withdraw_access().
v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-3-tzungbi@kernel.org
- No changes.
v2: https://lore.kernel.org/chrome-platform/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 | 410 ++++++++++++++++++++++++++++++++++
4 files changed, 419 insertions(+)
create mode 100644 drivers/base/revocable_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 01670bc4db8d..6ce7a5477f25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22414,6 +22414,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 1786d87b29e2..5c5dad850380 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -250,3 +250,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..4c097a843342
--- /dev/null
+++ b/drivers/base/revocable_test.c
@@ -0,0 +1,410 @@
+// 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 <kunit/test.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/refcount.h>
+#include <linux/revocable.h>
+
+static int get_refcount(struct revocable *rp)
+{
+ return refcount_read(&rp->kref.refcount);
+}
+
+static void revocable_test_basic(struct kunit *test)
+{
+ struct revocable *rp;
+ struct revocable_consumer rev;
+ void *real_res = (void *)0x12345678, *res;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_init(rp, &rev);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+
+ res = revocable_try_access(&rev);
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ revocable_withdraw_access(&rev);
+
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ revocable_deinit(&rev);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+ revocable_put(rp);
+}
+
+static void revocable_test_revocation(struct kunit *test)
+{
+ struct revocable *rp;
+ struct revocable_consumer rev;
+ void *real_res = (void *)0x12345678, *res;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_init(rp, &rev);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+
+ res = revocable_try_access(&rev);
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ revocable_withdraw_access(&rev);
+
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ res = revocable_try_access(&rev);
+ KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+ revocable_withdraw_access(&rev);
+
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+ revocable_deinit(&rev);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+ revocable_put(rp);
+}
+
+static void revocable_test_try_access_macro1(struct kunit *test)
+{
+ struct revocable *rp;
+ void *real_res = (void *)0x12345678, *res;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ {
+ revocable_try_access_with(rp, res);
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ }
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ {
+ revocable_try_access_with(rp, res);
+ KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+ }
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ revocable_put(rp);
+}
+
+static int call_revocable_try_access_or_return_err(struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return_err(rp, res, -ENXIO);
+ return 0;
+}
+
+static int call_revocable_try_access_or_return(struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return(rp, res);
+ return 0;
+}
+
+static void call_revocable_try_access_or_return_void(struct kunit *test,
+ struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return_void(rp, res);
+ KUNIT_FAIL(test, "unreachable");
+}
+
+static void revocable_test_try_access_macro2(struct kunit *test)
+{
+ struct revocable *rp;
+ void *real_res = (void *)0x12345678, *res;
+ int ret;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ {
+ revocable_try_access_with(rp, res);
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ }
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ ret = call_revocable_try_access_or_return_err(rp);
+ KUNIT_EXPECT_EQ(test, ret, -ENXIO);
+
+ ret = call_revocable_try_access_or_return(rp);
+ KUNIT_EXPECT_EQ(test, ret, -ENODEV);
+
+ call_revocable_try_access_or_return_void(test, rp);
+
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+ revocable_put(rp);
+}
+
+static void revocable_test_try_access_macro3(struct kunit *test)
+{
+ struct revocable *rp;
+ void *real_res = (void *)0x12345678, *res;
+ bool accessed;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ accessed = false;
+ revocable_try_access_with_scoped(rp, res) {
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ accessed = true;
+ }
+ KUNIT_EXPECT_TRUE(test, accessed);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ accessed = false;
+ revocable_try_access_with_scoped(rp, res) {
+ KUNIT_EXPECT_PTR_EQ(test, res, NULL);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+ accessed = true;
+ }
+ KUNIT_EXPECT_TRUE(test, accessed);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ revocable_put(rp);
+}
+
+static int call_revocable_try_access_or_return_err_scoped(struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return_err_scoped(rp, res, -ENXIO) {}
+ return 0;
+}
+
+static int call_revocable_try_access_or_return_scoped(struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return_scoped(rp, res) {}
+ return 0;
+}
+
+static void call_revocable_try_access_or_return_void_scoped(struct kunit *test,
+ struct revocable *rp)
+{
+ void *res;
+
+ revocable_try_access_or_return_void_scoped(rp, res) {}
+ KUNIT_FAIL(test, "unreachable");
+}
+
+static void revocable_test_try_access_macro4(struct kunit *test)
+{
+ struct revocable *rp;
+ void *real_res = (void *)0x12345678, *res;
+ bool accessed;
+ int ret;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ accessed = false;
+ revocable_try_access_with_scoped(rp, res) {
+ KUNIT_EXPECT_PTR_EQ(test, res, real_res);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 3);
+ accessed = true;
+ }
+ KUNIT_EXPECT_TRUE(test, accessed);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ revocable_revoke(rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ ret = call_revocable_try_access_or_return_err_scoped(rp);
+ KUNIT_EXPECT_EQ(test, ret, -ENXIO);
+
+ ret = call_revocable_try_access_or_return_scoped(rp);
+ KUNIT_EXPECT_EQ(test, ret, -ENODEV);
+
+ call_revocable_try_access_or_return_void_scoped(test, rp);
+
+ accessed = false;
+ revocable_try_access_or_skip_scoped(rp, res)
+ accessed = true;
+ KUNIT_EXPECT_FALSE(test, accessed);
+
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+ revocable_put(rp);
+}
+
+struct test_concurrent_access_context {
+ struct completion started, enter;
+ struct task_struct *thread;
+
+ union {
+ /* Used by test provider. */
+ struct revocable *rp;
+
+ /* Used by test consumer. */
+ struct {
+ struct completion exit;
+ struct revocable_consumer rev;
+ 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->rp);
+
+ 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->rev);
+ KUNIT_EXPECT_PTR_EQ(ctx->test, res, ctx->expected_res);
+
+ wait_for_completion(&ctx->exit);
+ revocable_withdraw_access(&ctx->rev);
+
+ return 0;
+}
+
+static void revocable_test_concurrent_access(struct kunit *test)
+{
+ struct revocable *rp;
+ void *real_res = (void *)0x12345678;
+ struct test_concurrent_access_context *ctx;
+ int i;
+
+ rp = revocable_alloc(real_res);
+ KUNIT_ASSERT_NOT_NULL(test, rp);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 2);
+
+ 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].rp = rp;
+ ctx[i].thread = kthread_run(
+ test_concurrent_access_provider, ctx + i,
+ "revocable_%d", i);
+ } else {
+ init_completion(&ctx[i].exit);
+ revocable_init(rp, &ctx[i].rev);
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 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(rp), 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].rev);
+ }
+
+ kthread_stop(ctx[0].thread);
+ /* provider0 exits as all readers exit their critical section. */
+ KUNIT_EXPECT_EQ(test, get_refcount(rp), 1);
+
+ /* Drop the caller reference. */
+ revocable_put(rp);
+}
+
+static struct kunit_case revocable_test_cases[] = {
+ KUNIT_CASE(revocable_test_basic),
+ KUNIT_CASE(revocable_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 <tzungbi@kernel.org>");
+MODULE_DESCRIPTION("KUnit tests for the revocable API");
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v8 3/3] selftests: revocable: Add kselftest cases
2026-02-13 9:23 [PATCH v8 0/3] drivers/base: Introduce revocable Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 1/3] revocable: Revocable resource management Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 2/3] revocable: Add KUnit test cases Tzung-Bi Shih
@ 2026-02-13 9:23 ` Tzung-Bi Shih
2 siblings, 0 replies; 6+ messages in thread
From: Tzung-Bi Shih @ 2026-02-13 9:23 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Bartosz Golaszewski, Wolfram Sang, Jason Gunthorpe, Johan Hovold,
Paul E . McKenney, Dan Williams, Linus Walleij, chrome-platform,
tzungbi, driver-core, linux-doc, linux-kernel, linux-kselftest
Add kselftest cases for the revocable API.
The test consists of three parts:
- A kernel module (revocable_test.ko) that creates a debugfs file.
- A user-space C program (revocable_test) that uses the kselftest
harness to interact with the debugfs file.
- An orchestrating shell script (test-revocable.sh) that loads the
module, runs the C program, and unloads the module.
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.
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v8:
- Squash:
- 4d7dc4d1a62d revocable: Fix races in revocable_alloc() using RCU
- 377563ce0653 revocable: fix SRCU index corruption by requiring caller-provided storage
- Change accordingly due to its dependency "revocable: Revocable resource
management" changes.
- Move:
- tools/testing/selftests/drivers/base/revocable/ ->
tools/testing/selftests/revocable/.
v7: https://lore.kernel.org/all/20260116080235.350305-4-tzungbi@kernel.org
- "2025" -> "2026" in copyright.
- Rename the test name "macro" -> "try_access_macro".
v6: https://lore.kernel.org/chrome-platform/20251106152330.11733-4-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/chrome-platform/20251016054204.1523139-4-tzungbi@kernel.org
- No changes.
v4: https://lore.kernel.org/chrome-platform/20250923075302.591026-4-tzungbi@kernel.org
- REVOCABLE() -> REVOCABLE_TRY_ACCESS_WITH().
- revocable_release() -> revocable_withdraw_access().
v3: https://lore.kernel.org/chrome-platform/20250912081718.3827390-4-tzungbi@kernel.org
- No changes.
v2: https://lore.kernel.org/chrome-platform/20250820081645.847919-4-tzungbi@kernel.org
- New in the series.
MAINTAINERS | 1 +
tools/testing/selftests/revocable/Makefile | 7 +
.../selftests/revocable/revocable_test.c | 177 +++++++++++++
.../selftests/revocable/revocable_test.h | 20 ++
.../selftests/revocable/test-revocable.sh | 34 +++
.../selftests/revocable/test_modules/Makefile | 10 +
.../revocable/test_modules/revocable_test.c | 234 ++++++++++++++++++
7 files changed, 483 insertions(+)
create mode 100644 tools/testing/selftests/revocable/Makefile
create mode 100644 tools/testing/selftests/revocable/revocable_test.c
create mode 100644 tools/testing/selftests/revocable/revocable_test.h
create mode 100755 tools/testing/selftests/revocable/test-revocable.sh
create mode 100644 tools/testing/selftests/revocable/test_modules/Makefile
create mode 100644 tools/testing/selftests/revocable/test_modules/revocable_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6ce7a5477f25..76816c741017 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22416,6 +22416,7 @@ 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
+F: tools/testing/selftests/revocable/
RFKILL
M: Johannes Berg <johannes@sipsolutions.net>
diff --git a/tools/testing/selftests/revocable/Makefile b/tools/testing/selftests/revocable/Makefile
new file mode 100644
index 000000000000..a986ad50200a
--- /dev/null
+++ b/tools/testing/selftests/revocable/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_MODS_DIR := test_modules
+TEST_GEN_PROGS_EXTENDED := revocable_test
+TEST_PROGS := test-revocable.sh
+
+include ../lib.mk
diff --git a/tools/testing/selftests/revocable/revocable_test.c b/tools/testing/selftests/revocable/revocable_test.c
new file mode 100644
index 000000000000..2e90de210d9c
--- /dev/null
+++ b/tools/testing/selftests/revocable/revocable_test.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 Google LLC
+ *
+ * A selftest 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "revocable_test.h"
+
+#include "../kselftest_harness.h"
+
+#define DEBUGFS_PATH "/sys/kernel/debug/revocable_test"
+
+FIXTURE(revocable_fixture) {
+ int fd;
+ char data[16];
+};
+
+FIXTURE_SETUP(revocable_fixture) {
+ int ret;
+
+ self->fd = open(DEBUGFS_PATH, O_RDWR);
+ ASSERT_NE(-1, self->fd)
+ TH_LOG("failed to open fd");
+}
+
+FIXTURE_TEARDOWN(revocable_fixture) {
+ close(self->fd);
+}
+
+/*
+ * ASSERT_* is only available in TEST or TEST_F block. Use
+ * macro for the helper.
+ */
+#define READ_TEST_DATA_MSG(_offset, _msg) \
+ do { \
+ int ret; \
+ \
+ ret = lseek(self->fd, _offset, SEEK_SET); \
+ ASSERT_NE(-1, ret) \
+ TH_LOG("failed to lseek"); \
+ \
+ ret = read(self->fd, self->data, sizeof(self->data)-1); \
+ ASSERT_NE(-1, ret) \
+ TH_LOG("failed to read test data" _msg); \
+ self->data[ret] = '\0'; \
+ } while (0)
+
+#define READ_TEST_DATA(_offset) \
+ READ_TEST_DATA_MSG(_offset, "")
+
+#define READ_TEST_DATA_ERR(_offset) \
+ do { \
+ int ret; \
+ \
+ ret = lseek(self->fd, _offset, SEEK_SET); \
+ ASSERT_NE(-1, ret) \
+ TH_LOG("failed to lseek"); \
+ \
+ ret = read(self->fd, self->data, sizeof(self->data)-1); \
+ EXPECT_EQ(-1, ret); \
+ } while (0)
+
+#define SIGNAL_RESOURCE_GONE() \
+ do { \
+ int ret; \
+ \
+ ret = write(self->fd, "", 0); \
+ ASSERT_NE(-1, ret) \
+ TH_LOG("failed to signal resource is gone"); \
+ } while (0)
+
+TEST_F(revocable_fixture, basic) {
+ READ_TEST_DATA(TEST_MAGIC_OFFSET_RAW);
+ EXPECT_STREQ(TEST_DATA, self->data);
+}
+
+TEST_F(revocable_fixture, revocation) {
+ const int offset = TEST_MAGIC_OFFSET_RAW;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_MSG(offset, " after resource gone");
+ EXPECT_STREQ(TEST_DATA_NULL, self->data);
+}
+
+TEST_F(revocable_fixture, try_access_macro1) {
+ const int offset = TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_MSG(offset, " after resource gone");
+ EXPECT_STREQ(TEST_DATA_NULL, self->data);
+}
+
+TEST_F(revocable_fixture, try_access_macro2) {
+ const int offset = TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_ERR(offset);
+ EXPECT_EQ(ENXIO, errno);
+}
+
+TEST_F(revocable_fixture, try_access_macro3) {
+ const int offset = TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_ERR(offset);
+ EXPECT_EQ(ENODEV, errno);
+}
+
+TEST_F(revocable_fixture, try_access_macro4) {
+ const int offset = TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH_SCOPED;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_MSG(offset, " after resource gone");
+ EXPECT_STREQ(TEST_DATA_NULL, self->data);
+}
+
+TEST_F(revocable_fixture, try_access_macro5) {
+ const int offset =
+ TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR_SCOPED;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_ERR(offset);
+ EXPECT_EQ(ENXIO, errno);
+}
+
+TEST_F(revocable_fixture, try_access_macro6) {
+ const int offset = TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_SCOPED;
+
+ READ_TEST_DATA(offset);
+ EXPECT_STREQ(TEST_DATA, self->data);
+
+ SIGNAL_RESOURCE_GONE();
+
+ READ_TEST_DATA_ERR(offset);
+ EXPECT_EQ(ENODEV, errno);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/revocable/revocable_test.h b/tools/testing/selftests/revocable/revocable_test.h
new file mode 100644
index 000000000000..270b456ef7d9
--- /dev/null
+++ b/tools/testing/selftests/revocable/revocable_test.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2026 Google LLC
+ */
+
+#ifndef __REVOCABLE_TEST_H
+#define __REVOCABLE_TEST_H
+
+#define TEST_DATA "12345678"
+#define TEST_DATA_NULL "(null)"
+
+#define TEST_MAGIC_OFFSET_RAW 0x0
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH 0x1
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR 0x2
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN 0x3
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH_SCOPED 0x4
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR_SCOPED 0x5
+#define TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_SCOPED 0x6
+
+#endif /* __REVOCABLE_TEST_H */
diff --git a/tools/testing/selftests/revocable/test-revocable.sh b/tools/testing/selftests/revocable/test-revocable.sh
new file mode 100755
index 000000000000..0cfc26a1c49a
--- /dev/null
+++ b/tools/testing/selftests/revocable/test-revocable.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+mod_name="revocable_test"
+ksft_fail=1
+ksft_skip=4
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo "$0: Must be run as root"
+ exit "$ksft_skip"
+fi
+
+if ! which insmod > /dev/null 2>&1; then
+ echo "$0: Need insmod"
+ exit "$ksft_skip"
+fi
+
+if ! which rmmod > /dev/null 2>&1; then
+ echo "$0: Need rmmod"
+ exit "$ksft_skip"
+fi
+
+if ! mountpoint -q /sys/kernel/debug; then
+ mount -t debugfs none /sys/kernel/debug
+fi
+
+insmod test_modules/"${mod_name}".ko
+
+./revocable_test
+ret=$?
+
+rmmod "${mod_name}"
+
+exit "${ret}"
diff --git a/tools/testing/selftests/revocable/test_modules/Makefile b/tools/testing/selftests/revocable/test_modules/Makefile
new file mode 100644
index 000000000000..f29e4f909402
--- /dev/null
+++ b/tools/testing/selftests/revocable/test_modules/Makefile
@@ -0,0 +1,10 @@
+TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= /lib/modules/$(shell uname -r)/build
+
+obj-m += revocable_test.o
+
+all:
+ $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR)
+
+clean:
+ $(Q)$(MAKE) -C $(KDIR) M=$(TESTMODS_DIR) clean
diff --git a/tools/testing/selftests/revocable/test_modules/revocable_test.c b/tools/testing/selftests/revocable/test_modules/revocable_test.c
new file mode 100644
index 000000000000..b2914c7b4ef5
--- /dev/null
+++ b/tools/testing/selftests/revocable/test_modules/revocable_test.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 Google LLC
+ *
+ * A kernel module for testing the revocable API.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/revocable.h>
+#include <linux/slab.h>
+
+#include "../revocable_test.h"
+
+struct dentry *test_file;
+
+struct revocable_test_priv {
+ struct revocable *rp;
+ char res[16];
+};
+
+/*
+ * This creates a revocable provider.
+ */
+static int revocable_test_open(struct inode *inode, struct file *filp)
+{
+ struct revocable_test_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ strscpy(priv->res, TEST_DATA);
+ priv->rp = revocable_alloc(&priv->res);
+ if (!priv->rp)
+ return -ENOMEM;
+
+ filp->private_data = priv;
+ return 0;
+}
+
+static int revocable_test_release(struct inode *inode,
+ struct file *filp)
+{
+ struct revocable_test_priv *priv = filp->private_data;
+ bool revoked = true;
+ void *res;
+
+ revocable_try_access_or_skip_scoped(priv->rp, res)
+ revoked = false;
+ if (!revoked)
+ revocable_revoke(priv->rp);
+
+ revocable_put(priv->rp);
+
+ kfree(priv);
+ return 0;
+}
+
+/*
+ * This revokes the resource. Here is a side command channel.
+ *
+ * The test can't just close the file descriptor for signaling the
+ * resource is gone. Subsequent file operations on the open file
+ * descriptor of debugfs return -EIO after calling debugfs_remove().
+ * See also debugfs_file_get().
+ */
+static ssize_t revocable_test_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ size_t copied;
+ char data[64];
+ struct revocable_test_priv *priv = filp->private_data;
+ bool revoked = true;
+ void *res;
+
+ revocable_try_access_or_skip_scoped(priv->rp, res)
+ revoked = false;
+ if (revoked)
+ return -EINVAL;
+
+ copied = strncpy_from_user(data, buf, sizeof(data));
+ if (copied < 0)
+ return copied;
+
+ revocable_revoke(priv->rp);
+ return copied;
+}
+
+static void copy_resource_data(char *data, size_t len, char *res)
+{
+ if (!res)
+ strscpy(data, TEST_DATA_NULL, len);
+ else
+ strscpy(data, res, len);
+}
+
+static int call_revocable_try_access_or_return_err(struct revocable *rp,
+ char *data, size_t len)
+{
+ char *res;
+
+ revocable_try_access_or_return_err(rp, res, -ENXIO);
+ copy_resource_data(data, len, res);
+ return 0;
+}
+
+static int call_revocable_try_access_or_return(struct revocable *rp,
+ char *data, size_t len)
+{
+ char *res;
+
+ revocable_try_access_or_return(rp, res);
+ copy_resource_data(data, len, res);
+ return 0;
+}
+
+static int call_revocable_try_access_or_return_err_scoped(struct revocable *rp,
+ char *data,
+ size_t len)
+{
+ char *res;
+
+ revocable_try_access_or_return_err_scoped(rp, res, -ENXIO)
+ copy_resource_data(data, len, res);
+ return 0;
+}
+
+static int call_revocable_try_access_or_return_scoped(struct revocable *rp,
+ char *data, size_t len)
+{
+ char *res;
+
+ revocable_try_access_or_return_scoped(rp, res)
+ copy_resource_data(data, len, res);
+ return 0;
+}
+
+/*
+ * This creates a revocable consumer and returns the resource value.
+ */
+static ssize_t revocable_test_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ char *res;
+ char data[16];
+ size_t len;
+ int ret;
+ struct revocable_test_priv *priv = filp->private_data;
+
+ switch (*offset) {
+ case TEST_MAGIC_OFFSET_RAW:
+ {
+ struct revocable_consumer rev;
+
+ revocable_init(priv->rp, &rev);
+ res = revocable_try_access(&rev);
+ copy_resource_data(data, sizeof(data), res);
+ revocable_withdraw_access(&rev);
+ revocable_deinit(&rev);
+ }
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH:
+ {
+ revocable_try_access_with(priv->rp, res);
+ copy_resource_data(data, sizeof(data), res);
+ }
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR:
+ ret = call_revocable_try_access_or_return_err(priv->rp, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN:
+ ret = call_revocable_try_access_or_return(priv->rp, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_WITH_SCOPED:
+ revocable_try_access_with_scoped(priv->rp, res)
+ copy_resource_data(data, sizeof(data), res);
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_ERR_SCOPED:
+ ret = call_revocable_try_access_or_return_err_scoped(
+ priv->rp, data, sizeof(data));
+ if (ret)
+ return ret;
+ break;
+ case TEST_MAGIC_OFFSET_MACRO_TRY_ACCESS_OR_RETURN_SCOPED:
+ ret = call_revocable_try_access_or_return_scoped(
+ priv->rp, data, sizeof(data));
+ if (ret)
+ return ret;
+ break;
+ default:
+ return 0;
+ }
+
+ len = min_t(size_t, strlen(data), count);
+ if (copy_to_user(buf, data, len))
+ return -EFAULT;
+
+ *offset = len;
+ return len;
+}
+
+static const struct file_operations revocable_test_fops = {
+ .open = revocable_test_open,
+ .release = revocable_test_release,
+ .write = revocable_test_write,
+ .read = revocable_test_read,
+ .llseek = default_llseek,
+};
+
+static int __init revocable_test_init(void)
+{
+ test_file = debugfs_create_file("revocable_test", 0600, NULL, NULL,
+ &revocable_test_fops);
+ return test_file ? 0 : -ENOMEM;
+}
+
+static void __exit revocable_test_exit(void)
+{
+ debugfs_remove(test_file);
+}
+
+module_init(revocable_test_init);
+module_exit(revocable_test_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tzung-Bi Shih <tzungbi@kernel.org>");
+MODULE_DESCRIPTION("Revocable Kselftest");
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v8 1/3] revocable: Revocable resource management
2026-02-13 9:23 ` [PATCH v8 1/3] revocable: Revocable resource management Tzung-Bi Shih
@ 2026-02-16 16:37 ` Bartosz Golaszewski
2026-02-23 7:03 ` Tzung-Bi Shih
0 siblings, 1 reply; 6+ messages in thread
From: Bartosz Golaszewski @ 2026-02-16 16:37 UTC (permalink / raw)
To: Tzung-Bi Shih
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart,
Bartosz Golaszewski, Wolfram Sang, Jason Gunthorpe, Johan Hovold,
Paul E . McKenney, Dan Williams, Linus Walleij, chrome-platform,
driver-core, linux-doc, linux-kernel, linux-kselftest,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
On Fri, 13 Feb 2026 10:23:05 +0100, Tzung-Bi Shih <tzungbi@kernel.org> said:
> The "revocable" mechanism is a synchronization primitive designed to
> manage safe access to resources that can be asynchronously removed or
> invalidated. Its primary purpose is to prevent Use-After-Free (UAF)
> errors when interacting with resources whose lifetimes are not
> guaranteed to outlast their consumers.
>
[snip]
> diff --git a/include/linux/revocable.h b/include/linux/revocable.h
> +
[snip]
> +/**
> + * struct revocable_consumer - A handle for resource consumer.
> + * @rp: The pointer of resource provider.
> + * @idx: The index for the SRCU critical section.
> + */
> +struct revocable_consumer {
> + struct revocable *rp;
> + int idx;
> +};
> +
> +void revocable_get(struct revocable *rp);
> +void revocable_put(struct revocable *rp);
> +struct revocable *revocable_alloc(void *res);
Is there any reason why we couldn't do:
int revocable_init(struct revocable *rp, void *res);
and then:
struct gpio_device {
struct revocable rp;
...
};
revocable_init(&gdev->rp, gc);
? It would be functionally equivalent to what we're doing now in GPIO.
Bartosz
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v8 1/3] revocable: Revocable resource management
2026-02-16 16:37 ` Bartosz Golaszewski
@ 2026-02-23 7:03 ` Tzung-Bi Shih
0 siblings, 0 replies; 6+ messages in thread
From: Tzung-Bi Shih @ 2026-02-23 7:03 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: Jonathan Corbet, Shuah Khan, Laurent Pinchart, Wolfram Sang,
Jason Gunthorpe, Johan Hovold, Paul E . McKenney, Dan Williams,
Linus Walleij, chrome-platform, driver-core, linux-doc,
linux-kernel, linux-kselftest, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich
On Mon, Feb 16, 2026 at 08:37:50AM -0800, Bartosz Golaszewski wrote:
> On Fri, 13 Feb 2026 10:23:05 +0100, Tzung-Bi Shih <tzungbi@kernel.org> said:
> > diff --git a/include/linux/revocable.h b/include/linux/revocable.h
> > +
>
> [snip]
>
> > +/**
> > + * struct revocable_consumer - A handle for resource consumer.
> > + * @rp: The pointer of resource provider.
> > + * @idx: The index for the SRCU critical section.
> > + */
> > +struct revocable_consumer {
> > + struct revocable *rp;
> > + int idx;
> > +};
> > +
> > +void revocable_get(struct revocable *rp);
> > +void revocable_put(struct revocable *rp);
> > +struct revocable *revocable_alloc(void *res);
>
> Is there any reason why we couldn't do:
>
> int revocable_init(struct revocable *rp, void *res);
>
> and then:
>
> struct gpio_device {
> struct revocable rp;
> ...
> };
>
> revocable_init(&gdev->rp, gc);
>
> ? It would be functionally equivalent to what we're doing now in GPIO.
In the case of struct gpio_device, it is possible to embed struct revocable
directly. Since the lifetime of gpio_device matches the revocable object,
the reference counting mechanism in struct revocable is redundant in this
specific instance.
However, the design allows for cases where there is no persistent parent
object. For example, a revocable handle may exist in the dev-managed
context of a kernel thread. In such cases, consumers are required to hold
a reference to the handle.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-02-23 7:03 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-13 9:23 [PATCH v8 0/3] drivers/base: Introduce revocable Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 1/3] revocable: Revocable resource management Tzung-Bi Shih
2026-02-16 16:37 ` Bartosz Golaszewski
2026-02-23 7:03 ` Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 2/3] revocable: Add KUnit test cases Tzung-Bi Shih
2026-02-13 9:23 ` [PATCH v8 3/3] selftests: revocable: Add kselftest cases Tzung-Bi Shih
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox