summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@intel.com>2026-03-06 09:24:44 +0200
committerAlexandre Belloni <alexandre.belloni@bootlin.com>2026-03-11 22:10:01 +0100
commitf0b5159637ca0b8feaaa95de0f5ea38f1ba26729 (patch)
tree62e5ae3d91f4fea39c45511c2a99a63f4a68e320 /drivers
parent1dca8aee80eea76d2aae21265de5dd64f6ba0f09 (diff)
i3c: mipi-i3c-hci: Fix race between DMA ring dequeue and interrupt handler
The DMA ring bookkeeping in the MIPI I3C HCI driver is updated from two contexts: the DMA ring dequeue path (hci_dma_dequeue_xfer()) and the interrupt handler (hci_dma_xfer_done()). Both modify the ring's in-flight transfer state - specifically rh->src_xfers[] and xfer->ring_entry - but without any serialization. This allows the two paths to race, potentially leading to inconsistent ring state. Serialize access to the shared ring state by extending the existing spinlock to cover the DMA dequeue path and the entire interrupt handler. Since the core IRQ handler now holds this lock, remove the per-function locking from the PIO and DMA sub-handlers. Additionally, clear the completed entry in rh->src_xfers[] in hci_dma_xfer_done() so it cannot be matched or completed again. Finally, place the ring restart sequence under the same lock in hci_dma_dequeue_xfer() to avoid concurrent enqueue or completion operations while the ring state is being modified. Fixes: 9ad9a52cce282 ("i3c/master: introduce the mipi-i3c-hci driver") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter <adrian.hunter@intel.com> Reviewed-by: Frank Li <Frank.Li@nxp.com> Link: https://patch.msgid.link/20260306072451.11131-8-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/core.c2
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/dma.c11
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/pio.c6
3 files changed, 8 insertions, 11 deletions
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 061e84a5c412..adf35b7fa498 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -567,6 +567,8 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
irqreturn_t result = IRQ_NONE;
u32 val;
+ guard(spinlock)(&hci->lock);
+
/*
* The IRQ can be shared, so the handler may be called when the IRQ is
* due to a different device. That could happen when runtime suspended,
diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index f7d411e5e11f..d7840ff69e59 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -560,6 +560,8 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
WARN_ON(1);
}
+ spin_lock_irq(&hci->lock);
+
for (i = 0; i < n; i++) {
struct hci_xfer *xfer = xfer_list + i;
int idx = xfer->ring_entry;
@@ -593,6 +595,8 @@ static bool hci_dma_dequeue_xfer(struct i3c_hci *hci,
/* restart the ring */
rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE);
+ spin_unlock_irq(&hci->lock);
+
return did_unqueue;
}
@@ -618,6 +622,7 @@ static void hci_dma_xfer_done(struct i3c_hci *hci, struct hci_rh_data *rh)
dev_dbg(&hci->master.dev, "orphaned ring entry");
} else {
hci_dma_unmap_xfer(hci, xfer, 1);
+ rh->src_xfers[done_ptr] = NULL;
xfer->ring_entry = -1;
xfer->response = resp;
if (tid != xfer->cmd_tid) {
@@ -635,14 +640,11 @@ static void hci_dma_xfer_done(struct i3c_hci *hci, struct hci_rh_data *rh)
done_cnt += 1;
}
- /* take care to update the software dequeue pointer atomically */
- spin_lock(&hci->lock);
rh->xfer_space += done_cnt;
op1_val = rh_reg_read(RING_OPERATION1);
op1_val &= ~RING_OP1_CR_SW_DEQ_PTR;
op1_val |= FIELD_PREP(RING_OP1_CR_SW_DEQ_PTR, done_ptr);
rh_reg_write(RING_OPERATION1, op1_val);
- spin_unlock(&hci->lock);
}
static int hci_dma_request_ibi(struct i3c_hci *hci, struct i3c_dev_desc *dev,
@@ -822,13 +824,10 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
i3c_master_queue_ibi(dev, slot);
done:
- /* take care to update the ibi dequeue pointer atomically */
- spin_lock(&hci->lock);
op1_val = rh_reg_read(RING_OPERATION1);
op1_val &= ~RING_OP1_IBI_DEQ_PTR;
op1_val |= FIELD_PREP(RING_OP1_IBI_DEQ_PTR, deq_ptr);
rh_reg_write(RING_OPERATION1, op1_val);
- spin_unlock(&hci->lock);
/* update the chunk pointer */
rh->ibi_chunk_ptr += ibi_chunks;
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index 02866c2237fa..8f48a81e65ab 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -1014,15 +1014,12 @@ static bool hci_pio_irq_handler(struct i3c_hci *hci)
struct hci_pio_data *pio = hci->io_data;
u32 status;
- spin_lock(&hci->lock);
status = pio_reg_read(INTR_STATUS);
dev_dbg(&hci->master.dev, "PIO_INTR_STATUS %#x/%#x",
status, pio->enabled_irqs);
status &= pio->enabled_irqs | STAT_LATENCY_WARNINGS;
- if (!status) {
- spin_unlock(&hci->lock);
+ if (!status)
return false;
- }
if (status & STAT_IBI_STATUS_THLD)
hci_pio_process_ibi(hci, pio);
@@ -1056,7 +1053,6 @@ static bool hci_pio_irq_handler(struct i3c_hci *hci)
pio_reg_write(INTR_SIGNAL_ENABLE, pio->enabled_irqs);
dev_dbg(&hci->master.dev, "PIO_INTR_STATUS %#x/%#x",
pio_reg_read(INTR_STATUS), pio_reg_read(INTR_SIGNAL_ENABLE));
- spin_unlock(&hci->lock);
return true;
}