@@ -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
/*
@@ -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)
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 <pbonzini@redhat.com> --- hw/scsi-defs.h | 2 + hw/scsi-luns.c | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+), 0 deletions(-)