206 lines
6.6 KiB
C
206 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2023 Red Hat
|
|
*/
|
|
|
|
#include "wait-queue.h"
|
|
|
|
#include <linux/device-mapper.h>
|
|
|
|
#include "permassert.h"
|
|
|
|
#include "status-codes.h"
|
|
|
|
/**
|
|
* vdo_waitq_enqueue_waiter() - Add a waiter to the tail end of a waitq.
|
|
* @waitq: The vdo_wait_queue to which to add the waiter.
|
|
* @waiter: The waiter to add to the waitq.
|
|
*
|
|
* The waiter must not already be waiting in a waitq.
|
|
*/
|
|
void vdo_waitq_enqueue_waiter(struct vdo_wait_queue *waitq, struct vdo_waiter *waiter)
|
|
{
|
|
BUG_ON(waiter->next_waiter != NULL);
|
|
|
|
if (waitq->last_waiter == NULL) {
|
|
/*
|
|
* The waitq is empty, so form the initial circular list by self-linking the
|
|
* initial waiter.
|
|
*/
|
|
waiter->next_waiter = waiter;
|
|
} else {
|
|
/* Splice the new waiter in at the end of the waitq. */
|
|
waiter->next_waiter = waitq->last_waiter->next_waiter;
|
|
waitq->last_waiter->next_waiter = waiter;
|
|
}
|
|
|
|
/* In both cases, the waiter we added to the ring becomes the last waiter. */
|
|
waitq->last_waiter = waiter;
|
|
waitq->length += 1;
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_transfer_all_waiters() - Transfer all waiters from one waitq to
|
|
* a second waitq, emptying the first waitq.
|
|
* @from_waitq: The waitq containing the waiters to move.
|
|
* @to_waitq: The waitq that will receive the waiters from the first waitq.
|
|
*/
|
|
void vdo_waitq_transfer_all_waiters(struct vdo_wait_queue *from_waitq,
|
|
struct vdo_wait_queue *to_waitq)
|
|
{
|
|
/* If the source waitq is empty, there's nothing to do. */
|
|
if (!vdo_waitq_has_waiters(from_waitq))
|
|
return;
|
|
|
|
if (vdo_waitq_has_waiters(to_waitq)) {
|
|
/*
|
|
* Both are non-empty. Splice the two circular lists together
|
|
* by swapping the next (head) pointers in the list tails.
|
|
*/
|
|
struct vdo_waiter *from_head = from_waitq->last_waiter->next_waiter;
|
|
struct vdo_waiter *to_head = to_waitq->last_waiter->next_waiter;
|
|
|
|
to_waitq->last_waiter->next_waiter = from_head;
|
|
from_waitq->last_waiter->next_waiter = to_head;
|
|
}
|
|
|
|
to_waitq->last_waiter = from_waitq->last_waiter;
|
|
to_waitq->length += from_waitq->length;
|
|
vdo_waitq_init(from_waitq);
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_notify_all_waiters() - Notify all the entries waiting in a waitq.
|
|
* @waitq: The vdo_wait_queue containing the waiters to notify.
|
|
* @callback: The function to call to notify each waiter, or NULL to invoke the callback field
|
|
* registered in each waiter.
|
|
* @context: The context to pass to the callback function.
|
|
*
|
|
* Notifies all the entries waiting in a waitq to continue execution by invoking a callback
|
|
* function on each of them in turn. The waitq is copied and emptied before invoking any callbacks,
|
|
* and only the waiters that were in the waitq at the start of the call will be notified.
|
|
*/
|
|
void vdo_waitq_notify_all_waiters(struct vdo_wait_queue *waitq,
|
|
vdo_waiter_callback_fn callback, void *context)
|
|
{
|
|
/*
|
|
* Copy and empty the waitq first, avoiding the possibility of an infinite
|
|
* loop if entries are returned to the waitq by the callback function.
|
|
*/
|
|
struct vdo_wait_queue waiters;
|
|
|
|
vdo_waitq_init(&waiters);
|
|
vdo_waitq_transfer_all_waiters(waitq, &waiters);
|
|
|
|
/* Drain the copied waitq, invoking the callback on every entry. */
|
|
while (vdo_waitq_has_waiters(&waiters))
|
|
vdo_waitq_notify_next_waiter(&waiters, callback, context);
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_get_first_waiter() - Return the waiter that is at the head end of a waitq.
|
|
* @waitq: The vdo_wait_queue from which to get the first waiter.
|
|
*
|
|
* Return: The first (oldest) waiter in the waitq, or NULL if the waitq is empty.
|
|
*/
|
|
struct vdo_waiter *vdo_waitq_get_first_waiter(const struct vdo_wait_queue *waitq)
|
|
{
|
|
struct vdo_waiter *last_waiter = waitq->last_waiter;
|
|
|
|
if (last_waiter == NULL) {
|
|
/* There are no waiters, so we're done. */
|
|
return NULL;
|
|
}
|
|
|
|
/* The waitq is circular, so the last entry links to the head of the waitq. */
|
|
return last_waiter->next_waiter;
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_dequeue_matching_waiters() - Remove all waiters that match based on the specified
|
|
* matching method and append them to a vdo_wait_queue.
|
|
* @waitq: The vdo_wait_queue to process.
|
|
* @waiter_match: The method to determine matching.
|
|
* @match_context: Contextual info for the match method.
|
|
* @matched_waitq: A wait_waitq to store matches.
|
|
*/
|
|
void vdo_waitq_dequeue_matching_waiters(struct vdo_wait_queue *waitq,
|
|
vdo_waiter_match_fn waiter_match,
|
|
void *match_context,
|
|
struct vdo_wait_queue *matched_waitq)
|
|
{
|
|
struct vdo_wait_queue iteration_waitq;
|
|
|
|
vdo_waitq_init(&iteration_waitq);
|
|
vdo_waitq_transfer_all_waiters(waitq, &iteration_waitq);
|
|
|
|
while (vdo_waitq_has_waiters(&iteration_waitq)) {
|
|
struct vdo_waiter *waiter = vdo_waitq_dequeue_waiter(&iteration_waitq);
|
|
|
|
vdo_waitq_enqueue_waiter((waiter_match(waiter, match_context) ?
|
|
matched_waitq : waitq), waiter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_dequeue_waiter() - Remove the first (oldest) waiter from a waitq.
|
|
* @waitq: The vdo_wait_queue from which to remove the first entry.
|
|
*
|
|
* The caller will be responsible for waking the waiter by continuing its
|
|
* execution appropriately.
|
|
*
|
|
* Return: The first (oldest) waiter in the waitq, or NULL if the waitq is empty.
|
|
*/
|
|
struct vdo_waiter *vdo_waitq_dequeue_waiter(struct vdo_wait_queue *waitq)
|
|
{
|
|
struct vdo_waiter *first_waiter = vdo_waitq_get_first_waiter(waitq);
|
|
struct vdo_waiter *last_waiter = waitq->last_waiter;
|
|
|
|
if (first_waiter == NULL)
|
|
return NULL;
|
|
|
|
if (first_waiter == last_waiter) {
|
|
/* The waitq has a single entry, so empty it by nulling the tail. */
|
|
waitq->last_waiter = NULL;
|
|
} else {
|
|
/*
|
|
* The waitq has multiple waiters, so splice the first waiter out
|
|
* of the circular waitq.
|
|
*/
|
|
last_waiter->next_waiter = first_waiter->next_waiter;
|
|
}
|
|
|
|
/* The waiter is no longer in a waitq. */
|
|
first_waiter->next_waiter = NULL;
|
|
waitq->length -= 1;
|
|
|
|
return first_waiter;
|
|
}
|
|
|
|
/**
|
|
* vdo_waitq_notify_next_waiter() - Notify the next entry waiting in a waitq.
|
|
* @waitq: The vdo_wait_queue containing the waiter to notify.
|
|
* @callback: The function to call to notify the waiter, or NULL to invoke the callback field
|
|
* registered in the waiter.
|
|
* @context: The context to pass to the callback function.
|
|
*
|
|
* Notifies the next entry waiting in a waitq to continue execution by invoking a callback function
|
|
* on it after removing it from the waitq.
|
|
*
|
|
* Return: true if there was a waiter in the waitq.
|
|
*/
|
|
bool vdo_waitq_notify_next_waiter(struct vdo_wait_queue *waitq,
|
|
vdo_waiter_callback_fn callback, void *context)
|
|
{
|
|
struct vdo_waiter *waiter = vdo_waitq_dequeue_waiter(waitq);
|
|
|
|
if (waiter == NULL)
|
|
return false;
|
|
|
|
if (callback == NULL)
|
|
callback = waiter->callback;
|
|
callback(waiter, context);
|
|
|
|
return true;
|
|
}
|