- Makes initial pass-through devices checks less strict. The main reason for that...
authorvlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Tue, 2 Mar 2010 17:32:23 +0000 (17:32 +0000)
committervlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Tue, 2 Mar 2010 17:32:23 +0000 (17:32 +0000)
 - Workarounds in vdisk readv/writev limitation to process only limited number of entries per call

git-svn-id: https://scst.svn.sourceforge.net/svnroot/scst/trunk@1528 d57e44dd-8a1f-0410-8b47-8ef2f437770f

scst/src/dev_handlers/scst_cdrom.c
scst/src/dev_handlers/scst_changer.c
scst/src/dev_handlers/scst_disk.c
scst/src/dev_handlers/scst_modisk.c
scst/src/dev_handlers/scst_processor.c
scst/src/dev_handlers/scst_raid.c
scst/src/dev_handlers/scst_tape.c
scst/src/dev_handlers/scst_vdisk.c

index 80f95ea..763ae56 100644 (file)
@@ -65,7 +65,7 @@ static struct scst_dev_type cdrom_devtype = {
  *************************************************************/
 static int cdrom_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        uint8_t cmd[10];
        const int buffer_size = 512;
        uint8_t *buffer = NULL;
@@ -106,10 +106,11 @@ static int cdrom_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        while (1) {
                memset(buffer, 0, buffer_size);
+               memset(sense_buffer, 0, sizeof(sense_buffer));
                data_dir = SCST_DATA_READ;
 
                TRACE_DBG("%s", "Doing READ_CAPACITY");
-               res = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
+               rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
                                   buffer_size, sense_buffer,
                                   SCST_GENERIC_CDROM_REG_TIMEOUT, 3, 0
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
@@ -117,9 +118,9 @@ static int cdrom_attach(struct scst_device *dev)
 #endif
                                  );
 
-               TRACE_DBG("READ_CAPACITY done: %x", res);
+               TRACE_DBG("READ_CAPACITY done: %x", rc);
 
-               if ((res == 0) ||
+               if ((rc == 0) ||
                    !scst_analyze_sense(sense_buffer,
                                sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
                                UNIT_ATTENTION, 0, 0))
@@ -129,11 +130,12 @@ static int cdrom_attach(struct scst_device *dev)
                        PRINT_ERROR("UA not cleared after %d retries",
                                SCST_DEV_UA_RETRIES);
                        params->block_shift = CDROM_DEF_BLOCK_SHIFT;
+                       res = -ENODEV;
                        goto out_free_buf;
                }
        }
 
-       if (res == 0) {
+       if (rc == 0) {
                int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
                                      (buffer[6] << 8) | (buffer[7] << 0));
                if (sector_size == 0)
@@ -144,9 +146,11 @@ static int cdrom_attach(struct scst_device *dev)
                TRACE_DBG("Sector size is %i scsi_level %d(SCSI_2 %d)",
                        sector_size, dev->scsi_dev->scsi_level, SCSI_2);
        } else {
-               TRACE_BUFFER("Returned sense", sense_buffer,
-                       sizeof(sense_buffer));
                params->block_shift = CDROM_DEF_BLOCK_SHIFT;
+               TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
+                       "sector size %d", rc, params->block_shift);
+               PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
+                       sizeof(sense_buffer));
        }
 
        res = scst_obtain_device_parameters(dev);
