mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/3] mfd: tps65217: Handle IRQ initialization errors
@ 2026-09-17 10:33 Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 1/3] mfd: tps65217: Fix NULL pointer dereference on IRQ init failure Жамбакиев Радий Рикардинович
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Жамбакиев Радий Рикардинович @ 2026-09-17 10:33 UTC (permalink / raw)
  To: Aaro Koskinen
  Cc: Жамбакиев
	Радий
	Рикардинович,
	Andreas Kemnade, Kevin Hilman, Roger Quadros, Tony Lindgren,
	Lee Jones, Grygorii Strashko, Marcin Niestroj, linux-omap, mfd,
	linux-kernel, lvc-project

From: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>

The TPS65217 MFD driver does not validate the outcome of IRQ
initialisation: tps65217_probe() ignores the return value of
tps65217_irq_init(), so a failed irq_domain creation leaves the
driver bound with a NULL tps->irq_domain and the remove callback
oopses, and a failed write of the interrupt mask leaves the software
mask out of sync with the hardware.

On top of that the irq_domain is torn down in the wrong order: the
remove callback disposes the mappings and removes the domain before
the MFD children are unbound, so children that requested interrupts
(e.g. tps65217-charger) release virtual IRQs whose descriptors are
already disposed, and on probe failure the domain is leaked with its
host_data pointing to freed memory. The enable_irq_wake() in probe
is never balanced.

This series plugs the probe error paths and moves the domain and
wake-up teardown into devres, registered right after the domain is
created. Devres actions are released in reverse order of
registration, so on both removal and probe failure the MFD children
release their IRQs first, then the parent interrupt is freed and its
thread stopped, and only then is the domain removed.

Changes since v3:
  - No functional changes; applied the code-style fixes requested by
    Lee Jones.

Link: https://lore.kernel.org/all/20260904063439.69881-1-r.zhambakiev@prosoftsystems.ru/

Radiy Zhambakiev (3):
  mfd: tps65217: Fix NULL pointer dereference on IRQ init failure
  mfd: tps65217: Check return value when masking interrupt sources
  mfd: tps65217: Use devres for IRQ domain and wake teardown

 drivers/mfd/tps65217.c | 70 +++++++++++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 21 deletions(-)

-- 
2.53.0

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

* [PATCH v4 1/3] mfd: tps65217: Fix NULL pointer dereference on IRQ init failure
  2026-09-17 10:33 [PATCH v4 0/3] mfd: tps65217: Handle IRQ initialization errors Жамбакиев Радий Рикардинович
@ 2026-09-17 10:33 ` Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 2/3] mfd: tps65217: Check return value when masking interrupt sources Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown Жамбакиев Радий Рикардинович
  2 siblings, 0 replies; 5+ messages in thread
From: Жамбакиев Радий Рикардинович @ 2026-09-17 10:33 UTC (permalink / raw)
  To: Aaro Koskinen
  Cc: Жамбакиев
	Радий
	Рикардинович,
	Andreas Kemnade, Kevin Hilman, Roger Quadros, Tony Lindgren,
	Lee Jones, Grygorii Strashko, Marcin Niestroj, linux-omap, mfd,
	linux-kernel, lvc-project, stable

From: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>

tps65217_probe() ignores the return value of tps65217_irq_init(), so
when the irq domain creation fails the probe still completes and the
driver ends up bound with a NULL tps->irq_domain. Unloading the
module then makes tps65217_remove() call irq_domain_remove() on the
NULL pointer and oops the kernel. On top of that, irq_find_mapping()
may fall back to the default irq domain and dispose of mappings that
belong to other interrupt controllers.

Check the return value and abort the probe on failure so the error
is reported and no inconsistent state is left for removal.

Fixes: 6556bdacf646fcaa ("mfd: tps65217: Add support for IRQs")
Cc: stable@vger.kernel.org
Signed-off-by: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>
---
 drivers/mfd/tps65217.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c
index c240fac0ede7..2d04d9e0ae29 100644
--- a/drivers/mfd/tps65217.c
+++ b/drivers/mfd/tps65217.c
@@ -333,7 +333,9 @@ static int tps65217_probe(struct i2c_client *client)
 	}
 
 	if (client->irq) {
-		tps65217_irq_init(tps, client->irq);
+		ret = tps65217_irq_init(tps, client->irq);
+		if (ret)
+			return ret;
 	} else {
 		int i;
 
-- 
2.53.0

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

* [PATCH v4 2/3] mfd: tps65217: Check return value when masking interrupt sources
  2026-09-17 10:33 [PATCH v4 0/3] mfd: tps65217: Handle IRQ initialization errors Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 1/3] mfd: tps65217: Fix NULL pointer dereference on IRQ init failure Жамбакиев Радий Рикардинович
@ 2026-09-17 10:33 ` Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown Жамбакиев Радий Рикардинович
  2 siblings, 0 replies; 5+ messages in thread
