]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
s390/vfio_ccw: Implement a crw lock
authorEric Farman <farman@linux.ibm.com>
Tue, 28 Jul 2026 03:30:22 +0000 (05:30 +0200)
committerChristian Borntraeger <borntraeger@linux.ibm.com>
Thu, 30 Jul 2026 17:50:48 +0000 (19:50 +0200)
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.

Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
Cc: stable@vger.kernel.org
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Reviewed-by: Farhan Ali <alifm@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
Signed-off-by: Christian Borntraeger <borntraeger@linux.ibm.com>
drivers/s390/cio/vfio_ccw_chp.c
drivers/s390/cio/vfio_ccw_drv.c
drivers/s390/cio/vfio_ccw_ops.c
drivers/s390/cio/vfio_ccw_private.h

index 9269b54f5cfd4f8b471d5aa103c8959e5e331697..7708eb4d6de0be582bbb846dc9dae3283f64f018 100644 (file)
@@ -93,18 +93,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
        loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
        struct ccw_crw_region *region;
        struct vfio_ccw_crw *crw;
+       unsigned long flags;
        int ret;
 
        if (pos + count > sizeof(*region))
                return -EINVAL;
 
        mutex_lock(&private->io_mutex);
-       crw = list_first_entry_or_null(&private->crw,
-                                      struct vfio_ccw_crw, next);
-
-       if (crw)
-               list_del(&crw->next);
-
        if (i >= private->num_regions) {
                ret = -EINVAL;
                goto out;
@@ -113,6 +108,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
        i = array_index_nospec(i, private->num_regions);
        region = private->region[i].data;
 
+       spin_lock_irqsave(&private->crw_lock, flags);
+       crw = list_first_entry_or_null(&private->crw,
+                                      struct vfio_ccw_crw, next);
+
+       if (crw)
+               list_del(&crw->next);
+
+       /* Drop CRW lock while copying to userspace */
+       spin_unlock_irqrestore(&private->crw_lock, flags);
+
        if (crw)
                memcpy(&region->crw, &crw->crw, sizeof(region->crw));
 
@@ -122,15 +127,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
                ret = count;
 
        region->crw = 0;
-
-out:
-       mutex_unlock(&private->io_mutex);
-
        kfree(crw);
 
        /* Notify the guest if more CRWs are on our queue */
+       spin_lock_irqsave(&private->crw_lock, flags);
        if (!list_empty(&private->crw) && private->crw_trigger)
                eventfd_signal(private->crw_trigger);
+       spin_unlock_irqrestore(&private->crw_lock, flags);
+
+out:
+       mutex_unlock(&private->io_mutex);
 
        return ret;
 }
index 757ff5b2556e33ccc89f05dbafe73cb023198755..ab6b518cc353f90bd821fdc606e2e43bc5e5a63b 100644 (file)
@@ -118,11 +118,14 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
 void vfio_ccw_crw_todo(struct work_struct *work)
 {
        struct vfio_ccw_private *private;
+       unsigned long flags;
 
        private = container_of(work, struct vfio_ccw_private, crw_work);
 
+       spin_lock_irqsave(&private->crw_lock, flags);
        if (!list_empty(&private->crw) && private->crw_trigger)
                eventfd_signal(private->crw_trigger);
+       spin_unlock_irqrestore(&private->crw_lock, flags);
 }
 
 void vfio_ccw_notoper_todo(struct work_struct *work)
@@ -286,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
                               unsigned int rsid)
 {
        struct vfio_ccw_crw *crw;
+       unsigned long flags;
 
        /*
         * If unable to allocate a CRW, just drop the event and
@@ -303,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
        crw->crw.erc = erc;
        crw->crw.rsid = rsid;
 
+       spin_lock_irqsave(&private->crw_lock, flags);
        list_add_tail(&crw->next, &private->crw);
+       spin_unlock_irqrestore(&private->crw_lock, flags);
        queue_work(vfio_ccw_work_q, &private->crw_work);
 }
 
index 1df6d649565b80a7390255b74ffce83d64d9f05f..5ce91285c7d52a58c49d287f33e1fc1c23672d50 100644 (file)
@@ -55,6 +55,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);
        INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo);
+       spin_lock_init(&private->crw_lock);
 
        private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX);
        if (!private->cp.guest_cp)
@@ -131,6 +132,7 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
        struct vfio_ccw_private *private =
                container_of(vdev, struct vfio_ccw_private, vdev);
        struct vfio_ccw_crw *crw, *temp;
+       unsigned long flags;
 
        /*
         * Ensure these work items are fully drained, so none can
@@ -146,10 +148,12 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
        cancel_work_sync(&private->crw_work);
        flush_work(&private->notoper_work);
 
+       spin_lock_irqsave(&private->crw_lock, flags);
        list_for_each_entry_safe(crw, temp, &private->crw, next) {
                list_del(&crw->next);
                kfree(crw);
        }
+       spin_unlock_irqrestore(&private->crw_lock, flags);
 
        kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
        kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
index 739121116ab64c930fd6e6e11b2046171db04a4b..3bd0171d38d04940062c5400e164a51111b1f573 100644 (file)
@@ -98,6 +98,8 @@ struct vfio_ccw_parent {
  * @cp: channel program for the current I/O operation
  * @irb: irb info received from interrupt
  * @scsw: scsw info
+ * @crw_lock: serialization of CRW list information
+ * @crw: list of Channel Report Word elements
  * @io_trigger: eventfd ctx for signaling userspace I/O results
  * @crw_trigger: eventfd ctx for signaling userspace CRW information
  * @req_trigger: eventfd ctx for signaling userspace to return device
@@ -120,6 +122,8 @@ struct vfio_ccw_private {
        struct channel_program  cp;
        struct irb              irb;
        union scsw              scsw;
+
+       spinlock_t              crw_lock;
        struct list_head        crw;
 
        struct eventfd_ctx      *io_trigger;