[PATCH v2 7/7] s390/vfio_ccw: implement a crw lock

From: Eric Farman

Date: Mon Jul 20 2026 - 16:23:59 EST


Unlike the channel_program struct, which covers synchronous I/O
submissions and asynchronous interrupts, the CRW region relies
exclusively on asynchronous events coming from hardware.

Implement a lock to manage the list of those payloads, to ensure
they are read cohesively.

Signed-off-by: Eric Farman <farman@xxxxxxxxxxxxx>
---
drivers/s390/cio/vfio_ccw_chp.c | 15 ++++++++++-----
drivers/s390/cio/vfio_ccw_drv.c | 4 ++++
drivers/s390/cio/vfio_ccw_ops.c | 3 +++
drivers/s390/cio/vfio_ccw_private.h | 2 ++
4 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
index 38c176cf6295..bbc445efd67f 100644
--- a/drivers/s390/cio/vfio_ccw_chp.c
+++ b/drivers/s390/cio/vfio_ccw_chp.c
@@ -90,14 +90,18 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
if (pos + count > sizeof(*region))
return -EINVAL;

+ mutex_lock(&private->io_mutex);
+ region = private->region[i].data;
+
+ spin_lock(&private->crw_lock);
crw = list_first_entry_or_null(&private->crw,
struct vfio_ccw_crw, next);

if (crw)
list_del(&crw->next);

- mutex_lock(&private->io_mutex);
- region = private->region[i].data;
+ /* Drop CRW lock while copying to userspace */
+ spin_unlock(&private->crw_lock);

if (crw)
memcpy(&region->crw, &crw->crw, sizeof(region->crw));
@@ -108,14 +112,15 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
ret = count;

region->crw = 0;
-
- mutex_unlock(&private->io_mutex);
-
kfree(crw);

/* Notify the guest if more CRWs are on our queue */
+ spin_lock(&private->crw_lock);
if (!list_empty(&private->crw) && private->crw_trigger)
eventfd_signal(private->crw_trigger);
+ spin_unlock(&private->crw_lock);
+
+ mutex_unlock(&private->io_mutex);

return ret;
}
diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index bfb68de6e52c..12edccb62cbf 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -125,8 +125,10 @@ void vfio_ccw_crw_todo(struct work_struct *work)

private = container_of(work, struct vfio_ccw_private, crw_work);

+ spin_lock(&private->crw_lock);
if (!list_empty(&private->crw) && private->crw_trigger)
eventfd_signal(private->crw_trigger);
+ spin_unlock(&private->crw_lock);
}

/*
@@ -296,7 +298,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
crw->crw.erc = erc;
crw->crw.rsid = rsid;

+ spin_lock(&private->crw_lock);
list_add_tail(&crw->next, &private->crw);
+ spin_unlock(&private->crw_lock);
queue_work(vfio_ccw_work_q, &private->crw_work);
}

diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index d2475b3158cf..b3699ffd2f1d 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -60,6 +60,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev)
INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
spin_lock_init(&private->cp_lock);
+ spin_lock_init(&private->crw_lock);

private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX);
if (!private->cp.guest_cp)
@@ -137,10 +138,12 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
container_of(vdev, struct vfio_ccw_private, vdev);
struct vfio_ccw_crw *crw, *temp;

+ spin_lock(&private->crw_lock);
list_for_each_entry_safe(crw, temp, &private->crw, next) {
list_del(&crw->next);
kfree(crw);
}
+ spin_unlock(&private->crw_lock);

kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index 061b594fde36..4a43609d6422 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -120,6 +120,8 @@ struct vfio_ccw_private {

struct irb irb;
union scsw scsw;
+
+ spinlock_t crw_lock;
struct list_head crw;

struct eventfd_ctx *io_trigger;
--
2.53.0