123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- /*
- * xvmalloc memory allocator
- *
- * Copyright (C) 2008, 2009, 2010 Nitin Gupta
- *
- * This code is released using a dual license strategy: BSD/GPL
- * You can choose the licence that better fits your requirements.
- *
- * Released under the terms of 3-clause BSD License
- * Released under the terms of GNU General Public License Version 2.0
- */
- #ifdef CONFIG_ZRAM_DEBUG
- #define DEBUG
- #endif
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/bitops.h>
- #include <linux/errno.h>
- #include <linux/highmem.h>
- #include <linux/init.h>
- #include <linux/string.h>
- #include <linux/slab.h>
- #include "xvmalloc.h"
- #include "xvmalloc_int.h"
- static void stat_inc(u64 *value)
- {
- *value = *value + 1;
- }
- static void stat_dec(u64 *value)
- {
- *value = *value - 1;
- }
- static int test_flag(struct block_header *block, enum blockflags flag)
- {
- return block->prev & BIT(flag);
- }
- static void set_flag(struct block_header *block, enum blockflags flag)
- {
- block->prev |= BIT(flag);
- }
- static void clear_flag(struct block_header *block, enum blockflags flag)
- {
- block->prev &= ~BIT(flag);
- }
- /*
- * Given <page, offset> pair, provide a dereferencable pointer.
- * This is called from xv_malloc/xv_free path, so it
- * needs to be fast.
- */
- static void *get_ptr_atomic(struct page *page, u16 offset, enum km_type type)
- {
- unsigned char *base;
- base = kmap_atomic(page, type);
- return base + offset;
- }
- static void put_ptr_atomic(void *ptr, enum km_type type)
- {
- kunmap_atomic(ptr, type);
- }
- static u32 get_blockprev(struct block_header *block)
- {
- return block->prev & PREV_MASK;
- }
- static void set_blockprev(struct block_header *block, u16 new_offset)
- {
- block->prev = new_offset | (block->prev & FLAGS_MASK);
- }
- static struct block_header *BLOCK_NEXT(struct block_header *block)
- {
- return (struct block_header *)
- ((char *)block + block->size + XV_ALIGN);
- }
- /*
- * Get index of free list containing blocks of maximum size
- * which is less than or equal to given size.
- */
- static u32 get_index_for_insert(u32 size)
- {
- if (unlikely(size > XV_MAX_ALLOC_SIZE))
- size = XV_MAX_ALLOC_SIZE;
- size &= ~FL_DELTA_MASK;
- return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
- }
- /*
- * Get index of free list having blocks of size greater than
- * or equal to requested size.
- */
- static u32 get_index(u32 size)
- {
- if (unlikely(size < XV_MIN_ALLOC_SIZE))
- size = XV_MIN_ALLOC_SIZE;
- size = ALIGN(size, FL_DELTA);
- return (size - XV_MIN_ALLOC_SIZE) >> FL_DELTA_SHIFT;
- }
- /**
- * find_block - find block of at least given size
- * @pool: memory pool to search from
- * @size: size of block required
- * @page: page containing required block
- * @offset: offset within the page where block is located.
- *
- * Searches two level bitmap to locate block of at least
- * the given size. If such a block is found, it provides
- * <page, offset> to identify this block and returns index
- * in freelist where we found this block.
- * Otherwise, returns 0 and <page, offset> params are not touched.
- */
- static u32 find_block(struct xv_pool *pool, u32 size,
- struct page **page, u32 *offset)
- {
- ulong flbitmap, slbitmap;
- u32 flindex, slindex, slbitstart;
- /* There are no free blocks in this pool */
- if (!pool->flbitmap)
- return 0;
- /* Get freelist index correspoding to this size */
- slindex = get_index(size);
- slbitmap = pool->slbitmap[slindex / BITS_PER_LONG];
- slbitstart = slindex % BITS_PER_LONG;
- /*
- * If freelist is not empty at this index, we found the
- * block - head of this list. This is approximate best-fit match.
- */
- if (test_bit(slbitstart, &slbitmap)) {
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
- return slindex;
- }
- /*
- * No best-fit found. Search a bit further in bitmap for a free block.
- * Second level bitmap consists of series of 32-bit chunks. Search
- * further in the chunk where we expected a best-fit, starting from
- * index location found above.
- */
- slbitstart++;
- slbitmap >>= slbitstart;
- /* Skip this search if we were already at end of this bitmap chunk */
- if ((slbitstart != BITS_PER_LONG) && slbitmap) {
- slindex += __ffs(slbitmap) + 1;
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
- return slindex;
- }
- /* Now do a full two-level bitmap search to find next nearest fit */
- flindex = slindex / BITS_PER_LONG;
- flbitmap = (pool->flbitmap) >> (flindex + 1);
- if (!flbitmap)
- return 0;
- flindex += __ffs(flbitmap) + 1;
- slbitmap = pool->slbitmap[flindex];
- slindex = (flindex * BITS_PER_LONG) + __ffs(slbitmap);
- *page = pool->freelist[slindex].page;
- *offset = pool->freelist[slindex].offset;
- return slindex;
- }
- /*
- * Insert block at <page, offset> in freelist of given pool.
- * freelist used depends on block size.
- */
- static void insert_block(struct xv_pool *pool, struct page *page, u32 offset,
- struct block_header *block)
- {
- u32 flindex, slindex;
- struct block_header *nextblock;
- slindex = get_index_for_insert(block->size);
- flindex = slindex / BITS_PER_LONG;
- block->link.prev_page = NULL;
- block->link.prev_offset = 0;
- block->link.next_page = pool->freelist[slindex].page;
- block->link.next_offset = pool->freelist[slindex].offset;
- pool->freelist[slindex].page = page;
- pool->freelist[slindex].offset = offset;
- if (block->link.next_page) {
- nextblock = get_ptr_atomic(block->link.next_page,
- block->link.next_offset, KM_USER1);
- nextblock->link.prev_page = page;
- nextblock->link.prev_offset = offset;
- put_ptr_atomic(nextblock, KM_USER1);
- /* If there was a next page then the free bits are set. */
- return;
- }
- __set_bit(slindex % BITS_PER_LONG, &pool->slbitmap[flindex]);
- __set_bit(flindex, &pool->flbitmap);
- }
- /*
- * Remove block from freelist. Index 'slindex' identifies the freelist.
- */
- static void remove_block(struct xv_pool *pool, struct page *page, u32 offset,
- struct block_header *block, u32 slindex)
- {
- u32 flindex = slindex / BITS_PER_LONG;
- struct block_header *tmpblock;
- if (block->link.prev_page) {
- tmpblock = get_ptr_atomic(block->link.prev_page,
- block->link.prev_offset, KM_USER1);
- tmpblock->link.next_page = block->link.next_page;
- tmpblock->link.next_offset = block->link.next_offset;
- put_ptr_atomic(tmpblock, KM_USER1);
- }
- if (block->link.next_page) {
- tmpblock = get_ptr_atomic(block->link.next_page,
- block->link.next_offset, KM_USER1);
- tmpblock->link.prev_page = block->link.prev_page;
- tmpblock->link.prev_offset = block->link.prev_offset;
- put_ptr_atomic(tmpblock, KM_USER1);
- }
- /* Is this block is at the head of the freelist? */
- if (pool->freelist[slindex].page == page
- && pool->freelist[slindex].offset == offset) {
- pool->freelist[slindex].page = block->link.next_page;
- pool->freelist[slindex].offset = block->link.next_offset;
- if (pool->freelist[slindex].page) {
- struct block_header *tmpblock;
- tmpblock = get_ptr_atomic(pool->freelist[slindex].page,
- pool->freelist[slindex].offset,
- KM_USER1);
- tmpblock->link.prev_page = NULL;
- tmpblock->link.prev_offset = 0;
- put_ptr_atomic(tmpblock, KM_USER1);
- } else {
- /* This freelist bucket is empty */
- __clear_bit(slindex % BITS_PER_LONG,
- &pool->slbitmap[flindex]);
- if (!pool->slbitmap[flindex])
- __clear_bit(flindex, &pool->flbitmap);
- }
- }
- block->link.prev_page = NULL;
- block->link.prev_offset = 0;
- block->link.next_page = NULL;
- block->link.next_offset = 0;
- }
- /*
- * Allocate a page and add it to freelist of given pool.
- */
- static int grow_pool(struct xv_pool *pool, gfp_t flags)
- {
- struct page *page;
- struct block_header *block;
- page = alloc_page(flags);
- if (unlikely(!page))
- return -ENOMEM;
- stat_inc(&pool->total_pages);
- spin_lock(&pool->lock);
- block = get_ptr_atomic(page, 0, KM_USER0);
- block->size = PAGE_SIZE - XV_ALIGN;
- set_flag(block, BLOCK_FREE);
- clear_flag(block, PREV_FREE);
- set_blockprev(block, 0);
- insert_block(pool, page, 0, block);
- put_ptr_atomic(block, KM_USER0);
- spin_unlock(&pool->lock);
- return 0;
- }
- /*
- * Create a memory pool. Allocates freelist, bitmaps and other
- * per-pool metadata.
- */
- struct xv_pool *xv_create_pool(void)
- {
- u32 ovhd_size;
- struct xv_pool *pool;
- ovhd_size = roundup(sizeof(*pool), PAGE_SIZE);
- pool = kzalloc(ovhd_size, GFP_KERNEL);
- if (!pool)
- return NULL;
- spin_lock_init(&pool->lock);
- return pool;
- }
- EXPORT_SYMBOL_GPL(xv_create_pool);
- void xv_destroy_pool(struct xv_pool *pool)
- {
- kfree(pool);
- }
- EXPORT_SYMBOL_GPL(xv_destroy_pool);
- /**
- * xv_malloc - Allocate block of given size from pool.
- * @pool: pool to allocate from
- * @size: size of block to allocate
- * @page: page no. that holds the object
- * @offset: location of object within page
- *
- * On success, <page, offset> identifies block allocated
- * and 0 is returned. On failure, <page, offset> is set to
- * 0 and -ENOMEM is returned.
- *
- * Allocation requests with size > XV_MAX_ALLOC_SIZE will fail.
- */
- int xv_malloc(struct xv_pool *pool, u32 size, struct page **page,
- u32 *offset, gfp_t flags)
- {
- int error;
- u32 index, tmpsize, origsize, tmpoffset;
- struct block_header *block, *tmpblock;
- *page = NULL;
- *offset = 0;
- origsize = size;
- if (unlikely(!size || size > XV_MAX_ALLOC_SIZE))
- return -ENOMEM;
- size = ALIGN(size, XV_ALIGN);
- spin_lock(&pool->lock);
- index = find_block(pool, size, page, offset);
- if (!*page) {
- spin_unlock(&pool->lock);
- if (flags & GFP_NOWAIT)
- return -ENOMEM;
- error = grow_pool(pool, flags);
- if (unlikely(error))
- return error;
- spin_lock(&pool->lock);
- index = find_block(pool, size, page, offset);
- }
- if (!*page) {
- spin_unlock(&pool->lock);
- return -ENOMEM;
- }
- block = get_ptr_atomic(*page, *offset, KM_USER0);
- remove_block(pool, *page, *offset, block, index);
- /* Split the block if required */
- tmpoffset = *offset + size + XV_ALIGN;
- tmpsize = block->size - size;
- tmpblock = (struct block_header *)((char *)block + size + XV_ALIGN);
- if (tmpsize) {
- tmpblock->size = tmpsize - XV_ALIGN;
- set_flag(tmpblock, BLOCK_FREE);
- clear_flag(tmpblock, PREV_FREE);
- set_blockprev(tmpblock, *offset);
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
- insert_block(pool, *page, tmpoffset, tmpblock);
- if (tmpoffset + XV_ALIGN + tmpblock->size != PAGE_SIZE) {
- tmpblock = BLOCK_NEXT(tmpblock);
- set_blockprev(tmpblock, tmpoffset);
- }
- } else {
- /* This block is exact fit */
- if (tmpoffset != PAGE_SIZE)
- clear_flag(tmpblock, PREV_FREE);
- }
- block->size = origsize;
- clear_flag(block, BLOCK_FREE);
- put_ptr_atomic(block, KM_USER0);
- spin_unlock(&pool->lock);
- *offset += XV_ALIGN;
- return 0;
- }
- EXPORT_SYMBOL_GPL(xv_malloc);
- /*
- * Free block identified with <page, offset>
- */
- void xv_free(struct xv_pool *pool, struct page *page, u32 offset)
- {
- void *page_start;
- struct block_header *block, *tmpblock;
- offset -= XV_ALIGN;
- spin_lock(&pool->lock);
- page_start = get_ptr_atomic(page, 0, KM_USER0);
- block = (struct block_header *)((char *)page_start + offset);
- /* Catch double free bugs */
- BUG_ON(test_flag(block, BLOCK_FREE));
- block->size = ALIGN(block->size, XV_ALIGN);
- tmpblock = BLOCK_NEXT(block);
- if (offset + block->size + XV_ALIGN == PAGE_SIZE)
- tmpblock = NULL;
- /* Merge next block if its free */
- if (tmpblock && test_flag(tmpblock, BLOCK_FREE)) {
- /*
- * Blocks smaller than XV_MIN_ALLOC_SIZE
- * are not inserted in any free list.
- */
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE) {
- remove_block(pool, page,
- offset + block->size + XV_ALIGN, tmpblock,
- get_index_for_insert(tmpblock->size));
- }
- block->size += tmpblock->size + XV_ALIGN;
- }
- /* Merge previous block if its free */
- if (test_flag(block, PREV_FREE)) {
- tmpblock = (struct block_header *)((char *)(page_start) +
- get_blockprev(block));
- offset = offset - tmpblock->size - XV_ALIGN;
- if (tmpblock->size >= XV_MIN_ALLOC_SIZE)
- remove_block(pool, page, offset, tmpblock,
- get_index_for_insert(tmpblock->size));
- tmpblock->size += block->size + XV_ALIGN;
- block = tmpblock;
- }
- /* No used objects in this page. Free it. */
- if (block->size == PAGE_SIZE - XV_ALIGN) {
- put_ptr_atomic(page_start, KM_USER0);
- spin_unlock(&pool->lock);
- __free_page(page);
- stat_dec(&pool->total_pages);
- return;
- }
- set_flag(block, BLOCK_FREE);
- if (block->size >= XV_MIN_ALLOC_SIZE)
- insert_block(pool, page, offset, block);
- if (offset + block->size + XV_ALIGN != PAGE_SIZE) {
- tmpblock = BLOCK_NEXT(block);
- set_flag(tmpblock, PREV_FREE);
- set_blockprev(tmpblock, offset);
- }
- put_ptr_atomic(page_start, KM_USER0);
- spin_unlock(&pool->lock);
- }
- EXPORT_SYMBOL_GPL(xv_free);
- u32 xv_get_object_size(void *obj)
- {
- struct block_header *blk;
- blk = (struct block_header *)((char *)(obj) - XV_ALIGN);
- return blk->size;
- }
- EXPORT_SYMBOL_GPL(xv_get_object_size);
- /*
- * Returns total memory used by allocator (userdata + metadata)
- */
- u64 xv_get_total_size_bytes(struct xv_pool *pool)
- {
- return pool->total_pages << PAGE_SHIFT;
- }
- EXPORT_SYMBOL_GPL(xv_get_total_size_bytes);
|