resettable.c (9678B)
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 */ 23static void resettable_phase_enter(Object *obj, void *opaque, ResetType type); 24static void resettable_phase_hold(Object *obj, void *opaque, ResetType type); 25static 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 */ 39static bool enter_phase_in_progress; 40static unsigned exit_phase_in_progress; 41 42void 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 49void 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 65void 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 79bool 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 */ 91static 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 */ 104static 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 114static 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 161static 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 190static 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 */ 219static 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 228void 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 267void resettable_cold_reset_fn(void *opaque) 268{ 269 resettable_reset((Object *) opaque, RESET_TYPE_COLD); 270} 271 272void 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 290static const TypeInfo resettable_interface_info = { 291 .name = TYPE_RESETTABLE_INTERFACE, 292 .parent = TYPE_INTERFACE, 293 .class_size = sizeof(ResettableClass), 294}; 295 296static void reset_register_types(void) 297{ 298 type_register_static(&resettable_interface_info); 299} 300 301type_init(reset_register_types)