[PATCH 10/21] soundwire: qcom: Serialise ctrl->intr_mask updates

From: Lee Jones

Date: Fri Oct 30 2026 - 07:03:30 EST


qcom_swrm_irq_handler(), swrm_runtime_suspend(), and
swrm_runtime_resume() concurrently read, modify, and write
ctrl->intr_mask and program the interrupt enable registers without
synchronisation. Overlapping read-modify-write updates can clobber
interrupt mask bits and leave the hardware interrupt mask out of sync.

Add ctrl->intr_lock to serialise accesses to ctrl->intr_mask and its
associated interrupt mask register updates.

Fixes: 74e79da9fd46 ("soundwire: qcom: add runtime pm support")
Closes: https://sashiko.dev/#/bug/linux-ea523727-6467-42aa-850f-8f838e4b84a8
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Lee Jones <lee@xxxxxxxxxx>
---
drivers/soundwire/qcom.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
index 555045b32a25..cfa3466ff8e1 100644
--- a/drivers/soundwire/qcom.c
+++ b/drivers/soundwire/qcom.c
@@ -202,6 +202,8 @@ struct qcom_swrm_ctrl {
struct completion enumeration;
/* Port alloc/free lock */
struct mutex port_lock;
+ /* Interrupt mask and broadcast completion lock */
+ struct mutex intr_lock;
struct clk *hclk;
int irq;
u32 ee;
@@ -784,7 +786,9 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)

ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_STATUS],
&intr_sts);
+ mutex_lock(&ctrl->intr_lock);
intr_sts_masked = intr_sts & ctrl->intr_mask;
+ mutex_unlock(&ctrl->intr_lock);

do {
for (i = 0; i < SWRM_INTERRUPT_MAX; i++) {
@@ -820,10 +824,12 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
dev_err_ratelimited(ctrl->dev,
"%s: SWR bus clsh detected\n",
__func__);
+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
ctrl->reg_write(ctrl,
ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);
break;
case SWRM_INTERRUPT_STATUS_RD_FIFO_OVERFLOW:
ctrl->reg_read(ctrl,
@@ -863,20 +869,24 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
dev_err_ratelimited(ctrl->dev,
"%s: SWR Port collision detected\n",
__func__);
+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION;
ctrl->reg_write(ctrl,
ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);
break;
case SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH:
dev_err_ratelimited(ctrl->dev,
"%s: SWR read enable valid mismatch\n",
__func__);
+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask &=
~SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH;
ctrl->reg_write(ctrl,
ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);
break;
case SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED:
complete(&ctrl->broadcast);
@@ -910,7 +920,9 @@ static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id)
intr_sts);
ctrl->reg_read(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_STATUS],
&intr_sts);
+ mutex_lock(&ctrl->intr_lock);
intr_sts_masked = intr_sts & ctrl->intr_mask;
+ mutex_unlock(&ctrl->intr_lock);
} while (intr_sts_masked);

clk_disable_unprepare(ctrl->hclk);
@@ -952,11 +964,13 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
/* Enable Auto enumeration */
ctrl->reg_write(ctrl, SWRM_ENUMERATOR_CFG_ADDR, 1);

+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask = SWRM_INTERRUPT_STATUS_RMSK;
/* Mask soundwire interrupts */
if (ctrl->version < SWRM_VERSION_2_0_0)
ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_MASK_ADDR],
- SWRM_INTERRUPT_STATUS_RMSK);
+ ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);

/* Configure No pings */
ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val);
@@ -997,8 +1011,10 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl)
0xFFFFFFFF);

/* enable CPU IRQs for the selected EE window */
+ mutex_lock(&ctrl->intr_lock);
ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
- SWRM_INTERRUPT_STATUS_RMSK);
+ ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);

/* Set IRQ to PULSE */
ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR,
@@ -1689,6 +1705,7 @@ static int qcom_swrm_probe(struct platform_device *pdev)
ctrl->dev = dev;
dev_set_drvdata(&pdev->dev, ctrl);
mutex_init(&ctrl->port_lock);
+ mutex_init(&ctrl->intr_lock);
init_completion(&ctrl->broadcast);
init_completion(&ctrl->enumeration);

@@ -1854,6 +1871,7 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev)
ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR],
SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET);

+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask |= SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
if (ctrl->version < SWRM_VERSION_2_0_0)
ctrl->reg_write(ctrl,
@@ -1861,6 +1879,7 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev)
ctrl->intr_mask);
ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);

usleep_range(100, 105);
if (!swrm_wait_for_frame_gen_enabled(ctrl))
@@ -1882,6 +1901,7 @@ static int __maybe_unused swrm_runtime_suspend(struct device *dev)
swrm_wait_for_wr_fifo_done(ctrl);
if (!ctrl->clock_stop_not_supported) {
/* Mask bus clash interrupt */
+ mutex_lock(&ctrl->intr_lock);
ctrl->intr_mask &= ~SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET;
if (ctrl->version < SWRM_VERSION_2_0_0)
ctrl->reg_write(ctrl,
@@ -1889,6 +1909,7 @@ static int __maybe_unused swrm_runtime_suspend(struct device *dev)
ctrl->intr_mask);
ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN],
ctrl->intr_mask);
+ mutex_unlock(&ctrl->intr_lock);
/* Prepare slaves for clock stop */
ret = sdw_bus_prep_clk_stop(&ctrl->bus);
if (ret < 0 && ret != -ENODATA) {
--
2.56.0.317.g454f8302e1-goog