hw/core: create Resettable QOM interface

This commit defines an interface allowing multi-phase reset. This aims
to solve a problem of the actual single-phase reset (built in
DeviceClass and BusClass): reset behavior is dependent on the order
in which reset handlers are called. In particular doing external
side-effect (like setting an qemu_irq) is problematic because receiving
object may not be reset yet.

The Resettable interface divides the reset in 3 well defined phases.
To reset an object tree, all 1st phases are executed then all 2nd then
all 3rd. See the comments in include/hw/resettable.h for a more complete
description. The interface defines 3 phases to let the future
possibility of holding an object into reset for some time.

The qdev/qbus reset in DeviceClass and BusClass will be modified in
following commits to use this interface. A mechanism is provided
to allow executing a transitional reset handler in place of the 2nd
phase which is executed in children-then-parent order inside a tree.
This will allow to transition devices and buses smoothly while
keeping the exact current qdev/qbus reset behavior for now.

Documentation will be added in a following commit.

Signed-off-by: Damien Hedde <damien.hedde@greensocs.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
index a522b72..9e41ec9 100644
--- a/hw/core/Makefile.objs
+++ b/hw/core/Makefile.objs
@@ -2,6 +2,7 @@
 common-obj-y += qdev.o qdev-properties.o
 common-obj-y += bus.o
 common-obj-y += cpu.o
+common-obj-y += resettable.o
 common-obj-y += hotplug.o
 common-obj-y += vmstate-if.o
 # irq.o needed for qdev GPIO handling:
