diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index 9208c0e56c33..059b88c94378 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -86,10 +86,13 @@ static void vfio_ccw_sch_io_todo(struct work_struct *work)
 
 	is_final = !(scsw_actl(&irb->scsw) &
 		     (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT));
-	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);
+	if (scsw_is_solicited(&irb->scsw) && private->cp) {
+		cp_update_scsw(private->cp, &irb->scsw);
+		if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) {
+			struct channel_program *cp = private->cp;
+			private->cp = NULL;
+			cp_free(cp);
+		}
 	}
 	mutex_lock(&private->io_mutex);
 	memcpy(private->io_region->irb_area, irb, sizeof(*irb));
@@ -129,9 +132,9 @@ static int vfio_ccw_sch_probe(struct subchannel *sch)
 	if (!private)
 		return -ENOMEM;
 
-	private->cp.guest_cp = kcalloc(CCWCHAIN_LEN_MAX, sizeof(struct ccw1),
+	private->cp_area.guest_cp = kcalloc(CCWCHAIN_LEN_MAX, sizeof(struct ccw1),
 				       GFP_KERNEL);
-	if (!private->cp.guest_cp)
+	if (!private->cp_area.guest_cp)
 		goto out_free;
 
 	private->io_region = kmem_cache_zalloc(vfio_ccw_io_region,
@@ -174,7 +177,7 @@ static int vfio_ccw_sch_probe(struct subchannel *sch)
 		kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
 	if (private->io_region)
 		kmem_cache_free(vfio_ccw_io_region, private->io_region);
-	kfree(private->cp.guest_cp);
+	kfree(private->cp_area.guest_cp);
 	kfree(private);
 	return ret;
 }
@@ -191,7 +194,7 @@ static int vfio_ccw_sch_remove(struct subchannel *sch)
 
 	kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
 	kmem_cache_free(vfio_ccw_io_region, private->io_region);
-	kfree(private->cp.guest_cp);
+	kfree(private->cp_area.guest_cp);
 	kfree(private);
 
 	return 0;
diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c
index 49d9d3da0282..543d007ddc46 100644
--- a/drivers/s390/cio/vfio_ccw_fsm.c
+++ b/drivers/s390/cio/vfio_ccw_fsm.c
@@ -18,7 +18,8 @@
 #define CREATE_TRACE_POINTS
 #include "vfio_ccw_trace.h"
 
-static int fsm_io_helper(struct vfio_ccw_private *private)
+static int fsm_io_helper(struct vfio_ccw_private *private,
+			 struct channel_program *cp)
 {
 	struct subchannel *sch;
 	union orb *orb;
@@ -31,7 +32,7 @@ static int fsm_io_helper(struct vfio_ccw_private *private)
 
 	spin_lock_irqsave(sch->lock, flags);
 
-	orb = cp_get_orb(&private->cp, (u32)(addr_t)sch, sch->lpm);
+	orb = cp_get_orb(cp, (u32)(addr_t)sch, sch->lpm);
 	if (!orb) {
 		ret = -EIO;
 		goto out;
@@ -47,6 +48,7 @@ static int fsm_io_helper(struct vfio_ccw_private *private)
 		 */
 		sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND;
 		ret = 0;
+		private->cp = cp;
 		private->state = VFIO_CCW_STATE_CP_PENDING;
 		break;
 	case 1:		/* Status pending */
@@ -236,31 +238,38 @@ static void fsm_io_request(struct vfio_ccw_private *private,
 	if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) {
 		orb = (union orb *)io_region->orb_area;
 
+		/* I/O already in progress? Should not happen (bug in FSM?). */
+		if (private->cp) {
+			io_region->ret_code = -EBUSY;
+			errstr = "cp in progress";
+			goto err_out;
+		}
 		/* Don't try to build a cp if transport mode is specified. */
 		if (orb->tm.b) {
 			io_region->ret_code = -EOPNOTSUPP;
 			errstr = "transport mode";
 			goto err_out;
 		}
-		io_region->ret_code = cp_init(&private->cp, mdev_dev(mdev),
-					      orb);
+		io_region->ret_code = cp_init(&private->cp_area,
+					      mdev_dev(mdev), orb);
 		if (io_region->ret_code) {
 			errstr = "cp init";
 			goto err_out;
 		}
 
-		io_region->ret_code = cp_prefetch(&private->cp);
+		io_region->ret_code = cp_prefetch(&private->cp_area);
 		if (io_region->ret_code) {
 			errstr = "cp prefetch";
-			cp_free(&private->cp);
+			cp_free(&private->cp_area);
 			goto err_out;
 		}
 
 		/* Start channel program and wait for I/O interrupt. */
-		io_region->ret_code = fsm_io_helper(private);
+		io_region->ret_code = fsm_io_helper(private,
+						    &private->cp_area);
 		if (io_region->ret_code) {
 			errstr = "cp fsm_io_helper";
-			cp_free(&private->cp);
+			cp_free(&private->cp_area);
 			goto err_out;
 		}
 		return;
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 5eb61116ca6f..5ad6a7b672bd 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -58,13 +58,14 @@ static int vfio_ccw_mdev_notifier(struct notifier_block *nb,
 	if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP) {
 		struct vfio_iommu_type1_dma_unmap *unmap = data;
 
-		if (!cp_iova_pinned(&private->cp, unmap->iova))
+		if (!cp_iova_pinned(&private->cp_area, unmap->iova))
 			return NOTIFY_OK;
 
 		if (vfio_ccw_mdev_reset(private->mdev))
 			return NOTIFY_BAD;
 
-		cp_free(&private->cp);
+		private->cp = NULL;
+		cp_free(&private->cp_area);
 		return NOTIFY_OK;
 	}
 
@@ -139,7 +140,8 @@ static int vfio_ccw_mdev_remove(struct mdev_device *mdev)
 		/* The state will be NOT_OPER on error. */
 	}
 
-	cp_free(&private->cp);
+	private->cp = NULL;
+	cp_free(&private->cp_area);
 	private->mdev = NULL;
 	atomic_inc(&private->avail);
 
@@ -180,7 +182,8 @@ static void vfio_ccw_mdev_release(struct mdev_device *mdev)
 		/* The state will be NOT_OPER on error. */
 	}
 
-	cp_free(&private->cp);
+	private->cp = NULL;
+	cp_free(&private->cp_area);
 	vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY,
 				 &private->nb);
 
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index f1092c3dc1b1..e792a20202c3 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -68,7 +68,8 @@ int vfio_ccw_register_async_dev_regions(struct vfio_ccw_private *private);
  * @region: additional regions for other subchannel operations
  * @cmd_region: MMIO region for asynchronous I/O commands other than START
  * @num_regions: number of additional regions
- * @cp: channel program for the current I/O operation
+ * @cp_area: channel program memory area
+ * @cp: pointer to channel program for the current I/O operation
  * @irb: irb info received from interrupt
  * @scsw: scsw info
  * @io_trigger: eventfd ctx for signaling userspace I/O results
@@ -87,7 +88,8 @@ struct vfio_ccw_private {
 	struct ccw_cmd_region	*cmd_region;
 	int num_regions;
 
-	struct channel_program	cp;
+	struct channel_program cp_area;
+	struct channel_program	*cp;
 	struct irb		irb;
 	union scsw		scsw;
 
