|  | /* | 
|  | * 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) |