Patchwork [5/5] scsi-bsg: Add initial support for BSG based SCSIDeviceInfo

login
register
mail settings
Submitter Nicholas A. Bellinger
Date Nov. 24, 2010, 8:40 a.m.
Message ID <1290588035-8950-1-git-send-email-nab@linux-iscsi.org>
Download mbox | patch
Permalink /patch/72820/
State New
Headers show

Comments

Nicholas A. Bellinger - Nov. 24, 2010, 8:40 a.m.
From: Nicholas Bellinger <nab@linux-iscsi.org>

This patch adds initial support for using the Linux BSG interface with write/read vectored
AIO as a QEMU backstore (SCSIDeviceInfo) with hw/scsi-bus.c compatible HBA emulation.

So far it has been tested with x86_64 host and guest using hw/megasas.c and TCM_Loop LLD
Port LUNs.  Because this patch uses struct iovec for struct sg_io_v4->d[out,in]_xferp payloads,
which currently requires a patch to linux/block/bsg.c:bsg_map_hdr() in order to setup the
user -> kernel iovecs.   This patch can be found in lio-core-2.6.git here:

commit fec4e8457c744de50e1ead69a36d5c4ee089d1ac
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date:   Sun Jun 13 23:13:20 2010 -0700

    [BSG]: Add support for struct sg_io_v4->d[out,in]_iovec_count

This also will only currently work with paired user/kernel (eg: 64bit user / 64bit kernel)
because of different pointer sizes in struct iovec->iov_base.

There are also two FIXMEs in hw/scsi-bsg.c:bsg_generic_initfn() related to extraction of
SCSI LUN and device type values using BSG and required by QEMU-KVM.

Signed-off-by: Nicholas A. Bellinger <nab@linux-iscsi.org>
---
 Makefile.objs |    2 +-
 hw/scsi-bsg.c |  720 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 721 insertions(+), 1 deletions(-)
 create mode 100644 hw/scsi-bsg.c
Paolo Bonzini - Nov. 24, 2010, 9:07 a.m.
On 11/24/2010 09:40 AM, Nicholas A. Bellinger wrote:
> From: Nicholas Bellinger<nab@linux-iscsi.org>
>
> This patch adds initial support for using the Linux BSG interface with write/read vectored
> AIO as a QEMU backstore (SCSIDeviceInfo) with hw/scsi-bus.c compatible HBA emulation.
>
> So far it has been tested with x86_64 host and guest using hw/megasas.c and TCM_Loop LLD
> Port LUNs.  Because this patch uses struct iovec for struct sg_io_v4->d[out,in]_xferp payloads,
> which currently requires a patch to linux/block/bsg.c:bsg_map_hdr() in order to setup the
> user ->  kernel iovecs.   This patch can be found in lio-core-2.6.git here:
>
> commit fec4e8457c744de50e1ead69a36d5c4ee089d1ac
> Author: Nicholas Bellinger<nab@linux-iscsi.org>
> Date:   Sun Jun 13 23:13:20 2010 -0700
>
>      [BSG]: Add support for struct sg_io_v4->d[out,in]_iovec_count
>
> This also will only currently work with paired user/kernel (eg: 64bit user / 64bit kernel)
> because of different pointer sizes in struct iovec->iov_base.
>
> There are also two FIXMEs in hw/scsi-bsg.c:bsg_generic_initfn() related to extraction of
> SCSI LUN and device type values using BSG and required by QEMU-KVM.

Any reason why this should be a different driver than scsi-generic, i.e. 
why the scsi-generic should not switch automatically between SG and BSG 
depending on the kind of device being opened?

In fact, there is no reason why scsi-generic should use bounce buffers 
rather than sg_iovec_t, so that most of the differences between 
scsi-generic and scsi-bsg would go away.

