| /* |
| * Copyright (C) 2010-2013 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. |
| */ |
| #include "mali_kernel_common.h" |
| #include "mali_kernel_memory_engine.h" |
| #include "mali_block_allocator.h" |
| #include "mali_osk.h" |
| |
| #define MALI_BLOCK_SIZE (256UL * 1024UL) /* 256 kB, remember to keep the ()s */ |
| |
| typedef struct block_info |
| { |
| struct block_info * next; |
| } block_info; |
| |
| /* The structure used as the handle produced by block_allocator_allocate, |
| * and removed by block_allocator_release */ |
| typedef struct block_allocator_allocation |
| { |
| /* The list will be released in reverse order */ |
| block_info *last_allocated; |
| mali_allocation_engine * engine; |
| mali_memory_allocation * descriptor; |
| u32 start_offset; |
| u32 mapping_length; |
| } block_allocator_allocation; |
| |
| |
| typedef struct block_allocator |
| { |
| _mali_osk_lock_t *mutex; |
| block_info * all_blocks; |
| block_info * first_free; |
| u32 base; |
| u32 cpu_usage_adjust; |
| u32 num_blocks; |
| } block_allocator; |
| |
| MALI_STATIC_INLINE u32 get_phys(block_allocator * info, block_info * block); |
| static mali_physical_memory_allocation_result block_allocator_allocate(void* ctx, mali_allocation_engine * engine, mali_memory_allocation * descriptor, u32* offset, mali_physical_memory_allocation * alloc_info); |
| static void block_allocator_release(void * ctx, void * handle); |
| static mali_physical_memory_allocation_result block_allocator_allocate_page_table_block(void * ctx, mali_page_table_block * block); |
| static void block_allocator_release_page_table_block( mali_page_table_block *page_table_block ); |
| static void block_allocator_destroy(mali_physical_memory_allocator * allocator); |
| static u32 block_allocator_stat(mali_physical_memory_allocator * allocator); |
| |
| mali_physical_memory_allocator * mali_block_allocator_create(u32 base_address, u32 cpu_usage_adjust, u32 size, const char *name) |
| { |
| mali_physical_memory_allocator * allocator; |
| block_allocator * info; |
| u32 usable_size; |
| u32 num_blocks; |
| |
| usable_size = size & ~(MALI_BLOCK_SIZE - 1); |
| MALI_DEBUG_PRINT(3, ("Mali block allocator create for region starting at 0x%08X length 0x%08X\n", base_address, size)); |
| MALI_DEBUG_PRINT(4, ("%d usable bytes\n", usable_size)); |
| num_blocks = usable_size / MALI_BLOCK_SIZE; |
| MALI_DEBUG_PRINT(4, ("which becomes %d blocks\n", num_blocks)); |
| |
| if (usable_size == 0) |
| { |
| MALI_DEBUG_PRINT(1, ("Memory block of size %d is unusable\n", size)); |
| return NULL; |
| } |
| |
| allocator = _mali_osk_malloc(sizeof(mali_physical_memory_allocator)); |
| if (NULL != allocator) |
| { |
| info = _mali_osk_malloc(sizeof(block_allocator)); |
| if (NULL != info) |
| { |
| info->mutex = _mali_osk_lock_init( _MALI_OSK_LOCKFLAG_ORDERED, 0, _MALI_OSK_LOCK_ORDER_MEM_INFO); |
| if (NULL != info->mutex) |
| { |
| info->all_blocks = _mali_osk_malloc(sizeof(block_info) * num_blocks); |
| if (NULL != info->all_blocks) |
| { |
| u32 i; |
| info->first_free = NULL; |
| info->num_blocks = num_blocks; |
| |
| info->base = base_address; |
| info->cpu_usage_adjust = cpu_usage_adjust; |
| |
| for ( i = 0; i < num_blocks; i++) |
| { |
| info->all_blocks[i].next = info->first_free; |
| info->first_free = &info->all_blocks[i]; |
| } |
| |
| allocator->allocate = block_allocator_allocate; |
| allocator->allocate_page_table_block = block_allocator_allocate_page_table_block; |
| allocator->destroy = block_allocator_destroy; |
| allocator->stat = block_allocator_stat; |
| allocator->ctx = info; |
| allocator->name = name; |
| |
| return allocator; |
| } |
| _mali_osk_lock_term(info->mutex); |
| } |
| _mali_osk_free(info); |
| } |
| _mali_osk_free(allocator); |
| } |
| |
| return NULL; |
| } |
| |
| static void block_allocator_destroy(mali_physical_memory_allocator * allocator) |
| { |
| block_allocator * info; |
| MALI_DEBUG_ASSERT_POINTER(allocator); |
| MALI_DEBUG_ASSERT_POINTER(allocator->ctx); |
| info = (block_allocator*)allocator->ctx; |
| |
| _mali_osk_free(info->all_blocks); |
| _mali_osk_lock_term(info->mutex); |
| _mali_osk_free(info); |
| _mali_osk_free(allocator); |
| } |
| |
| MALI_STATIC_INLINE u32 get_phys(block_allocator * info, block_info * block) |
| { |
| return info->base + ((block - info->all_blocks) * MALI_BLOCK_SIZE); |
| } |
| |
| static mali_physical_memory_allocation_result block_allocator_allocate(void* ctx, mali_allocation_engine * engine, mali_memory_allocation * descriptor, u32* offset, mali_physical_memory_allocation * alloc_info) |
| { |
| block_allocator * info; |
| u32 left; |
| block_info * last_allocated = NULL; |
| mali_physical_memory_allocation_result result = MALI_MEM_ALLOC_NONE; |
| block_allocator_allocation *ret_allocation; |
| |
| MALI_DEBUG_ASSERT_POINTER(ctx); |
| MALI_DEBUG_ASSERT_POINTER(descriptor); |
| MALI_DEBUG_ASSERT_POINTER(offset); |
| MALI_DEBUG_ASSERT_POINTER(alloc_info); |
| |
| info = (block_allocator*)ctx; |
| left = descriptor->size - *offset; |
| MALI_DEBUG_ASSERT(0 != left); |
| |
| if (_MALI_OSK_ERR_OK != _mali_osk_lock_wait(info->mutex, _MALI_OSK_LOCKMODE_RW)) return MALI_MEM_ALLOC_INTERNAL_FAILURE; |
| |
| ret_allocation = _mali_osk_malloc( sizeof(block_allocator_allocation) ); |
| |
| if ( NULL == ret_allocation ) |
| { |
| /* Failure; try another allocator by returning MALI_MEM_ALLOC_NONE */ |
| _mali_osk_lock_signal(info->mutex, _MALI_OSK_LOCKMODE_RW); |
| return result; |
| } |
| |
| ret_allocation->start_offset = *offset; |
| ret_allocation->mapping_length = 0; |
| |
| while ((left > 0) && (info->first_free)) |
| { |
| block_info * block; |
| u32 phys_addr; |
| u32 padding; |
| u32 current_mapping_size; |
| |
| block = info->first_free; |
| info->first_free = info->first_free->next; |
| block->next = last_allocated; |
| last_allocated = block; |
| |
| phys_addr = get_phys(info, block); |
| |
| padding = *offset & (MALI_BLOCK_SIZE-1); |
| |
| if (MALI_BLOCK_SIZE - padding < left) |
| { |
| current_mapping_size = MALI_BLOCK_SIZE - padding; |
| } |
| else |
| { |
| current_mapping_size = left; |
| } |
| |
| if (_MALI_OSK_ERR_OK != mali_allocation_engine_map_physical(engine, descriptor, *offset, phys_addr + padding, info->cpu_usage_adjust, current_mapping_size)) |
| { |
| MALI_DEBUG_PRINT(1, ("Mapping of physical memory failed\n")); |
| result = MALI_MEM_ALLOC_INTERNAL_FAILURE; |
| mali_allocation_engine_unmap_physical(engine, descriptor, ret_allocation->start_offset, ret_allocation->mapping_length, (_mali_osk_mem_mapregion_flags_t)0); |
| |
| /* release all memory back to the pool */ |
| while (last_allocated) |
| { |
| /* This relinks every block we've just allocated back into the free-list */ |
| block = last_allocated->next; |
| last_allocated->next = info->first_free; |
| info->first_free = last_allocated; |
| last_allocated = block; |
| } |
| |
| break; |
| } |
| |
| *offset += current_mapping_size; |
| left -= current_mapping_size; |
| ret_allocation->mapping_length += current_mapping_size; |
| } |
| |
| _mali_osk_lock_signal(info->mutex, _MALI_OSK_LOCKMODE_RW); |
| |
| if (last_allocated) |
| { |
| if (left) result = MALI_MEM_ALLOC_PARTIAL; |
| else result = MALI_MEM_ALLOC_FINISHED; |
| |
| /* Record all the information about this allocation */ |
| ret_allocation->last_allocated = last_allocated; |
| ret_allocation->engine = engine; |
| ret_allocation->descriptor = descriptor; |
| |
| alloc_info->ctx = info; |
| alloc_info->handle = ret_allocation; |
| alloc_info->release = block_allocator_release; |
| } |
| else |
| { |
| /* Free the allocation information - nothing to be passed back */ |
| _mali_osk_free( ret_allocation ); |
| } |
| |
| return result; |
| } |
| |
| static void block_allocator_release(void * ctx, void * handle) |
| { |
| block_allocator * info; |
| block_info * block, * next; |
| block_allocator_allocation *allocation; |
| |
| MALI_DEBUG_ASSERT_POINTER(ctx); |
| MALI_DEBUG_ASSERT_POINTER(handle); |
| |
| info = (block_allocator*)ctx; |
| allocation = (block_allocator_allocation*)handle; |
| block = allocation->last_allocated; |
| |
| MALI_DEBUG_ASSERT_POINTER(block); |
| |
| if (_MALI_OSK_ERR_OK != _mali_osk_lock_wait(info->mutex, _MALI_OSK_LOCKMODE_RW)) |
| { |
| MALI_DEBUG_PRINT(1, ("allocator release: Failed to get mutex\n")); |
| return; |
| } |
| |
| /* unmap */ |
| mali_allocation_engine_unmap_physical(allocation->engine, allocation->descriptor, allocation->start_offset, allocation->mapping_length, (_mali_osk_mem_mapregion_flags_t)0); |
| |
| while (block) |
| { |
| MALI_DEBUG_ASSERT(!((block < info->all_blocks) || (block > (info->all_blocks + info->num_blocks)))); |
| |
| next = block->next; |
| |
| /* relink into free-list */ |
| block->next = info->first_free; |
| info->first_free = block; |
| |
| /* advance the loop */ |
| block = next; |
| } |
| |
| _mali_osk_lock_signal(info->mutex, _MALI_OSK_LOCKMODE_RW); |
| |
| _mali_osk_free( allocation ); |
| } |
| |
| |
| static mali_physical_memory_allocation_result block_allocator_allocate_page_table_block(void * ctx, mali_page_table_block * block) |
| { |
| block_allocator * info; |
| mali_physical_memory_allocation_result result = MALI_MEM_ALLOC_INTERNAL_FAILURE; |
| |
| MALI_DEBUG_ASSERT_POINTER(ctx); |
| MALI_DEBUG_ASSERT_POINTER(block); |
| info = (block_allocator*)ctx; |
| |
| if (_MALI_OSK_ERR_OK != _mali_osk_lock_wait(info->mutex, _MALI_OSK_LOCKMODE_RW)) return MALI_MEM_ALLOC_INTERNAL_FAILURE; |
| |
| if (NULL != info->first_free) |
| { |
| void * virt; |
| u32 phys; |
| u32 size; |
| block_info * alloc; |
| alloc = info->first_free; |
| |
| phys = get_phys(info, alloc); /* Does not modify info or alloc */ |
| size = MALI_BLOCK_SIZE; /* Must be multiple of MALI_MMU_PAGE_SIZE */ |
| virt = _mali_osk_mem_mapioregion( phys, size, "Mali block allocator page tables" ); |
| |
| /* Failure of _mali_osk_mem_mapioregion will result in MALI_MEM_ALLOC_INTERNAL_FAILURE, |
| * because it's unlikely another allocator will be able to map in. */ |
| |
| if ( NULL != virt ) |
| { |
| block->ctx = info; /* same as incoming ctx */ |
| block->handle = alloc; |
| block->phys_base = phys; |
| block->size = size; |
| block->release = block_allocator_release_page_table_block; |
| block->mapping = virt; |
| |
| info->first_free = alloc->next; |
| |
| alloc->next = NULL; /* Could potentially link many blocks together instead */ |
| |
| _mali_osk_memset(block->mapping, 0, size); |
| |
| result = MALI_MEM_ALLOC_FINISHED; |
| } |
| } |
| else result = MALI_MEM_ALLOC_NONE; |
| |
| _mali_osk_lock_signal(info->mutex, _MALI_OSK_LOCKMODE_RW); |
| |
| return result; |
| } |
| |
| |
| static void block_allocator_release_page_table_block( mali_page_table_block *page_table_block ) |
| { |
| block_allocator * info; |
| block_info * block, * next; |
| |
| MALI_DEBUG_ASSERT_POINTER( page_table_block ); |
| |
| info = (block_allocator*)page_table_block->ctx; |
| block = (block_info*)page_table_block->handle; |
| |
| MALI_DEBUG_ASSERT_POINTER(info); |
| MALI_DEBUG_ASSERT_POINTER(block); |
| |
| |
| if (_MALI_OSK_ERR_OK != _mali_osk_lock_wait(info->mutex, _MALI_OSK_LOCKMODE_RW)) |
| { |
| MALI_DEBUG_PRINT(1, ("allocator release: Failed to get mutex\n")); |
| return; |
| } |
| |
| /* Unmap all the physical memory at once */ |
| _mali_osk_mem_unmapioregion( page_table_block->phys_base, page_table_block->size, page_table_block->mapping ); |
| |
| /** @note This loop handles the case where more than one block_info was linked. |
| * Probably unnecessary for page table block releasing. */ |
| while (block) |
| { |
| next = block->next; |
| |
| MALI_DEBUG_ASSERT(!((block < info->all_blocks) || (block > (info->all_blocks + info->num_blocks)))); |
| |
| block->next = info->first_free; |
| info->first_free = block; |
| |
| block = next; |
| } |
| |
| _mali_osk_lock_signal(info->mutex, _MALI_OSK_LOCKMODE_RW); |
| } |
| |
| static u32 block_allocator_stat(mali_physical_memory_allocator * allocator) |
| { |
| block_allocator * info; |
| block_info *block; |
| u32 free_blocks = 0; |
| |
| MALI_DEBUG_ASSERT_POINTER(allocator); |
| |
| info = (block_allocator*)allocator->ctx; |
| block = info->first_free; |
| |
| while(block) |
| { |
| free_blocks++; |
| block = block->next; |
| } |
| return (info->num_blocks - free_blocks) * MALI_BLOCK_SIZE; |
| } |