XArray: Redesign xa_alloc API

It was too easy to forget to initialise the start index.  Add an
xa_limit data structure which can be used to pass min & max, and
define a couple of special values for common cases.  Also add some
more tests cribbed from the IDR test suite.  Change the return value
from -ENOSPC to -EBUSY to match xa_insert().

Signed-off-by: Matthew Wilcox <willy@infradead.org>
This commit is contained in:
Matthew Wilcox 2018-12-31 10:41:01 -05:00
parent 3ccaf57a6a
commit a3e4d3f97e
3 changed files with 135 additions and 60 deletions

View File

@ -200,6 +200,27 @@ static inline int xa_err(void *entry)
return 0; return 0;
} }
/**
* struct xa_limit - Represents a range of IDs.
* @min: The lowest ID to allocate (inclusive).
* @max: The maximum ID to allocate (inclusive).
*
* This structure is used either directly or via the XA_LIMIT() macro
* to communicate the range of IDs that are valid for allocation.
* Two common ranges are predefined for you:
* * xa_limit_32b - [0 - UINT_MAX]
* * xa_limit_31b - [0 - INT_MAX]
*/
struct xa_limit {
u32 max;
u32 min;
};
#define XA_LIMIT(_min, _max) (struct xa_limit) { .min = _min, .max = _max }
#define xa_limit_32b XA_LIMIT(0, UINT_MAX)
#define xa_limit_31b XA_LIMIT(0, INT_MAX)
typedef unsigned __bitwise xa_mark_t; typedef unsigned __bitwise xa_mark_t;
#define XA_MARK_0 ((__force xa_mark_t)0U) #define XA_MARK_0 ((__force xa_mark_t)0U)
#define XA_MARK_1 ((__force xa_mark_t)1U) #define XA_MARK_1 ((__force xa_mark_t)1U)
@ -476,7 +497,8 @@ void *__xa_store(struct xarray *, unsigned long index, void *entry, gfp_t);
void *__xa_cmpxchg(struct xarray *, unsigned long index, void *old, void *__xa_cmpxchg(struct xarray *, unsigned long index, void *old,
void *entry, gfp_t); void *entry, gfp_t);
int __xa_insert(struct xarray *, unsigned long index, void *entry, gfp_t); int __xa_insert(struct xarray *, unsigned long index, void *entry, gfp_t);
int __xa_alloc(struct xarray *, u32 *id, u32 max, void *entry, gfp_t); int __must_check __xa_alloc(struct xarray *, u32 *id, void *entry,
struct xa_limit, gfp_t);
int __xa_reserve(struct xarray *, unsigned long index, gfp_t); int __xa_reserve(struct xarray *, unsigned long index, gfp_t);
void __xa_set_mark(struct xarray *, unsigned long index, xa_mark_t); void __xa_set_mark(struct xarray *, unsigned long index, xa_mark_t);
void __xa_clear_mark(struct xarray *, unsigned long index, xa_mark_t); void __xa_clear_mark(struct xarray *, unsigned long index, xa_mark_t);
@ -753,26 +775,26 @@ static inline int xa_insert_irq(struct xarray *xa, unsigned long index,
* xa_alloc() - Find somewhere to store this entry in the XArray. * xa_alloc() - Find somewhere to store this entry in the XArray.
* @xa: XArray. * @xa: XArray.
* @id: Pointer to ID. * @id: Pointer to ID.
* @max: Maximum ID to allocate (inclusive).
* @entry: New entry. * @entry: New entry.
* @limit: Range of ID to allocate.
* @gfp: Memory allocation flags. * @gfp: Memory allocation flags.
* *
* Allocates an unused ID in the range specified by @id and @max. * Finds an empty entry in @xa between @limit.min and @limit.max,
* Updates the @id pointer with the index, then stores the entry at that * stores the index into the @id pointer, then stores the entry at
* index. A concurrent lookup will not see an uninitialised @id. * that index. A concurrent lookup will not see an uninitialised @id.
* *
* Context: Process context. Takes and releases the xa_lock. May sleep if * Context: Any context. Takes and releases the xa_lock. May sleep if
* the @gfp flags permit. * the @gfp flags permit.
* Return: 0 on success, -ENOMEM if memory allocation fails or -ENOSPC if * Return: 0 on success, -ENOMEM if memory could not be allocated or
* there is no more space in the XArray. * -EBUSY if there are no free entries in @limit.
*/ */
static inline int xa_alloc(struct xarray *xa, u32 *id, u32 max, void *entry, static inline __must_check int xa_alloc(struct xarray *xa, u32 *id,
gfp_t gfp) void *entry, struct xa_limit limit, gfp_t gfp)
{ {
int err; int err;
xa_lock(xa); xa_lock(xa);
err = __xa_alloc(xa, id, max, entry, gfp); err = __xa_alloc(xa, id, entry, limit, gfp);
xa_unlock(xa); xa_unlock(xa);
return err; return err;
@ -782,26 +804,26 @@ static inline int xa_alloc(struct xarray *xa, u32 *id, u32 max, void *entry,
* xa_alloc_bh() - Find somewhere to store this entry in the XArray. * xa_alloc_bh() - Find somewhere to store this entry in the XArray.
* @xa: XArray. * @xa: XArray.
* @id: Pointer to ID. * @id: Pointer to ID.
* @max: Maximum ID to allocate (inclusive).
* @entry: New entry. * @entry: New entry.
* @limit: Range of ID to allocate.
* @gfp: Memory allocation flags. * @gfp: Memory allocation flags.
* *
* Allocates an unused ID in the range specified by @id and @max. * Finds an empty entry in @xa between @limit.min and @limit.max,
* Updates the @id pointer with the index, then stores the entry at that * stores the index into the @id pointer, then stores the entry at
* index. A concurrent lookup will not see an uninitialised @id. * that index. A concurrent lookup will not see an uninitialised @id.
* *
* Context: Any context. Takes and releases the xa_lock while * Context: Any context. Takes and releases the xa_lock while
* disabling softirqs. May sleep if the @gfp flags permit. * disabling softirqs. May sleep if the @gfp flags permit.
* Return: 0 on success, -ENOMEM if memory allocation fails or -ENOSPC if * Return: 0 on success, -ENOMEM if memory could not be allocated or
* there is no more space in the XArray. * -EBUSY if there are no free entries in @limit.
*/ */
static inline int xa_alloc_bh(struct xarray *xa, u32 *id, u32 max, void *entry, static inline int __must_check xa_alloc_bh(struct xarray *xa, u32 *id,
gfp_t gfp) void *entry, struct xa_limit limit, gfp_t gfp)
{ {
int err; int err;
xa_lock_bh(xa); xa_lock_bh(xa);
err = __xa_alloc(xa, id, max, entry, gfp); err = __xa_alloc(xa, id, entry, limit, gfp);
xa_unlock_bh(xa); xa_unlock_bh(xa);
return err; return err;
@ -811,26 +833,26 @@ static inline int xa_alloc_bh(struct xarray *xa, u32 *id, u32 max, void *entry,
* xa_alloc_irq() - Find somewhere to store this entry in the XArray. * xa_alloc_irq() - Find somewhere to store this entry in the XArray.
* @xa: XArray. * @xa: XArray.
* @id: Pointer to ID. * @id: Pointer to ID.
* @max: Maximum ID to allocate (inclusive).
* @entry: New entry. * @entry: New entry.
* @limit: Range of ID to allocate.
* @gfp: Memory allocation flags. * @gfp: Memory allocation flags.
* *
* Allocates an unused ID in the range specified by @id and @max. * Finds an empty entry in @xa between @limit.min and @limit.max,
* Updates the @id pointer with the index, then stores the entry at that * stores the index into the @id pointer, then stores the entry at
* index. A concurrent lookup will not see an uninitialised @id. * that index. A concurrent lookup will not see an uninitialised @id.
* *
* Context: Process context. Takes and releases the xa_lock while * Context: Process context. Takes and releases the xa_lock while
* disabling interrupts. May sleep if the @gfp flags permit. * disabling interrupts. May sleep if the @gfp flags permit.
* Return: 0 on success, -ENOMEM if memory allocation fails or -ENOSPC if * Return: 0 on success, -ENOMEM if memory could not be allocated or
* there is no more space in the XArray. * -EBUSY if there are no free entries in @limit.
*/ */
static inline int xa_alloc_irq(struct xarray *xa, u32 *id, u32 max, void *entry, static inline int __must_check xa_alloc_irq(struct xarray *xa, u32 *id,
gfp_t gfp) void *entry, struct xa_limit limit, gfp_t gfp)
{ {
int err; int err;
xa_lock_irq(xa); xa_lock_irq(xa);
err = __xa_alloc(xa, id, max, entry, gfp); err = __xa_alloc(xa, id, entry, limit, gfp);
xa_unlock_irq(xa); xa_unlock_irq(xa);
return err; return err;

View File

@ -40,9 +40,9 @@ static void *xa_store_index(struct xarray *xa, unsigned long index, gfp_t gfp)
static void xa_alloc_index(struct xarray *xa, unsigned long index, gfp_t gfp) static void xa_alloc_index(struct xarray *xa, unsigned long index, gfp_t gfp)
{ {
u32 id = 0; u32 id;
XA_BUG_ON(xa, xa_alloc(xa, &id, UINT_MAX, xa_mk_index(index), XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(index), xa_limit_32b,
gfp) != 0); gfp) != 0);
XA_BUG_ON(xa, id != index); XA_BUG_ON(xa, id != index);
} }
@ -640,28 +640,81 @@ static noinline void check_xa_alloc_1(struct xarray *xa, unsigned int base)
xa_destroy(xa); xa_destroy(xa);
/* Check that we fail properly at the limit of allocation */ /* Check that we fail properly at the limit of allocation */
id = 0xfffffffeU; XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(UINT_MAX - 1),
XA_BUG_ON(xa, xa_alloc(xa, &id, UINT_MAX, xa_mk_index(id), XA_LIMIT(UINT_MAX - 1, UINT_MAX),
GFP_KERNEL) != 0); GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != 0xfffffffeU); XA_BUG_ON(xa, id != 0xfffffffeU);
XA_BUG_ON(xa, xa_alloc(xa, &id, UINT_MAX, xa_mk_index(id), XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(UINT_MAX),
XA_LIMIT(UINT_MAX - 1, UINT_MAX),
GFP_KERNEL) != 0); GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != 0xffffffffU); XA_BUG_ON(xa, id != 0xffffffffU);
XA_BUG_ON(xa, xa_alloc(xa, &id, UINT_MAX, xa_mk_index(id), id = 3;
GFP_KERNEL) != -ENOSPC); XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(0),
XA_BUG_ON(xa, id != 0xffffffffU); XA_LIMIT(UINT_MAX - 1, UINT_MAX),
GFP_KERNEL) != -EBUSY);
XA_BUG_ON(xa, id != 3);
xa_destroy(xa); xa_destroy(xa);
id = 10; XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(10), XA_LIMIT(10, 5),
XA_BUG_ON(xa, xa_alloc(xa, &id, 5, xa_mk_index(id), GFP_KERNEL) != -EBUSY);
GFP_KERNEL) != -ENOSPC);
XA_BUG_ON(xa, xa_store_index(xa, 3, GFP_KERNEL) != 0); XA_BUG_ON(xa, xa_store_index(xa, 3, GFP_KERNEL) != 0);
XA_BUG_ON(xa, xa_alloc(xa, &id, 5, xa_mk_index(id), XA_BUG_ON(xa, xa_alloc(xa, &id, xa_mk_index(10), XA_LIMIT(10, 5),
GFP_KERNEL) != -ENOSPC); GFP_KERNEL) != -EBUSY);
xa_erase_index(xa, 3); xa_erase_index(xa, 3);
XA_BUG_ON(xa, !xa_empty(xa)); XA_BUG_ON(xa, !xa_empty(xa));
} }
static noinline void check_xa_alloc_2(struct xarray *xa, unsigned int base)
{
unsigned int i, id;
unsigned long index;
void *entry;
/* Allocate and free a NULL and check xa_empty() behaves */
XA_BUG_ON(xa, !xa_empty(xa));
XA_BUG_ON(xa, xa_alloc(xa, &id, NULL, xa_limit_32b, GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != base);
XA_BUG_ON(xa, xa_empty(xa));
XA_BUG_ON(xa, xa_erase(xa, id) != NULL);
XA_BUG_ON(xa, !xa_empty(xa));
/* Ditto, but check destroy instead of erase */
XA_BUG_ON(xa, !xa_empty(xa));
XA_BUG_ON(xa, xa_alloc(xa, &id, NULL, xa_limit_32b, GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != base);
XA_BUG_ON(xa, xa_empty(xa));
xa_destroy(xa);
XA_BUG_ON(xa, !xa_empty(xa));
for (i = base; i < base + 10; i++) {
XA_BUG_ON(xa, xa_alloc(xa, &id, NULL, xa_limit_32b,
GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != i);
}
XA_BUG_ON(xa, xa_store(xa, 3, xa_mk_index(3), GFP_KERNEL) != NULL);
XA_BUG_ON(xa, xa_store(xa, 4, xa_mk_index(4), GFP_KERNEL) != NULL);
XA_BUG_ON(xa, xa_store(xa, 4, NULL, GFP_KERNEL) != xa_mk_index(4));
XA_BUG_ON(xa, xa_erase(xa, 5) != NULL);
XA_BUG_ON(xa, xa_alloc(xa, &id, NULL, xa_limit_32b, GFP_KERNEL) != 0);
XA_BUG_ON(xa, id != 5);
xa_for_each(xa, index, entry) {
xa_erase_index(xa, index);
}
for (i = base; i < base + 9; i++) {
XA_BUG_ON(xa, xa_erase(xa, i) != NULL);
XA_BUG_ON(xa, xa_empty(xa));
}
XA_BUG_ON(xa, xa_erase(xa, 8) != NULL);
XA_BUG_ON(xa, xa_empty(xa));
XA_BUG_ON(xa, xa_erase(xa, base + 9) != NULL);
XA_BUG_ON(xa, !xa_empty(xa));
xa_destroy(xa);
}
static DEFINE_XARRAY_ALLOC(xa0); static DEFINE_XARRAY_ALLOC(xa0);
static DEFINE_XARRAY_ALLOC1(xa1); static DEFINE_XARRAY_ALLOC1(xa1);
@ -669,6 +722,8 @@ static noinline void check_xa_alloc(void)
{ {
check_xa_alloc_1(&xa0, 0); check_xa_alloc_1(&xa0, 0);
check_xa_alloc_1(&xa1, 1); check_xa_alloc_1(&xa1, 1);
check_xa_alloc_2(&xa0, 0);
check_xa_alloc_2(&xa1, 1);
} }
static noinline void __check_store_iter(struct xarray *xa, unsigned long start, static noinline void __check_store_iter(struct xarray *xa, unsigned long start,
@ -1219,9 +1274,8 @@ static void check_align_1(struct xarray *xa, char *name)
void *entry; void *entry;
for (i = 0; i < 8; i++) { for (i = 0; i < 8; i++) {
id = 0; XA_BUG_ON(xa, xa_alloc(xa, &id, name + i, xa_limit_32b,
XA_BUG_ON(xa, xa_alloc(xa, &id, UINT_MAX, name + i, GFP_KERNEL) GFP_KERNEL) != 0);
!= 0);
XA_BUG_ON(xa, id != i); XA_BUG_ON(xa, id != i);
} }
xa_for_each(xa, index, entry) xa_for_each(xa, index, entry)

View File

@ -1615,23 +1615,23 @@ EXPORT_SYMBOL(xa_store_range);
* __xa_alloc() - Find somewhere to store this entry in the XArray. * __xa_alloc() - Find somewhere to store this entry in the XArray.
* @xa: XArray. * @xa: XArray.
* @id: Pointer to ID. * @id: Pointer to ID.
* @max: Maximum ID to allocate (inclusive). * @limit: Range for allocated ID.
* @entry: New entry. * @entry: New entry.
* @gfp: Memory allocation flags. * @gfp: Memory allocation flags.
* *
* Allocates an unused ID in the range specified by @id and @max. * Finds an empty entry in @xa between @limit.min and @limit.max,
* Updates the @id pointer with the index, then stores the entry at that * stores the index into the @id pointer, then stores the entry at
* index. A concurrent lookup will not see an uninitialised @id. * that index. A concurrent lookup will not see an uninitialised @id.
* *
* Context: Any context. Expects xa_lock to be held on entry. May * Context: Any context. Expects xa_lock to be held on entry. May
* release and reacquire xa_lock if @gfp flags permit. * release and reacquire xa_lock if @gfp flags permit.
* Return: 0 on success, -ENOMEM if memory allocation fails or -ENOSPC if * Return: 0 on success, -ENOMEM if memory could not be allocated or
* there is no more space in the XArray. * -EBUSY if there are no free entries in @limit.
*/ */
int __xa_alloc(struct xarray *xa, u32 *id, u32 max, void *entry, gfp_t gfp) int __xa_alloc(struct xarray *xa, u32 *id, void *entry,
struct xa_limit limit, gfp_t gfp)
{ {
XA_STATE(xas, xa, 0); XA_STATE(xas, xa, 0);
int err;
if (WARN_ON_ONCE(xa_is_advanced(entry))) if (WARN_ON_ONCE(xa_is_advanced(entry)))
return -EINVAL; return -EINVAL;
@ -1642,18 +1642,17 @@ int __xa_alloc(struct xarray *xa, u32 *id, u32 max, void *entry, gfp_t gfp)
entry = XA_ZERO_ENTRY; entry = XA_ZERO_ENTRY;
do { do {
xas.xa_index = *id; xas.xa_index = limit.min;
xas_find_marked(&xas, max, XA_FREE_MARK); xas_find_marked(&xas, limit.max, XA_FREE_MARK);
if (xas.xa_node == XAS_RESTART) if (xas.xa_node == XAS_RESTART)
xas_set_err(&xas, -ENOSPC); xas_set_err(&xas, -EBUSY);
else
*id = xas.xa_index;
xas_store(&xas, entry); xas_store(&xas, entry);
xas_clear_mark(&xas, XA_FREE_MARK); xas_clear_mark(&xas, XA_FREE_MARK);
} while (__xas_nomem(&xas, gfp)); } while (__xas_nomem(&xas, gfp));
err = xas_error(&xas); return xas_error(&xas);
if (!err)
*id = xas.xa_index;
return err;
} }
EXPORT_SYMBOL(__xa_alloc); EXPORT_SYMBOL(__xa_alloc);