1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright 2023 Red Hat 4 */ 5 6 #include "action-manager.h" 7 8 #include "memory-alloc.h" 9 #include "permassert.h" 10 11 #include "admin-state.h" 12 #include "completion.h" 13 #include "status-codes.h" 14 #include "types.h" 15 #include "vdo.h" 16 17 /** 18 * struct action - An action to be performed in each of a set of zones. 19 * @in_use: Whether this structure is in use. 20 * @operation: The admin operation associated with this action. 21 * @preamble: The method to run on the initiator thread before the action is applied to each zone. 22 * @zone_action: The action to be performed in each zone. 23 * @conclusion: The method to run on the initiator thread after the action is applied to each zone. 24 * @parent: The object to notify when the action is complete. 25 * @context: The action specific context. 26 * @next: The action to perform after this one. 27 */ 28 struct action { 29 bool in_use; 30 const struct admin_state_code *operation; 31 vdo_action_preamble_fn preamble; 32 vdo_zone_action_fn zone_action; 33 vdo_action_conclusion_fn conclusion; 34 struct vdo_completion *parent; 35 void *context; 36 struct action *next; 37 }; 38 39 /** 40 * struct action_manager - Definition of an action manager. 41 * @completion: The completion for performing actions. 42 * @state: The state of this action manager. 43 * @actions: The two action slots. 44 * @current_action: The current action slot. 45 * @zones: The number of zones in which an action is to be applied. 46 * @Scheduler: A function to schedule a default next action. 47 * @get_zone_thread_id: A function to get the id of the thread on which to apply an action to a 48 * zone. 49 * @initiator_thread_id: The ID of the thread on which actions may be initiated. 50 * @context: Opaque data associated with this action manager. 51 * @acting_zone: The zone currently being acted upon. 52 */ 53 struct action_manager { 54 struct vdo_completion completion; 55 struct admin_state state; 56 struct action actions[2]; 57 struct action *current_action; 58 zone_count_t zones; 59 vdo_action_scheduler_fn scheduler; 60 vdo_zone_thread_getter_fn get_zone_thread_id; 61 thread_id_t initiator_thread_id; 62 void *context; 63 zone_count_t acting_zone; 64 }; 65 66 static inline struct action_manager *as_action_manager(struct vdo_completion *completion) 67 { 68 vdo_assert_completion_type(completion, VDO_ACTION_COMPLETION); 69 return container_of(completion, struct action_manager, completion); 70 } 71 72 /* Implements vdo_action_scheduler_fn. */ 73 static bool no_default_action(void *context __always_unused) 74 { 75 return false; 76 } 77 78 /* Implements vdo_action_preamble_fn. */ 79 static void no_preamble(void *context __always_unused, struct vdo_completion *completion) 80 { 81 vdo_finish_completion(completion); 82 } 83 84 /* Implements vdo_action_conclusion_fn. */ 85 static int no_conclusion(void *context __always_unused) 86 { 87 return VDO_SUCCESS; 88 } 89 90 /** 91 * vdo_make_action_manager() - Make an action manager. 92 * @zones: The number of zones to which actions will be applied. 93 * @get_zone_thread_id: A function to get the thread id associated with a zone. 94 * @initiator_thread_id: The thread on which actions may initiated. 95 * @context: The object which holds the per-zone context for the action. 96 * @scheduler: A function to schedule a next action after an action concludes if there is no 97 * pending action (may be NULL). 98 * @vdo: The vdo used to initialize completions. 99 * @manager_ptr: A pointer to hold the new action manager. 100 * 101 * Return: VDO_SUCCESS or an error code. 102 */ 103 int vdo_make_action_manager(zone_count_t zones, 104 vdo_zone_thread_getter_fn get_zone_thread_id, 105 thread_id_t initiator_thread_id, void *context, 106 vdo_action_scheduler_fn scheduler, struct vdo *vdo, 107 struct action_manager **manager_ptr) 108 { 109 struct action_manager *manager; 110 int result = vdo_allocate(1, struct action_manager, __func__, &manager); 111 112 if (result != VDO_SUCCESS) 113 return result; 114 115 *manager = (struct action_manager) { 116 .zones = zones, 117 .scheduler = 118 ((scheduler == NULL) ? no_default_action : scheduler), 119 .get_zone_thread_id = get_zone_thread_id, 120 .initiator_thread_id = initiator_thread_id, 121 .context = context, 122 }; 123 124 manager->actions[0].next = &manager->actions[1]; 125 manager->current_action = manager->actions[1].next = 126 &manager->actions[0]; 127 vdo_set_admin_state_code(&manager->state, VDO_ADMIN_STATE_NORMAL_OPERATION); 128 vdo_initialize_completion(&manager->completion, vdo, VDO_ACTION_COMPLETION); 129 *manager_ptr = manager; 130 return VDO_SUCCESS; 131 } 132 133 const struct admin_state_code *vdo_get_current_manager_operation(struct action_manager *manager) 134 { 135 return vdo_get_admin_state_code(&manager->state); 136 } 137 138 void *vdo_get_current_action_context(struct action_manager *manager) 139 { 140 return manager->current_action->in_use ? manager->current_action->context : NULL; 141 } 142 143 static void finish_action_callback(struct vdo_completion *completion); 144 static void apply_to_zone(struct vdo_completion *completion); 145 146 static thread_id_t get_acting_zone_thread_id(struct action_manager *manager) 147 { 148 return manager->get_zone_thread_id(manager->context, manager->acting_zone); 149 } 150 151 static void preserve_error(struct vdo_completion *completion) 152 { 153 if (completion->parent != NULL) 154 vdo_set_completion_result(completion->parent, completion->result); 155 156 vdo_reset_completion(completion); 157 vdo_run_completion(completion); 158 } 159 160 static void prepare_for_next_zone(struct action_manager *manager) 161 { 162 vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone, 163 preserve_error, 164 get_acting_zone_thread_id(manager), 165 manager->current_action->parent); 166 } 167 168 static void prepare_for_conclusion(struct action_manager *manager) 169 { 170 vdo_prepare_completion_for_requeue(&manager->completion, finish_action_callback, 171 preserve_error, manager->initiator_thread_id, 172 manager->current_action->parent); 173 } 174 175 static void apply_to_zone(struct vdo_completion *completion) 176 { 177 zone_count_t zone; 178 struct action_manager *manager = as_action_manager(completion); 179 180 VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == get_acting_zone_thread_id(manager)), 181 "%s() called on acting zones's thread", __func__); 182 183 zone = manager->acting_zone++; 184 if (manager->acting_zone == manager->zones) { 185 /* 186 * We are about to apply to the last zone. Once that is finished, we're done, so go 187 * back to the initiator thread and finish up. 188 */ 189 prepare_for_conclusion(manager); 190 } else { 191 /* Prepare to come back on the next zone */ 192 prepare_for_next_zone(manager); 193 } 194 195 manager->current_action->zone_action(manager->context, zone, completion); 196 } 197 198 static void handle_preamble_error(struct vdo_completion *completion) 199 { 200 /* Skip the zone actions since the preamble failed. */ 201 completion->callback = finish_action_callback; 202 preserve_error(completion); 203 } 204 205 static void launch_current_action(struct action_manager *manager) 206 { 207 struct action *action = manager->current_action; 208 int result = vdo_start_operation(&manager->state, action->operation); 209 210 if (result != VDO_SUCCESS) { 211 if (action->parent != NULL) 212 vdo_set_completion_result(action->parent, result); 213 214 /* We aren't going to run the preamble, so don't run the conclusion */ 215 action->conclusion = no_conclusion; 216 finish_action_callback(&manager->completion); 217 return; 218 } 219 220 if (action->zone_action == NULL) { 221 prepare_for_conclusion(manager); 222 } else { 223 manager->acting_zone = 0; 224 vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone, 225 handle_preamble_error, 226 get_acting_zone_thread_id(manager), 227 manager->current_action->parent); 228 } 229 230 action->preamble(manager->context, &manager->completion); 231 } 232 233 /** 234 * vdo_schedule_default_action() - Attempt to schedule the default action. 235 * @manager: The action manager. 236 * 237 * If the manager is not operating normally, the action will not be scheduled. 238 * 239 * Return: true if an action was scheduled. 240 */ 241 bool vdo_schedule_default_action(struct action_manager *manager) 242 { 243 /* Don't schedule a default action if we are operating or not in normal operation. */ 244 const struct admin_state_code *code = vdo_get_current_manager_operation(manager); 245 246 return ((code == VDO_ADMIN_STATE_NORMAL_OPERATION) && 247 manager->scheduler(manager->context)); 248 } 249 250 static void finish_action_callback(struct vdo_completion *completion) 251 { 252 bool has_next_action; 253 int result; 254 struct action_manager *manager = as_action_manager(completion); 255 struct action action = *(manager->current_action); 256 257 manager->current_action->in_use = false; 258 manager->current_action = manager->current_action->next; 259 260 /* 261 * We need to check this now to avoid use-after-free issues if running the conclusion or 262 * notifying the parent results in the manager being freed. 263 */ 264 has_next_action = 265 (manager->current_action->in_use || vdo_schedule_default_action(manager)); 266 result = action.conclusion(manager->context); 267 vdo_finish_operation(&manager->state, VDO_SUCCESS); 268 if (action.parent != NULL) 269 vdo_continue_completion(action.parent, result); 270 271 if (has_next_action) 272 launch_current_action(manager); 273 } 274 275 /** 276 * vdo_schedule_action() - Schedule an action to be applied to all zones. 277 * @manager: The action manager to schedule the action on. 278 * @preamble: A method to be invoked on the initiator thread once this action is started but before 279 * applying to each zone; may be NULL. 280 * @action: The action to apply to each zone; may be NULL. 281 * @conclusion: A method to be invoked back on the initiator thread once the action has been 282 * applied to all zones; may be NULL. 283 * @parent: The object to notify once the action is complete or if the action can not be scheduled; 284 * may be NULL. 285 * 286 * The action will be launched immediately if there is no current action, or as soon as the current 287 * action completes. If there is already a pending action, this action will not be scheduled, and, 288 * if it has a parent, that parent will be notified. At least one of the preamble, action, or 289 * conclusion must not be NULL. 290 * 291 * Return: true if the action was scheduled. 292 */ 293 bool vdo_schedule_action(struct action_manager *manager, vdo_action_preamble_fn preamble, 294 vdo_zone_action_fn action, vdo_action_conclusion_fn conclusion, 295 struct vdo_completion *parent) 296 { 297 return vdo_schedule_operation(manager, VDO_ADMIN_STATE_OPERATING, preamble, 298 action, conclusion, parent); 299 } 300 301 /** 302 * vdo_schedule_operation() - Schedule an operation to be applied to all zones. 303 * @manager: The action manager to schedule the action on. 304 * @operation: The operation this action will perform 305 * @preamble: A method to be invoked on the initiator thread once this action is started but before 306 * applying to each zone; may be NULL. 307 * @action: The action to apply to each zone; may be NULL. 308 * @conclusion: A method to be invoked back on the initiator thread once the action has been 309 * applied to all zones; may be NULL. 310 * @parent: The object to notify once the action is complete or if the action can not be scheduled; 311 * may be NULL. 312 * 313 * The operation's action will be launched immediately if there is no current action, or as soon as 314 * the current action completes. If there is already a pending action, this operation will not be 315 * scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble, 316 * action, or conclusion must not be NULL. 317 * 318 * Return: true if the action was scheduled. 319 */ 320 bool vdo_schedule_operation(struct action_manager *manager, 321 const struct admin_state_code *operation, 322 vdo_action_preamble_fn preamble, vdo_zone_action_fn action, 323 vdo_action_conclusion_fn conclusion, 324 struct vdo_completion *parent) 325 { 326 return vdo_schedule_operation_with_context(manager, operation, preamble, action, 327 conclusion, NULL, parent); 328 } 329 330 /** 331 * vdo_schedule_operation_with_context() - Schedule an operation on all zones. 332 * @manager: The action manager to schedule the action on. 333 * @operation: The operation this action will perform. 334 * @preamble: A method to be invoked on the initiator thread once this action is started but before 335 * applying to each zone; may be NULL. 336 * @action: The action to apply to each zone; may be NULL. 337 * @conclusion: A method to be invoked back on the initiator thread once the action has been 338 * applied to all zones; may be NULL. 339 * @context: An action-specific context which may be retrieved via 340 * vdo_get_current_action_context(); may be NULL. 341 * @parent: The object to notify once the action is complete or if the action can not be scheduled; 342 * may be NULL. 343 * 344 * The operation's action will be launched immediately if there is no current action, or as soon as 345 * the current action completes. If there is already a pending action, this operation will not be 346 * scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble, 347 * action, or conclusion must not be NULL. 348 * 349 * Return: true if the action was scheduled 350 */ 351 bool vdo_schedule_operation_with_context(struct action_manager *manager, 352 const struct admin_state_code *operation, 353 vdo_action_preamble_fn preamble, 354 vdo_zone_action_fn action, 355 vdo_action_conclusion_fn conclusion, 356 void *context, struct vdo_completion *parent) 357 { 358 struct action *current_action; 359 360 VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == manager->initiator_thread_id), 361 "action initiated from correct thread"); 362 if (!manager->current_action->in_use) { 363 current_action = manager->current_action; 364 } else if (!manager->current_action->next->in_use) { 365 current_action = manager->current_action->next; 366 } else { 367 if (parent != NULL) 368 vdo_continue_completion(parent, VDO_COMPONENT_BUSY); 369 370 return false; 371 } 372 373 *current_action = (struct action) { 374 .in_use = true, 375 .operation = operation, 376 .preamble = (preamble == NULL) ? no_preamble : preamble, 377 .zone_action = action, 378 .conclusion = (conclusion == NULL) ? no_conclusion : conclusion, 379 .context = context, 380 .parent = parent, 381 .next = current_action->next, 382 }; 383 384 if (current_action == manager->current_action) 385 launch_current_action(manager); 386 387 return true; 388 } 389