/* * Copyright (C) 2010-2011 ARM Limited. All rights reserved. * * This program is free software and is provided to you under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence. * * A copy of the licence is included with the program, and can also be obtained from Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* needed to detect kernel version specific code */ #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) #include #else /* pre 2.6.26 the file was in the arch specific location */ #include #endif #include #include #include #include #include "ump_kernel_common.h" #include "ump_kernel_memory_backend.h" #define UMP_BLOCK_SIZE (256UL * 1024UL) /* 256kB, remember to keep the ()s */ typedef struct block_info { struct block_info * next; } block_info; typedef struct block_allocator { struct semaphore mutex; block_info * all_blocks; block_info * first_free; u32 base; u32 num_blocks; u32 num_free; } block_allocator; static void block_allocator_shutdown(ump_memory_backend * backend); static int block_allocator_allocate(void* ctx, ump_dd_mem * mem); static void block_allocator_release(void * ctx, ump_dd_mem * handle); static inline u32 get_phys(block_allocator * allocator, block_info * block); static u32 block_allocator_stat(struct ump_memory_backend *backend); /* * Create dedicated memory backend */ ump_memory_backend * ump_block_allocator_create(u32 base_address, u32 size) { ump_memory_backend * backend; block_allocator * allocator; u32 usable_size; u32 num_blocks; usable_size = (size + UMP_BLOCK_SIZE - 1) & ~(UMP_BLOCK_SIZE - 1); num_blocks = usable_size / UMP_BLOCK_SIZE; if (0 == usable_size) { DBG_MSG(1, ("Memory block of size %u is unusable\n", size)); return NULL; } DBG_MSG(5, ("Creating dedicated UMP memory backend. Base address: 0x%08x, size: 0x%08x\n", base_address, size)); DBG_MSG(6, ("%u usable bytes which becomes %u blocks\n", usable_size, num_blocks)); backend = kzalloc(sizeof(ump_memory_backend), GFP_KERNEL); if (NULL != backend) { allocator = kmalloc(sizeof(block_allocator), GFP_KERNEL); if (NULL != allocator) { allocator->all_blocks = kmalloc(sizeof(block_allocator) * num_blocks, GFP_KERNEL); if (NULL != allocator->all_blocks) { int i; allocator->first_free = NULL; allocator->num_blocks = num_blocks; allocator->num_free = num_blocks; allocator->base = base_address; sema_init(&allocator->mutex, 1); for (i = 0; i < num_blocks; i++) { allocator->all_blocks[i].next = allocator->first_free; allocator->first_free = &allocator->all_blocks[i]; } backend->ctx = allocator; backend->allocate = block_allocator_allocate; backend->release = block_allocator_release; backend->shutdown = block_allocator_shutdown; backend->stat = block_allocator_stat; backend->pre_allocate_physical_check = NULL; backend->adjust_to_mali_phys = NULL; /* MALI_SEC */ backend->get = NULL; backend->set = NULL; return backend; } kfree(allocator); } kfree(backend); } return NULL; } /* * Destroy specified dedicated memory backend */ static void block_allocator_shutdown(ump_memory_backend * backend) { block_allocator * allocator; BUG_ON(!backend); BUG_ON(!backend->ctx); allocator = (block_allocator*)backend->ctx; DBG_MSG_IF(1, allocator->num_free != allocator->num_blocks, ("%u blocks still in use during shutdown\n", allocator->num_blocks - allocator->num_free)); kfree(allocator->all_blocks); kfree(allocator); kfree(backend); } static int block_allocator_allocate(void* ctx, ump_dd_mem * mem) { block_allocator * allocator; u32 left; block_info * last_allocated = NULL; int i = 0; BUG_ON(!ctx); BUG_ON(!mem); allocator = (block_allocator*)ctx; left = mem->size_bytes; BUG_ON(!left); BUG_ON(!&allocator->mutex); mem->nr_blocks = ((left + UMP_BLOCK_SIZE - 1) & ~(UMP_BLOCK_SIZE - 1)) / UMP_BLOCK_SIZE; mem->block_array = (ump_dd_physical_block*)vmalloc(sizeof(ump_dd_physical_block) * mem->nr_blocks); if (NULL == mem->block_array) { MSG_ERR(("Failed to allocate block array\n")); return 0; } if (down_interruptible(&allocator->mutex)) { MSG_ERR(("Could not get mutex to do block_allocate\n")); return 0; } mem->size_bytes = 0; while ((left > 0) && (allocator->first_free)) { block_info * block; block = allocator->first_free; allocator->first_free = allocator->first_free->next; block->next = last_allocated; last_allocated = block; allocator->num_free--; mem->block_array[i].addr = get_phys(allocator, block); mem->block_array[i].size = UMP_BLOCK_SIZE; mem->size_bytes += UMP_BLOCK_SIZE; i++; if (left < UMP_BLOCK_SIZE) left = 0; else left -= UMP_BLOCK_SIZE; } if (left) { block_info * block; /* release all memory back to the pool */ while (last_allocated) { block = last_allocated->next; last_allocated->next = allocator->first_free; allocator->first_free = last_allocated; last_allocated = block; allocator->num_free++; } vfree(mem->block_array); mem->backend_info = NULL; mem->block_array = NULL; DBG_MSG(4, ("Could not find a mem-block for the allocation.\n")); up(&allocator->mutex); return 0; } mem->backend_info = last_allocated; up(&allocator->mutex); mem->is_cached=0; return 1; } static void block_allocator_release(void * ctx, ump_dd_mem * handle) { block_allocator * allocator; block_info * block, * next; BUG_ON(!ctx); BUG_ON(!handle); allocator = (block_allocator*)ctx; block = (block_info*)handle->backend_info; BUG_ON(!block); if (down_interruptible(&allocator->mutex)) { MSG_ERR(("Allocator release: Failed to get mutex - memory leak\n")); return; } while (block) { next = block->next; BUG_ON( (block < allocator->all_blocks) || (block > (allocator->all_blocks + allocator->num_blocks))); block->next = allocator->first_free; allocator->first_free = block; allocator->num_free++; block = next; } DBG_MSG(3, ("%d blocks free after release call\n", allocator->num_free)); up(&allocator->mutex); vfree(handle->block_array); handle->block_array = NULL; } /* * Helper function for calculating the physical base adderss of a memory block */ static inline u32 get_phys(block_allocator * allocator, block_info * block) { return allocator->base + ((block - allocator->all_blocks) * UMP_BLOCK_SIZE); } static u32 block_allocator_stat(struct ump_memory_backend *backend) { block_allocator *allocator; BUG_ON(!backend); allocator = (block_allocator*)backend->ctx; BUG_ON(!allocator); return (allocator->num_blocks - allocator->num_free)* UMP_BLOCK_SIZE; }