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