xref: /linux/drivers/md/dm-vdo/action-manager.c (revision b7df4cc3a088a8ce6973c96731bc792dbf54ce28)
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