linux/drivers/md/dm-vdo/action-manager.c
Matthew Sakai 3f493fcea0 dm vdo: add administrative state and action manager
This patch adds the admin_state structures which are used to track the
states of individual vdo components for handling of operations like suspend
and resume. It also adds the action manager which is used to schedule and
manage cross-thread administrative and internal operations.

Co-developed-by: J. corwin Coburn <corwin@hurlbutnet.net>
Signed-off-by: J. corwin Coburn <corwin@hurlbutnet.net>
Signed-off-by: Matthew Sakai <msakai@redhat.com>
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
2024-02-20 13:43:14 -05:00

389 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "action-manager.h"
#include "memory-alloc.h"
#include "permassert.h"
#include "admin-state.h"
#include "completion.h"
#include "status-codes.h"
#include "types.h"
#include "vdo.h"
/**
* struct action - An action to be performed in each of a set of zones.
* @in_use: Whether this structure is in use.
* @operation: The admin operation associated with this action.
* @preamble: The method to run on the initiator thread before the action is applied to each zone.
* @zone_action: The action to be performed in each zone.
* @conclusion: The method to run on the initiator thread after the action is applied to each zone.
* @parent: The object to notify when the action is complete.
* @context: The action specific context.
* @next: The action to perform after this one.
*/
struct action {
bool in_use;
const struct admin_state_code *operation;
vdo_action_preamble_fn preamble;
vdo_zone_action_fn zone_action;
vdo_action_conclusion_fn conclusion;
struct vdo_completion *parent;
void *context;
struct action *next;
};
/**
* struct action_manager - Definition of an action manager.
* @completion: The completion for performing actions.
* @state: The state of this action manager.
* @actions: The two action slots.
* @current_action: The current action slot.
* @zones: The number of zones in which an action is to be applied.
* @Scheduler: A function to schedule a default next action.
* @get_zone_thread_id: A function to get the id of the thread on which to apply an action to a
* zone.
* @initiator_thread_id: The ID of the thread on which actions may be initiated.
* @context: Opaque data associated with this action manager.
* @acting_zone: The zone currently being acted upon.
*/
struct action_manager {
struct vdo_completion completion;
struct admin_state state;
struct action actions[2];
struct action *current_action;
zone_count_t zones;
vdo_action_scheduler_fn scheduler;
vdo_zone_thread_getter_fn get_zone_thread_id;
thread_id_t initiator_thread_id;
void *context;
zone_count_t acting_zone;
};
static inline struct action_manager *as_action_manager(struct vdo_completion *completion)
{
vdo_assert_completion_type(completion, VDO_ACTION_COMPLETION);
return container_of(completion, struct action_manager, completion);
}
/* Implements vdo_action_scheduler_fn. */
static bool no_default_action(void *context __always_unused)
{
return false;
}
/* Implements vdo_action_preamble_fn. */
static void no_preamble(void *context __always_unused, struct vdo_completion *completion)
{
vdo_finish_completion(completion);
}
/* Implements vdo_action_conclusion_fn. */
static int no_conclusion(void *context __always_unused)
{
return VDO_SUCCESS;
}
/**
* vdo_make_action_manager() - Make an action manager.
* @zones: The number of zones to which actions will be applied.
* @get_zone_thread_id: A function to get the thread id associated with a zone.
* @initiator_thread_id: The thread on which actions may initiated.
* @context: The object which holds the per-zone context for the action.
* @scheduler: A function to schedule a next action after an action concludes if there is no
* pending action (may be NULL).
* @vdo: The vdo used to initialize completions.
* @manager_ptr: A pointer to hold the new action manager.
*
* Return: VDO_SUCCESS or an error code.
*/
int vdo_make_action_manager(zone_count_t zones,
vdo_zone_thread_getter_fn get_zone_thread_id,
thread_id_t initiator_thread_id, void *context,
vdo_action_scheduler_fn scheduler, struct vdo *vdo,
struct action_manager **manager_ptr)
{
struct action_manager *manager;
int result = uds_allocate(1, struct action_manager, __func__, &manager);
if (result != VDO_SUCCESS)
return result;
*manager = (struct action_manager) {
.zones = zones,
.scheduler =
((scheduler == NULL) ? no_default_action : scheduler),
.get_zone_thread_id = get_zone_thread_id,
.initiator_thread_id = initiator_thread_id,
.context = context,
};
manager->actions[0].next = &manager->actions[1];
manager->current_action = manager->actions[1].next =
&manager->actions[0];
vdo_set_admin_state_code(&manager->state, VDO_ADMIN_STATE_NORMAL_OPERATION);
vdo_initialize_completion(&manager->completion, vdo, VDO_ACTION_COMPLETION);
*manager_ptr = manager;
return VDO_SUCCESS;
}
const struct admin_state_code *vdo_get_current_manager_operation(struct action_manager *manager)
{
return vdo_get_admin_state_code(&manager->state);
}
void *vdo_get_current_action_context(struct action_manager *manager)
{
return manager->current_action->in_use ? manager->current_action->context : NULL;
}
static void finish_action_callback(struct vdo_completion *completion);
static void apply_to_zone(struct vdo_completion *completion);
static thread_id_t get_acting_zone_thread_id(struct action_manager *manager)
{
return manager->get_zone_thread_id(manager->context, manager->acting_zone);
}
static void preserve_error(struct vdo_completion *completion)
{
if (completion->parent != NULL)
vdo_set_completion_result(completion->parent, completion->result);
vdo_reset_completion(completion);
vdo_run_completion(completion);
}
static void prepare_for_next_zone(struct action_manager *manager)
{
vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone,
preserve_error,
get_acting_zone_thread_id(manager),
manager->current_action->parent);
}
static void prepare_for_conclusion(struct action_manager *manager)
{
vdo_prepare_completion_for_requeue(&manager->completion, finish_action_callback,
preserve_error, manager->initiator_thread_id,
manager->current_action->parent);
}
static void apply_to_zone(struct vdo_completion *completion)
{
zone_count_t zone;
struct action_manager *manager = as_action_manager(completion);
ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == get_acting_zone_thread_id(manager)),
"%s() called on acting zones's thread", __func__);
zone = manager->acting_zone++;
if (manager->acting_zone == manager->zones) {
/*
* We are about to apply to the last zone. Once that is finished, we're done, so go
* back to the initiator thread and finish up.
*/
prepare_for_conclusion(manager);
} else {
/* Prepare to come back on the next zone */
prepare_for_next_zone(manager);
}
manager->current_action->zone_action(manager->context, zone, completion);
}
static void handle_preamble_error(struct vdo_completion *completion)
{
/* Skip the zone actions since the preamble failed. */
completion->callback = finish_action_callback;
preserve_error(completion);
}
static void launch_current_action(struct action_manager *manager)
{
struct action *action = manager->current_action;
int result = vdo_start_operation(&manager->state, action->operation);
if (result != VDO_SUCCESS) {
if (action->parent != NULL)
vdo_set_completion_result(action->parent, result);
/* We aren't going to run the preamble, so don't run the conclusion */
action->conclusion = no_conclusion;
finish_action_callback(&manager->completion);
return;
}
if (action->zone_action == NULL) {
prepare_for_conclusion(manager);
} else {
manager->acting_zone = 0;
vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone,
handle_preamble_error,
get_acting_zone_thread_id(manager),
manager->current_action->parent);
}
action->preamble(manager->context, &manager->completion);
}
/**
* vdo_schedule_default_action() - Attempt to schedule the default action.
* @manager: The action manager.
*
* If the manager is not operating normally, the action will not be scheduled.
*
* Return: true if an action was scheduled.
*/
bool vdo_schedule_default_action(struct action_manager *manager)
{
/* Don't schedule a default action if we are operating or not in normal operation. */
const struct admin_state_code *code = vdo_get_current_manager_operation(manager);
return ((code == VDO_ADMIN_STATE_NORMAL_OPERATION) &&
manager->scheduler(manager->context));
}
static void finish_action_callback(struct vdo_completion *completion)
{
bool has_next_action;
int result;
struct action_manager *manager = as_action_manager(completion);
struct action action = *(manager->current_action);
manager->current_action->in_use = false;
manager->current_action = manager->current_action->next;
/*
* We need to check this now to avoid use-after-free issues if running the conclusion or
* notifying the parent results in the manager being freed.
*/
has_next_action =
(manager->current_action->in_use || vdo_schedule_default_action(manager));
result = action.conclusion(manager->context);
vdo_finish_operation(&manager->state, VDO_SUCCESS);
if (action.parent != NULL)
vdo_continue_completion(action.parent, result);
if (has_next_action)
launch_current_action(manager);
}
/**
* vdo_schedule_action() - Schedule an action to be applied to all zones.
* @manager: The action manager to schedule the action on.
* @preamble: A method to be invoked on the initiator thread once this action is started but before
* applying to each zone; may be NULL.
* @action: The action to apply to each zone; may be NULL.
* @conclusion: A method to be invoked back on the initiator thread once the action has been
* applied to all zones; may be NULL.
* @parent: The object to notify once the action is complete or if the action can not be scheduled;
* may be NULL.
*
* The action will be launched immediately if there is no current action, or as soon as the current
* action completes. If there is already a pending action, this action will not be scheduled, and,
* if it has a parent, that parent will be notified. At least one of the preamble, action, or
* conclusion must not be NULL.
*
* Return: true if the action was scheduled.
*/
bool vdo_schedule_action(struct action_manager *manager, vdo_action_preamble_fn preamble,
vdo_zone_action_fn action, vdo_action_conclusion_fn conclusion,
struct vdo_completion *parent)
{
return vdo_schedule_operation(manager, VDO_ADMIN_STATE_OPERATING, preamble,
action, conclusion, parent);
}
/**
* vdo_schedule_operation() - Schedule an operation to be applied to all zones.
* @manager: The action manager to schedule the action on.
* @operation: The operation this action will perform
* @preamble: A method to be invoked on the initiator thread once this action is started but before
* applying to each zone; may be NULL.
* @action: The action to apply to each zone; may be NULL.
* @conclusion: A method to be invoked back on the initiator thread once the action has been
* applied to all zones; may be NULL.
* @parent: The object to notify once the action is complete or if the action can not be scheduled;
* may be NULL.
*
* The operation's action will be launched immediately if there is no current action, or as soon as
* the current action completes. If there is already a pending action, this operation will not be
* scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble,
* action, or conclusion must not be NULL.
*
* Return: true if the action was scheduled.
*/
bool vdo_schedule_operation(struct action_manager *manager,
const struct admin_state_code *operation,
vdo_action_preamble_fn preamble, vdo_zone_action_fn action,
vdo_action_conclusion_fn conclusion,
struct vdo_completion *parent)
{
return vdo_schedule_operation_with_context(manager, operation, preamble, action,
conclusion, NULL, parent);
}
/**
* vdo_schedule_operation_with_context() - Schedule an operation on all zones.
* @manager: The action manager to schedule the action on.
* @operation: The operation this action will perform.
* @preamble: A method to be invoked on the initiator thread once this action is started but before
* applying to each zone; may be NULL.
* @action: The action to apply to each zone; may be NULL.
* @conclusion: A method to be invoked back on the initiator thread once the action has been
* applied to all zones; may be NULL.
* @context: An action-specific context which may be retrieved via
* vdo_get_current_action_context(); may be NULL.
* @parent: The object to notify once the action is complete or if the action can not be scheduled;
* may be NULL.
*
* The operation's action will be launched immediately if there is no current action, or as soon as
* the current action completes. If there is already a pending action, this operation will not be
* scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble,
* action, or conclusion must not be NULL.
*
* Return: true if the action was scheduled
*/
bool vdo_schedule_operation_with_context(struct action_manager *manager,
const struct admin_state_code *operation,
vdo_action_preamble_fn preamble,
vdo_zone_action_fn action,
vdo_action_conclusion_fn conclusion,
void *context, struct vdo_completion *parent)
{
struct action *current_action;
ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == manager->initiator_thread_id),
"action initiated from correct thread");
if (!manager->current_action->in_use) {
current_action = manager->current_action;
} else if (!manager->current_action->next->in_use) {
current_action = manager->current_action->next;
} else {
if (parent != NULL)
vdo_continue_completion(parent, VDO_COMPONENT_BUSY);
return false;
}
*current_action = (struct action) {
.in_use = true,
.operation = operation,
.preamble = (preamble == NULL) ? no_preamble : preamble,
.zone_action = action,
.conclusion = (conclusion == NULL) ? no_conclusion : conclusion,
.context = context,
.parent = parent,
.next = current_action->next,
};
if (current_action == manager->current_action)
launch_current_action(manager);
return true;
}