Patchwork SCSI: Add SCSI passthrough via scsi-generic to libiscsi

login
register
mail settings
Submitter ronniesahlberg@gmail.com
Date May 25, 2012, 11:59 a.m.
Message ID <1337947141-22950-2-git-send-email-ronniesahlberg@gmail.com>
Download mbox | patch
Permalink /patch/161325/
State New
Headers show

Comments

ronniesahlberg@gmail.com - May 25, 2012, 11:59 a.m.
Update iscsi to allow passthrough of SG_IO scsi commands when the iscsi
device is forced to be scsi-generic.

Implement both bdrv_ioctl() and bdrv_aio_ioctl() in the iscsi backend,
emulate the SG_IO ioctl and pass the SCSI commands across to the
iscsi target.

This allows end-to-end passthrough of SCSI all the way from the guest,
to qemu, via scsi-generic, then libiscsi all the way to the iscsi target.

To activate this you need to specify that the iscsi lun should be treated
as a scsi-generic device.

Example:
    -device lsi -device scsi-generic,drive=MyISCSI \
    -drive file=iscsi://10.1.1.125/iqn.ronnie.test/1,if=none,id=MyISCSI

Note, you can currently not boot a qemu guest from a scsi device.

Note,
This only works when the host is linux, since the emulation relies on
definitions of SG_IO from the scsi-generic implementation in the
linux kernel.
It should be fairly easy to re-implement some structures similar enough
for non-linux hosts to do the same style of passthrough via a fake
scsi generic layer and libiscsi if need be.

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
---
 block/iscsi.c     |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 hw/scsi-generic.c |   13 ++--
 2 files changed, 189 insertions(+), 10 deletions(-)
