aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r--drivers/s390/cio/vfio_ccw_cp.c55
1 files changed, 50 insertions, 5 deletions
diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 086faf2dacd3..0467838aed23 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -294,6 +294,10 @@ static long copy_ccw_from_iova(struct channel_program *cp,
/*
* Helpers to operate ccwchain.
*/
+#define ccw_is_read(_ccw) (((_ccw)->cmd_code & 0x03) == 0x02)
+#define ccw_is_read_backward(_ccw) (((_ccw)->cmd_code & 0x0F) == 0x0C)
+#define ccw_is_sense(_ccw) (((_ccw)->cmd_code & 0x0F) == CCW_CMD_BASIC_SENSE)
+
#define ccw_is_test(_ccw) (((_ccw)->cmd_code & 0x0F) == 0)
#define ccw_is_noop(_ccw) ((_ccw)->cmd_code == CCW_CMD_NOOP)
@@ -301,11 +305,40 @@ static long copy_ccw_from_iova(struct channel_program *cp,
#define ccw_is_tic(_ccw) ((_ccw)->cmd_code == CCW_CMD_TIC)
#define ccw_is_idal(_ccw) ((_ccw)->flags & CCW_FLAG_IDA)
-
+#define ccw_is_skip(_ccw) ((_ccw)->flags & CCW_FLAG_SKIP)
#define ccw_is_chain(_ccw) ((_ccw)->flags & (CCW_FLAG_CC | CCW_FLAG_DC))
/*
+ * ccw_does_data_transfer()
+ *
+ * Determine whether a CCW will move any data, such that the guest pages
+ * would need to be pinned before performing the I/O.
+ *
+ * Returns 1 if yes, 0 if no.
+ */
+static inline int ccw_does_data_transfer(struct ccw1 *ccw)
+{
+ /* If the skip flag is off, then data will be transferred */
+ if (!ccw_is_skip(ccw))
+ return 1;
+
+ /*
+ * If the skip flag is on, it is only meaningful if the command
+ * code is a read, read backward, sense, or sense ID. In those
+ * cases, no data will be transferred.
+ */
+ if (ccw_is_read(ccw) || ccw_is_read_backward(ccw))
+ return 0;
+
+ if (ccw_is_sense(ccw))
+ return 0;
+
+ /* The skip flag is on, but it is ignored for this command code. */
+ return 1;
+}
+
+/*
* is_cpa_within_range()
*
* @cpa: channel program address being questioned
@@ -559,6 +592,7 @@ static int ccwchain_fetch_direct(struct ccwchain *chain,
struct pfn_array_table *pat;
unsigned long *idaws;
int ret;
+ int idaw_nr = 1;
ccw = chain->ch_ccw + idx;
@@ -570,6 +604,8 @@ static int ccwchain_fetch_direct(struct ccwchain *chain,
*/
ccw->flags |= CCW_FLAG_IDA;
return 0;
+ } else {
+ idaw_nr = idal_nr_words((void *)(u64)ccw->cda, ccw->count);
}
/*
@@ -586,12 +622,16 @@ static int ccwchain_fetch_direct(struct ccwchain *chain,
if (ret < 0)
goto out_unpin;
- ret = pfn_array_pin(pat->pat_pa, cp->mdev);
- if (ret < 0)
- goto out_unpin;
+ if (ccw_does_data_transfer(ccw)) {
+ ret = pfn_array_pin(pat->pat_pa, cp->mdev);
+ if (ret < 0)
+ goto out_unpin;
+ } else {
+ pat->pat_pa->pa_nr = 0;
+ }
/* Translate this direct ccw to a idal ccw. */
- idaws = kcalloc(ret, sizeof(*idaws), GFP_DMA | GFP_KERNEL);
+ idaws = kcalloc(idaw_nr, sizeof(*idaws), GFP_DMA | GFP_KERNEL);
if (!idaws) {
ret = -ENOMEM;
goto out_unpin;
@@ -661,6 +701,11 @@ static int ccwchain_fetch_idal(struct ccwchain *chain,
if (ret < 0)
goto out_free_idaws;
+ if (!ccw_does_data_transfer(ccw)) {
+ pa->pa_nr = 0;
+ continue;
+ }
+
ret = pfn_array_pin(pa, cp->mdev);
if (ret < 0)
goto out_free_idaws;