]>
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 | * | |
31 | * Note: This flag is only used to guarantee (using asserts) that the reset | |
32 | * API is used correctly. We can use a global variable because we rely on the | |
33 | * iothread mutex to ensure only one reset operation is in a progress at a | |
34 | * given time. | |
35 | */ | |
36 | static bool enter_phase_in_progress; | |
37 | ||
38 | void resettable_reset(Object *obj, ResetType type) | |
39 | { | |
40 | trace_resettable_reset(obj, type); | |
41 | resettable_assert_reset(obj, type); | |
42 | resettable_release_reset(obj, type); | |
43 | } | |
44 | ||
45 | void resettable_assert_reset(Object *obj, ResetType type) | |
46 | { | |
47 | /* TODO: change this assert when adding support for other reset types */ | |
48 | assert(type == RESET_TYPE_COLD); | |
49 | trace_resettable_reset_assert_begin(obj, type); | |
50 | assert(!enter_phase_in_progress); | |
51 | ||
52 | enter_phase_in_progress = true; | |
53 | resettable_phase_enter(obj, NULL, type); | |
54 | enter_phase_in_progress = false; | |
55 | ||
56 | resettable_phase_hold(obj, NULL, type); | |
57 | ||
58 | trace_resettable_reset_assert_end(obj); | |
59 | } | |
60 | ||
61 | void resettable_release_reset(Object *obj, ResetType type) | |
62 | { | |
63 | /* TODO: change this assert when adding support for other reset types */ | |
64 | assert(type == RESET_TYPE_COLD); | |
65 | trace_resettable_reset_release_begin(obj, type); | |
66 | assert(!enter_phase_in_progress); | |
67 | ||
68 | resettable_phase_exit(obj, NULL, type); | |
69 | ||
70 | trace_resettable_reset_release_end(obj); | |
71 | } | |
72 | ||
73 | bool resettable_is_in_reset(Object *obj) | |
74 | { | |
75 | ResettableClass *rc = RESETTABLE_GET_CLASS(obj); | |
76 | ResettableState *s = rc->get_state(obj); | |
77 | ||
78 | return s->count > 0; | |
79 | } | |
80 | ||
81 | /** | |
82 | * resettable_child_foreach: | |
83 | * helper to avoid checking the existence of the method. | |
84 | */ | |
85 | static void resettable_child_foreach(ResettableClass *rc, Object *obj, | |
86 | ResettableChildCallback cb, | |
87 | void *opaque, ResetType type) | |
88 | { | |
89 | if (rc->child_foreach) { | |
90 | rc->child_foreach(obj, cb, opaque, type); | |
91 | } | |
92 | } | |
93 | ||
94 | /** | |
95 | * resettable_get_tr_func: | |
96 | * helper to fetch transitional reset callback if any. | |
97 | */ | |
98 | static ResettableTrFunction resettable_get_tr_func(ResettableClass *rc, | |
99 | Object *obj) | |
100 | { | |
101 | ResettableTrFunction tr_func = NULL; | |
102 | if (rc->get_transitional_function) { | |
103 | tr_func = rc->get_transitional_function(obj); | |
104 | } | |
105 | return tr_func; | |
106 | } | |
107 | ||
108 | static void resettable_phase_enter(Object *obj, void *opaque, ResetType type) | |
109 | { | |
110 | ResettableClass *rc = RESETTABLE_GET_CLASS(obj); | |
111 | ResettableState *s = rc->get_state(obj); | |
112 | const char *obj_typename = object_get_typename(obj); | |
113 | bool action_needed = false; | |
114 | ||
115 | /* exit phase has to finish properly before entering back in reset */ | |
116 | assert(!s->exit_phase_in_progress); | |
117 | ||
118 | trace_resettable_phase_enter_begin(obj, obj_typename, s->count, type); | |
119 | ||
120 | /* Only take action if we really enter reset for the 1st time. */ | |
121 | /* | |
122 | * TODO: if adding more ResetType support, some additional checks | |
123 | * are probably needed here. | |
124 | */ | |
125 | if (s->count++ == 0) { | |
126 | action_needed = true; | |
127 | } | |
128 | /* | |
129 | * We limit the count to an arbitrary "big" value. The value is big | |
130 | * enough not to be triggered normally. | |
131 | * The assert will stop an infinite loop if there is a cycle in the | |
132 | * reset tree. The loop goes through resettable_foreach_child below | |
133 | * which at some point will call us again. | |
134 | */ | |
135 | assert(s->count <= 50); | |
136 | ||
137 | /* | |
138 | * handle the children even if action_needed is at false so that | |
139 | * child counts are incremented too | |
140 | */ | |
141 | resettable_child_foreach(rc, obj, resettable_phase_enter, NULL, type); | |
142 | ||
143 | /* execute enter phase for the object if needed */ | |
144 | if (action_needed) { | |
145 | trace_resettable_phase_enter_exec(obj, obj_typename, type, | |
146 | !!rc->phases.enter); | |
147 | if (rc->phases.enter && !resettable_get_tr_func(rc, obj)) { | |
148 | rc->phases.enter(obj, type); | |
149 | } | |
150 | s->hold_phase_pending = true; | |
151 | } | |
152 | trace_resettable_phase_enter_end(obj, obj_typename, s->count); | |
153 | } | |
154 | ||
155 | static void resettable_phase_hold(Object *obj, void *opaque, ResetType type) | |
156 | { | |
157 | ResettableClass *rc = RESETTABLE_GET_CLASS(obj); | |
158 | ResettableState *s = rc->get_state(obj); | |
159 | const char *obj_typename = object_get_typename(obj); | |
160 | ||
161 | /* exit phase has to finish properly before entering back in reset */ | |
162 | assert(!s->exit_phase_in_progress); | |
163 | ||
164 | trace_resettable_phase_hold_begin(obj, obj_typename, s->count, type); | |
165 | ||
166 | /* handle children first */ | |
167 | resettable_child_foreach(rc, obj, resettable_phase_hold, NULL, type); | |
168 | ||
169 | /* exec hold phase */ | |
170 | if (s->hold_phase_pending) { | |
171 | s->hold_phase_pending = false; | |
172 | ResettableTrFunction tr_func = resettable_get_tr_func(rc, obj); | |
173 | trace_resettable_phase_hold_exec(obj, obj_typename, !!rc->phases.hold); | |
174 | if (tr_func) { | |
175 | trace_resettable_transitional_function(obj, obj_typename); | |
176 | tr_func(obj); | |
177 | } else if (rc->phases.hold) { | |
178 | rc->phases.hold(obj); | |
179 | } | |
180 | } | |
181 | trace_resettable_phase_hold_end(obj, obj_typename, s->count); | |
182 | } | |
183 | ||
184 | static void resettable_phase_exit(Object *obj, void *opaque, ResetType type) | |
185 | { | |
186 | ResettableClass *rc = RESETTABLE_GET_CLASS(obj); | |
187 | ResettableState *s = rc->get_state(obj); | |
188 | const char *obj_typename = object_get_typename(obj); | |
189 | ||
190 | assert(!s->exit_phase_in_progress); | |
191 | trace_resettable_phase_exit_begin(obj, obj_typename, s->count, type); | |
192 | ||
193 | /* exit_phase_in_progress ensures this phase is 'atomic' */ | |
194 | s->exit_phase_in_progress = true; | |
195 | resettable_child_foreach(rc, obj, resettable_phase_exit, NULL, type); | |
196 | ||
197 | assert(s->count > 0); | |
198 | if (s->count == 1) { | |
199 | trace_resettable_phase_exit_exec(obj, obj_typename, !!rc->phases.exit); | |
200 | if (rc->phases.exit && !resettable_get_tr_func(rc, obj)) { | |
201 | rc->phases.exit(obj); | |
202 | } | |
203 | s->count = 0; | |
204 | } | |
205 | s->exit_phase_in_progress = false; | |
206 | trace_resettable_phase_exit_end(obj, obj_typename, s->count); | |
207 | } | |
208 | ||
209 | void resettable_class_set_parent_phases(ResettableClass *rc, | |
210 | ResettableEnterPhase enter, | |
211 | ResettableHoldPhase hold, | |
212 | ResettableExitPhase exit, | |
213 | ResettablePhases *parent_phases) | |
214 | { | |
215 | *parent_phases = rc->phases; | |
216 | if (enter) { | |
217 | rc->phases.enter = enter; | |
218 | } | |
219 | if (hold) { | |
220 | rc->phases.hold = hold; | |
221 | } | |
222 | if (exit) { | |
223 | rc->phases.exit = exit; | |
224 | } | |
225 | } | |
226 | ||
227 | static const TypeInfo resettable_interface_info = { | |
228 | .name = TYPE_RESETTABLE_INTERFACE, | |
229 | .parent = TYPE_INTERFACE, | |
230 | .class_size = sizeof(ResettableClass), | |
231 | }; | |
232 | ||
233 | static void reset_register_types(void) | |
234 | { | |
235 | type_register_static(&resettable_interface_info); | |
236 | } | |
237 | ||
238 | type_init(reset_register_types) |