xref: /qemu/hw/core/resettable.c (revision 631f46d4)
1 /*
2  * Resettable interface.
3  *
4  * Copyright (c) 2019 GreenSocs SAS
5  *
6  * Authors:
7  *   Damien Hedde
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10  * See the COPYING file in the top-level directory.
11  */
12 
13 #include "qemu/osdep.h"
14 #include "qemu/module.h"
15 #include "hw/resettable.h"
16 #include "trace.h"
17 
18 /**
19  * resettable_phase_enter/hold/exit:
20  * Function executing a phase recursively in a resettable object and its
21  * children.
22  */
23 static void resettable_phase_enter(Object *obj, void *opaque, ResetType type);
24 static void resettable_phase_hold(Object *obj, void *opaque, ResetType type);
25 static void resettable_phase_exit(Object *obj, void *opaque, ResetType type);
26 
27 /**
28  * enter_phase_in_progress:
29  * True if we are currently in reset enter phase.
30  *
31  * exit_phase_in_progress:
32  * count the number of exit phase we are in.
33  *
34  * Note: These flags are only used to guarantee (using asserts) that the reset
35  * API is used correctly. We can use global variables because we rely on the
36  * iothread mutex to ensure only one reset operation is in a progress at a
37  * given time.
38  */
39 static bool enter_phase_in_progress;
40 static unsigned exit_phase_in_progress;
41 
resettable_reset(Object * obj,ResetType type)42 void resettable_reset(Object *obj, ResetType type)
43 {
44     trace_resettable_reset(obj, type);
45     resettable_assert_reset(obj, type);
46     resettable_release_reset(obj, type);
47 }
48 
resettable_assert_reset(Object * obj,ResetType type)49 void resettable_assert_reset(Object *obj, ResetType type)
50 {
51     trace_resettable_reset_assert_begin(obj, type);
52     assert(!enter_phase_in_progress);
53 
54     enter_phase_in_progress = true;
55     resettable_phase_enter(obj, NULL, type);
56     enter_phase_in_progress = false;
57 
58     resettable_phase_hold(obj, NULL, type);
59 
60     trace_resettable_reset_assert_end(obj);
61 }
62 
resettable_release_reset(Object * obj,ResetType type)63 void resettable_release_reset(Object *obj, ResetType type)
64 {
65     trace_resettable_reset_release_begin(obj, type);
66     assert(!enter_phase_in_progress);
67 
68     exit_phase_in_progress += 1;
69     resettable_phase_exit(obj, NULL, type);
70     exit_phase_in_progress -= 1;
71 
72     trace_resettable_reset_release_end(obj);
73 }
74 
resettable_is_in_reset(Object * obj)75 bool resettable_is_in_reset(Object *obj)
76 {
77     ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
78     ResettableState *s = rc->get_state(obj);
79 
80     return s->count > 0;
81 }
82 
83 /**
84  * resettable_child_foreach:
85  * helper to avoid checking the existence of the method.
86  */
resettable_child_foreach(ResettableClass * rc,Object * obj,ResettableChildCallback cb,void * opaque,ResetType type)87 static void resettable_child_foreach(ResettableClass *rc, Object *obj,
88                                      ResettableChildCallback cb,
89                                      void *opaque, ResetType type)
90 {
91     if (rc->child_foreach) {
92         rc->child_foreach(obj, cb, opaque, type);
93     }
94 }
95 
96 /**
97  * resettable_get_tr_func:
98  * helper to fetch transitional reset callback if any.
99  */
resettable_get_tr_func(ResettableClass * rc,Object * obj)100 static ResettableTrFunction resettable_get_tr_func(ResettableClass *rc,
101                                                    Object *obj)
102 {
103     ResettableTrFunction tr_func = NULL;
104     if (rc->get_transitional_function) {
105         tr_func = rc->get_transitional_function(obj);
106     }
107     return tr_func;
108 }
109 
resettable_phase_enter(Object * obj,void * opaque,ResetType type)110 static void resettable_phase_enter(Object *obj, void *opaque, ResetType type)
111 {
112     ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
113     ResettableState *s = rc->get_state(obj);
114     const char *obj_typename = object_get_typename(obj);
115     bool action_needed = false;
116 
117     /* exit phase has to finish properly before entering back in reset */
118     assert(!s->exit_phase_in_progress);
119 
120     trace_resettable_phase_enter_begin(obj, obj_typename, s->count, type);
121 
122     /* Only take action if we really enter reset for the 1st time. */
123     /*
124      * TODO: if adding more ResetType support, some additional checks
125      * are probably needed here.
126      */
127     if (s->count++ == 0) {
128         action_needed = true;
129     }
130     /*
131      * We limit the count to an arbitrary "big" value. The value is big
132      * enough not to be triggered normally.
133      * The assert will stop an infinite loop if there is a cycle in the
134      * reset tree. The loop goes through resettable_foreach_child below
135      * which at some point will call us again.
136      */
137     assert(s->count <= 50);
138 
139     /*
140      * handle the children even if action_needed is at false so that
141      * child counts are incremented too
142      */
143     resettable_child_foreach(rc, obj, resettable_phase_enter, NULL, type);
144 
145     /* execute enter phase for the object if needed */
146     if (action_needed) {
147         trace_resettable_phase_enter_exec(obj, obj_typename, type,
148                                           !!rc->phases.enter);
149         if (rc->phases.enter && !resettable_get_tr_func(rc, obj)) {
150             rc->phases.enter(obj, type);
151         }
152         s->hold_phase_pending = true;
153     }
154     trace_resettable_phase_enter_end(obj, obj_typename, s->count);
155 }
156 
resettable_phase_hold(Object * obj,void * opaque,ResetType type)157 static void resettable_phase_hold(Object *obj, void *opaque, ResetType type)
158 {
159     ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
160     ResettableState *s = rc->get_state(obj);
161     const char *obj_typename = object_get_typename(obj);
162 
163     /* exit phase has to finish properly before entering back in reset */
164     assert(!s->exit_phase_in_progress);
165 
166     trace_resettable_phase_hold_begin(obj, obj_typename, s->count, type);
167 
168     /* handle children first */
169     resettable_child_foreach(rc, obj, resettable_phase_hold, NULL, type);
170 
171     /* exec hold phase */
172     if (s->hold_phase_pending) {
173         s->hold_phase_pending = false;
174         ResettableTrFunction tr_func = resettable_get_tr_func(rc, obj);
175         trace_resettable_phase_hold_exec(obj, obj_typename, !!rc->phases.hold);
176         if (tr_func) {
177             trace_resettable_transitional_function(obj, obj_typename);
178             tr_func(obj);
179         } else if (rc->phases.hold) {
180             rc->phases.hold(obj, type);
181         }
182     }
183     trace_resettable_phase_hold_end(obj, obj_typename, s->count);
184 }
185 
resettable_phase_exit(Object * obj,void * opaque,ResetType type)186 static void resettable_phase_exit(Object *obj, void *opaque, ResetType type)
187 {
188     ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
189     ResettableState *s = rc->get_state(obj);
190     const char *obj_typename = object_get_typename(obj);
191 
192     assert(!s->exit_phase_in_progress);
193     trace_resettable_phase_exit_begin(obj, obj_typename, s->count, type);
194 
195     /* exit_phase_in_progress ensures this phase is 'atomic' */
196     s->exit_phase_in_progress = true;
197     resettable_child_foreach(rc, obj, resettable_phase_exit, NULL, type);
198 
199     assert(s->count > 0);
200     if (--s->count == 0) {
201         trace_resettable_phase_exit_exec(obj, obj_typename, !!rc->phases.exit);
202         if (rc->phases.exit && !resettable_get_tr_func(rc, obj)) {
203             rc->phases.exit(obj, type);
204         }
205     }
206     s->exit_phase_in_progress = false;
207     trace_resettable_phase_exit_end(obj, obj_typename, s->count);
208 }
209 
210 /*
211  * resettable_get_count:
212  * Get the count of the Resettable object @obj. Return 0 if @obj is NULL.
213  */
resettable_get_count(Object * obj)214 static unsigned resettable_get_count(Object *obj)
215 {
216     if (obj) {
217         ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
218         return rc->get_state(obj)->count;
219     }
220     return 0;
221 }
222 
resettable_change_parent(Object * obj,Object * newp,Object * oldp)223 void resettable_change_parent(Object *obj, Object *newp, Object *oldp)
224 {
225     ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
226     ResettableState *s = rc->get_state(obj);
227     unsigned newp_count = resettable_get_count(newp);
228     unsigned oldp_count = resettable_get_count(oldp);
229 
230     /*
231      * Ensure we do not change parent when in enter or exit phase.
232      * During these phases, the reset subtree being updated is partly in reset
233      * and partly not in reset (it depends on the actual position in
234      * resettable_child_foreach()s). We are not able to tell in which part is a
235      * leaving or arriving device. Thus we cannot set the reset count of the
236      * moving device to the proper value.
237      */
238     assert(!enter_phase_in_progress && !exit_phase_in_progress);
239     trace_resettable_change_parent(obj, oldp, oldp_count, newp, newp_count);
240 
241     /*
242      * At most one of the two 'for' loops will be executed below
243      * in order to cope with the difference between the two counts.
244      */
245     /* if newp is more reset than oldp */
246     for (unsigned i = oldp_count; i < newp_count; i++) {
247         resettable_assert_reset(obj, RESET_TYPE_COLD);
248     }
249     /*
250      * if obj is leaving a bus under reset, we need to ensure
251      * hold phase is not pending.
252      */
253     if (oldp_count && s->hold_phase_pending) {
254         resettable_phase_hold(obj, NULL, RESET_TYPE_COLD);
255     }
256     /* if oldp is more reset than newp */
257     for (unsigned i = newp_count; i < oldp_count; i++) {
258         resettable_release_reset(obj, RESET_TYPE_COLD);
259     }
260 }
261 
resettable_cold_reset_fn(void * opaque)262 void resettable_cold_reset_fn(void *opaque)
263 {
264     resettable_reset((Object *) opaque, RESET_TYPE_COLD);
265 }
266 
resettable_class_set_parent_phases(ResettableClass * rc,ResettableEnterPhase enter,ResettableHoldPhase hold,ResettableExitPhase exit,ResettablePhases * parent_phases)267 void resettable_class_set_parent_phases(ResettableClass *rc,
268                                         ResettableEnterPhase enter,
269                                         ResettableHoldPhase hold,
270                                         ResettableExitPhase exit,
271                                         ResettablePhases *parent_phases)
272 {
273     *parent_phases = rc->phases;
274     if (enter) {
275         rc->phases.enter = enter;
276     }
277     if (hold) {
278         rc->phases.hold = hold;
279     }
280     if (exit) {
281         rc->phases.exit = exit;
282     }
283 }
284 
285 static const TypeInfo resettable_interface_info = {
286     .name       = TYPE_RESETTABLE_INTERFACE,
287     .parent     = TYPE_INTERFACE,
288     .class_size = sizeof(ResettableClass),
289 };
290 
reset_register_types(void)291 static void reset_register_types(void)
292 {
293     type_register_static(&resettable_interface_info);
294 }
295 
296 type_init(reset_register_types)
297