/*
  * 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)
 #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()
  *
        struct pfn_array_table *pat;
        unsigned long *idaws;
        int ret;
+       int idaw_nr = 1;
 
        ccw = chain->ch_ccw + idx;
 
                 */
                ccw->flags |= CCW_FLAG_IDA;
                return 0;
+       } else {
+               idaw_nr = idal_nr_words((void *)(u64)ccw->cda, ccw->count);
        }
 
        /*
        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;
                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;