The channel_program struct is manipulated without a serialization mechanism to ensure consistent behavior. Take a broad stroke of putting the entire structure behind a spin lock, which can be acquired with or without the mutex guarding the I/O regions that feed into the population of the channel program. Signed-off-by: Eric Farman --- drivers/s390/cio/vfio_ccw_cp.c | 13 ++++++++++++- drivers/s390/cio/vfio_ccw_cp.h | 1 + drivers/s390/cio/vfio_ccw_drv.c | 6 +++++- drivers/s390/cio/vfio_ccw_ops.c | 8 +++++++- drivers/s390/cio/vfio_ccw_private.h | 2 ++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index c6f0264fb5d3..7bfb3a952817 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -790,7 +790,7 @@ int cp_init(struct channel_program *cp, union orb *orb) * @cp, which must have been returned by a previous call to cp_init(). * Otherwise, undefined behavior occurs. */ -void cp_free(struct channel_program *cp) +void cp_free_locked(struct channel_program *cp) { struct vfio_device *vdev = &container_of(cp, struct vfio_ccw_private, cp)->vdev; @@ -810,6 +810,17 @@ void cp_free(struct channel_program *cp) } } +void cp_free(struct channel_program *cp) +{ + struct vfio_ccw_private *private = + container_of(cp, struct vfio_ccw_private, cp); + unsigned long flags; + + spin_lock_irqsave(&private->cp_lock, flags); + cp_free_locked(cp); + spin_unlock_irqrestore(&private->cp_lock, flags); +} + /** * cp_prefetch() - translate a guest physical address channel program to * a real-device runnable channel program. diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h index d547dbc969cc..edfb47a292ca 100644 --- a/drivers/s390/cio/vfio_ccw_cp.h +++ b/drivers/s390/cio/vfio_ccw_cp.h @@ -48,6 +48,7 @@ struct channel_program { }; int cp_init(struct channel_program *cp, union orb *orb); +void cp_free_locked(struct channel_program *cp); void cp_free(struct channel_program *cp); int cp_prefetch(struct channel_program *cp); union orb *cp_get_orb(struct channel_program *cp, struct subchannel *sch); diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 1a095085bc72..bfb68de6e52c 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -83,6 +83,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) { struct vfio_ccw_private *private; struct irb *irb; + unsigned long flags; bool is_final; bool cp_is_finished = false; @@ -91,13 +92,16 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) is_final = !(scsw_actl(&irb->scsw) & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)); + spin_lock_irqsave(&private->cp_lock, flags); if (scsw_is_solicited(&irb->scsw)) { cp_update_scsw(&private->cp, &irb->scsw); if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) { - cp_free(&private->cp); + cp_free_locked(&private->cp); cp_is_finished = true; } } + spin_unlock_irqrestore(&private->cp_lock, flags); + mutex_lock(&private->io_mutex); memcpy(private->io_region->irb_area, irb, sizeof(*irb)); mutex_unlock(&private->io_mutex); diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index e695cd985369..d2475b3158cf 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -36,10 +36,15 @@ static void vfio_ccw_dma_unmap(struct vfio_device *vdev, u64 iova, u64 length) { struct vfio_ccw_private *private = container_of(vdev, struct vfio_ccw_private, vdev); + unsigned long flags; /* Drivers MUST unpin pages in response to an invalidation. */ - if (!cp_iova_pinned(&private->cp, iova, length)) + spin_lock_irqsave(&private->cp_lock, flags); + if (!cp_iova_pinned(&private->cp, iova, length)) { + spin_unlock_irqrestore(&private->cp_lock, flags); return; + } + spin_unlock_irqrestore(&private->cp_lock, flags); vfio_ccw_mdev_reset(private); } @@ -54,6 +59,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev) INIT_LIST_HEAD(&private->crw); 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); private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX); if (!private->cp.guest_cp) diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 0501d4bbcdbd..061b594fde36 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -115,7 +115,9 @@ struct vfio_ccw_private { struct ccw_crw_region *crw_region; int num_regions; + spinlock_t cp_lock; struct channel_program cp; + struct irb irb; union scsw scsw; struct list_head crw; -- 2.53.0