An event completion may be deferred when the timer callback is active. MAD
events normally use ibmvfc_locked_done(), but send failures replace the
wrapper with the raw callback because the caller may already hold
host_lock. Preserve that locking requirement when the completion runs from
workqueue context.

Flush every event completion work item before taking the event pool list
lock, then remove the free-list entries while holding queue->l_lock. Route
purge completions through the same timer synchronization helper so all
completion paths obey the same ordering.

Fixes: 072b91f9c651 ("[SCSI] ibmvfc: IBM Power Virtual Fibre Channel Adapter 
Client Driver")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Runyu Xiao <[email protected]>
---
 drivers/scsi/ibmvscsi/ibmvfc-core.c | 61 ++++++++++++++++++++++++-----
 drivers/scsi/ibmvscsi/ibmvfc.h      |  3 ++
 2 files changed, 55 insertions(+), 9 deletions(-)

diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c 
b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index 93b9f699c..a3d016f6e 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -811,6 +811,35 @@ static int ibmvfc_send_crq_init_complete(struct 
ibmvfc_host *vhost)
        return ibmvfc_send_crq(vhost, 0xC002000000000000LL, 0);
 }
 
+static void ibmvfc_complete_event_work(struct work_struct *work)
+{
+       struct ibmvfc_event *evt = container_of(work, struct ibmvfc_event,
+                                               timer_work);
+       unsigned long flags;
+
+       timer_delete_sync(&evt->timer);
+       if (evt->done_needs_host_lock) {
+               spin_lock_irqsave(evt->vhost->host->host_lock, flags);
+               evt->done(evt);
+               spin_unlock_irqrestore(evt->vhost->host->host_lock, flags);
+       } else {
+               evt->done(evt);
+       }
+}
+
+static void ibmvfc_complete_event(struct ibmvfc_event *evt)
+{
+       /* The response path can run in hardirq context, so defer only when the
+        * timer callback is already executing on another context.
+        */
+       if (timer_delete_sync_try(&evt->timer) < 0) {
+               schedule_work(&evt->timer_work);
+               return;
+       }
+
+       evt->done(evt);
+}
+
 /**
  * ibmvfc_init_event_pool - Allocates and initializes the event pool for a host
  * @vhost:     ibmvfc host who owns the event pool
@@ -859,6 +888,7 @@ static int ibmvfc_init_event_pool(struct ibmvfc_host *vhost,
                 */
                atomic_set(&evt->active, -1);
                atomic_set(&evt->free, 1);
+               INIT_WORK(&evt->timer_work, ibmvfc_complete_event_work);
                evt->crq.valid = 0x80;
                evt->crq.ioba = cpu_to_be64(pool->iu_token + 
(sizeof(*evt->xfer_iu) * i));
                evt->xfer_iu = pool->iu_storage + i;
