#include "hw.h"
-#include "sysemu.h"
-#include "scsi-disk.h"
-#include "block.h"
+#include "qemu-error.h"
+#include "scsi.h"
+#include "scsi-defs.h"
#include "qdev.h"
+#include "blockdev.h"
static struct BusInfo scsi_bus_info = {
.name = "SCSI",
}
}
if (dev->id >= bus->ndev) {
- qemu_error("bad scsi device id: %d\n", dev->id);
+ error_report("bad scsi device id: %d", dev->id);
goto err;
}
bus->devs[dev->id] = dev;
dev->info = info;
+ QTAILQ_INIT(&dev->requests);
rc = dev->info->init(dev);
if (rc != 0) {
bus->devs[dev->id] = NULL;
}
/* handle legacy '-drive if=scsi,...' cmd line args */
-SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, DriveInfo *dinfo, int unit)
+SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv, int unit)
{
const char *driver;
DeviceState *dev;
- driver = bdrv_is_sg(dinfo->bdrv) ? "scsi-generic" : "scsi-disk";
+ driver = bdrv_is_sg(bdrv) ? "scsi-generic" : "scsi-disk";
dev = qdev_create(&bus->qbus, driver);
qdev_prop_set_uint32(dev, "scsi-id", unit);
- qdev_prop_set_drive(dev, "drive", dinfo);
- qdev_init(dev);
+ if (qdev_prop_set_drive(dev, "drive", bdrv) < 0) {
+ qdev_free(dev);
+ return NULL;
+ }
+ if (qdev_init(dev) < 0)
+ return NULL;
return DO_UPCAST(SCSIDevice, qdev, dev);
}
-void scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
+int scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
{
+ Location loc;
DriveInfo *dinfo;
- int unit;
+ int res = 0, unit;
+ loc_push_none(&loc);
for (unit = 0; unit < MAX_SCSI_DEVS; unit++) {
dinfo = drive_get(IF_SCSI, bus->busnr, unit);
if (dinfo == NULL) {
continue;
}
- scsi_bus_legacy_add_drive(bus, dinfo, unit);
+ qemu_opts_loc_restore(dinfo->opts);
+ if (!scsi_bus_legacy_add_drive(bus, dinfo->bdrv, unit)) {
+ res = -1;
+ break;
+ }
+ }
+ loc_pop(&loc);
+ return res;
+}
+
+void scsi_dev_clear_sense(SCSIDevice *dev)
+{
+ memset(&dev->sense, 0, sizeof(dev->sense));
+}
+
+void scsi_dev_set_sense(SCSIDevice *dev, uint8_t key)
+{
+ dev->sense.key = key;
+}
+
+SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun)
+{
+ SCSIRequest *req;
+
+ req = qemu_mallocz(size);
+ req->bus = scsi_bus_from_device(d);
+ req->dev = d;
+ req->tag = tag;
+ req->lun = lun;
+ req->status = -1;
+ req->enqueued = true;
+ QTAILQ_INSERT_TAIL(&d->requests, req, next);
+ return req;
+}
+
+SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag)
+{
+ SCSIRequest *req;
+
+ QTAILQ_FOREACH(req, &d->requests, next) {
+ if (req->tag == tag) {
+ return req;
+ }
}
+ return NULL;
+}
+
+static void scsi_req_dequeue(SCSIRequest *req)
+{
+ if (req->enqueued) {
+ QTAILQ_REMOVE(&req->dev->requests, req, next);
+ req->enqueued = false;
+ }
+}
+
+void scsi_req_free(SCSIRequest *req)
+{
+ scsi_req_dequeue(req);
+ qemu_free(req);
+}
+
+static int scsi_req_length(SCSIRequest *req, uint8_t *cmd)
+{
+ switch (cmd[0] >> 5) {
+ case 0:
+ req->cmd.xfer = cmd[4];
+ req->cmd.len = 6;
+ /* length 0 means 256 blocks */
+ if (req->cmd.xfer == 0)
+ req->cmd.xfer = 256;
+ break;
+ case 1:
+ case 2:
+ req->cmd.xfer = cmd[8] | (cmd[7] << 8);
+ req->cmd.len = 10;
+ break;
+ case 4:
+ req->cmd.xfer = cmd[13] | (cmd[12] << 8) | (cmd[11] << 16) | (cmd[10] << 24);
+ req->cmd.len = 16;
+ break;
+ case 5:
+ req->cmd.xfer = cmd[9] | (cmd[8] << 8) | (cmd[7] << 16) | (cmd[6] << 24);
+ req->cmd.len = 12;
+ break;
+ default:
+ return -1;
+ }
+
+ switch(cmd[0]) {
+ case TEST_UNIT_READY:
+ case REZERO_UNIT:
+ case START_STOP:
+ case SEEK_6:
+ case WRITE_FILEMARKS:
+ case SPACE:
+ case ERASE:
+ case ALLOW_MEDIUM_REMOVAL:
+ case VERIFY:
+ case SEEK_10:
+ case SYNCHRONIZE_CACHE:
+ case LOCK_UNLOCK_CACHE:
+ case LOAD_UNLOAD:
+ case SET_CD_SPEED:
+ case SET_LIMITS:
+ case WRITE_LONG:
+ case MOVE_MEDIUM:
+ case UPDATE_BLOCK:
+ req->cmd.xfer = 0;
+ break;
+ case MODE_SENSE:
+ break;
+ case WRITE_SAME:
+ req->cmd.xfer = 1;
+ break;
+ case READ_CAPACITY:
+ req->cmd.xfer = 8;
+ break;
+ case READ_BLOCK_LIMITS:
+ req->cmd.xfer = 6;
+ break;
+ case READ_POSITION:
+ req->cmd.xfer = 20;
+ break;
+ case SEND_VOLUME_TAG:
+ req->cmd.xfer *= 40;
+ break;
+ case MEDIUM_SCAN:
+ req->cmd.xfer *= 8;
+ break;
+ case WRITE_10:
+ case WRITE_VERIFY:
+ case WRITE_6:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ req->cmd.xfer *= req->dev->blocksize;
+ break;
+ case READ_10:
+ case READ_6:
+ case READ_REVERSE:
+ case RECOVER_BUFFERED_DATA:
+ case READ_12:
+ case READ_16:
+ req->cmd.xfer *= req->dev->blocksize;
+ break;
+ case INQUIRY:
+ req->cmd.xfer = cmd[4] | (cmd[3] << 8);
+ break;
+ case MAINTENANCE_OUT:
+ case MAINTENANCE_IN:
+ if (req->dev->type == TYPE_ROM) {
+ /* GPCMD_REPORT_KEY and GPCMD_SEND_KEY from multi media commands */
+ req->cmd.xfer = cmd[9] | (cmd[8] << 8);
+ }
+ break;
+ }
+ return 0;
+}
+
+static int scsi_req_stream_length(SCSIRequest *req, uint8_t *cmd)
+{
+ switch(cmd[0]) {
+ /* stream commands */
+ case READ_6:
+ case READ_REVERSE:
+ case RECOVER_BUFFERED_DATA:
+ case WRITE_6:
+ req->cmd.len = 6;
+ req->cmd.xfer = cmd[4] | (cmd[3] << 8) | (cmd[2] << 16);
+ if (cmd[1] & 0x01) /* fixed */
+ req->cmd.xfer *= req->dev->blocksize;
+ break;
+ case REWIND:
+ case START_STOP:
+ req->cmd.len = 6;
+ req->cmd.xfer = 0;
+ break;
+ /* generic commands */
+ default:
+ return scsi_req_length(req, cmd);
+ }
+ return 0;
+}
+
+static void scsi_req_xfer_mode(SCSIRequest *req)
+{
+ switch (req->cmd.buf[0]) {
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_VERIFY:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ case COPY:
+ case COPY_VERIFY:
+ case COMPARE:
+ case CHANGE_DEFINITION:
+ case LOG_SELECT:
+ case MODE_SELECT:
+ case MODE_SELECT_10:
+ case SEND_DIAGNOSTIC:
+ case WRITE_BUFFER:
+ case FORMAT_UNIT:
+ case REASSIGN_BLOCKS:
+ case RESERVE:
+ case SEARCH_EQUAL:
+ case SEARCH_HIGH:
+ case SEARCH_LOW:
+ case UPDATE_BLOCK:
+ case WRITE_LONG:
+ case WRITE_SAME:
+ case SEARCH_HIGH_12:
+ case SEARCH_EQUAL_12:
+ case SEARCH_LOW_12:
+ case SET_WINDOW:
+ case MEDIUM_SCAN:
+ case SEND_VOLUME_TAG:
+ case WRITE_LONG_2:
+ case PERSISTENT_RESERVE_OUT:
+ case MAINTENANCE_OUT:
+ req->cmd.mode = SCSI_XFER_TO_DEV;
+ break;
+ default:
+ if (req->cmd.xfer)
+ req->cmd.mode = SCSI_XFER_FROM_DEV;
+ else {
+ req->cmd.mode = SCSI_XFER_NONE;
+ }
+ break;
+ }
+}
+
+static uint64_t scsi_req_lba(SCSIRequest *req)
+{
+ uint8_t *buf = req->cmd.buf;
+ uint64_t lba;
+
+ switch (buf[0] >> 5) {
+ case 0:
+ lba = (uint64_t) buf[3] | ((uint64_t) buf[2] << 8) |
+ (((uint64_t) buf[1] & 0x1f) << 16);
+ break;
+ case 1:
+ case 2:
+ lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) |
+ ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24);
+ break;
+ case 4:
+ lba = (uint64_t) buf[9] | ((uint64_t) buf[8] << 8) |
+ ((uint64_t) buf[7] << 16) | ((uint64_t) buf[6] << 24) |
+ ((uint64_t) buf[5] << 32) | ((uint64_t) buf[4] << 40) |
+ ((uint64_t) buf[3] << 48) | ((uint64_t) buf[2] << 56);
+ break;
+ case 5:
+ lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) |
+ ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24);
+ break;
+ default:
+ lba = -1;
+
+ }
+ return lba;
+}
+
+int scsi_req_parse(SCSIRequest *req, uint8_t *buf)
+{
+ int rc;
+
+ if (req->dev->type == TYPE_TAPE) {
+ rc = scsi_req_stream_length(req, buf);
+ } else {
+ rc = scsi_req_length(req, buf);
+ }
+ if (rc != 0)
+ return rc;
+
+ memcpy(req->cmd.buf, buf, req->cmd.len);
+ scsi_req_xfer_mode(req);
+ req->cmd.lba = scsi_req_lba(req);
+ return 0;
+}
+
+static const char *scsi_command_name(uint8_t cmd)
+{
+ static const char *names[] = {
+ [ TEST_UNIT_READY ] = "TEST_UNIT_READY",
+ [ REZERO_UNIT ] = "REZERO_UNIT",
+ /* REWIND and REZERO_UNIT use the same operation code */
+ [ REQUEST_SENSE ] = "REQUEST_SENSE",
+ [ FORMAT_UNIT ] = "FORMAT_UNIT",
+ [ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS",
+ [ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS",
+ [ READ_6 ] = "READ_6",
+ [ WRITE_6 ] = "WRITE_6",
+ [ SEEK_6 ] = "SEEK_6",
+ [ READ_REVERSE ] = "READ_REVERSE",
+ [ WRITE_FILEMARKS ] = "WRITE_FILEMARKS",
+ [ SPACE ] = "SPACE",
+ [ INQUIRY ] = "INQUIRY",
+ [ RECOVER_BUFFERED_DATA ] = "RECOVER_BUFFERED_DATA",
+ [ MAINTENANCE_IN ] = "MAINTENANCE_IN",
+ [ MAINTENANCE_OUT ] = "MAINTENANCE_OUT",
+ [ MODE_SELECT ] = "MODE_SELECT",
+ [ RESERVE ] = "RESERVE",
+ [ RELEASE ] = "RELEASE",
+ [ COPY ] = "COPY",
+ [ ERASE ] = "ERASE",
+ [ MODE_SENSE ] = "MODE_SENSE",
+ [ START_STOP ] = "START_STOP",
+ [ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC",
+ [ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC",
+ [ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL",
+
+ [ SET_WINDOW ] = "SET_WINDOW",
+ [ READ_CAPACITY ] = "READ_CAPACITY",
+ [ READ_10 ] = "READ_10",
+ [ WRITE_10 ] = "WRITE_10",
+ [ SEEK_10 ] = "SEEK_10",
+ [ WRITE_VERIFY ] = "WRITE_VERIFY",
+ [ VERIFY ] = "VERIFY",
+ [ SEARCH_HIGH ] = "SEARCH_HIGH",
+ [ SEARCH_EQUAL ] = "SEARCH_EQUAL",
+ [ SEARCH_LOW ] = "SEARCH_LOW",
+ [ SET_LIMITS ] = "SET_LIMITS",
+ [ PRE_FETCH ] = "PRE_FETCH",
+ /* READ_POSITION and PRE_FETCH use the same operation code */
+ [ SYNCHRONIZE_CACHE ] = "SYNCHRONIZE_CACHE",
+ [ LOCK_UNLOCK_CACHE ] = "LOCK_UNLOCK_CACHE",
+ [ READ_DEFECT_DATA ] = "READ_DEFECT_DATA",
+ [ MEDIUM_SCAN ] = "MEDIUM_SCAN",
+ [ COMPARE ] = "COMPARE",
+ [ COPY_VERIFY ] = "COPY_VERIFY",
+ [ WRITE_BUFFER ] = "WRITE_BUFFER",
+ [ READ_BUFFER ] = "READ_BUFFER",
+ [ UPDATE_BLOCK ] = "UPDATE_BLOCK",
+ [ READ_LONG ] = "READ_LONG",
+ [ WRITE_LONG ] = "WRITE_LONG",
+ [ CHANGE_DEFINITION ] = "CHANGE_DEFINITION",
+ [ WRITE_SAME ] = "WRITE_SAME",
+ [ READ_TOC ] = "READ_TOC",
+ [ LOG_SELECT ] = "LOG_SELECT",
+ [ LOG_SENSE ] = "LOG_SENSE",
+ [ MODE_SELECT_10 ] = "MODE_SELECT_10",
+ [ RESERVE_10 ] = "RESERVE_10",
+ [ RELEASE_10 ] = "RELEASE_10",
+ [ MODE_SENSE_10 ] = "MODE_SENSE_10",
+ [ PERSISTENT_RESERVE_IN ] = "PERSISTENT_RESERVE_IN",
+ [ PERSISTENT_RESERVE_OUT ] = "PERSISTENT_RESERVE_OUT",
+ [ MOVE_MEDIUM ] = "MOVE_MEDIUM",
+ [ READ_12 ] = "READ_12",
+ [ WRITE_12 ] = "WRITE_12",
+ [ WRITE_VERIFY_12 ] = "WRITE_VERIFY_12",
+ [ SEARCH_HIGH_12 ] = "SEARCH_HIGH_12",
+ [ SEARCH_EQUAL_12 ] = "SEARCH_EQUAL_12",
+ [ SEARCH_LOW_12 ] = "SEARCH_LOW_12",
+ [ READ_ELEMENT_STATUS ] = "READ_ELEMENT_STATUS",
+ [ SEND_VOLUME_TAG ] = "SEND_VOLUME_TAG",
+ [ WRITE_LONG_2 ] = "WRITE_LONG_2",
+
+ [ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT",
+ [ GET_CONFIGURATION ] = "GET_CONFIGURATION",
+ [ READ_16 ] = "READ_16",
+ [ WRITE_16 ] = "WRITE_16",
+ [ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16",
+ [ SERVICE_ACTION_IN ] = "SERVICE_ACTION_IN",
+ [ REPORT_LUNS ] = "REPORT_LUNS",
+ [ LOAD_UNLOAD ] = "LOAD_UNLOAD",
+ [ SET_CD_SPEED ] = "SET_CD_SPEED",
+ [ BLANK ] = "BLANK",
+ };
+
+ if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL)
+ return "*UNKNOWN*";
+ return names[cmd];
+}
+
+void scsi_req_print(SCSIRequest *req)
+{
+ FILE *fp = stderr;
+ int i;
+
+ fprintf(fp, "[%s id=%d] %s",
+ req->dev->qdev.parent_bus->name,
+ req->dev->id,
+ scsi_command_name(req->cmd.buf[0]));
+ for (i = 1; i < req->cmd.len; i++) {
+ fprintf(fp, " 0x%02x", req->cmd.buf[i]);
+ }
+ switch (req->cmd.mode) {
+ case SCSI_XFER_NONE:
+ fprintf(fp, " - none\n");
+ break;
+ case SCSI_XFER_FROM_DEV:
+ fprintf(fp, " - from-dev len=%zd\n", req->cmd.xfer);
+ break;
+ case SCSI_XFER_TO_DEV:
+ fprintf(fp, " - to-dev len=%zd\n", req->cmd.xfer);
+ break;
+ default:
+ fprintf(fp, " - Oops\n");
+ break;
+ }
+}
+
+void scsi_req_complete(SCSIRequest *req)
+{
+ assert(req->status != -1);
+ scsi_req_dequeue(req);
+ req->bus->complete(req->bus, SCSI_REASON_DONE,
+ req->tag,
+ req->status);
}