| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright 2023 Red Hat | |
| */ | |
| #include "completion.h" | |
| #include <linux/kernel.h> | |
| #include "logger.h" | |
| #include "permassert.h" | |
| #include "status-codes.h" | |
| #include "types.h" | |
| #include "vio.h" | |
| #include "vdo.h" | |
| /** | |
| * DOC: vdo completions. | |
| * | |
| * Most of vdo's data structures are lock free, each either belonging to a single "zone," or | |
| * divided into a number of zones whose accesses to the structure do not overlap. During normal | |
| * operation, at most one thread will be operating in any given zone. Each zone has a | |
| * vdo_work_queue which holds vdo_completions that are to be run in that zone. A completion may | |
| * only be enqueued on one queue or operating in a single zone at a time. | |
| * | |
| * At each step of a multi-threaded operation, the completion performing the operation is given a | |
| * callback, error handler, and thread id for the next step. A completion is "run" when it is | |
| * operating on the correct thread (as specified by its callback_thread_id). If the value of its | |
| * "result" field is an error (i.e. not VDO_SUCCESS), the function in its "error_handler" will be | |
| * invoked. If the error_handler is NULL, or there is no error, the function set as its "callback" | |
| * will be invoked. Generally, a completion will not be run directly, but rather will be | |
| * "launched." In this case, it will check whether it is operating on the correct thread. If it is, | |
| * it will run immediately. Otherwise, it will be enqueue on the vdo_work_queue associated with the | |
| * completion's "callback_thread_id". When it is dequeued, it will be on the correct thread, and | |
| * will get run. In some cases, the completion should get queued instead of running immediately, | |
| * even if it is being launched from the correct thread. This is usually in cases where there is a | |
| * long chain of callbacks, all on the same thread, which could overflow the stack. In such cases, | |
| * the completion's "requeue" field should be set to true. Doing so will skip the current thread | |
| * check and simply enqueue the completion. | |
| * | |
| * A completion may be "finished," in which case its "complete" field will be set to true before it | |
| * is next run. It is a bug to attempt to set the result or re-finish a finished completion. | |
| * Because a completion's fields are not safe to examine from any thread other than the one on | |
| * which the completion is currently operating, this field is used only to aid in detecting | |
| * programming errors. It can not be used for cross-thread checking on the status of an operation. | |
| * A completion must be "reset" before it can be reused after it has been finished. Resetting will | |
| * also clear any error from the result field. | |
| **/ | |
| void vdo_initialize_completion(struct vdo_completion *completion, | |
| struct vdo *vdo, | |
| enum vdo_completion_type type) | |
| { | |
| memset(completion, 0, sizeof(*completion)); | |
| completion->vdo = vdo; | |
| completion->type = type; | |
| vdo_reset_completion(completion); | |
| } | |
| static inline void assert_incomplete(struct vdo_completion *completion) | |
| { | |
| VDO_ASSERT_LOG_ONLY(!completion->complete, "completion is not complete"); | |
| } | |
| /** | |
| * vdo_set_completion_result() - Set the result of a completion. | |
| * @completion: The completion to update. | |
| * @result: The result to set. | |
| * | |
| * Older errors will not be masked. | |
| */ | |
| void vdo_set_completion_result(struct vdo_completion *completion, int result) | |
| { | |
| assert_incomplete(completion); | |
| if (completion->result == VDO_SUCCESS) | |
| completion->result = result; | |
| } | |
| /** | |
| * vdo_launch_completion_with_priority() - Run or enqueue a completion. | |
| * @completion: The completion to launch. | |
| * @priority: The priority at which to enqueue the completion. | |
| * | |
| * If called on the correct thread (i.e. the one specified in the completion's callback_thread_id | |
| * field) and not marked for requeue, the completion will be run immediately. Otherwise, the | |
| * completion will be enqueued on the specified thread. | |
| */ | |
| void vdo_launch_completion_with_priority(struct vdo_completion *completion, | |
| enum vdo_completion_priority priority) | |
| { | |
| thread_id_t callback_thread = completion->callback_thread_id; | |
| if (completion->requeue || (callback_thread != vdo_get_callback_thread_id())) { | |
| vdo_enqueue_completion(completion, priority); | |
| return; | |
| } | |
| vdo_run_completion(completion); | |
| } | |
| /** vdo_finish_completion() - Mark a completion as complete and then launch it. */ | |
| void vdo_finish_completion(struct vdo_completion *completion) | |
| { | |
| assert_incomplete(completion); | |
| completion->complete = true; | |
| if (completion->callback != NULL) | |
| vdo_launch_completion(completion); | |
| } | |
| void vdo_enqueue_completion(struct vdo_completion *completion, | |
| enum vdo_completion_priority priority) | |
| { | |
| struct vdo *vdo = completion->vdo; | |
| thread_id_t thread_id = completion->callback_thread_id; | |
| if (VDO_ASSERT(thread_id < vdo->thread_config.thread_count, | |
| "thread_id %u (completion type %d) is less than thread count %u", | |
| thread_id, completion->type, | |
| vdo->thread_config.thread_count) != VDO_SUCCESS) | |
| BUG(); | |
| completion->requeue = false; | |
| completion->priority = priority; | |
| completion->my_queue = NULL; | |
| vdo_enqueue_work_queue(vdo->threads[thread_id].queue, completion); | |
| } | |
| /** | |
| * vdo_requeue_completion_if_needed() - Requeue a completion if not called on the specified thread. | |
| * @completion: The completion to requeue. | |
| * @callback_thread_id: The thread on which to requeue the completion. | |
| * | |
| * Return: True if the completion was requeued; callers may not access the completion in this case. | |
| */ | |
| bool vdo_requeue_completion_if_needed(struct vdo_completion *completion, | |
| thread_id_t callback_thread_id) | |
| { | |
| if (vdo_get_callback_thread_id() == callback_thread_id) | |
| return false; | |
| completion->callback_thread_id = callback_thread_id; | |
| vdo_enqueue_completion(completion, VDO_WORK_Q_DEFAULT_PRIORITY); | |
| return true; | |
| } |