From patchwork Fri May 25 07:43:16 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: ronnie sahlberg X-Patchwork-Id: 161252 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 56E76B6F77 for ; Fri, 25 May 2012 17:55:16 +1000 (EST) Received: from localhost ([::1]:42676 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SXpMg-0002dq-4r for incoming@patchwork.ozlabs.org; Fri, 25 May 2012 03:55:14 -0400 Received: from eggs.gnu.org ([208.118.235.92]:52464) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SXpMN-0002I5-9F for qemu-devel@nongnu.org; Fri, 25 May 2012 03:55:00 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SXpMK-0007kB-Br for qemu-devel@nongnu.org; Fri, 25 May 2012 03:54:54 -0400 Received: from mail-pb0-f45.google.com ([209.85.160.45]:61936) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SXpMK-0007ha-0S for qemu-devel@nongnu.org; Fri, 25 May 2012 03:54:52 -0400 Received: by mail-pb0-f45.google.com with SMTP id ro12so1588270pbb.4 for ; Fri, 25 May 2012 00:54:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; bh=XXXTYQTn2SnwEhBj2Z3ek1bDkSvGpv1Vyhs12C59l7A=; b=dZTUUXjAUUiHAVbWHa68Ex2rK/TLLa0xPWEiMyzCtFdENih7yhbR6QJqa5fzY6nGCJ InzcZjJzLtEAlJFxt/uZ/rKOdTrfviw1z4dhP6mxljPxX02Ktw/7ec1EaUf2TyNA3uME xKIV7wdCpwfXYqIU3EZiYt/iS3fvJUAxvZB0wCBYNeDQKSndig0hFjVPG8+GPF4r58Xn jOjnievDeMvjadrJA7oO32djMYFbIsAvXyHBWDaaZAZE4sB8Ch5Rj8k5mNAtPlyovC8z v8plhXEs23m1+v3mn8z7ZmPd/vGW8SfLFg0JLZIOhI4a3EEmDd7tbt697rGmX1+3mMj7 mrYQ== Received: by 10.68.234.129 with SMTP id ue1mr5957662pbc.140.1337932490817; Fri, 25 May 2012 00:54:50 -0700 (PDT) Received: from ronniesahlberg@gmail.com (CPE-138-130-106-226.lns3.cht.bigpond.net.au. [138.130.106.226]) by mx.google.com with ESMTPS id qq2sm8249464pbc.27.2012.05.25.00.54.45 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 25 May 2012 00:54:49 -0700 (PDT) Received: by ronniesahlberg@gmail.com (sSMTP sendmail emulation); Fri, 25 May 2012 17:43:44 +1000 From: Ronnie Sahlberg To: kwolf@redhat.com, pbonzini@redhat.com, qemu-devel@nongnu.org Date: Fri, 25 May 2012 17:43:16 +1000 Message-Id: <1337931796-2660-3-git-send-email-ronniesahlberg@gmail.com> X-Mailer: git-send-email 1.7.3.1 In-Reply-To: <1337931796-2660-1-git-send-email-ronniesahlberg@gmail.com> References: <1337931796-2660-1-git-send-email-ronniesahlberg@gmail.com> X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.160.45 Cc: Ronnie Sahlberg Subject: [Qemu-devel] [PATCH 2/2] SCSI: Add SCSI passthrough via scsi-generic to libiscsi X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 --- block.c | 18 ++++-- block.h | 1 + block/iscsi.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++- block/raw.c | 1 + block_int.h | 1 + hw/scsi-bus.c | 7 ++- hw/scsi-disk.c | 4 + hw/scsi-generic.c | 4 +- 8 files changed, 217 insertions(+), 10 deletions(-) diff --git a/block.c b/block.c index af2ab4f..2467b8e 100644 --- a/block.c +++ b/block.c @@ -507,10 +507,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; } @@ -550,9 +551,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) { @@ -879,6 +881,7 @@ void bdrv_close(BlockDriverState *bs) bs->encrypted = 0; bs->valid_key = 0; bs->sg = 0; + bs->iscsi = 0; bs->growable = 0; if (bs->file != NULL) { @@ -2355,6 +2358,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 7408acc..642194b 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 9642ee6..301ab69 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -34,10 +34,15 @@ #include #include +#ifdef __linux__ +#include +#include +#endif typedef struct IscsiLun { struct iscsi_context *iscsi; int lun; + enum scsi_inquiry_peripheral_device_type type; int block_size; unsigned long num_blocks; IOHandler *read_handler; @@ -55,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 { @@ -526,6 +534,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) { @@ -575,18 +717,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) { @@ -597,6 +754,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; @@ -803,6 +984,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: @@ -846,6 +1028,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/raw.c b/block/raw.c index 7086e31..62fc8c6 100644 --- a/block/raw.c +++ b/block/raw.c @@ -6,6 +6,7 @@ static int raw_open(BlockDriverState *bs, int flags) { bs->sg = bs->file->sg; + bs->iscsi = bs->file->iscsi; return 0; } diff --git a/block_int.h b/block_int.h index b80e66d..a6a9a1a 100644 --- a/block_int.h +++ b/block_int.h @@ -269,6 +269,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 8ab9bcd..1466b1d 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 045c764..925dbcf 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1711,6 +1711,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->features & (1 << SCSI_DISK_F_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; }