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