[PATCH 1/2] remoteproc: qcom: q6v5_pas: Fix unbalanced handover IRQ enable on attach

Shawn Guo <[email protected]> Fri, 31 Jul 2026 10:56:53 +0800
Newsgroups org.kernel.vger.linux-remoteproc,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
commit bb7c5d6f5b41 ("remoteproc: qcom: q6v5: Make handover IRQ
one-shot") introduced tracked, idempotent enable/disable helpers for
the handover IRQ, gated on a new q6v5->handover_irq_enabled flag. It
converted qcom_q6v5_prepare()/unprepare() and the handover ISR to use
these helpers, but missed qcom_pas_attach(), which still toggles the
IRQ directly via enable_irq()/disable_irq() without updating the
tracked state.

This desyncs the real IRQ enable depth from handover_irq_enabled for
any remoteproc that boots up already attached (e.g. ADSP started by
the bootloader). qcom_pas_attach()'s raw enable_irq() leaves the IRQ
enabled while handover_irq_enabled stays false, so a subsequent
stop's qcom_q6v5_unprepare() sees the flag as false and skips
disabling the IRQ. The next start's qcom_q6v5_prepare() then calls
enable_irq() on an IRQ that was never disabled, producing:

  WARNING: Unbalanced enable for IRQ ...

Additionally, any handover interrupt latched by hardware before probe
and delivered once qcom_pas_attach() unmasks it is no longer
suppressed (the ISR's early-return-if-already-issued guard was
replaced by irq disable-after-first-fire), so it runs the handover
callback and disables proxy power-domains/clocks that attach() never
enabled, causing genpd runtime PM usage-count underflow warnings.

Route qcom_pas_attach()'s IRQ handling through the same
qcom_q6v5_handover_irq_enable()/qcom_q6v5_handover_irq_disable()
helpers (now exported) used elsewhere, keeping handover_irq_enabled
in sync with the real IRQ state.

Fixes: bb7c5d6f5b41 ("remoteproc: qcom: q6v5: Make handover IRQ one-shot")
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Shawn Guo <[email protected]>
---
 drivers/remoteproc/qcom_q6v5.c     | 21 ++++++++++++++++-----
 drivers/remoteproc/qcom_q6v5.h     |  2 ++
 drivers/remoteproc/qcom_q6v5_pas.c |  4 ++--
 3 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
index fe148b4b3775..10fa38f264c8 100644
--- a/drivers/remoteproc/qcom_q6v5.c
+++ b/drivers/remoteproc/qcom_q6v5.c
@@ -36,7 +36,11 @@ static int q6v5_load_state_toggle(struct qcom_q6v5 *q6v5, bool enable)
 	return ret;
 }
 
-static void q6v5_handover_irq_enable(struct qcom_q6v5 *q6v5)
+/**
+ * qcom_q6v5_handover_irq_enable() - idempotently enable the handover IRQ
+ * @q6v5:	reference to qcom_q6v5 context
+ */
+void qcom_q6v5_handover_irq_enable(struct qcom_q6v5 *q6v5)
 {
 	unsigned long flags;
 	bool enable = false;
@@ -51,8 +55,14 @@ static void q6v5_handover_irq_enable(struct qcom_q6v5 *q6v5)
 	if (enable)
 		enable_irq(q6v5->handover_irq);
 }
+EXPORT_SYMBOL_GPL(qcom_q6v5_handover_irq_enable);
 
-static void q6v5_handover_irq_disable(struct qcom_q6v5 *q6v5, bool sync)
+/**
+ * qcom_q6v5_handover_irq_disable() - idempotently disable the handover IRQ
+ * @q6v5:	reference to qcom_q6v5 context
+ * @sync:	whether to synchronize against an in-flight handler
+ */
+void qcom_q6v5_handover_irq_disable(struct qcom_q6v5 *q6v5, bool sync)
 {
 	unsigned long flags;
 	bool disable = false;
@@ -69,6 +79,7 @@ static void q6v5_handover_irq_disable(struct qcom_q6v5 *q6v5, bool sync)
 	if (sync)
 		synchronize_irq(q6v5->handover_irq);
 }
+EXPORT_SYMBOL_GPL(qcom_q6v5_handover_irq_disable);
 
 /**
  * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start
@@ -98,7 +109,7 @@ int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5)
 	q6v5->running = true;
 	q6v5->handover_issued = false;
 
-	q6v5_handover_irq_enable(q6v5);
+	qcom_q6v5_handover_irq_enable(q6v5);
 
 	return 0;
 }
@@ -112,7 +123,7 @@ EXPORT_SYMBOL_GPL(qcom_q6v5_prepare);
  */
 int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5)
 {
-	q6v5_handover_irq_disable(q6v5, true);
+	qcom_q6v5_handover_irq_disable(q6v5, true);
 
 	q6v5_load_state_toggle(q6v5, false);
 
@@ -201,7 +212,7 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *data)
 
 	q6v5->handover_issued = true;
 
-	q6v5_handover_irq_disable(q6v5, false);
+	qcom_q6v5_handover_irq_disable(q6v5, false);
 
 	if (q6v5->handover)
 		q6v5->handover(q6v5);
diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h
index 8991ff090579..24263cb514ab 100644
--- a/drivers/remoteproc/qcom_q6v5.h
+++ b/drivers/remoteproc/qcom_q6v5.h
@@ -53,6 +53,8 @@ void qcom_q6v5_deinit(struct qcom_q6v5 *q6v5);
 
 int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5);
 int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5);
+void qcom_q6v5_handover_irq_enable(struct qcom_q6v5 *q6v5);
+void qcom_q6v5_handover_irq_disable(struct qcom_q6v5 *q6v5, bool sync);
 int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon);
 int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout);
 unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5);
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index a4b233d92efb..0f1ffd4b6182 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -521,7 +521,7 @@ static int qcom_pas_attach(struct rproc *rproc)
 	int ret;
 
 	pas->q6v5.handover_issued = true;
-	enable_irq(pas->q6v5.handover_irq);
+	qcom_q6v5_handover_irq_enable(&pas->q6v5);
 
 	pas->q6v5.running = true;
 	ret = irq_get_irqchip_state(pas->q6v5.fatal_irq,
@@ -567,7 +567,7 @@ static int qcom_pas_attach(struct rproc *rproc)
 	pas->rproc->state = RPROC_OFFLINE;
 	ret = -EINVAL;
 disable_running:
-	disable_irq(pas->q6v5.handover_irq);
+	qcom_q6v5_handover_irq_disable(&pas->q6v5, true);
 	pas->q6v5.running = false;
 
 	return ret;
-- 
2.43.0