| /* |
| * Copyright (C) 2010-2016 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_memory.h" |
| #include "mali_memory_block_alloc.h" |
| #include "mali_osk.h" |
| #include <linux/mutex.h> |
| |
| |
| static mali_block_allocator *mali_mem_block_gobal_allocator = NULL; |
| |
| unsigned long _mali_blk_item_get_phy_addr(mali_block_item *item) |
| { |
| return (item->phy_addr & ~(MALI_BLOCK_REF_MASK)); |
| } |
| |
| |
| unsigned long _mali_blk_item_get_pfn(mali_block_item *item) |
| { |
| return (item->phy_addr / MALI_BLOCK_SIZE); |
| } |
| |
| |
| u32 mali_mem_block_get_ref_count(mali_page_node *node) |
| { |
| MALI_DEBUG_ASSERT(node->type == MALI_PAGE_NODE_BLOCK); |
| return (node->blk_it->phy_addr & MALI_BLOCK_REF_MASK); |
| } |
| |
| |
| /* Increase the refence count |
| * It not atomic, so it need to get sp_lock before call this function |
| */ |
| |
| u32 mali_mem_block_add_ref(mali_page_node *node) |
| { |
| MALI_DEBUG_ASSERT(node->type == MALI_PAGE_NODE_BLOCK); |
| MALI_DEBUG_ASSERT(mali_mem_block_get_ref_count(node) < MALI_BLOCK_MAX_REF_COUNT); |
| return (node->blk_it->phy_addr++ & MALI_BLOCK_REF_MASK); |
| } |
| |
| /* Decase the refence count |
| * It not atomic, so it need to get sp_lock before call this function |
| */ |
| u32 mali_mem_block_dec_ref(mali_page_node *node) |
| { |
| MALI_DEBUG_ASSERT(node->type == MALI_PAGE_NODE_BLOCK); |
| MALI_DEBUG_ASSERT(mali_mem_block_get_ref_count(node) > 0); |
| return (node->blk_it->phy_addr-- & MALI_BLOCK_REF_MASK); |
| } |
| |
| |
| static mali_block_allocator *mali_mem_block_allocator_create(u32 base_address, u32 size) |
| { |
| mali_block_allocator *info; |
| u32 usable_size; |
| u32 num_blocks; |
| mali_page_node *m_node; |
| mali_block_item *mali_blk_items = NULL; |
| int i = 0; |
| |
| 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; |
| } |
| |
| info = _mali_osk_calloc(1, sizeof(mali_block_allocator)); |
| if (NULL != info) { |
| INIT_LIST_HEAD(&info->free); |
| spin_lock_init(&info->sp_lock); |
| info->total_num = num_blocks; |
| mali_blk_items = _mali_osk_calloc(1, sizeof(mali_block_item) * num_blocks); |
| |
| if (mali_blk_items) { |
| info->items = mali_blk_items; |
| /* add blocks(4k size) to free list*/ |
| for (i = 0 ; i < num_blocks ; i++) { |
| /* add block information*/ |
| mali_blk_items[i].phy_addr = base_address + (i * MALI_BLOCK_SIZE); |
| /* add to free list */ |
| m_node = _mali_page_node_allocate(MALI_PAGE_NODE_BLOCK); |
| if (m_node == NULL) |
| goto fail; |
| _mali_page_node_add_block_item(m_node, &(mali_blk_items[i])); |
| list_add_tail(&m_node->list, &info->free); |
| atomic_add(1, &info->free_num); |
| } |
| return info; |
| } |
| } |
| fail: |
| mali_mem_block_allocator_destroy(); |
| return NULL; |
| } |
| |
| void mali_mem_block_allocator_destroy(void) |
| { |
| struct mali_page_node *m_page, *m_tmp; |
| mali_block_allocator *info = mali_mem_block_gobal_allocator; |
| MALI_DEBUG_ASSERT_POINTER(info); |
| MALI_DEBUG_PRINT(4, ("Memory block destroy !\n")); |
| |
| if (NULL == info) |
| return; |
| |
| list_for_each_entry_safe(m_page, m_tmp , &info->free, list) { |
| MALI_DEBUG_ASSERT(m_page->type == MALI_PAGE_NODE_BLOCK); |
| list_del(&m_page->list); |
| kfree(m_page); |
| } |
| |
| _mali_osk_free(info->items); |
| _mali_osk_free(info); |
| } |
| |
| u32 mali_mem_block_release(mali_mem_backend *mem_bkend) |
| { |
| mali_mem_allocation *alloc = mem_bkend->mali_allocation; |
| u32 free_pages_nr = 0; |
| MALI_DEBUG_ASSERT(mem_bkend->type == MALI_MEM_BLOCK); |
| |
| /* Unmap the memory from the mali virtual address space. */ |
| mali_mem_block_mali_unmap(alloc); |
| mutex_lock(&mem_bkend->mutex); |
| free_pages_nr = mali_mem_block_free(&mem_bkend->block_mem); |
| mutex_unlock(&mem_bkend->mutex); |
| return free_pages_nr; |
| } |
| |
| |
| int mali_mem_block_alloc(mali_mem_block_mem *block_mem, u32 size) |
| { |
| struct mali_page_node *m_page, *m_tmp; |
| size_t page_count = PAGE_ALIGN(size) / _MALI_OSK_MALI_PAGE_SIZE; |
| mali_block_allocator *info = mali_mem_block_gobal_allocator; |
| MALI_DEBUG_ASSERT_POINTER(info); |
| |
| MALI_DEBUG_PRINT(4, ("BLOCK Mem: Allocate size = 0x%x\n", size)); |
| /*do some init */ |
| INIT_LIST_HEAD(&block_mem->pfns); |
| |
| spin_lock(&info->sp_lock); |
| /*check if have enough space*/ |
| if (atomic_read(&info->free_num) > page_count) { |
| list_for_each_entry_safe(m_page, m_tmp , &info->free, list) { |
| if (page_count > 0) { |
| MALI_DEBUG_ASSERT(m_page->type == MALI_PAGE_NODE_BLOCK); |
| MALI_DEBUG_ASSERT(mali_mem_block_get_ref_count(m_page) == 0); |
| list_move(&m_page->list, &block_mem->pfns); |
| block_mem->count++; |
| atomic_dec(&info->free_num); |
| _mali_page_node_ref(m_page); |
| } else { |
| break; |
| } |
| page_count--; |
| } |
| } else { |
| /* can't allocate from BLOCK memory*/ |
| spin_unlock(&info->sp_lock); |
| return -1; |
| } |
| |
| spin_unlock(&info->sp_lock); |
| return 0; |
| } |
| |
| u32 mali_mem_block_free(mali_mem_block_mem *block_mem) |
| { |
| u32 free_pages_nr = 0; |
| |
| free_pages_nr = mali_mem_block_free_list(&block_mem->pfns); |
| MALI_DEBUG_PRINT(4, ("BLOCK Mem free : allocated size = 0x%x, free size = 0x%x\n", block_mem->count * _MALI_OSK_MALI_PAGE_SIZE, |
| free_pages_nr * _MALI_OSK_MALI_PAGE_SIZE)); |
| block_mem->count = 0; |
| MALI_DEBUG_ASSERT(list_empty(&block_mem->pfns)); |
| |
| return free_pages_nr; |
| } |
| |
| |
| u32 mali_mem_block_free_list(struct list_head *list) |
| { |
| struct mali_page_node *m_page, *m_tmp; |
| mali_block_allocator *info = mali_mem_block_gobal_allocator; |
| u32 free_pages_nr = 0; |
| |
| if (info) { |
| spin_lock(&info->sp_lock); |
| list_for_each_entry_safe(m_page, m_tmp , list, list) { |
| if (1 == _mali_page_node_get_ref_count(m_page)) { |
| free_pages_nr++; |
| } |
| mali_mem_block_free_node(m_page); |
| } |
| spin_unlock(&info->sp_lock); |
| } |
| return free_pages_nr; |
| } |
| |
| /* free the node,*/ |
| void mali_mem_block_free_node(struct mali_page_node *node) |
| { |
| mali_block_allocator *info = mali_mem_block_gobal_allocator; |
| |
| /* only handle BLOCK node */ |
| if (node->type == MALI_PAGE_NODE_BLOCK && info) { |
| /*Need to make this atomic?*/ |
| if (1 == _mali_page_node_get_ref_count(node)) { |
| /*Move to free list*/ |
| _mali_page_node_unref(node); |
| list_move_tail(&node->list, &info->free); |
| atomic_add(1, &info->free_num); |
| } else { |
| _mali_page_node_unref(node); |
| list_del(&node->list); |
| kfree(node); |
| } |
| } |
| } |
| |
| /* unref the node, but not free it */ |
| _mali_osk_errcode_t mali_mem_block_unref_node(struct mali_page_node *node) |
| { |
| mali_block_allocator *info = mali_mem_block_gobal_allocator; |
| mali_page_node *new_node; |
| |
| /* only handle BLOCK node */ |
| if (node->type == MALI_PAGE_NODE_BLOCK && info) { |
| /*Need to make this atomic?*/ |
| if (1 == _mali_page_node_get_ref_count(node)) { |
| /* allocate a new node, Add to free list, keep the old node*/ |
| _mali_page_node_unref(node); |
| new_node = _mali_page_node_allocate(MALI_PAGE_NODE_BLOCK); |
| if (new_node) { |
| memcpy(new_node, node, sizeof(mali_page_node)); |
| list_add(&new_node->list, &info->free); |
| atomic_add(1, &info->free_num); |
| } else |
| return _MALI_OSK_ERR_FAULT; |
| |
| } else { |
| _mali_page_node_unref(node); |
| } |
| } |
| return _MALI_OSK_ERR_OK; |
| } |
| |
| |
| int mali_mem_block_mali_map(mali_mem_block_mem *block_mem, struct mali_session_data *session, u32 vaddr, u32 props) |
| { |
| struct mali_page_directory *pagedir = session->page_directory; |
| struct mali_page_node *m_page; |
| dma_addr_t phys; |
| u32 virt = vaddr; |
| u32 prop = props; |
| |
| list_for_each_entry(m_page, &block_mem->pfns, list) { |
| MALI_DEBUG_ASSERT(m_page->type == MALI_PAGE_NODE_BLOCK); |
| phys = _mali_page_node_get_dma_addr(m_page); |
| #if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT) |
| /* Verify that the "physical" address is 32-bit and |
| * usable for Mali, when on a system with bus addresses |
| * wider than 32-bit. */ |
| MALI_DEBUG_ASSERT(0 == (phys >> 32)); |
| #endif |
| mali_mmu_pagedir_update(pagedir, virt, (mali_dma_addr)phys, MALI_MMU_PAGE_SIZE, prop); |
| virt += MALI_MMU_PAGE_SIZE; |
| } |
| |
| return 0; |
| } |
| |
| void mali_mem_block_mali_unmap(mali_mem_allocation *alloc) |
| { |
| struct mali_session_data *session; |
| MALI_DEBUG_ASSERT_POINTER(alloc); |
| session = alloc->session; |
| MALI_DEBUG_ASSERT_POINTER(session); |
| |
| mali_session_memory_lock(session); |
| mali_mem_mali_map_free(session, alloc->psize, alloc->mali_vma_node.vm_node.start, |
| alloc->flags); |
| mali_session_memory_unlock(session); |
| } |
| |
| |
| int mali_mem_block_cpu_map(mali_mem_backend *mem_bkend, struct vm_area_struct *vma) |
| { |
| int ret; |
| mali_mem_block_mem *block_mem = &mem_bkend->block_mem; |
| unsigned long addr = vma->vm_start; |
| struct mali_page_node *m_page; |
| MALI_DEBUG_ASSERT(mem_bkend->type == MALI_MEM_BLOCK); |
| |
| list_for_each_entry(m_page, &block_mem->pfns, list) { |
| MALI_DEBUG_ASSERT(m_page->type == MALI_PAGE_NODE_BLOCK); |
| ret = vm_insert_pfn(vma, addr, _mali_page_node_get_pfn(m_page)); |
| |
| if (unlikely(0 != ret)) { |
| return -EFAULT; |
| } |
| addr += _MALI_OSK_MALI_PAGE_SIZE; |
| |
| } |
| |
| return 0; |
| } |
| |
| |
| _mali_osk_errcode_t mali_memory_core_resource_dedicated_memory(u32 start, u32 size) |
| { |
| mali_block_allocator *allocator; |
| |
| /* Do the low level linux operation first */ |
| |
| /* Request ownership of the memory */ |
| if (_MALI_OSK_ERR_OK != _mali_osk_mem_reqregion(start, size, "Dedicated Mali GPU memory")) { |
| MALI_DEBUG_PRINT(1, ("Failed to request memory region for frame buffer (0x%08X - 0x%08X)\n", start, start + size - 1)); |
| return _MALI_OSK_ERR_FAULT; |
| } |
| |
| /* Create generic block allocator object to handle it */ |
| allocator = mali_mem_block_allocator_create(start, size); |
| |
| if (NULL == allocator) { |
| MALI_DEBUG_PRINT(1, ("Memory bank registration failed\n")); |
| _mali_osk_mem_unreqregion(start, size); |
| MALI_ERROR(_MALI_OSK_ERR_FAULT); |
| } |
| |
| mali_mem_block_gobal_allocator = (mali_block_allocator *)allocator; |
| |
| return _MALI_OSK_ERR_OK; |
| } |
| |
| mali_bool mali_memory_have_dedicated_memory(void) |
| { |
| return mali_mem_block_gobal_allocator ? MALI_TRUE : MALI_FALSE; |
| } |
| |
| u32 mali_mem_block_allocator_stat(void) |
| { |
| mali_block_allocator *allocator = mali_mem_block_gobal_allocator; |
| MALI_DEBUG_ASSERT_POINTER(allocator); |
| |
| |
| return (allocator->total_num - atomic_read(&allocator->free_num)) * _MALI_OSK_MALI_PAGE_SIZE; |
| } |