@@ -882,18 +912,31 @@ static void ibmvfc_free_event_pool(struct ibmvfc_host 
*vhost,
                                   struct ibmvfc_queue *queue)
 {
        int i;
+       unsigned long flags;
        struct ibmvfc_event_pool *pool = &queue->evt_pool;
 
        ENTER;
+       if (!pool->size)
+               goto out;
+
+       for (i = 0; i < pool->size; ++i)
+               flush_work(&pool->events[i].timer_work);
+
+       spin_lock_irqsave(&queue->l_lock, flags);
        for (i = 0; i < pool->size; ++i) {
                list_del(&pool->events[i].queue_list);
                BUG_ON(atomic_read(&pool->events[i].free) != 1);
+       }
+       spin_unlock_irqrestore(&queue->l_lock, flags);
+
+       for (i = 0; i < pool->size; ++i) {
                if (pool->events[i].ext_list)
                        dma_pool_free(vhost->sg_pool,
                                      pool->events[i].ext_list,
                                      pool->events[i].ext_list_token);
        }
 
+out:
        kfree(pool->events);
        dma_free_coherent(vhost->dev,
                          pool->size * sizeof(*pool->iu_storage),
@@ -1134,7 +1177,7 @@ static void ibmvfc_complete_purge(struct list_head 
*purge_list)
        list_for_each_entry_safe(evt, pos, purge_list, queue_list) {
                list_del(&evt->queue_list);
                ibmvfc_trc_end(evt);
-               evt->done(evt);
+               ibmvfc_complete_event(evt);
        }
 }
 
@@ -1162,7 +1205,7 @@ static void ibmvfc_fail_request(struct ibmvfc_event *evt, 
int error_code)
        else
                evt->xfer_iu->mad_common.status = 
cpu_to_be16(IBMVFC_MAD_DRIVER_FAILED);
 
-       timer_delete(&evt->timer);
+       timer_delete_sync_try(&evt->timer);
 }
 
 /**
@@ -1669,6 +1712,7 @@ void ibmvfc_init_event(struct ibmvfc_event *evt,
                evt->_done = done;
                evt->done = ibmvfc_locked_done;
        }
+       evt->done_needs_host_lock = false;
        evt->hwq = 0;
 }
 
@@ -1824,7 +1868,6 @@ int ibmvfc_send_event(struct ibmvfc_event *evt,
                atomic_set(&evt->active, 0);
                list_del(&evt->queue_list);
                spin_unlock_irqrestore(&evt->queue->l_lock, flags);
-               timer_delete(&evt->timer);
 
                /* If send_crq returns H_CLOSED, return SCSI_MLQUEUE_HOST_BUSY.
                 * Firmware will send a CRQ with a transport event (0xFF) to
@@ -1836,7 +1879,8 @@ int ibmvfc_send_event(struct ibmvfc_event *evt,
                                dev_warn(vhost->dev, "Send warning. Receive 
queue closed, will retry.\n");
                        if (evt->cmnd)
                                scsi_dma_unmap(evt->cmnd);
-                       ibmvfc_free_event(evt);
+                       evt->done = ibmvfc_free_event;
+                       ibmvfc_complete_event(evt);
                        return SCSI_MLQUEUE_HOST_BUSY;
                }
 
@@ -1849,9 +1893,10 @@ int ibmvfc_send_event(struct ibmvfc_event *evt,
                } else {
                        evt->xfer_iu->mad_common.status = 
cpu_to_be16(IBMVFC_MAD_CRQ_ERROR);
                        evt->done = evt->_done;
+                       evt->done_needs_host_lock = true;
                }
 
-               evt->done(evt);
+               ibmvfc_complete_event(evt);
        } else {
                spin_unlock_irqrestore(&evt->queue->l_lock, flags);
                ibmvfc_trc_start(evt);
@@ -3925,10 +3970,9 @@ static void ibmvfc_tasklet(void *data)
        spin_unlock_irqrestore(vhost->host->host_lock, flags);
 
        list_for_each_entry_safe(evt, temp, &evt_doneq, queue_list) {
-               timer_delete(&evt->timer);
                list_del(&evt->queue_list);
                ibmvfc_trc_end(evt);
-               evt->done(evt);
+               ibmvfc_complete_event(evt);
        }
 }
 
@@ -4031,10 +4075,9 @@ static void ibmvfc_drain_sub_crq(struct ibmvfc_queue 
*scrq)
        spin_unlock_irqrestore(scrq->q_lock, flags);
 
        list_for_each_entry_safe(evt, temp, &evt_doneq, queue_list) {
-               timer_delete(&evt->timer);
                list_del(&evt->queue_list);
                ibmvfc_trc_end(evt);
-               evt->done(evt);
+               ibmvfc_complete_event(evt);
        }
 }
 
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.h b/drivers/scsi/ibmvscsi/ibmvfc.h
index ca80ceffe..912d250da 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc.h
+++ b/drivers/scsi/ibmvscsi/ibmvfc.h
@@ -14,6 +14,7 @@
 #include <linux/interrupt.h>
 #include <linux/list.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 #include <scsi/scsi_device.h>
 #include <scsi/viosrp.h>
 #include <linux/nvme.h>
@@ -867,6 +868,8 @@ struct ibmvfc_event {
        struct completion comp;
        struct completion *eh_comp;
        struct timer_list timer;
+       struct work_struct timer_work;
+       bool done_needs_host_lock;
        u16 hwq;
        u8 reserved;
 };
-- 
2.34.1


Reply via email to