/* * This work is licensed under the terms of the GNU LGPL, version 2. * * This is a simple allocator that provides contiguous physical addresses * with page granularity. */ #include "libcflat.h" #include "alloc.h" #include "alloc_phys.h" #include "alloc_page.h" #include "bitops.h" #include #include #include static struct spinlock lock; static void *freelist = 0; bool page_alloc_initialized(void) { return freelist != 0; } void free_pages(void *mem, unsigned long size) { void *old_freelist; void *end; assert_msg((unsigned long) mem % PAGE_SIZE == 0, "mem not page aligned: %p", mem); assert_msg(size % PAGE_SIZE == 0, "size not page aligned: %#lx", size); assert_msg(size == 0 || (uintptr_t)mem == -size || (uintptr_t)mem + size > (uintptr_t)mem, "mem + size overflow: %p + %#lx", mem, size); if (size == 0) { freelist = NULL; return; } spin_lock(&lock); old_freelist = freelist; freelist = mem; end = mem + size; while (mem + PAGE_SIZE != end) { *(void **)mem = (mem + PAGE_SIZE); mem += PAGE_SIZE; } *(void **)mem = old_freelist; spin_unlock(&lock); } void free_pages_by_order(void *mem, unsigned long order) { free_pages(mem, 1ul << (order + PAGE_SHIFT)); } void *alloc_page() { void *p; if (!freelist) return 0; spin_lock(&lock); p = freelist; freelist = *(void **)freelist; spin_unlock(&lock); if (p) memset(p, 0, PAGE_SIZE); return p; } /* * Allocates (1 << order) physically contiguous and naturally aligned pages. * Returns NULL if there's no memory left. */ void *alloc_pages(unsigned long order) { /* Generic list traversal. */ void *prev; void *curr = NULL; void *next = freelist; /* Looking for a run of length (1 << order). */ unsigned long run = 0; const unsigned long n = 1ul << order; const unsigned long align_mask = (n << PAGE_SHIFT) - 1; void *run_start = NULL; void *run_prev = NULL; unsigned long run_next_pa = 0; unsigned long pa; assert(order < sizeof(unsigned long) * 8); spin_lock(&lock); for (;;) { prev = curr; curr = next; if (!curr) { run_start = NULL; break; } next = *((void **) curr); pa = virt_to_phys(curr); if (run == 0) { if (!(pa & align_mask)) { run_start = curr; run_prev = prev; run_next_pa = pa + PAGE_SIZE; run = 1; } } else if (pa == run_next_pa) { run_next_pa += PAGE_SIZE; run += 1; } else { run = 0; } if (run == n) { if (run_prev) *((void **) run_prev) = next; else freelist = next; break; } } spin_unlock(&lock); if (run_start) memset(run_start, 0, n * PAGE_SIZE); return run_start; } void free_page(void *page) { spin_lock(&lock); *(void **)page = freelist; freelist = page; spin_unlock(&lock); } static void *page_memalign(size_t alignment, size_t size) { unsigned long n = ALIGN(size, PAGE_SIZE) >> PAGE_SHIFT; unsigned long order; if (!size) return NULL; order = get_order(n); return alloc_pages(order); } static void page_free(void *mem, size_t size) { free_pages(mem, size); } static struct alloc_ops page_alloc_ops = { .memalign = page_memalign, .free = page_free, .align_min = PAGE_SIZE, }; void page_alloc_ops_enable(void) { alloc_ops = &page_alloc_ops; } unsigned int get_order(size_t size) { return is_power_of_2(size) ? fls(size) : fls(size) + 1; }