 abb89dbf2b
			
		
	
	
		abb89dbf2b
		
	
	
	
	
		
			
			Deprecate device_legacy_reset(), qdev_reset_all() and qbus_reset_all() to be replaced by new functions device_cold_reset() and bus_cold_reset() which uses resettable API. Also introduce resettable_cold_reset_fn() which may be used as a replacement for qdev_reset_all_fn and qbus_reset_all_fn(). Following patches will be needed to look at legacy reset call sites and switch to resettable api. The legacy functions will be removed when unused. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-9-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
		
			
				
	
	
		
			302 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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)
 |