[PATCH] scsi: ibmvfc: synchronize event timer completion

From: Runyu Xiao

Date: Wed Sep 30 2026 - 04:00:30 EST


The response and purge paths remove an event from their queue and
complete it immediately after timer_delete(). A timer callback can still
be running on another CPU and can access the event while its completion
returns it to the event pool for reuse.

Use timer_delete_sync_try() in the interrupt-side completion path and defer
the completion to workqueue context when the timer callback is active.
Flush the work before releasing the event pool so an event cannot be reused
or freed while its callback is still running.

Fixes: 072b91f9c651 ("[SCSI] ibmvfc: IBM Power Virtual Fibre Channel Adapter Client Driver")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Runyu Xiao <runyu.xiao@xxxxxxxxxx>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 40 ++++++++++++++++++++++-------
drivers/scsi/ibmvscsi/ibmvfc.h | 2 ++
2 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index 93b9f699c..072008411 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -811,6 +811,28 @@ 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);
+
+ timer_delete_sync(&evt->timer);
+ 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 +881,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;
@@ -886,6 +909,7 @@ static void ibmvfc_free_event_pool(struct ibmvfc_host *vhost,

ENTER;
for (i = 0; i < pool->size; ++i) {
+ flush_work(&pool->events[i].timer_work);
list_del(&pool->events[i].queue_list);
BUG_ON(atomic_read(&pool->events[i].free) != 1);
if (pool->events[i].ext_list)
@@ -1134,7 +1158,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 +1186,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);
}

/**
@@ -1824,7 +1848,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 +1859,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;
}

@@ -1851,7 +1875,7 @@ int ibmvfc_send_event(struct ibmvfc_event *evt,
evt->done = evt->_done;
}

- evt->done(evt);
+ ibmvfc_complete_event(evt);
} else {
spin_unlock_irqrestore(&evt->queue->l_lock, flags);
ibmvfc_trc_start(evt);
@@ -3925,10 +3949,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 +4054,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..fba2e3a23 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,7 @@ struct ibmvfc_event {
struct completion comp;
struct completion *eh_comp;
struct timer_list timer;
+ struct work_struct timer_work;
u16 hwq;
u8 reserved;
};
--
2.34.1