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