1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2023 Red Hat
6 #include "action-manager.h"
8 #include "memory-alloc.h"
9 #include "permassert.h"
11 #include "admin-state.h"
12 #include "completion.h"
13 #include "status-codes.h"
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.
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
;
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
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.
53 struct action_manager
{
54 struct vdo_completion completion
;
55 struct admin_state state
;
56 struct action actions
[2];
57 struct action
*current_action
;
59 vdo_action_scheduler_fn scheduler
;
60 vdo_zone_thread_getter_fn get_zone_thread_id
;
61 thread_id_t initiator_thread_id
;
63 zone_count_t acting_zone
;
66 static inline struct action_manager
*as_action_manager(struct vdo_completion
*completion
)
68 vdo_assert_completion_type(completion
, VDO_ACTION_COMPLETION
);
69 return container_of(completion
, struct action_manager
, completion
);
72 /* Implements vdo_action_scheduler_fn. */
73 static bool no_default_action(void *context __always_unused
)
78 /* Implements vdo_action_preamble_fn. */
79 static void no_preamble(void *context __always_unused
, struct vdo_completion
*completion
)
81 vdo_finish_completion(completion
);
84 /* Implements vdo_action_conclusion_fn. */
85 static int no_conclusion(void *context __always_unused
)
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.
101 * Return: VDO_SUCCESS or an error code.
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
)
109 struct action_manager
*manager
;
110 int result
= vdo_allocate(1, struct action_manager
, __func__
, &manager
);
112 if (result
!= VDO_SUCCESS
)
115 *manager
= (struct action_manager
) {
118 ((scheduler
== NULL
) ? no_default_action
: scheduler
),
119 .get_zone_thread_id
= get_zone_thread_id
,
120 .initiator_thread_id
= initiator_thread_id
,
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
;
133 const struct admin_state_code
*vdo_get_current_manager_operation(struct action_manager
*manager
)
135 return vdo_get_admin_state_code(&manager
->state
);
138 void *vdo_get_current_action_context(struct action_manager
*manager
)
140 return manager
->current_action
->in_use
? manager
->current_action
->context
: NULL
;
143 static void finish_action_callback(struct vdo_completion
*completion
);
144 static void apply_to_zone(struct vdo_completion
*completion
);
146 static thread_id_t
get_acting_zone_thread_id(struct action_manager
*manager
)
148 return manager
->get_zone_thread_id(manager
->context
, manager
->acting_zone
);
151 static void preserve_error(struct vdo_completion
*completion
)
153 if (completion
->parent
!= NULL
)
154 vdo_set_completion_result(completion
->parent
, completion
->result
);
156 vdo_reset_completion(completion
);
157 vdo_run_completion(completion
);
160 static void prepare_for_next_zone(struct action_manager
*manager
)
162 vdo_prepare_completion_for_requeue(&manager
->completion
, apply_to_zone
,
164 get_acting_zone_thread_id(manager
),
165 manager
->current_action
->parent
);
168 static void prepare_for_conclusion(struct action_manager
*manager
)
170 vdo_prepare_completion_for_requeue(&manager
->completion
, finish_action_callback
,
171 preserve_error
, manager
->initiator_thread_id
,
172 manager
->current_action
->parent
);
175 static void apply_to_zone(struct vdo_completion
*completion
)
178 struct action_manager
*manager
= as_action_manager(completion
);
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__
);
183 zone
= manager
->acting_zone
++;
184 if (manager
->acting_zone
== manager
->zones
) {
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.
189 prepare_for_conclusion(manager
);
191 /* Prepare to come back on the next zone */
192 prepare_for_next_zone(manager
);
195 manager
->current_action
->zone_action(manager
->context
, zone
, completion
);
198 static void handle_preamble_error(struct vdo_completion
*completion
)
200 /* Skip the zone actions since the preamble failed. */
201 completion
->callback
= finish_action_callback
;
202 preserve_error(completion
);
205 static void launch_current_action(struct action_manager
*manager
)
207 struct action
*action
= manager
->current_action
;
208 int result
= vdo_start_operation(&manager
->state
, action
->operation
);
210 if (result
!= VDO_SUCCESS
) {
211 if (action
->parent
!= NULL
)
212 vdo_set_completion_result(action
->parent
, result
);
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
);
220 if (action
->zone_action
== NULL
) {
221 prepare_for_conclusion(manager
);
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
);
230 action
->preamble(manager
->context
, &manager
->completion
);
234 * vdo_schedule_default_action() - Attempt to schedule the default action.
235 * @manager: The action manager.
237 * If the manager is not operating normally, the action will not be scheduled.
239 * Return: true if an action was scheduled.
241 bool vdo_schedule_default_action(struct action_manager
*manager
)
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
);
246 return ((code
== VDO_ADMIN_STATE_NORMAL_OPERATION
) &&
247 manager
->scheduler(manager
->context
));
250 static void finish_action_callback(struct vdo_completion
*completion
)
252 bool has_next_action
;
254 struct action_manager
*manager
= as_action_manager(completion
);
255 struct action action
= *(manager
->current_action
);
257 manager
->current_action
->in_use
= false;
258 manager
->current_action
= manager
->current_action
->next
;
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.
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
);
272 launch_current_action(manager
);
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;
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.
291 * Return: true if the action was scheduled.
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
)
297 return vdo_schedule_operation(manager
, VDO_ADMIN_STATE_OPERATING
, preamble
,
298 action
, conclusion
, parent
);
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;
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.
318 * Return: true if the action was scheduled.
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
)
326 return vdo_schedule_operation_with_context(manager
, operation
, preamble
, action
,
327 conclusion
, NULL
, parent
);
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;
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.
349 * Return: true if the action was scheduled
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
)
358 struct action
*current_action
;
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
;
368 vdo_continue_completion(parent
, VDO_COMPONENT_BUSY
);
373 *current_action
= (struct action
) {
375 .operation
= operation
,
376 .preamble
= (preamble
== NULL
) ? no_preamble
: preamble
,
377 .zone_action
= action
,
378 .conclusion
= (conclusion
== NULL
) ? no_conclusion
: conclusion
,
381 .next
= current_action
->next
,
384 if (current_action
== manager
->current_action
)
385 launch_current_action(manager
);