diff --git a/hw/core/resettable.c b/hw/core/resettable.c
new file mode 100644
index 0000000..9133208
--- /dev/null
+++ b/hw/core/resettable.c
@@ -0,0 +1,238 @@
+/*
+ * Resettable interface.
+ *
+ * Copyright (c) 2019 GreenSocs SAS
+ *
+ * Authors:
+ *   Damien Hedde
+ *
+ * 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 "qemu/osdep.h"
+#include "qemu/module.h"
+#include "hw/resettable.h"
+#include "trace.h"
+
+/**
+ * resettable_phase_enter/hold/exit:
+ * Function executing a phase recursively in a resettable object and its
+ * children.
+ */
+static void resettable_phase_enter(Object *obj, void *opaque, ResetType type);
+static void resettable_phase_hold(Object *obj, void *opaque, ResetType type);
+static void resettable_phase_exit(Object *obj, void *opaque, ResetType type);
+
+/**
+ * enter_phase_in_progress:
+ * True if we are currently in reset enter phase.
+ *
+ * Note: This flag is only used to guarantee (using asserts) that the reset
+ * API is used correctly. We can use a global variable because we rely on the
+ * iothread mutex to ensure only one reset operation is in a progress at a
+ * given time.
+ */
+static bool enter_phase_in_progress;
+
+void resettable_reset(Object *obj, ResetType type)
+{
+    trace_resettable_reset(obj, type);
+    resettable_assert_reset(obj, type);
+    resettable_release_reset(obj, type);
+}
+
+void resettable_assert_reset(Object *obj, ResetType type)
+{
+    /* TODO: change this assert when adding support for other reset types */
+    assert(type == RESET_TYPE_COLD);
+    trace_resettable_reset_assert_begin(obj, type);
+    assert(!enter_phase_in_progress);
+
+    enter_phase_in_progress = true;
+    resettable_phase_enter(obj, NULL, type);
+    enter_phase_in_progress = false;
+
+    resettable_phase_hold(obj, NULL, type);
+
+    trace_resettable_reset_assert_end(obj);
+}
+
+void resettable_release_reset(Object *obj, ResetType type)
+{
+    /* TODO: change this assert when adding support for other reset types */
+    assert(type == RESET_TYPE_COLD);
+    trace_resettable_reset_release_begin(obj, type);
+    assert(!enter_phase_in_progress);
+
+    resettable_phase_exit(obj, NULL, type);
+
+    trace_resettable_reset_release_end(obj);
+}
+
+bool resettable_is_in_reset(Object *obj)
+{
+    ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+    ResettableState *s = rc->get_state(obj);
+
+    return s->count > 0;
+}
+
+/**
+ * resettable_child_foreach:
+ * helper to avoid checking the existence of the method.
+ */
+static void resettable_child_foreach(ResettableClass *rc, Object *obj,
+                                     ResettableChildCallback cb,
+                                     void *opaque, ResetType type)
+{
+    if (rc->child_foreach) {
+        rc->child_foreach(obj, cb, opaque, type);
+    }
+}
+
+/**
+ * resettable_get_tr_func:
+ * helper to fetch transitional reset callback if any.
+ */
+static ResettableTrFunction resettable_get_tr_func(ResettableClass *rc,
+                                                   Object *obj)
+{
+    ResettableTrFunction tr_func = NULL;
+    if (rc->get_transitional_function) {
+        tr_func = rc->get_transitional_function(obj);
+    }
+    return tr_func;
+}
+
+static void resettable_phase_enter(Object *obj, void *opaque, ResetType type)
+{
+    ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+    ResettableState *s = rc->get_state(obj);
+    const char *obj_typename = object_get_typename(obj);
+    bool action_needed = false;
+
+    /* exit phase has to finish properly before entering back in reset */
+    assert(!s->exit_phase_in_progress);
+
+    trace_resettable_phase_enter_begin(obj, obj_typename, s->count, type);
+
+    /* Only take action if we really enter reset for the 1st time. */
+    /*
+     * TODO: if adding more ResetType support, some additional checks
+     * are probably needed here.
+     */
+    if (s->count++ == 0) {
+        action_needed = true;
+    }
+    /*
+     * We limit the count to an arbitrary "big" value. The value is big
+     * enough not to be triggered normally.
+     * The assert will stop an infinite loop if there is a cycle in the
+     * reset tree. The loop goes through resettable_foreach_child below
+     * which at some point will call us again.
+     */
+    assert(s->count <= 50);
+
+    /*
+     * handle the children even if action_needed is at false so that
+     * child counts are incremented too
+     */
+    resettable_child_foreach(rc, obj, resettable_phase_enter, NULL, type);
+
+    /* execute enter phase for the object if needed */
+    if (action_needed) {
+        trace_resettable_phase_enter_exec(obj, obj_typename, type,
+                                          !!rc->phases.enter);
+        if (rc->phases.enter && !resettable_get_tr_func(rc, obj)) {
+            rc->phases.enter(obj, type);
+        }
+        s->hold_phase_pending = true;
+    }
+    trace_resettable_phase_enter_end(obj, obj_typename, s->count);
+}
+
+static void resettable_phase_hold(Object *obj, void *opaque, ResetType type)
+{
+    ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+    ResettableState *s = rc->get_state(obj);
+    const char *obj_typename = object_get_typename(obj);
+
+    /* exit phase has to finish properly before entering back in reset */
+    assert(!s->exit_phase_in_progress);
+
+    trace_resettable_phase_hold_begin(obj, obj_typename, s->count, type);
+
+    /* handle children first */
+    resettable_child_foreach(rc, obj, resettable_phase_hold, NULL, type);
+
+    /* exec hold phase */
+    if (s->hold_phase_pending) {
+        s->hold_phase_pending = false;
+        ResettableTrFunction tr_func = resettable_get_tr_func(rc, obj);
+        trace_resettable_phase_hold_exec(obj, obj_typename, !!rc->phases.hold);
+        if (tr_func) {
+            trace_resettable_transitional_function(obj, obj_typename);
+            tr_func(obj);
+        } else if (rc->phases.hold) {
+            rc->phases.hold(obj);
+        }
+    }
+    trace_resettable_phase_hold_end(obj, obj_typename, s->count);
+}
+
+static void resettable_phase_exit(Object *obj, void *opaque, ResetType type)
+{
+    ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+    ResettableState *s = rc->get_state(obj);
+    const char *obj_typename = object_get_typename(obj);
+
+    assert(!s->exit_phase_in_progress);
+    trace_resettable_phase_exit_begin(obj, obj_typename, s->count, type);
+
+    /* exit_phase_in_progress ensures this phase is 'atomic' */
+    s->exit_phase_in_progress = true;
+    resettable_child_foreach(rc, obj, resettable_phase_exit, NULL, type);
+
+    assert(s->count > 0);
+    if (s->count == 1) {
+        trace_resettable_phase_exit_exec(obj, obj_typename, !!rc->phases.exit);
+        if (rc->phases.exit && !resettable_get_tr_func(rc, obj)) {
+            rc->phases.exit(obj);
+        }
+        s->count = 0;
+    }
+    s->exit_phase_in_progress = false;
+    trace_resettable_phase_exit_end(obj, obj_typename, s->count);
+}
+
+void resettable_class_set_parent_phases(ResettableClass *rc,
+                                        ResettableEnterPhase enter,
+                                        ResettableHoldPhase hold,
+                                        ResettableExitPhase exit,
+                                        ResettablePhases *parent_phases)
+{
+    *parent_phases = rc->phases;
+    if (enter) {
+        rc->phases.enter = enter;
+    }
+    if (hold) {
+        rc->phases.hold = hold;
+    }
+    if (exit) {
+        rc->phases.exit = exit;
+    }
+}
+
+static const TypeInfo resettable_interface_info = {
+    .name       = TYPE_RESETTABLE_INTERFACE,
+    .parent     = TYPE_INTERFACE,
+    .class_size = sizeof(ResettableClass),
+};
+
+static void reset_register_types(void)
+{
+    type_register_static(&resettable_interface_info);
+}
+
+type_init(reset_register_types)
diff --git a/hw/core/trace-events b/hw/core/trace-events
index a375aa8..77d61cb 100644
--- a/hw/core/trace-events
+++ b/hw/core/trace-events
@@ -9,3 +9,20 @@
 qbus_reset_all(void *obj, const char *objtype) "obj=%p(%s)"
 qbus_reset_tree(void *obj, const char *objtype) "obj=%p(%s)"
 qdev_update_parent_bus(void *obj, const char *objtype, void *oldp, const char *oldptype, void *newp, const char *newptype) "obj=%p(%s) old_parent=%p(%s) new_parent=%p(%s)"
