Add qemu 2.4.0
[kvmfornfv.git] / qemu / hw / scsi / scsi-generic.c
diff --git a/qemu/hw/scsi/scsi-generic.c b/qemu/hw/scsi/scsi-generic.c
new file mode 100644 (file)
index 0000000..e53470f
--- /dev/null
@@ -0,0 +1,496 @@
+/*
+ * Generic SCSI Device support
+ *
+ * Copyright (c) 2007 Bull S.A.S.
+ * Based on code by Paul Brook
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Laurent Vivier <Laurent.Vivier@bull.net>
+ *
+ * This code is licensed under the LGPL.
+ *
+ */
+
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/scsi/scsi.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+
+#ifdef __linux__
+
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, ...) \
+do { printf("scsi-generic: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "scsi-generic: " fmt , ## __VA_ARGS__); } while (0)
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <scsi/sg.h>
+#include "block/scsi.h"
+
+#define SG_ERR_DRIVER_TIMEOUT  0x06
+#define SG_ERR_DRIVER_SENSE    0x08
+
+#define SG_ERR_DID_OK          0x00
+#define SG_ERR_DID_NO_CONNECT  0x01
+#define SG_ERR_DID_BUS_BUSY    0x02
+#define SG_ERR_DID_TIME_OUT    0x03
+
+#ifndef MAX_UINT
+#define MAX_UINT ((unsigned int)-1)
+#endif
+
+typedef struct SCSIGenericReq {
+    SCSIRequest req;
+    uint8_t *buf;
+    int buflen;
+    int len;
+    sg_io_hdr_t io_header;
+} SCSIGenericReq;
+
+static void scsi_generic_save_request(QEMUFile *f, SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+    qemu_put_sbe32s(f, &r->buflen);
+    if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+        assert(!r->req.sg);
+        qemu_put_buffer(f, r->buf, r->req.cmd.xfer);
+    }
+}
+
+static void scsi_generic_load_request(QEMUFile *f, SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+    qemu_get_sbe32s(f, &r->buflen);
+    if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+        assert(!r->req.sg);
+        qemu_get_buffer(f, r->buf, r->req.cmd.xfer);
+    }
+}
+
+static void scsi_free_request(SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+    g_free(r->buf);
+}
+
+/* Helper function for command completion.  */
+static void scsi_command_complete(void *opaque, int ret)
+{
+    int status;
+    SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+
+    r->req.aiocb = NULL;
+    if (r->req.io_canceled) {
+        scsi_req_cancel_complete(&r->req);
+        goto done;
+    }
+    if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
+        r->req.sense_len = r->io_header.sb_len_wr;
+    }
+
+    if (ret != 0) {
+        switch (ret) {
+        case -EDOM:
+            status = TASK_SET_FULL;
+            break;
+        case -ENOMEM:
+            status = CHECK_CONDITION;
+            scsi_req_build_sense(&r->req, SENSE_CODE(TARGET_FAILURE));
+            break;
+        default:
+            status = CHECK_CONDITION;
+            scsi_req_build_sense(&r->req, SENSE_CODE(IO_ERROR));
+            break;
+        }
+    } else {
+        if (r->io_header.host_status == SG_ERR_DID_NO_CONNECT ||
+            r->io_header.host_status == SG_ERR_DID_BUS_BUSY ||
+            r->io_header.host_status == SG_ERR_DID_TIME_OUT ||
+            (r->io_header.driver_status & SG_ERR_DRIVER_TIMEOUT)) {
+            status = BUSY;
+            BADF("Driver Timeout\n");
+        } else if (r->io_header.host_status) {
+            status = CHECK_CONDITION;
+            scsi_req_build_sense(&r->req, SENSE_CODE(I_T_NEXUS_LOSS));
+        } else if (r->io_header.status) {
+            status = r->io_header.status;
+        } else if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
+            status = CHECK_CONDITION;
+        } else {
+            status = GOOD;
+        }
+    }
+    DPRINTF("Command complete 0x%p tag=0x%x status=%d\n",
+            r, r->req.tag, status);
+
+    scsi_req_complete(&r->req, status);
+done:
+    scsi_req_unref(&r->req);
+}
+
+static int execute_command(BlockBackend *blk,
+                           SCSIGenericReq *r, int direction,
+                           BlockCompletionFunc *complete)
+{
+    r->io_header.interface_id = 'S';
+    r->io_header.dxfer_direction = direction;
+    r->io_header.dxferp = r->buf;
+    r->io_header.dxfer_len = r->buflen;
+    r->io_header.cmdp = r->req.cmd.buf;
+    r->io_header.cmd_len = r->req.cmd.len;
+    r->io_header.mx_sb_len = sizeof(r->req.sense);
+    r->io_header.sbp = r->req.sense;
+    r->io_header.timeout = MAX_UINT;
+    r->io_header.usr_ptr = r;
+    r->io_header.flags |= SG_FLAG_DIRECT_IO;
+
+    r->req.aiocb = blk_aio_ioctl(blk, SG_IO, &r->io_header, complete, r);
+    if (r->req.aiocb == NULL) {
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+    SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+    SCSIDevice *s = r->req.dev;
+    int len;
+
+    r->req.aiocb = NULL;
+    if (ret || r->req.io_canceled) {
+        scsi_command_complete(r, ret);
+        return;
+    }
+    len = r->io_header.dxfer_len - r->io_header.resid;
+    DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, len);
+
+    r->len = -1;
+    if (len == 0) {
+        scsi_command_complete(r, 0);
+    } else {
+        /* Snoop READ CAPACITY output to set the blocksize.  */
+        if (r->req.cmd.buf[0] == READ_CAPACITY_10 &&
+            (ldl_be_p(&r->buf[0]) != 0xffffffffU || s->max_lba == 0)) {
+            s->blocksize = ldl_be_p(&r->buf[4]);
+            s->max_lba = ldl_be_p(&r->buf[0]) & 0xffffffffULL;
+        } else if (r->req.cmd.buf[0] == SERVICE_ACTION_IN_16 &&
+                   (r->req.cmd.buf[1] & 31) == SAI_READ_CAPACITY_16) {
+            s->blocksize = ldl_be_p(&r->buf[8]);
+            s->max_lba = ldq_be_p(&r->buf[0]);
+        }
+        blk_set_guest_block_size(s->conf.blk, s->blocksize);
+
+        scsi_req_data(&r->req, len);
+        scsi_req_unref(&r->req);
+    }
+}
+
+/* Read more data from scsi device into buffer.  */
+static void scsi_read_data(SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+    SCSIDevice *s = r->req.dev;
+    int ret;
+
+    DPRINTF("scsi_read_data 0x%x\n", req->tag);
+
+    /* The request is used as the AIO opaque value, so add a ref.  */
+    scsi_req_ref(&r->req);
+    if (r->len == -1) {
+        scsi_command_complete(r, 0);
+        return;
+    }
+
+    ret = execute_command(s->conf.blk, r, SG_DXFER_FROM_DEV,
+                          scsi_read_complete);
+    if (ret < 0) {
+        scsi_command_complete(r, ret);
+    }
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+    SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+    SCSIDevice *s = r->req.dev;
+
+    DPRINTF("scsi_write_complete() ret = %d\n", ret);
+    r->req.aiocb = NULL;
+    if (ret || r->req.io_canceled) {
+        scsi_command_complete(r, ret);
+        return;
+    }
+
+    if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 &&
+        s->type == TYPE_TAPE) {
+        s->blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11];
+        DPRINTF("block size %d\n", s->blocksize);
+    }
+
+    scsi_command_complete(r, ret);
+}
+
+/* Write data to a scsi device.  Returns nonzero on failure.
+   The transfer may complete asynchronously.  */
+static void scsi_write_data(SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+    SCSIDevice *s = r->req.dev;
+    int ret;
+
+    DPRINTF("scsi_write_data 0x%x\n", req->tag);
+    if (r->len == 0) {
+        r->len = r->buflen;
+        scsi_req_data(&r->req, r->len);
+        return;
+    }
+
+    /* The request is used as the AIO opaque value, so add a ref.  */
+    scsi_req_ref(&r->req);
+    ret = execute_command(s->conf.blk, r, SG_DXFER_TO_DEV, scsi_write_complete);
+    if (ret < 0) {
+        scsi_command_complete(r, ret);
+    }
+}
+
+/* Return a pointer to the data buffer.  */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+    return r->buf;
+}
+
+/* Execute a scsi command.  Returns the length of the data expected by the
+   command.  This will be Positive for data transfers from the device
+   (eg. disk reads), negative for transfers to the device (eg. disk writes),
+   and zero if the command does not transfer any data.  */
+
+static int32_t scsi_send_command(SCSIRequest *req, uint8_t *cmd)
+{
+    SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+    SCSIDevice *s = r->req.dev;
+    int ret;
+
+#ifdef DEBUG_SCSI
+    {
+        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) {
+        g_free(r->buf);
+        r->buflen = 0;
+        r->buf = NULL;
+        /* The request is used as the AIO opaque value, so add a ref.  */
+        scsi_req_ref(&r->req);
+        ret = execute_command(s->conf.blk, r, SG_DXFER_NONE,
+                              scsi_command_complete);
+        if (ret < 0) {
+            scsi_command_complete(r, ret);
+            return 0;
+        }
+        return 0;
+    }
+
+    if (r->buflen != r->req.cmd.xfer) {
+        g_free(r->buf);
+        r->buf = g_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;
+    } else {
+        return r->req.cmd.xfer;
+    }
+}
+
+static int get_stream_blocksize(BlockBackend *blk)
+{
+    uint8_t cmd[6];
+    uint8_t buf[12];
+    uint8_t sensebuf[8];
+    sg_io_hdr_t io_header;
+    int ret;
+
+    memset(cmd, 0, sizeof(cmd));
+    memset(buf, 0, sizeof(buf));
+    cmd[0] = MODE_SENSE;
+    cmd[4] = sizeof(buf);
+
+    memset(&io_header, 0, sizeof(io_header));
+    io_header.interface_id = 'S';
+    io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+    io_header.dxfer_len = sizeof(buf);
+    io_header.dxferp = buf;
+    io_header.cmdp = cmd;
+    io_header.cmd_len = sizeof(cmd);
+    io_header.mx_sb_len = sizeof(sensebuf);
+    io_header.sbp = sensebuf;
+    io_header.timeout = 6000; /* XXX */
+
+    ret = blk_ioctl(blk, SG_IO, &io_header);
+    if (ret < 0 || io_header.driver_status || io_header.host_status) {
+        return -1;
+    }
+    return (buf[9] << 16) | (buf[10] << 8) | buf[11];
+}
+
+static void scsi_generic_reset(DeviceState *dev)
+{
+    SCSIDevice *s = SCSI_DEVICE(dev);
+
+    scsi_device_purge_requests(s, SENSE_CODE(RESET));
+}
+
+static void scsi_generic_realize(SCSIDevice *s, Error **errp)
+{
+    int rc;
+    int sg_version;
+    struct sg_scsi_id scsiid;
+
+    if (!s->conf.blk) {
+        error_setg(errp, "drive property not set");
+        return;
+    }
+
+    if (blk_get_on_error(s->conf.blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
+        error_setg(errp, "Device doesn't support drive option werror");
+        return;
+    }
+    if (blk_get_on_error(s->conf.blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+        error_setg(errp, "Device doesn't support drive option rerror");
+        return;
+    }
+
+    /* check we are using a driver managing SG_IO (version 3 and after */
+    rc = blk_ioctl(s->conf.blk, SG_GET_VERSION_NUM, &sg_version);
+    if (rc < 0) {
+        error_setg(errp, "cannot get SG_IO version number: %s.  "
+                         "Is this a SCSI device?",
+                         strerror(-rc));
+        return;
+    }
+    if (sg_version < 30000) {
+        error_setg(errp, "scsi generic interface too old");
+        return;
+    }
+
+    /* get LUN of the /dev/sg? */
+    if (blk_ioctl(s->conf.blk, SG_GET_SCSI_ID, &scsiid)) {
+        error_setg(errp, "SG_GET_SCSI_ID ioctl failed");
+        return;
+    }
+
+    /* define device state */
+    s->type = scsiid.scsi_type;
+    DPRINTF("device type %d\n", s->type);
+
+    switch (s->type) {
+    case TYPE_TAPE:
+        s->blocksize = get_stream_blocksize(s->conf.blk);
+        if (s->blocksize == -1) {
+            s->blocksize = 0;
+        }
+        break;
+
+        /* Make a guess for block devices, we'll fix it when the guest sends.
+         * READ CAPACITY.  If they don't, they likely would assume these sizes
+         * anyway. (TODO: they could also send MODE SENSE).
+         */
+    case TYPE_ROM:
+    case TYPE_WORM:
+        s->blocksize = 2048;
+        break;
+    default:
+        s->blocksize = 512;
+        break;
+    }
+
+    DPRINTF("block size %d\n", s->blocksize);
+}
+
+const SCSIReqOps scsi_generic_req_ops = {
+    .size         = sizeof(SCSIGenericReq),
+    .free_req     = scsi_free_request,
+    .send_command = scsi_send_command,
+    .read_data    = scsi_read_data,
+    .write_data   = scsi_write_data,
+    .get_buf      = scsi_get_buf,
+    .load_request = scsi_generic_load_request,
+    .save_request = scsi_generic_save_request,
+};
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
+                                     uint8_t *buf, void *hba_private)
+{
+    SCSIRequest *req;
+
+    req = scsi_req_alloc(&scsi_generic_req_ops, d, tag, lun, hba_private);
+    return req;
+}
+
+static Property scsi_generic_properties[] = {
+    DEFINE_PROP_DRIVE("drive", SCSIDevice, conf.blk),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static int scsi_generic_parse_cdb(SCSIDevice *dev, SCSICommand *cmd,
+                                  uint8_t *buf, void *hba_private)
+{
+    return scsi_bus_parse_cdb(dev, cmd, buf, hba_private);
+}
+
+static void scsi_generic_class_initfn(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+    sc->realize      = scsi_generic_realize;
+    sc->alloc_req    = scsi_new_request;
+    sc->parse_cdb    = scsi_generic_parse_cdb;
+    dc->fw_name = "disk";
+    dc->desc = "pass through generic scsi device (/dev/sg*)";
+    dc->reset = scsi_generic_reset;
+    dc->props = scsi_generic_properties;
+    dc->vmsd  = &vmstate_scsi_device;
+}
+
+static const TypeInfo scsi_generic_info = {
+    .name          = "scsi-generic",
+    .parent        = TYPE_SCSI_DEVICE,
+    .instance_size = sizeof(SCSIDevice),
+    .class_init    = scsi_generic_class_initfn,
+};
+
+static void scsi_generic_register_types(void)
+{
+    type_register_static(&scsi_generic_info);
+}
+
+type_init(scsi_generic_register_types)
+
+#endif /* __linux__ */