| /* |
| * Copyright (C) 2010-2011, 2013-2014 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 <linux/version.h> |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| #include <linux/semaphore.h> |
| #else /* pre 2.6.26 the file was in the arch specific location */ |
| #include <asm/semaphore.h> |
| #endif |
| |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| #include <asm/atomic.h> |
| #include <linux/vmalloc.h> |
| #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_info) * 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; |
| |
| 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; |
| } |