diff options
Diffstat (limited to 'hw/core/qdev.c')
-rw-r--r-- | hw/core/qdev.c | 900 |
1 files changed, 354 insertions, 546 deletions
diff --git a/hw/core/qdev.c b/hw/core/qdev.c index 046d8f1f76..c68d0f7c51 100644 --- a/hw/core/qdev.c +++ b/hw/core/qdev.c @@ -6,7 +6,7 @@ * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -26,19 +26,21 @@ this API directly. */ #include "qemu/osdep.h" -#include "hw/qdev.h" -#include "sysemu/sysemu.h" #include "qapi/error.h" -#include "qapi/qapi-events-misc.h" +#include "qapi/qapi-events-qdev.h" +#include "qapi/qmp/qdict.h" #include "qapi/qmp/qerror.h" #include "qapi/visitor.h" #include "qemu/error-report.h" #include "qemu/option.h" -#include "hw/hotplug.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" #include "hw/boards.h" #include "hw/sysbus.h" +#include "hw/qdev-clock.h" +#include "migration/vmstate.h" +#include "trace.h" -bool qdev_hotplug = false; static bool qdev_hot_added = false; bool qdev_hot_removed = false; @@ -48,6 +50,12 @@ const VMStateDescription *qdev_get_vmsd(DeviceState *dev) return dc->vmsd; } +static void bus_free_bus_child(BusChild *kid) +{ + object_unref(OBJECT(kid->child)); + g_free(kid); +} + static void bus_remove_child(BusState *bus, DeviceState *child) { BusChild *kid; @@ -57,13 +65,16 @@ static void bus_remove_child(BusState *bus, DeviceState *child) char name[32]; snprintf(name, sizeof(name), "child[%d]", kid->index); - QTAILQ_REMOVE(&bus->children, kid, sibling); + QTAILQ_REMOVE_RCU(&bus->children, kid, sibling); + + bus->num_children--; /* This gives back ownership of kid->child back to us. */ - object_property_del(OBJECT(bus), name, NULL); - object_unref(OBJECT(kid->child)); - g_free(kid); - return; + object_property_del(OBJECT(bus), name); + + /* free the bus kid, when it is safe to do so*/ + call_rcu(kid, bus_free_bus_child, rcu); + break; } } } @@ -73,11 +84,12 @@ static void bus_add_child(BusState *bus, DeviceState *child) char name[32]; BusChild *kid = g_malloc0(sizeof(*kid)); + bus->num_children++; kid->index = bus->max_index++; kid->child = child; object_ref(OBJECT(kid->child)); - QTAILQ_INSERT_HEAD(&bus->children, kid, sibling); + QTAILQ_INSERT_HEAD_RCU(&bus->children, kid, sibling); /* This transfers ownership of kid->child to the property. */ snprintf(name, sizeof(name), "child[%d]", kid->index); @@ -85,80 +97,84 @@ static void bus_add_child(BusState *bus, DeviceState *child) object_get_typename(OBJECT(child)), (Object **)&kid->child, NULL, /* read-only property */ - 0, /* return ownership on prop deletion */ - NULL); + 0); } -void qdev_set_parent_bus(DeviceState *dev, BusState *bus) +static bool bus_check_address(BusState *bus, DeviceState *child, Error **errp) { - bool replugging = dev->parent_bus != NULL; + BusClass *bc = BUS_GET_CLASS(bus); + return !bc->check_address || bc->check_address(bus, child, errp); +} + +bool qdev_set_parent_bus(DeviceState *dev, BusState *bus, Error **errp) +{ + BusState *old_parent_bus = dev->parent_bus; + DeviceClass *dc = DEVICE_GET_CLASS(dev); - if (replugging) { - /* Keep a reference to the device while it's not plugged into + assert(dc->bus_type && object_dynamic_cast(OBJECT(bus), dc->bus_type)); + + if (!bus_check_address(bus, dev, errp)) { + return false; + } + + if (old_parent_bus) { + trace_qdev_update_parent_bus(dev, object_get_typename(OBJECT(dev)), + old_parent_bus, object_get_typename(OBJECT(old_parent_bus)), + OBJECT(bus), object_get_typename(OBJECT(bus))); + /* + * Keep a reference to the device while it's not plugged into * any bus, to avoid it potentially evaporating when it is * dereffed in bus_remove_child(). + * Also keep the ref of the parent bus until the end, so that + * we can safely call resettable_change_parent() below. */ object_ref(OBJECT(dev)); bus_remove_child(dev->parent_bus, dev); - object_unref(OBJECT(dev->parent_bus)); } dev->parent_bus = bus; object_ref(OBJECT(bus)); bus_add_child(bus, dev); - if (replugging) { + if (dev->realized) { + resettable_change_parent(OBJECT(dev), OBJECT(bus), + OBJECT(old_parent_bus)); + } + if (old_parent_bus) { + object_unref(OBJECT(old_parent_bus)); object_unref(OBJECT(dev)); } + return true; } -/* Create a new device. This only initializes the device state - structure and allows properties to be set. The device still needs - to be realized. See qdev-core.h. */ -DeviceState *qdev_create(BusState *bus, const char *name) +DeviceState *qdev_new(const char *name) { - DeviceState *dev; - - dev = qdev_try_create(bus, name); - if (!dev) { - if (bus) { - error_report("Unknown device '%s' for bus '%s'", name, - object_get_typename(OBJECT(bus))); + ObjectClass *oc = object_class_by_name(name); +#ifdef CONFIG_MODULES + if (!oc) { + int rv = module_load_qom(name, &error_fatal); + if (rv > 0) { + oc = object_class_by_name(name); } else { - error_report("Unknown device '%s' for default sysbus", name); + error_report("could not find a module for type '%s'", name); + exit(1); } + } +#endif + if (!oc) { + error_report("unknown type '%s'", name); abort(); } - - return dev; + return DEVICE(object_new(name)); } -DeviceState *qdev_try_create(BusState *bus, const char *type) +DeviceState *qdev_try_new(const char *name) { - DeviceState *dev; - - if (object_class_by_name(type) == NULL) { + if (!module_object_class_by_name(name)) { return NULL; } - dev = DEVICE(object_new(type)); - if (!dev) { - return NULL; - } - - if (!bus) { - /* Assert that the device really is a SysBusDevice before - * we put it onto the sysbus. Non-sysbus devices which aren't - * being put onto a bus should be created with object_new(TYPE_FOO), - * not qdev_create(NULL, TYPE_FOO). - */ - g_assert(object_dynamic_cast(OBJECT(dev), TYPE_SYS_BUS_DEVICE)); - bus = sysbus_get_default(); - } - - qdev_set_parent_bus(dev, bus); - object_unref(OBJECT(dev)); - return dev; + return DEVICE(object_new(name)); } -static QTAILQ_HEAD(device_listeners, DeviceListener) device_listeners +static QTAILQ_HEAD(, DeviceListener) device_listeners = QTAILQ_HEAD_INITIALIZER(device_listeners); enum ListenerDirection { Forward, Reverse }; @@ -177,7 +193,7 @@ enum ListenerDirection { Forward, Reverse }; break; \ case Reverse: \ QTAILQ_FOREACH_REVERSE(_listener, &device_listeners, \ - device_listeners, link) { \ + link) { \ if (_listener->_callback) { \ _listener->_callback(_listener, ##_args); \ } \ @@ -208,318 +224,115 @@ void device_listener_unregister(DeviceListener *listener) QTAILQ_REMOVE(&device_listeners, listener, link); } -void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, - int required_for_version) +bool qdev_should_hide_device(const QDict *opts, bool from_json, Error **errp) { - assert(!dev->realized); - dev->instance_id_alias = alias_id; - dev->alias_required_for_version = required_for_version; -} + ERRP_GUARD(); + DeviceListener *listener; -HotplugHandler *qdev_get_machine_hotplug_handler(DeviceState *dev) -{ - MachineState *machine; - MachineClass *mc; - Object *m_obj = qdev_get_machine(); - - if (object_dynamic_cast(m_obj, TYPE_MACHINE)) { - machine = MACHINE(m_obj); - mc = MACHINE_GET_CLASS(machine); - if (mc->get_hotplug_handler) { - return mc->get_hotplug_handler(machine, dev); + QTAILQ_FOREACH(listener, &device_listeners, link) { + if (listener->hide_device) { + if (listener->hide_device(listener, opts, from_json, errp)) { + return true; + } else if (*errp) { + return false; + } } } - return NULL; -} - -HotplugHandler *qdev_get_hotplug_handler(DeviceState *dev) -{ - HotplugHandler *hotplug_ctrl; - - if (dev->parent_bus && dev->parent_bus->hotplug_handler) { - hotplug_ctrl = dev->parent_bus->hotplug_handler; - } else { - hotplug_ctrl = qdev_get_machine_hotplug_handler(dev); - } - return hotplug_ctrl; -} - -static int qdev_reset_one(DeviceState *dev, void *opaque) -{ - device_reset(dev); - - return 0; -} - -static int qbus_reset_one(BusState *bus, void *opaque) -{ - BusClass *bc = BUS_GET_CLASS(bus); - if (bc->reset) { - bc->reset(bus); - } - return 0; -} - -void qdev_reset_all(DeviceState *dev) -{ - qdev_walk_children(dev, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL); -} - -void qdev_reset_all_fn(void *opaque) -{ - qdev_reset_all(DEVICE(opaque)); -} - -void qbus_reset_all(BusState *bus) -{ - qbus_walk_children(bus, NULL, NULL, qdev_reset_one, qbus_reset_one, NULL); -} - -void qbus_reset_all_fn(void *opaque) -{ - BusState *bus = opaque; - qbus_reset_all(bus); -} - -/* can be used as ->unplug() callback for the simple cases */ -void qdev_simple_device_unplug_cb(HotplugHandler *hotplug_dev, - DeviceState *dev, Error **errp) -{ - /* just zap it */ - object_unparent(OBJECT(dev)); + return false; } -/* - * Realize @dev. - * Device properties should be set before calling this function. IRQs - * and MMIO regions should be connected/mapped after calling this - * function. - * On failure, report an error with error_report() and terminate the - * program. This is okay during machine creation. Don't use for - * hotplug, because there callers need to recover from failure. - * Exception: if you know the device's init() callback can't fail, - * then qdev_init_nofail() can't fail either, and is therefore usable - * even then. But relying on the device implementation that way is - * somewhat unclean, and best avoided. - */ -void qdev_init_nofail(DeviceState *dev) +void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, + int required_for_version) { - Error *err = NULL; - assert(!dev->realized); - - object_ref(OBJECT(dev)); - object_property_set_bool(OBJECT(dev), true, "realized", &err); - if (err) { - error_reportf_err(err, "Initialization of device %s failed: ", - object_get_typename(OBJECT(dev))); - exit(1); - } - object_unref(OBJECT(dev)); -} - -void qdev_machine_creation_done(void) -{ - /* - * ok, initial machine setup is done, starting from now we can - * only create hotpluggable devices - */ - qdev_hotplug = true; + dev->instance_id_alias = alias_id; + dev->alias_required_for_version = required_for_version; } -bool qdev_machine_modified(void) +void device_cold_reset(DeviceState *dev) { - return qdev_hot_added || qdev_hot_removed; + resettable_reset(OBJECT(dev), RESET_TYPE_COLD); } -BusState *qdev_get_parent_bus(DeviceState *dev) +bool device_is_in_reset(DeviceState *dev) { - return dev->parent_bus; + return resettable_is_in_reset(OBJECT(dev)); } -static NamedGPIOList *qdev_get_named_gpio_list(DeviceState *dev, - const char *name) +static ResettableState *device_get_reset_state(Object *obj) { - NamedGPIOList *ngl; - - QLIST_FOREACH(ngl, &dev->gpios, node) { - /* NULL is a valid and matchable name, otherwise do a normal - * strcmp match. - */ - if ((!ngl->name && !name) || - (name && ngl->name && strcmp(name, ngl->name) == 0)) { - return ngl; - } - } - - ngl = g_malloc0(sizeof(*ngl)); - ngl->name = g_strdup(name); - QLIST_INSERT_HEAD(&dev->gpios, ngl, node); - return ngl; + DeviceState *dev = DEVICE(obj); + return &dev->reset; } -void qdev_init_gpio_in_named_with_opaque(DeviceState *dev, - qemu_irq_handler handler, - void *opaque, - const char *name, int n) +static void device_reset_child_foreach(Object *obj, ResettableChildCallback cb, + void *opaque, ResetType type) { - int i; - NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name); - - assert(gpio_list->num_out == 0 || !name); - gpio_list->in = qemu_extend_irqs(gpio_list->in, gpio_list->num_in, handler, - opaque, n); - - if (!name) { - name = "unnamed-gpio-in"; - } - for (i = gpio_list->num_in; i < gpio_list->num_in + n; i++) { - gchar *propname = g_strdup_printf("%s[%u]", name, i); + DeviceState *dev = DEVICE(obj); + BusState *bus; - object_property_add_child(OBJECT(dev), propname, - OBJECT(gpio_list->in[i]), &error_abort); - g_free(propname); + QLIST_FOREACH(bus, &dev->child_bus, sibling) { + cb(OBJECT(bus), opaque, type); } - - gpio_list->num_in += n; } -void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n) +bool qdev_realize(DeviceState *dev, BusState *bus, Error **errp) { - qdev_init_gpio_in_named(dev, handler, NULL, n); -} + assert(!dev->realized && !dev->parent_bus); -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); - - assert(gpio_list->num_in == 0 || !name); - - if (!name) { - name = "unnamed-gpio-out"; - } - memset(pins, 0, sizeof(*pins) * n); - for (i = 0; i < n; ++i) { - gchar *propname = g_strdup_printf("%s[%u]", name, - gpio_list->num_out + i); - - object_property_add_link(OBJECT(dev), propname, TYPE_IRQ, - (Object **)&pins[i], - object_property_allow_set_link, - OBJ_PROP_LINK_STRONG, - &error_abort); - g_free(propname); + if (bus) { + if (!qdev_set_parent_bus(dev, bus, errp)) { + return false; + } + } else { + assert(!DEVICE_GET_CLASS(dev)->bus_type); } - gpio_list->num_out += n; -} - -void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n) -{ - qdev_init_gpio_out_named(dev, pins, NULL, n); -} - -qemu_irq qdev_get_gpio_in_named(DeviceState *dev, const char *name, int n) -{ - NamedGPIOList *gpio_list = qdev_get_named_gpio_list(dev, name); - assert(n >= 0 && n < gpio_list->num_in); - return gpio_list->in[n]; + return object_property_set_bool(OBJECT(dev), "realized", true, errp); } -qemu_irq qdev_get_gpio_in(DeviceState *dev, int n) +bool qdev_realize_and_unref(DeviceState *dev, BusState *bus, Error **errp) { - return qdev_get_gpio_in_named(dev, NULL, n); -} + bool ret; -void qdev_connect_gpio_out_named(DeviceState *dev, const char *name, int n, - qemu_irq pin) -{ - char *propname = g_strdup_printf("%s[%d]", - name ? name : "unnamed-gpio-out", n); - if (pin) { - /* We need a name for object_property_set_link to work. If the - * object has a parent, object_property_add_child will come back - * with an error without doing anything. If it has none, it will - * never fail. So we can just call it with a NULL Error pointer. - */ - object_property_add_child(container_get(qdev_get_machine(), - "/unattached"), - "non-qdev-gpio[*]", OBJECT(pin), NULL); - } - object_property_set_link(OBJECT(dev), OBJECT(pin), propname, &error_abort); - g_free(propname); + ret = qdev_realize(dev, bus, errp); + object_unref(OBJECT(dev)); + return ret; } -qemu_irq qdev_get_gpio_out_connector(DeviceState *dev, const char *name, int n) +void qdev_unrealize(DeviceState *dev) { - char *propname = g_strdup_printf("%s[%d]", - name ? name : "unnamed-gpio-out", n); - - qemu_irq ret = (qemu_irq)object_property_get_link(OBJECT(dev), propname, - NULL); - - return ret; + object_property_set_bool(OBJECT(dev), "realized", false, &error_abort); } -/* disconnect a GPIO output, returning the disconnected input (if any) */ - -static qemu_irq qdev_disconnect_gpio_out_named(DeviceState *dev, - const char *name, int n) +static int qdev_assert_realized_properly_cb(Object *obj, void *opaque) { - char *propname = g_strdup_printf("%s[%d]", - name ? name : "unnamed-gpio-out", n); + DeviceState *dev = DEVICE(object_dynamic_cast(obj, TYPE_DEVICE)); + DeviceClass *dc; - qemu_irq ret = (qemu_irq)object_property_get_link(OBJECT(dev), propname, - NULL); - if (ret) { - object_property_set_link(OBJECT(dev), NULL, propname, NULL); + if (dev) { + dc = DEVICE_GET_CLASS(dev); + assert(dev->realized); + assert(dev->parent_bus || !dc->bus_type); } - g_free(propname); - return ret; + return 0; } -qemu_irq qdev_intercept_gpio_out(DeviceState *dev, qemu_irq icpt, - const char *name, int n) +void qdev_assert_realized_properly(void) { - qemu_irq disconnected = qdev_disconnect_gpio_out_named(dev, name, n); - qdev_connect_gpio_out_named(dev, name, n, icpt); - return disconnected; + object_child_foreach_recursive(object_get_root(), + qdev_assert_realized_properly_cb, NULL); } -void qdev_connect_gpio_out(DeviceState * dev, int n, qemu_irq pin) +bool qdev_machine_modified(void) { - qdev_connect_gpio_out_named(dev, NULL, n, pin); + return qdev_hot_added || qdev_hot_removed; } -void qdev_pass_gpios(DeviceState *dev, DeviceState *container, - const char *name) +BusState *qdev_get_parent_bus(const DeviceState *dev) { - int i; - NamedGPIOList *ngl = qdev_get_named_gpio_list(dev, name); - - for (i = 0; i < ngl->num_in; i++) { - const char *nm = ngl->name ? ngl->name : "unnamed-gpio-in"; - char *propname = g_strdup_printf("%s[%d]", nm, i); - - object_property_add_alias(OBJECT(container), propname, - OBJECT(dev), propname, - &error_abort); - g_free(propname); - } - for (i = 0; i < ngl->num_out; i++) { - const char *nm = ngl->name ? ngl->name : "unnamed-gpio-out"; - char *propname = g_strdup_printf("%s[%d]", nm, i); - - object_property_add_alias(OBJECT(container), propname, - OBJECT(dev), propname, - &error_abort); - g_free(propname); - } - QLIST_REMOVE(ngl, node); - QLIST_INSERT_HEAD(&container->gpios, ngl, node); + return dev->parent_bus; } BusState *qdev_get_child_bus(DeviceState *dev, const char *name) @@ -579,17 +392,19 @@ DeviceState *qdev_find_recursive(BusState *bus, const char *id) DeviceState *ret; BusState *child; - QTAILQ_FOREACH(kid, &bus->children, sibling) { - DeviceState *dev = kid->child; + WITH_RCU_READ_LOCK_GUARD() { + QTAILQ_FOREACH_RCU(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; - if (dev->id && strcmp(dev->id, id) == 0) { - return dev; - } + if (dev->id && strcmp(dev->id, id) == 0) { + return dev; + } - QLIST_FOREACH(child, &dev->child_bus, sibling) { - ret = qdev_find_recursive(child, id); - if (ret) { - return ret; + QLIST_FOREACH(child, &dev->child_bus, sibling) { + ret = qdev_find_recursive(child, id); + if (ret) { + return ret; + } } } } @@ -612,156 +427,24 @@ char *qdev_get_dev_path(DeviceState *dev) return NULL; } -/** - * Legacy property handling - */ - -static void qdev_get_legacy_property(Object *obj, Visitor *v, - const char *name, void *opaque, - Error **errp) +void qdev_add_unplug_blocker(DeviceState *dev, Error *reason) { - DeviceState *dev = DEVICE(obj); - Property *prop = opaque; - - char buffer[1024]; - char *ptr = buffer; - - prop->info->print(dev, prop, buffer, sizeof(buffer)); - visit_type_str(v, name, &ptr, errp); + dev->unplug_blockers = g_slist_prepend(dev->unplug_blockers, reason); } -/** - * qdev_property_add_legacy: - * @dev: Device to add the property to. - * @prop: The qdev property definition. - * @errp: location to store error information. - * - * Add a legacy QOM property to @dev for qdev property @prop. - * On error, store error in @errp. - * - * Legacy properties are string versions of QOM properties. The format of - * the string depends on the property type. Legacy properties are only - * needed for "info qtree". - * - * Do not use this in new code! QOM Properties added through this interface - * will be given names in the "legacy" namespace. - */ -static void qdev_property_add_legacy(DeviceState *dev, Property *prop, - Error **errp) +void qdev_del_unplug_blocker(DeviceState *dev, Error *reason) { - gchar *name; - - /* Register pointer properties as legacy properties */ - if (!prop->info->print && prop->info->get) { - return; - } - - if (prop->info->create) { - return; - } - - name = g_strdup_printf("legacy-%s", prop->name); - object_property_add(OBJECT(dev), name, "str", - prop->info->print ? qdev_get_legacy_property : prop->info->get, - NULL, - NULL, - prop, errp); - - g_free(name); + dev->unplug_blockers = g_slist_remove(dev->unplug_blockers, reason); } -/** - * qdev_property_add_static: - * @dev: Device to add the property to. - * @prop: The qdev property definition. - * @errp: location to store error information. - * - * Add a static QOM property to @dev for qdev property @prop. - * On error, store error in @errp. Static properties access data in a struct. - * The type of the QOM property is derived from prop->info. - */ -void qdev_property_add_static(DeviceState *dev, Property *prop, - Error **errp) +bool qdev_unplug_blocked(DeviceState *dev, Error **errp) { - Error *local_err = NULL; - Object *obj = OBJECT(dev); - - if (prop->info->create) { - prop->info->create(obj, prop, &local_err); - } else { - /* - * TODO qdev_prop_ptr does not have getters or setters. It must - * go now that it can be replaced with links. The test should be - * removed along with it: all static properties are read/write. - */ - if (!prop->info->get && !prop->info->set) { - return; - } - object_property_add(obj, prop->name, prop->info->name, - prop->info->get, prop->info->set, - prop->info->release, - prop, &local_err); - } - - if (local_err) { - error_propagate(errp, local_err); - return; - } - - object_property_set_description(obj, prop->name, - prop->info->description, - &error_abort); - - if (prop->set_default) { - prop->info->set_default_value(obj, prop); + if (dev->unplug_blockers) { + error_propagate(errp, error_copy(dev->unplug_blockers->data)); + return true; } -} - -/* @qdev_alias_all_properties - Add alias properties to the source object for - * all qdev properties on the target DeviceState. - */ -void qdev_alias_all_properties(DeviceState *target, Object *source) -{ - ObjectClass *class; - Property *prop; - class = object_get_class(OBJECT(target)); - do { - DeviceClass *dc = DEVICE_CLASS(class); - - for (prop = dc->props; prop && prop->name; prop++) { - object_property_add_alias(source, prop->name, - OBJECT(target), prop->name, - &error_abort); - } - class = object_class_get_parent(class); - } while (class != object_class_by_name(TYPE_DEVICE)); -} - -static int qdev_add_hotpluggable_device(Object *obj, void *opaque) -{ - GSList **list = opaque; - DeviceState *dev = (DeviceState *)object_dynamic_cast(OBJECT(obj), - TYPE_DEVICE); - - if (dev == NULL) { - return 0; - } - - if (dev->realized && object_property_get_bool(obj, "hotpluggable", NULL)) { - *list = g_slist_append(*list, dev); - } - - return 0; -} - -GSList *qdev_build_hotpluggable_device_list(Object *peripheral) -{ - GSList *list = NULL; - - object_child_foreach(peripheral, qdev_add_hotpluggable_device, &list); - - return list; + return false; } static bool device_get_realized(Object *obj, Error **errp) @@ -770,12 +453,12 @@ static bool device_get_realized(Object *obj, Error **errp) return dev->realized; } -static bool check_only_migratable(Object *obj, Error **err) +static bool check_only_migratable(Object *obj, Error **errp) { DeviceClass *dc = DEVICE_GET_CLASS(obj); if (!vmstate_check_only_migratable(dc->vmsd)) { - error_setg(err, "Device %s is not migratable, but " + error_setg(errp, "Device %s is not migratable, but " "--only-migratable was specified", object_get_typename(obj)); return false; @@ -790,6 +473,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp) DeviceClass *dc = DEVICE_GET_CLASS(dev); HotplugHandler *hotplug_ctrl; BusState *bus; + NamedClockList *ncl; Error *local_err = NULL; bool unattached_parent = false; static int unattached_count; @@ -800,7 +484,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp) } if (value && !dev->realized) { - if (!check_only_migratable(obj, &local_err)) { + if (!check_only_migratable(obj, errp)) { goto fail; } @@ -809,7 +493,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp) object_property_add_child(container_get(qdev_get_machine(), "/unattached"), - name, obj, &error_abort); + name, obj); unattached_parent = true; g_free(name); } @@ -824,31 +508,31 @@ static void device_set_realized(Object *obj, bool value, Error **errp) if (dc->realize) { dc->realize(dev, &local_err); - } - - if (local_err != NULL) { - goto fail; + if (local_err != NULL) { + goto fail; + } } DEVICE_LISTENER_CALL(realize, Forward, dev); - if (hotplug_ctrl) { - hotplug_handler_plug(hotplug_ctrl, dev, &local_err); - } - - if (local_err != NULL) { - goto post_realize_fail; - } - /* * always free/re-initialize here since the value cannot be cleaned up * in device_unrealize due to its usage later on in the unplug path */ g_free(dev->canonical_path); dev->canonical_path = object_get_canonical_path(OBJECT(dev)); + QLIST_FOREACH(ncl, &dev->clocks, node) { + if (ncl->alias) { + continue; + } else { + clock_setup_canonical_path(ncl->clock); + } + } if (qdev_get_vmsd(dev)) { - if (vmstate_register_with_alias_id(dev, -1, qdev_get_vmsd(dev), dev, + if (vmstate_register_with_alias_id(VMSTATE_IF(dev), + VMSTATE_INSTANCE_ID_ANY, + qdev_get_vmsd(dev), dev, dev->instance_id_alias, dev->alias_required_for_version, &local_err) < 0) { @@ -856,66 +540,94 @@ static void device_set_realized(Object *obj, bool value, Error **errp) } } + /* + * Clear the reset state, in case the object was previously unrealized + * with a dirty state. + */ + resettable_state_clear(&dev->reset); + QLIST_FOREACH(bus, &dev->child_bus, sibling) { - object_property_set_bool(OBJECT(bus), true, "realized", - &local_err); - if (local_err != NULL) { + if (!qbus_realize(bus, errp)) { goto child_realize_fail; } } if (dev->hotplugged) { - device_reset(dev); + /* + * Reset the device, as well as its subtree which, at this point, + * should be realized too. + */ + resettable_assert_reset(OBJECT(dev), RESET_TYPE_COLD); + resettable_change_parent(OBJECT(dev), OBJECT(dev->parent_bus), + NULL); + resettable_release_reset(OBJECT(dev), RESET_TYPE_COLD); } dev->pending_deleted_event = false; if (hotplug_ctrl) { - hotplug_handler_post_plug(hotplug_ctrl, dev); - } + hotplug_handler_plug(hotplug_ctrl, dev, &local_err); + if (local_err != NULL) { + goto child_realize_fail; + } + } + + qatomic_store_release(&dev->realized, value); + } else if (!value && dev->realized) { - Error **local_errp = NULL; + + /* + * Change the value so that any concurrent users are aware + * that the device is going to be unrealized + * + * TODO: change .realized property to enum that states + * each phase of the device realization/unrealization + */ + + qatomic_set(&dev->realized, value); + /* + * Ensure that concurrent users see this update prior to + * any other changes done by unrealize. + */ + smp_wmb(); + QLIST_FOREACH(bus, &dev->child_bus, sibling) { - local_errp = local_err ? NULL : &local_err; - object_property_set_bool(OBJECT(bus), false, "realized", - local_errp); + qbus_unrealize(bus); } if (qdev_get_vmsd(dev)) { - vmstate_unregister(dev, qdev_get_vmsd(dev), dev); + vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev); } if (dc->unrealize) { - local_errp = local_err ? NULL : &local_err; - dc->unrealize(dev, local_errp); + dc->unrealize(dev); } dev->pending_deleted_event = true; DEVICE_LISTENER_CALL(unrealize, Reverse, dev); } - if (local_err != NULL) { - goto fail; - } - - dev->realized = value; + assert(local_err == NULL); return; child_realize_fail: QLIST_FOREACH(bus, &dev->child_bus, sibling) { - object_property_set_bool(OBJECT(bus), false, "realized", - NULL); + qbus_unrealize(bus); } if (qdev_get_vmsd(dev)) { - vmstate_unregister(dev, qdev_get_vmsd(dev), dev); + vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev); } post_realize_fail: g_free(dev->canonical_path); dev->canonical_path = NULL; if (dc->unrealize) { - dc->unrealize(dev, NULL); + dc->unrealize(dev); } fail: error_propagate(errp, local_err); if (unattached_parent) { + /* + * Beware, this doesn't just revert + * object_property_add_child(), it also runs bus_remove()! + */ object_unparent(OBJECT(dev)); unattached_count--; } @@ -930,7 +642,7 @@ static bool device_get_hotpluggable(Object *obj, Error **errp) qbus_is_hotpluggable(dev->parent_bus)); } -static bool device_get_hotplugged(Object *obj, Error **err) +static bool device_get_hotplugged(Object *obj, Error **errp) { DeviceState *dev = DEVICE(obj); @@ -940,42 +652,27 @@ static bool device_get_hotplugged(Object *obj, Error **err) static void device_initfn(Object *obj) { DeviceState *dev = DEVICE(obj); - ObjectClass *class; - Property *prop; - if (qdev_hotplug) { + if (phase_check(PHASE_MACHINE_READY)) { dev->hotplugged = 1; qdev_hot_added = true; } dev->instance_id_alias = -1; dev->realized = false; + dev->allow_unplug_during_migration = false; - object_property_add_bool(obj, "realized", - device_get_realized, device_set_realized, NULL); - object_property_add_bool(obj, "hotpluggable", - device_get_hotpluggable, NULL, NULL); - object_property_add_bool(obj, "hotplugged", - device_get_hotplugged, NULL, - &error_abort); - - class = object_get_class(OBJECT(dev)); - do { - for (prop = DEVICE_CLASS(class)->props; prop && prop->name; prop++) { - qdev_property_add_legacy(dev, prop, &error_abort); - qdev_property_add_static(dev, prop, &error_abort); - } - class = object_class_get_parent(class); - } while (class != object_class_by_name(TYPE_DEVICE)); - - object_property_add_link(OBJECT(dev), "parent_bus", TYPE_BUS, - (Object **)&dev->parent_bus, NULL, 0, - &error_abort); QLIST_INIT(&dev->gpios); + QLIST_INIT(&dev->clocks); } static void device_post_init(Object *obj) { + /* + * Note: ordered so that the user's global properties take + * precedence. + */ + object_apply_compat_props(obj); qdev_prop_set_globals(DEVICE(obj)); } @@ -986,6 +683,8 @@ static void device_finalize(Object *obj) DeviceState *dev = DEVICE(obj); + g_assert(!dev->unplug_blockers); + QLIST_FOREACH_SAFE(ngl, &dev->gpios, node, next) { QLIST_REMOVE(ngl, node); qemu_free_irqs(ngl->in, ngl->num_in); @@ -996,16 +695,19 @@ static void device_finalize(Object *obj) */ } + qdev_finalize_clocklist(dev); + /* Only send event if the device had been completely realized */ if (dev->pending_deleted_event) { g_assert(dev->canonical_path); - qapi_event_send_device_deleted(!!dev->id, dev->id, dev->canonical_path); + qapi_event_send_device_deleted(dev->id, dev->canonical_path); g_free(dev->canonical_path); dev->canonical_path = NULL; } - qemu_opts_del(dev->opts); + qobject_unref(dev->opts); + g_free(dev->id); } static void device_class_base_init(ObjectClass *class, void *data) @@ -1015,7 +717,7 @@ static void device_class_base_init(ObjectClass *class, void *data) /* We explicitly look up properties in the superclasses, * so do not propagate them to the subclasses. */ - klass->props = NULL; + klass->props_ = NULL; } static void device_unparent(Object *obj) @@ -1024,7 +726,7 @@ static void device_unparent(Object *obj) BusState *bus; if (dev->realized) { - object_property_set_bool(obj, false, "realized", NULL); + qdev_unrealize(dev); } while (dev->num_child_bus) { bus = QLIST_FIRST(&dev->child_bus); @@ -1037,9 +739,70 @@ static void device_unparent(Object *obj) } } +static char * +device_vmstate_if_get_id(VMStateIf *obj) +{ + DeviceState *dev = DEVICE(obj); + + return qdev_get_dev_path(dev); +} + +/** + * device_phases_reset: + * Transition reset method for devices to allow moving + * smoothly from legacy reset method to multi-phases + */ +static void device_phases_reset(DeviceState *dev) +{ + ResettableClass *rc = RESETTABLE_GET_CLASS(dev); + + if (rc->phases.enter) { + rc->phases.enter(OBJECT(dev), RESET_TYPE_COLD); + } + if (rc->phases.hold) { + rc->phases.hold(OBJECT(dev)); + } + if (rc->phases.exit) { + rc->phases.exit(OBJECT(dev)); + } +} + +static void device_transitional_reset(Object *obj) +{ + DeviceClass *dc = DEVICE_GET_CLASS(obj); + + /* + * This will call either @device_phases_reset (for multi-phases transitioned + * devices) or a device's specific method for not-yet transitioned devices. + * In both case, it does not reset children. + */ + if (dc->reset) { + dc->reset(DEVICE(obj)); + } +} + +/** + * device_get_transitional_reset: + * check if the device's class is ready for multi-phase + */ +static ResettableTrFunction device_get_transitional_reset(Object *obj) +{ + DeviceClass *dc = DEVICE_GET_CLASS(obj); + if (dc->reset != device_phases_reset) { + /* + * dc->reset has been overridden by a subclass, + * the device is not ready for multi phase yet. + */ + return device_transitional_reset; + } + return NULL; +} + static void device_class_init(ObjectClass *class, void *data) { DeviceClass *dc = DEVICE_CLASS(class); + VMStateIfClass *vc = VMSTATE_IF_CLASS(class); + ResettableClass *rc = RESETTABLE_CLASS(class); class->unparent = device_unparent; @@ -1051,6 +814,34 @@ static void device_class_init(ObjectClass *class, void *data) */ dc->hotpluggable = true; dc->user_creatable = true; + vc->get_id = device_vmstate_if_get_id; + rc->get_state = device_get_reset_state; + rc->child_foreach = device_reset_child_foreach; + + /* + * @device_phases_reset is put as the default reset method below, allowing + * to do the multi-phase transition from base classes to leaf classes. It + * allows a legacy-reset Device class to extend a multi-phases-reset + * Device class for the following reason: + * + If a base class B has been moved to multi-phase, then it does not + * override this default reset method and may have defined phase methods. + * + A child class C (extending class B) which uses + * device_class_set_parent_reset() (or similar means) to override the + * reset method will still work as expected. @device_phases_reset function + * will be registered as the parent reset method and effectively call + * parent reset phases. + */ + dc->reset = device_phases_reset; + rc->get_transitional_function = device_get_transitional_reset; + + object_class_property_add_bool(class, "realized", + device_get_realized, device_set_realized); + object_class_property_add_bool(class, "hotpluggable", + device_get_hotpluggable, NULL); + object_class_property_add_bool(class, "hotplugged", + device_get_hotplugged, NULL); + object_class_property_add_link(class, "parent_bus", TYPE_BUS, + offsetof(DeviceState, parent_bus), NULL, 0); } void device_class_set_parent_reset(DeviceClass *dc, @@ -1077,15 +868,6 @@ void device_class_set_parent_unrealize(DeviceClass *dc, dc->unrealize = dev_unrealize; } -void device_reset(DeviceState *dev) -{ - DeviceClass *klass = DEVICE_GET_CLASS(dev); - - if (klass->reset) { - klass->reset(dev); - } -} - Object *qdev_get_machine(void) { static Object *dev; @@ -1097,6 +879,27 @@ Object *qdev_get_machine(void) return dev; } +char *qdev_get_human_name(DeviceState *dev) +{ + g_assert(dev != NULL); + + return dev->id ? + g_strdup(dev->id) : object_get_canonical_path(OBJECT(dev)); +} + +static MachineInitPhase machine_phase; + +bool phase_check(MachineInitPhase phase) +{ + return machine_phase >= phase; +} + +void phase_advance(MachineInitPhase phase) +{ + assert(machine_phase == phase - 1); + machine_phase = phase; +} + static const TypeInfo device_type_info = { .name = TYPE_DEVICE, .parent = TYPE_OBJECT, @@ -1108,6 +911,11 @@ static const TypeInfo device_type_info = { .class_init = device_class_init, .abstract = true, .class_size = sizeof(DeviceClass), + .interfaces = (InterfaceInfo[]) { + { TYPE_VMSTATE_IF }, + { TYPE_RESETTABLE_INTERFACE }, + { } + } }; static void qdev_register_types(void) |