brintos

brintos / linux-shallow public Read only

0
0
Text · 13.8 KiB · a0e5e70 Raw
389 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright 2023 Red Hat4 */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 */28struct 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 a48 *                      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 */53struct 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 66static 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. */73static bool no_default_action(void *context __always_unused)74{75	return false;76}77 78/* Implements vdo_action_preamble_fn. */79static 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. */85static 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 no97 *             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 */103int 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 133const 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 138void *vdo_get_current_action_context(struct action_manager *manager)139{140	return manager->current_action->in_use ? manager->current_action->context : NULL;141}142 143static void finish_action_callback(struct vdo_completion *completion);144static void apply_to_zone(struct vdo_completion *completion);145 146static 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 151static 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 160static 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 168static 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 175static 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 go187		 * 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 198static 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 205static 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 */241bool 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 250static 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 or262	 * 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 before279 *            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 been282 *              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 current287 * 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, or289 * conclusion must not be NULL.290 *291 * Return: true if the action was scheduled.292 */293bool 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 perform305 * @preamble: A method to be invoked on the initiator thread once this action is started but before306 *            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 been309 *              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 as314 * the current action completes. If there is already a pending action, this operation will not be315 * 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 */320bool 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 before335 *            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 been338 *              applied to all zones; may be NULL.339 * @context: An action-specific context which may be retrieved via340 *           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 as345 * the current action completes. If there is already a pending action, this operation will not be346 * 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 scheduled350 */351bool 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