From patchwork Sat Apr 28 14:55:45 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: ronnie sahlberg X-Patchwork-Id: 155660 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 1CB04B6FE3 for ; Sun, 29 Apr 2012 00:56:21 +1000 (EST) Received: from localhost ([::1]:35885 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SO94N-0000BU-1o for incoming@patchwork.ozlabs.org; Sat, 28 Apr 2012 10:56:19 -0400 Received: from eggs.gnu.org ([208.118.235.92]:42287) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SO94C-00009q-Sj for qemu-devel@nongnu.org; Sat, 28 Apr 2012 10:56:11 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SO94A-0007Yz-1D for qemu-devel@nongnu.org; Sat, 28 Apr 2012 10:56:08 -0400 Received: from mail-pz0-f46.google.com ([209.85.210.46]:46544) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SO949-0007YH-Kv for qemu-devel@nongnu.org; Sat, 28 Apr 2012 10:56:05 -0400 Received: by mail-pz0-f46.google.com with SMTP id z9so2497475dad.33 for ; Sat, 28 Apr 2012 07:56:04 -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=wyMb01bj6IdjQ3gXXDiSHvObnpMqXC/S1N98YUlE7yM=; b=F/2TLhbv+plkpoVLYl0LlYjujFtOm3a7g7rmnv0zkGoNoJ0LpSmSzX8Kvqy5Omvgp+ rjKsUj13EGllbzWZBu2JIa+BqlFN9DVqEVdEbNfSy1HzcMdjWJxxlJmFNiFW+6vN8t2C Npzkp5QonIfPCUF+jZyehBMi3ihVT8gzcwdmPDyh1vMwOmPlRm+chzOPaB0HdSWwO7F0 CvEUNybGKHlHdi2WEHUM3nlApucHoOiYHsK3y32MOTeekGyK2EH7azTyQEa5uPoaU5Go 8E/ZRm9ja/apWty3rcQNxmHQL2KPVqlfZsjbJGSLMebbcf2HubHSREOP19qQZ2FgpdTL fpnw== Received: by 10.68.195.233 with SMTP id ih9mr6715383pbc.128.1335624964660; Sat, 28 Apr 2012 07:56:04 -0700 (PDT) Received: from ronniesahlberg@gmail.com (CPE-58-166-90-251.lnse5.cht.bigpond.net.au. [58.166.90.251]) by mx.google.com with ESMTPS id vn10sm8607557pbc.43.2012.04.28.07.56.00 (version=TLSv1/SSLv3 cipher=OTHER); Sat, 28 Apr 2012 07:56:03 -0700 (PDT) Received: by ronniesahlberg@gmail.com (sSMTP sendmail emulation); Sun, 29 Apr 2012 00:56:01 +1000 From: Ronnie Sahlberg To: qemu-devel@nongnu.org, pbonzini@redhat.com Date: Sun, 29 Apr 2012 00:55:45 +1000 Message-Id: <1335624945-18459-2-git-send-email-ronniesahlberg@gmail.com> X-Mailer: git-send-email 1.7.3.1 In-Reply-To: <1335624945-18459-1-git-send-email-ronniesahlberg@gmail.com> References: <1335624945-18459-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.210.46 Cc: Ronnie Sahlberg Subject: [Qemu-devel] [PATCH 1/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_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(-) 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 #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; } 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; }