diff options
Diffstat (limited to 'hw/core/bus.c')
-rw-r--r-- | hw/core/bus.c | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/hw/core/bus.c b/hw/core/bus.c new file mode 100644 index 000000000..c7831b529 --- /dev/null +++ b/hw/core/bus.c @@ -0,0 +1,341 @@ +/* + * Dynamic device configuration and creation -- buses. + * + * Copyright (c) 2009 CodeSourcery + * + * 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.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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-properties.h" +#include "qemu/ctype.h" +#include "qemu/module.h" +#include "qapi/error.h" + +void qbus_set_hotplug_handler(BusState *bus, Object *handler) +{ + object_property_set_link(OBJECT(bus), QDEV_HOTPLUG_HANDLER_PROPERTY, + handler, &error_abort); +} + +void qbus_set_bus_hotplug_handler(BusState *bus) +{ + qbus_set_hotplug_handler(bus, OBJECT(bus)); +} + +int qbus_walk_children(BusState *bus, + qdev_walkerfn *pre_devfn, qbus_walkerfn *pre_busfn, + qdev_walkerfn *post_devfn, qbus_walkerfn *post_busfn, + void *opaque) +{ + BusChild *kid; + int err; + + if (pre_busfn) { + err = pre_busfn(bus, opaque); + if (err) { + return err; + } + } + + WITH_RCU_READ_LOCK_GUARD() { + QTAILQ_FOREACH_RCU(kid, &bus->children, sibling) { + err = qdev_walk_children(kid->child, + pre_devfn, pre_busfn, + post_devfn, post_busfn, opaque); + if (err < 0) { + return err; + } + } + } + + if (post_busfn) { + err = post_busfn(bus, opaque); + if (err) { + return err; + } + } + + return 0; +} + +void bus_cold_reset(BusState *bus) +{ + resettable_reset(OBJECT(bus), RESET_TYPE_COLD); +} + +bool bus_is_in_reset(BusState *bus) +{ + return resettable_is_in_reset(OBJECT(bus)); +} + +static ResettableState *bus_get_reset_state(Object *obj) +{ + BusState *bus = BUS(obj); + return &bus->reset; +} + +static void bus_reset_child_foreach(Object *obj, ResettableChildCallback cb, + void *opaque, ResetType type) +{ + BusState *bus = BUS(obj); + BusChild *kid; + + WITH_RCU_READ_LOCK_GUARD() { + QTAILQ_FOREACH_RCU(kid, &bus->children, sibling) { + cb(OBJECT(kid->child), opaque, type); + } + } +} + +static void qbus_init_internal(BusState *bus, DeviceState *parent, + const char *name) +{ + const char *typename = object_get_typename(OBJECT(bus)); + BusClass *bc; + int i, bus_id; + + bus->parent = parent; + + if (name) { + bus->name = g_strdup(name); + } else if (bus->parent && bus->parent->id) { + /* parent device has id -> use it plus parent-bus-id for bus name */ + bus_id = bus->parent->num_child_bus; + bus->name = g_strdup_printf("%s.%d", bus->parent->id, bus_id); + } else { + /* no id -> use lowercase bus type plus global bus-id for bus name */ + bc = BUS_GET_CLASS(bus); + bus_id = bc->automatic_ids++; + bus->name = g_strdup_printf("%s.%d", typename, bus_id); + for (i = 0; bus->name[i]; i++) { + bus->name[i] = qemu_tolower(bus->name[i]); + } + } + + if (bus->parent) { + QLIST_INSERT_HEAD(&bus->parent->child_bus, bus, sibling); + bus->parent->num_child_bus++; + object_property_add_child(OBJECT(bus->parent), bus->name, OBJECT(bus)); + object_unref(OBJECT(bus)); + } else { + /* The only bus without a parent is the main system bus */ + assert(bus == sysbus_get_default()); + } +} + +static void bus_unparent(Object *obj) +{ + BusState *bus = BUS(obj); + BusChild *kid; + + /* Only the main system bus has no parent, and that bus is never freed */ + assert(bus->parent); + + while ((kid = QTAILQ_FIRST(&bus->children)) != NULL) { + DeviceState *dev = kid->child; + object_unparent(OBJECT(dev)); + } + QLIST_REMOVE(bus, sibling); + bus->parent->num_child_bus--; + bus->parent = NULL; +} + +void qbus_init(void *bus, size_t size, const char *typename, + DeviceState *parent, const char *name) +{ + object_initialize(bus, size, typename); + qbus_init_internal(bus, parent, name); +} + +BusState *qbus_new(const char *typename, DeviceState *parent, const char *name) +{ + BusState *bus; + + bus = BUS(object_new(typename)); + qbus_init_internal(bus, parent, name); + + return bus; +} + +bool qbus_realize(BusState *bus, Error **errp) +{ + return object_property_set_bool(OBJECT(bus), "realized", true, errp); +} + +void qbus_unrealize(BusState *bus) +{ + object_property_set_bool(OBJECT(bus), "realized", false, &error_abort); +} + +static bool bus_get_realized(Object *obj, Error **errp) +{ + BusState *bus = BUS(obj); + + return bus->realized; +} + +static void bus_set_realized(Object *obj, bool value, Error **errp) +{ + BusState *bus = BUS(obj); + BusClass *bc = BUS_GET_CLASS(bus); + BusChild *kid; + + if (value && !bus->realized) { + if (bc->realize) { + bc->realize(bus, errp); + } + + /* TODO: recursive realization */ + } else if (!value && bus->realized) { + WITH_RCU_READ_LOCK_GUARD() { + QTAILQ_FOREACH_RCU(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + qdev_unrealize(dev); + } + } + if (bc->unrealize) { + bc->unrealize(bus); + } + } + + bus->realized = value; +} + +static void qbus_initfn(Object *obj) +{ + BusState *bus = BUS(obj); + + QTAILQ_INIT(&bus->children); + object_property_add_link(obj, QDEV_HOTPLUG_HANDLER_PROPERTY, + TYPE_HOTPLUG_HANDLER, + (Object **)&bus->hotplug_handler, + object_property_allow_set_link, + 0); + object_property_add_bool(obj, "realized", + bus_get_realized, bus_set_realized); +} + +static char *default_bus_get_fw_dev_path(DeviceState *dev) +{ + return g_strdup(object_get_typename(OBJECT(dev))); +} + +/** + * bus_phases_reset: + * Transition reset method for buses to allow moving + * smoothly from legacy reset method to multi-phases + */ +static void bus_phases_reset(BusState *bus) +{ + ResettableClass *rc = RESETTABLE_GET_CLASS(bus); + + if (rc->phases.enter) { + rc->phases.enter(OBJECT(bus), RESET_TYPE_COLD); + } + if (rc->phases.hold) { + rc->phases.hold(OBJECT(bus)); + } + if (rc->phases.exit) { + rc->phases.exit(OBJECT(bus)); + } +} + +static void bus_transitional_reset(Object *obj) +{ + BusClass *bc = BUS_GET_CLASS(obj); + + /* + * This will call either @bus_phases_reset (for multi-phases transitioned + * buses) or a bus's specific method for not-yet transitioned buses. + * In both case, it does not reset children. + */ + if (bc->reset) { + bc->reset(BUS(obj)); + } +} + +/** + * bus_get_transitional_reset: + * check if the bus's class is ready for multi-phase + */ +static ResettableTrFunction bus_get_transitional_reset(Object *obj) +{ + BusClass *dc = BUS_GET_CLASS(obj); + if (dc->reset != bus_phases_reset) { + /* + * dc->reset has been overridden by a subclass, + * the bus is not ready for multi phase yet. + */ + return bus_transitional_reset; + } + return NULL; +} + +static void bus_class_init(ObjectClass *class, void *data) +{ + BusClass *bc = BUS_CLASS(class); + ResettableClass *rc = RESETTABLE_CLASS(class); + + class->unparent = bus_unparent; + bc->get_fw_dev_path = default_bus_get_fw_dev_path; + + rc->get_state = bus_get_reset_state; + rc->child_foreach = bus_reset_child_foreach; + + /* + * @bus_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 Bus class to extend a multi-phases-reset + * Bus 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 + * bus_class_set_parent_reset() (or similar means) to override the + * reset method will still work as expected. @bus_phases_reset function + * will be registered as the parent reset method and effectively call + * parent reset phases. + */ + bc->reset = bus_phases_reset; + rc->get_transitional_function = bus_get_transitional_reset; +} + +static void qbus_finalize(Object *obj) +{ + BusState *bus = BUS(obj); + + g_free(bus->name); +} + +static const TypeInfo bus_info = { + .name = TYPE_BUS, + .parent = TYPE_OBJECT, + .instance_size = sizeof(BusState), + .abstract = true, + .class_size = sizeof(BusClass), + .instance_init = qbus_initfn, + .instance_finalize = qbus_finalize, + .class_init = bus_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_RESETTABLE_INTERFACE }, + { } + }, +}; + +static void bus_register_types(void) +{ + type_register_static(&bus_info); +} + +type_init(bus_register_types) |