[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 1/2] SCSI: Add SCSI passthrough via scsi-generic to
From: |
Ronnie Sahlberg |
Subject: |
[Qemu-devel] [PATCH 1/2] SCSI: Add SCSI passthrough via scsi-generic to libiscsi |
Date: |
Sun, 29 Apr 2012 00:55:45 +1000 |
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 <address@hidden>
---
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 <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;
}
--
1.7.3.1