#include "hw.h"
-#include "sysemu.h"
+#include "qemu-error.h"
#include "scsi.h"
#include "scsi-defs.h"
-#include "block.h"
#include "qdev.h"
+#include "blockdev.h"
+#include "trace.h"
+
+static char *scsibus_get_fw_dev_path(DeviceState *dev);
static struct BusInfo scsi_bus_info = {
.name = "SCSI",
.size = sizeof(SCSIBus),
+ .get_fw_dev_path = scsibus_get_fw_dev_path,
.props = (Property[]) {
DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1),
DEFINE_PROP_END_OF_LIST(),
/* Create a scsi bus, and attach devices to it. */
void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev,
- scsi_completionfn complete)
+ const SCSIBusOps *ops)
{
qbus_create_inplace(&bus->qbus, &scsi_bus_info, host, NULL);
bus->busnr = next_scsi_bus++;
bus->tcq = tcq;
bus->ndev = ndev;
- bus->complete = complete;
+ bus->ops = ops;
bus->qbus.allow_hotplug = 1;
}
}
}
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;
}
}
/* handle legacy '-drive if=scsi,...' cmd line args */
-/* FIXME callers should check for failure, but don't */
-SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, DriveInfo *dinfo, int unit)
+SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv,
+ int unit, bool removable)
{
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);
+ if (qdev_prop_exists(dev, "removable")) {
+ qdev_prop_set_bit(dev, "removable", removable);
+ }
+ 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;
- for (unit = 0; unit < MAX_SCSI_DEVS; unit++) {
+ loc_push_none(&loc);
+ for (unit = 0; unit < bus->ndev; 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, false)) {
+ res = -1;
+ break;
+ }
}
-}
-
-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;
+ loc_pop(&loc);
+ return res;
}
SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun)
SCSIRequest *req;
req = qemu_mallocz(size);
+ req->refcount = 1;
req->bus = scsi_bus_from_device(d);
req->dev = d;
req->tag = tag;
req->lun = lun;
req->status = -1;
- QTAILQ_INSERT_TAIL(&d->requests, req, next);
+ trace_scsi_req_alloc(req->dev->id, req->lun, req->tag);
return req;
}
-SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag)
+void scsi_req_enqueue(SCSIRequest *req)
{
- SCSIRequest *req;
-
- QTAILQ_FOREACH(req, &d->requests, next) {
- if (req->tag == tag) {
- return req;
- }
- }
- return NULL;
+ assert(!req->enqueued);
+ scsi_req_ref(req);
+ req->enqueued = true;
+ QTAILQ_INSERT_TAIL(&req->dev->requests, req, next);
}
-void scsi_req_free(SCSIRequest *req)
+void scsi_req_dequeue(SCSIRequest *req)
{
- QTAILQ_REMOVE(&req->dev->requests, req, next);
- qemu_free(req);
+ trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag);
+ if (req->enqueued) {
+ QTAILQ_REMOVE(&req->dev->requests, req, next);
+ req->enqueued = false;
+ scsi_req_unref(req);
+ }
}
static int scsi_req_length(SCSIRequest *req, uint8_t *cmd)
req->cmd.len = 12;
break;
default:
+ trace_scsi_req_parse_bad(req->dev->id, req->lun, req->tag, cmd[0]);
return -1;
}
case SEEK_6:
case WRITE_FILEMARKS:
case SPACE:
+ case RESERVE:
+ case RELEASE:
case ERASE:
case ALLOW_MEDIUM_REMOVAL:
case VERIFY:
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;
}
case WRITE_BUFFER:
case FORMAT_UNIT:
case REASSIGN_BLOCKS:
- case RESERVE:
case SEARCH_EQUAL:
case SEARCH_HIGH:
case SEARCH_LOW:
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:
memcpy(req->cmd.buf, buf, req->cmd.len);
scsi_req_xfer_mode(req);
req->cmd.lba = scsi_req_lba(req);
+ trace_scsi_req_parsed(req->dev->id, req->lun, req->tag, buf[0],
+ req->cmd.mode, req->cmd.xfer, req->cmd.lba);
return 0;
}
[ 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",
return names[cmd];
}
+SCSIRequest *scsi_req_ref(SCSIRequest *req)
+{
+ req->refcount++;
+ return req;
+}
+
+void scsi_req_unref(SCSIRequest *req)
+{
+ if (--req->refcount == 0) {
+ if (req->dev->info->free_req) {
+ req->dev->info->free_req(req);
+ }
+ qemu_free(req);
+ }
+}
+
+/* Called by the devices when data is ready for the HBA. The HBA should
+ start a DMA operation to read or fill the device's data buffer.
+ Once it completes, calling one of req->dev->info->read_data or
+ req->dev->info->write_data (depending on the direction of the
+ transfer) will restart I/O. */
+void scsi_req_data(SCSIRequest *req, int len)
+{
+ trace_scsi_req_data(req->dev->id, req->lun, req->tag, len);
+ req->bus->ops->complete(req, SCSI_REASON_DATA, len);
+}
+
void scsi_req_print(SCSIRequest *req)
{
FILE *fp = stderr;
void scsi_req_complete(SCSIRequest *req)
{
assert(req->status != -1);
- req->bus->complete(req->bus, SCSI_REASON_DONE,
- req->tag,
- req->status);
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->bus->ops->complete(req, SCSI_REASON_DONE, req->status);
+ scsi_req_unref(req);
+}
+
+void scsi_req_cancel(SCSIRequest *req)
+{
+ if (req->dev && req->dev->info->cancel_io) {
+ req->dev->info->cancel_io(req);
+ }
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ if (req->bus->ops->cancel) {
+ req->bus->ops->cancel(req);
+ }
+ scsi_req_unref(req);
+}
+
+void scsi_req_abort(SCSIRequest *req, int status)
+{
+ req->status = status;
+ if (req->dev && req->dev->info->cancel_io) {
+ req->dev->info->cancel_io(req);
+ }
+ scsi_req_complete(req);
+}
+
+void scsi_device_purge_requests(SCSIDevice *sdev)
+{
+ SCSIRequest *req;
+
+ while (!QTAILQ_EMPTY(&sdev->requests)) {
+ req = QTAILQ_FIRST(&sdev->requests);
+ scsi_req_cancel(req);
+ }
+}
+
+static char *scsibus_get_fw_dev_path(DeviceState *dev)
+{
+ SCSIDevice *d = (SCSIDevice*)dev;
+ SCSIBus *bus = scsi_bus_from_device(d);
+ char path[100];
+ int i;
+
+ for (i = 0; i < bus->ndev; i++) {
+ if (bus->devs[i] == d) {
+ break;
+ }
+ }
+
+ assert(i != bus->ndev);
+
+ snprintf(path, sizeof(path), "%s@%x", qdev_fw_name(dev), i);
+
+ return strdup(path);
}