[PATCH v1 4/6] s390/vfio_ccw: copy maximum possible IDAL from guest
From: Eric Farman
Date: Tue Jul 14 2026 - 19:25:36 EST
An Indirect Data Address word is always 2K/4K aligned (depending on
format/type), except for the first word in a list. This unaligned
word makes calculating the number of addresses in a list challenging.
The current code attempts to be efficient about this by reading the
first word before making its calculations, but it introduces
inefficiencies trying to do the math on supposedly equal values.
Since an IDAL list cannot cross a 2K/4K boundary, copy the maximum
possible list in a way similar to guest_cp, and use that as the source
for populating the host IDAL.
Fixes: 01aa26c672c0 ("s390/cio: Combine direct and indirect CCW paths")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Eric Farman <farman@xxxxxxxxxxxxx>
---
drivers/s390/cio/vfio_ccw_cp.c | 27 +++++++++++++++++----------
drivers/s390/cio/vfio_ccw_cp.h | 1 +
drivers/s390/cio/vfio_ccw_ops.c | 9 ++++++++-
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 74b1f25e01e7..dac53e26509e 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -523,16 +523,24 @@ static int ccwchain_fetch_tic(struct ccw1 *ccw,
return -EFAULT;
}
+static int calc_max_idal_len(struct ccw1 *ccw, struct channel_program *cp)
+{
+ int idal_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
+ int idal_mask = ~(idal_size - 1);
+ int idal_len = idal_size - (ccw->cda & ~idal_mask);
+
+ /* This overestimates for Format-1 or 2K-Format-2 IDAWs */
+ return idal_len / 8;
+}
+
static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int idaw_nr)
{
- struct vfio_device *vdev =
- &container_of(cp, struct vfio_ccw_private, cp)->vdev;
dma64_t *idaws;
dma32_t *idaws_f1;
int idal_len = idaw_nr * sizeof(*idaws);
int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
int idaw_mask = ~(idaw_size - 1);
- int i, ret;
+ int i;
idaws = kzalloc_objs(*idaws, idaw_nr, GFP_DMA | GFP_KERNEL);
if (!idaws)
@@ -540,11 +548,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int
if (ccw_is_idal(ccw)) {
/* Copy IDAL from guest */
- ret = vfio_dma_rw(vdev, dma32_to_u32(ccw->cda), idaws, idal_len, false);
- if (ret) {
- kfree(idaws);
- return ERR_PTR(ret);
- }
+ memcpy(idaws, cp->guest_idal, idal_len);
} else {
/* Fabricate an IDAL based off CCW data address */
if (cp->orb.cmd.c64) {
@@ -586,7 +590,7 @@ static int ccw_count_idaws(struct ccw1 *ccw,
struct vfio_device *vdev =
&container_of(cp, struct vfio_ccw_private, cp)->vdev;
u64 iova;
- int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32);
+ int size = calc_max_idal_len(ccw, cp);
int ret;
int bytes = 1;
@@ -596,10 +600,13 @@ static int ccw_count_idaws(struct ccw1 *ccw,
if (ccw_is_idal(ccw)) {
/* Read first IDAW to check its starting address. */
/* All subsequent IDAWs will be 2K- or 4K-aligned. */
- ret = vfio_dma_rw(vdev, dma32_to_u32(ccw->cda), &iova, size, false);
+ ret = vfio_dma_rw(vdev, dma32_to_u32(ccw->cda),
+ cp->guest_idal, size, false);
if (ret)
return ret;
+ iova = cp->guest_idal[0];
+
/*
* Format-1 IDAWs only occupy the first 32 bits,
* and bit 0 is always off.
diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h
index dc91a317ef19..f33fea569b14 100644
--- a/drivers/s390/cio/vfio_ccw_cp.h
+++ b/drivers/s390/cio/vfio_ccw_cp.h
@@ -43,6 +43,7 @@ struct channel_program {
union orb orb;
bool initialized;
struct ccw1 *guest_cp;
+ dma64_t *guest_idal;
int ccwchain_count;
};
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 45ec722d25ea..afe9448c165e 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -55,9 +55,13 @@ 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);
+ private->cp.guest_idal = kzalloc_objs(dma64_t, 512);
+ if (!private->cp.guest_idal)
+ goto out_free_private;
+
private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX);
if (!private->cp.guest_cp)
- goto out_free_private;
+ goto out_free_idal;
private->io_region = kmem_cache_zalloc(vfio_ccw_io_region,
GFP_KERNEL | GFP_DMA);
@@ -89,6 +93,8 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev)
kmem_cache_free(vfio_ccw_io_region, private->io_region);
out_free_cp:
kfree(private->cp.guest_cp);
+out_free_idal:
+ kfree(private->cp.guest_idal);
out_free_private:
mutex_destroy(&private->io_mutex);
return -ENOMEM;
@@ -141,6 +147,7 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
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.guest_idal);
mutex_destroy(&private->io_mutex);
}
--
2.53.0