From 983924532f61091fd90d1f2fafa4aa938c414dbb Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Fri, 25 May 2012 21:59:01 +1000 Subject: [PATCH] ISCSI: Add SCSI passthrough via scsi-generic to libiscsi 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 Signed-off-by: Paolo Bonzini --- block/iscsi.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++ hw/scsi-generic.c | 13 ++--- 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/block/iscsi.c b/block/iscsi.c index 22888a0845..029739eb61 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -35,6 +35,10 @@ #include #include +#ifdef __linux__ +#include +#include +#endif typedef struct IscsiLun { struct iscsi_context *iscsi; @@ -56,6 +60,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 { @@ -515,6 +522,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) { @@ -926,6 +1063,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 d856d23b3b..8d5106061e 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; }