index 3d0b072..e6f6735 100644 (file)
@@ -61,7 +61,7 @@ static struct scst_dev_type changer_devtype = {
  *************************************************************/
 static int changer_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        int retries;
 
        TRACE_ENTRY();
@@ -86,18 +86,19 @@ static int changer_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        do {
                TRACE_DBG("%s", "Doing TEST_UNIT_READY");
-               res = scsi_test_unit_ready(dev->scsi_dev,
+               rc = scsi_test_unit_ready(dev->scsi_dev,
                        SCST_GENERIC_CHANGER_TIMEOUT, CHANGER_RETRIES
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
                                          );
 #else
                                          , NULL);
 #endif
-               TRACE_DBG("TEST_UNIT_READY done: %x", res);
-       } while ((--retries > 0) && res);
-       if (res) {
-               res = -ENODEV;
-               goto out;
+               TRACE_DBG("TEST_UNIT_READY done: %x", rc);
+       } while ((--retries > 0) && rc);
+
+       if (rc) {
+               PRINT_WARNING("Unit not ready: %x", rc);
+               /* Let's try not to be too smart and continue processing */
        }
 
        res = scst_obtain_device_parameters(dev);
index ad30507..1047402 100644 (file)
@@ -152,7 +152,7 @@ module_exit(exit_scst_disk_driver);
  *************************************************************/
 static int disk_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        uint8_t cmd[10];
        const int buffer_size = 512;
        uint8_t *buffer = NULL;
@@ -193,10 +193,11 @@ static int disk_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        while (1) {
                memset(buffer, 0, buffer_size);
+               memset(sense_buffer, 0, sizeof(sense_buffer));
                data_dir = SCST_DATA_READ;
 
                TRACE_DBG("%s", "Doing READ_CAPACITY");
-               res = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
+               rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
                                   buffer_size, sense_buffer,
                                   SCST_GENERIC_DISK_REG_TIMEOUT, 3, 0
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
@@ -204,15 +205,12 @@ static int disk_attach(struct scst_device *dev)
 #endif
                                  );
 
-               TRACE_DBG("READ_CAPACITY done: %x", res);
+               TRACE_DBG("READ_CAPACITY done: %x", rc);
 
-               if ((res == 0) ||
+               if ((rc == 0) ||
                    !scst_analyze_sense(sense_buffer,
-                               sizeof(sense_buffer), SCST_SENSE_ALL_VALID,
-                               SCST_LOAD_SENSE(scst_sense_medium_changed_UA)) ||
-                   !scst_analyze_sense(sense_buffer, sizeof(sense_buffer),
-                               SCST_SENSE_KEY_VALID | SCST_SENSE_ASC_VALID,
-                               UNIT_ATTENTION, 0x29, 0))
+                               sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
+                               UNIT_ATTENTION, 0, 0))
                        break;
                if (!--retries) {
                        PRINT_ERROR("UA not clear after %d retries",
@@ -221,7 +219,7 @@ static int disk_attach(struct scst_device *dev)
                        goto out_free_buf;
                }
        }
-       if (res == 0) {
+       if (rc == 0) {
                int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
                                     (buffer[6] << 8) | (buffer[7] << 0));
                if (sector_size == 0)
@@ -230,10 +228,11 @@ static int disk_attach(struct scst_device *dev)
                        params->block_shift =
                                scst_calc_block_shift(sector_size);
        } else {
-               TRACE_BUFFER("Returned sense", sense_buffer,
+               params->block_shift = DISK_DEF_BLOCK_SHIFT;
+               TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
+                       "sector size %d", rc, params->block_shift);
+               PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
                        sizeof(sense_buffer));
-               res = -ENODEV;
-               goto out_free_buf;
        }
 
        res = scst_obtain_device_parameters(dev);
index 5512635..ab347b9 100644 (file)
@@ -152,7 +152,7 @@ module_exit(exit_scst_modisk_driver);
  *************************************************************/
 static int modisk_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        uint8_t cmd[10];
        const int buffer_size = 512;
        uint8_t *buffer = NULL;
