Patchwork [1/2] SCSI: Add SCSI passthrough via scsi-generic to libiscsi

login
register
mail settings
Submitter ronniesahlberg@gmail.com
Date April 28, 2012, 2:55 p.m.
Message ID <1335624945-18459-2-git-send-email-ronniesahlberg@gmail.com>
Download mbox | patch
Permalink /patch/155660/
State New
Headers show

Comments

ronniesahlberg@gmail.com - April 28, 2012, 2:55 p.m.
Update scsi-generic to allow passthrough of SG_IO scsi commands
to iscsi devices too in addition to the real scsi-generic devices.

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 interface type as scsi when
creating the device.

Example:
-drive file=iscsi://10.1.1.125/iqn.ronnie.test/1,if=scsi,bus=0,unit=5

Note, you can currently not boot a qemu guest from a 'if=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.c           |   18 ++++--
 block.h           |    1 +
 block/iscsi.c     |  191 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 block_int.h       |    1 +
 hw/scsi-bus.c     |    7 ++-
 hw/scsi-disk.c    |    4 +
 hw/scsi-generic.c |    4 +-
 7 files changed, 216 insertions(+), 10 deletions(-)

Patch

diff --git a/block.c b/block.c
index c0c90f0..6b22d10 100644
--- a/block.c
+++ b/block.c
@@ -458,10 +458,11 @@  static int find_image_format(const char *filename, BlockDriver **pdrv)
         return ret;
     }
 
-    /* Return the raw BlockDriver * to scsi-generic devices or empty drives */
-    if (bs->sg || !bdrv_is_inserted(bs)) {
+    /* Return the raw BlockDriver * to scsi-generic devices,
+       iscsi devices or empty drives */
+    if (bs->sg || bs->iscsi || !bdrv_is_inserted(bs)) {
         bdrv_delete(bs);
-        drv = bdrv_find_format("raw");
+        drv = bdrv_find_format(bs->iscsi ? "iscsi" : "raw");
         if (!drv) {
             ret = -ENOENT;
         }
@@ -501,9 +502,10 @@  static int refresh_total_sectors(BlockDriverState *bs, int64_t hint)
 {
     BlockDriver *drv = bs->drv;
 
-    /* Do not attempt drv->bdrv_getlength() on scsi-generic devices */
-    if (bs->sg)
+    /* Do not attempt drv->bdrv_getlength() on scsi-generic/iscsi devices */
+    if (bs->sg || bs->iscsi) {
         return 0;
+    }
 
     /* query actual device if possible, otherwise just trust the hint */
     if (drv->bdrv_getlength) {
@@ -578,6 +580,7 @@  static int bdrv_open_common(BlockDriverState *bs, const char *filename,
     bs->encrypted = 0;
     bs->valid_key = 0;
     bs->sg = 0;
+    bs->iscsi = 0;
     bs->open_flags = flags;
     bs->growable = 0;
     bs->buffer_alignment = 512;
@@ -2260,6 +2263,11 @@  int bdrv_is_sg(BlockDriverState *bs)
     return bs->sg;
 }
 
+int bdrv_is_iscsi(BlockDriverState *bs)
+{
+    return bs->iscsi;
+}
+
 int bdrv_enable_write_cache(BlockDriverState *bs)
 {
     return bs->enable_write_cache;
diff --git a/block.h b/block.h
index f163e54..3d6309d 100644
--- a/block.h
+++ b/block.h
@@ -279,6 +279,7 @@  void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error,
 BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read);
 int bdrv_is_read_only(BlockDriverState *bs);
 int bdrv_is_sg(BlockDriverState *bs);
+int bdrv_is_iscsi(BlockDriverState *bs);
 int bdrv_enable_write_cache(BlockDriverState *bs);
 int bdrv_is_inserted(BlockDriverState *bs);
 int bdrv_media_changed(BlockDriverState *bs);
diff --git a/block/iscsi.c b/block/iscsi.c
index eb49093..c7b4a29 100644
--- a/block/iscsi.c
+++ b/block/iscsi.c
@@ -33,10 +33,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;
 } IscsiLun;
@@ -52,6 +57,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 {
@@ -442,6 +450,140 @@  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;
+
+    if (req != SG_IO) {
+        error_report("iSCSI: iscsi_aio_ioctl called for non-SG_IO "
+                     "ioctl");
+        return NULL;
+    }
+
+    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)
 {
@@ -491,18 +633,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) {
@@ -513,6 +670,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;
@@ -719,6 +900,7 @@  static int iscsi_open(BlockDriverState *bs, const char *filename, int flags)
     if (iscsi_url != NULL) {
         iscsi_destroy_url(iscsi_url);
     }
+    bs->iscsi = 1;
     return 0;
 
 failed:
@@ -760,6 +942,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/block_int.h b/block_int.h
index 0e5a032..16e6041 100644
--- a/block_int.h
+++ b/block_int.h
@@ -264,6 +264,7 @@  struct BlockDriverState {
     int encrypted; /* if true, the media is encrypted */
     int valid_key; /* if true, a valid encryption key has been set */
     int sg;        /* if true, the device is a /dev/sg* */
+    int iscsi;     /* if true, the device is a iscsi://... */
     int copy_on_read; /* if true, copy read backing sectors into image
                          note this is a reference count */
 
diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c
index 8e76c5d..87d59d8 100644
--- a/hw/scsi-bus.c
+++ b/hw/scsi-bus.c
@@ -199,7 +199,12 @@  SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv,
     const char *driver;
     DeviceState *dev;
 
-    driver = bdrv_is_sg(bdrv) ? "scsi-generic" : "scsi-disk";
+    if (bdrv_is_sg(bdrv) || bdrv_is_iscsi(bdrv)) {
+        driver = "scsi-generic";
+    } else {
+        driver = "scsi-disk";
+    }
+
     dev = qdev_create(&bus->qbus, driver);
     qdev_prop_set_uint32(dev, "scsi-id", unit);
     if (bootindex >= 0) {
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 9949786..65e87cc 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -1618,6 +1618,10 @@  static int scsi_initfn(SCSIDevice *dev)
         error_report("unwanted /dev/sg*");
         return -1;
     }
+    if (bdrv_is_iscsi(s->qdev.conf.bs)) {
+        error_report("unwanted iscsi://*");
+        return -1;
+    }
 
     if (s->removable) {
         bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_cd_block_ops, s);
diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c
index d856d23..dfc5c54 100644
--- a/hw/scsi-generic.c
+++ b/hw/scsi-generic.c
@@ -401,8 +401,8 @@  static int scsi_generic_initfn(SCSIDevice *s)
     }
 
     /* check we are really using a /dev/sg* file */
-    if (!bdrv_is_sg(s->conf.bs)) {
-        error_report("not /dev/sg*");
+    if (!bdrv_is_sg(s->conf.bs) && !bdrv_is_iscsi(s->conf.bs)) {
+        error_report("not /dev/sg* or iscsi://");
         return -1;
     }