/* * 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 == 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); } /* * 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)