mirror of
https://github.com/torvalds/linux.git
synced 2024-11-13 23:51:39 +00:00
6a79248b42
Also rename ASSERT to VDO_ASSERT and ASSERT_LOG_ONLY to VDO_ASSERT_LOG_ONLY. But re-introduce ASSERT and ASSERT_LOG_ONLY as a placeholder for the benefit of dm-vdo/indexer. Signed-off-by: Mike Snitzer <snitzer@kernel.org> Signed-off-by: Matthew Sakai <msakai@redhat.com>
225 lines
6.5 KiB
C
225 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2023 Red Hat
|
|
*/
|
|
|
|
#include "priority-table.h"
|
|
|
|
#include <linux/log2.h>
|
|
|
|
#include "errors.h"
|
|
#include "memory-alloc.h"
|
|
#include "permassert.h"
|
|
|
|
#include "status-codes.h"
|
|
|
|
/* We use a single 64-bit search vector, so the maximum priority is 63 */
|
|
#define MAX_PRIORITY 63
|
|
|
|
/*
|
|
* All the entries with the same priority are queued in a circular list in a bucket for that
|
|
* priority. The table is essentially an array of buckets.
|
|
*/
|
|
struct bucket {
|
|
/*
|
|
* The head of a queue of table entries, all having the same priority
|
|
*/
|
|
struct list_head queue;
|
|
/* The priority of all the entries in this bucket */
|
|
unsigned int priority;
|
|
};
|
|
|
|
/*
|
|
* A priority table is an array of buckets, indexed by priority. New entries are added to the end
|
|
* of the queue in the appropriate bucket. The dequeue operation finds the highest-priority
|
|
* non-empty bucket by searching a bit vector represented as a single 8-byte word, which is very
|
|
* fast with compiler and CPU support.
|
|
*/
|
|
struct priority_table {
|
|
/* The maximum priority of entries that may be stored in this table */
|
|
unsigned int max_priority;
|
|
/* A bit vector flagging all buckets that are currently non-empty */
|
|
u64 search_vector;
|
|
/* The array of all buckets, indexed by priority */
|
|
struct bucket buckets[];
|
|
};
|
|
|
|
/**
|
|
* vdo_make_priority_table() - Allocate and initialize a new priority_table.
|
|
* @max_priority: The maximum priority value for table entries.
|
|
* @table_ptr: A pointer to hold the new table.
|
|
*
|
|
* Return: VDO_SUCCESS or an error code.
|
|
*/
|
|
int vdo_make_priority_table(unsigned int max_priority, struct priority_table **table_ptr)
|
|
{
|
|
struct priority_table *table;
|
|
int result;
|
|
unsigned int priority;
|
|
|
|
if (max_priority > MAX_PRIORITY)
|
|
return UDS_INVALID_ARGUMENT;
|
|
|
|
result = vdo_allocate_extended(struct priority_table, max_priority + 1,
|
|
struct bucket, __func__, &table);
|
|
if (result != VDO_SUCCESS)
|
|
return result;
|
|
|
|
for (priority = 0; priority <= max_priority; priority++) {
|
|
struct bucket *bucket = &table->buckets[priority];
|
|
|
|
bucket->priority = priority;
|
|
INIT_LIST_HEAD(&bucket->queue);
|
|
}
|
|
|
|
table->max_priority = max_priority;
|
|
table->search_vector = 0;
|
|
|
|
*table_ptr = table;
|
|
return VDO_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* vdo_free_priority_table() - Free a priority_table.
|
|
* @table: The table to free.
|
|
*
|
|
* The table does not own the entries stored in it and they are not freed by this call.
|
|
*/
|
|
void vdo_free_priority_table(struct priority_table *table)
|
|
{
|
|
if (table == NULL)
|
|
return;
|
|
|
|
/*
|
|
* Unlink the buckets from any entries still in the table so the entries won't be left with
|
|
* dangling pointers to freed memory.
|
|
*/
|
|
vdo_reset_priority_table(table);
|
|
|
|
vdo_free(table);
|
|
}
|
|
|
|
/**
|
|
* vdo_reset_priority_table() - Reset a priority table, leaving it in the same empty state as when
|
|
* newly constructed.
|
|
* @table: The table to reset.
|
|
*
|
|
* The table does not own the entries stored in it and they are not freed (or even unlinked from
|
|
* each other) by this call.
|
|
*/
|
|
void vdo_reset_priority_table(struct priority_table *table)
|
|
{
|
|
unsigned int priority;
|
|
|
|
table->search_vector = 0;
|
|
for (priority = 0; priority <= table->max_priority; priority++)
|
|
list_del_init(&table->buckets[priority].queue);
|
|
}
|
|
|
|
/**
|
|
* vdo_priority_table_enqueue() - Add a new entry to the priority table, appending it to the queue
|
|
* for entries with the specified priority.
|
|
* @table: The table in which to store the entry.
|
|
* @priority: The priority of the entry.
|
|
* @entry: The list_head embedded in the entry to store in the table (the caller must have
|
|
* initialized it).
|
|
*/
|
|
void vdo_priority_table_enqueue(struct priority_table *table, unsigned int priority,
|
|
struct list_head *entry)
|
|
{
|
|
VDO_ASSERT_LOG_ONLY((priority <= table->max_priority),
|
|
"entry priority must be valid for the table");
|
|
|
|
/* Append the entry to the queue in the specified bucket. */
|
|
list_move_tail(entry, &table->buckets[priority].queue);
|
|
|
|
/* Flag the bucket in the search vector since it must be non-empty. */
|
|
table->search_vector |= (1ULL << priority);
|
|
}
|
|
|
|
static inline void mark_bucket_empty(struct priority_table *table, struct bucket *bucket)
|
|
{
|
|
table->search_vector &= ~(1ULL << bucket->priority);
|
|
}
|
|
|
|
/**
|
|
* vdo_priority_table_dequeue() - Find the highest-priority entry in the table, remove it from the
|
|
* table, and return it.
|
|
* @table: The priority table from which to remove an entry.
|
|
*
|
|
* If there are multiple entries with the same priority, the one that has been in the table with
|
|
* that priority the longest will be returned.
|
|
*
|
|
* Return: The dequeued entry, or NULL if the table is currently empty.
|
|
*/
|
|
struct list_head *vdo_priority_table_dequeue(struct priority_table *table)
|
|
{
|
|
struct bucket *bucket;
|
|
struct list_head *entry;
|
|
int top_priority;
|
|
|
|
if (table->search_vector == 0) {
|
|
/* All buckets are empty. */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find the highest priority non-empty bucket by finding the highest-order non-zero bit in
|
|
* the search vector.
|
|
*/
|
|
top_priority = ilog2(table->search_vector);
|
|
|
|
/* Dequeue the first entry in the bucket. */
|
|
bucket = &table->buckets[top_priority];
|
|
entry = bucket->queue.next;
|
|
list_del_init(entry);
|
|
|
|
/* Clear the bit in the search vector if the bucket has been emptied. */
|
|
if (list_empty(&bucket->queue))
|
|
mark_bucket_empty(table, bucket);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* vdo_priority_table_remove() - Remove a specified entry from its priority table.
|
|
* @table: The table from which to remove the entry.
|
|
* @entry: The entry to remove from the table.
|
|
*/
|
|
void vdo_priority_table_remove(struct priority_table *table, struct list_head *entry)
|
|
{
|
|
struct list_head *next_entry;
|
|
|
|
/*
|
|
* We can't guard against calls where the entry is on a list for a different table, but
|
|
* it's easy to deal with an entry not in any table or list.
|
|
*/
|
|
if (list_empty(entry))
|
|
return;
|
|
|
|
/*
|
|
* Remove the entry from the bucket list, remembering a pointer to another entry in the
|
|
* ring.
|
|
*/
|
|
next_entry = entry->next;
|
|
list_del_init(entry);
|
|
|
|
/*
|
|
* If the rest of the list is now empty, the next node must be the list head in the bucket
|
|
* and we can use it to update the search vector.
|
|
*/
|
|
if (list_empty(next_entry))
|
|
mark_bucket_empty(table, list_entry(next_entry, struct bucket, queue));
|
|
}
|
|
|
|
/**
|
|
* vdo_is_priority_table_empty() - Return whether the priority table is empty.
|
|
* @table: The table to check.
|
|
*
|
|
* Return: true if the table is empty.
|
|
*/
|
|
bool vdo_is_priority_table_empty(struct priority_table *table)
|
|
{
|
|
return (table->search_vector == 0);
|
|
}
|