mirror of https://github.com/xemu-project/xemu.git
SCSI patches include bug fixes from Fam and Peter, improved error
reporting from Fam and a fix for DPRINTF bitrot. Memory patches try again to initialize name from the QOM name. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJT/zhXAAoJEBvWZb6bTYby/UMP/jyhrHAaCgPgrvM4bXzMXBoZ l4UQXSTAmhlpr/OUI9pgT/392IGNTkDZ3mJi0sqgG6p6egWUT6a4+lzJjlhExTJy K5GRkbgfj83nVI1Jr0uxs58dwM527IFc5RD2Fzz0QXJIMA+HDseLfSYfa3gxbdTU iU8fK4PG1usb8FMR+Rd7SzGgGbGhgs6KOar98izH9C+SsPtCIGEu86KW9EkCh2dZ t7RI9PAJZUA1Ci2GuFISAuxl08ZkfKo29fXfM0DsovbaQda2dph7j1y6sYqXYQBA jZW0BEedpC1PfbWEODU81PG5t4AH5AUNmrNIsG04NiDwcRzWQckA5/x6qHsdA/33 N/GGzqfmLknvIPecuzmwmgRBVMzf+K0xXd+StSFJWR9dwP09Y0UfhdkuBsiqdd1a H+xtsDyBl9pR9VgqtetIq8uQf3fpiHSRUnh++YYU8V/uK2C8ZyTmYYBJNwk2FK6l 2PBTD1Jsl0WYCZBScM0IK+BnDNDwrygdfAa2CF1KcfNHQSiHvjHIQwsVo6Lwev7i 1eE6/0zQ7Yumi3LOSyUx3v6JwdKH1zsN3uIjlrg4SxpmgzK/vhwrtuJti4W7HpLd eHLHfszCWAmNO6zx0/bH44lPZNDBBFZeaZ+NVjW0nv7y3pAeLP3qyuY3pUsr+Suh 0xRPwhfmoSz9CZ+5mAIX =iLTP -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/bonzini/tags/for-upstream' into staging SCSI patches include bug fixes from Fam and Peter, improved error reporting from Fam and a fix for DPRINTF bitrot. Memory patches try again to initialize name from the QOM name. # gpg: Signature made Thu 28 Aug 2014 15:10:31 BST using RSA key ID 9B4D86F2 # gpg: Good signature from "Paolo Bonzini <pbonzini@redhat.com>" # gpg: aka "Paolo Bonzini <bonzini@gnu.org>" * remotes/bonzini/tags/for-upstream: memory: Lazy init name from QOM name as needed xen: hvm: Abstract away memory region name ref xen-hvm: Constify string virtio-scsi: Report error if num_queues is 0 or too large scsi-generic: remove superfluous DPRINTF avoid to break compiling block/iscsi: fix memory corruption on iscsi resize scsi-bus: Convert DeviceClass init to realize block: Pass errp in blkconf_geometry Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
a6aebb38ba
|
@ -22,8 +22,9 @@ void blkconf_serial(BlockConf *conf, char **serial)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int blkconf_geometry(BlockConf *conf, int *ptrans,
|
void blkconf_geometry(BlockConf *conf, int *ptrans,
|
||||||
unsigned cyls_max, unsigned heads_max, unsigned secs_max)
|
unsigned cyls_max, unsigned heads_max, unsigned secs_max,
|
||||||
|
Error **errp)
|
||||||
{
|
{
|
||||||
DriveInfo *dinfo;
|
DriveInfo *dinfo;
|
||||||
|
|
||||||
|
@ -46,17 +47,16 @@ int blkconf_geometry(BlockConf *conf, int *ptrans,
|
||||||
}
|
}
|
||||||
if (conf->cyls || conf->heads || conf->secs) {
|
if (conf->cyls || conf->heads || conf->secs) {
|
||||||
if (conf->cyls < 1 || conf->cyls > cyls_max) {
|
if (conf->cyls < 1 || conf->cyls > cyls_max) {
|
||||||
error_report("cyls must be between 1 and %u", cyls_max);
|
error_setg(errp, "cyls must be between 1 and %u", cyls_max);
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
if (conf->heads < 1 || conf->heads > heads_max) {
|
if (conf->heads < 1 || conf->heads > heads_max) {
|
||||||
error_report("heads must be between 1 and %u", heads_max);
|
error_setg(errp, "heads must be between 1 and %u", heads_max);
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
if (conf->secs < 1 || conf->secs > secs_max) {
|
if (conf->secs < 1 || conf->secs > secs_max) {
|
||||||
error_report("secs must be between 1 and %u", secs_max);
|
error_setg(errp, "secs must be between 1 and %u", secs_max);
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -729,9 +729,7 @@ static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
||||||
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
||||||
VirtIOBlock *s = VIRTIO_BLK(dev);
|
VirtIOBlock *s = VIRTIO_BLK(dev);
|
||||||
VirtIOBlkConf *blk = &(s->blk);
|
VirtIOBlkConf *blk = &(s->blk);
|
||||||
#ifdef CONFIG_VIRTIO_BLK_DATA_PLANE
|
|
||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
#endif
|
|
||||||
static int virtio_blk_id;
|
static int virtio_blk_id;
|
||||||
|
|
||||||
if (!blk->conf.bs) {
|
if (!blk->conf.bs) {
|
||||||
|
@ -745,8 +743,9 @@ static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
|
||||||
|
|
||||||
blkconf_serial(&blk->conf, &blk->serial);
|
blkconf_serial(&blk->conf, &blk->serial);
|
||||||
s->original_wce = bdrv_enable_write_cache(blk->conf.bs);
|
s->original_wce = bdrv_enable_write_cache(blk->conf.bs);
|
||||||
if (blkconf_geometry(&blk->conf, NULL, 65535, 255, 255) < 0) {
|
blkconf_geometry(&blk->conf, NULL, 65535, 255, 255, &err);
|
||||||
error_setg(errp, "Error setting geometry");
|
if (err) {
|
||||||
|
error_propagate(errp, err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind)
|
||||||
{
|
{
|
||||||
IDEBus *bus = DO_UPCAST(IDEBus, qbus, dev->qdev.parent_bus);
|
IDEBus *bus = DO_UPCAST(IDEBus, qbus, dev->qdev.parent_bus);
|
||||||
IDEState *s = bus->ifs + dev->unit;
|
IDEState *s = bus->ifs + dev->unit;
|
||||||
|
Error *err = NULL;
|
||||||
|
|
||||||
if (dev->conf.discard_granularity == -1) {
|
if (dev->conf.discard_granularity == -1) {
|
||||||
dev->conf.discard_granularity = 512;
|
dev->conf.discard_granularity = 512;
|
||||||
|
@ -161,10 +162,14 @@ static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
blkconf_serial(&dev->conf, &dev->serial);
|
blkconf_serial(&dev->conf, &dev->serial);
|
||||||
if (kind != IDE_CD
|
if (kind != IDE_CD) {
|
||||||
&& blkconf_geometry(&dev->conf, &dev->chs_trans, 65536, 16, 255) < 0) {
|
blkconf_geometry(&dev->conf, &dev->chs_trans, 65536, 16, 255, &err);
|
||||||
|
if (err) {
|
||||||
|
error_report("%s", error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ide_init_drive(s, dev->conf.bs, kind,
|
if (ide_init_drive(s, dev->conf.bs, kind,
|
||||||
dev->version, dev->serial, dev->model, dev->wwn,
|
dev->version, dev->serial, dev->model, dev->wwn,
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "hw/pci/pci.h"
|
#include "hw/pci/pci.h"
|
||||||
#include "hw/scsi/scsi.h"
|
#include "hw/scsi/scsi.h"
|
||||||
#include "sysemu/dma.h"
|
#include "sysemu/dma.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
|
|
||||||
//#define DEBUG_LSI
|
//#define DEBUG_LSI
|
||||||
//#define DEBUG_LSI_REG
|
//#define DEBUG_LSI_REG
|
||||||
|
|
|
@ -36,20 +36,19 @@ static const TypeInfo scsi_bus_info = {
|
||||||
};
|
};
|
||||||
static int next_scsi_bus;
|
static int next_scsi_bus;
|
||||||
|
|
||||||
static int scsi_device_init(SCSIDevice *s)
|
static void scsi_device_realize(SCSIDevice *s, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
|
SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
|
||||||
if (sc->init) {
|
if (sc->realize) {
|
||||||
return sc->init(s);
|
sc->realize(s, errp);
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scsi_device_destroy(SCSIDevice *s)
|
static void scsi_device_unrealize(SCSIDevice *s, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
|
SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
|
||||||
if (sc->destroy) {
|
if (sc->unrealize) {
|
||||||
sc->destroy(s);
|
sc->unrealize(s, errp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,24 +142,24 @@ static void scsi_dma_restart_cb(void *opaque, int running, RunState state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_qdev_init(DeviceState *qdev)
|
static void scsi_qdev_realize(DeviceState *qdev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDevice *dev = SCSI_DEVICE(qdev);
|
SCSIDevice *dev = SCSI_DEVICE(qdev);
|
||||||
SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
|
SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
|
||||||
SCSIDevice *d;
|
SCSIDevice *d;
|
||||||
int rc = -1;
|
Error *local_err = NULL;
|
||||||
|
|
||||||
if (dev->channel > bus->info->max_channel) {
|
if (dev->channel > bus->info->max_channel) {
|
||||||
error_report("bad scsi channel id: %d", dev->channel);
|
error_setg(errp, "bad scsi channel id: %d", dev->channel);
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
if (dev->id != -1 && dev->id > bus->info->max_target) {
|
if (dev->id != -1 && dev->id > bus->info->max_target) {
|
||||||
error_report("bad scsi device id: %d", dev->id);
|
error_setg(errp, "bad scsi device id: %d", dev->id);
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
if (dev->lun != -1 && dev->lun > bus->info->max_lun) {
|
if (dev->lun != -1 && dev->lun > bus->info->max_lun) {
|
||||||
error_report("bad scsi device lun: %d", dev->lun);
|
error_setg(errp, "bad scsi device lun: %d", dev->lun);
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev->id == -1) {
|
if (dev->id == -1) {
|
||||||
|
@ -172,8 +171,8 @@ static int scsi_qdev_init(DeviceState *qdev)
|
||||||
d = scsi_device_find(bus, dev->channel, ++id, dev->lun);
|
d = scsi_device_find(bus, dev->channel, ++id, dev->lun);
|
||||||
} while (d && d->lun == dev->lun && id < bus->info->max_target);
|
} while (d && d->lun == dev->lun && id < bus->info->max_target);
|
||||||
if (d && d->lun == dev->lun) {
|
if (d && d->lun == dev->lun) {
|
||||||
error_report("no free target");
|
error_setg(errp, "no free target");
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
dev->id = id;
|
dev->id = id;
|
||||||
} else if (dev->lun == -1) {
|
} else if (dev->lun == -1) {
|
||||||
|
@ -182,43 +181,41 @@ static int scsi_qdev_init(DeviceState *qdev)
|
||||||
d = scsi_device_find(bus, dev->channel, dev->id, ++lun);
|
d = scsi_device_find(bus, dev->channel, dev->id, ++lun);
|
||||||
} while (d && d->lun == lun && lun < bus->info->max_lun);
|
} while (d && d->lun == lun && lun < bus->info->max_lun);
|
||||||
if (d && d->lun == lun) {
|
if (d && d->lun == lun) {
|
||||||
error_report("no free lun");
|
error_setg(errp, "no free lun");
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
dev->lun = lun;
|
dev->lun = lun;
|
||||||
} else {
|
} else {
|
||||||
d = scsi_device_find(bus, dev->channel, dev->id, dev->lun);
|
d = scsi_device_find(bus, dev->channel, dev->id, dev->lun);
|
||||||
assert(d);
|
assert(d);
|
||||||
if (d->lun == dev->lun && dev != d) {
|
if (d->lun == dev->lun && dev != d) {
|
||||||
error_report("lun already used by '%s'", d->qdev.id);
|
error_setg(errp, "lun already used by '%s'", d->qdev.id);
|
||||||
goto err;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QTAILQ_INIT(&dev->requests);
|
QTAILQ_INIT(&dev->requests);
|
||||||
rc = scsi_device_init(dev);
|
scsi_device_realize(dev, &local_err);
|
||||||
if (rc == 0) {
|
if (local_err) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
dev->vmsentry = qemu_add_vm_change_state_handler(scsi_dma_restart_cb,
|
dev->vmsentry = qemu_add_vm_change_state_handler(scsi_dma_restart_cb,
|
||||||
dev);
|
dev);
|
||||||
}
|
|
||||||
|
|
||||||
if (bus->info->hotplug) {
|
if (bus->info->hotplug) {
|
||||||
bus->info->hotplug(bus, dev);
|
bus->info->hotplug(bus, dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
err:
|
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_qdev_exit(DeviceState *qdev)
|
static void scsi_qdev_unrealize(DeviceState *qdev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDevice *dev = SCSI_DEVICE(qdev);
|
SCSIDevice *dev = SCSI_DEVICE(qdev);
|
||||||
|
|
||||||
if (dev->vmsentry) {
|
if (dev->vmsentry) {
|
||||||
qemu_del_vm_change_state_handler(dev->vmsentry);
|
qemu_del_vm_change_state_handler(dev->vmsentry);
|
||||||
}
|
}
|
||||||
scsi_device_destroy(dev);
|
scsi_device_unrealize(dev, errp);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle legacy '-drive if=scsi,...' cmd line args */
|
/* handle legacy '-drive if=scsi,...' cmd line args */
|
||||||
|
@ -273,6 +270,7 @@ void scsi_bus_legacy_handle_cmdline(SCSIBus *bus, Error **errp)
|
||||||
scsi_bus_legacy_add_drive(bus, dinfo->bdrv, unit, false, -1, NULL,
|
scsi_bus_legacy_add_drive(bus, dinfo->bdrv, unit, false, -1, NULL,
|
||||||
&err);
|
&err);
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
|
error_report("%s", error_get_pretty(err));
|
||||||
error_propagate(errp, err);
|
error_propagate(errp, err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1993,9 +1991,9 @@ static void scsi_device_class_init(ObjectClass *klass, void *data)
|
||||||
DeviceClass *k = DEVICE_CLASS(klass);
|
DeviceClass *k = DEVICE_CLASS(klass);
|
||||||
set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
|
set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
|
||||||
k->bus_type = TYPE_SCSI_BUS;
|
k->bus_type = TYPE_SCSI_BUS;
|
||||||
k->init = scsi_qdev_init;
|
k->realize = scsi_qdev_realize;
|
||||||
k->unplug = scsi_qdev_unplug;
|
k->unplug = scsi_qdev_unplug;
|
||||||
k->exit = scsi_qdev_exit;
|
k->unrealize = scsi_qdev_unrealize;
|
||||||
k->props = scsi_props;
|
k->props = scsi_props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2151,7 +2151,7 @@ static void scsi_disk_reset(DeviceState *dev)
|
||||||
s->tray_open = 0;
|
s->tray_open = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scsi_destroy(SCSIDevice *dev)
|
static void scsi_unrealize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
||||||
|
|
||||||
|
@ -2234,25 +2234,29 @@ static void scsi_disk_unit_attention_reported(SCSIDevice *dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_initfn(SCSIDevice *dev)
|
static void scsi_realize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
||||||
|
Error *err = NULL;
|
||||||
|
|
||||||
if (!s->qdev.conf.bs) {
|
if (!s->qdev.conf.bs) {
|
||||||
error_report("drive property not set");
|
error_setg(errp, "drive property not set");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
|
if (!(s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
|
||||||
!bdrv_is_inserted(s->qdev.conf.bs)) {
|
!bdrv_is_inserted(s->qdev.conf.bs)) {
|
||||||
error_report("Device needs media, but drive is empty");
|
error_setg(errp, "Device needs media, but drive is empty");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
blkconf_serial(&s->qdev.conf, &s->serial);
|
blkconf_serial(&s->qdev.conf, &s->serial);
|
||||||
if (dev->type == TYPE_DISK
|
if (dev->type == TYPE_DISK) {
|
||||||
&& blkconf_geometry(&dev->conf, NULL, 65535, 255, 255) < 0) {
|
blkconf_geometry(&dev->conf, NULL, 65535, 255, 255, &err);
|
||||||
return -1;
|
if (err) {
|
||||||
|
error_propagate(errp, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->qdev.conf.discard_granularity == -1) {
|
if (s->qdev.conf.discard_granularity == -1) {
|
||||||
|
@ -2268,8 +2272,8 @@ static int scsi_initfn(SCSIDevice *dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_is_sg(s->qdev.conf.bs)) {
|
if (bdrv_is_sg(s->qdev.conf.bs)) {
|
||||||
error_report("unwanted /dev/sg*");
|
error_setg(errp, "unwanted /dev/sg*");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
|
if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
|
||||||
|
@ -2282,10 +2286,9 @@ static int scsi_initfn(SCSIDevice *dev)
|
||||||
|
|
||||||
bdrv_iostatus_enable(s->qdev.conf.bs);
|
bdrv_iostatus_enable(s->qdev.conf.bs);
|
||||||
add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, NULL);
|
add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, NULL);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_hd_initfn(SCSIDevice *dev)
|
static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
||||||
s->qdev.blocksize = s->qdev.conf.logical_block_size;
|
s->qdev.blocksize = s->qdev.conf.logical_block_size;
|
||||||
|
@ -2293,10 +2296,10 @@ static int scsi_hd_initfn(SCSIDevice *dev)
|
||||||
if (!s->product) {
|
if (!s->product) {
|
||||||
s->product = g_strdup("QEMU HARDDISK");
|
s->product = g_strdup("QEMU HARDDISK");
|
||||||
}
|
}
|
||||||
return scsi_initfn(&s->qdev);
|
scsi_realize(&s->qdev, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_cd_initfn(SCSIDevice *dev)
|
static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
||||||
s->qdev.blocksize = 2048;
|
s->qdev.blocksize = 2048;
|
||||||
|
@ -2305,22 +2308,26 @@ static int scsi_cd_initfn(SCSIDevice *dev)
|
||||||
if (!s->product) {
|
if (!s->product) {
|
||||||
s->product = g_strdup("QEMU CD-ROM");
|
s->product = g_strdup("QEMU CD-ROM");
|
||||||
}
|
}
|
||||||
return scsi_initfn(&s->qdev);
|
scsi_realize(&s->qdev, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_disk_initfn(SCSIDevice *dev)
|
static void scsi_disk_realize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
DriveInfo *dinfo;
|
DriveInfo *dinfo;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
|
||||||
if (!dev->conf.bs) {
|
if (!dev->conf.bs) {
|
||||||
return scsi_initfn(dev); /* ... and die there */
|
scsi_realize(dev, &local_err);
|
||||||
|
assert(local_err);
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dinfo = drive_get_by_blockdev(dev->conf.bs);
|
dinfo = drive_get_by_blockdev(dev->conf.bs);
|
||||||
if (dinfo->media_cd) {
|
if (dinfo->media_cd) {
|
||||||
return scsi_cd_initfn(dev);
|
scsi_cd_realize(dev, errp);
|
||||||
} else {
|
} else {
|
||||||
return scsi_hd_initfn(dev);
|
scsi_hd_realize(dev, errp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2452,35 +2459,35 @@ static int get_device_type(SCSIDiskState *s)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_block_initfn(SCSIDevice *dev)
|
static void scsi_block_realize(SCSIDevice *dev, Error **errp)
|
||||||
{
|
{
|
||||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
||||||
int sg_version;
|
int sg_version;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if (!s->qdev.conf.bs) {
|
if (!s->qdev.conf.bs) {
|
||||||
error_report("drive property not set");
|
error_setg(errp, "drive property not set");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check we are using a driver managing SG_IO (version 3 and after) */
|
/* check we are using a driver managing SG_IO (version 3 and after) */
|
||||||
rc = bdrv_ioctl(s->qdev.conf.bs, SG_GET_VERSION_NUM, &sg_version);
|
rc = bdrv_ioctl(s->qdev.conf.bs, SG_GET_VERSION_NUM, &sg_version);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
error_report("cannot get SG_IO version number: %s. "
|
error_setg(errp, "cannot get SG_IO version number: %s. "
|
||||||
"Is this a SCSI device?",
|
"Is this a SCSI device?",
|
||||||
strerror(-rc));
|
strerror(-rc));
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
if (sg_version < 30000) {
|
if (sg_version < 30000) {
|
||||||
error_report("scsi generic interface too old");
|
error_setg(errp, "scsi generic interface too old");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get device type from INQUIRY data */
|
/* get device type from INQUIRY data */
|
||||||
rc = get_device_type(s);
|
rc = get_device_type(s);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
error_report("INQUIRY failed");
|
error_setg(errp, "INQUIRY failed");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make a guess for the block size, we'll fix it when the guest sends.
|
/* Make a guess for the block size, we'll fix it when the guest sends.
|
||||||
|
@ -2498,7 +2505,7 @@ static int scsi_block_initfn(SCSIDevice *dev)
|
||||||
*/
|
*/
|
||||||
s->features |= (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS);
|
s->features |= (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS);
|
||||||
|
|
||||||
return scsi_initfn(&s->qdev);
|
scsi_realize(&s->qdev, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool scsi_block_is_passthrough(SCSIDiskState *s, uint8_t *buf)
|
static bool scsi_block_is_passthrough(SCSIDiskState *s, uint8_t *buf)
|
||||||
|
@ -2621,8 +2628,8 @@ static void scsi_hd_class_initfn(ObjectClass *klass, void *data)
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
||||||
|
|
||||||
sc->init = scsi_hd_initfn;
|
sc->realize = scsi_hd_realize;
|
||||||
sc->destroy = scsi_destroy;
|
sc->unrealize = scsi_unrealize;
|
||||||
sc->alloc_req = scsi_new_request;
|
sc->alloc_req = scsi_new_request;
|
||||||
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
||||||
dc->fw_name = "disk";
|
dc->fw_name = "disk";
|
||||||
|
@ -2652,8 +2659,8 @@ static void scsi_cd_class_initfn(ObjectClass *klass, void *data)
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
||||||
|
|
||||||
sc->init = scsi_cd_initfn;
|
sc->realize = scsi_cd_realize;
|
||||||
sc->destroy = scsi_destroy;
|
sc->unrealize = scsi_unrealize;
|
||||||
sc->alloc_req = scsi_new_request;
|
sc->alloc_req = scsi_new_request;
|
||||||
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
||||||
dc->fw_name = "disk";
|
dc->fw_name = "disk";
|
||||||
|
@ -2682,8 +2689,8 @@ static void scsi_block_class_initfn(ObjectClass *klass, void *data)
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
||||||
|
|
||||||
sc->init = scsi_block_initfn;
|
sc->realize = scsi_block_realize;
|
||||||
sc->destroy = scsi_destroy;
|
sc->unrealize = scsi_unrealize;
|
||||||
sc->alloc_req = scsi_block_new_request;
|
sc->alloc_req = scsi_block_new_request;
|
||||||
sc->parse_cdb = scsi_block_parse_cdb;
|
sc->parse_cdb = scsi_block_parse_cdb;
|
||||||
dc->fw_name = "disk";
|
dc->fw_name = "disk";
|
||||||
|
@ -2720,8 +2727,8 @@ static void scsi_disk_class_initfn(ObjectClass *klass, void *data)
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
||||||
|
|
||||||
sc->init = scsi_disk_initfn;
|
sc->realize = scsi_disk_realize;
|
||||||
sc->destroy = scsi_destroy;
|
sc->unrealize = scsi_unrealize;
|
||||||
sc->alloc_req = scsi_new_request;
|
sc->alloc_req = scsi_new_request;
|
||||||
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
sc->unit_attention_reported = scsi_disk_unit_attention_reported;
|
||||||
dc->fw_name = "disk";
|
dc->fw_name = "disk";
|
||||||
|
|
|
@ -303,9 +303,6 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *cmd)
|
||||||
SCSIDevice *s = r->req.dev;
|
SCSIDevice *s = r->req.dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF("Command: lun=%d tag=0x%x len %zd data=0x%02x", lun, tag,
|
|
||||||
r->req.cmd.xfer, cmd[0]);
|
|
||||||
|
|
||||||
#ifdef DEBUG_SCSI
|
#ifdef DEBUG_SCSI
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -386,49 +383,49 @@ static void scsi_generic_reset(DeviceState *dev)
|
||||||
scsi_device_purge_requests(s, SENSE_CODE(RESET));
|
scsi_device_purge_requests(s, SENSE_CODE(RESET));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scsi_destroy(SCSIDevice *s)
|
static void scsi_unrealize(SCSIDevice *s, Error **errp)
|
||||||
{
|
{
|
||||||
scsi_device_purge_requests(s, SENSE_CODE(NO_SENSE));
|
scsi_device_purge_requests(s, SENSE_CODE(NO_SENSE));
|
||||||
blockdev_mark_auto_del(s->conf.bs);
|
blockdev_mark_auto_del(s->conf.bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scsi_generic_initfn(SCSIDevice *s)
|
static void scsi_generic_realize(SCSIDevice *s, Error **errp)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
int sg_version;
|
int sg_version;
|
||||||
struct sg_scsi_id scsiid;
|
struct sg_scsi_id scsiid;
|
||||||
|
|
||||||
if (!s->conf.bs) {
|
if (!s->conf.bs) {
|
||||||
error_report("drive property not set");
|
error_setg(errp, "drive property not set");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_get_on_error(s->conf.bs, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
|
if (bdrv_get_on_error(s->conf.bs, 0) != BLOCKDEV_ON_ERROR_ENOSPC) {
|
||||||
error_report("Device doesn't support drive option werror");
|
error_setg(errp, "Device doesn't support drive option werror");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
if (bdrv_get_on_error(s->conf.bs, 1) != BLOCKDEV_ON_ERROR_REPORT) {
|
if (bdrv_get_on_error(s->conf.bs, 1) != BLOCKDEV_ON_ERROR_REPORT) {
|
||||||
error_report("Device doesn't support drive option rerror");
|
error_setg(errp, "Device doesn't support drive option rerror");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check we are using a driver managing SG_IO (version 3 and after */
|
/* check we are using a driver managing SG_IO (version 3 and after */
|
||||||
rc = bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version);
|
rc = bdrv_ioctl(s->conf.bs, SG_GET_VERSION_NUM, &sg_version);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
error_report("cannot get SG_IO version number: %s. "
|
error_setg(errp, "cannot get SG_IO version number: %s. "
|
||||||
"Is this a SCSI device?",
|
"Is this a SCSI device?",
|
||||||
strerror(-rc));
|
strerror(-rc));
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
if (sg_version < 30000) {
|
if (sg_version < 30000) {
|
||||||
error_report("scsi generic interface too old");
|
error_setg(errp, "scsi generic interface too old");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get LUN of the /dev/sg? */
|
/* get LUN of the /dev/sg? */
|
||||||
if (bdrv_ioctl(s->conf.bs, SG_GET_SCSI_ID, &scsiid)) {
|
if (bdrv_ioctl(s->conf.bs, SG_GET_SCSI_ID, &scsiid)) {
|
||||||
error_report("SG_GET_SCSI_ID ioctl failed");
|
error_setg(errp, "SG_GET_SCSI_ID ioctl failed");
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* define device state */
|
/* define device state */
|
||||||
|
@ -460,7 +457,6 @@ static int scsi_generic_initfn(SCSIDevice *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF("block size %d\n", s->blocksize);
|
DPRINTF("block size %d\n", s->blocksize);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCSIReqOps scsi_generic_req_ops = {
|
const SCSIReqOps scsi_generic_req_ops = {
|
||||||
|
@ -501,8 +497,8 @@ static void scsi_generic_class_initfn(ObjectClass *klass, void *data)
|
||||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
|
||||||
|
|
||||||
sc->init = scsi_generic_initfn;
|
sc->realize = scsi_generic_realize;
|
||||||
sc->destroy = scsi_destroy;
|
sc->unrealize = scsi_unrealize;
|
||||||
sc->alloc_req = scsi_new_request;
|
sc->alloc_req = scsi_new_request;
|
||||||
sc->parse_cdb = scsi_generic_parse_cdb;
|
sc->parse_cdb = scsi_generic_parse_cdb;
|
||||||
dc->fw_name = "disk";
|
dc->fw_name = "disk";
|
||||||
|
|
|
@ -699,6 +699,12 @@ void virtio_scsi_common_realize(DeviceState *dev, Error **errp,
|
||||||
virtio_init(vdev, "virtio-scsi", VIRTIO_ID_SCSI,
|
virtio_init(vdev, "virtio-scsi", VIRTIO_ID_SCSI,
|
||||||
sizeof(VirtIOSCSIConfig));
|
sizeof(VirtIOSCSIConfig));
|
||||||
|
|
||||||
|
if (s->conf.num_queues <= 0 || s->conf.num_queues > VIRTIO_PCI_QUEUE_MAX) {
|
||||||
|
error_setg(errp, "Invalid number of queues (= %" PRId32 "), "
|
||||||
|
"must be a positive integer less than %d.",
|
||||||
|
s->conf.num_queues, VIRTIO_PCI_QUEUE_MAX);
|
||||||
|
return;
|
||||||
|
}
|
||||||
s->cmd_vqs = g_malloc0(s->conf.num_queues * sizeof(VirtQueue *));
|
s->cmd_vqs = g_malloc0(s->conf.num_queues * sizeof(VirtQueue *));
|
||||||
s->sense_size = VIRTIO_SCSI_SENSE_SIZE;
|
s->sense_size = VIRTIO_SCSI_SENSE_SIZE;
|
||||||
s->cdb_size = VIRTIO_SCSI_CDB_SIZE;
|
s->cdb_size = VIRTIO_SCSI_CDB_SIZE;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#define HW_BLOCK_COMMON_H
|
#define HW_BLOCK_COMMON_H
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
|
||||||
/* Configuration */
|
/* Configuration */
|
||||||
|
|
||||||
|
@ -60,8 +61,9 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf)
|
||||||
/* Configuration helpers */
|
/* Configuration helpers */
|
||||||
|
|
||||||
void blkconf_serial(BlockConf *conf, char **serial);
|
void blkconf_serial(BlockConf *conf, char **serial);
|
||||||
int blkconf_geometry(BlockConf *conf, int *trans,
|
void blkconf_geometry(BlockConf *conf, int *trans,
|
||||||
unsigned cyls_max, unsigned heads_max, unsigned secs_max);
|
unsigned cyls_max, unsigned heads_max, unsigned secs_max,
|
||||||
|
Error **errp);
|
||||||
|
|
||||||
/* Hard disk geometry */
|
/* Hard disk geometry */
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,8 @@ struct SCSIRequest {
|
||||||
|
|
||||||
typedef struct SCSIDeviceClass {
|
typedef struct SCSIDeviceClass {
|
||||||
DeviceClass parent_class;
|
DeviceClass parent_class;
|
||||||
int (*init)(SCSIDevice *dev);
|
void (*realize)(SCSIDevice *dev, Error **errp);
|
||||||
void (*destroy)(SCSIDevice *s);
|
void (*unrealize)(SCSIDevice *dev, Error **errp);
|
||||||
int (*parse_cdb)(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
|
int (*parse_cdb)(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
|
||||||
void *hba_private);
|
void *hba_private);
|
||||||
SCSIRequest *(*alloc_req)(SCSIDevice *s, uint32_t tag, uint32_t lun,
|
SCSIRequest *(*alloc_req)(SCSIDevice *s, uint32_t tag, uint32_t lun,
|
||||||
|
|
4
memory.c
4
memory.c
|
@ -1309,6 +1309,10 @@ uint64_t memory_region_size(MemoryRegion *mr)
|
||||||
|
|
||||||
const char *memory_region_name(const MemoryRegion *mr)
|
const char *memory_region_name(const MemoryRegion *mr)
|
||||||
{
|
{
|
||||||
|
if (!mr->name) {
|
||||||
|
((MemoryRegion *)mr)->name =
|
||||||
|
object_get_canonical_path_component(OBJECT(mr));
|
||||||
|
}
|
||||||
return mr->name;
|
return mr->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,13 +149,11 @@ QEMU_PROG: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized
|
||||||
Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk
|
Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk
|
||||||
QEMU X.Y.Z monitor - type 'help' for more information
|
QEMU X.Y.Z monitor - type 'help' for more information
|
||||||
(qemu) QEMU_PROG: -device scsi-disk,drive=disk: Device needs media, but drive is empty
|
(qemu) QEMU_PROG: -device scsi-disk,drive=disk: Device needs media, but drive is empty
|
||||||
QEMU_PROG: -device scsi-disk,drive=disk: Device initialization failed.
|
|
||||||
QEMU_PROG: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized
|
QEMU_PROG: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized
|
||||||
|
|
||||||
Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk
|
Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk
|
||||||
QEMU X.Y.Z monitor - type 'help' for more information
|
QEMU X.Y.Z monitor - type 'help' for more information
|
||||||
(qemu) QEMU_PROG: -device scsi-hd,drive=disk: Device needs media, but drive is empty
|
(qemu) QEMU_PROG: -device scsi-hd,drive=disk: Device needs media, but drive is empty
|
||||||
QEMU_PROG: -device scsi-hd,drive=disk: Device initialization failed.
|
|
||||||
QEMU_PROG: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized
|
QEMU_PROG: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized
|
||||||
|
|
||||||
|
|
||||||
|
|
11
xen-hvm.c
11
xen-hvm.c
|
@ -71,7 +71,7 @@ static inline ioreq_t *xen_vcpu_ioreq(shared_iopage_t *shared_page, int vcpu)
|
||||||
typedef struct XenPhysmap {
|
typedef struct XenPhysmap {
|
||||||
hwaddr start_addr;
|
hwaddr start_addr;
|
||||||
ram_addr_t size;
|
ram_addr_t size;
|
||||||
char *name;
|
const char *name;
|
||||||
hwaddr phys_offset;
|
hwaddr phys_offset;
|
||||||
|
|
||||||
QLIST_ENTRY(XenPhysmap) list;
|
QLIST_ENTRY(XenPhysmap) list;
|
||||||
|
@ -291,6 +291,7 @@ static int xen_add_to_physmap(XenIOState *state,
|
||||||
hwaddr pfn, start_gpfn;
|
hwaddr pfn, start_gpfn;
|
||||||
hwaddr phys_offset = memory_region_get_ram_addr(mr);
|
hwaddr phys_offset = memory_region_get_ram_addr(mr);
|
||||||
char path[80], value[17];
|
char path[80], value[17];
|
||||||
|
const char *mr_name;
|
||||||
|
|
||||||
if (get_physmapping(state, start_addr, size)) {
|
if (get_physmapping(state, start_addr, size)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -326,11 +327,13 @@ go_physmap:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mr_name = memory_region_name(mr);
|
||||||
|
|
||||||
physmap = g_malloc(sizeof (XenPhysmap));
|
physmap = g_malloc(sizeof (XenPhysmap));
|
||||||
|
|
||||||
physmap->start_addr = start_addr;
|
physmap->start_addr = start_addr;
|
||||||
physmap->size = size;
|
physmap->size = size;
|
||||||
physmap->name = (char *)mr->name;
|
physmap->name = mr_name;
|
||||||
physmap->phys_offset = phys_offset;
|
physmap->phys_offset = phys_offset;
|
||||||
|
|
||||||
QLIST_INSERT_HEAD(&state->physmap, physmap, list);
|
QLIST_INSERT_HEAD(&state->physmap, physmap, list);
|
||||||
|
@ -354,11 +357,11 @@ go_physmap:
|
||||||
if (!xs_write(state->xenstore, 0, path, value, strlen(value))) {
|
if (!xs_write(state->xenstore, 0, path, value, strlen(value))) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (mr->name) {
|
if (mr_name) {
|
||||||
snprintf(path, sizeof(path),
|
snprintf(path, sizeof(path),
|
||||||
"/local/domain/0/device-model/%d/physmap/%"PRIx64"/name",
|
"/local/domain/0/device-model/%d/physmap/%"PRIx64"/name",
|
||||||
xen_domid, (uint64_t)phys_offset);
|
xen_domid, (uint64_t)phys_offset);
|
||||||
if (!xs_write(state->xenstore, 0, path, mr->name, strlen(mr->name))) {
|
if (!xs_write(state->xenstore, 0, path, mr_name, strlen(mr_name))) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue