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 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 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 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 */ 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 */ 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 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 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 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 */ 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 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 262 void resettable_cold_reset_fn(void *opaque) 263 { 264 resettable_reset((Object *) opaque, RESET_TYPE_COLD); 265 } 266 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 291 static void reset_register_types(void) 292 { 293 type_register_static(&resettable_interface_info); 294 } 295 296 type_init(reset_register_types) 297