summaryrefslogtreecommitdiffstats
path: root/hw/core/resettable.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/core/resettable.c')
-rw-r--r--hw/core/resettable.c300
1 files changed, 300 insertions, 0 deletions
diff --git a/hw/core/resettable.c b/hw/core/resettable.c
new file mode 100644
index 00000000..c3df75c6
--- /dev/null
+++ b/hw/core/resettable.c
@@ -0,0 +1,300 @@
+/*
+ * 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.
+ *
+ * exit_phase_in_progress:
+ * count the number of exit phase we are in.
+ *
+ * Note: These flags are only used to guarantee (using asserts) that the reset
+ * API is used correctly. We can use global variables 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;
+static unsigned exit_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);
+
+ exit_phase_in_progress += 1;
+ resettable_phase_exit(obj, NULL, type);
+ exit_phase_in_progress -= 1;
+
+ 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 == 0) {
+ 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->exit_phase_in_progress = false;
+ trace_resettable_phase_exit_end(obj, obj_typename, s->count);
+}
+
+/*
+ * resettable_get_count:
+ * Get the count of the Resettable object @obj. Return 0 if @obj is NULL.
+ */
+static unsigned resettable_get_count(Object *obj)
+{
+ if (obj) {
+ ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+ return rc->get_state(obj)->count;
+ }
+ return 0;
+}
+
+void resettable_change_parent(Object *obj, Object *newp, Object *oldp)
+{
+ ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
+ ResettableState *s = rc->get_state(obj);
+ unsigned newp_count = resettable_get_count(newp);
+ unsigned oldp_count = resettable_get_count(oldp);
+
+ /*
+ * Ensure we do not change parent when in enter or exit phase.
+ * During these phases, the reset subtree being updated is partly in reset
+ * and partly not in reset (it depends on the actual position in
+ * resettable_child_foreach()s). We are not able to tell in which part is a
+ * leaving or arriving device. Thus we cannot set the reset count of the
+ * moving device to the proper value.
+ */
+ assert(!enter_phase_in_progress && !exit_phase_in_progress);
+ trace_resettable_change_parent(obj, oldp, oldp_count, newp, newp_count);
+
+ /*
+ * At most one of the two 'for' loops will be executed below
+ * in order to cope with the difference between the two counts.
+ */
+ /* if newp is more reset than oldp */
+ for (unsigned i = oldp_count; i < newp_count; i++) {
+ resettable_assert_reset(obj, RESET_TYPE_COLD);
+ }
+ /*
+ * if obj is leaving a bus under reset, we need to ensure
+ * hold phase is not pending.
+ */
+ if (oldp_count && s->hold_phase_pending) {
+ resettable_phase_hold(obj, NULL, RESET_TYPE_COLD);
+ }
+ /* if oldp is more reset than newp */
+ for (unsigned i = newp_count; i < oldp_count; i++) {
+ resettable_release_reset(obj, RESET_TYPE_COLD);
+ }
+}
+
+void resettable_cold_reset_fn(void *opaque)
+{
+ resettable_reset((Object *) opaque, RESET_TYPE_COLD);
+}
+
+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)