xref: /linux/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c (revision 367e6e4a8173d47b4c57181cdd9dcbfc291755f0)
186426a28SEmil Tsalapatis // SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
286426a28SEmil Tsalapatis /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
386426a28SEmil Tsalapatis 
486426a28SEmil Tsalapatis #include <libarena/common.h>
586426a28SEmil Tsalapatis #include <libarena/asan.h>
686426a28SEmil Tsalapatis #include <libarena/buddy.h>
786426a28SEmil Tsalapatis 
886426a28SEmil Tsalapatis /*
986426a28SEmil Tsalapatis  * Buddy allocator arena-based implementation.
1086426a28SEmil Tsalapatis  *
1186426a28SEmil Tsalapatis  * Memory is organized into chunks. These chunks
1286426a28SEmil Tsalapatis  * cannot be coalesced or split. Allocating
1386426a28SEmil Tsalapatis  * chunks allocates their memory eagerly.
1486426a28SEmil Tsalapatis  *
1586426a28SEmil Tsalapatis  * Internally, each chunk is organized into blocks.
1686426a28SEmil Tsalapatis  * Blocks _can_ be coalesced/split, but only inside
1786426a28SEmil Tsalapatis  * the chunk. Each block can be allocated or
1886426a28SEmil Tsalapatis  * unallocated. If allocated, the entire block holds
1986426a28SEmil Tsalapatis  * user data. If unallocated, the block is mostly
2086426a28SEmil Tsalapatis  * invalid memory, with the exception of a header
2186426a28SEmil Tsalapatis  * used for freelist tracking.
2286426a28SEmil Tsalapatis  *
2386426a28SEmil Tsalapatis  * The header is placed at an offset inside the block
2486426a28SEmil Tsalapatis  * to prevent off-by-one errors from the previous block
2586426a28SEmil Tsalapatis  * from trivially overwriting the header. Such an error
2686426a28SEmil Tsalapatis  * is also not catchable by ASAN, since the header remains
2786426a28SEmil Tsalapatis  * valid memory even after the block is freed. It is still
2886426a28SEmil Tsalapatis  * theoretically possible for the header to be corrupted
2986426a28SEmil Tsalapatis  * without being caught by ASAN, but harder.
3086426a28SEmil Tsalapatis  *
3186426a28SEmil Tsalapatis  * Since the allocator needs to track order information for
3286426a28SEmil Tsalapatis  * both allocated and free blocks, and allocated blocks cannot
3386426a28SEmil Tsalapatis  * store a header, the allocator also stores per-chunk order
3486426a28SEmil Tsalapatis  * information in a reserved region at the beginning of the
3586426a28SEmil Tsalapatis  * chunk. The header includes a bitmap with the order of blocks
3686426a28SEmil Tsalapatis  * and their allocation state. It also includes the freelist
3786426a28SEmil Tsalapatis  * heads for the allocation itself.
3886426a28SEmil Tsalapatis  */
3986426a28SEmil Tsalapatis 
4086426a28SEmil Tsalapatis 
4186426a28SEmil Tsalapatis enum {
4286426a28SEmil Tsalapatis 	BUDDY_POISONED = (s8)0xef,
4386426a28SEmil Tsalapatis 
4486426a28SEmil Tsalapatis 	/* Number of pages to be allocated per chunk. */
4586426a28SEmil Tsalapatis 	BUDDY_CHUNK_PAGES	= BUDDY_CHUNK_BYTES / __PAGE_SIZE
4686426a28SEmil Tsalapatis };
4786426a28SEmil Tsalapatis 
48b9b23fe1SEmil Tsalapatis static inline int buddy_lock(struct buddy __arena *buddy)
4986426a28SEmil Tsalapatis {
5086426a28SEmil Tsalapatis 	return arena_spin_lock(&buddy->lock);
5186426a28SEmil Tsalapatis }
5286426a28SEmil Tsalapatis 
53b9b23fe1SEmil Tsalapatis static inline void buddy_unlock(struct buddy __arena *buddy)
5486426a28SEmil Tsalapatis {
5586426a28SEmil Tsalapatis 	arena_spin_unlock(&buddy->lock);
5686426a28SEmil Tsalapatis }
5786426a28SEmil Tsalapatis 
5886426a28SEmil Tsalapatis /*
5986426a28SEmil Tsalapatis  * Reserve part of the arena address space for the allocator. We use
6086426a28SEmil Tsalapatis  * this to get aligned addresses for the chunks, since the arena
6186426a28SEmil Tsalapatis  * page alloc kfuncs do not support aligning to a boundary (in this
6286426a28SEmil Tsalapatis  * case 1 MiB, see buddy.h on how this is derived).
6386426a28SEmil Tsalapatis  */
64b9b23fe1SEmil Tsalapatis static int buddy_reserve_arena_vaddr(struct buddy __arena *buddy)
6586426a28SEmil Tsalapatis {
6686426a28SEmil Tsalapatis 	buddy->vaddr = 0;
6786426a28SEmil Tsalapatis 
6886426a28SEmil Tsalapatis 	return bpf_arena_reserve_pages(&arena,
6986426a28SEmil Tsalapatis 				       (void __arena *)BUDDY_VADDR_OFFSET,
7086426a28SEmil Tsalapatis 				       BUDDY_VADDR_SIZE / __PAGE_SIZE);
7186426a28SEmil Tsalapatis }
7286426a28SEmil Tsalapatis 
7386426a28SEmil Tsalapatis /*
7486426a28SEmil Tsalapatis  * Free up any unused address space. Used only during teardown.
7586426a28SEmil Tsalapatis  */
76b9b23fe1SEmil Tsalapatis static void buddy_unreserve_arena_vaddr(struct buddy __arena *buddy)
7786426a28SEmil Tsalapatis {
7886426a28SEmil Tsalapatis 	bpf_arena_free_pages(
7986426a28SEmil Tsalapatis 		&arena, (void __arena *)(BUDDY_VADDR_OFFSET + buddy->vaddr),
8086426a28SEmil Tsalapatis 		(BUDDY_VADDR_SIZE - buddy->vaddr) / __PAGE_SIZE);
8186426a28SEmil Tsalapatis 
8286426a28SEmil Tsalapatis 	buddy->vaddr = 0;
8386426a28SEmil Tsalapatis }
8486426a28SEmil Tsalapatis 
8586426a28SEmil Tsalapatis /*
8686426a28SEmil Tsalapatis  * Carve out part of the reserved address space and hand it over
8786426a28SEmil Tsalapatis  * to the buddy allocator.
8886426a28SEmil Tsalapatis  *
8986426a28SEmil Tsalapatis  * We are assuming the buddy allocator is the only allocator in the
9086426a28SEmil Tsalapatis  * system, so there is no race between this function reserving a
9186426a28SEmil Tsalapatis  * page range and some other allocator actually making the BPF call
9286426a28SEmil Tsalapatis  * to really create and reserve it.
9386426a28SEmil Tsalapatis  *
9486426a28SEmil Tsalapatis  * However, bump allocation must still be atomic because this function
9586426a28SEmil Tsalapatis  * is called without the buddy lock from multiple threads concurrently.
9686426a28SEmil Tsalapatis  */
97b9b23fe1SEmil Tsalapatis __weak int buddy_alloc_arena_vaddr(struct buddy __arena *buddy, u64 *vaddrp)
9886426a28SEmil Tsalapatis {
9986426a28SEmil Tsalapatis 	u64 vaddr, old, new;
10086426a28SEmil Tsalapatis 
10186426a28SEmil Tsalapatis 	if (!buddy || !vaddrp)
10286426a28SEmil Tsalapatis 		return -EINVAL;
10386426a28SEmil Tsalapatis 
10486426a28SEmil Tsalapatis 	do {
10586426a28SEmil Tsalapatis 		vaddr = buddy->vaddr;
10686426a28SEmil Tsalapatis 		new = vaddr + BUDDY_CHUNK_BYTES;
10786426a28SEmil Tsalapatis 
10886426a28SEmil Tsalapatis 		if (new > BUDDY_VADDR_SIZE)
10986426a28SEmil Tsalapatis 			return -EINVAL;
11086426a28SEmil Tsalapatis 
11186426a28SEmil Tsalapatis 		old = __sync_val_compare_and_swap(&buddy->vaddr, vaddr, new);
11286426a28SEmil Tsalapatis 	} while (old != vaddr && can_loop);
11386426a28SEmil Tsalapatis 
11486426a28SEmil Tsalapatis 	if (old != vaddr)
11586426a28SEmil Tsalapatis 		return -EINVAL;
11686426a28SEmil Tsalapatis 
11786426a28SEmil Tsalapatis 	*vaddrp = BUDDY_VADDR_OFFSET + vaddr;
11886426a28SEmil Tsalapatis 
11986426a28SEmil Tsalapatis 	return 0;
12086426a28SEmil Tsalapatis }
12186426a28SEmil Tsalapatis 
12286426a28SEmil Tsalapatis static u64 arena_next_pow2(__u64 n)
12386426a28SEmil Tsalapatis {
12486426a28SEmil Tsalapatis 	n--;
12586426a28SEmil Tsalapatis 	n |= n >> 1;
12686426a28SEmil Tsalapatis 	n |= n >> 2;
12786426a28SEmil Tsalapatis 	n |= n >> 4;
12886426a28SEmil Tsalapatis 	n |= n >> 8;
12986426a28SEmil Tsalapatis 	n |= n >> 16;
13086426a28SEmil Tsalapatis 	n |= n >> 32;
13186426a28SEmil Tsalapatis 	n++;
13286426a28SEmil Tsalapatis 
13386426a28SEmil Tsalapatis 	return n;
13486426a28SEmil Tsalapatis }
13586426a28SEmil Tsalapatis 
13686426a28SEmil Tsalapatis __weak
137b9b23fe1SEmil Tsalapatis int idx_set_allocated(struct buddy_chunk __arena *chunk, u64 idx, bool allocated)
13886426a28SEmil Tsalapatis {
13986426a28SEmil Tsalapatis 	bool already_allocated;
14086426a28SEmil Tsalapatis 
14186426a28SEmil Tsalapatis 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
14286426a28SEmil Tsalapatis 		arena_stderr("setting state of invalid idx (%ld, max %d)\n", idx,
14386426a28SEmil Tsalapatis 			     BUDDY_CHUNK_ITEMS);
14486426a28SEmil Tsalapatis 		return -EINVAL;
14586426a28SEmil Tsalapatis 	}
14686426a28SEmil Tsalapatis 
14786426a28SEmil Tsalapatis 	already_allocated = chunk->allocated[idx / 8] & (1 << (idx % 8));
14886426a28SEmil Tsalapatis 	if (unlikely(already_allocated == allocated)) {
14986426a28SEmil Tsalapatis 		arena_stderr("Double %s of idx %ld for chunk %p",
15086426a28SEmil Tsalapatis 				allocated ? "alloc" : "free",
15186426a28SEmil Tsalapatis 				idx, chunk);
15286426a28SEmil Tsalapatis 		return -EINVAL;
15386426a28SEmil Tsalapatis 	}
15486426a28SEmil Tsalapatis 
15586426a28SEmil Tsalapatis 	if (allocated)
15686426a28SEmil Tsalapatis 		chunk->allocated[idx / 8] |= 1 << (idx % 8);
15786426a28SEmil Tsalapatis 	else
15886426a28SEmil Tsalapatis 		chunk->allocated[idx / 8] &= ~(1 << (idx % 8));
15986426a28SEmil Tsalapatis 
16086426a28SEmil Tsalapatis 	return 0;
16186426a28SEmil Tsalapatis }
16286426a28SEmil Tsalapatis 
163b9b23fe1SEmil Tsalapatis static int idx_is_allocated(struct buddy_chunk __arena *chunk, u64 idx, bool *allocated)
16486426a28SEmil Tsalapatis {
16586426a28SEmil Tsalapatis 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
16686426a28SEmil Tsalapatis 		arena_stderr("getting state of invalid idx (%llu, max %d)\n", idx,
16786426a28SEmil Tsalapatis 			     BUDDY_CHUNK_ITEMS);
16886426a28SEmil Tsalapatis 		return -EINVAL;
16986426a28SEmil Tsalapatis 	}
17086426a28SEmil Tsalapatis 
17186426a28SEmil Tsalapatis 	*allocated = chunk->allocated[idx / 8] & (1 << (idx % 8));
17286426a28SEmil Tsalapatis 	return 0;
17386426a28SEmil Tsalapatis }
17486426a28SEmil Tsalapatis 
17586426a28SEmil Tsalapatis __weak
176b9b23fe1SEmil Tsalapatis int idx_set_order(struct buddy_chunk __arena *chunk, u64 idx, u8 order)
17786426a28SEmil Tsalapatis {
17886426a28SEmil Tsalapatis 	u8 prev_order;
17986426a28SEmil Tsalapatis 
18086426a28SEmil Tsalapatis 	if (unlikely(order >= BUDDY_CHUNK_NUM_ORDERS)) {
18186426a28SEmil Tsalapatis 		arena_stderr("setting invalid order %u\n", order);
18286426a28SEmil Tsalapatis 		return -EINVAL;
18386426a28SEmil Tsalapatis 	}
18486426a28SEmil Tsalapatis 
18586426a28SEmil Tsalapatis 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
18686426a28SEmil Tsalapatis 		arena_stderr("setting order of invalid idx (%d, max %d)\n", idx,
18786426a28SEmil Tsalapatis 			     BUDDY_CHUNK_ITEMS);
18886426a28SEmil Tsalapatis 		return -EINVAL;
18986426a28SEmil Tsalapatis 	}
19086426a28SEmil Tsalapatis 
19186426a28SEmil Tsalapatis 	/*
19286426a28SEmil Tsalapatis 	 * We store two order instances per byte, one per nibble.
19386426a28SEmil Tsalapatis 	 * Retain the existing nibble.
19486426a28SEmil Tsalapatis 	 */
19586426a28SEmil Tsalapatis 	prev_order = chunk->orders[idx / 2];
19686426a28SEmil Tsalapatis 	if (idx & 0x1) {
19786426a28SEmil Tsalapatis 		order &= 0xf;
19886426a28SEmil Tsalapatis 		order |= (prev_order & 0xf0);
19986426a28SEmil Tsalapatis 	} else {
20086426a28SEmil Tsalapatis 		order <<= 4;
20186426a28SEmil Tsalapatis 		order |= (prev_order & 0xf);
20286426a28SEmil Tsalapatis 	}
20386426a28SEmil Tsalapatis 
20486426a28SEmil Tsalapatis 	chunk->orders[idx / 2] = order;
20586426a28SEmil Tsalapatis 
20686426a28SEmil Tsalapatis 	return 0;
20786426a28SEmil Tsalapatis }
20886426a28SEmil Tsalapatis 
209b9b23fe1SEmil Tsalapatis static u8 idx_get_order(struct buddy_chunk __arena *chunk, u64 idx)
21086426a28SEmil Tsalapatis {
21186426a28SEmil Tsalapatis 	u8 result;
21286426a28SEmil Tsalapatis 
21386426a28SEmil Tsalapatis 	_Static_assert(BUDDY_CHUNK_NUM_ORDERS <= 16,
21486426a28SEmil Tsalapatis 		       "order must fit in 4 bits");
21586426a28SEmil Tsalapatis 
21686426a28SEmil Tsalapatis 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
21786426a28SEmil Tsalapatis 		arena_stderr("getting order of invalid idx %u\n", idx);
21886426a28SEmil Tsalapatis 		return BUDDY_CHUNK_NUM_ORDERS;
21986426a28SEmil Tsalapatis 	}
22086426a28SEmil Tsalapatis 
22186426a28SEmil Tsalapatis 	result = chunk->orders[idx / 2];
22286426a28SEmil Tsalapatis 
22386426a28SEmil Tsalapatis 	return (idx & 0x1) ? (result & 0xf) : (result >> 4);
22486426a28SEmil Tsalapatis }
22586426a28SEmil Tsalapatis 
226b9b23fe1SEmil Tsalapatis static void __arena *idx_to_addr(struct buddy_chunk __arena *chunk, size_t idx)
22786426a28SEmil Tsalapatis {
22886426a28SEmil Tsalapatis 	u64 address;
22986426a28SEmil Tsalapatis 
23086426a28SEmil Tsalapatis 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
23186426a28SEmil Tsalapatis 		arena_stderr("translating invalid idx %u\n", idx);
23286426a28SEmil Tsalapatis 		return NULL;
23386426a28SEmil Tsalapatis 	}
23486426a28SEmil Tsalapatis 
23586426a28SEmil Tsalapatis 	/*
23686426a28SEmil Tsalapatis 	 * The data blocks start in the chunk after the metadata block.
23786426a28SEmil Tsalapatis 	 * We find the actual address by indexing into the region at an
23886426a28SEmil Tsalapatis 	 * BUDDY_MIN_ALLOC_BYTES granularity, the minimum allowed.
23986426a28SEmil Tsalapatis 	 * The index number already accounts for the fact that the first
24086426a28SEmil Tsalapatis 	 * blocks in the chunk are occupied by the metadata, so we do
24186426a28SEmil Tsalapatis 	 * not need to offset it.
24286426a28SEmil Tsalapatis 	 */
24386426a28SEmil Tsalapatis 
24486426a28SEmil Tsalapatis 	address = (u64)chunk + (idx * BUDDY_MIN_ALLOC_BYTES);
24586426a28SEmil Tsalapatis 
24686426a28SEmil Tsalapatis 	return (void __arena *)address;
24786426a28SEmil Tsalapatis }
24886426a28SEmil Tsalapatis 
249b9b23fe1SEmil Tsalapatis static struct buddy_header __arena *idx_to_header(struct buddy_chunk __arena *chunk, size_t idx)
25086426a28SEmil Tsalapatis {
25186426a28SEmil Tsalapatis 	bool allocated;
25286426a28SEmil Tsalapatis 	u64 address;
25386426a28SEmil Tsalapatis 
25486426a28SEmil Tsalapatis 	if (unlikely(idx_is_allocated(chunk, idx, &allocated))) {
25586426a28SEmil Tsalapatis 		arena_stderr("accessing invalid idx 0x%lx\n", idx);
25686426a28SEmil Tsalapatis 		return NULL;
25786426a28SEmil Tsalapatis 	}
25886426a28SEmil Tsalapatis 
25986426a28SEmil Tsalapatis 	if (unlikely(allocated)) {
26086426a28SEmil Tsalapatis 		arena_stderr("accessing allocated idx 0x%lx as header\n", idx);
26186426a28SEmil Tsalapatis 		return NULL;
26286426a28SEmil Tsalapatis 	}
26386426a28SEmil Tsalapatis 
26486426a28SEmil Tsalapatis 	address = (u64)idx_to_addr(chunk, idx);
26586426a28SEmil Tsalapatis 	if (!address)
26686426a28SEmil Tsalapatis 		return NULL;
26786426a28SEmil Tsalapatis 
26886426a28SEmil Tsalapatis 	/*
26986426a28SEmil Tsalapatis 	 * Offset the header within the block. This avoids accidental overwrites
27086426a28SEmil Tsalapatis 	 * to the header because of off-by-one errors when using adjacent blocks.
27186426a28SEmil Tsalapatis 	 *
27286426a28SEmil Tsalapatis 	 * The offset has been chosen as a compromise between ASAN effectiveness
27386426a28SEmil Tsalapatis 	 * and allocator granularity:
27486426a28SEmil Tsalapatis 	 * 1) ASAN dictates valid data runs are 8-byte aligned.
27586426a28SEmil Tsalapatis 	 * 2) We want to keep a low minimum allocation size (currently 16).
27686426a28SEmil Tsalapatis 	 *
27786426a28SEmil Tsalapatis 	 * As a result, we have only two possible positions for the header: Bytes
27886426a28SEmil Tsalapatis 	 * 0 and 8. Keeping the header in byte 0 means off-by-ones from the previous
27986426a28SEmil Tsalapatis 	 * block touch the header, and, since the header must be accessible, ASAN
28086426a28SEmil Tsalapatis 	 * will not trigger. Keeping the header on byte 8 means off-by-one errors from
28186426a28SEmil Tsalapatis 	 * the previous block are caught by ASAN. Negative offsets are rarer, so
28286426a28SEmil Tsalapatis 	 * while accesses into the block from the next block are possible, they are
28386426a28SEmil Tsalapatis 	 * less probable.
28486426a28SEmil Tsalapatis 	 */
28586426a28SEmil Tsalapatis 
286b9b23fe1SEmil Tsalapatis 	return (struct buddy_header __arena *)(address + BUDDY_HEADER_OFF);
28786426a28SEmil Tsalapatis }
28886426a28SEmil Tsalapatis 
289b9b23fe1SEmil Tsalapatis static void header_add_freelist(struct buddy_chunk __arena *chunk, struct buddy_header __arena *header,
29086426a28SEmil Tsalapatis 		u64 idx, u8 order)
29186426a28SEmil Tsalapatis {
292b9b23fe1SEmil Tsalapatis 	struct buddy_header __arena *tmp_header;
29386426a28SEmil Tsalapatis 
29486426a28SEmil Tsalapatis 	idx_set_order(chunk, idx, order);
29586426a28SEmil Tsalapatis 
29686426a28SEmil Tsalapatis 	header->next_index = chunk->freelists[order];
29786426a28SEmil Tsalapatis 	header->prev_index = BUDDY_CHUNK_ITEMS;
29886426a28SEmil Tsalapatis 
29986426a28SEmil Tsalapatis 	if (header->next_index != BUDDY_CHUNK_ITEMS) {
30086426a28SEmil Tsalapatis 		tmp_header = idx_to_header(chunk, header->next_index);
30186426a28SEmil Tsalapatis 		tmp_header->prev_index = idx;
30286426a28SEmil Tsalapatis 	}
30386426a28SEmil Tsalapatis 
30486426a28SEmil Tsalapatis 	chunk->freelists[order] = idx;
30586426a28SEmil Tsalapatis }
30686426a28SEmil Tsalapatis 
307b9b23fe1SEmil Tsalapatis static void header_remove_freelist(struct buddy_chunk __arena  *chunk,
308b9b23fe1SEmil Tsalapatis 				   struct buddy_header __arena *header, u8 order)
30986426a28SEmil Tsalapatis {
310b9b23fe1SEmil Tsalapatis 	struct buddy_header __arena *tmp_header;
31186426a28SEmil Tsalapatis 
31286426a28SEmil Tsalapatis 	if (header->prev_index != BUDDY_CHUNK_ITEMS) {
31386426a28SEmil Tsalapatis 		tmp_header = idx_to_header(chunk, header->prev_index);
31486426a28SEmil Tsalapatis 		tmp_header->next_index = header->next_index;
31586426a28SEmil Tsalapatis 	}
31686426a28SEmil Tsalapatis 
31786426a28SEmil Tsalapatis 	if (header->next_index != BUDDY_CHUNK_ITEMS) {
31886426a28SEmil Tsalapatis 		tmp_header = idx_to_header(chunk, header->next_index);
31986426a28SEmil Tsalapatis 		tmp_header->prev_index = header->prev_index;
32086426a28SEmil Tsalapatis 	}
32186426a28SEmil Tsalapatis 
32286426a28SEmil Tsalapatis 	/* Pop off the list head if necessary. */
32386426a28SEmil Tsalapatis 	if (idx_to_header(chunk, chunk->freelists[order]) == header)
32486426a28SEmil Tsalapatis 		chunk->freelists[order] = header->next_index;
32586426a28SEmil Tsalapatis 
32686426a28SEmil Tsalapatis 	header->prev_index = BUDDY_CHUNK_ITEMS;
32786426a28SEmil Tsalapatis 	header->next_index = BUDDY_CHUNK_ITEMS;
32886426a28SEmil Tsalapatis }
32986426a28SEmil Tsalapatis 
33086426a28SEmil Tsalapatis static u64 size_to_order(size_t size)
33186426a28SEmil Tsalapatis {
33286426a28SEmil Tsalapatis 	u64 order;
33386426a28SEmil Tsalapatis 
33486426a28SEmil Tsalapatis 	/*
33586426a28SEmil Tsalapatis 	 * Legal sizes are [1, 4GiB] (the biggest possible arena).
33686426a28SEmil Tsalapatis 	 * Of course, sizes close to GiB are practically impossible
33786426a28SEmil Tsalapatis 	 * to fulfill and allocation will fail, but that's taken care
33886426a28SEmil Tsalapatis 	 * of by the caller.
33986426a28SEmil Tsalapatis 	 */
34086426a28SEmil Tsalapatis 
34186426a28SEmil Tsalapatis 	if (unlikely(size == 0 || size > (1UL << 32))) {
34286426a28SEmil Tsalapatis 		arena_stderr("illegal size request %lu\n", size);
34386426a28SEmil Tsalapatis 		return 64;
34486426a28SEmil Tsalapatis 	}
34586426a28SEmil Tsalapatis 	/*
34686426a28SEmil Tsalapatis 	 * To find the order of the allocation we find the first power of two
34786426a28SEmil Tsalapatis 	 * >= the requested size, take the log2, then adjust it for the minimum
34886426a28SEmil Tsalapatis 	 * allocation size by removing the minimum shift from it. Requests
34986426a28SEmil Tsalapatis 	 * smaller than the minimum allocation size are rounded up.
35086426a28SEmil Tsalapatis 	 */
35186426a28SEmil Tsalapatis 	order = arena_fls(arena_next_pow2(size)) - 1;
35286426a28SEmil Tsalapatis 	if (order < BUDDY_MIN_ALLOC_SHIFT)
35386426a28SEmil Tsalapatis 		return 0;
35486426a28SEmil Tsalapatis 
35586426a28SEmil Tsalapatis 	return order - BUDDY_MIN_ALLOC_SHIFT;
35686426a28SEmil Tsalapatis }
35786426a28SEmil Tsalapatis 
35886426a28SEmil Tsalapatis __weak
359b9b23fe1SEmil Tsalapatis int add_leftovers_to_freelist(struct buddy_chunk __arena *chunk, u32 cur_idx,
36086426a28SEmil Tsalapatis 		u64 min_order, u64 max_order)
36186426a28SEmil Tsalapatis {
362b9b23fe1SEmil Tsalapatis 	struct buddy_header __arena *header;
36386426a28SEmil Tsalapatis 	u64 ord;
36486426a28SEmil Tsalapatis 	u32 idx;
36586426a28SEmil Tsalapatis 
36686426a28SEmil Tsalapatis 	for (ord = min_order; ord < max_order && can_loop; ord++) {
36786426a28SEmil Tsalapatis 		/* Mark the buddy as free and add it to the freelists. */
36886426a28SEmil Tsalapatis 		idx = cur_idx + (1 << ord);
36986426a28SEmil Tsalapatis 
37086426a28SEmil Tsalapatis 		header = idx_to_header(chunk, idx);
37186426a28SEmil Tsalapatis 		if (unlikely(!header)) {
37286426a28SEmil Tsalapatis 			arena_stderr("idx %u has no header", idx);
37386426a28SEmil Tsalapatis 			return -EINVAL;
37486426a28SEmil Tsalapatis 		}
37586426a28SEmil Tsalapatis 
37686426a28SEmil Tsalapatis 		asan_unpoison(header, sizeof(*header));
37786426a28SEmil Tsalapatis 
37886426a28SEmil Tsalapatis 		header_add_freelist(chunk, header, idx, ord);
37986426a28SEmil Tsalapatis 	}
38086426a28SEmil Tsalapatis 
38186426a28SEmil Tsalapatis 	return 0;
38286426a28SEmil Tsalapatis }
38386426a28SEmil Tsalapatis 
384b9b23fe1SEmil Tsalapatis static struct buddy_chunk __arena *buddy_chunk_get(struct buddy __arena *buddy)
38586426a28SEmil Tsalapatis {
38686426a28SEmil Tsalapatis 	u64 order, ord, min_order, max_order;
387b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena  *chunk;
38886426a28SEmil Tsalapatis 	size_t left;
38986426a28SEmil Tsalapatis 	int power2;
39086426a28SEmil Tsalapatis 	u64 vaddr;
39186426a28SEmil Tsalapatis 	u32 idx;
39286426a28SEmil Tsalapatis 	int ret;
39386426a28SEmil Tsalapatis 
39486426a28SEmil Tsalapatis 	/*
39586426a28SEmil Tsalapatis 	 * Step 1:  Allocate a properly aligned chunk, and
39686426a28SEmil Tsalapatis 	 * prep it for insertion into the buddy allocator.
39786426a28SEmil Tsalapatis 	 * We don't need the allocator lock until step 2.
39886426a28SEmil Tsalapatis 	 */
39986426a28SEmil Tsalapatis 
40086426a28SEmil Tsalapatis 	ret = buddy_alloc_arena_vaddr(buddy, &vaddr);
40186426a28SEmil Tsalapatis 	if (ret)
40286426a28SEmil Tsalapatis 		return NULL;
40386426a28SEmil Tsalapatis 
40486426a28SEmil Tsalapatis 	/* Addresses must be aligned to the chunk boundary. */
40586426a28SEmil Tsalapatis 	if (vaddr % BUDDY_CHUNK_BYTES)
40686426a28SEmil Tsalapatis 		return NULL;
40786426a28SEmil Tsalapatis 
40886426a28SEmil Tsalapatis 	/* Unreserve the address space. */
40986426a28SEmil Tsalapatis 	bpf_arena_free_pages(&arena, (void __arena *)vaddr,
41086426a28SEmil Tsalapatis 			     BUDDY_CHUNK_PAGES);
41186426a28SEmil Tsalapatis 
41286426a28SEmil Tsalapatis 	chunk = bpf_arena_alloc_pages(&arena, (void __arena *)vaddr,
41386426a28SEmil Tsalapatis 				      BUDDY_CHUNK_PAGES, NUMA_NO_NODE, 0);
41486426a28SEmil Tsalapatis 	if (!chunk) {
41586426a28SEmil Tsalapatis 		arena_stderr("[ALLOC FAILED]");
41686426a28SEmil Tsalapatis 		return NULL;
41786426a28SEmil Tsalapatis 	}
41886426a28SEmil Tsalapatis 
41986426a28SEmil Tsalapatis 	if (buddy_lock(buddy)) {
42086426a28SEmil Tsalapatis 		/*
42186426a28SEmil Tsalapatis 		 * We cannot reclaim the vaddr space, but that is ok - this
42286426a28SEmil Tsalapatis 		 * operation should always succeed. The error path is to catch
42386426a28SEmil Tsalapatis 		 * accidental deadlocks that will cause -ENOMEMs to the program as
42486426a28SEmil Tsalapatis 		 * the allocator fails to refill itself, in which case vaddr usage
42586426a28SEmil Tsalapatis 		 * is the least of our worries.
42686426a28SEmil Tsalapatis 		 */
42786426a28SEmil Tsalapatis 		bpf_arena_free_pages(&arena, (void __arena *)vaddr, BUDDY_CHUNK_PAGES);
42886426a28SEmil Tsalapatis 		return NULL;
42986426a28SEmil Tsalapatis 	}
43086426a28SEmil Tsalapatis 
43186426a28SEmil Tsalapatis 	asan_poison(chunk, BUDDY_POISONED, BUDDY_CHUNK_PAGES * __PAGE_SIZE);
43286426a28SEmil Tsalapatis 
43386426a28SEmil Tsalapatis 	/* Unpoison the chunk itself. */
43486426a28SEmil Tsalapatis 	asan_unpoison(chunk, sizeof(*chunk));
43586426a28SEmil Tsalapatis 
43686426a28SEmil Tsalapatis 	/* Mark all freelists as empty. */
43786426a28SEmil Tsalapatis 	for (ord = zero; ord < BUDDY_CHUNK_NUM_ORDERS && can_loop; ord++)
43886426a28SEmil Tsalapatis 		chunk->freelists[ord] = BUDDY_CHUNK_ITEMS;
43986426a28SEmil Tsalapatis 
44086426a28SEmil Tsalapatis 	/*
44186426a28SEmil Tsalapatis 	 * Initialize the chunk by carving out a page range to hold the metadata
44286426a28SEmil Tsalapatis 	 * struct above, then dumping the rest of the pages into the allocator.
44386426a28SEmil Tsalapatis 	 */
44486426a28SEmil Tsalapatis 
44586426a28SEmil Tsalapatis 	_Static_assert(BUDDY_CHUNK_PAGES * __PAGE_SIZE >=
44686426a28SEmil Tsalapatis 			       BUDDY_MIN_ALLOC_BYTES *
44786426a28SEmil Tsalapatis 				       BUDDY_CHUNK_ITEMS,
44886426a28SEmil Tsalapatis 		       "chunk must fit within the allocation");
44986426a28SEmil Tsalapatis 
45086426a28SEmil Tsalapatis 	/*
45186426a28SEmil Tsalapatis 	 * Step 2: Reserve a chunk for the chunk metadata, then breaks
45286426a28SEmil Tsalapatis 	 * the rest of the full allocation into the different buckets.
45386426a28SEmil Tsalapatis 	 * We allocating the memory by grabbing blocks of progressively
45486426a28SEmil Tsalapatis 	 * smaller sizes from the allocator, which are guaranteed to be
45586426a28SEmil Tsalapatis 	 * continuous.
45686426a28SEmil Tsalapatis 	 *
45786426a28SEmil Tsalapatis 	 * This operation also populates the allocator.
45886426a28SEmil Tsalapatis 	 *
45986426a28SEmil Tsalapatis 	 * Algorithm:
46086426a28SEmil Tsalapatis 	 *
46186426a28SEmil Tsalapatis 	 * - max_order: The last order allocation we made
46286426a28SEmil Tsalapatis 	 * - left: How many bytes are left to allocate
46386426a28SEmil Tsalapatis 	 * - cur_index: Current index into the top-level block we are
46486426a28SEmil Tsalapatis 	 * allocating from.
46586426a28SEmil Tsalapatis 	 *
46686426a28SEmil Tsalapatis 	 * Step 3:
46786426a28SEmil Tsalapatis 	 * - Find the largest power-of-2 allocation still smaller than left (infimum)
46886426a28SEmil Tsalapatis 	 * - Reserve a chunk of that size, along with its buddy
46986426a28SEmil Tsalapatis 	 * - For every order from [infimum + 1, last order), carve out a block
47086426a28SEmil Tsalapatis 	 *   and put it into the allocator.
47186426a28SEmil Tsalapatis 	 *
47286426a28SEmil Tsalapatis 	 *  Example: Chunk size 0b1010000 (80 bytes)
47386426a28SEmil Tsalapatis 	 *
47486426a28SEmil Tsalapatis 	 *  Step 1:
47586426a28SEmil Tsalapatis 	 *
47686426a28SEmil Tsalapatis 	 *   idx  infimum                             1 << max_order
47786426a28SEmil Tsalapatis 	 *   0        64        128                    1 << 20
47886426a28SEmil Tsalapatis 	 *   |________|_________|______________________|
47986426a28SEmil Tsalapatis 	 *
48086426a28SEmil Tsalapatis 	 *   Blocks set aside:
48186426a28SEmil Tsalapatis 	 *   	[0, 64)         - Completely allocated
48286426a28SEmil Tsalapatis 	 *   	[64, 128)       - Will be further split in the next iteration
48386426a28SEmil Tsalapatis 	 *
48486426a28SEmil Tsalapatis 	 *   Blocks added to the allocator:
48586426a28SEmil Tsalapatis 	 *   	[128, 256)
48686426a28SEmil Tsalapatis 	 *   	[256, 512)
48786426a28SEmil Tsalapatis 	 *   	...
48886426a28SEmil Tsalapatis 	 *   	[1 << 18, 1 << 19)
48986426a28SEmil Tsalapatis 	 *   	[1 << 19, 1 << 20)
49086426a28SEmil Tsalapatis 	 *
49186426a28SEmil Tsalapatis 	 *  Step 2:
49286426a28SEmil Tsalapatis 	 *
49386426a28SEmil Tsalapatis 	 *   idx  infimum			   idx + 1 << max_order
49486426a28SEmil Tsalapatis 	 *   64	      80	96		   	64 + 1 << 6 = 128
49586426a28SEmil Tsalapatis 	 *   |________|_________|______________________|
49686426a28SEmil Tsalapatis 	 *
49786426a28SEmil Tsalapatis 	 *   Blocks set aside:
49886426a28SEmil Tsalapatis 	 *   	[64, 80)	- Completely allocated
49986426a28SEmil Tsalapatis 	 *
50086426a28SEmil Tsalapatis 	 *   Blocks added to the allocator:
50186426a28SEmil Tsalapatis 	 *      [80, 96) - left == 0 so the buddy is unused and marked as freed
50286426a28SEmil Tsalapatis 	 *   	[96, 128)
50386426a28SEmil Tsalapatis 	 */
50486426a28SEmil Tsalapatis 	 max_order = BUDDY_CHUNK_NUM_ORDERS;
50586426a28SEmil Tsalapatis 	left = sizeof(*chunk);
50686426a28SEmil Tsalapatis 	idx = 0;
50786426a28SEmil Tsalapatis 	while (left && can_loop) {
50886426a28SEmil Tsalapatis 		power2 = arena_fls(left) - 1;
50986426a28SEmil Tsalapatis 		/*
51086426a28SEmil Tsalapatis 		 * Note: The condition below only triggers to catch serious bugs
51186426a28SEmil Tsalapatis 		 * early. There is no sane way to undo any block insertions from
51286426a28SEmil Tsalapatis 		 * the allocated chunk, so just leak any leftover allocations,
51386426a28SEmil Tsalapatis 		 * emit a diagnostic, unlock and exit.
51486426a28SEmil Tsalapatis 		 *
51586426a28SEmil Tsalapatis 		 */
51686426a28SEmil Tsalapatis 		if (unlikely(power2 >= BUDDY_CHUNK_NUM_ORDERS)) {
51786426a28SEmil Tsalapatis 			arena_stderr(
51886426a28SEmil Tsalapatis 				"buddy chunk metadata require allocation of order %d\n",
51986426a28SEmil Tsalapatis 				power2);
52086426a28SEmil Tsalapatis 			arena_stderr(
52186426a28SEmil Tsalapatis 				"chunk has size of 0x%lx bytes (left %lx bytes)\n",
52286426a28SEmil Tsalapatis 				sizeof(*chunk), left);
52386426a28SEmil Tsalapatis 			buddy_unlock(buddy);
52486426a28SEmil Tsalapatis 
52586426a28SEmil Tsalapatis 			return NULL;
52686426a28SEmil Tsalapatis 		}
52786426a28SEmil Tsalapatis 
52886426a28SEmil Tsalapatis 		/* Round up allocations that are too small. */
52986426a28SEmil Tsalapatis 
53086426a28SEmil Tsalapatis 		left -= (power2 >= BUDDY_MIN_ALLOC_SHIFT) ? 1 << power2 : left;
53186426a28SEmil Tsalapatis 		order = (power2 >= BUDDY_MIN_ALLOC_SHIFT) ? power2 - BUDDY_MIN_ALLOC_SHIFT : 0;
53286426a28SEmil Tsalapatis 
53386426a28SEmil Tsalapatis 		if (idx_set_allocated(chunk, idx, true)) {
53486426a28SEmil Tsalapatis 			buddy_unlock(buddy);
53586426a28SEmil Tsalapatis 			return NULL;
53686426a28SEmil Tsalapatis 		}
53786426a28SEmil Tsalapatis 
53886426a28SEmil Tsalapatis 		/*
53986426a28SEmil Tsalapatis 		 * Starting an order above the one we allocated, populate
54086426a28SEmil Tsalapatis 		 * the allocator with free blocks. If this is the last
54186426a28SEmil Tsalapatis 		 * allocation (left == 0), also mark the buddy as free.
54286426a28SEmil Tsalapatis 		 *
54386426a28SEmil Tsalapatis 		 * See comment above about error handling: The error path
54486426a28SEmil Tsalapatis 		 * is only there as a way to mitigate deeply buggy allocator
54586426a28SEmil Tsalapatis 		 * states by emitting a diagnostic in add_leftovers_to_freelist()
54686426a28SEmil Tsalapatis 		 * and leaking any memory not added in the freelists.
54786426a28SEmil Tsalapatis 		 */
54886426a28SEmil Tsalapatis 		min_order = left ? order + 1 : order;
54986426a28SEmil Tsalapatis 		if (add_leftovers_to_freelist(chunk, idx, min_order, max_order)) {
55086426a28SEmil Tsalapatis 			buddy_unlock(buddy);
55186426a28SEmil Tsalapatis 			return NULL;
55286426a28SEmil Tsalapatis 		}
55386426a28SEmil Tsalapatis 
55486426a28SEmil Tsalapatis 		/* Adjust the index. */
55586426a28SEmil Tsalapatis 		idx += 1 << order;
55686426a28SEmil Tsalapatis 		max_order = order;
55786426a28SEmil Tsalapatis 	}
55886426a28SEmil Tsalapatis 
55986426a28SEmil Tsalapatis 	buddy_unlock(buddy);
56086426a28SEmil Tsalapatis 
56186426a28SEmil Tsalapatis 	return chunk;
56286426a28SEmil Tsalapatis }
56386426a28SEmil Tsalapatis 
564b9b23fe1SEmil Tsalapatis __weak int buddy_init(struct buddy __arena *buddy)
56586426a28SEmil Tsalapatis {
566b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena *chunk;
56786426a28SEmil Tsalapatis 	int ret;
56886426a28SEmil Tsalapatis 
56986426a28SEmil Tsalapatis 	if (!asan_ready())
57086426a28SEmil Tsalapatis 		return -EINVAL;
57186426a28SEmil Tsalapatis 
57286426a28SEmil Tsalapatis 	/* Reserve enough address space to ensure allocations are aligned. */
57386426a28SEmil Tsalapatis 	ret = buddy_reserve_arena_vaddr(buddy);
57486426a28SEmil Tsalapatis 	if (ret)
57586426a28SEmil Tsalapatis 		return ret;
57686426a28SEmil Tsalapatis 
57786426a28SEmil Tsalapatis 	_Static_assert(BUDDY_CHUNK_PAGES > 0,
57886426a28SEmil Tsalapatis 		       "chunk must use one or more pages");
57986426a28SEmil Tsalapatis 
58086426a28SEmil Tsalapatis 	chunk = buddy_chunk_get(buddy);
58186426a28SEmil Tsalapatis 
58286426a28SEmil Tsalapatis 	if (buddy_lock(buddy)) {
58386426a28SEmil Tsalapatis 		bpf_arena_free_pages(&arena, chunk, BUDDY_CHUNK_PAGES);
58486426a28SEmil Tsalapatis 		return -EINVAL;
58586426a28SEmil Tsalapatis 	}
58686426a28SEmil Tsalapatis 
58786426a28SEmil Tsalapatis 	/* Chunk is already properly unpoisoned if allocated. */
58886426a28SEmil Tsalapatis 	if (chunk)
58986426a28SEmil Tsalapatis 		chunk->next = buddy->first_chunk;
59086426a28SEmil Tsalapatis 
59186426a28SEmil Tsalapatis 	/* Put the chunk at the beginning of the list. */
59286426a28SEmil Tsalapatis 	buddy->first_chunk = chunk;
59386426a28SEmil Tsalapatis 
59486426a28SEmil Tsalapatis 	buddy_unlock(buddy);
59586426a28SEmil Tsalapatis 
59686426a28SEmil Tsalapatis 	return chunk ? 0 : -ENOMEM;
59786426a28SEmil Tsalapatis }
59886426a28SEmil Tsalapatis 
59986426a28SEmil Tsalapatis /*
60086426a28SEmil Tsalapatis  * Destroy the allocator. This does not check whether there are any allocations
60186426a28SEmil Tsalapatis  * currently in use, so any pages being accessed will start taking arena faults.
60286426a28SEmil Tsalapatis  * We do not take a lock because we are freeing arena pages, and nobody should
60386426a28SEmil Tsalapatis  * be using the allocator at that point in the execution.
60486426a28SEmil Tsalapatis  */
605b9b23fe1SEmil Tsalapatis __weak int buddy_destroy(struct buddy __arena *buddy)
60686426a28SEmil Tsalapatis {
607b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena *chunk, *next;
60886426a28SEmil Tsalapatis 
60986426a28SEmil Tsalapatis 	if (!buddy)
61086426a28SEmil Tsalapatis 		return -EINVAL;
61186426a28SEmil Tsalapatis 
61286426a28SEmil Tsalapatis 	/*
61386426a28SEmil Tsalapatis 	 * Traverse all buddy chunks and free them back to the arena
61486426a28SEmil Tsalapatis 	 * with the same granularity they were allocated with.
61586426a28SEmil Tsalapatis 	 */
61686426a28SEmil Tsalapatis 	for (chunk = buddy->first_chunk; chunk && can_loop; chunk = next) {
61786426a28SEmil Tsalapatis 		next = chunk->next;
61886426a28SEmil Tsalapatis 
61986426a28SEmil Tsalapatis 		/* Wholesale poison the entire block. */
62086426a28SEmil Tsalapatis 		asan_poison(chunk, BUDDY_POISONED,
62186426a28SEmil Tsalapatis 			    BUDDY_CHUNK_PAGES * __PAGE_SIZE);
62286426a28SEmil Tsalapatis 		bpf_arena_free_pages(&arena, chunk, BUDDY_CHUNK_PAGES);
62386426a28SEmil Tsalapatis 	}
62486426a28SEmil Tsalapatis 
62586426a28SEmil Tsalapatis 	/* Free up any part of the address space that did not get used. */
62686426a28SEmil Tsalapatis 	buddy_unreserve_arena_vaddr(buddy);
62786426a28SEmil Tsalapatis 
62886426a28SEmil Tsalapatis 	/* Clear all fields. */
62986426a28SEmil Tsalapatis 	buddy->first_chunk = NULL;
63086426a28SEmil Tsalapatis 
63186426a28SEmil Tsalapatis 	return 0;
63286426a28SEmil Tsalapatis }
63386426a28SEmil Tsalapatis 
634b9b23fe1SEmil Tsalapatis __weak u64 buddy_chunk_alloc(struct buddy_chunk __arena *chunk, int order_req)
63586426a28SEmil Tsalapatis {
636b9b23fe1SEmil Tsalapatis 	struct buddy_header __arena *header, *tmp_header, *next_header;
63786426a28SEmil Tsalapatis 	u32 idx, tmpidx, retidx;
63886426a28SEmil Tsalapatis 	u64 address;
63986426a28SEmil Tsalapatis 	u64 order = 0;
64086426a28SEmil Tsalapatis 	u64 i;
64186426a28SEmil Tsalapatis 
64286426a28SEmil Tsalapatis 	for (order = order_req; order < BUDDY_CHUNK_NUM_ORDERS && can_loop; order++) {
64386426a28SEmil Tsalapatis 		if (chunk->freelists[order] != BUDDY_CHUNK_ITEMS)
64486426a28SEmil Tsalapatis 			break;
64586426a28SEmil Tsalapatis 	}
64686426a28SEmil Tsalapatis 
64786426a28SEmil Tsalapatis 	if (order >= BUDDY_CHUNK_NUM_ORDERS)
64886426a28SEmil Tsalapatis 		return (u64)NULL;
64986426a28SEmil Tsalapatis 
65086426a28SEmil Tsalapatis 	retidx = chunk->freelists[order];
65186426a28SEmil Tsalapatis 	header = idx_to_header(chunk, retidx);
65286426a28SEmil Tsalapatis 	if (unlikely(!header))
65386426a28SEmil Tsalapatis 		return (u64) NULL;
65486426a28SEmil Tsalapatis 
65586426a28SEmil Tsalapatis 	chunk->freelists[order] = header->next_index;
65686426a28SEmil Tsalapatis 
65786426a28SEmil Tsalapatis 	if (header->next_index != BUDDY_CHUNK_ITEMS) {
65886426a28SEmil Tsalapatis 		next_header = idx_to_header(chunk, header->next_index);
65986426a28SEmil Tsalapatis 		next_header->prev_index = BUDDY_CHUNK_ITEMS;
66086426a28SEmil Tsalapatis 	}
66186426a28SEmil Tsalapatis 
66286426a28SEmil Tsalapatis 	header->prev_index = BUDDY_CHUNK_ITEMS;
66386426a28SEmil Tsalapatis 	header->next_index = BUDDY_CHUNK_ITEMS;
66486426a28SEmil Tsalapatis 	if (idx_set_order(chunk, retidx, order_req))
66586426a28SEmil Tsalapatis 		return (u64)NULL;
66686426a28SEmil Tsalapatis 
66786426a28SEmil Tsalapatis 	if (idx_set_allocated(chunk, retidx, true))
66886426a28SEmil Tsalapatis 		return (u64)NULL;
66986426a28SEmil Tsalapatis 
67086426a28SEmil Tsalapatis 	/*
67186426a28SEmil Tsalapatis 	 * Do not unpoison the address yet, will be done by the caller
67286426a28SEmil Tsalapatis 	 * because the caller has the exact allocation size requested.
67386426a28SEmil Tsalapatis 	 */
67486426a28SEmil Tsalapatis 	address = (u64)idx_to_addr(chunk, retidx);
67586426a28SEmil Tsalapatis 	if (!address)
67686426a28SEmil Tsalapatis 		return (u64)NULL;
67786426a28SEmil Tsalapatis 
67886426a28SEmil Tsalapatis 	/* If we allocated from a larger-order chunk, split the buddies. */
67986426a28SEmil Tsalapatis 	for (i = order_req; i < order && can_loop; i++) {
68086426a28SEmil Tsalapatis 		/*
68186426a28SEmil Tsalapatis 		 * Flip the bit for the current order (the bit is guaranteed
68286426a28SEmil Tsalapatis 		 * to be 0, so just add 1 << i).
68386426a28SEmil Tsalapatis 		 */
68486426a28SEmil Tsalapatis 		idx = retidx + (1 << i);
68586426a28SEmil Tsalapatis 
68686426a28SEmil Tsalapatis 		/* Add the buddy of the allocation to the free list. */
68786426a28SEmil Tsalapatis 		header = idx_to_header(chunk, idx);
68886426a28SEmil Tsalapatis 		/* Unpoison the buddy header */
68986426a28SEmil Tsalapatis 		asan_unpoison(header, sizeof(*header));
69086426a28SEmil Tsalapatis 
69186426a28SEmil Tsalapatis 		if (idx_set_order(chunk, idx, i))
69286426a28SEmil Tsalapatis 			return (u64)NULL;
69386426a28SEmil Tsalapatis 
69486426a28SEmil Tsalapatis 		/* Push the header to the beginning of the freelists list. */
69586426a28SEmil Tsalapatis 		tmpidx = chunk->freelists[i];
69686426a28SEmil Tsalapatis 
69786426a28SEmil Tsalapatis 		header->prev_index = BUDDY_CHUNK_ITEMS;
69886426a28SEmil Tsalapatis 		header->next_index = tmpidx;
69986426a28SEmil Tsalapatis 
70086426a28SEmil Tsalapatis 		if (tmpidx != BUDDY_CHUNK_ITEMS) {
70186426a28SEmil Tsalapatis 			tmp_header = idx_to_header(chunk, tmpidx);
70286426a28SEmil Tsalapatis 			tmp_header->prev_index = idx;
70386426a28SEmil Tsalapatis 		}
70486426a28SEmil Tsalapatis 
70586426a28SEmil Tsalapatis 		chunk->freelists[i] = idx;
70686426a28SEmil Tsalapatis 	}
70786426a28SEmil Tsalapatis 
70886426a28SEmil Tsalapatis 	return address;
70986426a28SEmil Tsalapatis }
71086426a28SEmil Tsalapatis 
71186426a28SEmil Tsalapatis /* Scan the existing chunks for available memory. */
712b9b23fe1SEmil Tsalapatis static u64 buddy_alloc_from_existing_chunks(struct buddy __arena *buddy, int order)
71386426a28SEmil Tsalapatis {
714b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena *chunk;
71586426a28SEmil Tsalapatis 	u64 address;
71686426a28SEmil Tsalapatis 
71786426a28SEmil Tsalapatis 	for (chunk = buddy->first_chunk; chunk != NULL && can_loop;
71886426a28SEmil Tsalapatis 	     chunk = chunk->next) {
71986426a28SEmil Tsalapatis 		address = buddy_chunk_alloc(chunk, order);
72086426a28SEmil Tsalapatis 		if (address)
72186426a28SEmil Tsalapatis 			return address;
72286426a28SEmil Tsalapatis 	}
72386426a28SEmil Tsalapatis 
72486426a28SEmil Tsalapatis 	return (u64)NULL;
72586426a28SEmil Tsalapatis }
72686426a28SEmil Tsalapatis 
72786426a28SEmil Tsalapatis /*
72886426a28SEmil Tsalapatis  * Try an allocation from a newly allocated chunk. Also
72986426a28SEmil Tsalapatis  * incorporate the chunk into the linked list.
73086426a28SEmil Tsalapatis  */
731b9b23fe1SEmil Tsalapatis static u64 buddy_alloc_from_new_chunk(struct buddy __arena *buddy, struct buddy_chunk __arena *chunk, int order)
73286426a28SEmil Tsalapatis {
73386426a28SEmil Tsalapatis 	u64 address;
73486426a28SEmil Tsalapatis 
73586426a28SEmil Tsalapatis 	if (buddy_lock(buddy))
73686426a28SEmil Tsalapatis 		return (u64)NULL;
73786426a28SEmil Tsalapatis 
73886426a28SEmil Tsalapatis 
73986426a28SEmil Tsalapatis 	/*
74086426a28SEmil Tsalapatis 	 * Add the chunk into the allocator and try
74186426a28SEmil Tsalapatis 	 * to allocate specifically from that chunk.
74286426a28SEmil Tsalapatis 	 */
74386426a28SEmil Tsalapatis 	chunk->next = buddy->first_chunk;
74486426a28SEmil Tsalapatis 	buddy->first_chunk = chunk;
74586426a28SEmil Tsalapatis 
74686426a28SEmil Tsalapatis 	address = buddy_chunk_alloc(buddy->first_chunk, order);
74786426a28SEmil Tsalapatis 
74886426a28SEmil Tsalapatis 	buddy_unlock(buddy);
74986426a28SEmil Tsalapatis 
75086426a28SEmil Tsalapatis 	return (u64)address;
75186426a28SEmil Tsalapatis }
75286426a28SEmil Tsalapatis __weak
753*367e6e4aSEmil Tsalapatis void __arena *buddy_alloc(struct buddy __arena *buddy, size_t size)
75486426a28SEmil Tsalapatis {
755*367e6e4aSEmil Tsalapatis 	void __arena *address = NULL;
756b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena *chunk;
75786426a28SEmil Tsalapatis 	int order;
75886426a28SEmil Tsalapatis 
75986426a28SEmil Tsalapatis 	if (!buddy)
760*367e6e4aSEmil Tsalapatis 		return NULL;
76186426a28SEmil Tsalapatis 
76286426a28SEmil Tsalapatis 	order = size_to_order(size);
76386426a28SEmil Tsalapatis 	if (order >= BUDDY_CHUNK_NUM_ORDERS || order < 0) {
76486426a28SEmil Tsalapatis 		arena_stderr("invalid order %d (sz %lu)\n", order, size);
765*367e6e4aSEmil Tsalapatis 		return NULL;
76686426a28SEmil Tsalapatis 	}
76786426a28SEmil Tsalapatis 
76886426a28SEmil Tsalapatis 	if (buddy_lock(buddy))
769*367e6e4aSEmil Tsalapatis 		return NULL;
77086426a28SEmil Tsalapatis 
771*367e6e4aSEmil Tsalapatis 	address = (u8 __arena *)buddy_alloc_from_existing_chunks(buddy, order);
77286426a28SEmil Tsalapatis 	buddy_unlock(buddy);
77386426a28SEmil Tsalapatis 	if (address)
77486426a28SEmil Tsalapatis 		goto done;
77586426a28SEmil Tsalapatis 
77686426a28SEmil Tsalapatis 	/* Get a new chunk. */
77786426a28SEmil Tsalapatis 	chunk = buddy_chunk_get(buddy);
77886426a28SEmil Tsalapatis 	if (chunk)
779*367e6e4aSEmil Tsalapatis 		address = (u8 __arena *)buddy_alloc_from_new_chunk(buddy, chunk, order);
78086426a28SEmil Tsalapatis 
78186426a28SEmil Tsalapatis done:
78286426a28SEmil Tsalapatis 	/* If we failed to allocate memory, return NULL. */
78386426a28SEmil Tsalapatis 	if (!address)
784*367e6e4aSEmil Tsalapatis 		return NULL;
78586426a28SEmil Tsalapatis 
78686426a28SEmil Tsalapatis 	/*
78786426a28SEmil Tsalapatis 	 * Unpoison exactly the amount of bytes requested. If the
78886426a28SEmil Tsalapatis 	 * data is smaller than the header, we must poison any
78986426a28SEmil Tsalapatis 	 * unused bytes that were part of the header.
79086426a28SEmil Tsalapatis 	 */
791b9b23fe1SEmil Tsalapatis 	if (size < BUDDY_HEADER_OFF + sizeof(struct buddy_header __arena))
792*367e6e4aSEmil Tsalapatis 		asan_poison(address + BUDDY_HEADER_OFF, BUDDY_POISONED,
793b9b23fe1SEmil Tsalapatis 			    sizeof(struct buddy_header __arena));
79486426a28SEmil Tsalapatis 
795*367e6e4aSEmil Tsalapatis 	asan_unpoison(address, size);
79686426a28SEmil Tsalapatis 
79786426a28SEmil Tsalapatis 	return address;
79886426a28SEmil Tsalapatis }
79986426a28SEmil Tsalapatis 
800b9b23fe1SEmil Tsalapatis static __always_inline int buddy_free_unlocked(struct buddy __arena *buddy, u64 addr)
80186426a28SEmil Tsalapatis {
802b9b23fe1SEmil Tsalapatis 	struct buddy_header __arena *header, *buddy_header;
80386426a28SEmil Tsalapatis 	u64 idx, buddy_idx, tmp_idx;
804b9b23fe1SEmil Tsalapatis 	struct buddy_chunk __arena *chunk;
80586426a28SEmil Tsalapatis 	bool allocated;
80686426a28SEmil Tsalapatis 	u8 order;
80786426a28SEmil Tsalapatis 	int ret;
80886426a28SEmil Tsalapatis 
80986426a28SEmil Tsalapatis 	if (!buddy)
81086426a28SEmil Tsalapatis 		return -EINVAL;
81186426a28SEmil Tsalapatis 
81286426a28SEmil Tsalapatis 	if (addr & (BUDDY_MIN_ALLOC_BYTES - 1)) {
81386426a28SEmil Tsalapatis 		arena_stderr("Freeing unaligned address %llx\n", addr);
81486426a28SEmil Tsalapatis 		return -EINVAL;
81586426a28SEmil Tsalapatis 	}
81686426a28SEmil Tsalapatis 
81786426a28SEmil Tsalapatis 	/* Get (chunk, idx) out of the address. */
81886426a28SEmil Tsalapatis 	chunk = (void __arena *)(addr & ~BUDDY_CHUNK_OFFSET_MASK);
81986426a28SEmil Tsalapatis 	idx = (addr & BUDDY_CHUNK_OFFSET_MASK) / BUDDY_MIN_ALLOC_BYTES;
82086426a28SEmil Tsalapatis 
82186426a28SEmil Tsalapatis 	/* Mark the block as unallocated so we can access the header. */
82286426a28SEmil Tsalapatis 	ret = idx_set_allocated(chunk, idx, false);
82386426a28SEmil Tsalapatis 	if (ret)
82486426a28SEmil Tsalapatis 		return ret;
82586426a28SEmil Tsalapatis 
82686426a28SEmil Tsalapatis 	order  = idx_get_order(chunk, idx);
82786426a28SEmil Tsalapatis 	header = idx_to_header(chunk, idx);
82886426a28SEmil Tsalapatis 
82986426a28SEmil Tsalapatis 	/* The header is in the block itself, keep it unpoisoned. */
83086426a28SEmil Tsalapatis 	asan_poison((u8 __arena *)addr, BUDDY_POISONED,
83186426a28SEmil Tsalapatis 		    BUDDY_MIN_ALLOC_BYTES << order);
83286426a28SEmil Tsalapatis 	asan_unpoison(header, sizeof(*header));
83386426a28SEmil Tsalapatis 
83486426a28SEmil Tsalapatis 	/*
83586426a28SEmil Tsalapatis 	 * Coalescing loop. Merge with free buddies of equal order.
83686426a28SEmil Tsalapatis 	 * For every coalescing step, keep the left buddy and
83786426a28SEmil Tsalapatis 	 * drop the right buddy's header.
83886426a28SEmil Tsalapatis 	 */
83986426a28SEmil Tsalapatis 	for (; order < BUDDY_CHUNK_NUM_ORDERS && can_loop; order++) {
84086426a28SEmil Tsalapatis 		buddy_idx = idx ^ (1 << order);
84186426a28SEmil Tsalapatis 
84286426a28SEmil Tsalapatis 		/* Check if the buddy is actually free. */
84386426a28SEmil Tsalapatis 		idx_is_allocated(chunk, buddy_idx, &allocated);
84486426a28SEmil Tsalapatis 		if (allocated)
84586426a28SEmil Tsalapatis 			break;
84686426a28SEmil Tsalapatis 
84786426a28SEmil Tsalapatis 		/*
84886426a28SEmil Tsalapatis 		 * If buddy is not the same order as the chunk
84986426a28SEmil Tsalapatis 		 * being freed, then we're done coalescing.
85086426a28SEmil Tsalapatis 		 */
85186426a28SEmil Tsalapatis 		if (idx_get_order(chunk, buddy_idx) != order)
85286426a28SEmil Tsalapatis 			break;
85386426a28SEmil Tsalapatis 
85486426a28SEmil Tsalapatis 		buddy_header = idx_to_header(chunk, buddy_idx);
85586426a28SEmil Tsalapatis 		header_remove_freelist(chunk, buddy_header, order);
85686426a28SEmil Tsalapatis 
85786426a28SEmil Tsalapatis 		/* Keep the left header out of the two buddies, drop the other one. */
85886426a28SEmil Tsalapatis 		if (buddy_idx < idx) {
85986426a28SEmil Tsalapatis 			tmp_idx = idx;
86086426a28SEmil Tsalapatis 			idx = buddy_idx;
86186426a28SEmil Tsalapatis 			buddy_idx = tmp_idx;
86286426a28SEmil Tsalapatis 		}
86386426a28SEmil Tsalapatis 
86486426a28SEmil Tsalapatis 		/* Remove the buddy from the freelists so that we can merge it. */
86586426a28SEmil Tsalapatis 		idx_set_order(chunk, buddy_idx, order);
86686426a28SEmil Tsalapatis 
86786426a28SEmil Tsalapatis 		buddy_header = idx_to_header(chunk, buddy_idx);
86886426a28SEmil Tsalapatis 		asan_poison(buddy_header, BUDDY_POISONED,
86986426a28SEmil Tsalapatis 			    sizeof(*buddy_header));
87086426a28SEmil Tsalapatis 	}
87186426a28SEmil Tsalapatis 
87286426a28SEmil Tsalapatis 	/* Header properly freed but not in any freelists yet .*/
87386426a28SEmil Tsalapatis 	idx_set_order(chunk, idx, order);
87486426a28SEmil Tsalapatis 
87586426a28SEmil Tsalapatis 	header = idx_to_header(chunk, idx);
87686426a28SEmil Tsalapatis 	header_add_freelist(chunk, header, idx, order);
87786426a28SEmil Tsalapatis 
87886426a28SEmil Tsalapatis 	return 0;
87986426a28SEmil Tsalapatis }
88086426a28SEmil Tsalapatis 
881b9b23fe1SEmil Tsalapatis __weak int buddy_free(struct buddy __arena *buddy, void __arena *addr)
88286426a28SEmil Tsalapatis {
88386426a28SEmil Tsalapatis 	int ret;
88486426a28SEmil Tsalapatis 
88586426a28SEmil Tsalapatis 	if (!buddy)
88686426a28SEmil Tsalapatis 		return -EINVAL;
88786426a28SEmil Tsalapatis 
88886426a28SEmil Tsalapatis 	/* Freeing NULL is a valid no-op. */
88986426a28SEmil Tsalapatis 	if (!addr)
89086426a28SEmil Tsalapatis 		return 0;
89186426a28SEmil Tsalapatis 
89286426a28SEmil Tsalapatis 	ret = buddy_lock(buddy);
89386426a28SEmil Tsalapatis 	if (ret)
89486426a28SEmil Tsalapatis 		return ret;
89586426a28SEmil Tsalapatis 
896b9b23fe1SEmil Tsalapatis 	ret = buddy_free_unlocked(buddy, (u64)addr);
89786426a28SEmil Tsalapatis 
89886426a28SEmil Tsalapatis 	buddy_unlock(buddy);
89986426a28SEmil Tsalapatis 
90086426a28SEmil Tsalapatis 	return ret;
90186426a28SEmil Tsalapatis }
90286426a28SEmil Tsalapatis 
90386426a28SEmil Tsalapatis __weak char _license[] SEC("license") = "GPL";
904