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