+
+# resettable.c
+resettable_reset(void *obj, int cold) "obj=%p cold=%d"
+resettable_reset_assert_begin(void *obj, int cold) "obj=%p cold=%d"
+resettable_reset_assert_end(void *obj) "obj=%p"
+resettable_reset_release_begin(void *obj, int cold) "obj=%p cold=%d"
+resettable_reset_release_end(void *obj) "obj=%p"
+resettable_phase_enter_begin(void *obj, const char *objtype, unsigned count, int type) "obj=%p(%s) count=%d type=%d"
+resettable_phase_enter_exec(void *obj, const char *objtype, int type, int has_method) "obj=%p(%s) type=%d method=%d"
+resettable_phase_enter_end(void *obj, const char *objtype, unsigned count) "obj=%p(%s) count=%d"
+resettable_phase_hold_begin(void *obj, const char *objtype, unsigned count, int type) "obj=%p(%s) count=%d type=%d"
+resettable_phase_hold_exec(void *obj, const char *objtype, int has_method) "obj=%p(%s) method=%d"
+resettable_phase_hold_end(void *obj, const char *objtype, unsigned count) "obj=%p(%s) count=%d"
+resettable_phase_exit_begin(void *obj, const char *objtype, unsigned count, int type) "obj=%p(%s) count=%d type=%d"
+resettable_phase_exit_exec(void *obj, const char *objtype, int has_method) "obj=%p(%s) method=%d"
+resettable_phase_exit_end(void *obj, const char *objtype, unsigned count) "obj=%p(%s) count=%d"
+resettable_transitional_function(void *obj, const char *objtype) "obj=%p(%s)"