This introduces a device that dispatches SCSI requests according to the
"LUN" field of a hierarchical LUN.  In addition, the device always
processes REPORT LUNS commands, as well as commands for invalid and
well-known LUNs.

Finally, the device implements dummy INQUIRY emulation in case the
user sets up all devices on non-zero LUNs.  This is not enough in Linux
to trigger a scan; I'll fix it before final submission.

Signed-off-by: Paolo Bonzini <pbonz...@redhat.com>
---
 hw/scsi-defs.h |    2 +
 hw/scsi-luns.c |  340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 342 insertions(+), 0 deletions(-)

diff --git a/hw/scsi-defs.h b/hw/scsi-defs.h
index 66dfd4a..32fc82a 100644
--- a/hw/scsi-defs.h
+++ b/hw/scsi-defs.h
@@ -162,6 +162,9 @@
                                     * - treated as TYPE_DISK */
 #define TYPE_MEDIUM_CHANGER 0x08
 #define TYPE_ENCLOSURE     0x0d    /* Enclosure Services Device */
+#define TYPE_WLUN           0x1e    /* Well known LUN */
+#define TYPE_NOT_PRESENT    0x1f
+#define TYPE_INACTIVE       0x20
 #define TYPE_NO_LUN         0x7f
 
 /*
diff --git a/hw/scsi-luns.c b/hw/scsi-luns.c
index 699e3c1..4b158a9 100644
--- a/hw/scsi-luns.c
+++ b/hw/scsi-luns.c
@@ -8,11 +8,310 @@
  * This code is licenced under the LGPL.
  */
 
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, ...) \
+do { printf("scsi-target: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "scsi-target: " fmt , ## __VA_ARGS__); } while (0)
+
 #include "qemu-common.h"
 #include "qemu-error.h"
 #include "scsi.h"
+#include "scsi-defs.h"
 #include "sysemu.h"
 
+#define SCSI_MAX_INQUIRY_LEN 256
+
+typedef struct SCSITargetState SCSITargetState;
+
+ typedef struct SCSITargetReq {
+    SCSIRequest req;
+    uint32_t buflen;
+    uint32_t status;
+    uint8_t buf[128];
+    uint8_t *p_buf;
+} SCSITargetReq;
+
+struct SCSITargetState
+{
+    SCSIDevice qdev;
+    char *version;
+    SCSISense sense;
+};
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, DeviceState *initiator,
+                                    uint32_t tag, uint32_t lun)
+{
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, d);
+    SCSIRequest *req;
+    SCSITargetReq *r;
+
+    req = scsi_req_alloc(sizeof(SCSITargetReq), &s->qdev, initiator, tag, lun);
+    r = DO_UPCAST(SCSITargetReq, req, req);
+    return req;
+}
+
+static void scsi_free_request(SCSIRequest *req)
+{
+    SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+    if (r->p_buf) {
+        qemu_free(r->p_buf);
+    }
+}
+
+static void scsi_target_clear_sense(SCSITargetState *s)
+{
+    memset(&s->sense, 0, sizeof(s->sense));
+}
+
+static void scsi_req_set_status(SCSITargetReq *r, int status, SCSISense sense)
+{
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, r->req.dev);
+
+    r->req.status = status;
+    s->sense = sense;
+}
+
+/* Helper function for command completion.  */
+static void scsi_command_complete(SCSITargetReq *r, int status, SCSISense 
sense)
+{
+    scsi_req_set_status(r, status, sense);
+    scsi_req_complete(&r->req);
+}
+
+/* Read more data from scsi device into buffer.  */
+static void scsi_read_data(SCSIRequest *req)
+{
+    SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+    uint32_t n;
+
+    n = r->buflen;
+    if (n > 0) {
+        r->buflen = 0;
+        scsi_req_data(&r->req, n);
+    } else {
+        scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE));
+    }
+}
+
+/* Return a pointer to the data buffer.  */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+    SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+    return r->p_buf ? r->p_buf : r->buf;
+}
+
+/* Copy sense information into the provided buffer */
+static int scsi_get_sense(SCSIRequest *req, uint8_t *outbuf, int len)
+{
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, req->dev);
+
+    return scsi_build_sense(s->sense, outbuf, len, len > 14);
+}
+
+static void store_lun(uint8_t *outbuf, int id)
+{
+    if (id < 256) {
+        outbuf[1] = id;
+        return;
+    }
+    if (id < 16384) {
+        outbuf[1] = (id & 255);
+        outbuf[0] = (id >> 8) | (ADDR_FLAT_SPACE << 6);
+        return;
+    }
+    outbuf[3] = (id & 255);
+    outbuf[2] = ((id >> 8) & 255);
+    outbuf[1] = ((id >> 16) & 255);
+    outbuf[0] = ADDR_FLAT_SPACE_EXT;
+}
+
+static int scsi_target_emulate_report_luns(SCSITargetReq *r)
+{
+    SCSIBus *bus = r->req.dev->children;
+    DeviceState *d;
+    uint32_t list_len;
+    uint8_t *outbuf;
+    int n, buflen;
+    if (r->req.cmd.xfer < 16) {
+        return -1;
+    }
+    if (r->req.cmd.buf[2] > 2) {
+        return -1;
+    }
+
+    n = 0;
+    QLIST_FOREACH(d, &bus->qbus.children, sibling) {
+        n++;
+    }
+
+    list_len = n * 8;
+    buflen = list_len + 8;
+    if (buflen > sizeof(r->buf)) {
+        r->p_buf = qemu_malloc(buflen);
+        outbuf = r->p_buf;
+    } else {
+        outbuf = r->buf;
+    }
+
+    buflen = MIN(buflen, r->req.cmd.xfer);
+    memset(outbuf, 0, buflen);
+    outbuf[3] = (list_len & 0xff);
+    outbuf[2] = ((list_len >> 8) & 0xff);
+    outbuf[1] = ((list_len >> 16) & 0xff);
+    outbuf[0] = ((list_len >> 24) & 0xff);
+    outbuf += 8;
+
+    QLIST_FOREACH(d, &bus->qbus.children, sibling) {
+        SCSIDevice *dev = DO_UPCAST(SCSIDevice, qdev, d);
+        store_lun(outbuf, dev->id);
+        outbuf += 8;
+    }
+    return buflen;
+}
+
+static int scsi_target_emulate_inquiry(SCSITargetReq *r)
+{
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, r->req.dev);
+    uint8_t *outbuf = r->buf;
+    int buflen = 0;
+
+    if (r->req.cmd.buf[1] & 0x2) {
+        /* Command support data - optional, not implemented */
+        return -1;
+    }
+
+    if (r->req.cmd.buf[1] & 0x1) {
+        /* Vital product data */
+        uint8_t page_code = r->req.cmd.buf[2];
+        if (r->req.cmd.xfer < 4) {
+            return -1;
+        }
+
+        outbuf[buflen++] = page_code ; // this page
+        outbuf[buflen++] = 0x00;
+
+        switch (page_code) {
+        case 0x00: /* Supported page codes, mandatory */
+        {
+            int pages;
+            pages = buflen++;
+            outbuf[buflen++] = 0x00; // list of supported pages (this page)
+            outbuf[pages] = buflen - pages - 1; // number of pages
+            break;
+        }
+        default:
+            return -1;
+        }
+        /* done with EVPD */
+        return buflen;
+    }
+
+    /* Standard INQUIRY data */
+    if (r->req.cmd.buf[2] != 0) {
+        return -1;
+    }
+
+    /* PAGE CODE == 0 */
+    if (r->req.cmd.xfer < 5) {
+        return -1;
+    }
+
+    buflen = MIN(36, r->req.cmd.xfer);
+    memset(outbuf, 0, buflen);
+
+    if (r->req.lun == 0) {
+        outbuf[0] = TYPE_INACTIVE | TYPE_NOT_PRESENT;
+    } else if (r->req.lun == (LUN_WLUN_BASE | WLUN_REPORT_LUNS)) {
+        outbuf[0] = TYPE_WLUN;
+    } else {
+        outbuf[0] = TYPE_NO_LUN;       /* LUN not supported */
+        return buflen;
+    }
+
+    outbuf[2] = 5; /* Version */
+    outbuf[3] = 2 | 0x10; /* HiSup, response data format */
+    outbuf[4] = MAX(buflen, 36) - 5; /* Additional Length = (Len - 1) - 4 */
+    outbuf[7] = 0x10 | (r->req.bus->tcq ? 0x02 : 0); /* Sync, TCQ.  */
+    memcpy(&outbuf[8], "QEMU    ", 8);
+    memset(&outbuf[16], ' ', 16);
+    strncpy((char *) &outbuf[32], s->version, 4);
+    return buflen;
+}
+
+/* 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 *buf)
+{
+    SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, req->dev);
+    int32_t len;
+    uint8_t command;
+
+    command = buf[0];
+
+    if (scsi_req_parse(&r->req, buf) != 0) {
+        BADF("Unsupported command length, command %x\n", command);
+        scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_OPCODE));
+        return 0;
+    }
+
+    if (command != REQUEST_SENSE && command != INQUIRY &&
+        req->lun != 0 && req->lun != (LUN_WLUN_BASE | WLUN_REPORT_LUNS)) {
+        /* Only one LUN supported.  */
+        DPRINTF("Unimplemented LUN %d\n", req->lun);
+        scsi_command_complete(r, CHECK_CONDITION,
+                              SENSE_CODE(LUN_NOT_SUPPORTED));
+        return 0;
+    }
+    switch (command) {
+    case TEST_UNIT_READY:
+        scsi_command_complete(r, GOOD, SENSE_CODE(NO_SENSE));
+       return 0;
+    case REQUEST_SENSE:
+        if (req->cmd.xfer < 4)
+            goto illegal_request;
+        r->buflen = scsi_build_sense(s->sense, r->buf, req->cmd.xfer,
+                                     req->cmd.xfer > 13);
+        scsi_target_clear_sense(s);
+        scsi_req_set_status(r, GOOD, SENSE_CODE(NO_SENSE));
+        break;
+    case INQUIRY:
+        len = scsi_target_emulate_inquiry(r);
+        if (len < 0)
+            goto illegal_request;
+        r->buflen = len;
+        scsi_req_set_status(r, GOOD, SENSE_CODE(NO_SENSE));
+       break;
+    case REPORT_LUNS:
+        len = scsi_target_emulate_report_luns(r);
+        if (len < 0)
+            goto illegal_request;
+        r->buflen = len;
+        scsi_req_set_status(r, GOOD, SENSE_CODE(NO_SENSE));
+        break;
+    default:
+        scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_OPCODE));
+        return 0;
+    illegal_request:
+        scsi_command_complete(r, CHECK_CONDITION, SENSE_CODE(INVALID_FIELD));
+        return 0;
+    }
+    assert (r->req.cmd.mode == SCSI_XFER_FROM_DEV);
+    return r->buflen;
+}
+
 static struct SCSIBusOps path_scsi_ops = {
     .decode_lun = scsi_decode_target_from_lun
 };
