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