| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright 2023 Red Hat |
| */ |
| |
| #include "funnel-queue.h" |
| |
| #include "cpu.h" |
| #include "memory-alloc.h" |
| #include "permassert.h" |
| |
| int vdo_make_funnel_queue(struct funnel_queue **queue_ptr) |
| { |
| int result; |
| struct funnel_queue *queue; |
| |
| result = vdo_allocate(1, struct funnel_queue, "funnel queue", &queue); |
| if (result != VDO_SUCCESS) |
| return result; |
| |
| /* |
| * Initialize the stub entry and put it in the queue, establishing the invariant that |
| * queue->newest and queue->oldest are never null. |
| */ |
| queue->stub.next = NULL; |
| queue->newest = &queue->stub; |
| queue->oldest = &queue->stub; |
| |
| *queue_ptr = queue; |
| return VDO_SUCCESS; |
| } |
| |
| void vdo_free_funnel_queue(struct funnel_queue *queue) |
| { |
| vdo_free(queue); |
| } |
| |
| static struct funnel_queue_entry *get_oldest(struct funnel_queue *queue) |
| { |
| /* |
| * Barrier requirements: We need a read barrier between reading a "next" field pointer |
| * value and reading anything it points to. There's an accompanying barrier in |
| * vdo_funnel_queue_put() between its caller setting up the entry and making it visible. |
| */ |
| struct funnel_queue_entry *oldest = queue->oldest; |
| struct funnel_queue_entry *next = READ_ONCE(oldest->next); |
| |
| if (oldest == &queue->stub) { |
| /* |
| * When the oldest entry is the stub and it has no successor, the queue is |
| * logically empty. |
| */ |
| if (next == NULL) |
| return NULL; |
| /* |
| * The stub entry has a successor, so the stub can be dequeued and ignored without |
| * breaking the queue invariants. |
| */ |
| oldest = next; |
| queue->oldest = oldest; |
| next = READ_ONCE(oldest->next); |
| } |
| |
| /* |
| * We have a non-stub candidate to dequeue. If it lacks a successor, we'll need to put the |
| * stub entry back on the queue first. |
| */ |
| if (next == NULL) { |
| struct funnel_queue_entry *newest = READ_ONCE(queue->newest); |
| |
| if (oldest != newest) { |
| /* |
| * Another thread has already swung queue->newest atomically, but not yet |
| * assigned previous->next. The queue is really still empty. |
| */ |
| return NULL; |
| } |
| |
| /* |
| * Put the stub entry back on the queue, ensuring a successor will eventually be |
| * seen. |
| */ |
| vdo_funnel_queue_put(queue, &queue->stub); |
| |
| /* Check again for a successor. */ |
| next = READ_ONCE(oldest->next); |
| if (next == NULL) { |
| /* |
| * We lost a race with a producer who swapped queue->newest before we did, |
| * but who hasn't yet updated previous->next. Try again later. |
| */ |
| return NULL; |
| } |
| } |
| |
| return oldest; |
| } |
| |
| /* |
| * Poll a queue, removing the oldest entry if the queue is not empty. This function must only be |
| * called from a single consumer thread. |
| */ |
| struct funnel_queue_entry *vdo_funnel_queue_poll(struct funnel_queue *queue) |
| { |
| struct funnel_queue_entry *oldest = get_oldest(queue); |
| |
| if (oldest == NULL) |
| return oldest; |
| |
| /* |
| * Dequeue the oldest entry and return it. Only one consumer thread may call this function, |
| * so no locking, atomic operations, or fences are needed; queue->oldest is owned by the |
| * consumer and oldest->next is never used by a producer thread after it is swung from NULL |
| * to non-NULL. |
| */ |
| queue->oldest = READ_ONCE(oldest->next); |
| /* |
| * Make sure the caller sees the proper stored data for this entry. Since we've already |
| * fetched the entry pointer we stored in "queue->oldest", this also ensures that on entry |
| * to the next call we'll properly see the dependent data. |
| */ |
| smp_rmb(); |
| /* |
| * If "oldest" is a very light-weight work item, we'll be looking for the next one very |
| * soon, so prefetch it now. |
| */ |
| uds_prefetch_address(queue->oldest, true); |
| WRITE_ONCE(oldest->next, NULL); |
| return oldest; |
| } |
| |
| /* |
| * Check whether the funnel queue is empty or not. If the queue is in a transition state with one |
| * or more entries being added such that the list view is incomplete, this function will report the |
| * queue as empty. |
| */ |
| bool vdo_is_funnel_queue_empty(struct funnel_queue *queue) |
| { |
| return get_oldest(queue) == NULL; |
| } |
| |
| /* |
| * Check whether the funnel queue is idle or not. If the queue has entries available to be |
| * retrieved, it is not idle. If the queue is in a transition state with one or more entries being |
| * added such that the list view is incomplete, it may not be possible to retrieve an entry with |
| * the vdo_funnel_queue_poll() function, but the queue will not be considered idle. |
| */ |
| bool vdo_is_funnel_queue_idle(struct funnel_queue *queue) |
| { |
| /* |
| * Oldest is not the stub, so there's another entry, though if next is NULL we can't |
| * retrieve it yet. |
| */ |
| if (queue->oldest != &queue->stub) |
| return false; |
| |
| /* |
| * Oldest is the stub, but newest has been updated by _put(); either there's another, |
| * retrievable entry in the list, or the list is officially empty but in the intermediate |
| * state of having an entry added. |
| * |
| * Whether anything is retrievable depends on whether stub.next has been updated and become |
| * visible to us, but for idleness we don't care. And due to memory ordering in _put(), the |
| * update to newest would be visible to us at the same time or sooner. |
| */ |
| if (READ_ONCE(queue->newest) != &queue->stub) |
| return false; |
| |
| return true; |
| } |