Paolo Bonzini - May 26, 2012, 7:50 a.m.
Il 25/05/2012 13:59, Ronnie Sahlberg ha scritto:
> Update iscsi to allow passthrough of SG_IO scsi commands when the iscsi
> device is forced to be scsi-generic.
> 
> Implement both bdrv_ioctl() and bdrv_aio_ioctl() in the iscsi backend,
> emulate the SG_IO ioctl and pass the SCSI commands across to the
> iscsi target.
> 
> This allows end-to-end passthrough of SCSI all the way from the guest,
> to qemu, via scsi-generic, then libiscsi all the way to the iscsi target.
> 
> To activate this you need to specify that the iscsi lun should be treated
> as a scsi-generic device.
> 
> Example:
>     -device lsi -device scsi-generic,drive=MyISCSI \
>     -drive file=iscsi://10.1.1.125/iqn.ronnie.test/1,if=none,id=MyISCSI
> 
> Note, you can currently not boot a qemu guest from a scsi device.
> 
> Note,
> This only works when the host is linux, since the emulation relies on
> definitions of SG_IO from the scsi-generic implementation in the
> linux kernel.
> It should be fairly easy to re-implement some structures similar enough
> for non-linux hosts to do the same style of passthrough via a fake
> scsi generic layer and libiscsi if need be.
> 
> Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
> ---
>  block/iscsi.c     |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  hw/scsi-generic.c |   13 ++--
>  2 files changed, 189 insertions(+), 10 deletions(-)
> 
> diff --git a/block/iscsi.c b/block/iscsi.c
> index d710b86..0d40637 100644
> --- a/block/iscsi.c
> +++ b/block/iscsi.c
> @@ -34,10 +34,15 @@
>  #include <iscsi/iscsi.h>
>  #include <iscsi/scsi-lowlevel.h>
>  
> +#ifdef __linux__
> +#include <scsi/sg.h>
> +#include <hw/scsi-defs.h>
> +#endif
>  
>  typedef struct IscsiLun {
>      struct iscsi_context *iscsi;
>      int lun;
> +    enum scsi_inquiry_peripheral_device_type type;
>      int block_size;
>      unsigned long num_blocks;
>      int events;
> @@ -54,6 +59,9 @@ typedef struct IscsiAIOCB {
>      int canceled;
>      size_t read_size;
>      size_t read_offset;
> +#ifdef __linux__
> +    sg_io_hdr_t *ioh;
> +#endif
>  } IscsiAIOCB;
>  
>  struct IscsiTask {
> @@ -509,6 +517,136 @@ iscsi_aio_discard(BlockDriverState *bs,
>      return &acb->common;
>  }
>  
> +#ifdef __linux__
> +static void
> +iscsi_aio_ioctl_cb(struct iscsi_context *iscsi, int status,
> +                     void *command_data, void *opaque)
> +{
> +    IscsiAIOCB *acb = opaque;
> +
> +    if (acb->canceled != 0) {
> +        qemu_aio_release(acb);
> +        scsi_free_scsi_task(acb->task);
> +        acb->task = NULL;
> +        return;
> +    }
> +
> +    acb->status = 0;
> +    if (status < 0) {
> +        error_report("Failed to ioctl(SG_IO) to iSCSI lun. %s",
> +                     iscsi_get_error(iscsi));
> +        acb->status = -EIO;
> +    }
> +
> +    acb->ioh->driver_status = 0;
> +    acb->ioh->host_status   = 0;
> +    acb->ioh->resid         = 0;
> +
> +#define SG_ERR_DRIVER_SENSE    0x08
> +
> +    if (status == SCSI_STATUS_CHECK_CONDITION && acb->task->datain.size >= 2) {
> +        int ss;
> +
> +        acb->ioh->driver_status |= SG_ERR_DRIVER_SENSE;
> +
> +        acb->ioh->sb_len_wr = acb->task->datain.size - 2;
> +        ss = (acb->ioh->mx_sb_len >= acb->ioh->sb_len_wr) ?
> +             acb->ioh->mx_sb_len : acb->ioh->sb_len_wr;
> +        memcpy(acb->ioh->sbp, &acb->task->datain.data[2], ss);
> +    }
> +
> +    iscsi_schedule_bh(iscsi_readv_writev_bh_cb, acb);
> +    scsi_free_scsi_task(acb->task);
> +    acb->task = NULL;
> +}
> +
> +static BlockDriverAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
> +        unsigned long int req, void *buf,
> +        BlockDriverCompletionFunc *cb, void *opaque)
> +{
> +    IscsiLun *iscsilun = bs->opaque;
> +    struct iscsi_context *iscsi = iscsilun->iscsi;
> +    struct iscsi_data data;
> +    IscsiAIOCB *acb;
> +
> +    assert(req == SG_IO);
> +
> +    acb = qemu_aio_get(&iscsi_aio_pool, bs, cb, opaque);
> +
> +    acb->iscsilun = iscsilun;
> +    acb->canceled    = 0;
> +    acb->buf         = NULL;
> +    acb->ioh         = buf;
> +
> +    acb->task = malloc(sizeof(struct scsi_task));
> +    if (acb->task == NULL) {
> +        error_report("iSCSI: Failed to allocate task for scsi command. %s",
> +                     iscsi_get_error(iscsi));
> +        qemu_aio_release(acb);
> +        return NULL;
> +    }
> +    memset(acb->task, 0, sizeof(struct scsi_task));
> +
> +    switch (acb->ioh->dxfer_direction) {
> +    case SG_DXFER_TO_DEV:
> +        acb->task->xfer_dir = SCSI_XFER_WRITE;
> +        break;
> +    case SG_DXFER_FROM_DEV:
> +        acb->task->xfer_dir = SCSI_XFER_READ;
> +        break;
> +    default:
> +        acb->task->xfer_dir = SCSI_XFER_NONE;
> +        break;
> +    }
> +
> +    acb->task->cdb_size = acb->ioh->cmd_len;
> +    memcpy(&acb->task->cdb[0], acb->ioh->cmdp, acb->ioh->cmd_len);
> +    acb->task->expxferlen = acb->ioh->dxfer_len;
> +
> +    if (acb->task->xfer_dir == SCSI_XFER_WRITE) {
> +        data.data = acb->ioh->dxferp;
> +        data.size = acb->ioh->dxfer_len;
> +    }
> +    if (iscsi_scsi_command_async(iscsi, iscsilun->lun, acb->task,
> +                                 iscsi_aio_ioctl_cb,
> +                                 (acb->task->xfer_dir == SCSI_XFER_WRITE) ?
> +                                     &data : NULL,
> +                                 acb) != 0) {
> +        scsi_free_scsi_task(acb->task);
> +        qemu_aio_release(acb);
> +        return NULL;
> +    }
> +
> +    /* tell libiscsi to read straight into the buffer we got from ioctl */
> +    if (acb->task->xfer_dir == SCSI_XFER_READ) {
> +        scsi_task_add_data_in_buffer(acb->task,
> +                                     acb->ioh->dxfer_len,
> +                                     acb->ioh->dxferp);
> +    }
> +
> +    iscsi_set_events(iscsilun);
> +
> +    return &acb->common;
> +}
> +
> +static int iscsi_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
> +{
> +    IscsiLun *iscsilun = bs->opaque;
> +
> +    switch (req) {
> +    case SG_GET_VERSION_NUM:
> +        *(int *)buf = 30000;
> +        break;
> +    case SG_GET_SCSI_ID:
> +        ((struct sg_scsi_id *)buf)->scsi_type = iscsilun->type;
> +        break;
> +    default:
> +        return -1;
> +    }
> +    return 0;
> +}
> +#endif
> +
>  static int64_t
>  iscsi_getlength(BlockDriverState *bs)
>  {
> @@ -558,18 +696,33 @@ iscsi_readcapacity16_cb(struct iscsi_context *iscsi, int status,
>  }
>  
>  static void
> -iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
> +iscsi_inquiry_cb(struct iscsi_context *iscsi, int status, void *command_data,
>                   void *opaque)
>  {
>      struct IscsiTask *itask = opaque;
> -    struct scsi_task *task;
> +    struct scsi_task *task = command_data;
> +    struct scsi_inquiry_standard *inq;
>  
>      if (status != 0) {
>          itask->status   = 1;
>          itask->complete = 1;
> +        scsi_free_scsi_task(task);
>          return;
>      }
>  
> +    inq = scsi_datain_unmarshall(task);
> +    if (inq == NULL) {
> +        error_report("iSCSI: Failed to unmarshall inquiry data.");
> +        itask->status   = 1;
> +        itask->complete = 1;
> +        scsi_free_scsi_task(task);
> +        return;
> +    }
> +
> +    itask->iscsilun->type = inq->periperal_device_type;
> +
> +    scsi_free_scsi_task(task);
> +
>      task = iscsi_readcapacity16_task(iscsi, itask->iscsilun->lun,
>                                     iscsi_readcapacity16_cb, opaque);
>      if (task == NULL) {
> @@ -580,6 +733,30 @@ iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
>      }
>  }
>  
> +static void
> +iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
> +                 void *opaque)
> +{
> +    struct IscsiTask *itask = opaque;
> +    struct scsi_task *task;
> +
> +    if (status != 0) {
> +        itask->status   = 1;
> +        itask->complete = 1;
> +        return;
> +    }
> +
> +    task = iscsi_inquiry_task(iscsi, itask->iscsilun->lun,
> +                              0, 0, 36,
> +                              iscsi_inquiry_cb, opaque);
> +    if (task == NULL) {
> +        error_report("iSCSI: failed to send inquiry command.");
> +        itask->status   = 1;
> +        itask->complete = 1;
> +        return;
> +    }
> +}
> +
>  static int parse_chap(struct iscsi_context *iscsi, const char *target)
>  {
>      QemuOptsList *list;
> @@ -827,6 +1004,11 @@ static BlockDriver bdrv_iscsi = {
>      .bdrv_aio_flush  = iscsi_aio_flush,
>  
>      .bdrv_aio_discard = iscsi_aio_discard,
> +
> +#ifdef __linux__
> +    .bdrv_ioctl       = iscsi_ioctl,
> +    .bdrv_aio_ioctl   = iscsi_aio_ioctl,
> +#endif
>  };
>  
>  static void iscsi_block_init(void)
> diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c
> index d856d23..8d51060 100644
> --- a/hw/scsi-generic.c
> +++ b/hw/scsi-generic.c
> @@ -400,12 +400,6 @@ static int scsi_generic_initfn(SCSIDevice *s)
>          return -1;
>      }
>  
> -    /* check we are really using a /dev/sg* file */
> -    if (!bdrv_is_sg(s->conf.bs)) {
> -        error_report("not /dev/sg*");
> -        return -1;
> -    }
> -
>      if (bdrv_get_on_error(s->conf.bs, 0) != BLOCK_ERR_STOP_ENOSPC) {
>          error_report("Device doesn't support drive option werror");
>          return -1;
> @@ -416,8 +410,11 @@ static int scsi_generic_initfn(SCSIDevice *s)
>      }
>  
>      /* check we are using a driver managing SG_IO (version 3 and after */
> -    if (bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version) < 0 ||
> -        sg_version < 30000) {
> +    if (bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version) < 0) {
> +        error_report("scsi generic interface not supported");
> +        return -1;
> +    }
> +    if (sg_version < 30000) {
>          error_report("scsi generic interface too old");
>          return -1;
>      }

