i3c: master: svc: fix race condition in ibi work thread
authorFrank Li <Frank.Li@nxp.com>
Mon, 23 Oct 2023 16:16:53 +0000 (12:16 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 28 Nov 2023 17:20:06 +0000 (17:20 +0000)
commit 6bf3fc268183816856c96b8794cd66146bc27b35 upstream.

The ibi work thread operates asynchronously with other transfers, such as
svc_i3c_master_priv_xfers(). Introduce mutex protection to ensure the
completion of the entire i3c/i2c transaction.

Fixes: dd3c52846d59 ("i3c: master: svc: Add Silvaco I3C master driver")
Cc: <stable@vger.kernel.org>
Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
Link: https://lore.kernel.org/r/20231023161658.3890811-2-Frank.Li@nxp.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/i3c/master/svc-i3c-master.c

index 8f8295a..87f8199 100644 (file)
@@ -175,6 +175,7 @@ struct svc_i3c_regs_save {
  * @ibi.slots: Available IBI slots
  * @ibi.tbq_slot: To be queued IBI slot
  * @ibi.lock: IBI lock
+ * @lock: Transfer lock, protect between IBI work thread and callbacks from master
  */
 struct svc_i3c_master {
        struct i3c_master_controller base;
@@ -203,6 +204,7 @@ struct svc_i3c_master {
                /* Prevent races within IBI handlers */
                spinlock_t lock;
        } ibi;
+       struct mutex lock;
 };
 
 /**
@@ -384,6 +386,7 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
        u32 status, val;
        int ret;
 
+       mutex_lock(&master->lock);
        /* Acknowledge the incoming interrupt with the AUTOIBI mechanism */
        writel(SVC_I3C_MCTRL_REQUEST_AUTO_IBI |
               SVC_I3C_MCTRL_IBIRESP_AUTO,
@@ -460,6 +463,7 @@ static void svc_i3c_master_ibi_work(struct work_struct *work)
 
 reenable_ibis:
        svc_i3c_master_enable_interrupts(master, SVC_I3C_MINT_SLVSTART);
+       mutex_unlock(&master->lock);
 }
 
 static irqreturn_t svc_i3c_master_irq_handler(int irq, void *dev_id)
@@ -1204,9 +1208,11 @@ static int svc_i3c_master_send_bdcast_ccc_cmd(struct svc_i3c_master *master,
        cmd->read_len = 0;
        cmd->continued = false;
 
+       mutex_lock(&master->lock);
        svc_i3c_master_enqueue_xfer(master, xfer);
        if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
                svc_i3c_master_dequeue_xfer(master, xfer);
+       mutex_unlock(&master->lock);
 
        ret = xfer->ret;
        kfree(buf);
@@ -1250,9 +1256,11 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
        cmd->read_len = read_len;
        cmd->continued = false;
 
+       mutex_lock(&master->lock);
        svc_i3c_master_enqueue_xfer(master, xfer);
        if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
                svc_i3c_master_dequeue_xfer(master, xfer);
+       mutex_unlock(&master->lock);
 
        if (cmd->read_len != xfer_len)
                ccc->dests[0].payload.len = cmd->read_len;
@@ -1309,9 +1317,11 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
                cmd->continued = (i + 1) < nxfers;
        }
 
+       mutex_lock(&master->lock);
        svc_i3c_master_enqueue_xfer(master, xfer);
        if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
                svc_i3c_master_dequeue_xfer(master, xfer);
+       mutex_unlock(&master->lock);
 
        ret = xfer->ret;
        svc_i3c_master_free_xfer(xfer);
@@ -1347,9 +1357,11 @@ static int svc_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
                cmd->continued = (i + 1 < nxfers);
        }
 
+       mutex_lock(&master->lock);
        svc_i3c_master_enqueue_xfer(master, xfer);
        if (!wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000)))
                svc_i3c_master_dequeue_xfer(master, xfer);
+       mutex_unlock(&master->lock);
 
        ret = xfer->ret;
        svc_i3c_master_free_xfer(xfer);
@@ -1540,6 +1552,8 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
 
        INIT_WORK(&master->hj_work, svc_i3c_master_hj_work);
        INIT_WORK(&master->ibi_work, svc_i3c_master_ibi_work);
+       mutex_init(&master->lock);
+
        ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
                               IRQF_NO_SUSPEND, "svc-i3c-irq", master);
        if (ret)