]>
Commit | Line | Data |
---|---|---|
bc5a39bf DH |
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 | * | |
614f731a DH |
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 | |
bc5a39bf DH |
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; | |
614f731a | 40 | static unsigned exit_phase_in_progress; |
bc5a39bf DH |
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 | ||
614f731a | 72 | exit_phase_in_progress += 1; |
bc5a39bf | 73 | resettable_phase_exit(obj, NULL, type); |
614f731a | 74 | exit_phase_in_progress -= 1; |
bc5a39bf DH |
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 | ||
614f731a DH |
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 | ||
abb89dbf DH |
267 | void resettable_cold_reset_fn(void *opaque) |
268 | { | |
269 | resettable_reset((Object *) opaque, RESET_TYPE_COLD); | |
270 | } | |
271 | ||
bc5a39bf DH |
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) |