diff options
66 files changed, 1794 insertions, 697 deletions
diff --git a/Makefile.objs b/Makefile.objs index add83755be..18fd35cf15 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -50,7 +50,7 @@ common-obj-$(CONFIG_LINUX) += fsdev/ common-obj-y += migration.o migration-tcp.o common-obj-y += vmstate.o -common-obj-y += qemu-file.o +common-obj-y += qemu-file.o qemu-file-unix.o qemu-file-stdio.o common-obj-$(CONFIG_RDMA) += migration-rdma.o common-obj-y += qemu-char.o #aio.o common-obj-y += block-migration.o diff --git a/arch_init.c b/arch_init.c index 9b3e25d805..88a5ba0837 100644 --- a/arch_init.c +++ b/arch_init.c @@ -1040,8 +1040,7 @@ void ram_handle_compressed(void *host, uint8_t ch, uint64_t size) static int ram_load(QEMUFile *f, void *opaque, int version_id) { - ram_addr_t addr; - int flags, ret = 0; + int flags = 0, ret = 0; static uint64_t seq_iter; seq_iter++; @@ -1050,21 +1049,24 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) ret = -EINVAL; } - while (!ret) { - addr = qemu_get_be64(f); + while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) { + ram_addr_t addr, total_ram_bytes; + void *host; + uint8_t ch; + addr = qemu_get_be64(f); flags = addr & ~TARGET_PAGE_MASK; addr &= TARGET_PAGE_MASK; - if (flags & RAM_SAVE_FLAG_MEM_SIZE) { + switch (flags & ~RAM_SAVE_FLAG_CONTINUE) { + case RAM_SAVE_FLAG_MEM_SIZE: /* Synchronize RAM block list */ - char id[256]; - ram_addr_t length; - ram_addr_t total_ram_bytes = addr; - - while (total_ram_bytes) { + total_ram_bytes = addr; + while (!ret && total_ram_bytes) { RAMBlock *block; uint8_t len; + char id[256]; + ram_addr_t length; len = qemu_get_byte(f); qemu_get_buffer(f, (uint8_t *)id, len); @@ -1088,16 +1090,11 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) "accept migration", id); ret = -EINVAL; } - if (ret) { - break; - } total_ram_bytes -= length; } - } else if (flags & RAM_SAVE_FLAG_COMPRESS) { - void *host; - uint8_t ch; - + break; + case RAM_SAVE_FLAG_COMPRESS: host = host_from_stream_offset(f, addr, flags); if (!host) { error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); @@ -1107,9 +1104,8 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) ch = qemu_get_byte(f); ram_handle_compressed(host, ch, TARGET_PAGE_SIZE); - } else if (flags & RAM_SAVE_FLAG_PAGE) { - void *host; - + break; + case RAM_SAVE_FLAG_PAGE: host = host_from_stream_offset(f, addr, flags); if (!host) { error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); @@ -1118,8 +1114,9 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) } qemu_get_buffer(f, host, TARGET_PAGE_SIZE); - } else if (flags & RAM_SAVE_FLAG_XBZRLE) { - void *host = host_from_stream_offset(f, addr, flags); + break; + case RAM_SAVE_FLAG_XBZRLE: + host = host_from_stream_offset(f, addr, flags); if (!host) { error_report("Illegal RAM offset " RAM_ADDR_FMT, addr); ret = -EINVAL; @@ -1132,17 +1129,22 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id) ret = -EINVAL; break; } - } else if (flags & RAM_SAVE_FLAG_HOOK) { - ram_control_load_hook(f, flags); - } else if (flags & RAM_SAVE_FLAG_EOS) { - /* normal exit */ break; - } else { - error_report("Unknown migration flags: %#x", flags); - ret = -EINVAL; + case RAM_SAVE_FLAG_EOS: + /* normal exit */ break; + default: + if (flags & RAM_SAVE_FLAG_HOOK) { + ram_control_load_hook(f, flags); + } else { + error_report("Unknown combination of migration flags: %#x", + flags); + ret = -EINVAL; + } + } + if (!ret) { + ret = qemu_file_get_error(f); } - ret = qemu_file_get_error(f); } DPRINTF("Completed load of VM with exit code %d seq iteration " @@ -5043,6 +5043,11 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp) return; } + if (!(bs->open_flags & BDRV_O_INCOMING)) { + return; + } + bs->open_flags &= ~BDRV_O_INCOMING; + if (bs->drv->bdrv_invalidate_cache) { bs->drv->bdrv_invalidate_cache(bs, &local_err); } else if (bs->file) { @@ -5078,19 +5083,6 @@ void bdrv_invalidate_cache_all(Error **errp) } } -void bdrv_clear_incoming_migration_all(void) -{ - BlockDriverState *bs; - - QTAILQ_FOREACH(bs, &bdrv_states, device_list) { - AioContext *aio_context = bdrv_get_aio_context(bs); - - aio_context_acquire(aio_context); - bs->open_flags = bs->open_flags & ~(BDRV_O_INCOMING); - aio_context_release(aio_context); - } -} - int bdrv_flush(BlockDriverState *bs) { Coroutine *co; diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c index b72b34e5c9..0bfa814f71 100644 --- a/hw/acpi/piix4.c +++ b/hw/acpi/piix4.c @@ -354,8 +354,8 @@ static void piix4_device_plug_cb(HotplugHandler *hotplug_dev, } } -static void piix4_device_unplug_cb(HotplugHandler *hotplug_dev, - DeviceState *dev, Error **errp) +static void piix4_device_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { PIIX4PMState *s = PIIX4_PM(hotplug_dev); @@ -615,7 +615,7 @@ static void piix4_pm_class_init(ObjectClass *klass, void *data) dc->cannot_instantiate_with_device_add_yet = true; dc->hotpluggable = false; hc->plug = piix4_device_plug_cb; - hc->unplug = piix4_device_unplug_cb; + hc->unplug_request = piix4_device_unplug_request_cb; adevc->ospm_status = piix4_ospm_status; } diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c index 3931085983..c6870f19e1 100644 --- a/hw/char/virtio-serial-bus.c +++ b/hw/char/virtio-serial-bus.c @@ -904,6 +904,12 @@ static void virtser_port_device_realize(DeviceState *dev, Error **errp) } port->elem.out_num = 0; +} + +static void virtser_port_device_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev); QTAILQ_INSERT_TAIL(&port->vser->ports, port, next); port->ivq = port->vser->ivqs[port->id]; @@ -912,7 +918,7 @@ static void virtser_port_device_realize(DeviceState *dev, Error **errp) add_port(port->vser, port->id); /* Send an update to the guest about this new port added */ - virtio_notify_config(vdev); + virtio_notify_config(VIRTIO_DEVICE(hotplug_dev)); } static void virtser_port_device_unrealize(DeviceState *dev, Error **errp) @@ -935,7 +941,6 @@ static void virtio_serial_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOSerial *vser = VIRTIO_SERIAL(dev); - BusState *bus; uint32_t i, max_supported_ports; if (!vser->serial.max_virtserial_ports) { @@ -957,8 +962,7 @@ static void virtio_serial_device_realize(DeviceState *dev, Error **errp) /* Spawn a new virtio-serial bus on which the ports will ride as devices */ qbus_create_inplace(&vser->bus, sizeof(vser->bus), TYPE_VIRTIO_SERIAL_BUS, dev, vdev->bus_name); - bus = BUS(&vser->bus); - bus->allow_hotplug = 1; + qbus_set_hotplug_handler(BUS(&vser->bus), DEVICE(vser), errp); vser->bus.vser = vser; QTAILQ_INIT(&vser->ports); @@ -1021,7 +1025,6 @@ static void virtio_serial_port_class_init(ObjectClass *klass, void *data) k->bus_type = TYPE_VIRTIO_SERIAL_BUS; k->realize = virtser_port_device_realize; k->unrealize = virtser_port_device_unrealize; - k->unplug = qdev_simple_unplug_cb; k->props = virtser_props; } @@ -1064,6 +1067,7 @@ static void virtio_serial_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); QLIST_INIT(&vserdevices.devices); @@ -1077,6 +1081,8 @@ static void virtio_serial_class_init(ObjectClass *klass, void *data) vdc->reset = vser_reset; vdc->save = virtio_serial_save_device; vdc->load = virtio_serial_load_device; + hc->plug = virtser_port_device_plug; + hc->unplug = qdev_simple_device_unplug_cb; } static const TypeInfo virtio_device_info = { @@ -1084,6 +1090,10 @@ static const TypeInfo virtio_device_info = { .parent = TYPE_VIRTIO_DEVICE, .instance_size = sizeof(VirtIOSerial), .class_init = virtio_serial_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static void virtio_serial_register_types(void) diff --git a/hw/core/hotplug.c b/hw/core/hotplug.c index 5573d9d2d9..4e01074557 100644 --- a/hw/core/hotplug.c +++ b/hw/core/hotplug.c @@ -23,6 +23,17 @@ void hotplug_handler_plug(HotplugHandler *plug_handler, } } +void hotplug_handler_unplug_request(HotplugHandler *plug_handler, + DeviceState *plugged_dev, + Error **errp) +{ + HotplugHandlerClass *hdc = HOTPLUG_HANDLER_GET_CLASS(plug_handler); + + if (hdc->unplug_request) { + hdc->unplug_request(plug_handler, plugged_dev, errp); + } +} + void hotplug_handler_unplug(HotplugHandler *plug_handler, DeviceState *plugged_dev, Error **errp) diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 84caa1d694..f2bd954be5 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -112,7 +112,7 @@ static void set_drive(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_drive = { .name = "str", - .legacy_name = "drive", + .description = "ID of a drive to use as a backend", .get = get_drive, .set = set_drive, .release = release_drive, @@ -169,7 +169,7 @@ static void set_chr(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_chr = { .name = "str", - .legacy_name = "chr", + .description = "ID of a chardev to use as a backend", .get = get_chr, .set = set_chr, .release = release_chr, @@ -248,7 +248,7 @@ static void set_netdev(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_netdev = { .name = "str", - .legacy_name = "netdev", + .description = "ID of a netdev to use as a backend", .get = get_netdev, .set = set_netdev, }; @@ -328,7 +328,7 @@ static void set_vlan(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_vlan = { .name = "int32", - .legacy_name = "vlan", + .description = "Integer VLAN id to connect to", .print = print_vlan, .get = get_vlan, .set = set_vlan, diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 66556d3bf9..2ed995f2b7 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -120,7 +120,7 @@ static void prop_set_bit(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_bit = { .name = "bool", - .legacy_name = "on/off", + .description = "on/off", .get = prop_get_bit, .set = prop_set_bit, }; @@ -455,7 +455,7 @@ inval: PropertyInfo qdev_prop_macaddr = { .name = "str", - .legacy_name = "macaddr", + .description = "Ethernet 6-byte MAC Address, example: 52:54:00:12:34:56", .get = get_mac, .set = set_mac, }; @@ -477,7 +477,8 @@ QEMU_BUILD_BUG_ON(sizeof(BiosAtaTranslation) != sizeof(int)); PropertyInfo qdev_prop_bios_chs_trans = { .name = "BiosAtaTranslation", - .legacy_name = "bios-chs-trans", + .description = "Logical CHS translation algorithm, " + "auto/none/lba/large/rechs", .enum_table = BiosAtaTranslation_lookup, .get = get_enum, .set = set_enum, @@ -551,7 +552,7 @@ static int print_pci_devfn(DeviceState *dev, Property *prop, char *dest, PropertyInfo qdev_prop_pci_devfn = { .name = "int32", - .legacy_name = "pci-devfn", + .description = "Slot and optional function number, example: 06.0 or 06", .print = print_pci_devfn, .get = get_int32, .set = set_pci_devfn, @@ -598,7 +599,7 @@ static void set_blocksize(Object *obj, Visitor *v, void *opaque, PropertyInfo qdev_prop_blocksize = { .name = "uint16", - .legacy_name = "blocksize", + .description = "A power of two between 512 and 32768", .get = get_uint16, .set = set_blocksize, }; @@ -706,7 +707,8 @@ inval: PropertyInfo qdev_prop_pci_host_devaddr = { .name = "str", - .legacy_name = "pci-host-devaddr", + .description = "Address (bus/device/function) of " + "the host device, example: 04:10.0", .get = get_pci_host_devaddr, .set = set_pci_host_devaddr, }; diff --git a/hw/core/qdev.c b/hw/core/qdev.c index fcb16383a1..a1e9247772 100644 --- a/hw/core/qdev.c +++ b/hw/core/qdev.c @@ -85,10 +85,6 @@ static void bus_add_child(BusState *bus, DeviceState *child) char name[32]; BusChild *kid = g_malloc0(sizeof(*kid)); - if (qdev_hotplug) { - assert(bus->allow_hotplug); - } - kid->index = bus->max_index++; kid->child = child; object_ref(OBJECT(kid->child)); @@ -112,6 +108,24 @@ void qdev_set_parent_bus(DeviceState *dev, BusState *bus) bus_add_child(bus, dev); } +static void qbus_set_hotplug_handler_internal(BusState *bus, Object *handler, + Error **errp) +{ + + object_property_set_link(OBJECT(bus), OBJECT(handler), + QDEV_HOTPLUG_HANDLER_PROPERTY, errp); +} + +void qbus_set_hotplug_handler(BusState *bus, DeviceState *handler, Error **errp) +{ + qbus_set_hotplug_handler_internal(bus, OBJECT(handler), errp); +} + +void qbus_set_bus_hotplug_handler(BusState *bus, Error **errp) +{ + qbus_set_hotplug_handler_internal(bus, OBJECT(bus), errp); +} + /* Create a new device. This only initializes the device state structure and allows properties to be set. qdev_init should be called to initialize the actual device emulation. */ @@ -209,11 +223,30 @@ void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, dev->alias_required_for_version = required_for_version; } +static HotplugHandler *qdev_get_hotplug_handler(DeviceState *dev) +{ + HotplugHandler *hotplug_ctrl = NULL; + + if (dev->parent_bus && dev->parent_bus->hotplug_handler) { + hotplug_ctrl = dev->parent_bus->hotplug_handler; + } else if (object_dynamic_cast(qdev_get_machine(), TYPE_MACHINE)) { + MachineState *machine = MACHINE(qdev_get_machine()); + MachineClass *mc = MACHINE_GET_CLASS(machine); + + if (mc->get_hotplug_handler) { + hotplug_ctrl = mc->get_hotplug_handler(machine, dev); + } + } + return hotplug_ctrl; +} + void qdev_unplug(DeviceState *dev, Error **errp) { DeviceClass *dc = DEVICE_GET_CLASS(dev); + HotplugHandler *hotplug_ctrl; + HotplugHandlerClass *hdc; - if (dev->parent_bus && !dev->parent_bus->allow_hotplug) { + if (dev->parent_bus && !qbus_is_hotpluggable(dev->parent_bus)) { error_set(errp, QERR_BUS_NO_HOTPLUG, dev->parent_bus->name); return; } @@ -226,13 +259,18 @@ void qdev_unplug(DeviceState *dev, Error **errp) qdev_hot_removed = true; - if (dev->parent_bus && dev->parent_bus->hotplug_handler) { - hotplug_handler_unplug(dev->parent_bus->hotplug_handler, dev, errp); + hotplug_ctrl = qdev_get_hotplug_handler(dev); + /* hotpluggable device MUST have HotplugHandler, if it doesn't + * then something is very wrong with it */ + g_assert(hotplug_ctrl); + + /* If device supports async unplug just request it to be done, + * otherwise just remove it synchronously */ + hdc = HOTPLUG_HANDLER_GET_CLASS(hotplug_ctrl); + if (hdc->unplug_request) { + hotplug_handler_unplug_request(hotplug_ctrl, dev, errp); } else { - assert(dc->unplug != NULL); - if (dc->unplug(dev) < 0) { /* legacy handler */ - error_set(errp, QERR_UNDEFINED_ERROR); - } + hotplug_handler_unplug(hotplug_ctrl, dev, errp); } } @@ -269,14 +307,13 @@ void qbus_reset_all_fn(void *opaque) } /* can be used as ->unplug() callback for the simple cases */ -int qdev_simple_unplug_cb(DeviceState *dev) +void qdev_simple_device_unplug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { /* just zap it */ object_unparent(OBJECT(dev)); - return 0; } - /* Like qdev_init(), but terminate program via error_report() instead of returning an error value. This is okay during machine creation. Don't use for hotplug, because there callers need to recover from @@ -337,10 +374,20 @@ static NamedGPIOList *qdev_get_named_gpio_list(DeviceState *dev, void qdev_init_gpio_in_named(DeviceState *dev, qemu_irq_handler handler, const char *name, int n) { + int i; NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name); + char *propname = g_strdup_printf("%s[*]", name ? name : "unnamed-gpio-in"); + assert(gpio_list->num_out == 0 || !name); gpio_list->in = qemu_extend_irqs(gpio_list->in, gpio_list->num_in, handler, dev, n); + + for (i = gpio_list->num_in; i < gpio_list->num_in + n; i++) { + object_property_add_child(OBJECT(dev), propname, + OBJECT(gpio_list->in[i]), &error_abort); + } + g_free(propname); + gpio_list->num_in += n; } @@ -352,11 +399,24 @@ void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n) void qdev_init_gpio_out_named(DeviceState *dev, qemu_irq *pins, const char *name, int n) { + int i; NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name); + char *propname = g_strdup_printf("%s[*]", name ? name : "unnamed-gpio-out"); + assert(gpio_list->num_in == 0 || !name); assert(gpio_list->num_out == 0); gpio_list->num_out = n; gpio_list->out = pins; + + for (i = 0; i < n; ++i) { + memset(&pins[i], 0, sizeof(*pins)); + object_property_add_link(OBJECT(dev), propname, TYPE_IRQ, + (Object **)&pins[i], + object_property_allow_set_link, + OBJ_PROP_LINK_UNREF_ON_RELEASE, + &error_abort); + } + g_free(propname); } void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n) @@ -766,6 +826,11 @@ void qdev_property_add_static(DeviceState *dev, Property *prop, error_propagate(errp, local_err); return; } + + object_property_set_description(obj, prop->name, + prop->info->description, + &error_abort); + if (prop->qtype == QTYPE_NONE) { return; } @@ -811,6 +876,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp) { DeviceState *dev = DEVICE(obj); DeviceClass *dc = DEVICE_GET_CLASS(dev); + HotplugHandler *hotplug_ctrl; BusState *bus; Error *local_err = NULL; @@ -838,20 +904,9 @@ static void device_set_realized(Object *obj, bool value, Error **errp) goto fail; } - if (dev->parent_bus && dev->parent_bus->hotplug_handler) { - hotplug_handler_plug(dev->parent_bus->hotplug_handler, - dev, &local_err); - } else if (object_dynamic_cast(qdev_get_machine(), TYPE_MACHINE)) { - HotplugHandler *hotplug_ctrl; - MachineState *machine = MACHINE(qdev_get_machine()); - MachineClass *mc = MACHINE_GET_CLASS(machine); - - if (mc->get_hotplug_handler) { - hotplug_ctrl = mc->get_hotplug_handler(machine, dev); - if (hotplug_ctrl) { - hotplug_handler_plug(hotplug_ctrl, dev, &local_err); - } - } + hotplug_ctrl = qdev_get_hotplug_handler(dev); + if (hotplug_ctrl) { + hotplug_handler_plug(hotplug_ctrl, dev, &local_err); } if (local_err != NULL) { @@ -925,7 +980,7 @@ static bool device_get_hotpluggable(Object *obj, Error **errp) DeviceState *dev = DEVICE(obj); return dc->hotpluggable && (dev->parent_bus == NULL || - dev->parent_bus->allow_hotplug); + qbus_is_hotpluggable(dev->parent_bus)); } static bool device_get_hotplugged(Object *obj, Error **err) diff --git a/hw/cpu/icc_bus.c b/hw/cpu/icc_bus.c index 7f44c59b25..9575fd6a42 100644 --- a/hw/cpu/icc_bus.c +++ b/hw/cpu/icc_bus.c @@ -24,18 +24,10 @@ /* icc-bridge implementation */ -static void icc_bus_init(Object *obj) -{ - BusState *b = BUS(obj); - - b->allow_hotplug = true; -} - static const TypeInfo icc_bus_info = { .name = TYPE_ICC_BUS, .parent = TYPE_BUS, .instance_size = sizeof(ICCBus), - .instance_init = icc_bus_init, }; diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c index a3133211a8..00be4bb12a 100644 --- a/hw/i386/acpi-build.c +++ b/hw/i386/acpi-build.c @@ -774,7 +774,7 @@ static void *acpi_set_bsel(PCIBus *bus, void *opaque) unsigned *bsel_alloc = opaque; unsigned *bus_bsel; - if (bus->qbus.allow_hotplug) { + if (qbus_is_hotpluggable(BUS(bus))) { bus_bsel = g_malloc(sizeof *bus_bsel); *bus_bsel = (*bsel_alloc)++; diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index 177023bcaf..530b074551 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -607,8 +607,8 @@ static void ich9_device_plug_cb(HotplugHandler *hotplug_dev, ich9_pm_device_plug_cb(&lpc->pm, dev, errp); } -static void ich9_device_unplug_cb(HotplugHandler *hotplug_dev, - DeviceState *dev, Error **errp) +static void ich9_device_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { error_setg(errp, "acpi: device unplug request for not supported device" " type: %s", object_get_typename(OBJECT(dev))); @@ -676,7 +676,7 @@ static void ich9_lpc_class_init(ObjectClass *klass, void *data) */ dc->cannot_instantiate_with_device_add_yet = true; hc->plug = ich9_device_plug_cb; - hc->unplug = ich9_device_unplug_cb; + hc->unplug_request = ich9_device_unplug_request_cb; adevc->ospm_status = ich9_pm_ospm_status; } diff --git a/hw/pci-bridge/pci_bridge_dev.c b/hw/pci-bridge/pci_bridge_dev.c index 92799d08ef..252ea5eb53 100644 --- a/hw/pci-bridge/pci_bridge_dev.c +++ b/hw/pci-bridge/pci_bridge_dev.c @@ -150,7 +150,7 @@ static void pci_bridge_dev_class_init(ObjectClass *klass, void *data) dc->vmsd = &pci_bridge_dev_vmstate; set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); hc->plug = shpc_device_hotplug_cb; - hc->unplug = shpc_device_hot_unplug_cb; + hc->unplug_request = shpc_device_hot_unplug_request_cb; } static const TypeInfo pci_bridge_dev_info = { diff --git a/hw/pci/pci-hotplug-old.c b/hw/pci/pci-hotplug-old.c index d87c469096..6ab28b703c 100644 --- a/hw/pci/pci-hotplug-old.c +++ b/hw/pci/pci-hotplug-old.c @@ -77,7 +77,7 @@ static PCIDevice *qemu_pci_hot_add_nic(Monitor *mon, monitor_printf(mon, "Invalid PCI device address %s\n", devaddr); return NULL; } - if (!((BusState*)bus)->allow_hotplug) { + if (!qbus_is_hotpluggable(BUS(bus))) { monitor_printf(mon, "PCI bus doesn't support hotplug\n"); return NULL; } @@ -227,7 +227,7 @@ static PCIDevice *qemu_pci_hot_add_storage(Monitor *mon, monitor_printf(mon, "Invalid PCI device address %s\n", devaddr); return NULL; } - if (!((BusState*)bus)->allow_hotplug) { + if (!qbus_is_hotpluggable(BUS(bus))) { monitor_printf(mon, "PCI bus doesn't support hotplug\n"); return NULL; } diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c index 1babddff4d..b64a004631 100644 --- a/hw/pci/pcie.c +++ b/hw/pci/pcie.c @@ -262,8 +262,8 @@ void pcie_cap_slot_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, PCI_EXP_HP_EV_PDC | PCI_EXP_HP_EV_ABP); } -void pcie_cap_slot_hot_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, - Error **errp) +void pcie_cap_slot_hot_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { uint8_t *exp_cap; diff --git a/hw/pci/pcie_port.c b/hw/pci/pcie_port.c index fa24877955..40ca8d5d17 100644 --- a/hw/pci/pcie_port.c +++ b/hw/pci/pcie_port.c @@ -154,7 +154,7 @@ static void pcie_slot_class_init(ObjectClass *oc, void *data) dc->props = pcie_slot_props; hc->plug = pcie_cap_slot_hotplug_cb; - hc->unplug = pcie_cap_slot_hot_unplug_cb; + hc->unplug_request = pcie_cap_slot_hot_unplug_request_cb; } static const TypeInfo pcie_slot_type_info = { diff --git a/hw/pci/shpc.c b/hw/pci/shpc.c index 1fcb8c4d85..65b2f5103f 100644 --- a/hw/pci/shpc.c +++ b/hw/pci/shpc.c @@ -549,8 +549,8 @@ void shpc_device_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, shpc_interrupt_update(pci_hotplug_dev); } -void shpc_device_hot_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, - Error **errp) +void shpc_device_hot_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { Error *local_err = NULL; PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev); diff --git a/hw/s390x/event-facility.c b/hw/s390x/event-facility.c index 597db34019..78da718362 100644 --- a/hw/s390x/event-facility.c +++ b/hw/s390x/event-facility.c @@ -333,7 +333,6 @@ static int init_event_facility(SCLPEventFacility *event_facility) /* Spawn a new bus for SCLP events */ qbus_create_inplace(&event_facility->sbus, sizeof(event_facility->sbus), TYPE_SCLP_EVENTS_BUS, sdev, NULL); - event_facility->sbus.qbus.allow_hotplug = 0; quiesce = qdev_create(&event_facility->sbus.qbus, "sclpquiesce"); if (!quiesce) { @@ -408,7 +407,6 @@ static void event_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); dc->bus_type = TYPE_SCLP_EVENTS_BUS; - dc->unplug = qdev_simple_unplug_cb; dc->realize = event_realize; dc->unrealize = event_unrealize; } diff --git a/hw/s390x/s390-virtio-bus.c b/hw/s390x/s390-virtio-bus.c index fabde3722b..29ecdb4194 100644 --- a/hw/s390x/s390-virtio-bus.c +++ b/hw/s390x/s390-virtio-bus.c @@ -102,7 +102,7 @@ VirtIOS390Bus *s390_virtio_bus_init(ram_addr_t *ram_size) bus->next_ring = bus->dev_page + TARGET_PAGE_SIZE; /* Enable hotplugging */ - _bus->allow_hotplug = 1; + qbus_set_hotplug_handler(_bus, dev, &error_abort); /* Allocate RAM for VirtIO device pages (descriptors, queues, rings) */ *ram_size += S390_DEVICE_PAGES * TARGET_PAGE_SIZE; @@ -604,7 +604,6 @@ static void virtio_s390_device_class_init(ObjectClass *klass, void *data) dc->init = s390_virtio_busdev_init; dc->bus_type = TYPE_S390_VIRTIO_BUS; - dc->unplug = qdev_simple_unplug_cb; dc->reset = s390_virtio_busdev_reset; } @@ -685,6 +684,10 @@ static const TypeInfo s390_virtio_bridge_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(SysBusDevice), .class_init = s390_virtio_bridge_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; /* virtio-s390-bus */ @@ -693,13 +696,10 @@ static void virtio_s390_bus_new(VirtioBusState *bus, size_t bus_size, VirtIOS390Device *dev) { DeviceState *qdev = DEVICE(dev); - BusState *qbus; char virtio_bus_name[] = "virtio-bus"; qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_S390_BUS, qdev, virtio_bus_name); - qbus = BUS(bus); - qbus->allow_hotplug = 1; } static void virtio_s390_bus_class_init(ObjectClass *klass, void *data) diff --git a/hw/s390x/virtio-ccw.c b/hw/s390x/virtio-ccw.c index 6bb870810f..f5ec94657f 100644 --- a/hw/s390x/virtio-ccw.c +++ b/hw/s390x/virtio-ccw.c @@ -230,7 +230,7 @@ VirtualCssBus *virtual_css_bus_init(void) cbus = VIRTUAL_CSS_BUS(bus); /* Enable hotplugging */ - bus->allow_hotplug = 1; + qbus_set_hotplug_handler(bus, dev, &error_abort); return cbus; } @@ -1594,7 +1594,8 @@ static int virtio_ccw_busdev_exit(DeviceState *dev) return _info->exit(_dev); } -static int virtio_ccw_busdev_unplug(DeviceState *dev) +static void virtio_ccw_busdev_unplug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) { VirtioCcwDevice *_dev = (VirtioCcwDevice *)dev; SubchDev *sch = _dev->sch; @@ -1613,7 +1614,6 @@ static int virtio_ccw_busdev_unplug(DeviceState *dev) css_generate_sch_crws(sch->cssid, sch->ssid, sch->schid, 1, 0); object_unparent(OBJECT(dev)); - return 0; } static Property virtio_ccw_properties[] = { @@ -1628,9 +1628,7 @@ static void virtio_ccw_device_class_init(ObjectClass *klass, void *data) dc->props = virtio_ccw_properties; dc->init = virtio_ccw_busdev_init; dc->exit = virtio_ccw_busdev_exit; - dc->unplug = virtio_ccw_busdev_unplug; dc->bus_type = TYPE_VIRTUAL_CSS_BUS; - } static const TypeInfo virtio_ccw_device_info = { @@ -1654,8 +1652,10 @@ static int virtual_css_bridge_init(SysBusDevice *dev) static void virtual_css_bridge_class_init(ObjectClass *klass, void *data) { SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); k->init = virtual_css_bridge_init; + hc->unplug = virtio_ccw_busdev_unplug; } static const TypeInfo virtual_css_bridge_info = { @@ -1663,6 +1663,10 @@ static const TypeInfo virtual_css_bridge_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(SysBusDevice), .class_init = virtual_css_bridge_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; /* virtio-ccw-bus */ @@ -1671,13 +1675,10 @@ static void virtio_ccw_bus_new(VirtioBusState *bus, size_t bus_size, VirtioCcwDevice *dev) { DeviceState *qdev = DEVICE(dev); - BusState *qbus; char virtio_bus_name[] = "virtio-bus"; qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_CCW_BUS, qdev, virtio_bus_name); - qbus = BUS(bus); - qbus->allow_hotplug = 1; } static void virtio_ccw_bus_class_init(ObjectClass *klass, void *data) diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c index e7b1cf6730..022a524074 100644 --- a/hw/scsi/scsi-bus.c +++ b/hw/scsi/scsi-bus.c @@ -23,9 +23,11 @@ static Property scsi_props[] = { 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; + hc->unplug = qdev_simple_device_unplug_cb; } static const TypeInfo scsi_bus_info = { @@ -33,6 +35,10 @@ static const TypeInfo scsi_bus_info = { .parent = TYPE_BUS, .instance_size = sizeof(SCSIBus), .class_init = scsi_bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static int next_scsi_bus; @@ -92,7 +98,7 @@ void scsi_bus_new(SCSIBus *bus, size_t bus_size, DeviceState *host, qbus_create_inplace(bus, bus_size, TYPE_SCSI_BUS, host, bus_name); bus->busnr = next_scsi_bus++; bus->info = info; - bus->qbus.allow_hotplug = 1; + qbus_set_bus_hotplug_handler(BUS(bus), &error_abort); } static void scsi_dma_restart_bh(void *opaque) @@ -202,10 +208,6 @@ static void scsi_qdev_realize(DeviceState *qdev, Error **errp) } dev->vmsentry = qemu_add_vm_change_state_handler(scsi_dma_restart_cb, dev); - - if (bus->info->hotplug) { - bus->info->hotplug(bus, dev); - } } static void scsi_qdev_unrealize(DeviceState *qdev, Error **errp) @@ -1938,17 +1940,6 @@ static int get_scsi_requests(QEMUFile *f, void *pv, size_t size) return 0; } -static int scsi_qdev_unplug(DeviceState *qdev) -{ - SCSIDevice *dev = SCSI_DEVICE(qdev); - SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); - - if (bus->info->hot_unplug) { - bus->info->hot_unplug(bus, dev); - } - return qdev_simple_unplug_cb(qdev); -} - static const VMStateInfo vmstate_info_scsi_requests = { .name = "scsi-requests", .get = get_scsi_requests, @@ -2012,7 +2003,6 @@ static void scsi_device_class_init(ObjectClass *klass, void *data) set_bit(DEVICE_CATEGORY_STORAGE, k->categories); k->bus_type = TYPE_SCSI_BUS; k->realize = scsi_qdev_realize; - k->unplug = scsi_qdev_unplug; k->unrealize = scsi_qdev_unrealize; k->props = scsi_props; } diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 6c02fe2b9a..8547ea0475 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -737,26 +737,29 @@ static void virtio_scsi_change(SCSIBus *bus, SCSIDevice *dev, SCSISense sense) } } -static void virtio_scsi_hotplug(SCSIBus *bus, SCSIDevice *dev) +static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, + Error **errp) { - VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); - VirtIODevice *vdev = VIRTIO_DEVICE(s); + VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev); if ((vdev->guest_features >> VIRTIO_SCSI_F_HOTPLUG) & 1) { - virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_TRANSPORT_RESET, + virtio_scsi_push_event(VIRTIO_SCSI(hotplug_dev), SCSI_DEVICE(dev), + VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_RESCAN); } } -static void virtio_scsi_hot_unplug(SCSIBus *bus, SCSIDevice *dev) +static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev, + Error **errp) { - VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); - VirtIODevice *vdev = VIRTIO_DEVICE(s); + VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev); if ((vdev->guest_features >> VIRTIO_SCSI_F_HOTPLUG) & 1) { - virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_TRANSPORT_RESET, + virtio_scsi_push_event(VIRTIO_SCSI(hotplug_dev), SCSI_DEVICE(dev), + VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_REMOVED); } + qdev_simple_device_unplug_cb(hotplug_dev, dev, errp); } static struct SCSIBusInfo virtio_scsi_scsi_info = { @@ -768,8 +771,6 @@ static struct SCSIBusInfo virtio_scsi_scsi_info = { .complete = virtio_scsi_command_complete, .cancel = virtio_scsi_request_cancelled, .change = virtio_scsi_change, - .hotplug = virtio_scsi_hotplug, - .hot_unplug = virtio_scsi_hot_unplug, .parse_cdb = virtio_scsi_parse_cdb, .get_sg_list = virtio_scsi_get_sg_list, .save_request = virtio_scsi_save_request, @@ -853,6 +854,8 @@ static void virtio_scsi_device_realize(DeviceState *dev, Error **errp) scsi_bus_new(&s->bus, sizeof(s->bus), dev, &virtio_scsi_scsi_info, vdev->bus_name); + /* override default SCSI bus hotplug-handler, with virtio-scsi's one */ + qbus_set_hotplug_handler(BUS(&s->bus), dev, &error_abort); if (!dev->hotplugged) { scsi_bus_legacy_handle_cmdline(&s->bus, &err); @@ -915,6 +918,7 @@ static void virtio_scsi_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); dc->props = virtio_scsi_properties; set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); @@ -923,6 +927,8 @@ static void virtio_scsi_class_init(ObjectClass *klass, void *data) vdc->set_config = virtio_scsi_set_config; vdc->get_features = virtio_scsi_get_features; vdc->reset = virtio_scsi_reset; + hc->plug = virtio_scsi_hotplug; + hc->unplug = virtio_scsi_hotunplug; } static const TypeInfo virtio_scsi_common_info = { @@ -939,6 +945,10 @@ static const TypeInfo virtio_scsi_info = { .instance_size = sizeof(VirtIOSCSI), .instance_init = virtio_scsi_instance_init, .class_init = virtio_scsi_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static void virtio_register_types(void) diff --git a/hw/scsi/vmw_pvscsi.c b/hw/scsi/vmw_pvscsi.c index 5734d19789..d3a92fbabf 100644 --- a/hw/scsi/vmw_pvscsi.c +++ b/hw/scsi/vmw_pvscsi.c @@ -524,17 +524,20 @@ pvscsi_send_msg(PVSCSIState *s, SCSIDevice *dev, uint32_t msg_type) } static void -pvscsi_hotplug(SCSIBus *bus, SCSIDevice *dev) +pvscsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { - PVSCSIState *s = container_of(bus, PVSCSIState, bus); - pvscsi_send_msg(s, dev, PVSCSI_MSG_DEV_ADDED); + PVSCSIState *s = PVSCSI(hotplug_dev); + + pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_ADDED); } static void -pvscsi_hot_unplug(SCSIBus *bus, SCSIDevice *dev) +pvscsi_hot_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { - PVSCSIState *s = container_of(bus, PVSCSIState, bus); - pvscsi_send_msg(s, dev, PVSCSI_MSG_DEV_REMOVED); + PVSCSIState *s = PVSCSI(hotplug_dev); + + pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_REMOVED); + qdev_simple_device_unplug_cb(hotplug_dev, dev, errp); } static void @@ -1057,8 +1060,6 @@ static const struct SCSIBusInfo pvscsi_scsi_info = { .get_sg_list = pvscsi_get_sg_list, .complete = pvscsi_command_complete, .cancel = pvscsi_request_cancelled, - .hotplug = pvscsi_hotplug, - .hot_unplug = pvscsi_hot_unplug, }; static int @@ -1092,6 +1093,8 @@ pvscsi_init(PCIDevice *pci_dev) scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(pci_dev), &pvscsi_scsi_info, NULL); + /* override default SCSI bus hotplug-handler, with pvscsi's one */ + qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(s), &error_abort); pvscsi_reset_state(s); return 0; @@ -1187,6 +1190,7 @@ static void pvscsi_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); k->init = pvscsi_init; k->exit = pvscsi_uninit; @@ -1199,6 +1203,8 @@ static void pvscsi_class_init(ObjectClass *klass, void *data) dc->props = pvscsi_properties; set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); k->config_write = pvscsi_write_config; + hc->unplug = pvscsi_hot_unplug; + hc->plug = pvscsi_hotplug; } static const TypeInfo pvscsi_info = { @@ -1206,6 +1212,10 @@ static const TypeInfo pvscsi_info = { .parent = TYPE_PCI_DEVICE, .instance_size = sizeof(PVSCSIState), .class_init = pvscsi_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static void diff --git a/hw/usb/bus.c b/hw/usb/bus.c index da1eba9fbd..986b2d8da8 100644 --- a/hw/usb/bus.c +++ b/hw/usb/bus.c @@ -24,10 +24,12 @@ static Property usb_props[] = { static void usb_bus_class_init(ObjectClass *klass, void *data) { BusClass *k = BUS_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); k->print_dev = usb_bus_dev_print; k->get_dev_path = usb_get_dev_path; k->get_fw_dev_path = usb_get_fw_dev_path; + hc->unplug = qdev_simple_device_unplug_cb; } static const TypeInfo usb_bus_info = { @@ -35,6 +37,10 @@ static const TypeInfo usb_bus_info = { .parent = TYPE_BUS, .instance_size = sizeof(USBBus), .class_init = usb_bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static int next_usb_bus = 0; @@ -79,9 +85,9 @@ void usb_bus_new(USBBus *bus, size_t bus_size, USBBusOps *ops, DeviceState *host) { qbus_create_inplace(bus, bus_size, TYPE_USB_BUS, host, NULL); + qbus_set_bus_hotplug_handler(BUS(bus), &error_abort); bus->ops = ops; bus->busnr = next_usb_bus++; - bus->qbus.allow_hotplug = 1; /* Yes, we can */ QTAILQ_INIT(&bus->free); QTAILQ_INIT(&bus->used); QTAILQ_INSERT_TAIL(&busses, bus, next); @@ -701,7 +707,6 @@ static void usb_device_class_init(ObjectClass *klass, void *data) { DeviceClass *k = DEVICE_CLASS(klass); k->bus_type = TYPE_USB_BUS; - k->unplug = qdev_simple_unplug_cb; k->realize = usb_qdev_realize; k->unrealize = usb_qdev_unrealize; k->props = usb_props; diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c index d37ed02d2e..78ce681671 100644 --- a/hw/usb/dev-smartcard-reader.c +++ b/hw/usb/dev-smartcard-reader.c @@ -1312,8 +1312,8 @@ static void ccid_realize(USBDevice *dev, Error **errp) usb_desc_init(dev); qbus_create_inplace(&s->bus, sizeof(s->bus), TYPE_CCID_BUS, DEVICE(dev), NULL); + qbus_set_hotplug_handler(BUS(&s->bus), DEVICE(dev), &error_abort); s->intr = usb_ep_get(dev, USB_TOKEN_IN, CCID_INT_IN_EP); - s->bus.qbus.allow_hotplug = 1; s->card = NULL; s->migration_state = MIGRATION_NONE; s->migration_target_ip = 0; @@ -1439,6 +1439,7 @@ static void ccid_class_initfn(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); uc->realize = ccid_realize; uc->product_desc = "QEMU USB CCID"; @@ -1451,6 +1452,7 @@ static void ccid_class_initfn(ObjectClass *klass, void *data) dc->vmsd = &ccid_vmstate; dc->props = ccid_properties; set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + hc->unplug = qdev_simple_device_unplug_cb; } static const TypeInfo ccid_info = { @@ -1458,6 +1460,10 @@ static const TypeInfo ccid_info = { .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBCCIDState), .class_init = ccid_class_initfn, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } }; static void ccid_card_class_init(ObjectClass *klass, void *data) diff --git a/hw/usb/dev-storage.c b/hw/usb/dev-storage.c index 7406fdd4a9..5bfc72ca45 100644 --- a/hw/usb/dev-storage.c +++ b/hw/usb/dev-storage.c @@ -634,7 +634,6 @@ static void usb_msd_realize_storage(USBDevice *dev, Error **errp) error_propagate(errp, err); return; } - s->bus.qbus.allow_hotplug = 0; usb_msd_handle_reset(dev); s->scsi_dev = scsi_dev; @@ -656,7 +655,6 @@ static void usb_msd_realize_bot(USBDevice *dev, Error **errp) usb_desc_init(dev); scsi_bus_new(&s->bus, sizeof(s->bus), DEVICE(dev), &usb_msd_scsi_info_bot, NULL); - s->bus.qbus.allow_hotplug = 0; usb_msd_handle_reset(dev); } @@ -821,9 +819,11 @@ static void usb_msd_instance_init(Object *obj) static void usb_msd_class_initfn_bot(ObjectClass *klass, void *data) { USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); uc->realize = usb_msd_realize_bot; usb_msd_class_initfn_common(klass); + dc->hotpluggable = false; } static const TypeInfo msd_info = { diff --git a/hw/virtio/virtio-mmio.c b/hw/virtio/virtio-mmio.c index 18c6e5b55c..2450c13747 100644 --- a/hw/virtio/virtio-mmio.c +++ b/hw/virtio/virtio-mmio.c @@ -89,9 +89,6 @@ typedef struct { VirtioBusState bus; } VirtIOMMIOProxy; -static void virtio_mmio_bus_new(VirtioBusState *bus, size_t bus_size, - VirtIOMMIOProxy *dev); - static uint64_t virtio_mmio_read(void *opaque, hwaddr offset, unsigned size) { VirtIOMMIOProxy *proxy = (VirtIOMMIOProxy *)opaque; @@ -362,7 +359,8 @@ static void virtio_mmio_realizefn(DeviceState *d, Error **errp) VirtIOMMIOProxy *proxy = VIRTIO_MMIO(d); SysBusDevice *sbd = SYS_BUS_DEVICE(d); - virtio_mmio_bus_new(&proxy->bus, sizeof(proxy->bus), proxy); + qbus_create_inplace(&proxy->bus, sizeof(proxy->bus), TYPE_VIRTIO_MMIO_BUS, + d, NULL); sysbus_init_irq(sbd, &proxy->irq); memory_region_init_io(&proxy->iomem, OBJECT(d), &virtio_mem_ops, proxy, TYPE_VIRTIO_MMIO, 0x200); @@ -393,17 +391,6 @@ static const TypeInfo virtio_mmio_info = { /* virtio-mmio-bus. */ -static void virtio_mmio_bus_new(VirtioBusState *bus, size_t bus_size, - VirtIOMMIOProxy *dev) -{ - DeviceState *qdev = DEVICE(dev); - BusState *qbus; - - qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_MMIO_BUS, qdev, NULL); - qbus = BUS(bus); - qbus->allow_hotplug = 0; -} - static void virtio_mmio_bus_class_init(ObjectClass *klass, void *data) { BusClass *bus_class = BUS_CLASS(klass); diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index 3f219ae248..ae7fef96d5 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -1546,13 +1546,10 @@ static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size, VirtIOPCIProxy *dev) { DeviceState *qdev = DEVICE(dev); - BusState *qbus; char virtio_bus_name[] = "virtio-bus"; qbus_create_inplace(bus, bus_size, TYPE_VIRTIO_PCI_BUS, qdev, virtio_bus_name); - qbus = BUS(bus); - qbus->allow_hotplug = 1; } static void virtio_pci_bus_class_init(ObjectClass *klass, void *data) diff --git a/include/glib-compat.h b/include/glib-compat.h index 4ae0671a8e..e29bf69849 100644 --- a/include/glib-compat.h +++ b/include/glib-compat.h @@ -18,6 +18,11 @@ #include <glib.h> +/* GLIB version compatibility flags */ +#if !GLIB_CHECK_VERSION(2, 26, 0) +#define G_TIME_SPAN_SECOND (G_GINT64_CONSTANT(1000000)) +#endif + #if !GLIB_CHECK_VERSION(2, 14, 0) static inline guint g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) @@ -26,6 +31,20 @@ static inline guint g_timeout_add_seconds(guint interval, GSourceFunc function, } #endif +#if !GLIB_CHECK_VERSION(2, 28, 0) +static inline gint64 g_get_monotonic_time(void) +{ + /* g_get_monotonic_time() is best-effort so we can use the wall clock as a + * fallback. + */ + + GTimeVal time; + g_get_current_time(&time); + + return time.tv_sec * G_TIME_SPAN_SECOND + time.tv_usec; +} +#endif + #ifdef _WIN32 /* * g_poll has a problem on Windows when using diff --git a/include/hw/hotplug.h b/include/hw/hotplug.h index a6533cb0b1..050d2f0530 100644 --- a/include/hw/hotplug.h +++ b/include/hw/hotplug.h @@ -47,7 +47,12 @@ typedef void (*hotplug_fn)(HotplugHandler *plug_handler, * * @parent: Opaque parent interface. * @plug: plug callback. + * @unplug_request: unplug request callback. + * Used as a means to initiate device unplug for devices that + * require asynchronous unplug handling. * @unplug: unplug callback. + * Used for device removal with devices that implement + * asynchronous and synchronous (suprise) removal. */ typedef struct HotplugHandlerClass { /* <private> */ @@ -55,6 +60,7 @@ typedef struct HotplugHandlerClass { /* <public> */ hotplug_fn plug; + hotplug_fn unplug_request; hotplug_fn unplug; } HotplugHandlerClass; @@ -68,9 +74,17 @@ void hotplug_handler_plug(HotplugHandler *plug_handler, Error **errp); /** + * hotplug_handler_unplug_request: + * + * Calls #HotplugHandlerClass.unplug_request callback of @plug_handler. + */ +void hotplug_handler_unplug_request(HotplugHandler *plug_handler, + DeviceState *plugged_dev, + Error **errp); +/** * hotplug_handler_unplug: * - * Call #HotplugHandlerClass.unplug callback of @plug_handler. + * Calls #HotplugHandlerClass.unplug callback of @plug_handler. */ void hotplug_handler_unplug(HotplugHandler *plug_handler, DeviceState *plugged_dev, diff --git a/include/hw/pci/pcie.h b/include/hw/pci/pcie.h index d139d588f6..b48a7a2c5a 100644 --- a/include/hw/pci/pcie.h +++ b/include/hw/pci/pcie.h @@ -128,6 +128,6 @@ extern const VMStateDescription vmstate_pcie_device; void pcie_cap_slot_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp); -void pcie_cap_slot_hot_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, - Error **errp); +void pcie_cap_slot_hot_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp); #endif /* QEMU_PCIE_H */ diff --git a/include/hw/pci/shpc.h b/include/hw/pci/shpc.h index eef1a1ad6e..025bc5b268 100644 --- a/include/hw/pci/shpc.h +++ b/include/hw/pci/shpc.h @@ -46,8 +46,8 @@ void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int len); void shpc_device_hotplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp); -void shpc_device_hot_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, - Error **errp); +void shpc_device_hot_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp); extern VMStateInfo shpc_vmstate_info; #define SHPC_VMSTATE(_field, _type) \ diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h index 178fee2ef6..1fca75c591 100644 --- a/include/hw/qdev-core.h +++ b/include/hw/qdev-core.h @@ -126,7 +126,6 @@ typedef struct DeviceClass { /* Private to qdev / bus. */ qdev_initfn init; /* TODO remove, once users are converted to realize */ - qdev_event unplug; qdev_event exit; /* TODO remove, once users are converted to unrealize */ const char *bus_type; } DeviceClass; @@ -210,7 +209,6 @@ struct BusState { Object obj; DeviceState *parent; const char *name; - int allow_hotplug; HotplugHandler *hotplug_handler; int max_index; bool realized; @@ -232,7 +230,7 @@ struct Property { struct PropertyInfo { const char *name; - const char *legacy_name; + const char *description; const char **enum_table; int (*print)(DeviceState *dev, Property *prop, char *dest, size_t len); ObjectPropertyAccessor *get; @@ -264,7 +262,8 @@ void qdev_init_nofail(DeviceState *dev); void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, int required_for_version); void qdev_unplug(DeviceState *dev, Error **errp); -int qdev_simple_unplug_cb(DeviceState *dev); +void qdev_simple_device_unplug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp); void qdev_machine_creation_done(void); bool qdev_machine_modified(void); @@ -361,11 +360,13 @@ extern int qdev_hotplug; char *qdev_get_dev_path(DeviceState *dev); -static inline void qbus_set_hotplug_handler(BusState *bus, DeviceState *handler, - Error **errp) +void qbus_set_hotplug_handler(BusState *bus, DeviceState *handler, + Error **errp); + +void qbus_set_bus_hotplug_handler(BusState *bus, Error **errp); + +static inline bool qbus_is_hotpluggable(BusState *bus) { - object_property_set_link(OBJECT(bus), OBJECT(handler), - QDEV_HOTPLUG_HANDLER_PROPERTY, errp); - bus->allow_hotplug = 1; + return bus->hotplug_handler; } #endif diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h index b61bedb28c..caaa3201ce 100644 --- a/include/hw/scsi/scsi.h +++ b/include/hw/scsi/scsi.h @@ -146,8 +146,6 @@ struct SCSIBusInfo { void (*transfer_data)(SCSIRequest *req, uint32_t arg); void (*complete)(SCSIRequest *req, uint32_t arg, size_t resid); void (*cancel)(SCSIRequest *req); - void (*hotplug)(SCSIBus *bus, SCSIDevice *dev); - void (*hot_unplug)(SCSIBus *bus, SCSIDevice *dev); void (*change)(SCSIBus *bus, SCSIDevice *dev, SCSISense sense); QEMUSGList *(*get_sg_list)(SCSIRequest *req); diff --git a/include/migration/qemu-file.h b/include/migration/qemu-file.h index c90f5298ab..401676bf4d 100644 --- a/include/migration/qemu-file.h +++ b/include/migration/qemu-file.h @@ -25,6 +25,8 @@ #define QEMU_FILE_H 1 #include "exec/cpu-common.h" +#include <stdint.h> + /* This function writes a chunk of data to a file at the given position. * The pos argument can be ignored if the file is only being used for * streaming. The handler should try to write all of the data it can. @@ -94,11 +96,19 @@ typedef struct QEMUFileOps { QEMURamSaveFunc *save_page; } QEMUFileOps; +struct QEMUSizedBuffer { + struct iovec *iov; + size_t n_iov; + size_t size; /* total allocated size in all iov's */ + size_t used; /* number of used bytes */ +}; + QEMUFile *qemu_fopen_ops(void *opaque, const QEMUFileOps *ops); QEMUFile *qemu_fopen(const char *filename, const char *mode); QEMUFile *qemu_fdopen(int fd, const char *mode); QEMUFile *qemu_fopen_socket(int fd, const char *mode); QEMUFile *qemu_popen_cmd(const char *command, const char *mode); +QEMUFile *qemu_bufopen(const char *mode, QEMUSizedBuffer *input); int qemu_get_fd(QEMUFile *f); int qemu_fclose(QEMUFile *f); int64_t qemu_ftell(QEMUFile *f); @@ -110,6 +120,23 @@ void qemu_put_byte(QEMUFile *f, int v); */ void qemu_put_buffer_async(QEMUFile *f, const uint8_t *buf, int size); bool qemu_file_mode_is_not_valid(const char *mode); +bool qemu_file_is_writable(QEMUFile *f); + +QEMUSizedBuffer *qsb_create(const uint8_t *buffer, size_t len); +QEMUSizedBuffer *qsb_clone(const QEMUSizedBuffer *); +void qsb_free(QEMUSizedBuffer *); +size_t qsb_set_length(QEMUSizedBuffer *qsb, size_t length); +size_t qsb_get_length(const QEMUSizedBuffer *qsb); +ssize_t qsb_get_buffer(const QEMUSizedBuffer *, off_t start, size_t count, + uint8_t *buf); +ssize_t qsb_write_at(QEMUSizedBuffer *qsb, const uint8_t *buf, + off_t pos, size_t count); + + +/* + * For use on files opened with qemu_bufopen + */ +const QEMUSizedBuffer *qemu_buf_get(QEMUFile *f); static inline void qemu_put_ubyte(QEMUFile *f, unsigned int v) { diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index 9a001bd28f..e45fc49cb1 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -484,6 +484,17 @@ extern const VMStateInfo vmstate_info_bitmap; .start = (_start), \ } +#define VMSTATE_VBUFFER_ALLOC_UINT32(_field, _state, _version, _test, _start, _field_size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .info = &vmstate_info_buffer, \ + .flags = VMS_VBUFFER|VMS_POINTER|VMS_ALLOC, \ + .offset = offsetof(_state, _field), \ + .start = (_start), \ +} + #define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \ .name = (stringify(_field)), \ .version_id = (_version), \ diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index 04df51b6fc..168a4086d8 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -71,6 +71,7 @@ typedef struct SSIBus SSIBus; typedef struct EventNotifier EventNotifier; typedef struct VirtIODevice VirtIODevice; typedef struct QEMUSGList QEMUSGList; +typedef struct QEMUSizedBuffer QEMUSizedBuffer; typedef struct SHPCDevice SHPCDevice; typedef struct FWCfgState FWCfgState; typedef struct PcGuestInfo PcGuestInfo; diff --git a/include/qom/object.h b/include/qom/object.h index 8a05a81a99..89c3092967 100644 --- a/include/qom/object.h +++ b/include/qom/object.h @@ -338,6 +338,7 @@ typedef struct ObjectProperty { gchar *name; gchar *type; + gchar *description; ObjectPropertyAccessor *get; ObjectPropertyAccessor *set; ObjectPropertyResolve *resolve; @@ -1275,6 +1276,19 @@ void object_property_add_alias(Object *obj, const char *name, Error **errp); /** + * object_property_set_description: + * @obj: the object owning the property + * @name: the name of the property + * @description: the description of the property on the object + * @errp: if an error occurs, a pointer to an area to store the error + * + * Set an object property's description. + * + */ +void object_property_set_description(Object *obj, const char *name, + const char *description, Error **errp); + +/** * object_child_foreach: * @obj: the object whose children will be navigated * @fn: the iterator function to be called diff --git a/migration.c b/migration.c index 8d675b31a1..c49a05a165 100644 --- a/migration.c +++ b/migration.c @@ -103,7 +103,6 @@ static void process_incoming_migration_co(void *opaque) } qemu_announce_self(); - bdrv_clear_incoming_migration_all(); /* Make sure all file formats flush their mutable metadata */ bdrv_invalidate_cache_all(&local_err); if (local_err) { @@ -972,6 +972,12 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, exp->ctx = bdrv_get_aio_context(bs); bdrv_ref(bs); bdrv_add_aio_context_notifier(bs, bs_aio_attached, bs_aio_detach, exp); + /* + * NBD exports are used for non-shared storage migration. Make sure + * that BDRV_O_INCOMING is cleared and the image is ready for write + * access since the export could be available before migration handover. + */ + bdrv_invalidate_cache(bs, NULL); return exp; } diff --git a/qapi-schema.json b/qapi-schema.json index 4f0d7e3250..24379ab3af 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1615,11 +1615,13 @@ # # @name: the name of the property # @type: the typename of the property +# @description: #optional if specified, the description of the property. +# (since 2.2) # # Since: 1.2 ## { 'type': 'DevicePropertyInfo', - 'data': { 'name': 'str', 'type': 'str' } } + 'data': { 'name': 'str', 'type': 'str', '*description': 'str' } } ## # @device-list-properties: diff --git a/qdev-monitor.c b/qdev-monitor.c index 5ec66067f5..fac7d179fe 100644 --- a/qdev-monitor.c +++ b/qdev-monitor.c @@ -213,9 +213,14 @@ int qdev_device_help(QemuOpts *opts) } for (prop = prop_list; prop; prop = prop->next) { - error_printf("%s.%s=%s\n", driver, + error_printf("%s.%s=%s", driver, prop->value->name, prop->value->type); + if (prop->value->has_description) { + error_printf(" (%s)\n", prop->value->description); + } else { + error_printf("\n"); + } } qapi_free_DevicePropertyInfoList(prop_list); @@ -487,7 +492,8 @@ DeviceState *qdev_device_add(QemuOpts *opts) } dc = DEVICE_CLASS(oc); - if (dc->cannot_instantiate_with_device_add_yet) { + if (dc->cannot_instantiate_with_device_add_yet || + (qdev_hotplug && !dc->hotpluggable)) { qerror_report(QERR_INVALID_PARAMETER_VALUE, "driver", "pluggable device type"); return NULL; @@ -515,7 +521,7 @@ DeviceState *qdev_device_add(QemuOpts *opts) return NULL; } } - if (qdev_hotplug && bus && !bus->allow_hotplug) { + if (qdev_hotplug && bus && !qbus_is_hotpluggable(bus)) { qerror_report(QERR_BUS_NO_HOTPLUG, bus->name); return NULL; } @@ -685,15 +691,20 @@ int do_device_add(Monitor *mon, const QDict *qdict, QObject **ret_data) void qmp_device_del(const char *id, Error **errp) { - DeviceState *dev; + Object *obj; + char *root_path = object_get_canonical_path(qdev_get_peripheral()); + char *path = g_strdup_printf("%s/%s", root_path, id); - dev = qdev_find_recursive(sysbus_get_default(), id); - if (!dev) { + g_free(root_path); + obj = object_resolve_path_type(path, TYPE_DEVICE, NULL); + g_free(path); + + if (!obj) { error_set(errp, QERR_DEVICE_NOT_FOUND, id); return; } - qdev_unplug(dev, errp); + qdev_unplug(DEVICE(obj), errp); } void qdev_machine_init(void) diff --git a/qemu-file-stdio.c b/qemu-file-stdio.c new file mode 100644 index 0000000000..285068b303 --- /dev/null +++ b/qemu-file-stdio.c @@ -0,0 +1,194 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "block/coroutine.h" +#include "migration/qemu-file.h" + +typedef struct QEMUFileStdio { + FILE *stdio_file; + QEMUFile *file; +} QEMUFileStdio; + +static int stdio_get_fd(void *opaque) +{ + QEMUFileStdio *s = opaque; + + return fileno(s->stdio_file); +} + +static int stdio_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, + int size) +{ + QEMUFileStdio *s = opaque; + int res; + + res = fwrite(buf, 1, size, s->stdio_file); + + if (res != size) { + return -errno; + } + return res; +} + +static int stdio_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) +{ + QEMUFileStdio *s = opaque; + FILE *fp = s->stdio_file; + int bytes; + + for (;;) { + clearerr(fp); + bytes = fread(buf, 1, size, fp); + if (bytes != 0 || !ferror(fp)) { + break; + } + if (errno == EAGAIN) { + yield_until_fd_readable(fileno(fp)); + } else if (errno != EINTR) { + break; + } + } + return bytes; +} + +static int stdio_pclose(void *opaque) +{ + QEMUFileStdio *s = opaque; + int ret; + ret = pclose(s->stdio_file); + if (ret == -1) { + ret = -errno; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) { + /* close succeeded, but non-zero exit code: */ + ret = -EIO; /* fake errno value */ + } + g_free(s); + return ret; +} + +static int stdio_fclose(void *opaque) +{ + QEMUFileStdio *s = opaque; + int ret = 0; + + if (qemu_file_is_writable(s->file)) { + int fd = fileno(s->stdio_file); + struct stat st; + + ret = fstat(fd, &st); + if (ret == 0 && S_ISREG(st.st_mode)) { + /* + * If the file handle is a regular file make sure the + * data is flushed to disk before signaling success. + */ + ret = fsync(fd); + if (ret != 0) { + ret = -errno; + return ret; + } + } + } + if (fclose(s->stdio_file) == EOF) { + ret = -errno; + } + g_free(s); + return ret; +} + +static const QEMUFileOps stdio_pipe_read_ops = { + .get_fd = stdio_get_fd, + .get_buffer = stdio_get_buffer, + .close = stdio_pclose +}; + +static const QEMUFileOps stdio_pipe_write_ops = { + .get_fd = stdio_get_fd, + .put_buffer = stdio_put_buffer, + .close = stdio_pclose +}; + +QEMUFile *qemu_popen_cmd(const char *command, const char *mode) +{ + FILE *stdio_file; + QEMUFileStdio *s; + + if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || mode[1] != 0) { + fprintf(stderr, "qemu_popen: Argument validity check failed\n"); + return NULL; + } + + stdio_file = popen(command, mode); + if (stdio_file == NULL) { + return NULL; + } + + s = g_malloc0(sizeof(QEMUFileStdio)); + + s->stdio_file = stdio_file; + + if (mode[0] == 'r') { + s->file = qemu_fopen_ops(s, &stdio_pipe_read_ops); + } else { + s->file = qemu_fopen_ops(s, &stdio_pipe_write_ops); + } + return s->file; +} + +static const QEMUFileOps stdio_file_read_ops = { + .get_fd = stdio_get_fd, + .get_buffer = stdio_get_buffer, + .close = stdio_fclose +}; + +static const QEMUFileOps stdio_file_write_ops = { + .get_fd = stdio_get_fd, + .put_buffer = stdio_put_buffer, + .close = stdio_fclose +}; + +QEMUFile *qemu_fopen(const char *filename, const char *mode) +{ + QEMUFileStdio *s; + + if (qemu_file_mode_is_not_valid(mode)) { + return NULL; + } + + s = g_malloc0(sizeof(QEMUFileStdio)); + + s->stdio_file = fopen(filename, mode); + if (!s->stdio_file) { + goto fail; + } + + if (mode[0] == 'w') { + s->file = qemu_fopen_ops(s, &stdio_file_write_ops); + } else { + s->file = qemu_fopen_ops(s, &stdio_file_read_ops); + } + return s->file; +fail: + g_free(s); + return NULL; +} diff --git a/qemu-file-unix.c b/qemu-file-unix.c new file mode 100644 index 0000000000..9682396d97 --- /dev/null +++ b/qemu-file-unix.c @@ -0,0 +1,223 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "qemu/iov.h" +#include "qemu/sockets.h" +#include "block/coroutine.h" +#include "migration/qemu-file.h" + +typedef struct QEMUFileSocket { + int fd; + QEMUFile *file; +} QEMUFileSocket; + +static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, + int64_t pos) +{ + QEMUFileSocket *s = opaque; + ssize_t len; + ssize_t size = iov_size(iov, iovcnt); + + len = iov_send(s->fd, iov, iovcnt, 0, size); + if (len < size) { + len = -socket_error(); + } + return len; +} + +static int socket_get_fd(void *opaque) +{ + QEMUFileSocket *s = opaque; + + return s->fd; +} + +static int socket_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) +{ + QEMUFileSocket *s = opaque; + ssize_t len; + + for (;;) { + len = qemu_recv(s->fd, buf, size, 0); + if (len != -1) { + break; + } + if (socket_error() == EAGAIN) { + yield_until_fd_readable(s->fd); + } else if (socket_error() != EINTR) { + break; + } + } + + if (len == -1) { + len = -socket_error(); + } + return len; +} + +static int socket_close(void *opaque) +{ + QEMUFileSocket *s = opaque; + closesocket(s->fd); + g_free(s); + return 0; +} + +static ssize_t unix_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, + int64_t pos) +{ + QEMUFileSocket *s = opaque; + ssize_t len, offset; + ssize_t size = iov_size(iov, iovcnt); + ssize_t total = 0; + + assert(iovcnt > 0); + offset = 0; + while (size > 0) { + /* Find the next start position; skip all full-sized vector elements */ + while (offset >= iov[0].iov_len) { + offset -= iov[0].iov_len; + iov++, iovcnt--; + } + + /* skip `offset' bytes from the (now) first element, undo it on exit */ + assert(iovcnt > 0); + iov[0].iov_base += offset; + iov[0].iov_len -= offset; + + do { + len = writev(s->fd, iov, iovcnt); + } while (len == -1 && errno == EINTR); + if (len == -1) { + return -errno; + } + + /* Undo the changes above */ + iov[0].iov_base -= offset; + iov[0].iov_len += offset; + + /* Prepare for the next iteration */ + offset += len; + total += len; + size -= len; + } + + return total; +} + +static int unix_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) +{ + QEMUFileSocket *s = opaque; + ssize_t len; + + for (;;) { + len = read(s->fd, buf, size); + if (len != -1) { + break; + } + if (errno == EAGAIN) { + yield_until_fd_readable(s->fd); + } else if (errno != EINTR) { + break; + } + } + + if (len == -1) { + len = -errno; + } + return len; +} + +static int unix_close(void *opaque) +{ + QEMUFileSocket *s = opaque; + close(s->fd); + g_free(s); + return 0; +} + +static const QEMUFileOps unix_read_ops = { + .get_fd = socket_get_fd, + .get_buffer = unix_get_buffer, + .close = unix_close +}; + +static const QEMUFileOps unix_write_ops = { + .get_fd = socket_get_fd, + .writev_buffer = unix_writev_buffer, + .close = unix_close +}; + +QEMUFile *qemu_fdopen(int fd, const char *mode) +{ + QEMUFileSocket *s; + + if (mode == NULL || + (mode[0] != 'r' && mode[0] != 'w') || + mode[1] != 'b' || mode[2] != 0) { + fprintf(stderr, "qemu_fdopen: Argument validity check failed\n"); + return NULL; + } + + s = g_malloc0(sizeof(QEMUFileSocket)); + s->fd = fd; + + if (mode[0] == 'r') { + s->file = qemu_fopen_ops(s, &unix_read_ops); + } else { + s->file = qemu_fopen_ops(s, &unix_write_ops); + } + return s->file; +} + +static const QEMUFileOps socket_read_ops = { + .get_fd = socket_get_fd, + .get_buffer = socket_get_buffer, + .close = socket_close +}; + +static const QEMUFileOps socket_write_ops = { + .get_fd = socket_get_fd, + .writev_buffer = socket_writev_buffer, + .close = socket_close +}; + +QEMUFile *qemu_fopen_socket(int fd, const char *mode) +{ + QEMUFileSocket *s; + + if (qemu_file_mode_is_not_valid(mode)) { + return NULL; + } + + s = g_malloc0(sizeof(QEMUFileSocket)); + s->fd = fd; + if (mode[0] == 'w') { + qemu_set_block(s->fd); + s->file = qemu_fopen_ops(s, &socket_write_ops); + } else { + s->file = qemu_fopen_ops(s, &socket_read_ops); + } + return s->file; +} diff --git a/qemu-file.c b/qemu-file.c index a8e39127f2..f938e36fe8 100644 --- a/qemu-file.c +++ b/qemu-file.c @@ -1,3 +1,26 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "qemu-common.h" #include "qemu/iov.h" #include "qemu/sockets.h" @@ -28,324 +51,6 @@ struct QEMUFile { int last_error; }; -typedef struct QEMUFileStdio { - FILE *stdio_file; - QEMUFile *file; -} QEMUFileStdio; - -typedef struct QEMUFileSocket { - int fd; - QEMUFile *file; -} QEMUFileSocket; - -static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, - int64_t pos) -{ - QEMUFileSocket *s = opaque; - ssize_t len; - ssize_t size = iov_size(iov, iovcnt); - - len = iov_send(s->fd, iov, iovcnt, 0, size); - if (len < size) { - len = -socket_error(); - } - return len; -} - -static int socket_get_fd(void *opaque) -{ - QEMUFileSocket *s = opaque; - - return s->fd; -} - -static int socket_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) -{ - QEMUFileSocket *s = opaque; - ssize_t len; - - for (;;) { - len = qemu_recv(s->fd, buf, size, 0); - if (len != -1) { - break; - } - if (socket_error() == EAGAIN) { - yield_until_fd_readable(s->fd); - } else if (socket_error() != EINTR) { - break; - } - } - - if (len == -1) { - len = -socket_error(); - } - return len; -} - -static int socket_close(void *opaque) -{ - QEMUFileSocket *s = opaque; - closesocket(s->fd); - g_free(s); - return 0; -} - -static int stdio_get_fd(void *opaque) -{ - QEMUFileStdio *s = opaque; - - return fileno(s->stdio_file); -} - -static int stdio_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, - int size) -{ - QEMUFileStdio *s = opaque; - int res; - - res = fwrite(buf, 1, size, s->stdio_file); - - if (res != size) { - return -errno; - } - return res; -} - -static int stdio_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) -{ - QEMUFileStdio *s = opaque; - FILE *fp = s->stdio_file; - int bytes; - - for (;;) { - clearerr(fp); - bytes = fread(buf, 1, size, fp); - if (bytes != 0 || !ferror(fp)) { - break; - } - if (errno == EAGAIN) { - yield_until_fd_readable(fileno(fp)); - } else if (errno != EINTR) { - break; - } - } - return bytes; -} - -static int stdio_pclose(void *opaque) -{ - QEMUFileStdio *s = opaque; - int ret; - ret = pclose(s->stdio_file); - if (ret == -1) { - ret = -errno; - } else if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) { - /* close succeeded, but non-zero exit code: */ - ret = -EIO; /* fake errno value */ - } - g_free(s); - return ret; -} - -static int stdio_fclose(void *opaque) -{ - QEMUFileStdio *s = opaque; - int ret = 0; - - if (s->file->ops->put_buffer || s->file->ops->writev_buffer) { - int fd = fileno(s->stdio_file); - struct stat st; - - ret = fstat(fd, &st); - if (ret == 0 && S_ISREG(st.st_mode)) { - /* - * If the file handle is a regular file make sure the - * data is flushed to disk before signaling success. - */ - ret = fsync(fd); - if (ret != 0) { - ret = -errno; - return ret; - } - } - } - if (fclose(s->stdio_file) == EOF) { - ret = -errno; - } - g_free(s); - return ret; -} - -static const QEMUFileOps stdio_pipe_read_ops = { - .get_fd = stdio_get_fd, - .get_buffer = stdio_get_buffer, - .close = stdio_pclose -}; - -static const QEMUFileOps stdio_pipe_write_ops = { - .get_fd = stdio_get_fd, - .put_buffer = stdio_put_buffer, - .close = stdio_pclose -}; - -QEMUFile *qemu_popen_cmd(const char *command, const char *mode) -{ - FILE *stdio_file; - QEMUFileStdio *s; - - if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || mode[1] != 0) { - fprintf(stderr, "qemu_popen: Argument validity check failed\n"); - return NULL; - } - - stdio_file = popen(command, mode); - if (stdio_file == NULL) { - return NULL; - } - - s = g_malloc0(sizeof(QEMUFileStdio)); - - s->stdio_file = stdio_file; - - if (mode[0] == 'r') { - s->file = qemu_fopen_ops(s, &stdio_pipe_read_ops); - } else { - s->file = qemu_fopen_ops(s, &stdio_pipe_write_ops); - } - return s->file; -} - -static const QEMUFileOps stdio_file_read_ops = { - .get_fd = stdio_get_fd, - .get_buffer = stdio_get_buffer, - .close = stdio_fclose -}; - -static const QEMUFileOps stdio_file_write_ops = { - .get_fd = stdio_get_fd, - .put_buffer = stdio_put_buffer, - .close = stdio_fclose -}; - -static ssize_t unix_writev_buffer(void *opaque, struct iovec *iov, int iovcnt, - int64_t pos) -{ - QEMUFileSocket *s = opaque; - ssize_t len, offset; - ssize_t size = iov_size(iov, iovcnt); - ssize_t total = 0; - - assert(iovcnt > 0); - offset = 0; - while (size > 0) { - /* Find the next start position; skip all full-sized vector elements */ - while (offset >= iov[0].iov_len) { - offset -= iov[0].iov_len; - iov++, iovcnt--; - } - - /* skip `offset' bytes from the (now) first element, undo it on exit */ - assert(iovcnt > 0); - iov[0].iov_base += offset; - iov[0].iov_len -= offset; - - do { - len = writev(s->fd, iov, iovcnt); - } while (len == -1 && errno == EINTR); - if (len == -1) { - return -errno; - } - - /* Undo the changes above */ - iov[0].iov_base -= offset; - iov[0].iov_len += offset; - - /* Prepare for the next iteration */ - offset += len; - total += len; - size -= len; - } - - return total; -} - -static int unix_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) -{ - QEMUFileSocket *s = opaque; - ssize_t len; - - for (;;) { - len = read(s->fd, buf, size); - if (len != -1) { - break; - } - if (errno == EAGAIN) { - yield_until_fd_readable(s->fd); - } else if (errno != EINTR) { - break; - } - } - - if (len == -1) { - len = -errno; - } - return len; -} - -static int unix_close(void *opaque) -{ - QEMUFileSocket *s = opaque; - close(s->fd); - g_free(s); - return 0; -} - -static const QEMUFileOps unix_read_ops = { - .get_fd = socket_get_fd, - .get_buffer = unix_get_buffer, - .close = unix_close -}; - -static const QEMUFileOps unix_write_ops = { - .get_fd = socket_get_fd, - .writev_buffer = unix_writev_buffer, - .close = unix_close -}; - -QEMUFile *qemu_fdopen(int fd, const char *mode) -{ - QEMUFileSocket *s; - - if (mode == NULL || - (mode[0] != 'r' && mode[0] != 'w') || - mode[1] != 'b' || mode[2] != 0) { - fprintf(stderr, "qemu_fdopen: Argument validity check failed\n"); - return NULL; - } - - s = g_malloc0(sizeof(QEMUFileSocket)); - s->fd = fd; - - if (mode[0] == 'r') { - s->file = qemu_fopen_ops(s, &unix_read_ops); - } else { - s->file = qemu_fopen_ops(s, &unix_write_ops); - } - return s->file; -} - -static const QEMUFileOps socket_read_ops = { - .get_fd = socket_get_fd, - .get_buffer = socket_get_buffer, - .close = socket_close -}; - -static const QEMUFileOps socket_write_ops = { - .get_fd = socket_get_fd, - .writev_buffer = socket_writev_buffer, - .close = socket_close -}; - bool qemu_file_mode_is_not_valid(const char *mode) { if (mode == NULL || @@ -358,51 +63,6 @@ bool qemu_file_mode_is_not_valid(const char *mode) return false; } -QEMUFile *qemu_fopen_socket(int fd, const char *mode) -{ - QEMUFileSocket *s; - - if (qemu_file_mode_is_not_valid(mode)) { - return NULL; - } - - s = g_malloc0(sizeof(QEMUFileSocket)); - s->fd = fd; - if (mode[0] == 'w') { - qemu_set_block(s->fd); - s->file = qemu_fopen_ops(s, &socket_write_ops); - } else { - s->file = qemu_fopen_ops(s, &socket_read_ops); - } - return s->file; -} - -QEMUFile *qemu_fopen(const char *filename, const char *mode) -{ - QEMUFileStdio *s; - - if (qemu_file_mode_is_not_valid(mode)) { - return NULL; - } - - s = g_malloc0(sizeof(QEMUFileStdio)); - - s->stdio_file = fopen(filename, mode); - if (!s->stdio_file) { - goto fail; - } - - if (mode[0] == 'w') { - s->file = qemu_fopen_ops(s, &stdio_file_write_ops); - } else { - s->file = qemu_fopen_ops(s, &stdio_file_read_ops); - } - return s->file; -fail: - g_free(s); - return NULL; -} - QEMUFile *qemu_fopen_ops(void *opaque, const QEMUFileOps *ops) { QEMUFile *f; @@ -433,7 +93,7 @@ void qemu_file_set_error(QEMUFile *f, int ret) } } -static inline bool qemu_file_is_writable(QEMUFile *f) +bool qemu_file_is_writable(QEMUFile *f) { return f->ops->writev_buffer || f->ops->put_buffer; } @@ -878,3 +538,458 @@ uint64_t qemu_get_be64(QEMUFile *f) v |= qemu_get_be32(f); return v; } + +#define QSB_CHUNK_SIZE (1 << 10) +#define QSB_MAX_CHUNK_SIZE (16 * QSB_CHUNK_SIZE) + +/** + * Create a QEMUSizedBuffer + * This type of buffer uses scatter-gather lists internally and + * can grow to any size. Any data array in the scatter-gather list + * can hold different amount of bytes. + * + * @buffer: Optional buffer to copy into the QSB + * @len: size of initial buffer; if @buffer is given, buffer must + * hold at least len bytes + * + * Returns a pointer to a QEMUSizedBuffer or NULL on allocation failure + */ +QEMUSizedBuffer *qsb_create(const uint8_t *buffer, size_t len) +{ + QEMUSizedBuffer *qsb; + size_t alloc_len, num_chunks, i, to_copy; + size_t chunk_size = (len > QSB_MAX_CHUNK_SIZE) + ? QSB_MAX_CHUNK_SIZE + : QSB_CHUNK_SIZE; + + num_chunks = DIV_ROUND_UP(len ? len : QSB_CHUNK_SIZE, chunk_size); + alloc_len = num_chunks * chunk_size; + + qsb = g_try_new0(QEMUSizedBuffer, 1); + if (!qsb) { + return NULL; + } + + qsb->iov = g_try_new0(struct iovec, num_chunks); + if (!qsb->iov) { + g_free(qsb); + return NULL; + } + + qsb->n_iov = num_chunks; + + for (i = 0; i < num_chunks; i++) { + qsb->iov[i].iov_base = g_try_malloc0(chunk_size); + if (!qsb->iov[i].iov_base) { + /* qsb_free is safe since g_free can cope with NULL */ + qsb_free(qsb); + return NULL; + } + + qsb->iov[i].iov_len = chunk_size; + if (buffer) { + to_copy = (len - qsb->used) > chunk_size + ? chunk_size : (len - qsb->used); + memcpy(qsb->iov[i].iov_base, &buffer[qsb->used], to_copy); + qsb->used += to_copy; + } + } + + qsb->size = alloc_len; + + return qsb; +} + +/** + * Free the QEMUSizedBuffer + * + * @qsb: The QEMUSizedBuffer to free + */ +void qsb_free(QEMUSizedBuffer *qsb) +{ + size_t i; + + if (!qsb) { + return; + } + + for (i = 0; i < qsb->n_iov; i++) { + g_free(qsb->iov[i].iov_base); + } + g_free(qsb->iov); + g_free(qsb); +} + +/** + * Get the number of used bytes in the QEMUSizedBuffer + * + * @qsb: A QEMUSizedBuffer + * + * Returns the number of bytes currently used in this buffer + */ +size_t qsb_get_length(const QEMUSizedBuffer *qsb) +{ + return qsb->used; +} + +/** + * Set the length of the buffer; the primary usage of this + * function is to truncate the number of used bytes in the buffer. + * The size will not be extended beyond the current number of + * allocated bytes in the QEMUSizedBuffer. + * + * @qsb: A QEMUSizedBuffer + * @new_len: The new length of bytes in the buffer + * + * Returns the number of bytes the buffer was truncated or extended + * to. + */ +size_t qsb_set_length(QEMUSizedBuffer *qsb, size_t new_len) +{ + if (new_len <= qsb->size) { + qsb->used = new_len; + } else { + qsb->used = qsb->size; + } + return qsb->used; +} + +/** + * Get the iovec that holds the data for a given position @pos. + * + * @qsb: A QEMUSizedBuffer + * @pos: The index of a byte in the buffer + * @d_off: Pointer to an offset that this function will indicate + * at what position within the returned iovec the byte + * is to be found + * + * Returns the index of the iovec that holds the byte at the given + * index @pos in the byte stream; a negative number if the iovec + * for the given position @pos does not exist. + */ +static ssize_t qsb_get_iovec(const QEMUSizedBuffer *qsb, + off_t pos, off_t *d_off) +{ + ssize_t i; + off_t curr = 0; + + if (pos > qsb->used) { + return -1; + } + + for (i = 0; i < qsb->n_iov; i++) { + if (curr + qsb->iov[i].iov_len > pos) { + *d_off = pos - curr; + return i; + } + curr += qsb->iov[i].iov_len; + } + return -1; +} + +/* + * Convert the QEMUSizedBuffer into a flat buffer. + * + * Note: If at all possible, try to avoid this function since it + * may unnecessarily copy memory around. + * + * @qsb: pointer to QEMUSizedBuffer + * @start: offset to start at + * @count: number of bytes to copy + * @buf: a pointer to a buffer to write into (at least @count bytes) + * + * Returns the number of bytes copied into the output buffer + */ +ssize_t qsb_get_buffer(const QEMUSizedBuffer *qsb, off_t start, + size_t count, uint8_t *buffer) +{ + const struct iovec *iov; + size_t to_copy, all_copy; + ssize_t index; + off_t s_off; + off_t d_off = 0; + char *s; + + if (start > qsb->used) { + return 0; + } + + all_copy = qsb->used - start; + if (all_copy > count) { + all_copy = count; + } else { + count = all_copy; + } + + index = qsb_get_iovec(qsb, start, &s_off); + if (index < 0) { + return 0; + } + + while (all_copy > 0) { + iov = &qsb->iov[index]; + + s = iov->iov_base; + + to_copy = iov->iov_len - s_off; + if (to_copy > all_copy) { + to_copy = all_copy; + } + memcpy(&buffer[d_off], &s[s_off], to_copy); + + d_off += to_copy; + all_copy -= to_copy; + + s_off = 0; + index++; + } + + return count; +} + +/** + * Grow the QEMUSizedBuffer to the given size and allocate + * memory for it. + * + * @qsb: A QEMUSizedBuffer + * @new_size: The new size of the buffer + * + * Return: + * a negative error code in case of memory allocation failure + * or + * the new size of the buffer. The returned size may be greater or equal + * to @new_size. + */ +static ssize_t qsb_grow(QEMUSizedBuffer *qsb, size_t new_size) +{ + size_t needed_chunks, i; + + if (qsb->size < new_size) { + struct iovec *new_iov; + size_t size_diff = new_size - qsb->size; + size_t chunk_size = (size_diff > QSB_MAX_CHUNK_SIZE) + ? QSB_MAX_CHUNK_SIZE : QSB_CHUNK_SIZE; + + needed_chunks = DIV_ROUND_UP(size_diff, chunk_size); + + new_iov = g_try_new(struct iovec, qsb->n_iov + needed_chunks); + if (new_iov == NULL) { + return -ENOMEM; + } + + /* Allocate new chunks as needed into new_iov */ + for (i = qsb->n_iov; i < qsb->n_iov + needed_chunks; i++) { + new_iov[i].iov_base = g_try_malloc0(chunk_size); + new_iov[i].iov_len = chunk_size; + if (!new_iov[i].iov_base) { + size_t j; + + /* Free previously allocated new chunks */ + for (j = qsb->n_iov; j < i; j++) { + g_free(new_iov[j].iov_base); + } + g_free(new_iov); + + return -ENOMEM; + } + } + + /* + * Now we can't get any allocation errors, copy over to new iov + * and switch. + */ + for (i = 0; i < qsb->n_iov; i++) { + new_iov[i] = qsb->iov[i]; + } + + qsb->n_iov += needed_chunks; + g_free(qsb->iov); + qsb->iov = new_iov; + qsb->size += (needed_chunks * chunk_size); + } + + return qsb->size; +} + +/** + * Write into the QEMUSizedBuffer at a given position and a given + * number of bytes. This function will automatically grow the + * QEMUSizedBuffer. + * + * @qsb: A QEMUSizedBuffer + * @source: A byte array to copy data from + * @pos: The position within the @qsb to write data to + * @size: The number of bytes to copy into the @qsb + * + * Returns @size or a negative error code in case of memory allocation failure, + * or with an invalid 'pos' + */ +ssize_t qsb_write_at(QEMUSizedBuffer *qsb, const uint8_t *source, + off_t pos, size_t count) +{ + ssize_t rc = qsb_grow(qsb, pos + count); + size_t to_copy; + size_t all_copy = count; + const struct iovec *iov; + ssize_t index; + char *dest; + off_t d_off, s_off = 0; + + if (rc < 0) { + return rc; + } + + if (pos + count > qsb->used) { + qsb->used = pos + count; + } + + index = qsb_get_iovec(qsb, pos, &d_off); + if (index < 0) { + return -EINVAL; + } + + while (all_copy > 0) { + iov = &qsb->iov[index]; + + dest = iov->iov_base; + + to_copy = iov->iov_len - d_off; + if (to_copy > all_copy) { + to_copy = all_copy; + } + + memcpy(&dest[d_off], &source[s_off], to_copy); + + s_off += to_copy; + all_copy -= to_copy; + + d_off = 0; + index++; + } + + return count; +} + +/** + * Create a deep copy of the given QEMUSizedBuffer. + * + * @qsb: A QEMUSizedBuffer + * + * Returns a clone of @qsb or NULL on allocation failure + */ +QEMUSizedBuffer *qsb_clone(const QEMUSizedBuffer *qsb) +{ + QEMUSizedBuffer *out = qsb_create(NULL, qsb_get_length(qsb)); + size_t i; + ssize_t res; + off_t pos = 0; + + if (!out) { + return NULL; + } + + for (i = 0; i < qsb->n_iov; i++) { + res = qsb_write_at(out, qsb->iov[i].iov_base, + pos, qsb->iov[i].iov_len); + if (res < 0) { + qsb_free(out); + return NULL; + } + pos += res; + } + + return out; +} + +typedef struct QEMUBuffer { + QEMUSizedBuffer *qsb; + QEMUFile *file; +} QEMUBuffer; + +static int buf_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size) +{ + QEMUBuffer *s = opaque; + ssize_t len = qsb_get_length(s->qsb) - pos; + + if (len <= 0) { + return 0; + } + + if (len > size) { + len = size; + } + return qsb_get_buffer(s->qsb, pos, len, buf); +} + +static int buf_put_buffer(void *opaque, const uint8_t *buf, + int64_t pos, int size) +{ + QEMUBuffer *s = opaque; + + return qsb_write_at(s->qsb, buf, pos, size); +} + +static int buf_close(void *opaque) +{ + QEMUBuffer *s = opaque; + + qsb_free(s->qsb); + + g_free(s); + + return 0; +} + +const QEMUSizedBuffer *qemu_buf_get(QEMUFile *f) +{ + QEMUBuffer *p; + + qemu_fflush(f); + + p = f->opaque; + + return p->qsb; +} + +static const QEMUFileOps buf_read_ops = { + .get_buffer = buf_get_buffer, + .close = buf_close, +}; + +static const QEMUFileOps buf_write_ops = { + .put_buffer = buf_put_buffer, + .close = buf_close, +}; + +QEMUFile *qemu_bufopen(const char *mode, QEMUSizedBuffer *input) +{ + QEMUBuffer *s; + + if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || + mode[1] != '\0') { + error_report("qemu_bufopen: Argument validity check failed"); + return NULL; + } + + s = g_malloc0(sizeof(QEMUBuffer)); + if (mode[0] == 'r') { + s->qsb = input; + } + + if (s->qsb == NULL) { + s->qsb = qsb_create(NULL, 0); + } + if (!s->qsb) { + g_free(s); + error_report("qemu_bufopen: qsb_create failed"); + return NULL; + } + + + if (mode[0] == 'r') { + s->file = qemu_fopen_ops(s, &buf_read_ops); + } else { + s->file = qemu_fopen_ops(s, &buf_write_ops); + } + return s->file; +} @@ -442,7 +442,8 @@ ObjectTypeInfoList *qmp_qom_list_types(bool has_implements, */ static DevicePropertyInfo *make_device_property_info(ObjectClass *klass, const char *name, - const char *default_type) + const char *default_type, + const char *description) { DevicePropertyInfo *info; Property *prop; @@ -465,7 +466,9 @@ static DevicePropertyInfo *make_device_property_info(ObjectClass *klass, info = g_malloc0(sizeof(*info)); info->name = g_strdup(prop->name); - info->type = g_strdup(prop->info->legacy_name ?: prop->info->name); + info->type = g_strdup(prop->info->name); + info->has_description = !!prop->info->description; + info->description = g_strdup(prop->info->description); return info; } klass = object_class_get_parent(klass); @@ -475,6 +478,9 @@ static DevicePropertyInfo *make_device_property_info(ObjectClass *klass, info = g_malloc0(sizeof(*info)); info->name = g_strdup(name); info->type = g_strdup(default_type); + info->has_description = !!description; + info->description = g_strdup(description); + return info; } @@ -521,7 +527,8 @@ DevicePropertyInfoList *qmp_device_list_properties(const char *typename, continue; } - info = make_device_property_info(klass, prop->name, prop->type); + info = make_device_property_info(klass, prop->name, prop->type, + prop->description); if (!info) { continue; } diff --git a/qom/object.c b/qom/object.c index da0919a3dd..a751367e61 100644 --- a/qom/object.c +++ b/qom/object.c @@ -369,6 +369,7 @@ static void object_property_del_all(Object *obj) g_free(prop->name); g_free(prop->type); + g_free(prop->description); g_free(prop); } } @@ -803,6 +804,7 @@ void object_property_del(Object *obj, const char *name, Error **errp) g_free(prop->name); g_free(prop->type); + g_free(prop->description); g_free(prop); } @@ -1010,11 +1012,19 @@ char *object_property_print(Object *obj, const char *name, bool human, Error **errp) { StringOutputVisitor *mo; - char *string; + char *string = NULL; + Error *local_err = NULL; mo = string_output_visitor_new(human); - object_property_get(obj, string_output_get_visitor(mo), name, errp); + object_property_get(obj, string_output_get_visitor(mo), name, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto out; + } + string = string_output_get_string(mo); + +out: string_output_visitor_cleanup(mo); return string; } @@ -1634,6 +1644,7 @@ void object_property_add_alias(Object *obj, const char *name, ObjectProperty *op; ObjectProperty *target_prop; gchar *prop_type; + Error *local_err = NULL; target_prop = object_property_find(target_obj, target_name, errp); if (!target_prop) { @@ -1655,12 +1666,36 @@ void object_property_add_alias(Object *obj, const char *name, property_get_alias, property_set_alias, property_release_alias, - prop, errp); + prop, &local_err); + if (local_err) { + error_propagate(errp, local_err); + g_free(prop); + goto out; + } op->resolve = property_resolve_alias; + object_property_set_description(obj, name, + target_prop->description, + &error_abort); + +out: g_free(prop_type); } +void object_property_set_description(Object *obj, const char *name, + const char *description, Error **errp) +{ + ObjectProperty *op; + + op = object_property_find(obj, name, errp); + if (!op) { + return; + } + + g_free(op->description); + op->description = g_strdup(description); +} + static void object_instance_init(Object *obj) { object_property_add_str(obj, "type", qdev_get_type, NULL, NULL); diff --git a/target-ppc/translate_init.c b/target-ppc/translate_init.c index 65b840da03..33fb4cc9c3 100644 --- a/target-ppc/translate_init.c +++ b/target-ppc/translate_init.c @@ -8044,7 +8044,7 @@ static void powerpc_set_compat(Object *obj, Visitor *v, static PropertyInfo powerpc_compat_propinfo = { .name = "str", - .legacy_name = "powerpc-server-compat", + .description = "compatibility mode, power6/power7/power8", .get = powerpc_get_compat, .set = powerpc_set_compat, }; diff --git a/tests/Makefile b/tests/Makefile index ffa8312eb5..16f0e4c805 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -258,8 +258,8 @@ tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \ $(test-qapi-obj-y) \ libqemuutil.a libqemustub.a tests/test-vmstate$(EXESUF): tests/test-vmstate.o \ - vmstate.o qemu-file.o \ - libqemuutil.a + vmstate.o qemu-file.o qemu-file-unix.o \ + libqemuutil.a libqemustub.a tests/test-qapi-types.c tests/test-qapi-types.h :\ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py @@ -301,6 +301,7 @@ libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o libqos-pc-obj-y += tests/libqos/malloc-pc.o libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o +libqos-usb-obj-y = $(libqos-pc-obj-y) tests/libqos/usb.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o @@ -324,8 +325,8 @@ tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) -tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o -tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o +tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) +tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o $(libqos-pc-obj-y) tests/virtio-scsi-test$(EXESUF): tests/virtio-scsi-test.o tests/virtio-9p-test$(EXESUF): tests/virtio-9p-test.o tests/virtio-serial-test$(EXESUF): tests/virtio-serial-test.o @@ -343,10 +344,10 @@ tests/ac97-test$(EXESUF): tests/ac97-test.o tests/es1370-test$(EXESUF): tests/es1370-test.o tests/intel-hda-test$(EXESUF): tests/intel-hda-test.o tests/ioh3420-test$(EXESUF): tests/ioh3420-test.o -tests/usb-hcd-ohci-test$(EXESUF): tests/usb-hcd-ohci-test.o -tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o -tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-pc-obj-y) -tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o +tests/usb-hcd-ohci-test$(EXESUF): tests/usb-hcd-ohci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y) tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y) tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o libqemuutil.a libqemustub.a diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c index 0609294af0..6dba0db00a 100644 --- a/tests/libqos/pci-pc.c +++ b/tests/libqos/pci-pc.c @@ -20,6 +20,9 @@ #include <glib.h> +#define ACPI_PCIHP_ADDR 0xae00 +#define PCI_EJ_BASE 0x0008 + typedef struct QPCIBusPC { QPCIBus bus; @@ -247,3 +250,49 @@ void qpci_free_pc(QPCIBus *bus) g_free(s); } + +void qpci_plug_device_test(const char *driver, const char *id, + uint8_t slot, const char *opts) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': '%s'," + " 'addr': '%d'," + " %s%s" + " 'id': '%s'" + "}}", driver, slot, + opts ? opts : "", opts ? "," : "", + id); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); +} + +void qpci_unplug_acpi_device_test(const char *id, uint8_t slot) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_del'," + " 'arguments': {" + " 'id': '%s'" + "}}", id); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + outb(ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << slot); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h index d51eb9e219..dfaee9ec37 100644 --- a/tests/libqos/pci.h +++ b/tests/libqos/pci.h @@ -87,4 +87,7 @@ void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value); void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr); void qpci_iounmap(QPCIDevice *dev, void *data); +void qpci_plug_device_test(const char *driver, const char *id, + uint8_t slot, const char *opts); +void qpci_unplug_acpi_device_test(const char *id, uint8_t slot); #endif diff --git a/tests/libqos/usb.c b/tests/libqos/usb.c new file mode 100644 index 0000000000..41d89b8487 --- /dev/null +++ b/tests/libqos/usb.c @@ -0,0 +1,71 @@ +/* + * common code shared by usb tests + * + * Copyright (c) 2014 Red Hat, Inc + * + * Authors: + * Gerd Hoffmann <kraxel@redhat.com> + * John Snow <jsnow@redhat.com> + * Igor Mammedov <imammedo@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include "libqtest.h" +#include "qemu/osdep.h" +#include "hw/usb/uhci-regs.h" +#include "libqos/usb.h" + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, uint32_t devfn, int bar) +{ + hc->dev = qpci_device_find(pcibus, devfn); + g_assert(hc->dev != NULL); + qpci_device_enable(hc->dev); + hc->base = qpci_iomap(hc->dev, bar, NULL); + g_assert(hc->base != NULL); +} + +void uhci_port_test(struct qhc *hc, int port, uint16_t expect) +{ + void *addr = hc->base + 0x10 + 2 * port; + uint16_t value = qpci_io_readw(hc->dev, addr); + uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); + + g_assert((value & mask) == (expect & mask)); +} + +void usb_test_hotplug(const char *hcd_id, const int port, + void (*port_check)(void)) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-tablet'," + " 'port': '%d'," + " 'bus': '%s.0'," + " 'id': 'usbdev%d'" + "}}", port, hcd_id, port); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + if (port_check) { + port_check(); + } + + cmd = g_strdup_printf("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'usbdev%d'" + "}}", port); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); +} diff --git a/tests/libqos/usb.h b/tests/libqos/usb.h new file mode 100644 index 0000000000..8fe56872b7 --- /dev/null +++ b/tests/libqos/usb.h @@ -0,0 +1,17 @@ +#ifndef LIBQOS_USB_H +#define LIBQOS_USB_H + +#include "libqos/pci-pc.h" + +struct qhc { + QPCIDevice *dev; + void *base; +}; + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, + uint32_t devfn, int bar); +void uhci_port_test(struct qhc *hc, int port, uint16_t expect); + +void usb_test_hotplug(const char *bus_name, const int port, + void (*port_check)(void)); +#endif diff --git a/tests/libqtest.h b/tests/libqtest.h index 3e12cab2f2..e7413d52dc 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -23,6 +23,7 @@ #include <stdarg.h> #include <sys/types.h> #include "qapi/qmp/qdict.h" +#include "glib-compat.h" typedef struct QTestState QTestState; diff --git a/tests/test-vmstate.c b/tests/test-vmstate.c index d72c64c90b..5e0fd13cc4 100644 --- a/tests/test-vmstate.c +++ b/tests/test-vmstate.c @@ -43,6 +43,12 @@ void yield_until_fd_readable(int fd) select(fd + 1, &fds, NULL, NULL, NULL); } +/* + * Some tests use 'open_test_file' to work on a real fd, some use + * an in memory file (QEMUSizedBuffer+qemu_bufopen); we could pick one + * but this way we test both. + */ + /* Duplicate temp_fd and seek to the beginning of the file */ static QEMUFile *open_test_file(bool write) { @@ -54,6 +60,30 @@ static QEMUFile *open_test_file(bool write) return qemu_fdopen(fd, write ? "wb" : "rb"); } +/* Open a read-only qemu-file from an existing memory block */ +static QEMUFile *open_mem_file_read(const void *data, size_t len) +{ + /* The qsb gets freed by qemu_fclose */ + QEMUSizedBuffer *qsb = qsb_create(data, len); + g_assert(qsb); + + return qemu_bufopen("r", qsb); +} + +/* + * Check that the contents of the memory-buffered file f match + * the given size/data. + */ +static void check_mem_file(QEMUFile *f, void *data, size_t size) +{ + uint8_t *result = g_malloc(size); + const QEMUSizedBuffer *qsb = qemu_buf_get(f); + g_assert_cmpint(qsb_get_length(qsb), ==, size); + g_assert_cmpint(qsb_get_buffer(qsb, 0, size, result), ==, size); + g_assert_cmpint(memcmp(result, data, size), ==, 0); + g_free(result); +} + #define SUCCESS(val) \ g_assert_cmpint((val), ==, 0) @@ -371,14 +401,12 @@ static const VMStateDescription vmstate_skipping = { static void test_save_noskip(void) { - QEMUFile *fsave = open_test_file(true); + QEMUFile *fsave = qemu_bufopen("w", NULL); TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6, .skip_c_e = false }; vmstate_save_state(fsave, &vmstate_skipping, &obj); g_assert(!qemu_file_get_error(fsave)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); uint8_t expected[] = { 0, 0, 0, 1, /* a */ 0, 0, 0, 2, /* b */ @@ -387,52 +415,31 @@ static void test_save_noskip(void) 0, 0, 0, 5, /* e */ 0, 0, 0, 0, 0, 0, 0, 6, /* f */ }; - uint8_t result[sizeof(expected)]; - g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==, - sizeof(result)); - g_assert(!qemu_file_get_error(loading)); - g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0); - - /* Must reach EOF */ - qemu_get_byte(loading); - g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO); - - qemu_fclose(loading); + check_mem_file(fsave, expected, sizeof(expected)); + qemu_fclose(fsave); } static void test_save_skip(void) { - QEMUFile *fsave = open_test_file(true); + QEMUFile *fsave = qemu_bufopen("w", NULL); TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6, .skip_c_e = true }; vmstate_save_state(fsave, &vmstate_skipping, &obj); g_assert(!qemu_file_get_error(fsave)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); uint8_t expected[] = { 0, 0, 0, 1, /* a */ 0, 0, 0, 2, /* b */ 0, 0, 0, 0, 0, 0, 0, 4, /* d */ 0, 0, 0, 0, 0, 0, 0, 6, /* f */ }; - uint8_t result[sizeof(expected)]; - g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==, - sizeof(result)); - g_assert(!qemu_file_get_error(loading)); - g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0); - - - /* Must reach EOF */ - qemu_get_byte(loading); - g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO); + check_mem_file(fsave, expected, sizeof(expected)); - qemu_fclose(loading); + qemu_fclose(fsave); } static void test_load_noskip(void) { - QEMUFile *fsave = open_test_file(true); uint8_t buf[] = { 0, 0, 0, 10, /* a */ 0, 0, 0, 20, /* b */ @@ -442,10 +449,8 @@ static void test_load_noskip(void) 0, 0, 0, 0, 0, 0, 0, 60, /* f */ QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ }; - qemu_put_buffer(fsave, buf, sizeof(buf)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); + QEMUFile *loading = open_mem_file_read(buf, sizeof(buf)); TestStruct obj = { .skip_c_e = false }; vmstate_load_state(loading, &vmstate_skipping, &obj, 2); g_assert(!qemu_file_get_error(loading)); @@ -460,7 +465,6 @@ static void test_load_noskip(void) static void test_load_skip(void) { - QEMUFile *fsave = open_test_file(true); uint8_t buf[] = { 0, 0, 0, 10, /* a */ 0, 0, 0, 20, /* b */ @@ -468,10 +472,8 @@ static void test_load_skip(void) 0, 0, 0, 0, 0, 0, 0, 60, /* f */ QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ }; - qemu_put_buffer(fsave, buf, sizeof(buf)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); + QEMUFile *loading = open_mem_file_read(buf, sizeof(buf)); TestStruct obj = { .skip_c_e = true, .c = 300, .e = 500 }; vmstate_load_state(loading, &vmstate_skipping, &obj, 2); g_assert(!qemu_file_get_error(loading)); diff --git a/tests/usb-hcd-ehci-test.c b/tests/usb-hcd-ehci-test.c index c990492835..75073bf24e 100644 --- a/tests/usb-hcd-ehci-test.c +++ b/tests/usb-hcd-ehci-test.c @@ -15,11 +15,7 @@ #include "qemu/osdep.h" #include "hw/usb/uhci-regs.h" #include "hw/usb/ehci-regs.h" - -struct qhc { - QPCIDevice *dev; - void *base; -}; +#include "libqos/usb.h" static QPCIBus *pcibus; static struct qhc uhci1; @@ -29,15 +25,6 @@ static struct qhc ehci1; /* helpers */ -static void pci_init_one(struct qhc *hc, uint32_t devfn, int bar) -{ - hc->dev = qpci_device_find(pcibus, devfn); - g_assert(hc->dev != NULL); - qpci_device_enable(hc->dev); - hc->base = qpci_iomap(hc->dev, bar, NULL); - g_assert(hc->base != NULL); -} - #if 0 static void uhci_port_update(struct qhc *hc, int port, uint16_t set, uint16_t clear) @@ -52,19 +39,6 @@ static void uhci_port_update(struct qhc *hc, int port, } #endif -static void uhci_port_test(struct qhc *hc, int port, uint16_t expect) -{ - void *addr = hc->base + 0x10 + 2 * port; - uint16_t value = qpci_io_readw(hc->dev, addr); - uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); - -#if 0 - fprintf(stderr, "%s: %d, have 0x%04x, want 0x%04x\n", - __func__, port, value & mask, expect & mask); -#endif - g_assert((value & mask) == (expect & mask)); -} - static void ehci_port_test(struct qhc *hc, int port, uint32_t expect) { void *addr = hc->base + 0x64 + 4 * port; @@ -88,10 +62,10 @@ static void pci_init(void) pcibus = qpci_init_pc(); g_assert(pcibus != NULL); - pci_init_one(&uhci1, QPCI_DEVFN(0x1d, 0), 4); - pci_init_one(&uhci2, QPCI_DEVFN(0x1d, 1), 4); - pci_init_one(&uhci3, QPCI_DEVFN(0x1d, 2), 4); - pci_init_one(&ehci1, QPCI_DEVFN(0x1d, 7), 0); + qusb_pci_init_one(pcibus, &uhci1, QPCI_DEVFN(0x1d, 0), 4); + qusb_pci_init_one(pcibus, &uhci2, QPCI_DEVFN(0x1d, 1), 4); + qusb_pci_init_one(pcibus, &uhci3, QPCI_DEVFN(0x1d, 2), 4); + qusb_pci_init_one(pcibus, &ehci1, QPCI_DEVFN(0x1d, 7), 0); } static void pci_uhci_port_1(void) @@ -154,6 +128,19 @@ static void pci_ehci_port_2(void) } } +static void pci_ehci_port_3_hotplug(void) +{ + /* check for presence of hotplugged usb-tablet */ + g_assert(pcibus != NULL); + ehci_port_test(&ehci1, 2, PORTSC_PPOWER | PORTSC_CONNECT); +} + +static void pci_ehci_port_hotplug(void) +{ + usb_test_hotplug("ich9-ehci-1", 3, pci_ehci_port_3_hotplug); +} + + int main(int argc, char **argv) { int ret; @@ -165,6 +152,7 @@ int main(int argc, char **argv) qtest_add_func("/ehci/pci/ehci-config", pci_ehci_config); qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2); qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2); + qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug); qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7," "multifunction=on,id=ich9-ehci-1 " diff --git a/tests/usb-hcd-ohci-test.c b/tests/usb-hcd-ohci-test.c index fbc3ffeebd..1160bde840 100644 --- a/tests/usb-hcd-ohci-test.c +++ b/tests/usb-hcd-ohci-test.c @@ -11,15 +11,18 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/usb.h" static void test_ohci_init(void) { - qtest_start("-device pci-ohci,id=ohci"); - qtest_end(); } +static void test_ohci_hotplug(void) +{ + usb_test_hotplug("ohci", 1, NULL); +} int main(int argc, char **argv) { @@ -28,8 +31,11 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); qtest_add_func("/ohci/pci/init", test_ohci_init); + qtest_add_func("/ohci/pci/hotplug", test_ohci_hotplug); + qtest_start("-device pci-ohci,id=ohci"); ret = g_test_run(); + qtest_end(); return ret; } diff --git a/tests/usb-hcd-uhci-test.c b/tests/usb-hcd-uhci-test.c index 94e858f4c6..8cf2c5bcaa 100644 --- a/tests/usb-hcd-uhci-test.c +++ b/tests/usb-hcd-uhci-test.c @@ -11,15 +11,69 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/usb.h" +#include "hw/usb/uhci-regs.h" static void test_uhci_init(void) { - qtest_start("-device piix3-usb-uhci,id=uhci"); +} - qtest_end(); +static void test_port(int port) +{ + QPCIBus *pcibus; + struct qhc uhci; + + g_assert(port > 0); + pcibus = qpci_init_pc(); + g_assert(pcibus != NULL); + qusb_pci_init_one(pcibus, &uhci, QPCI_DEVFN(0x1d, 0), 4); + uhci_port_test(&uhci, port - 1, UHCI_PORT_CCS); +} + +static void test_port_1(void) +{ + test_port(1); } +static void test_port_2(void) +{ + test_port(2); +} + +static void test_uhci_hotplug(void) +{ + usb_test_hotplug("uhci", 2, test_port_2); +} + +static void test_usb_storage_hotplug(void) +{ + QDict *response; + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-storage'," + " 'drive': 'drive0'," + " 'id': 'usbdev0'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'usbdev0'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} int main(int argc, char **argv) { @@ -28,8 +82,15 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); qtest_add_func("/uhci/pci/init", test_uhci_init); + qtest_add_func("/uhci/pci/port1", test_port_1); + qtest_add_func("/uhci/pci/hotplug", test_uhci_hotplug); + qtest_add_func("/uhci/pci/hotplug/usb-storage", test_usb_storage_hotplug); + qtest_start("-device piix3-usb-uhci,id=uhci,addr=1d.0" + " -drive id=drive0,if=none,file=/dev/null" + " -device usb-tablet,bus=uhci.0,port=1"); ret = g_test_run(); + qtest_end(); return ret; } diff --git a/tests/usb-hcd-xhci-test.c b/tests/usb-hcd-xhci-test.c index 743e9798cd..b1a7dec5bb 100644 --- a/tests/usb-hcd-xhci-test.c +++ b/tests/usb-hcd-xhci-test.c @@ -11,15 +11,74 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/usb.h" static void test_xhci_init(void) { - qtest_start("-device nec-usb-xhci,id=xhci"); +} - qtest_end(); +static void test_xhci_hotplug(void) +{ + usb_test_hotplug("xhci", 1, NULL); } +static void test_usb_uas_hotplug(void) +{ + QDict *response; + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-uas'," + " 'id': 'uas'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'scsi-hd'," + " 'drive': 'drive0'," + " 'id': 'scsi-hd'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + /* TODO: + UAS HBA driver in libqos, to check that + added disk is visible after BUS rescan + */ + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'scsi-hd'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); + + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'uas'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} int main(int argc, char **argv) { @@ -28,8 +87,13 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); qtest_add_func("/xhci/pci/init", test_xhci_init); + qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug); + qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug); + qtest_start("-device nec-usb-xhci,id=xhci" + " -drive id=drive0,if=none,file=/dev/null"); ret = g_test_run(); + qtest_end(); return ret; } diff --git a/tests/vhost-user-test.c b/tests/vhost-user-test.c index 75fedf0977..fdf91e7897 100644 --- a/tests/vhost-user-test.c +++ b/tests/vhost-user-test.c @@ -21,15 +21,6 @@ #include <sys/vfs.h> #include <qemu/sockets.h> -/* GLIB version compatibility flags */ -#if !GLIB_CHECK_VERSION(2, 26, 0) -#define G_TIME_SPAN_SECOND (G_GINT64_CONSTANT(1000000)) -#endif - -#if GLIB_CHECK_VERSION(2, 28, 0) -#define HAVE_MONOTONIC_TIME -#endif - #if GLIB_CHECK_VERSION(2, 32, 0) #define HAVE_MUTEX_INIT #define HAVE_COND_INIT @@ -116,18 +107,6 @@ static VhostUserMemory memory; static GMutex *data_mutex; static GCond *data_cond; -static gint64 _get_time(void) -{ -#ifdef HAVE_MONOTONIC_TIME - return g_get_monotonic_time(); -#else - GTimeVal time; - g_get_current_time(&time); - - return time.tv_sec * G_TIME_SPAN_SECOND + time.tv_usec; -#endif -} - static GMutex *_mutex_new(void) { GMutex *mutex; @@ -210,7 +189,7 @@ static void read_guest_mem(void) g_mutex_lock(data_mutex); - end_time = _get_time() + 5 * G_TIME_SPAN_SECOND; + end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; while (!fds_num) { if (!_cond_wait_until(data_cond, data_mutex, end_time)) { /* timeout has passed */ diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index 5ce6e79757..ead3911e28 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -45,6 +45,8 @@ #define PCI_SLOT 0x04 #define PCI_FN 0x00 +#define PCI_SLOT_HP 0x06 + typedef struct QVirtioBlkReq { uint32_t type; uint32_t ioprio; @@ -55,7 +57,7 @@ typedef struct QVirtioBlkReq { static QPCIBus *test_start(void) { - char cmdline[100]; + char *cmdline; char tmp_path[] = "/tmp/qtest.XXXXXX"; int fd, ret; @@ -66,11 +68,14 @@ static QPCIBus *test_start(void) g_assert_cmpint(ret, ==, 0); close(fd); - snprintf(cmdline, 100, "-drive if=none,id=drive0,file=%s " - "-device virtio-blk-pci,drive=drive0,addr=%x.%x", - tmp_path, PCI_SLOT, PCI_FN); + cmdline = g_strdup_printf("-drive if=none,id=drive0,file=%s " + "-drive if=none,id=drive1,file=/dev/null " + "-device virtio-blk-pci,id=drv0,drive=drive0," + "addr=%x.%x", + tmp_path, PCI_SLOT, PCI_FN); qtest_start(cmdline); unlink(tmp_path); + g_free(cmdline); return qpci_init_pc(); } @@ -80,14 +85,14 @@ static void test_end(void) qtest_end(); } -static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus) +static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus, int slot) { QVirtioPCIDevice *dev; dev = qvirtio_pci_device_find(bus, QVIRTIO_BLK_DEVICE_ID); g_assert(dev != NULL); g_assert_cmphex(dev->vdev.device_type, ==, QVIRTIO_BLK_DEVICE_ID); - g_assert_cmphex(dev->pdev->devfn, ==, ((PCI_SLOT << 3) | PCI_FN)); + g_assert_cmphex(dev->pdev->devfn, ==, ((slot << 3) | PCI_FN)); qvirtio_pci_device_enable(dev); qvirtio_reset(&qvirtio_pci, &dev->vdev); @@ -147,7 +152,7 @@ static void pci_basic(void) bus = test_start(); - dev = virtio_blk_init(bus); + dev = virtio_blk_init(bus, PCI_SLOT); /* MSI-X is not enabled */ addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; @@ -293,7 +298,7 @@ static void pci_indirect(void) bus = test_start(); - dev = virtio_blk_init(bus); + dev = virtio_blk_init(bus, PCI_SLOT); /* MSI-X is not enabled */ addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; @@ -384,7 +389,7 @@ static void pci_config(void) bus = test_start(); - dev = virtio_blk_init(bus); + dev = virtio_blk_init(bus, PCI_SLOT); /* MSI-X is not enabled */ addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; @@ -425,7 +430,7 @@ static void pci_msix(void) bus = test_start(); alloc = pc_alloc_init(); - dev = virtio_blk_init(bus); + dev = virtio_blk_init(bus, PCI_SLOT); qpci_msix_enable(dev->pdev); qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); @@ -534,7 +539,7 @@ static void pci_idx(void) bus = test_start(); alloc = pc_alloc_init(); - dev = virtio_blk_init(bus); + dev = virtio_blk_init(bus, PCI_SLOT); qpci_msix_enable(dev->pdev); qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); @@ -637,6 +642,27 @@ static void pci_idx(void) test_end(); } +static void hotplug(void) +{ + QPCIBus *bus; + QVirtioPCIDevice *dev; + + bus = test_start(); + + /* plug secondary disk */ + qpci_plug_device_test("virtio-blk-pci", "drv1", PCI_SLOT_HP, + "'drive': 'drive1'"); + + dev = virtio_blk_init(bus, PCI_SLOT_HP); + g_assert(dev); + qvirtio_pci_device_disable(dev); + g_free(dev); + + /* unplug secondary disk */ + qpci_unplug_acpi_device_test("drv1", PCI_SLOT_HP); + test_end(); +} + int main(int argc, char **argv) { int ret; @@ -648,6 +674,7 @@ int main(int argc, char **argv) g_test_add_func("/virtio/blk/pci/config", pci_config); g_test_add_func("/virtio/blk/pci/msix", pci_msix); g_test_add_func("/virtio/blk/pci/idx", pci_idx); + g_test_add_func("/virtio/blk/pci/hotplug", hotplug); ret = g_test_run(); diff --git a/tests/virtio-net-test.c b/tests/virtio-net-test.c index df99343238..ea7478c278 100644 --- a/tests/virtio-net-test.c +++ b/tests/virtio-net-test.c @@ -11,18 +11,28 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/pci.h" + +#define PCI_SLOT_HP 0x06 /* Tests only initialization so far. TODO: Replace with functional tests */ static void pci_nop(void) { } +static void hotplug(void) +{ + qpci_plug_device_test("virtio-net-pci", "net1", PCI_SLOT_HP, NULL); + qpci_unplug_acpi_device_test("net1", PCI_SLOT_HP); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/net/pci/nop", pci_nop); + qtest_add_func("/virtio/net/pci/hotplug", hotplug); qtest_start("-device virtio-net-pci"); ret = g_test_run(); diff --git a/tests/virtio-rng-test.c b/tests/virtio-rng-test.c index 402c2060da..41c1cdb1aa 100644 --- a/tests/virtio-rng-test.c +++ b/tests/virtio-rng-test.c @@ -11,18 +11,28 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/pci.h" + +#define PCI_SLOT_HP 0x06 /* Tests only initialization so far. TODO: Replace with functional tests */ static void pci_nop(void) { } +static void hotplug(void) +{ + qpci_plug_device_test("virtio-rng-pci", "rng1", PCI_SLOT_HP, NULL); + qpci_unplug_acpi_device_test("rng1", PCI_SLOT_HP); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/rng/pci/nop", pci_nop); + qtest_add_func("/virtio/rng/pci/hotplug", hotplug); qtest_start("-device virtio-rng-pci"); ret = g_test_run(); diff --git a/tests/virtio-scsi-test.c b/tests/virtio-scsi-test.c index 3230908b98..41f9602e44 100644 --- a/tests/virtio-scsi-test.c +++ b/tests/virtio-scsi-test.c @@ -17,14 +17,43 @@ static void pci_nop(void) { } +static void hotplug(void) +{ + QDict *response; + + response = qmp("{\"execute\": \"device_add\"," + " \"arguments\": {" + " \"driver\": \"scsi-hd\"," + " \"id\": \"scsi-hd\"," + " \"drive\": \"drv1\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{\"execute\": \"device_del\"," + " \"arguments\": {" + " \"id\": \"scsi-hd\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/scsi/pci/nop", pci_nop); + qtest_add_func("/virtio/scsi/pci/hotplug", hotplug); qtest_start("-drive id=drv0,if=none,file=/dev/null " + "-drive id=drv1,if=none,file=/dev/null " "-device virtio-scsi-pci,id=vscsi0 " "-device scsi-hd,bus=vscsi0.0,drive=drv0"); ret = g_test_run(); diff --git a/tests/virtio-serial-test.c b/tests/virtio-serial-test.c index e7438751ea..bf030a6162 100644 --- a/tests/virtio-serial-test.c +++ b/tests/virtio-serial-test.c @@ -17,12 +17,39 @@ static void pci_nop(void) { } +static void hotplug(void) +{ + QDict *response; + + response = qmp("{\"execute\": \"device_add\"," + " \"arguments\": {" + " \"driver\": \"virtserialport\"," + " \"id\": \"hp-port\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{\"execute\": \"device_del\"," + " \"arguments\": {" + " \"id\": \"hp-port\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/serial/pci/nop", pci_nop); + qtest_add_func("/virtio/serial/pci/hotplug", hotplug); qtest_start("-device virtio-serial-pci"); ret = g_test_run(); @@ -49,9 +49,16 @@ static void *vmstate_base_addr(void *opaque, VMStateField *field, bool alloc) if (field->flags & VMS_POINTER) { if (alloc && (field->flags & VMS_ALLOC)) { - int n_elems = vmstate_n_elems(opaque, field); - if (n_elems) { - gsize size = n_elems * field->size; + gsize size = 0; + if (field->flags & VMS_VBUFFER) { + size = vmstate_size(opaque, field); + } else { + int n_elems = vmstate_n_elems(opaque, field); + if (n_elems) { + size = n_elems * field->size; + } + } + if (size) { *((void **)base_addr + field->start) = g_malloc(size); } } |