Applied to scsi-next branch for 1.2.

Paolo

Patch

diff --git a/block/iscsi.c b/block/iscsi.c
index d710b86..0d40637 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -34,10 +34,15 @@ 
 #include <iscsi/iscsi.h>
 #include <iscsi/scsi-lowlevel.h>
 
+#ifdef __linux__
+#include <scsi/sg.h>
+#include <hw/scsi-defs.h>
+#endif
 
 typedef struct IscsiLun {
     struct iscsi_context *iscsi;
     int lun;
+    enum scsi_inquiry_peripheral_device_type type;
     int block_size;
     unsigned long num_blocks;
     int events;
@@ -54,6 +59,9 @@  typedef struct IscsiAIOCB {
     int canceled;
     size_t read_size;
     size_t read_offset;
+#ifdef __linux__
+    sg_io_hdr_t *ioh;
+#endif
 } IscsiAIOCB;
 
 struct IscsiTask {
@@ -509,6 +517,136 @@  iscsi_aio_discard(BlockDriverState *bs,
     return &acb->common;
 }
 
+#ifdef __linux__
+static void
+iscsi_aio_ioctl_cb(struct iscsi_context *iscsi, int status,
+                     void *command_data, void *opaque)
+{
+    IscsiAIOCB *acb = opaque;
+
+    if (acb->canceled != 0) {
+        qemu_aio_release(acb);
+        scsi_free_scsi_task(acb->task);
+        acb->task = NULL;
+        return;
+    }
+
+    acb->status = 0;
+    if (status < 0) {
+        error_report("Failed to ioctl(SG_IO) to iSCSI lun. %s",
+                     iscsi_get_error(iscsi));
+        acb->status = -EIO;
+    }
+
+    acb->ioh->driver_status = 0;
+    acb->ioh->host_status   = 0;
+    acb->ioh->resid         = 0;
+
+#define SG_ERR_DRIVER_SENSE    0x08
+
+    if (status == SCSI_STATUS_CHECK_CONDITION && acb->task->datain.size >= 2) {
+        int ss;
+
+        acb->ioh->driver_status |= SG_ERR_DRIVER_SENSE;
+
+        acb->ioh->sb_len_wr = acb->task->datain.size - 2;
+        ss = (acb->ioh->mx_sb_len >= acb->ioh->sb_len_wr) ?
+             acb->ioh->mx_sb_len : acb->ioh->sb_len_wr;
+        memcpy(acb->ioh->sbp, &acb->task->datain.data[2], ss);
+    }
+
+    iscsi_schedule_bh(iscsi_readv_writev_bh_cb, acb);
+    scsi_free_scsi_task(acb->task);
+    acb->task = NULL;
+}
+
+static BlockDriverAIOCB *iscsi_aio_ioctl(BlockDriverState *bs,
+        unsigned long int req, void *buf,
+        BlockDriverCompletionFunc *cb, void *opaque)
+{
+    IscsiLun *iscsilun = bs->opaque;
+    struct iscsi_context *iscsi = iscsilun->iscsi;
+    struct iscsi_data data;
+    IscsiAIOCB *acb;
+
+    assert(req == SG_IO);
+
+    acb = qemu_aio_get(&iscsi_aio_pool, bs, cb, opaque);
+
+    acb->iscsilun = iscsilun;
+    acb->canceled    = 0;
+    acb->buf         = NULL;
+    acb->ioh         = buf;
+
+    acb->task = malloc(sizeof(struct scsi_task));
+    if (acb->task == NULL) {
+        error_report("iSCSI: Failed to allocate task for scsi command. %s",
+                     iscsi_get_error(iscsi));
+        qemu_aio_release(acb);
+        return NULL;
+    }
+    memset(acb->task, 0, sizeof(struct scsi_task));
+
+    switch (acb->ioh->dxfer_direction) {
+    case SG_DXFER_TO_DEV:
+        acb->task->xfer_dir = SCSI_XFER_WRITE;
+        break;
+    case SG_DXFER_FROM_DEV:
+        acb->task->xfer_dir = SCSI_XFER_READ;
+        break;
+    default:
+        acb->task->xfer_dir = SCSI_XFER_NONE;
+        break;
+    }
+
+    acb->task->cdb_size = acb->ioh->cmd_len;
+    memcpy(&acb->task->cdb[0], acb->ioh->cmdp, acb->ioh->cmd_len);
+    acb->task->expxferlen = acb->ioh->dxfer_len;
+
+    if (acb->task->xfer_dir == SCSI_XFER_WRITE) {
+        data.data = acb->ioh->dxferp;
+        data.size = acb->ioh->dxfer_len;
+    }
+    if (iscsi_scsi_command_async(iscsi, iscsilun->lun, acb->task,
+                                 iscsi_aio_ioctl_cb,
+                                 (acb->task->xfer_dir == SCSI_XFER_WRITE) ?
+                                     &data : NULL,
+                                 acb) != 0) {
+        scsi_free_scsi_task(acb->task);
+        qemu_aio_release(acb);
+        return NULL;
+    }
+
+    /* tell libiscsi to read straight into the buffer we got from ioctl */
+    if (acb->task->xfer_dir == SCSI_XFER_READ) {
+        scsi_task_add_data_in_buffer(acb->task,
+                                     acb->ioh->dxfer_len,
+                                     acb->ioh->dxferp);
+    }
+
+    iscsi_set_events(iscsilun);
+
+    return &acb->common;
+}
+
+static int iscsi_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
+{
+    IscsiLun *iscsilun = bs->opaque;
+
+    switch (req) {
+    case SG_GET_VERSION_NUM:
+        *(int *)buf = 30000;
+        break;
+    case SG_GET_SCSI_ID:
+        ((struct sg_scsi_id *)buf)->scsi_type = iscsilun->type;
+        break;
+    default:
+        return -1;
+    }
+    return 0;
+}
+#endif
+
 static int64_t
 iscsi_getlength(BlockDriverState *bs)
 {
@@ -558,18 +696,33 @@  iscsi_readcapacity16_cb(struct iscsi_context *iscsi, int status,
 }
 
 static void
-iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
+iscsi_inquiry_cb(struct iscsi_context *iscsi, int status, void *command_data,
                  void *opaque)
 {
     struct IscsiTask *itask = opaque;
-    struct scsi_task *task;
+    struct scsi_task *task = command_data;
+    struct scsi_inquiry_standard *inq;
 
     if (status != 0) {
         itask->status   = 1;
         itask->complete = 1;
+        scsi_free_scsi_task(task);
         return;
     }
 
+    inq = scsi_datain_unmarshall(task);
+    if (inq == NULL) {
+        error_report("iSCSI: Failed to unmarshall inquiry data.");
+        itask->status   = 1;
+        itask->complete = 1;
+        scsi_free_scsi_task(task);
+        return;
+    }
+
+    itask->iscsilun->type = inq->periperal_device_type;
+
+    scsi_free_scsi_task(task);
+
     task = iscsi_readcapacity16_task(iscsi, itask->iscsilun->lun,
                                    iscsi_readcapacity16_cb, opaque);
     if (task == NULL) {
@@ -580,6 +733,30 @@  iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
     }
 }
 
+static void
+iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data,
+                 void *opaque)
+{
+    struct IscsiTask *itask = opaque;
+    struct scsi_task *task;
+
+    if (status != 0) {
+        itask->status   = 1;
+        itask->complete = 1;
+        return;
+    }
+
+    task = iscsi_inquiry_task(iscsi, itask->iscsilun->lun,
+                              0, 0, 36,
+                              iscsi_inquiry_cb, opaque);
+    if (task == NULL) {
+        error_report("iSCSI: failed to send inquiry command.");
+        itask->status   = 1;
+        itask->complete = 1;
+        return;
+    }
+}
+
 static int parse_chap(struct iscsi_context *iscsi, const char *target)
 {
     QemuOptsList *list;
@@ -827,6 +1004,11 @@  static BlockDriver bdrv_iscsi = {
     .bdrv_aio_flush  = iscsi_aio_flush,
 
     .bdrv_aio_discard = iscsi_aio_discard,
+
+#ifdef __linux__
+    .bdrv_ioctl       = iscsi_ioctl,
+    .bdrv_aio_ioctl   = iscsi_aio_ioctl,
+#endif
 };
 
 static void iscsi_block_init(void)
diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c
index d856d23..8d51060 100644
--- a/hw/scsi-generic.c
+++ b/hw/scsi-generic.c
@@ -400,12 +400,6 @@  static int scsi_generic_initfn(SCSIDevice *s)
         return -1;
     }
 
-    /* check we are really using a /dev/sg* file */
-    if (!bdrv_is_sg(s->conf.bs)) {
-        error_report("not /dev/sg*");
-        return -1;
-    }
-
     if (bdrv_get_on_error(s->conf.bs, 0) != BLOCK_ERR_STOP_ENOSPC) {
         error_report("Device doesn't support drive option werror");
         return -1;
@@ -416,8 +410,11 @@  static int scsi_generic_initfn(SCSIDevice *s)
     }
 
     /* check we are using a driver managing SG_IO (version 3 and after */
-    if (bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version) < 0 ||
-        sg_version < 30000) {
+    if (bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version) < 0) {
+        error_report("scsi generic interface not supported");
+        return -1;
+    }
+    if (sg_version < 30000) {
         error_report("scsi generic interface too old");
         return -1;
     }