diff options
Diffstat (limited to 'hw/scsi/scsi-bus.c')
-rw-r--r-- | hw/scsi/scsi-bus.c | 262 |
1 files changed, 168 insertions, 94 deletions
diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c index 3284a5d1fb..b901e701f0 100644 --- a/hw/scsi/scsi-bus.c +++ b/hw/scsi/scsi-bus.c @@ -22,34 +22,67 @@ static void scsi_req_dequeue(SCSIRequest *req); static uint8_t *scsi_target_alloc_buf(SCSIRequest *req, size_t len); static void scsi_target_free_buf(SCSIRequest *req); -static Property scsi_props[] = { - DEFINE_PROP_UINT32("channel", SCSIDevice, channel, 0), - DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1), - DEFINE_PROP_UINT32("lun", SCSIDevice, lun, -1), - DEFINE_PROP_END_OF_LIST(), -}; +static int next_scsi_bus; -static void scsi_bus_class_init(ObjectClass *klass, void *data) +static SCSIDevice *do_scsi_device_find(SCSIBus *bus, + int channel, int id, int lun, + bool include_unrealized) { - BusClass *k = BUS_CLASS(klass); - HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); + BusChild *kid; + SCSIDevice *retval = NULL; - k->get_dev_path = scsibus_get_dev_path; - k->get_fw_dev_path = scsibus_get_fw_dev_path; - hc->unplug = qdev_simple_device_unplug_cb; + QTAILQ_FOREACH_RCU(kid, &bus->qbus.children, sibling) { + DeviceState *qdev = kid->child; + SCSIDevice *dev = SCSI_DEVICE(qdev); + + if (dev->channel == channel && dev->id == id) { + if (dev->lun == lun) { + retval = dev; + break; + } + + /* + * If we don't find exact match (channel/bus/lun), + * we will return the first device which matches channel/bus + */ + + if (!retval) { + retval = dev; + } + } + } + + /* + * This function might run on the IO thread and we might race against + * main thread hot-plugging the device. + * We assume that as soon as .realized is set to true we can let + * the user access the device. + */ + + if (retval && !include_unrealized && + !qatomic_load_acquire(&retval->qdev.realized)) { + retval = NULL; + } + + return retval; } -static const TypeInfo scsi_bus_info = { - .name = TYPE_SCSI_BUS, - .parent = TYPE_BUS, - .instance_size = sizeof(SCSIBus), - .class_init = scsi_bus_class_init, - .interfaces = (InterfaceInfo[]) { - { TYPE_HOTPLUG_HANDLER }, - { } +SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int id, int lun) +{ + RCU_READ_LOCK_GUARD(); + return do_scsi_device_find(bus, channel, id, lun, false); +} + +SCSIDevice *scsi_device_get(SCSIBus *bus, int channel, int id, int lun) +{ + SCSIDevice *d; + RCU_READ_LOCK_GUARD(); + d = do_scsi_device_find(bus, channel, id, lun, false); + if (d) { + object_ref(d); } -}; -static int next_scsi_bus; + return d; +} static void scsi_device_realize(SCSIDevice *s, Error **errp) { @@ -160,35 +193,71 @@ static void scsi_dma_restart_cb(void *opaque, int running, RunState state) } } -static void scsi_qdev_realize(DeviceState *qdev, Error **errp) +static bool scsi_bus_is_address_free(SCSIBus *bus, + int channel, int target, int lun, + SCSIDevice **p_dev) { - SCSIDevice *dev = SCSI_DEVICE(qdev); - SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); SCSIDevice *d; - Error *local_err = NULL; + + RCU_READ_LOCK_GUARD(); + d = do_scsi_device_find(bus, channel, target, lun, true); + if (d && d->lun == lun) { + if (p_dev) { + *p_dev = d; + } + return false; + } + if (p_dev) { + *p_dev = NULL; + } + return true; +} + +static bool scsi_bus_check_address(BusState *qbus, DeviceState *qdev, Error **errp) +{ + SCSIDevice *dev = SCSI_DEVICE(qdev); + SCSIBus *bus = SCSI_BUS(qbus); if (dev->channel > bus->info->max_channel) { error_setg(errp, "bad scsi channel id: %d", dev->channel); - return; + return false; } if (dev->id != -1 && dev->id > bus->info->max_target) { error_setg(errp, "bad scsi device id: %d", dev->id); - return; + return false; } if (dev->lun != -1 && dev->lun > bus->info->max_lun) { error_setg(errp, "bad scsi device lun: %d", dev->lun); - return; + return false; + } + + if (dev->id != -1 && dev->lun != -1) { + SCSIDevice *d; + if (!scsi_bus_is_address_free(bus, dev->channel, dev->id, dev->lun, &d)) { + error_setg(errp, "lun already used by '%s'", d->qdev.id); + return false; + } } + return true; +} + +static void scsi_qdev_realize(DeviceState *qdev, Error **errp) +{ + SCSIDevice *dev = SCSI_DEVICE(qdev); + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); + bool is_free; + Error *local_err = NULL; + if (dev->id == -1) { int id = -1; if (dev->lun == -1) { dev->lun = 0; } do { - d = scsi_device_find(bus, dev->channel, ++id, dev->lun); - } while (d && d->lun == dev->lun && id < bus->info->max_target); - if (d && d->lun == dev->lun) { + is_free = scsi_bus_is_address_free(bus, dev->channel, ++id, dev->lun, NULL); + } while (!is_free && id < bus->info->max_target); + if (!is_free) { error_setg(errp, "no free target"); return; } @@ -196,20 +265,13 @@ static void scsi_qdev_realize(DeviceState *qdev, Error **errp) } else if (dev->lun == -1) { int lun = -1; do { - d = scsi_device_find(bus, dev->channel, dev->id, ++lun); - } while (d && d->lun == lun && lun < bus->info->max_lun); - if (d && d->lun == lun) { + is_free = scsi_bus_is_address_free(bus, dev->channel, dev->id, ++lun, NULL); + } while (!is_free && lun < bus->info->max_lun); + if (!is_free) { error_setg(errp, "no free lun"); return; } dev->lun = lun; - } else { - d = scsi_device_find(bus, dev->channel, dev->id, dev->lun); - assert(d); - if (d->lun == dev->lun && dev != d) { - error_setg(errp, "lun already used by '%s'", d->qdev.id); - return; - } } QTAILQ_INIT(&dev->requests); @@ -376,19 +438,23 @@ struct SCSITargetReq { static void store_lun(uint8_t *outbuf, int lun) { if (lun < 256) { + /* Simple logical unit addressing method*/ + outbuf[0] = 0; outbuf[1] = lun; - return; + } else { + /* Flat space addressing method */ + outbuf[0] = 0x40 | (lun >> 8); + outbuf[1] = (lun & 255); } - outbuf[1] = (lun & 255); - outbuf[0] = (lun >> 8) | 0x40; } static bool scsi_target_emulate_report_luns(SCSITargetReq *r) { BusChild *kid; - int i, len, n; int channel, id; - bool found_lun0; + uint8_t tmp[8] = {0}; + int len = 0; + GByteArray *buf; if (r->req.cmd.xfer < 16) { return false; @@ -396,42 +462,40 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r) if (r->req.cmd.buf[2] > 2) { return false; } + + /* reserve space for 63 LUNs*/ + buf = g_byte_array_sized_new(512); + channel = r->req.dev->channel; id = r->req.dev->id; - found_lun0 = false; - n = 0; - QTAILQ_FOREACH(kid, &r->req.bus->qbus.children, sibling) { - DeviceState *qdev = kid->child; - SCSIDevice *dev = SCSI_DEVICE(qdev); - if (dev->channel == channel && dev->id == id) { - if (dev->lun == 0) { - found_lun0 = true; - } - n += 8; - } - } - if (!found_lun0) { - n += 8; - } + /* add size (will be updated later to correct value */ + g_byte_array_append(buf, tmp, 8); + len += 8; - scsi_target_alloc_buf(&r->req, n + 8); + /* add LUN0 */ + g_byte_array_append(buf, tmp, 8); + len += 8; - len = MIN(n + 8, r->req.cmd.xfer & ~7); - memset(r->buf, 0, len); - stl_be_p(&r->buf[0], n); - i = found_lun0 ? 8 : 16; - QTAILQ_FOREACH(kid, &r->req.bus->qbus.children, sibling) { - DeviceState *qdev = kid->child; - SCSIDevice *dev = SCSI_DEVICE(qdev); + WITH_RCU_READ_LOCK_GUARD() { + QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) { + DeviceState *qdev = kid->child; + SCSIDevice *dev = SCSI_DEVICE(qdev); - if (dev->channel == channel && dev->id == id) { - store_lun(&r->buf[i], dev->lun); - i += 8; + if (dev->channel == channel && dev->id == id && dev->lun != 0) { + store_lun(tmp, dev->lun); + g_byte_array_append(buf, tmp, 8); + len += 8; + } } } - assert(i == n + 8); - r->len = len; + + r->buf_len = len; + r->buf = g_byte_array_free(buf, FALSE); + r->len = MIN(len, r->req.cmd.xfer & ~7); + + /* store the LUN list length */ + stl_be_p(&r->buf[0], len - 8); return true; } @@ -1567,25 +1631,6 @@ static char *scsibus_get_fw_dev_path(DeviceState *dev) qdev_fw_name(dev), d->id, d->lun); } -SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int id, int lun) -{ - BusChild *kid; - SCSIDevice *target_dev = NULL; - - QTAILQ_FOREACH_REVERSE(kid, &bus->qbus.children, sibling) { - DeviceState *qdev = kid->child; - SCSIDevice *dev = SCSI_DEVICE(qdev); - - if (dev->channel == channel && dev->id == id) { - if (dev->lun == lun) { - return dev; - } - target_dev = dev; - } - } - return target_dev; -} - /* SCSI request list. For simplicity, pv points to the whole device */ static int put_scsi_requests(QEMUFile *f, void *pv, size_t size, @@ -1709,6 +1754,13 @@ const VMStateDescription vmstate_scsi_device = { } }; +static Property scsi_props[] = { + DEFINE_PROP_UINT32("channel", SCSIDevice, channel, 0), + DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1), + DEFINE_PROP_UINT32("lun", SCSIDevice, lun, -1), + DEFINE_PROP_END_OF_LIST(), +}; + static void scsi_device_class_init(ObjectClass *klass, void *data) { DeviceClass *k = DEVICE_CLASS(klass); @@ -1739,6 +1791,28 @@ static const TypeInfo scsi_device_type_info = { .instance_init = scsi_dev_instance_init, }; +static void scsi_bus_class_init(ObjectClass *klass, void *data) +{ + BusClass *k = BUS_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); + + k->get_dev_path = scsibus_get_dev_path; + k->get_fw_dev_path = scsibus_get_fw_dev_path; + k->check_address = scsi_bus_check_address; + hc->unplug = qdev_simple_device_unplug_cb; +} + +static const TypeInfo scsi_bus_info = { + .name = TYPE_SCSI_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(SCSIBus), + .class_init = scsi_bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } +}; + static void scsi_register_types(void) { type_register_static(&scsi_bus_info); |