1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2023 Red Hat
6 #include "priority-table.h"
8 #include <linux/log2.h>
11 #include "memory-alloc.h"
12 #include "permassert.h"
14 #include "status-codes.h"
16 /* We use a single 64-bit search vector, so the maximum priority is 63 */
17 #define MAX_PRIORITY 63
20 * All the entries with the same priority are queued in a circular list in a bucket for that
21 * priority. The table is essentially an array of buckets.
25 * The head of a queue of table entries, all having the same priority
27 struct list_head queue
;
28 /* The priority of all the entries in this bucket */
29 unsigned int priority
;
33 * A priority table is an array of buckets, indexed by priority. New entries are added to the end
34 * of the queue in the appropriate bucket. The dequeue operation finds the highest-priority
35 * non-empty bucket by searching a bit vector represented as a single 8-byte word, which is very
36 * fast with compiler and CPU support.
38 struct priority_table
{
39 /* The maximum priority of entries that may be stored in this table */
40 unsigned int max_priority
;
41 /* A bit vector flagging all buckets that are currently non-empty */
43 /* The array of all buckets, indexed by priority */
44 struct bucket buckets
[];
48 * vdo_make_priority_table() - Allocate and initialize a new priority_table.
49 * @max_priority: The maximum priority value for table entries.
50 * @table_ptr: A pointer to hold the new table.
52 * Return: VDO_SUCCESS or an error code.
54 int vdo_make_priority_table(unsigned int max_priority
, struct priority_table
**table_ptr
)
56 struct priority_table
*table
;
58 unsigned int priority
;
60 if (max_priority
> MAX_PRIORITY
)
61 return UDS_INVALID_ARGUMENT
;
63 result
= vdo_allocate_extended(struct priority_table
, max_priority
+ 1,
64 struct bucket
, __func__
, &table
);
65 if (result
!= VDO_SUCCESS
)
68 for (priority
= 0; priority
<= max_priority
; priority
++) {
69 struct bucket
*bucket
= &table
->buckets
[priority
];
71 bucket
->priority
= priority
;
72 INIT_LIST_HEAD(&bucket
->queue
);
75 table
->max_priority
= max_priority
;
76 table
->search_vector
= 0;
83 * vdo_free_priority_table() - Free a priority_table.
84 * @table: The table to free.
86 * The table does not own the entries stored in it and they are not freed by this call.
88 void vdo_free_priority_table(struct priority_table
*table
)
94 * Unlink the buckets from any entries still in the table so the entries won't be left with
95 * dangling pointers to freed memory.
97 vdo_reset_priority_table(table
);
103 * vdo_reset_priority_table() - Reset a priority table, leaving it in the same empty state as when
105 * @table: The table to reset.
107 * The table does not own the entries stored in it and they are not freed (or even unlinked from
108 * each other) by this call.
110 void vdo_reset_priority_table(struct priority_table
*table
)
112 unsigned int priority
;
114 table
->search_vector
= 0;
115 for (priority
= 0; priority
<= table
->max_priority
; priority
++)
116 list_del_init(&table
->buckets
[priority
].queue
);
120 * vdo_priority_table_enqueue() - Add a new entry to the priority table, appending it to the queue
121 * for entries with the specified priority.
122 * @table: The table in which to store the entry.
123 * @priority: The priority of the entry.
124 * @entry: The list_head embedded in the entry to store in the table (the caller must have
127 void vdo_priority_table_enqueue(struct priority_table
*table
, unsigned int priority
,
128 struct list_head
*entry
)
130 VDO_ASSERT_LOG_ONLY((priority
<= table
->max_priority
),
131 "entry priority must be valid for the table");
133 /* Append the entry to the queue in the specified bucket. */
134 list_move_tail(entry
, &table
->buckets
[priority
].queue
);
136 /* Flag the bucket in the search vector since it must be non-empty. */
137 table
->search_vector
|= (1ULL << priority
);
140 static inline void mark_bucket_empty(struct priority_table
*table
, struct bucket
*bucket
)
142 table
->search_vector
&= ~(1ULL << bucket
->priority
);
146 * vdo_priority_table_dequeue() - Find the highest-priority entry in the table, remove it from the
147 * table, and return it.
148 * @table: The priority table from which to remove an entry.
150 * If there are multiple entries with the same priority, the one that has been in the table with
151 * that priority the longest will be returned.
153 * Return: The dequeued entry, or NULL if the table is currently empty.
155 struct list_head
*vdo_priority_table_dequeue(struct priority_table
*table
)
157 struct bucket
*bucket
;
158 struct list_head
*entry
;
161 if (table
->search_vector
== 0) {
162 /* All buckets are empty. */
167 * Find the highest priority non-empty bucket by finding the highest-order non-zero bit in
170 top_priority
= ilog2(table
->search_vector
);
172 /* Dequeue the first entry in the bucket. */
173 bucket
= &table
->buckets
[top_priority
];
174 entry
= bucket
->queue
.next
;
175 list_del_init(entry
);
177 /* Clear the bit in the search vector if the bucket has been emptied. */
178 if (list_empty(&bucket
->queue
))
179 mark_bucket_empty(table
, bucket
);
185 * vdo_priority_table_remove() - Remove a specified entry from its priority table.
186 * @table: The table from which to remove the entry.
187 * @entry: The entry to remove from the table.
189 void vdo_priority_table_remove(struct priority_table
*table
, struct list_head
*entry
)
191 struct list_head
*next_entry
;
194 * We can't guard against calls where the entry is on a list for a different table, but
195 * it's easy to deal with an entry not in any table or list.
197 if (list_empty(entry
))
201 * Remove the entry from the bucket list, remembering a pointer to another entry in the
204 next_entry
= entry
->next
;
205 list_del_init(entry
);
208 * If the rest of the list is now empty, the next node must be the list head in the bucket
209 * and we can use it to update the search vector.
211 if (list_empty(next_entry
))
212 mark_bucket_empty(table
, list_entry(next_entry
, struct bucket
, queue
));
216 * vdo_is_priority_table_empty() - Return whether the priority table is empty.
217 * @table: The table to check.
219 * Return: true if the table is empty.
221 bool vdo_is_priority_table_empty(struct priority_table
*table
)
223 return (table
->search_vector
== 0);