From: Жамбакиев Радий Рикардинович @ 2026-09-17 10:33 UTC (permalink / raw)
  To: Aaro Koskinen
  Cc: Жамбакиев
	Радий
	Рикардинович,
	Andreas Kemnade, Kevin Hilman, Roger Quadros, Tony Lindgren,
	Lee Jones, Grygorii Strashko, Marcin Niestroj, linux-omap, mfd,
	linux-kernel, lvc-project, stable

From: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>

tps65217_irq_init() ignores the error returned by
tps65217_set_bits() when masking all interrupt sources. A failed
register write leaves the driver's software mask out of sync with the
hardware and may result in spurious interrupts.

Check the return value and propagate the error to the caller.

Found by Linux Verification Center (linuxtesting.org) with SVACE.

Fixes: 6556bdacf646fcaa ("mfd: tps65217: Add support for IRQs")
Cc: stable@vger.kernel.org
Signed-off-by: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>
---
 drivers/mfd/tps65217.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c
index 2d04d9e0ae29..ee640e3fcf7b 100644
--- a/drivers/mfd/tps65217.c
+++ b/drivers/mfd/tps65217.c
@@ -155,8 +155,12 @@ static int tps65217_irq_init(struct tps65217 *tps, int irq)
 
 	/* Mask all interrupt sources */
 	tps->irq_mask = TPS65217_INT_MASK;
-	tps65217_set_bits(tps, TPS65217_REG_INT, TPS65217_INT_MASK,
-			  TPS65217_INT_MASK, TPS65217_PROTECT_NONE);
+	ret = tps65217_set_bits(tps, TPS65217_REG_INT, TPS65217_INT_MASK,
+				TPS65217_INT_MASK, TPS65217_PROTECT_NONE);
+	if (ret) {
+		dev_err(tps->dev, "Failed to mask interrupt sources: %d\n", ret);
+		return ret;
+	}
 
 	tps->irq_domain = irq_domain_create_linear(dev_fwnode(tps->dev), TPS65217_NUM_IRQ,
 						   &tps65217_irq_domain_ops, tps);
-- 
2.53.0

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