@@ -207,10 +207,11 @@ static int modisk_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        while (1) {
                memset(buffer, 0, buffer_size);
+               memset(sense_buffer, 0, sizeof(sense_buffer));
                data_dir = SCST_DATA_READ;
 
                TRACE_DBG("%s", "Doing READ_CAPACITY");
-               res = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
+               rc = scsi_execute(dev->scsi_dev, cmd, data_dir, buffer,
                                   buffer_size, sense_buffer,
                                   SCST_GENERIC_MODISK_REG_TIMEOUT, 3, 0
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
@@ -218,9 +219,9 @@ static int modisk_attach(struct scst_device *dev)
 #endif
                                  );
 
-               TRACE_DBG("READ_CAPACITY done: %x", res);
+               TRACE_DBG("READ_CAPACITY done: %x", rc);
 
-               if (!res || !scst_analyze_sense(sense_buffer,
+               if (!rc || !scst_analyze_sense(sense_buffer,
                                sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
                                UNIT_ATTENTION, 0, 0))
                        break;
@@ -232,7 +233,7 @@ static int modisk_attach(struct scst_device *dev)
                }
        }
 
-       if (res == 0) {
+       if (rc == 0) {
                int sector_size = ((buffer[4] << 24) | (buffer[5] << 16) |
                                       (buffer[6] << 8) | (buffer[7] << 0));
                if (sector_size == 0)
@@ -243,13 +244,11 @@ static int modisk_attach(struct scst_device *dev)
                TRACE_DBG("Sector size is %i scsi_level %d(SCSI_2 %d)",
                      sector_size, dev->scsi_dev->scsi_level, SCSI_2);
        } else {
-               TRACE_BUFFER("Returned sense", sense_buffer,
+               params->block_shift = MODISK_DEF_BLOCK_SHIFT;
+               TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
+                       "sector size %d", rc, params->block_shift);
+               PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
                        sizeof(sense_buffer));
-
-               if (sense_buffer[2] != NOT_READY) {
-                       res = -ENODEV;
-                       goto out_free_buf;
-               }
        }
 
        res = scst_obtain_device_parameters(dev);
index 55d4911..6facb80 100644 (file)
@@ -61,7 +61,7 @@ static struct scst_dev_type processor_devtype = {
  *************************************************************/
 static int processor_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        int retries;
 
        TRACE_ENTRY();
@@ -86,18 +86,19 @@ static int processor_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        do {
                TRACE_DBG("%s", "Doing TEST_UNIT_READY");
-               res = scsi_test_unit_ready(dev->scsi_dev,
+               rc = scsi_test_unit_ready(dev->scsi_dev,
                        SCST_GENERIC_PROCESSOR_TIMEOUT, PROCESSOR_RETRIES
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
                                          );
 #else
                                          , NULL);
 #endif
-               TRACE_DBG("TEST_UNIT_READY done: %x", res);
-       } while ((--retries > 0) && res);
-       if (res) {
-               res = -ENODEV;
-               goto out;
+               TRACE_DBG("TEST_UNIT_READY done: %x", rc);
+       } while ((--retries > 0) && rc);
+
+       if (rc) {
+               PRINT_WARNING("Unit not ready: %x", rc);
+               /* Let's try not to be too smart and continue processing */
        }
 
        res = scst_obtain_device_parameters(dev);
index e08b902..40e8e88 100644 (file)
@@ -61,7 +61,7 @@ static struct scst_dev_type raid_devtype = {
  *************************************************************/
 static int raid_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        int retries;
 
        TRACE_ENTRY();
@@ -86,18 +86,19 @@ static int raid_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        do {
                TRACE_DBG("%s", "Doing TEST_UNIT_READY");
-               res = scsi_test_unit_ready(dev->scsi_dev,
+               rc = scsi_test_unit_ready(dev->scsi_dev,
                        SCST_GENERIC_RAID_TIMEOUT, RAID_RETRIES
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
                                          );
 #else
                                          , NULL);
 #endif
-               TRACE_DBG("TEST_UNIT_READY done: %x", res);
-       } while ((--retries > 0) && res);
-       if (res) {
-               res = -ENODEV;
-               goto out;
+               TRACE_DBG("TEST_UNIT_READY done: %x", rc);
+       } while ((--retries > 0) && rc);
+
+       if (rc) {
+               PRINT_WARNING("Unit not ready: %x", rc);
+               /* Let's try not to be too smart and continue processing */
        }
 
        res = scst_obtain_device_parameters(dev);
index ffe4c87..9e19d6b 100644 (file)
@@ -157,7 +157,7 @@ module_exit(exit_scst_tape_driver);
  *************************************************************/
 static int tape_attach(struct scst_device *dev)
 {
-       int res = 0;
+       int res, rc;
        int retries;
        struct scsi_mode_data data;
        const int buffer_size = 512;
@@ -181,6 +181,8 @@ static int tape_attach(struct scst_device *dev)
                goto out;
        }
 
+       params->block_size = TAPE_DEF_BLOCK_SIZE;
+
        buffer = kmalloc(buffer_size, GFP_KERNEL);
        if (!buffer) {
                TRACE(TRACE_OUT_OF_MEM, "%s", "Memory allocation failure");
@@ -191,39 +193,40 @@ static int tape_attach(struct scst_device *dev)
        retries = SCST_DEV_UA_RETRIES;
        do {
                TRACE_DBG("%s", "Doing TEST_UNIT_READY");
-               res = scsi_test_unit_ready(dev->scsi_dev,
+               rc = scsi_test_unit_ready(dev->scsi_dev,
                        SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
                                          );
 #else
                                          , NULL);
 #endif
-               TRACE_DBG("TEST_UNIT_READY done: %x", res);
-       } while ((--retries > 0) && res);
-       if (res) {
-               res = -ENODEV;
-               goto out;
+               TRACE_DBG("TEST_UNIT_READY done: %x", rc);
+       } while ((--retries > 0) && rc);
+
+       if (rc) {
+               PRINT_WARNING("Unit not ready: %x", rc);
+               /* Let's try not to be too smart and continue processing */
+               goto obtain;
        }
 
        TRACE_DBG("%s", "Doing MODE_SENSE");
-       res = scsi_mode_sense(dev->scsi_dev,
+       rc = scsi_mode_sense(dev->scsi_dev,
                              ((dev->scsi_dev->scsi_level <= SCSI_2) ?
                               ((dev->scsi_dev->lun << 5) & 0xe0) : 0),
                              0 /* Mode Page 0 */,
                              buffer, buffer_size,
                              SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES,
                              &data, NULL);
-       TRACE_DBG("MODE_SENSE done: %x", res);
+       TRACE_DBG("MODE_SENSE done: %x", rc);
 
-       if (res == 0) {
+       if (rc == 0) {
                int medium_type, mode, speed, density;
                if (buffer[3] == 8) {
                        params->block_size = ((buffer[9] << 16) |
                                            (buffer[10] << 8) |
                                            (buffer[11] << 0));
-               } else {
+               } else
                        params->block_size = TAPE_DEF_BLOCK_SIZE;
-               }
                medium_type = buffer[1];
                mode = (buffer[2] & 0x70) >> 4;
                speed = buffer[2] & 0x0f;
@@ -232,10 +235,12 @@ static int tape_attach(struct scst_device *dev)
                      "speed 0x%02x dens 0x%02x", dev->scsi_dev->lun,
                      params->block_size, medium_type, mode, speed, density);
        } else {
+               PRINT_ERROR("MODE_SENSE failed: %x", rc);
                res = -ENODEV;
                goto out_free_buf;
        }
 
+obtain:
        res = scst_obtain_device_parameters(dev);
        if (res != 0) {
                PRINT_ERROR("Failed to obtain control parameters for device "
index aa4bddf..73a193a 100644 (file)
@@ -2353,7 +2353,7 @@ static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd,
 {
        int iv_count;
 
-       iv_count = scst_get_buf_count(cmd);
+       iv_count = min_t(int, scst_get_buf_count(cmd), UIO_MAXIOV);
        if (iv_count > thr->iv_count) {
                kfree(thr->iv);
                /* It can't be called in atomic context */
@@ -2382,6 +2382,7 @@ static void vdisk_exec_read(struct scst_cmd *cmd,
        struct file *fd = thr->fd;
        struct iovec *iv;
        int iv_count, i;
+       bool finished = false;
 
        TRACE_ENTRY();
 
@@ -2392,70 +2393,96 @@ static void vdisk_exec_read(struct scst_cmd *cmd,
        if (iv == NULL)
                goto out;
 
-       iv_count = 0;
-       full_len = 0;
-       i = -1;
        length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
-       while (length > 0) {
-               full_len += length;
-               i++;
-               iv_count++;
-               iv[i].iov_base = address;
-               iv[i].iov_len = length;
-               length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
-       }
        if (unlikely(length < 0)) {
-               PRINT_ERROR("scst_get_buf_() failed: %zd", length);
+               PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
                scst_set_cmd_error(cmd,
                    SCST_LOAD_SENSE(scst_sense_hardw_error));
-               goto out_put;
+               goto out;
        }
 
        old_fs = get_fs();
        set_fs(get_ds());
 
-       TRACE_DBG("reading(iv_count %d, full_len %zd)", iv_count, full_len);
-       /* SEEK */
-       if (fd->f_op->llseek)
-               err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
-       else
-               err = default_llseek(fd, loff, 0/*SEEK_SET*/);
-       if (err != loff) {
-               PRINT_ERROR("lseek trouble %lld != %lld",
-                           (long long unsigned int)err,
-                           (long long unsigned int)loff);
-               scst_set_cmd_error(cmd,
-                       SCST_LOAD_SENSE(scst_sense_hardw_error));
-               goto out_set_fs;
-       }
-
-       /* READ */
-       err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count,
-                       &fd->f_pos);
+       while (1) {
+               iv_count = 0;
+               full_len = 0;
+               i = -1;
+               while (length > 0) {
+                       full_len += length;
+                       i++;
+                       iv_count++;
+                       iv[i].iov_base = address;
+                       iv[i].iov_len = length;
+                       if (iv_count == UIO_MAXIOV)
+                               break;
+                       length = scst_get_buf_next(cmd,
+                               (uint8_t __force **)&address);
+               }
+               if (length == 0) {
+                       finished = true;
+                       if (unlikely(iv_count == 0))
+                               break;
+               } else if (unlikely(length < 0)) {
+                       PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
+                       scst_set_cmd_error(cmd,
+                           SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out_set_fs;
+               }
 
-       if ((err < 0) || (err < full_len)) {
-               PRINT_ERROR("readv() returned %lld from %zd",
-                           (long long unsigned int)err,
-                           full_len);
-               if (err == -EAGAIN)
-                       scst_set_busy(cmd);
-               else {
+               TRACE_DBG("(iv_count %d, full_len %zd)", iv_count, full_len);
+               /* SEEK */
+               if (fd->f_op->llseek)
+                       err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
+               else
+                       err = default_llseek(fd, loff, 0/*SEEK_SET*/);
+               if (err != loff) {
+                       PRINT_ERROR("lseek trouble %lld != %lld",
+                                   (long long unsigned int)err,
+                                   (long long unsigned int)loff);
                        scst_set_cmd_error(cmd,
-                           SCST_LOAD_SENSE(scst_sense_read_error));
+                               SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out_set_fs;
                }
-               goto out_set_fs;
-       }
 
-out_set_fs:
-       set_fs(old_fs);
+               /* READ */
+               err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count,
+                               &fd->f_pos);
 
-out_put:
-       for (; i >= 0; i--)
-               scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+               if ((err < 0) || (err < full_len)) {
+                       PRINT_ERROR("readv() returned %lld from %zd",
+                                   (long long unsigned int)err,
+                                   full_len);
+                       if (err == -EAGAIN)
+                               scst_set_busy(cmd);
+                       else {
+                               scst_set_cmd_error(cmd,
+                                   SCST_LOAD_SENSE(scst_sense_read_error));
+                       }
+                       goto out_set_fs;
+               }
+
+               for (i = 0; i < iv_count; i++)
+                       scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+
+               if (finished)
+                       break;
+
+               loff += full_len;
+               length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
+       };
+
+       set_fs(old_fs);
 
 out:
        TRACE_EXIT();
        return;
+
+out_set_fs:
+       set_fs(old_fs);
+       for (i = 0; i < iv_count; i++)
+               scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+       goto out;
 }
 
 static void vdisk_exec_write(struct scst_cmd *cmd,
@@ -2463,13 +2490,14 @@ static void vdisk_exec_write(struct scst_cmd *cmd,
 {
        mm_segment_t old_fs;
        loff_t err;
-       ssize_t length, full_len;
+       ssize_t length, full_len, saved_full_len;
        uint8_t __user *address;
        struct scst_vdisk_dev *virt_dev =
            (struct scst_vdisk_dev *)cmd->dev->dh_priv;
        struct file *fd = thr->fd;
        struct iovec *iv, *eiv;
-       int iv_count, eiv_count;
+       int i, iv_count, eiv_count;
+       bool finished = false;
 
        TRACE_ENTRY();
 
@@ -2480,102 +2508,128 @@ static void vdisk_exec_write(struct scst_cmd *cmd,
        if (iv == NULL)
                goto out;
 
-       iv_count = 0;
-       full_len = 0;
        length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
-       while (length > 0) {
-               full_len += length;
-               iv[iv_count].iov_base = address;
-               iv[iv_count].iov_len = length;
-               iv_count++;
-               length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
-       }
        if (unlikely(length < 0)) {
-               PRINT_ERROR("scst_get_buf_() failed: %zd", length);
+               PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
                scst_set_cmd_error(cmd,
                    SCST_LOAD_SENSE(scst_sense_hardw_error));
-               goto out_put;
+               goto out;
        }
 
        old_fs = get_fs();
        set_fs(get_ds());
 
-       eiv = iv;
-       eiv_count = iv_count;
+       while (1) {
+               iv_count = 0;
+               full_len = 0;
+               i = -1;
+               while (length > 0) {
+                       full_len += length;
+                       i++;
+                       iv_count++;
+                       iv[i].iov_base = address;
+                       iv[i].iov_len = length;
+                       if (iv_count == UIO_MAXIOV)
+                               break;
+                       length = scst_get_buf_next(cmd,
+                               (uint8_t __force **)&address);
+               }
+               if (length == 0) {
+                       finished = true;
+                       if (unlikely(iv_count == 0))
+                               break;
+               } else if (unlikely(length < 0)) {
+                       PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
+                       scst_set_cmd_error(cmd,
+                           SCST_LOAD_SENSE(scst_sense_hardw_error));
+                       goto out_set_fs;
+               }
+
+               saved_full_len = full_len;
+               eiv = iv;
+               eiv_count = iv_count;
 restart:
-       TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len);
+               TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len);
 
-       /* SEEK */
-       if (fd->f_op->llseek)
-               err = fd->f_op->llseek(fd, loff, 0 /*SEEK_SET */);
-       else
-               err = default_llseek(fd, loff, 0 /*SEEK_SET */);
-       if (err != loff) {
-               PRINT_ERROR("lseek trouble %lld != %lld",
-                           (long long unsigned int)err,
-                           (long long unsigned int)loff);
-               scst_set_cmd_error(cmd,
+               /* SEEK */
+               if (fd->f_op->llseek)
+                       err = fd->f_op->llseek(fd, loff, 0 /*SEEK_SET */);
+               else
+                       err = default_llseek(fd, loff, 0 /*SEEK_SET */);
+               if (err != loff) {
+                       PRINT_ERROR("lseek trouble %lld != %lld",
+                                   (long long unsigned int)err,
+                                   (long long unsigned int)loff);
+                       scst_set_cmd_error(cmd,
                                   SCST_LOAD_SENSE(scst_sense_hardw_error));
-               goto out_set_fs;
-       }
+                       goto out_set_fs;
+               }
 
-       /* WRITE */
-       err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count,
-                        &fd->f_pos);
+               /* WRITE */
+               err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count,
+                                &fd->f_pos);
 
-       if (err < 0) {
-               PRINT_ERROR("write() returned %lld from %zd",
-                           (long long unsigned int)err,
-                           full_len);
-               if (err == -EAGAIN)
-                       scst_set_busy(cmd);
-               else {
-                       scst_set_cmd_error(cmd,
-                           SCST_LOAD_SENSE(scst_sense_write_error));
-               }
-               goto out_set_fs;
-       } else if (err < full_len) {
-               /*
-                * Probably that's wrong, but sometimes write() returns
-                * value less, than requested. Let's restart.
-                */
-               int i, e = eiv_count;
-               TRACE_MGMT_DBG("write() returned %d from %zd "
-                       "(iv_count=%d)", (int)err, full_len,
-                       eiv_count);
-               if (err == 0) {
-                       PRINT_INFO("Suspicious: write() returned 0 from "
-                               "%zd (iv_count=%d)", full_len, eiv_count);
-               }
-               full_len -= err;
-               for (i = 0; i < e; i++) {
-                       if ((long long)eiv->iov_len < err) {
-                               err -= eiv->iov_len;
-                               eiv++;
-                               eiv_count--;
-                       } else {
-                               eiv->iov_base =
-                                       (uint8_t __force __user *)eiv->iov_base +
-                                       err;
-                               eiv->iov_len -= err;
-                               break;
+               if (err < 0) {
+                       PRINT_ERROR("write() returned %lld from %zd",
+                                   (long long unsigned int)err,
+                                   full_len);
+                       if (err == -EAGAIN)
+                               scst_set_busy(cmd);
+                       else {
+                               scst_set_cmd_error(cmd,
+                                   SCST_LOAD_SENSE(scst_sense_write_error));
+                       }
+                       goto out_set_fs;
+               } else if (err < full_len) {
+                       /*
+                        * Probably that's wrong, but sometimes write() returns
+                        * value less, than requested. Let's restart.
+                        */
+                       int i, e = eiv_count;
+                       TRACE_MGMT_DBG("write() returned %d from %zd "
+                               "(iv_count=%d)", (int)err, full_len,
+                               eiv_count);
+                       if (err == 0) {
+                               PRINT_INFO("Suspicious: write() returned 0 from "
+                                       "%zd (iv_count=%d)", full_len, eiv_count);
+                       }
+                       full_len -= err;
+                       for (i = 0; i < e; i++) {
+                               if ((long long)eiv->iov_len < err) {
+                                       err -= eiv->iov_len;
+                                       eiv++;
+                                       eiv_count--;
+                               } else {
+                                       eiv->iov_base =
+                                           (uint8_t __force __user *)eiv->iov_base + err;
+                                       eiv->iov_len -= err;
+                                       break;
+                               }
                        }
+                       goto restart;
                }
-               goto restart;
-       }
 
-out_set_fs:
-       set_fs(old_fs);
+               for (i = 0; i < iv_count; i++)
+                       scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
 
-out_put:
-       while (iv_count > 0) {
-               scst_put_buf(cmd, (void __force *)(iv[iv_count-1].iov_base));
-               iv_count--;
+               if (finished)
+                       break;
+
+               loff += saved_full_len;
+               length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
        }
 
+       set_fs(old_fs);
+
 out:
        TRACE_EXIT();
        return;
+
+out_set_fs:
+       set_fs(old_fs);
+       for (i = 0; i < iv_count; i++)
+               scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+       goto out;
 }
 
 struct scst_blockio_work {