Paolo
Nicholas A. Bellinger - Nov. 24, 2010, 9:08 a.m.
On Wed, 2010-11-24 at 10:07 +0100, Paolo Bonzini wrote:
> On 11/24/2010 09:40 AM, Nicholas A. Bellinger wrote:
> > From: Nicholas Bellinger<nab@linux-iscsi.org>
> >
> > This patch adds initial support for using the Linux BSG interface with write/read vectored
> > AIO as a QEMU backstore (SCSIDeviceInfo) with hw/scsi-bus.c compatible HBA emulation.
> >
> > So far it has been tested with x86_64 host and guest using hw/megasas.c and TCM_Loop LLD
> > Port LUNs.  Because this patch uses struct iovec for struct sg_io_v4->d[out,in]_xferp payloads,
> > which currently requires a patch to linux/block/bsg.c:bsg_map_hdr() in order to setup the
> > user ->  kernel iovecs.   This patch can be found in lio-core-2.6.git here:
> >
> > commit fec4e8457c744de50e1ead69a36d5c4ee089d1ac
> > Author: Nicholas Bellinger<nab@linux-iscsi.org>
> > Date:   Sun Jun 13 23:13:20 2010 -0700
> >
> >      [BSG]: Add support for struct sg_io_v4->d[out,in]_iovec_count
> >
> > This also will only currently work with paired user/kernel (eg: 64bit user / 64bit kernel)
> > because of different pointer sizes in struct iovec->iov_base.
> >
> > There are also two FIXMEs in hw/scsi-bsg.c:bsg_generic_initfn() related to extraction of
> > SCSI LUN and device type values using BSG and required by QEMU-KVM.
> 
> Any reason why this should be a different driver than scsi-generic, i.e. 
> why the scsi-generic should not switch automatically between SG and BSG 
> depending on the kind of device being opened?
> 
> In fact, there is no reason why scsi-generic should use bounce buffers 
> rather than sg_iovec_t, so that most of the differences between 
> scsi-generic and scsi-bsg would go away.
> 

Originally I split these up into a seperate scsi-bsg.c file because:

*) BSG uses aio_submit_len() and polling reads for interaction with the
BSG ioctl.

*) BSG uses a different struct API (struct sg_io_v4) for user <-> kernel
communication.

Between hw/scsi-generic.c with Hannes's and Co's recent cleanups
scsi-generic.c and scsi-bsg.c are more similar than they used to be in
v0.12.5 w/ v1 of Gerd's SCSI SGL passthrough, but I really don't have a
strong perference here.

Thanks!

--nab

Patch

diff --git a/Makefile.objs b/Makefile.objs
index 0443c7e..842e70c 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -79,7 +79,7 @@  common-obj-$(CONFIG_DS1338) += ds1338.o
 common-obj-y += i2c.o smbus.o smbus_eeprom.o
 common-obj-y += eeprom93xx.o
 common-obj-y += scsi-disk.o cdrom.o
-common-obj-y += scsi-generic.o scsi-bus.o
+common-obj-y += scsi-generic.o scsi-bsg.o scsi-bus.o
 common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o
 common-obj-y += usb-serial.o usb-net.o usb-bus.o
 common-obj-$(CONFIG_SSI) += ssi.o
