[PATCH v2] scsi: ibmvfc: synchronize event timer completion
flat view
DORMANTno replies
From: Runyu Xiao <hidden>
Date: 2026-09-30 08:49:27
Also in:
linux-scsi, lkml, stable
Subsystem:
ibm power virtual fc device drivers, linux for powerpc (32-bit and 64-bit), scsi subsystem, the rest · Maintainers:
Tyrel Datwyler, Madhavan Srinivasan, "James E.J. Bottomley", "Martin K. Petersen", Linus Torvalds
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: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Runyu Xiao <redacted>
---
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