mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/2] reset: Serialize shared resets whose ops may sleep
@ 2026-10-23  7:16 shengjiu.wang
  2026-10-23  7:16 ` [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets shengjiu.wang
  2026-10-23  7:16 ` [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization shengjiu.wang
  0 siblings, 2 replies; 5+ messages in thread
From: shengjiu.wang @ 2026-10-23  7:16 UTC (permalink / raw)
  To: p.zabel, krzk, hansg, linux-kernel, broonie, alvin.sipraga

From: Shengjiu Wang <shengjiu.wang@nxp.com>

For a shared reset only the first caller runs the hardware op; the others
bump the refcount and return. When the op can sleep, a concurrent caller can
return before the first caller's op has finished and then touch hardware
still in reset.

Serialize the shared refcount update and the hardware op under a new
per-control mutex, but only for providers that opt in via a new
reset_controller_dev::can_sleep flag. Providers that leave it clear keep the
lockless path, so atomic callers are unaffected. reset-gpio is the first
provider to set the flag.

Changes in v4:
- Move the can_sleep/mutex description to the top of the core commit message.
- Also serialize reset_control_assert() under shared_lock.
- Split the reset-gpio can_sleep assignment into its own patch (2/2).
- Fix a kerneldoc typo; drop the Assisted-by trailer.
- Drop the Fixes: tag, present as a correctness improvement.

Changes in v3:
- Drop the reset_control_reset_atomic()/reset_control_deassert_atomic()
  variants and their reset.h declarations/stubs; gate the mutex on
  rcdev->can_sleep instead, so non-sleeping controllers keep the lockless
  path and need no new API.
- Use a dedicated rstc->shared_lock taken inside the SRCU read-side section
  instead of reusing rstc->lock before SRCU.
- Set can_sleep in reset-gpio; no other provider is converted.
- Drop the lockdep_assert_not_held() additions and the reset.rst / kerneldoc
  text about sleeping-vs-atomic variants.
  
Changes in v2:
- Serialize only the shared may-sleep path under rstc->lock (taken before
  SRCU to match reset_control_acquire()'s order); exclusive path stays
  lockless, and reset_control_assert() is left unchanged.
- Add lockless reset_control_reset_atomic()/reset_control_deassert_atomic()
  for atomic callers; gate the mutex on can_sleep so no shared consumer is
  regressed and so it works on all configs.
- Factor the body into __reset_control_*()/__reset_control_*_locked()
  helpers, and lockdep_assert_not_held() at each lock site to catch a
  controller op re-entering the API on the same control.
- Declare the new variants and stubs in reset.h, and document the sleeping
  vs atomic variants in the kerneldoc and reset.rst.

Shengjiu Wang (2):
  reset: core: Serialize concurrent deassert/reset of shared resets
  reset: gpio: Set can_sleep for the shared reset serialization

 drivers/reset/core.c             | 205 ++++++++++++++++++++-----------
 drivers/reset/reset-gpio.c       |   1 +
 include/linux/reset-controller.h |   4 +
 3 files changed, 139 insertions(+), 71 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets
  2026-10-23  7:16 [PATCH v4 0/2] reset: Serialize shared resets whose ops may sleep shengjiu.wang
@ 2026-10-23  7:16 ` shengjiu.wang
  2026-10-23  8:42   ` Alvin Šipraga
  2026-10-23  7:16 ` [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization shengjiu.wang
  1 sibling, 1 reply; 5+ messages in thread
From: shengjiu.wang @ 2026-10-23  7:16 UTC (permalink / raw)
  To: p.zabel, krzk, hansg, linux-kernel, broonie, alvin.sipraga

From: Shengjiu Wang <shengjiu.wang@nxp.com>

Add a reset_controller_dev::can_sleep flag that a provider sets when its
reset ops may sleep. When it is set, serialize the shared refcount update
and the hardware op in reset_control_reset(), reset_control_deassert() and
reset_control_assert() under a new per-control mutex, taken inside the SRCU
read-side critical section. Providers that leave the flag clear keep the
existing lockless path, so callers in atomic context are unaffected.

reset_control_deassert() and reset_control_reset() are documented to
guarantee that the reset line has been deasserted, respectively that a
reset pulse has been issued, by the time they return.

For a shared reset only the first caller runs the hardware op; the other
callers just bump the refcount and return. When the op can sleep this
breaks the guarantee: a second caller can observe the bumped refcount and
return before the first caller's op has finished, and then touch hardware
that is still in reset. A reset_control_assert() racing a concurrent
reset_control_deassert() can likewise interleave the two sleeping ops and
leave the line asserted while the refcount says it is deasserted.

Suggested-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
Link: https://lore.kernel.org/all/aszDZlJ0KahNwgZO@analog.com/
---
 drivers/reset/core.c             | 205 ++++++++++++++++++++-----------
 include/linux/reset-controller.h |   4 +
 2 files changed, 138 insertions(+), 71 deletions(-)

diff --git a/drivers/reset/core.c b/drivers/reset/core.c
index 38e189d04d09..7eb4575413d6 100644
--- a/drivers/reset/core.c
+++ b/drivers/reset/core.c
@@ -52,6 +52,9 @@ static DEFINE_IDA(reset_gpio_ida);
  *                   only used for shared resets, which means that the value
  *                   will be either 0 or 1.
  * @lock: serializes the internals of reset_control_acquire()
+ * @shared_lock: serializes the shared refcount update and the hardware op for
+ *               controllers whose ops may sleep (rcdev->can_sleep); taken only
+ *               inside the SRCU read-side critical section
  */
 struct reset_control {
 	struct reset_controller_dev __rcu *rcdev;
@@ -65,6 +68,8 @@ struct reset_control {
 	atomic_t deassert_count;
 	atomic_t triggered_count;
 	struct mutex lock;
+	/* serializes the shared refcount update with its hardware op */
+	struct mutex shared_lock;
 };
 
 /**
@@ -355,6 +360,32 @@ static inline bool reset_control_is_array(struct reset_control *rstc)
 	return rstc->array;
 }
 
+static int __reset_control_reset(struct reset_control *rstc,
+				 struct reset_controller_dev *rcdev)
+{
+	int ret;
+
+	if (!rcdev->ops->reset)
+		return -ENOTSUPP;
+
+	if (rstc->shared) {
+		if (WARN_ON(atomic_read(&rstc->deassert_count) != 0))
+			return -EINVAL;
+
+		if (atomic_inc_return(&rstc->triggered_count) != 1)
+			return 0;
+	} else {
+		if (!rstc->acquired)
+			return -EPERM;
+	}
+
+	ret = rcdev->ops->reset(rcdev, rstc->id);
+	if (rstc->shared && ret)
+		atomic_dec(&rstc->triggered_count);
+
+	return ret;
+}
+
 /**
  * reset_control_reset - reset the controlled device
  * @rstc: reset controller
@@ -362,6 +393,10 @@ static inline bool reset_control_is_array(struct reset_control *rstc)
  * On a shared reset line the actual reset pulse is only triggered once for the
  * lifetime of the reset_control instance: for all but the first caller this is
  * a no-op.
+ *
+ * After calling this function, the reset line is guaranteed to have seen a
+ * reset pulse at any point in the past, not necessarily during the function
+ * call.
  * Consumers must not use reset_control_(de)assert on shared reset lines when
  * reset_control_reset has been used.
  *
@@ -371,7 +406,6 @@ static inline bool reset_control_is_array(struct reset_control *rstc)
 int reset_control_reset(struct reset_control *rstc)
 {
 	struct reset_controller_dev *rcdev;
-	int ret;
 
 	if (!rstc)
 		return 0;
@@ -388,25 +422,18 @@ int reset_control_reset(struct reset_control *rstc)
 	if (!rcdev)
 		return -ENODEV;
 
-	if (!rcdev->ops->reset)
-		return -ENOTSUPP;
-
-	if (rstc->shared) {
-		if (WARN_ON(atomic_read(&rstc->deassert_count) != 0))
-			return -EINVAL;
-
-		if (atomic_inc_return(&rstc->triggered_count) != 1)
-			return 0;
-	} else {
-		if (!rstc->acquired)
-			return -EPERM;
+	/*
+	 * For a shared reset only the first caller runs the hardware op while
+	 * concurrent callers bump the refcount and return. If the op may sleep
+	 * (rcdev->can_sleep), serialize the refcount update and the op so a
+	 * concurrent caller cannot return before the op has finished.
+	 */
+	if (rstc->shared && rcdev->can_sleep) {
+		guard(mutex)(&rstc->shared_lock);
+		return __reset_control_reset(rstc, rcdev);
 	}
 
-	ret = rcdev->ops->reset(rcdev, rstc->id);
-	if (rstc->shared && ret)
-		atomic_dec(&rstc->triggered_count);
-
-	return ret;
+	return __reset_control_reset(rstc, rcdev);
 }
 EXPORT_SYMBOL_GPL(reset_control_reset);
 
@@ -474,6 +501,44 @@ int reset_control_rearm(struct reset_control *rstc)
 }
 EXPORT_SYMBOL_GPL(reset_control_rearm);
 
+static int __reset_control_assert(struct reset_control *rstc,
+				  struct reset_controller_dev *rcdev)
+{
+	if (rstc->shared) {
+		if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
+			return -EINVAL;
+
+		if (WARN_ON(atomic_read(&rstc->deassert_count) == 0))
+			return -EINVAL;
+
+		if (atomic_dec_return(&rstc->deassert_count) != 0)
+			return 0;
+
+		/*
+		 * Shared reset controls allow the reset line to be in any state
+		 * after this call, so doing nothing is a valid option.
+		 */
+		if (!rcdev->ops->assert)
+			return 0;
+	} else {
+		/*
+		 * If the reset controller does not implement .assert(), there
+		 * is no way to guarantee that the reset line is asserted after
+		 * this call.
+		 */
+		if (!rcdev->ops->assert)
+			return -ENOTSUPP;
+
+		if (!rstc->acquired) {
+			WARN(1, "reset %s (ID: %u) is not acquired\n",
+			     rcdev_name(rcdev), rstc->id);
+			return -EPERM;
+		}
+	}
+
+	return rcdev->ops->assert(rcdev, rstc->id);
+}
+
 /**
  * reset_control_assert - asserts the reset line
  * @rstc: reset controller
@@ -509,39 +574,18 @@ int reset_control_assert(struct reset_control *rstc)
 	if (!rcdev)
 		return -ENODEV;
 
-	if (rstc->shared) {
-		if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
-			return -EINVAL;
-
-		if (WARN_ON(atomic_read(&rstc->deassert_count) == 0))
-			return -EINVAL;
-
-		if (atomic_dec_return(&rstc->deassert_count) != 0)
-			return 0;
-
-		/*
-		 * Shared reset controls allow the reset line to be in any state
-		 * after this call, so doing nothing is a valid option.
-		 */
-		if (!rcdev->ops->assert)
-			return 0;
-	} else {
-		/*
-		 * If the reset controller does not implement .assert(), there
-		 * is no way to guarantee that the reset line is asserted after
-		 * this call.
-		 */
-		if (!rcdev->ops->assert)
-			return -ENOTSUPP;
-
-		if (!rstc->acquired) {
-			WARN(1, "reset %s (ID: %u) is not acquired\n",
-			     rcdev_name(rcdev), rstc->id);
-			return -EPERM;
-		}
+	/*
+	 * For a shared reset the last caller runs the hardware op while the
+	 * other callers just drop the refcount and return. If the op may sleep
+	 * (rcdev->can_sleep), serialize the refcount update and the op against
+	 * a concurrent reset_control_deassert() so the two cannot interleave.
+	 */
+	if (rstc->shared && rcdev->can_sleep) {
+		guard(mutex)(&rstc->shared_lock);
+		return __reset_control_assert(rstc, rcdev);
 	}
 
-	return rcdev->ops->assert(rcdev, rstc->id);
+	return __reset_control_assert(rstc, rcdev);
 }
 EXPORT_SYMBOL_GPL(reset_control_assert);
 
@@ -575,6 +619,36 @@ int reset_control_bulk_assert(int num_rstcs,
 }
 EXPORT_SYMBOL_GPL(reset_control_bulk_assert);
 
+static int __reset_control_deassert(struct reset_control *rstc,
+				    struct reset_controller_dev *rcdev)
+{
+	if (rstc->shared) {
+		if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
+			return -EINVAL;
+
+		if (atomic_inc_return(&rstc->deassert_count) != 1)
+			return 0;
+	} else {
+		if (!rstc->acquired) {
+			WARN(1, "reset %s (ID: %u) is not acquired\n",
+			     rcdev_name(rcdev), rstc->id);
+			return -EPERM;
+		}
+	}
+
+	/*
+	 * If the reset controller does not implement .deassert(), we assume
+	 * that it handles self-deasserting reset lines via .reset(). In that
+	 * case, the reset lines are deasserted by default. If that is not the
+	 * case, the reset controller driver should implement .deassert() and
+	 * return -ENOTSUPP.
+	 */
+	if (!rcdev->ops->deassert)
+		return 0;
+
+	return rcdev->ops->deassert(rcdev, rstc->id);
+}
+
 /**
  * reset_control_deassert - deasserts the reset line
  * @rstc: reset controller
@@ -605,31 +679,18 @@ int reset_control_deassert(struct reset_control *rstc)
 	if (!rcdev)
 		return -ENODEV;
 
-	if (rstc->shared) {
-		if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
-			return -EINVAL;
-
-		if (atomic_inc_return(&rstc->deassert_count) != 1)
-			return 0;
-	} else {
-		if (!rstc->acquired) {
-			WARN(1, "reset %s (ID: %u) is not acquired\n",
-			     rcdev_name(rcdev), rstc->id);
-			return -EPERM;
-		}
-	}
-
 	/*
-	 * If the reset controller does not implement .deassert(), we assume
-	 * that it handles self-deasserting reset lines via .reset(). In that
-	 * case, the reset lines are deasserted by default. If that is not the
-	 * case, the reset controller driver should implement .deassert() and
-	 * return -ENOTSUPP.
+	 * For a shared reset only the first caller runs the hardware op while
+	 * concurrent callers bump the refcount and return. If the op may sleep
+	 * (rcdev->can_sleep), serialize the refcount update and the op so a
+	 * concurrent caller cannot return before the op has finished.
 	 */
-	if (!rcdev->ops->deassert)
-		return 0;
+	if (rstc->shared && rcdev->can_sleep) {
+		guard(mutex)(&rstc->shared_lock);
+		return __reset_control_deassert(rstc, rcdev);
+	}
 
-	return rcdev->ops->deassert(rcdev, rstc->id);
+	return __reset_control_deassert(rstc, rcdev);
 }
 EXPORT_SYMBOL_GPL(reset_control_deassert);
 
@@ -879,6 +940,7 @@ __reset_control_get_internal(struct reset_controller_dev *rcdev,
 	rstc->id = index;
 	kref_init(&rstc->refcnt);
 	mutex_init(&rstc->lock);
+	mutex_init(&rstc->shared_lock);
 	rstc->acquired = acquired;
 	rstc->shared = shared;
 	get_device(rcdev->dev);
@@ -901,6 +963,7 @@ static void __reset_control_release(struct kref *kref)
 	}
 
 	mutex_destroy(&rstc->lock);
+	mutex_destroy(&rstc->shared_lock);
 }
 
 static void reset_control_put_internal(struct reset_control *rstc)
diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h
index 52a5a4e81f18..587fb57c2c52 100644
--- a/include/linux/reset-controller.h
+++ b/include/linux/reset-controller.h
@@ -47,6 +47,9 @@ struct of_phandle_args;
  *                id as given to the reset control ops, defaults to
  *                :c:func:`fwnode_reset_simple_xlate`
  * @nr_resets: number of reset controls in this reset controller device
+ * @can_sleep: set by the provider if its reset ops may sleep; makes the core
+ *             serialize the shared reset, assert and deassert paths under a
+ *             mutex
  * @lock: protects the reset control list from concurrent access
  */
 struct reset_controller_dev {
@@ -64,6 +67,7 @@ struct reset_controller_dev {
 	int (*fwnode_xlate)(struct reset_controller_dev *rcdev,
 			    const struct fwnode_reference_args *reset_spec);
 	unsigned int nr_resets;
+	bool can_sleep;
 	struct mutex lock;
 };
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization
  2026-10-23  7:16 [PATCH v4 0/2] reset: Serialize shared resets whose ops may sleep shengjiu.wang
  2026-10-23  7:16 ` [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets shengjiu.wang
@ 2026-10-23  7:16 ` shengjiu.wang
  2026-10-23  8:43   ` Alvin Šipraga
  1 sibling, 1 reply; 5+ messages in thread
From: shengjiu.wang @ 2026-10-23  7:16 UTC (permalink / raw)
  To: p.zabel, krzk, hansg, linux-kernel, broonie, alvin.sipraga

From: Shengjiu Wang <shengjiu.wang@nxp.com>

reset-gpio drives the line with gpiod_set_value_cansleep(), so its reset
ops may sleep. Set reset_controller_dev::can_sleep so the core serializes
the shared refcount update and the hardware op under its per-control mutex,
preventing a concurrent consumer from returning before the op has finished.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
 drivers/reset/reset-gpio.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/reset/reset-gpio.c b/drivers/reset/reset-gpio.c
index bf95296a512e..b4a198792107 100644
--- a/drivers/reset/reset-gpio.c
+++ b/drivers/reset/reset-gpio.c
@@ -67,6 +67,7 @@ static int reset_gpio_probe(struct auxiliary_device *adev,
 				     "Could not get reset gpios\n");
 
 	priv->rc.ops = &reset_gpio_ops;
+	priv->rc.can_sleep = true;
 	priv->rc.owner = THIS_MODULE;
 	priv->rc.dev = dev;
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets
  2026-10-23  7:16 ` [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets shengjiu.wang
@ 2026-10-23  8:42   ` Alvin Šipraga
  0 siblings, 0 replies; 5+ messages in thread
From: Alvin Šipraga @ 2026-10-23  8:42 UTC (permalink / raw)
  To: shengjiu.wang; +Cc: p.zabel, krzk, hansg, linux-kernel, broonie

On Fri, Oct 23, 2026 at 03:16:08PM +0800, shengjiu.wang@oss.nxp.com wrote:
> From: Shengjiu Wang <shengjiu.wang@nxp.com>
> 
> Add a reset_controller_dev::can_sleep flag that a provider sets when its
> reset ops may sleep. When it is set, serialize the shared refcount update
> and the hardware op in reset_control_reset(), reset_control_deassert() and
> reset_control_assert() under a new per-control mutex, taken inside the SRCU
> read-side critical section. Providers that leave the flag clear keep the
> existing lockless path, so callers in atomic context are unaffected.
> 
> reset_control_deassert() and reset_control_reset() are documented to
> guarantee that the reset line has been deasserted, respectively that a
> reset pulse has been issued, by the time they return.
> 
> For a shared reset only the first caller runs the hardware op; the other
> callers just bump the refcount and return. When the op can sleep this
> breaks the guarantee: a second caller can observe the bumped refcount and
> return before the first caller's op has finished, and then touch hardware
> that is still in reset. A reset_control_assert() racing a concurrent
> reset_control_deassert() can likewise interleave the two sleeping ops and
> leave the line asserted while the refcount says it is deasserted.
> 
> Suggested-by: Philipp Zabel <p.zabel@pengutronix.de>
> Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
> Link: https://lore.kernel.org/all/aszDZlJ0KahNwgZO@analog.com/

Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>

> ---
>  drivers/reset/core.c             | 205 ++++++++++++++++++++-----------
>  include/linux/reset-controller.h |   4 +
>  2 files changed, 138 insertions(+), 71 deletions(-)

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization
  2026-10-23  7:16 ` [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization shengjiu.wang
@ 2026-10-23  8:43   ` Alvin Šipraga
  0 siblings, 0 replies; 5+ messages in thread
From: Alvin Šipraga @ 2026-10-23  8:43 UTC (permalink / raw)
  To: shengjiu.wang; +Cc: p.zabel, krzk, hansg, linux-kernel, broonie

On Fri, Oct 23, 2026 at 03:16:09PM +0800, shengjiu.wang@oss.nxp.com wrote:
> From: Shengjiu Wang <shengjiu.wang@nxp.com>
> 
> reset-gpio drives the line with gpiod_set_value_cansleep(), so its reset
> ops may sleep. Set reset_controller_dev::can_sleep so the core serializes
> the shared refcount update and the hardware op under its per-control mutex,
> preventing a concurrent consumer from returning before the op has finished.
> 
> Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>

Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>

> ---
>  drivers/reset/reset-gpio.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/reset/reset-gpio.c b/drivers/reset/reset-gpio.c
> index bf95296a512e..b4a198792107 100644
> --- a/drivers/reset/reset-gpio.c
> +++ b/drivers/reset/reset-gpio.c
> @@ -67,6 +67,7 @@ static int reset_gpio_probe(struct auxiliary_device *adev,
>  				     "Could not get reset gpios\n");
>  
>  	priv->rc.ops = &reset_gpio_ops;
> +	priv->rc.can_sleep = true;
>  	priv->rc.owner = THIS_MODULE;
>  	priv->rc.dev = dev;
>  
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-23  8:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23  7:16 [PATCH v4 0/2] reset: Serialize shared resets whose ops may sleep shengjiu.wang
2026-10-23  7:16 ` [PATCH v4 1/2] reset: core: Serialize concurrent deassert/reset of shared resets shengjiu.wang
2026-10-23  8:42   ` Alvin Šipraga
2026-10-23  7:16 ` [PATCH v4 2/2] reset: gpio: Set can_sleep for the shared reset serialization shengjiu.wang
2026-10-23  8:43   ` Alvin Šipraga

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®