diff --git a/hw/scsi-bsg.c b/hw/scsi-bsg.c
new file mode 100644
index 0000000..93936e0
--- /dev/null
+++ b/hw/scsi-bsg.c
@@ -0,0 +1,720 @@ 
+/*
+ * block layer implementation of the sg v4 interface for Linux hosts
+ *
+ * Copyright (c) 2010 Rising Tide Systems
+ * Written by Nicholas A. Bellinger <nab@linux-iscsi.org>
+ *
+ * Based on hw/scsi-generic code by Laurent Vivier, Paul Brook, and Fabrice Bellard
+ *
+ * This code is licenced under the LGPL.
+ */
+
+#include "qemu-common.h"
+#include "qemu-error.h"
+#include "block.h"
+#include "scsi.h"
+#include "block/raw-posix-aio.h"
+
+#ifdef __linux__
+
+#define DEBUG_BSG
+//#define DEBUG_BSG_IO
+
+#ifdef DEBUG_BSG
+#define DPRINTF(fmt, ...) \
+do { printf("scsi-bsg: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#ifdef DEBUG_BSG_IO
+#define DPRINTF_BSG_IO(fmt, ...)    DPRINTF(fmt , ## __VA_ARGS__)
+#else
+#define DPRINTF_BSG_IO(fmt, ...)
+#endif
+
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "scsi-bsg: " fmt , ## __VA_ARGS__); } while (0)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+#include <scsi/sg.h>
+#include <linux/bsg.h>
+#include "scsi-defs.h"
+
+#define SCSI_SENSE_BUF_SIZE 96
+
+#define SG_ERR_DRIVER_TIMEOUT 0x06
+#define SG_ERR_DRIVER_SENSE 0x08
+
+typedef struct SCSIBSGState SCSIBSGState;
+
+typedef struct SCSIBSGReq {
+    SCSIRequest req;
+    uint8_t *buf;
+    int buflen;
+    int len;
+//    QEMUIOVector iov;
+    struct iovec *saved_iov;
+    uint32_t saved_iov_num;
+    QEMUIOVector aio_iov;
+    struct sg_io_v4 bsg_hdr;
+} SCSIBSGReq;
+
+struct SCSIBSGState {
+    SCSIDevice qdev;
+    BlockDriverState *bs;
+    int lun;
+    int driver_status;
+    uint8_t sensebuf[SCSI_SENSE_BUF_SIZE];
+    uint8_t senselen;
+};
+
+static int bsg_read(int fd, void *p_read, int to_read)
+{
+    int err;
+
+    while (to_read > 0) {
+        err = read(fd, p_read, to_read);
+        if (err >= 0) {
+            to_read -= err;
+            p_read += err;
+        } else if (errno == EINTR)
+            continue;
+        else {
+            error_report("bsg device %d read failed, errno: %d\n",
+                    fd, errno);
+            return errno;
+        }
+    }
+    return 0;
+}
+
+static int bsg_set_sense(SCSIBSGState *s, SCSISense sense)
+{
+    int len;
+
+    len = scsi_build_sense(sense, s->sensebuf, SCSI_SENSE_BUF_SIZE, 0);
+    s->driver_status = SG_ERR_DRIVER_SENSE;
+
+    return len;
+}
+
+static void bsg_clear_sense(SCSIBSGState *s)
+{
+    memset(s->sensebuf, 0, SCSI_SENSE_BUF_SIZE);
+    s->senselen = 0;
+    s->driver_status = 0;
+}
+
+static int bsg_get_sense(SCSIRequest *req, uint8_t *outbuf, int len)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, req->dev);
+    int size = SCSI_SENSE_BUF_SIZE;
+
+    if (s->driver_status & SG_ERR_DRIVER_SENSE) {
+        if (len < SCSI_SENSE_BUF_SIZE)
+            size = len;
+        else
+            size = SCSI_SENSE_BUF_SIZE;
+
+        memcpy(outbuf, s->sensebuf, size);
+    }
+
+    return size;
+}
+
+static void bsg_command_complete(void *opaque, int ret)
+{
+    SCSIBSGReq *r = opaque;
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+
+    s->driver_status = r->bsg_hdr.driver_status;
+    if (s->driver_status)
+        s->senselen = SCSI_SENSE_BUF_SIZE;
+
+    if (ret != 0) {
+        switch (ret) {
+        case -ENODEV:
+            s->senselen = bsg_set_sense(s, SENSE_CODE(LUN_NOT_SUPPORTED));
+            break;
+        case -EINVAL:
+            s->senselen = bsg_set_sense(s, SENSE_CODE(INVALID_FIELD));
+            break;
+        case -EBADR:
+            s->senselen = bsg_set_sense(s, SENSE_CODE(TARGET_FAILURE));
+            break;
+        default:
+            s->senselen = bsg_set_sense(s, SENSE_CODE(IO_ERROR));
+            break;
+        }
+        scsi_req_print(&r->req);
+        error_report("%s: ret %d (%s)\n", __FUNCTION__,
+                ret, strerror(-ret));
+        s->driver_status = SG_ERR_DRIVER_SENSE;
+        r->req.status = CHECK_CONDITION;
+    } else {
+        if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) {
+            scsi_req_print(&r->req);
+            r->req.status = BUSY;
+            error_report("%s: timeout\n", __FUNCTION__);
+        } else if (r->bsg_hdr.device_status) {
+            r->req.status = r->bsg_hdr.device_status;
+        } else if (s->driver_status & SG_ERR_DRIVER_SENSE) {
+            scsi_req_print(&r->req);
+            error_report("%s: driver sense\n", __FUNCTION__);
+            r->req.status = CHECK_CONDITION;
+        } else {
+            r->req.status = GOOD;
+        }
+    }
+    DPRINTF_BSG_IO("Command complete 0x%p tag=0x%x status=%d\n",
+            r, r->req.tag, r->req.status);
+
+    scsi_req_complete(&r->req);
+}
+
+static int bsg_execute_command(BlockDriverState *bdrv,
+                               SCSIBSGReq *r,
+                               BlockDriverCompletionFunc *complete)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+    /*
+     * Following linux/include/linux/bsg.h
+     */ 
+    /* [i] 'Q' to differentiate from v3 */
+    r->bsg_hdr.guard = 'Q';
+    r->bsg_hdr.protocol = BSG_PROTOCOL_SCSI;
+    r->bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+    r->bsg_hdr.request_len = r->req.cmd.len;
+    r->bsg_hdr.request = (unsigned long)r->req.cmd.buf;
+    r->bsg_hdr.max_response_len = sizeof(s->sensebuf);
+    /* SCSI: (auto)sense data */
+    r->bsg_hdr.response = (unsigned long)s->sensebuf;
+    /* Unlimited timeout */
+    r->bsg_hdr.timeout = UINT_MAX;
+    /* [i->o] unused internally */
+    r->bsg_hdr.usr_ptr = (unsigned long)r;
+    /* Bsg does Q_AT_HEAD by default */
+    r->bsg_hdr.flags = BSG_FLAG_Q_AT_TAIL;
+
+    qemu_iovec_reset(&r->aio_iov);
+    qemu_iovec_add(&r->aio_iov, &r->bsg_hdr, sizeof(r->bsg_hdr));
+
+    r->req.aiocb = paio_submit_len(bdrv, bdrv->fd, 0, &r->aio_iov,
+                sizeof(r->bsg_hdr), complete, r, QEMU_AIO_WRITE);
+    if (r->req.aiocb == NULL) {
+        BADF("execute_command: paio_submit_len() failed\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void bsg_setup_command(SCSIBSGReq *r)
+{
+    if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+        r->bsg_hdr.dout_xferp = (unsigned long)r->buf;
+        r->bsg_hdr.dout_xfer_len = r->buflen;
+    } else if (r->req.cmd.mode == SCSI_XFER_FROM_DEV) {
+        r->bsg_hdr.din_xferp = (unsigned long)r->buf;
+        r->bsg_hdr.din_xfer_len = r->buflen;
+    }
+    DPRINTF_BSG_IO("setup BUF: %p, dxfer_len %u\n", r->buf, r->buflen);
+}
+
+static void bsg_setup_command_iov(SCSIBSGReq *r)
+{
+    struct iovec *iov = r->saved_iov;
+    int i;
+
+    if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+        r->bsg_hdr.dout_iovec_count = r->saved_iov_num;
+        r->bsg_hdr.dout_xferp = (unsigned long)r->saved_iov;
+        r->bsg_hdr.dout_xfer_len = 0;
+        for (i = 0; i < r->saved_iov_num; i++)
+            r->bsg_hdr.dout_xfer_len += iov[i].iov_len;
+
+    } else if (r->req.cmd.mode == SCSI_XFER_FROM_DEV) {
+        r->bsg_hdr.din_iovec_count = r->saved_iov_num;
+        r->bsg_hdr.din_xferp = (unsigned long)r->saved_iov;
+        r->bsg_hdr.din_xfer_len = 0;
+        for (i = 0; i < r->saved_iov_num; i++)
+            r->bsg_hdr.din_xfer_len += iov[i].iov_len;
+    }
+
+    DPRINTF_BSG_IO("setup IOV: iovec_num: %u, iov: %p,"
+            " dout_xfer_len: %u din_xfer_len: %u\n",
+            r->saved_iov_num, r->saved_iov,
+            r->bsg_hdr.dout_xfer_len, r->bsg_hdr.din_xfer_len);
+}
+
+static void bsg_read_complete(void * opaque, int ret)
+{
+    SCSIBSGReq *r = (SCSIBSGReq *)opaque;
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+    struct sg_io_v4 io_hdr;
+    int err, len;
+
+    if (ret) {
+        DPRINTF("IO error ret %d\n", ret);
+        bsg_command_complete(r, ret);
+        return;
+    }
+
+    r->req.aiocb = NULL;
+
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    /* [i] 'Q' to differentiate from v3 */
+    io_hdr.guard = 'Q';
+    err = bsg_read(s->bs->fd, &io_hdr, sizeof(io_hdr));
+    if (err) {
+        DPRINTF("bsg_read() failed with ret: %d\n", err);
+        bsg_command_complete(r, EBADR);
+        return;
+    }
+
+    len = r->bsg_hdr.din_xfer_len - r->bsg_hdr.din_resid;
+    DPRINTF_BSG_IO("BSG READ Data ready tag=0x%x len=%d\n", r->req.tag, len);
+
+    r->len = -1;
+    r->req.bus->complete(&r->req, SCSI_REASON_DATA, len);
+}
+
+/* Read more data from scsi device into buffer.  */
+static void bsg_read_data(SCSIRequest *req)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, req->dev);
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+    int ret;
+
+    DPRINTF_BSG_IO("bsg_read_data 0x%x\n", req->tag);
+    if (r->len == -1) {
+        bsg_command_complete(r, 0);
+        return;
+    }
+
+    if (r->req.cmd.buf[0] == REQUEST_SENSE &&
+            s->driver_status & SG_ERR_DRIVER_SENSE) {
+        s->senselen = MIN(r->len, s->senselen);
+        memcpy(r->buf, s->sensebuf, s->senselen);
+        r->bsg_hdr.driver_status = 0;
+        r->bsg_hdr.device_status = 0;
+        r->bsg_hdr.max_response_len = s->senselen;
+        r->len = -1;
+        DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, s->senselen);
+        DPRINTF("Sense: %d %d %d %d %d %d %d %d\n",
+                r->buf[0], r->buf[1], r->buf[2], r->buf[3],
+                r->buf[4], r->buf[5], r->buf[6], r->buf[7]);
+        r->req.bus->complete(&r->req, SCSI_REASON_DATA, s->senselen);
+        /* Clear sensebuf after REQUEST_SENSE */
+        bsg_clear_sense(s);
+        return;
+    }
+
+    if (r->buf != NULL)
+        bsg_setup_command(r);
+    else
+        bsg_setup_command_iov(r);
+
+    ret = bsg_execute_command(s->bs, r, bsg_read_complete);
+    if (ret == -1) {
+        bsg_command_complete(r, -EBADR);
+        return;
+    }
+}
+
+static void bsg_write_complete(void *opaque, int ret)
+{
+    SCSIBSGReq *r = (SCSIBSGReq *)opaque;
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+    struct sg_io_v4 io_hdr;
+    int err;
+
+    DPRINTF_BSG_IO("bsg_write_complete() ret = %d\n", ret);
+    if (ret) {
+        DPRINTF("IO error\n");
+        bsg_command_complete(r, ret);
+        return;
+    }
+
+    r->req.aiocb = NULL;
+
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    /* [i] 'Q' to differentiate from v3 */
+    io_hdr.guard = 'Q';
+    err = bsg_read(s->bs->fd, &io_hdr, sizeof(io_hdr));
+    if (err) {
+            DPRINTF("bsg_read() failed with ret: %d\n", err);
+            bsg_command_complete(r, EBADR);
+            return;
+    }
+
+    /*
+     * Copied from hw/scsi-generic.c:scsi_write_complete(), is this still
+     * necessary for BSG..?
+     */
+    if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 &&
+        s->qdev.type == TYPE_TAPE) {
+        s->qdev.blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11];
+        DPRINTF("block size %d\n", s->qdev.blocksize);
+    }
+
+    bsg_command_complete(r, ret);
+}
+
+static int bsg_write_data(SCSIRequest *req)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, req->dev);
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+    int ret;
+
+    DPRINTF_BSG_IO("bsg_write_data 0x%x\n", req->tag);
+
+    if (r->len == 0 && r->bsg_hdr.dout_xfer_len == 0) {
+        r->len = r->buflen;
+        r->req.bus->complete(&r->req, SCSI_REASON_DATA, r->len);
+        return 0;
+    }
+
+    ret = bsg_execute_command(s->bs, r, bsg_write_complete);
+    if (ret == -1) {
+        bsg_command_complete(r, -EBADR);
+        return 1;
+    }
+
+    return 0;
+}
+
+static void bsg_req_fixup(SCSIRequest *req)
+{
+    return;
+}
+
+static int bsg_get_blocksize(BlockDriverState *bdrv)
+{
+    uint8_t cmd[10];
+    uint8_t buf[8];
+    uint8_t sensebuf[8];
+    struct sg_io_v4 bsg_hdr;
+    int ret;
+
+    memset(cmd, 0, sizeof(cmd));
+    memset(buf, 0, sizeof(buf));
+    cmd[0] = READ_CAPACITY;
+
+    memset(&bsg_hdr, 0, sizeof(bsg_hdr));
+    bsg_hdr.guard = 'Q';
+    bsg_hdr.protocol = BSG_PROTOCOL_SCSI;
+    bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+    bsg_hdr.request_len = sizeof(cmd);
+    bsg_hdr.request = (unsigned long)cmd;
+    bsg_hdr.din_xfer_len = sizeof(buf);
+    bsg_hdr.din_xferp = (unsigned long)buf;
+    bsg_hdr.max_response_len = sizeof(sensebuf);
+    bsg_hdr.response = (unsigned long)sensebuf;
+    bsg_hdr.timeout = 6000; /* XXX */
+
+    ret = bdrv_ioctl(bdrv, SG_IO, (void *)&bsg_hdr);
+    if (ret < 0)
+        return -1;
+
+    return (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
+}
+
+static int bsg_get_stream_blocksize(BlockDriverState *bdrv)
+{
+    uint8_t cmd[6];
+    uint8_t buf[12];
+    uint8_t sensebuf[8];
+    struct sg_io_v4 bsg_hdr;
+    int ret;
+
+    memset(cmd, 0, sizeof(cmd));
+    memset(buf, 0, sizeof(buf));
+    cmd[0] = MODE_SENSE;
+    cmd[4] = sizeof(buf);
+
+    memset(&bsg_hdr, 0, sizeof(bsg_hdr));
+    bsg_hdr.guard = 'Q';
+    bsg_hdr.protocol = BSG_PROTOCOL_SCSI;
+    bsg_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+    bsg_hdr.request_len = sizeof(cmd);
+    bsg_hdr.request = (unsigned long)cmd;
+    bsg_hdr.din_xfer_len = sizeof(buf);
+    bsg_hdr.din_xferp = (unsigned long)buf;
+    bsg_hdr.max_response_len = sizeof(sensebuf);
+    bsg_hdr.response = (unsigned long)sensebuf;
+    bsg_hdr.timeout = 6000; /* XXX */
+
+    ret = bdrv_ioctl(bdrv, SG_IO, (void *)&bsg_hdr);
+    if (ret < 0)
+        return -1;
+
+    return (buf[9] << 16) | (buf[10] << 8) | buf[11];
+}
+
+static void bsg_remove_request(SCSIRequest *);
+
+static void bsg_purge_requests(SCSIBSGState *s)
+{
+    SCSIBSGReq *r;
+
+    while (!QTAILQ_EMPTY(&s->qdev.requests)) {
+        r = DO_UPCAST(SCSIBSGReq, req, QTAILQ_FIRST(&s->qdev.requests));
+        if (r->req.aiocb) {
+            bdrv_aio_cancel(r->req.aiocb);
+        }
+        bsg_remove_request(&r->req);
+    }
+}
+
+static void bsg_reset(DeviceState *dev)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev.qdev, dev);
+
+    bsg_purge_requests(s);
+}
+
+static void bsg_destroy(SCSIDevice *d)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, d);
+
+    bsg_purge_requests(s);
+    blockdev_mark_auto_del(s->qdev.conf.bs);
+}
+
+struct scsi_idlun {
+    uint32_t dev_id;
+    uint32_t host_unique_id;
+};
+
+static int bsg_generic_initfn(SCSIDevice *dev)
+{
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, dev);
+
+    if (!s->qdev.conf.bs) {
+        error_report("scsi-bsg: drive property not set");
+        return -1;
+    }
+    s->bs = s->qdev.conf.bs;
+
+    /* check we are really using a /dev/bsg/ * file */
+    if (!bdrv_is_bsg(s->bs)) {
+        error_report("scsi-bsg: not BSG*");
+        return -1;
+    }
+#if 0
+    /* get LUN of the BSG */
+    if (bdrv_ioctl(s->bs, SG_GET_SCSI_ID, &scsiid)) {
+        error_report("scsi-bsg: SG_GET_SCSI_ID ioctl failed");
+        return -1;
+    }
+#endif
+// FIXME: Get SCSI lun from BSG 
+    s->lun = 0;
+// FIXME: Get SCSI device type from BSG INQUIRY
+    s->qdev.type = TYPE_DISK;
+    DPRINTF("LUN %d\n", s->lun);
+    DPRINTF("device type %d\n", s->qdev.type);
+
+    if (s->qdev.type == TYPE_TAPE) {
+        s->qdev.blocksize = bsg_get_stream_blocksize(s->bs);
+        if (s->qdev.blocksize == -1)
+            s->qdev.blocksize = 0;
+    } else {
+        s->qdev.blocksize = bsg_get_blocksize(s->bs);
+        /* removable media returns 0 if not present */
+        if (s->qdev.blocksize <= 0) {
+            if (s->qdev.type == TYPE_ROM || s->qdev.type  == TYPE_WORM)
+                s->qdev.blocksize = 2048;
+            else
+                s->qdev.blocksize = 512;
+        }
+    }
+    DPRINTF("block size %d\n", s->qdev.blocksize);
+    s->driver_status = 0;
+    memset(s->sensebuf, 0, sizeof(s->sensebuf));
+    return 0;
+}
+
+static SCSIRequest *bsg_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun)
+{
+    return scsi_req_alloc(sizeof(SCSIBSGReq), d, tag, lun);
+}
+
+static SCSIRequest *bsg_new_request_iovec(SCSIDevice *d, uint32_t tag,
+                                        uint32_t lun, struct iovec *iov, int iov_num)
+{
+    SCSIRequest *req;
+    SCSIBSGReq *r;
+
+    req = scsi_req_alloc(sizeof(SCSIBSGReq), d, tag, lun);
+    if (!req)
+        return NULL;
+
+    r = DO_UPCAST(SCSIBSGReq, req, req);
+    r->saved_iov = iov;
+    r->saved_iov_num = iov_num;
+
+    qemu_iovec_init(&r->aio_iov, 1);
+
+    r->buf = NULL;
+    r->buflen = 0;
+
+    return req;
+}
+
+static void bsg_remove_request(SCSIRequest *req)
+{
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+
+    qemu_free(r->buf);
+    qemu_iovec_destroy(&r->aio_iov);
+    scsi_req_free(&r->req);
+}
+
+/*
+ * Used as a QEMU block completion callback by bsg_send_command()
+ * for SG_DXFER_NONE and zero-length SCSI_XFER_TO_DEV.
+ */
+static void bsg_complete_none(void *opaque, int ret)
+{
+    SCSIRequest *req = opaque;
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+    struct sg_io_v4 io_hdr;
+    int err;
+
+    req->aiocb = NULL;
+
+    memset(&io_hdr, 0, sizeof(io_hdr));
+    /* [i] 'Q' to differentiate from v3 */
+    io_hdr.guard = 'Q';
+
+    err = bsg_read(s->bs->fd, &io_hdr, sizeof(io_hdr));
+    if (err) {
+        DPRINTF("bsg_read() failed with ret: %d\n", err);
+        return;
+    }
+
+    bsg_command_complete(opaque, ret);
+}
+
+static int32_t bsg_send_command(SCSIRequest *req, uint8_t *cmd)
+{
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+    SCSIBSGState *s = DO_UPCAST(SCSIBSGState, qdev, r->req.dev);
+    int ret;
+
+    if (cmd[0] != REQUEST_SENSE &&
+        (req->lun != s->lun || (cmd[1] >> 5) != s->lun)) {
+
+        DPRINTF("Unimplemented LUN %d\n", req->lun ? req->lun : cmd[1] >> 5);
+        bsg_command_complete(r, -ENODEV);
+        return 0;
+    }
+
+    if (-1 == scsi_req_parse(&r->req, cmd)) {
+        BADF("Unsupported command length, command %x\n", cmd[0]);
+        bsg_command_complete(r, -EINVAL);
+        return 0;
+    }
+    bsg_req_fixup(&r->req);
+
+    DPRINTF_BSG_IO("bsg_send_command: lun=%d tag=0x%x len %zd data=0x%02x",
+            req->lun, req->tag, r->req.cmd.xfer, cmd[0]);
+
+#ifdef DEBUG_BSG_IO
+    {
+        int i;
+        for (i = 1; i < r->req.cmd.len; i++) {
+            printf(" 0x%02x", cmd[i]);
+        }
+        printf("\n");
+    }
+#endif
+
+    if (r->req.cmd.xfer == 0) {
+        if (r->buf != NULL)
+            qemu_free(r->buf);
+        r->buflen = 0;
+        r->buf = NULL;
+        ret = bsg_execute_command(s->bs, r, bsg_complete_none);
+        if (ret == -1) {
+            bsg_command_complete(r, -EBADR);
+            return 0;
+        }
+        return 0;
+    }
+
+    if (!r->saved_iov_num) {
+        if (r->buflen != r->req.cmd.xfer) {
+            if (r->buf != NULL)
+                qemu_free(r->buf);
+
+            r->buf = qemu_malloc(r->req.cmd.xfer);
+            r->buflen = r->req.cmd.xfer;
+        }
+
+        memset(r->buf, 0, r->buflen);
+    }
+    r->len = r->req.cmd.xfer;
+    if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+        r->len = 0;
+        return r->req.cmd.xfer;
+    }
+
+    return r->req.cmd.xfer;
+}
+
+/* Return a pointer to the data buffer.  */
+static uint8_t *bsg_get_buf(SCSIRequest *req)
+{
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+
+    return r->buf;
+}
+
+static void bsg_cancel_io(SCSIRequest *req)
+{
+    SCSIBSGReq *r = DO_UPCAST(SCSIBSGReq, req, req);
+
+    if (r->req.aiocb) {
+        bdrv_aio_cancel(r->req.aiocb);
+        r->req.aiocb = NULL;
+    }
+}
+
+static SCSIDeviceInfo bsg_info = {
+    .qdev.name      = "scsi-bsg",
+    .qdev.desc      = "pass through block layer scsi generic (/dev/bsg/*)",
+    .qdev.size      = sizeof(SCSIBSGState),
+    .qdev.reset     = bsg_reset,
+    .init           = bsg_generic_initfn,
+    .destroy        = bsg_destroy,
+    .alloc_req      = bsg_new_request,
+    .alloc_req_iov  = bsg_new_request_iovec,
+    .free_req       = bsg_remove_request,
+    .send_command   = bsg_send_command,
+    .read_data      = bsg_read_data,
+    .write_data     = bsg_write_data,
+    .cancel_io      = bsg_cancel_io,
+    .get_buf        = bsg_get_buf,
+    .get_sense      = bsg_get_sense,
+    .qdev.props     = (Property[]) {
+        DEFINE_BLOCK_PROPERTIES(SCSIBSGState, qdev.conf),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+static void bsg_register_devices(void)
+{
+    scsi_qdev_register(&bsg_info);
+}
+device_init(bsg_register_devices)
+
+#endif /* __linux__ */