[PATCH v14 5/5] reset: Leverage revocable for accessing struct reset_controller_dev
From: Tzung-Bi Shih
Date: Sun Oct 11 2026 - 08:15:49 EST
Commit 78ebbff6d1a0 ("reset: handle removing supplier before consumers")
protected the `rcdev` pointer in `struct reset_control` with SRCU so
that the reset core can handle suppliers unbinding while consumers still
hold reset control handles.
Because `struct reset_control` handles can outlive `rcdev`, each of the
N handles had to embed its own `srcu_struct` and call synchronize_srcu()
on release, and reset_controller_unregister() had to call
synchronize_srcu() N times in a loop.
Leverage dynamic revocable to allocate a single reference-counted
`struct revocable` for `rcdev` shared across all N `struct reset_control`
handles (1-to-N). This reduces N `srcu_struct` instances per controller
to 1, drops synchronize_srcu() from reset_control_put_internal(), and
replaces the N synchronize_srcu() calls on unregistration with a single
revocable_revoke().
In addition, calling revocable_revoke() outside `rcdev->lock` avoids an
ABBA lock inversion between `rcdev->lock` and SRCU, as
reset_control_acquire() and reset_control_put_internal() acquire
`rcdev->lock` inside the SRCU read-side critical section.
Signed-off-by: Tzung-Bi Shih <tzungbi@xxxxxxxxxx>
---
v14:
- New to the series.
---
drivers/reset/core.c | 93 ++++++++------------------------
include/linux/reset-controller.h | 3 ++
2 files changed, 26 insertions(+), 70 deletions(-)
diff --git a/drivers/reset/core.c b/drivers/reset/core.c
index 38e189d04d09..788a235c3965 100644
--- a/drivers/reset/core.c
+++ b/drivers/reset/core.c
@@ -24,8 +24,8 @@
#include <linux/property.h>
#include <linux/reset.h>
#include <linux/reset-controller.h>
+#include <linux/revocable.h>
#include <linux/slab.h>
-#include <linux/srcu.h>
static DEFINE_MUTEX(reset_list_mutex);
static LIST_HEAD(reset_controller_list);
@@ -37,9 +37,7 @@ static DEFINE_IDA(reset_gpio_ida);
/**
* struct reset_control - a reset control
- * @rcdev: a pointer to the reset controller device
- * this reset control belongs to
- * @srcu: protects the rcdev pointer from removal during consumer access
+ * @rev: revocable handle for accessing the reset controller device
* @list: list entry for the rcdev's reset controller list
* @id: ID of the reset controller in the reset
* controller device
@@ -54,8 +52,7 @@ static DEFINE_IDA(reset_gpio_ida);
* @lock: serializes the internals of reset_control_acquire()
*/
struct reset_control {
- struct reset_controller_dev __rcu *rcdev;
- struct srcu_struct srcu;
+ struct revocable *rev;
struct list_head list;
unsigned int id;
struct kref refcnt;
@@ -141,6 +138,10 @@ int reset_controller_register(struct reset_controller_dev *rcdev)
return -EINVAL;
}
+ rcdev->rev = revocable_alloc(rcdev);
+ if (!rcdev->rev)
+ return -ENOMEM;
+
if (rcdev->of_xlate)
rcdev->fwnode_reset_n_cells = rcdev->of_reset_n_cells;
@@ -181,18 +182,14 @@ void reset_controller_unregister(struct reset_controller_dev *rcdev)
scoped_guard(mutex, &reset_list_mutex)
list_del(&rcdev->list);
+ revocable_revoke(rcdev->rev);
+
scoped_guard(mutex, &rcdev->lock) {
- /*
- * Numb but don't free the remaining reset control handles that are
- * still held by consumers.
- */
- list_for_each_entry_safe(rstc, pos, &rcdev->reset_control_head, list) {
- rcu_assign_pointer(rstc->rcdev, NULL);
- synchronize_srcu(&rstc->srcu);
+ list_for_each_entry_safe(rstc, pos, &rcdev->reset_control_head, list)
reset_controller_remove(rcdev, rstc);
- }
}
+ revocable_put(rcdev->rev);
mutex_destroy(&rcdev->lock);
}
EXPORT_SYMBOL_GPL(reset_controller_unregister);
@@ -382,11 +379,7 @@ int reset_control_reset(struct reset_control *rstc)
if (reset_control_is_array(rstc))
return reset_control_array_reset(rstc_to_array(rstc));
- guard(srcu)(&rstc->srcu);
-
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- return -ENODEV;
+ revocable_try_access_or_return(rstc->rev, rcdev);
if (!rcdev->ops->reset)
return -ENOTSUPP;
@@ -503,11 +496,7 @@ int reset_control_assert(struct reset_control *rstc)
if (reset_control_is_array(rstc))
return reset_control_array_assert(rstc_to_array(rstc));
- guard(srcu)(&rstc->srcu);
-
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- return -ENODEV;
+ revocable_try_access_or_return(rstc->rev, rcdev);
if (rstc->shared) {
if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
@@ -599,11 +588,7 @@ int reset_control_deassert(struct reset_control *rstc)
if (reset_control_is_array(rstc))
return reset_control_array_deassert(rstc_to_array(rstc));
- guard(srcu)(&rstc->srcu);
-
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- return -ENODEV;
+ revocable_try_access_or_return(rstc->rev, rcdev);
if (rstc->shared) {
if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
@@ -679,11 +664,7 @@ int reset_control_status(struct reset_control *rstc)
if (WARN_ON(IS_ERR(rstc)) || reset_control_is_array(rstc))
return -EINVAL;
- guard(srcu)(&rstc->srcu);
-
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- return -ENODEV;
+ revocable_try_access_or_return(rstc->rev, rcdev);
if (rcdev->ops->status)
return rcdev->ops->status(rcdev, rstc->id);
@@ -731,11 +712,7 @@ int reset_control_acquire(struct reset_control *rstc)
if (rstc->acquired)
return 0;
- guard(srcu)(&rstc->srcu);
-
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- return -ENODEV;
+ revocable_try_access_or_return(rstc->rev, rcdev);
scoped_guard(mutex, &rcdev->lock) {
list_for_each_entry(rc, &rcdev->reset_control_head, list) {
@@ -831,7 +808,6 @@ __reset_control_get_internal(struct reset_controller_dev *rcdev,
bool shared = flags & RESET_CONTROL_FLAGS_BIT_SHARED;
bool acquired = flags & RESET_CONTROL_FLAGS_BIT_ACQUIRED;
struct reset_control *rstc;
- int ret;
lockdep_assert_held(&rcdev->lock);
@@ -862,19 +838,13 @@ __reset_control_get_internal(struct reset_controller_dev *rcdev,
if (!rstc)
return ERR_PTR(-ENOMEM);
- ret = init_srcu_struct(&rstc->srcu);
- if (ret) {
- kfree(rstc);
- return ERR_PTR(ret);
- }
-
if (!try_module_get(rcdev->owner)) {
- cleanup_srcu_struct(&rstc->srcu);
kfree(rstc);
return ERR_PTR(-ENODEV);
}
- rcu_assign_pointer(rstc->rcdev, rcdev);
+ revocable_get(rcdev->rev);
+ rstc->rev = rcdev->rev;
list_add(&rstc->list, &rcdev->reset_control_head);
rstc->id = index;
kref_init(&rstc->refcnt);
@@ -890,40 +860,23 @@ static void __reset_control_release(struct kref *kref)
{
struct reset_control *rstc = container_of(kref, struct reset_control,
refcnt);
- struct reset_controller_dev *rcdev;
-
- lockdep_assert_held(&rstc->srcu);
-
- rcdev = rcu_replace_pointer(rstc->rcdev, NULL, true);
- if (rcdev) {
- lockdep_assert_held(&rcdev->lock);
- reset_controller_remove(rcdev, rstc);
- }
mutex_destroy(&rstc->lock);
+ revocable_put(rstc->rev);
}
static void reset_control_put_internal(struct reset_control *rstc)
{
struct reset_controller_dev *rcdev;
- int ret = 0;
if (IS_ERR_OR_NULL(rstc))
return;
- scoped_guard(srcu, &rstc->srcu) {
- rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
- if (!rcdev)
- /* Already released. */
- return;
-
- guard(mutex)(&rcdev->lock);
- ret = kref_put(&rstc->refcnt, __reset_control_release);
- }
+ revocable_try_access_or_return_void(rstc->rev, rcdev);
- if (ret) {
- synchronize_srcu(&rstc->srcu);
- cleanup_srcu_struct(&rstc->srcu);
+ guard(mutex)(&rcdev->lock);
+ if (kref_put(&rstc->refcnt, __reset_control_release)) {
+ reset_controller_remove(rcdev, rstc);
kfree(rstc);
}
}
diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h
index 52a5a4e81f18..9abf5c59fd9a 100644
--- a/include/linux/reset-controller.h
+++ b/include/linux/reset-controller.h
@@ -28,6 +28,7 @@ struct reset_control_ops {
struct module;
struct device_node;
struct of_phandle_args;
+struct revocable;
/**
* struct reset_controller_dev - reset controller entity that might
@@ -48,6 +49,7 @@ struct of_phandle_args;
* :c:func:`fwnode_reset_simple_xlate`
* @nr_resets: number of reset controls in this reset controller device
* @lock: protects the reset control list from concurrent access
+ * @rev: revocable provider handle for itself
*/
struct reset_controller_dev {
const struct reset_control_ops *ops;
@@ -65,6 +67,7 @@ struct reset_controller_dev {
const struct fwnode_reference_args *reset_spec);
unsigned int nr_resets;
struct mutex lock;
+ struct revocable *rev;
};
#if IS_ENABLED(CONFIG_RESET_CONTROLLER)
--
2.53.0