| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  |  * 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 | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  |  * iothread mutex to ensure only one reset operation is in a progress at a | 
					
						
							|  |  |  |  * given time. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static bool enter_phase_in_progress; | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  | static unsigned exit_phase_in_progress; | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  |     exit_phase_in_progress += 1; | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  |     resettable_phase_exit(obj, NULL, type); | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  |     exit_phase_in_progress -= 1; | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:04 +00:00
										 |  |  | void resettable_cold_reset_fn(void *opaque) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     resettable_reset((Object *) opaque, RESET_TYPE_COLD); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 16:02:03 +00:00
										 |  |  | 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) |