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 */
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 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 */
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 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 */
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 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
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 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 */
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 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 */
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 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 */
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