Linux USB
 help / color / mirror / Atom feed
From: jianfeng.gao@intel.com
To: Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
	Li Yifan <yifan2.li@intel.com>,
	Munirah Izyani Mohammad Amin
	<munirah.izyani.mohammad.amin@intel.com>,
	Gao Jianfeng <jianfeng.gao@intel.com>,
	stable@vger.kernel.org
Subject: [PATCH] usb: typec: tipd: mask interrupt across suspend to fix IRQ storm
Date: Fri, 18 Sep 2026 13:18:59 +0800	[thread overview]
Message-ID: <20260918051859.825873-1-jianfeng.gao@intel.com> (raw)

From: Gao Jianfeng <jianfeng.gao@intel.com>

When tps->wakeup is false, tps6598x_suspend() left the IRQ enabled
because disable_irq() was gated inside the "if (tps->wakeup)" branch.
During hibernation the parent I2C (designware) adapter suspends right
after the child's freeze callback, so with the IRQ still enabled and the
type-C line still asserted, tps6598x_interrupt() keeps re-firing. Its
register reads now fail with -ESHUTDOWN, the handler bails with
event1/event2 == 0 and returns IRQ_NONE, so the line is never acked and
the handler loops, flooding the log with "failed to read ..." errors
until the noirq phase finally masks interrupts.

Mask the interrupt unconditionally in suspend and re-enable it in
resume, keeping enable_irq()/disable_irq() balanced. The wake-source
arming stays conditional on tps->wakeup.

The resume error paths jump to a common exit so the IRQ is re-enabled
(and the wake source disarmed) even when the controller fails to come
back. The line is requested with IRQF_SHARED, so a leaked disable_irq()
would mask it for every other device sharing it, and the imbalance would
never be recovered by a later resume.

Fixes: 481735d64794 ("usb: typec: tipd: Support wakeup")
Cc: stable@vger.kernel.org
Co-developed-by: Li Yifan <yifan2.li@intel.com>
Signed-off-by: Li Yifan <yifan2.li@intel.com>
Signed-off-by: Gao Jianfeng <jianfeng.gao@intel.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Tested-by: Munirah Izyani Mohammad Amin <munirah.izyani.mohammad.amin@intel.com>
---
 drivers/usb/typec/tipd/core.c | 38 ++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 9248f0dd3f49..5ed573e30479 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -1972,12 +1972,19 @@ static int __maybe_unused tps6598x_suspend(struct device *dev)
 	struct i2c_client *client = to_i2c_client(dev);
 	struct tps6598x *tps = i2c_get_clientdata(client);
 
-	if (tps->wakeup) {
+	/*
+	 * Mask the interrupt before the parent I2C controller is suspended:
+	 * otherwise a still-asserted level IRQ keeps re-entering the handler,
+	 * whose register reads then fail with -ESHUTDOWN, producing an
+	 * interrupt storm until the noirq phase globally disables interrupts.
+	 */
+	if (client->irq)
 		disable_irq(client->irq);
+
+	if (tps->wakeup)
 		enable_irq_wake(client->irq);
-	} else if (tps->reset) {
+	else if (tps->reset)
 		gpiod_set_value_cansleep(tps->reset, 1);
-	}
 
 	if (!client->irq)
 		cancel_delayed_work_sync(&tps->wq_poll);
@@ -1993,18 +2000,16 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
 
 	ret = tps6598x_check_mode(tps);
 	if (ret < 0)
-		return ret;
+		goto out_enable_irq;
 
 	if (ret == TPS_MODE_PTCH) {
 		ret = tps->data->init(tps);
 		if (ret)
-			return ret;
+			goto out_enable_irq;
 	}
+	ret = 0;
 
-	if (tps->wakeup) {
-		disable_irq_wake(client->irq);
-		enable_irq(client->irq);
-	} else if (tps->reset) {
+	if (!tps->wakeup && tps->reset) {
 		gpiod_set_value_cansleep(tps->reset, 0);
 		msleep(TPS_SETUP_MS);
 	}
@@ -2013,7 +2018,20 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
 		queue_delayed_work(system_power_efficient_wq, &tps->wq_poll,
 				   msecs_to_jiffies(POLL_INTERVAL));
 
-	return 0;
+out_enable_irq:
+	/*
+	 * Unwind suspend unconditionally, including on the error paths above:
+	 * the line is requested with IRQF_SHARED, so a leaked disable_irq()
+	 * would mask it for every other device on it, and the imbalance would
+	 * never be recovered by a later resume.
+	 */
+	if (tps->wakeup)
+		disable_irq_wake(client->irq);
+
+	if (client->irq)
+		enable_irq(client->irq);
+
+	return ret;
 }
 
 static const struct dev_pm_ops tps6598x_pm_ops = {
-- 
2.34.1


                 reply	other threads:[~2026-09-18  5:24 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260918051859.825873-1-jianfeng.gao@intel.com \
    --to=jianfeng.gao@intel.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=munirah.izyani.mohammad.amin@intel.com \
    --cc=stable@vger.kernel.org \
    --cc=yifan2.li@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox