| 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, expected: 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(state: &manager->state, code: VDO_ADMIN_STATE_NORMAL_OPERATION); |
| 128 | vdo_initialize_completion(completion: &manager->completion, vdo, type: 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(state: &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: completion->parent, result: 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(completion: &manager->completion, callback: apply_to_zone, |
| 163 | error_handler: preserve_error, |
| 164 | callback_thread_id: get_acting_zone_thread_id(manager), |
| 165 | parent: manager->current_action->parent); |
| 166 | } |
| 167 | |
| 168 | static void prepare_for_conclusion(struct action_manager *manager) |
| 169 | { |
| 170 | vdo_prepare_completion_for_requeue(completion: &manager->completion, callback: finish_action_callback, |
| 171 | error_handler: preserve_error, callback_thread_id: manager->initiator_thread_id, |
| 172 | parent: 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(state: &manager->state, operation: action->operation); |
| 209 | |
| 210 | if (result != VDO_SUCCESS) { |
| 211 | if (action->parent != NULL) |
| 212 | vdo_set_completion_result(completion: 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(completion: &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(completion: &manager->completion, callback: apply_to_zone, |
| 225 | error_handler: handle_preamble_error, |
| 226 | callback_thread_id: get_acting_zone_thread_id(manager), |
| 227 | parent: 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(state: &manager->state, VDO_SUCCESS); |
| 268 | if (action.parent != NULL) |
| 269 | vdo_continue_completion(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, operation: 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(completion: parent, result: 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 | |