* [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown
  2026-09-17 10:33 [PATCH v4 0/3] mfd: tps65217: Handle IRQ initialization errors Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 1/3] mfd: tps65217: Fix NULL pointer dereference on IRQ init failure Жамбакиев Радий Рикардинович
  2026-09-17 10:33 ` [PATCH v4 2/3] mfd: tps65217: Check return value when masking interrupt sources Жамбакиев Радий Рикардинович
@ 2026-09-17 10:33 ` Жамбакиев Радий Рикардинович
  2026-09-23  8:33   ` Lee Jones
  2 siblings, 1 reply; 5+ messages in thread
From: Жамбакиев Радий Рикардинович @ 2026-09-17 10:33 UTC (permalink / raw)
  To: Aaro Koskinen
  Cc: Жамбакиев
	Радий
	Рикардинович,
	Andreas Kemnade, Kevin Hilman, Roger Quadros, Tony Lindgren,
	Lee Jones, Grygorii Strashko, Marcin Niestroj, linux-omap, mfd,
	linux-kernel, lvc-project, stable

From: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>

Removing the parent device used to tear the IRQ domain down in
tps65217_remove() before the MFD children are unbound: the i2c core
only releases the parent's devres after the remove callback returns,
whereas devm_mfd_add_devices() registered its own devres action which
unbinds the children. Children that requested interrupts from the
domain (e.g. tps65217-charger) therefore call free_irq() on virtual
IRQs whose descriptors have already been disposed. With
CONFIG_SPARSE_IRQ free_irq() then returns early without stopping the
threaded handler, leaking the irqaction, the IRQ kthread and a module
reference.

Register the domain teardown as a devres action right after the domain
is created instead. Devres actions are released in reverse order of
registration, so the MFD children release their IRQs first, then the
parent interrupt is freed and its thread stopped, and only then is the
domain torn down. The same ordering covers probe failure, which no
longer needs manual cleanup, and the remove callback can be dropped
entirely.

Balance the enable_irq_wake() in tps65217_irq_init() by registering
disable_irq_wake() as a devres action as well. Only do so when
enabling actually succeeded, since the parent chip may not support
setting wake-up.

Fixes: 6556bdacf646fcaa ("mfd: tps65217: Add support for IRQs")
Cc: stable@vger.kernel.org
Signed-off-by: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>
---
 drivers/mfd/tps65217.c | 58 +++++++++++++++++++++++++++++-------------
 1 file changed, 40 insertions(+), 18 deletions(-)

diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c
index ee640e3fcf7b..b5138d0f318f 100644
--- a/drivers/mfd/tps65217.c
+++ b/drivers/mfd/tps65217.c
@@ -146,6 +146,31 @@ static const struct irq_domain_ops tps65217_irq_domain_ops = {
 	.map = tps65217_irq_map,
 };
 
+static void tps65217_irq_cleanup(void *data)
+{
+	struct tps65217 *tps = data;
+	unsigned int virq;
+
+	if (!tps->irq_domain)
+		return;
+
+	for (int i = 0; i < TPS65217_NUM_IRQ; i++) {
+		virq = irq_find_mapping(tps->irq_domain, i);
+		if (virq)
+			irq_dispose_mapping(virq);
+	}
+
+	irq_domain_remove(tps->irq_domain);
+	tps->irq_domain = NULL;
+}
+
+static void tps65217_irq_wake_disable(void *data)
+{
+	unsigned long irq = (unsigned long)data;
+
+	disable_irq_wake(irq);
+}
+
 static int tps65217_irq_init(struct tps65217 *tps, int irq)
 {
 	int ret;
@@ -169,6 +194,10 @@ static int tps65217_irq_init(struct tps65217 *tps, int irq)
 		return -ENOMEM;
 	}
 
+	ret = devm_add_action_or_reset(tps->dev, tps65217_irq_cleanup, tps);
+	if (ret)
+		return ret;
+
 	ret = devm_request_threaded_irq(tps->dev, irq, NULL,
 					tps65217_irq_thread, IRQF_ONESHOT,
 					"tps65217-irq", tps);
@@ -178,7 +207,17 @@ static int tps65217_irq_init(struct tps65217 *tps, int irq)
 		return ret;
 	}
 
-	enable_irq_wake(irq);
+	ret = enable_irq_wake(irq);
+	if (ret) {
+		dev_warn(tps->dev, "failed to enable IRQ wake: %d\n", ret);
+		return 0;
+	}
+
+	ret = devm_add_action_or_reset(tps->dev,
+			tps65217_irq_wake_disable,
+			(void *)(unsigned long)irq);
+	if (ret)
+		return ret;
 
 	return 0;
 }
@@ -379,22 +418,6 @@ static int tps65217_probe(struct i2c_client *client)
 	return 0;
 }
 
-static void tps65217_remove(struct i2c_client *client)
-{
-	struct tps65217 *tps = i2c_get_clientdata(client);
-	unsigned int virq;
-	int i;
-
-	for (i = 0; i < TPS65217_NUM_IRQ; i++) {
-		virq = irq_find_mapping(tps->irq_domain, i);
-		if (virq)
-			irq_dispose_mapping(virq);
-	}
-
-	irq_domain_remove(tps->irq_domain);
-	tps->irq_domain = NULL;
-}
-
 static const struct i2c_device_id tps65217_id_table[] = {
 	{"tps65217", TPS65217},
 	{ /* sentinel */ }
@@ -408,7 +431,6 @@ static struct i2c_driver tps65217_driver = {
 	},
 	.id_table	= tps65217_id_table,
 	.probe		= tps65217_probe,
-	.remove		= tps65217_remove,
 };
 
 static int __init tps65217_init(void)
-- 
2.53.0

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

* Re: [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown
  2026-09-17 10:33 ` [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown Жамбакиев Радий Рикардинович
@ 2026-09-23  8:33   ` Lee Jones
  0 siblings, 0 replies; 5+ messages in thread
From: Lee Jones @ 2026-09-23  8:33 UTC (permalink / raw)
  To: Жамбакиев
	Радий
	Рикардинович
  Cc: Aaro Koskinen, Andreas Kemnade, Kevin Hilman, Roger Quadros,
	Tony Lindgren, Grygorii Strashko, Marcin Niestroj, linux-omap,
	mfd, linux-kernel, lvc-project, stable

On Thu, 17 Sep 2026, Жамбакиев Радий Рикардинович wrote:

> From: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>
> 
> Removing the parent device used to tear the IRQ domain down in
> tps65217_remove() before the MFD children are unbound: the i2c core
> only releases the parent's devres after the remove callback returns,
> whereas devm_mfd_add_devices() registered its own devres action which
> unbinds the children. Children that requested interrupts from the
> domain (e.g. tps65217-charger) therefore call free_irq() on virtual
> IRQs whose descriptors have already been disposed. With
> CONFIG_SPARSE_IRQ free_irq() then returns early without stopping the
> threaded handler, leaking the irqaction, the IRQ kthread and a module
> reference.
> 
> Register the domain teardown as a devres action right after the domain
> is created instead. Devres actions are released in reverse order of
> registration, so the MFD children release their IRQs first, then the
> parent interrupt is freed and its thread stopped, and only then is the
> domain torn down. The same ordering covers probe failure, which no
> longer needs manual cleanup, and the remove callback can be dropped
> entirely.
> 
> Balance the enable_irq_wake() in tps65217_irq_init() by registering
> disable_irq_wake() as a devres action as well. Only do so when
> enabling actually succeeded, since the parent chip may not support
> setting wake-up.
> 
> Fixes: 6556bdacf646fcaa ("mfd: tps65217: Add support for IRQs")
> Cc: stable@vger.kernel.org
> Signed-off-by: Radiy Zhambakiev <r.zhambakiev@prosoftsystems.ru>
> ---
>  drivers/mfd/tps65217.c | 58 +++++++++++++++++++++++++++++-------------
>  1 file changed, 40 insertions(+), 18 deletions(-)
> 
> diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c
> index ee640e3fcf7b..b5138d0f318f 100644
> --- a/drivers/mfd/tps65217.c
> +++ b/drivers/mfd/tps65217.c
> @@ -146,6 +146,31 @@ static const struct irq_domain_ops tps65217_irq_domain_ops = {
>  	.map = tps65217_irq_map,
>  };
>  
> +static void tps65217_irq_cleanup(void *data)
> +{
> +	struct tps65217 *tps = data;
> +	unsigned int virq;
> +
> +	if (!tps->irq_domain)
> +		return;
> +
> +	for (int i = 0; i < TPS65217_NUM_IRQ; i++) {
> +		virq = irq_find_mapping(tps->irq_domain, i);
> +		if (virq)
> +			irq_dispose_mapping(virq);
> +	}
> +
> +	irq_domain_remove(tps->irq_domain);
> +	tps->irq_domain = NULL;
> +}
> +
> +static void tps65217_irq_wake_disable(void *data)
> +{
> +	unsigned long irq = (unsigned long)data;
> +
> +	disable_irq_wake(irq);
> +}
> +
>  static int tps65217_irq_init(struct tps65217 *tps, int irq)
>  {
>  	int ret;
> @@ -169,6 +194,10 @@ static int tps65217_irq_init(struct tps65217 *tps, int irq)
>  		return -ENOMEM;
>  	}
>  
> +	ret = devm_add_action_or_reset(tps->dev, tps65217_irq_cleanup, tps);
> +	if (ret)
> +		return ret;
> +
>  	ret = devm_request_threaded_irq(tps->dev, irq, NULL,
>  					tps65217_irq_thread, IRQF_ONESHOT,
>  					"tps65217-irq", tps);
> @@ -178,7 +207,17 @@ static int tps65217_irq_init(struct tps65217 *tps, int irq)
>  		return ret;
>  	}
>  
> -	enable_irq_wake(irq);
> +	ret = enable_irq_wake(irq);
> +	if (ret) {
> +		dev_warn(tps->dev, "failed to enable IRQ wake: %d\n", ret);
> +		return 0;
> +	}
> +
> +	ret = devm_add_action_or_reset(tps->dev,
> +			tps65217_irq_wake_disable,
> +			(void *)(unsigned long)irq);

Nit: What on earth is going on with this formatting?

Firstly, there is no need to wrap this early and secondly, if there is
space left, which there clearly is here, we always line-up with the '('.

	ret = devm_add_action_or_reset(tps->dev, tps65217_irq_wake_disable,
				       (void *)(unsigned long)irq);

> +	if (ret)
> +		return ret;
>  
>  	return 0;
>  }
> @@ -379,22 +418,6 @@ static int tps65217_probe(struct i2c_client *client)
>  	return 0;
>  }
>  
> -static void tps65217_remove(struct i2c_client *client)
> -{
> -	struct tps65217 *tps = i2c_get_clientdata(client);
> -	unsigned int virq;
> -	int i;
> -
> -	for (i = 0; i < TPS65217_NUM_IRQ; i++) {
> -		virq = irq_find_mapping(tps->irq_domain, i);
> -		if (virq)
> -			irq_dispose_mapping(virq);
> -	}
> -
> -	irq_domain_remove(tps->irq_domain);
> -	tps->irq_domain = NULL;
> -}
> -
>  static const struct i2c_device_id tps65217_id_table[] = {
>  	{"tps65217", TPS65217},
>  	{ /* sentinel */ }
> @@ -408,7 +431,6 @@ static struct i2c_driver tps65217_driver = {
>  	},
>  	.id_table	= tps65217_id_table,
>  	.probe		= tps65217_probe,
> -	.remove		= tps65217_remove,
>  };
>  
>  static int __init tps65217_init(void)
> -- 
> 2.53.0

-- 
Lee Jones

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

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

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 10:33 [PATCH v4 0/3] mfd: tps65217: Handle IRQ initialization errors Жамбакиев Радий Рикардинович
2026-09-17 10:33 ` [PATCH v4 1/3] mfd: tps65217: Fix NULL pointer dereference on IRQ init failure Жамбакиев Радий Рикардинович
2026-09-17 10:33 ` [PATCH v4 2/3] mfd: tps65217: Check return value when masking interrupt sources Жамбакиев Радий Рикардинович
2026-09-17 10:33 ` [PATCH v4 3/3] mfd: tps65217: Use devres for IRQ domain and wake teardown Жамбакиев Радий Рикардинович
2026-09-23  8:33   ` Lee Jones

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®