@@ -35,8 +334,49 @@ static SCSIDeviceInfo scsi_path_info = {
     },
 };
 
+static int scsi_target_get_child_lun(SCSIDevice *dev)
+{
+    return dev->id;
+}
+
+static struct SCSIBusOps target_scsi_ops = {
+    .get_child_lun = scsi_target_get_child_lun
+};
+
+static int scsi_target_initfn(SCSIDevice *dev)
+{
+    SCSITargetState *s = DO_UPCAST(SCSITargetState, qdev, dev);
+
+    if (!s->version) {
+        s->version = qemu_strdup(QEMU_VERSION);
+    }
+
+    s->qdev.children = qemu_mallocz(sizeof(SCSIBus));
+    scsi_bus_new(s->qdev.children, &dev->qdev, 1, MAX_SCSI_DEVS,
+                 &target_scsi_ops);
+    return 0;
+}
+
+static SCSIDeviceInfo scsi_target_info = {
+    .qdev.name    = "scsi-target",
+    .qdev.desc    = "SCSI target device connected to multiple logical units",
+    .qdev.size    = sizeof(SCSITargetState),
+    .init         = scsi_target_initfn,
+    .alloc_req    = scsi_new_request,
+    .free_req     = scsi_free_request,
+    .send_command = scsi_send_command,
+    .read_data    = scsi_read_data,
+    .get_buf      = scsi_get_buf,
+    .get_sense    = scsi_get_sense,
+    .qdev.props   = (Property[]) {
+        DEFINE_PROP_STRING("ver",  SCSITargetState, version),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
 static void scsi_luns_register_devices(void)
 {
     scsi_qdev_register(&scsi_path_info);
+    scsi_qdev_register(&scsi_target_info);
 }
 device_init(scsi_luns_register_devices)
-- 
1.7.4.4



Reply via email to