/* * Copyright (C) 2013-2017 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 #include #include #include #include #include #include #include #ifdef CONFIG_ARM #include #endif #include #include "mali_memory.h" #include "mali_kernel_common.h" #include "mali_uk_types.h" #include "mali_osk.h" #include "mali_kernel_linux.h" #include "mali_memory_cow.h" #include "mali_memory_block_alloc.h" #include "mali_memory_swap_alloc.h" /** * allocate pages for COW backend and flush cache */ static struct page *mali_mem_cow_alloc_page(void) { mali_mem_os_mem os_mem; struct mali_page_node *node; struct page *new_page; int ret = 0; /* allocate pages from os mem */ ret = mali_mem_os_alloc_pages(&os_mem, _MALI_OSK_MALI_PAGE_SIZE); if (ret) { return NULL; } MALI_DEBUG_ASSERT(1 == os_mem.count); node = _MALI_OSK_CONTAINER_OF(os_mem.pages.next, struct mali_page_node, list); new_page = node->page; node->page = NULL; list_del(&node->list); kfree(node); return new_page; } static struct list_head *_mali_memory_cow_get_node_list(mali_mem_backend *target_bk, u32 target_offset, u32 target_size) { MALI_DEBUG_ASSERT(MALI_MEM_OS == target_bk->type || MALI_MEM_COW == target_bk->type || MALI_MEM_BLOCK == target_bk->type || MALI_MEM_SWAP == target_bk->type); if (MALI_MEM_OS == target_bk->type) { MALI_DEBUG_ASSERT(&target_bk->os_mem); MALI_DEBUG_ASSERT(((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE) <= target_bk->os_mem.count); return &target_bk->os_mem.pages; } else if (MALI_MEM_COW == target_bk->type) { MALI_DEBUG_ASSERT(&target_bk->cow_mem); MALI_DEBUG_ASSERT(((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE) <= target_bk->cow_mem.count); return &target_bk->cow_mem.pages; } else if (MALI_MEM_BLOCK == target_bk->type) { MALI_DEBUG_ASSERT(&target_bk->block_mem); MALI_DEBUG_ASSERT(((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE) <= target_bk->block_mem.count); return &target_bk->block_mem.pfns; } else if (MALI_MEM_SWAP == target_bk->type) { MALI_DEBUG_ASSERT(&target_bk->swap_mem); MALI_DEBUG_ASSERT(((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE) <= target_bk->swap_mem.count); return &target_bk->swap_mem.pages; } return NULL; } /** * Do COW for os memory - support do COW for memory from bank memory * The range_start/size can be zero, which means it will call cow_modify_range * latter. * This function allocate new pages for COW backend from os mem for a modified range * It will keep the page which not in the modified range and Add ref to it * * @target_bk - target allocation's backend(the allocation need to do COW) * @target_offset - the offset in target allocation to do COW(for support COW a memory allocated from memory_bank, 4K align) * @target_size - size of target allocation to do COW (for support memory bank) * @backend -COW backend * @range_start - offset of modified range (4K align) * @range_size - size of modified range */ _mali_osk_errcode_t mali_memory_cow_os_memory(mali_mem_backend *target_bk, u32 target_offset, u32 target_size, mali_mem_backend *backend, u32 range_start, u32 range_size) { mali_mem_cow *cow = &backend->cow_mem; struct mali_page_node *m_page, *m_tmp, *page_node; int target_page = 0; struct page *new_page; struct list_head *pages = NULL; pages = _mali_memory_cow_get_node_list(target_bk, target_offset, target_size); if (NULL == pages) { MALI_DEBUG_PRINT_ERROR(("No memory page need to cow ! \n")); return _MALI_OSK_ERR_FAULT; } MALI_DEBUG_ASSERT(0 == cow->count); INIT_LIST_HEAD(&cow->pages); mutex_lock(&target_bk->mutex); list_for_each_entry_safe(m_page, m_tmp, pages, list) { /* add page from (target_offset,target_offset+size) to cow backend */ if ((target_page >= target_offset / _MALI_OSK_MALI_PAGE_SIZE) && (target_page < ((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE))) { /* allocate a new page node, alway use OS memory for COW */ page_node = _mali_page_node_allocate(MALI_PAGE_NODE_OS); if (NULL == page_node) { mutex_unlock(&target_bk->mutex); goto error; } INIT_LIST_HEAD(&page_node->list); /* check if in the modified range*/ if ((cow->count >= range_start / _MALI_OSK_MALI_PAGE_SIZE) && (cow->count < (range_start + range_size) / _MALI_OSK_MALI_PAGE_SIZE)) { /* need to allocate a new page */ /* To simplify the case, All COW memory is allocated from os memory ?*/ new_page = mali_mem_cow_alloc_page(); if (NULL == new_page) { kfree(page_node); mutex_unlock(&target_bk->mutex); goto error; } _mali_page_node_add_page(page_node, new_page); } else { /*Add Block memory case*/ if (m_page->type != MALI_PAGE_NODE_BLOCK) { _mali_page_node_add_page(page_node, m_page->page); } else { page_node->type = MALI_PAGE_NODE_BLOCK; _mali_page_node_add_block_item(page_node, m_page->blk_it); } /* add ref to this page */ _mali_page_node_ref(m_page); } /* add it to COW backend page list */ list_add_tail(&page_node->list, &cow->pages); cow->count++; } target_page++; } mutex_unlock(&target_bk->mutex); return _MALI_OSK_ERR_OK; error: mali_mem_cow_release(backend, MALI_FALSE); return _MALI_OSK_ERR_FAULT; } _mali_osk_errcode_t mali_memory_cow_swap_memory(mali_mem_backend *target_bk, u32 target_offset, u32 target_size, mali_mem_backend *backend, u32 range_start, u32 range_size) { mali_mem_cow *cow = &backend->cow_mem; struct mali_page_node *m_page, *m_tmp, *page_node; int target_page = 0; struct mali_swap_item *swap_item; struct list_head *pages = NULL; pages = _mali_memory_cow_get_node_list(target_bk, target_offset, target_size); if (NULL == pages) { MALI_DEBUG_PRINT_ERROR(("No swap memory page need to cow ! \n")); return _MALI_OSK_ERR_FAULT; } MALI_DEBUG_ASSERT(0 == cow->count); INIT_LIST_HEAD(&cow->pages); mutex_lock(&target_bk->mutex); backend->flags |= MALI_MEM_BACKEND_FLAG_UNSWAPPED_IN; list_for_each_entry_safe(m_page, m_tmp, pages, list) { /* add page from (target_offset,target_offset+size) to cow backend */ if ((target_page >= target_offset / _MALI_OSK_MALI_PAGE_SIZE) && (target_page < ((target_size + target_offset) / _MALI_OSK_MALI_PAGE_SIZE))) { /* allocate a new page node, use swap memory for COW memory swap cowed flag. */ page_node = _mali_page_node_allocate(MALI_PAGE_NODE_SWAP); if (NULL == page_node) { mutex_unlock(&target_bk->mutex); goto error; } /* check if in the modified range*/ if ((cow->count >= range_start / _MALI_OSK_MALI_PAGE_SIZE) && (cow->count < (range_start + range_size) / _MALI_OSK_MALI_PAGE_SIZE)) { /* need to allocate a new page */ /* To simplify the case, All COW memory is allocated from os memory ?*/ swap_item = mali_mem_swap_alloc_swap_item(); if (NULL == swap_item) { kfree(page_node); mutex_unlock(&target_bk->mutex); goto error; } swap_item->idx = mali_mem_swap_idx_alloc(); if (_MALI_OSK_BITMAP_INVALIDATE_INDEX == swap_item->idx) { MALI_DEBUG_PRINT(1, ("Failed to allocate swap index in swap CoW.\n")); kfree(page_node); kfree(swap_item); mutex_unlock(&target_bk->mutex); goto error; } _mali_page_node_add_swap_item(page_node, swap_item); } else { _mali_page_node_add_swap_item(page_node, m_page->swap_it); /* add ref to this page */ _mali_page_node_ref(m_page); } list_add_tail(&page_node->list, &cow->pages); cow->count++; } target_page++; } mutex_unlock(&target_bk->mutex); return _MALI_OSK_ERR_OK; error: mali_mem_swap_release(backend, MALI_FALSE); return _MALI_OSK_ERR_FAULT; } _mali_osk_errcode_t _mali_mem_put_page_node(mali_page_node *node) { if (node->type == MALI_PAGE_NODE_OS) { return mali_mem_os_put_page(node->page); } else if (node->type == MALI_PAGE_NODE_BLOCK) { return mali_mem_block_unref_node(node); } else if (node->type == MALI_PAGE_NODE_SWAP) { return _mali_mem_swap_put_page_node(node); } else MALI_DEBUG_ASSERT(0); return _MALI_OSK_ERR_FAULT; } /** * Modify a range of a exist COW backend * @backend -COW backend * @range_start - offset of modified range (4K align) * @range_size - size of modified range(in byte) */ _mali_osk_errcode_t mali_memory_cow_modify_range(mali_mem_backend *backend, u32 range_start, u32 range_size) { mali_mem_allocation *alloc = NULL; struct mali_session_data *session; mali_mem_cow *cow = &backend->cow_mem; struct mali_page_node *m_page, *m_tmp; LIST_HEAD(pages); struct page *new_page; u32 count = 0; s32 change_pages_nr = 0; _mali_osk_errcode_t ret = _MALI_OSK_ERR_OK; if (range_start % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); if (range_size % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); alloc = backend->mali_allocation; MALI_DEBUG_ASSERT_POINTER(alloc); session = alloc->session; MALI_DEBUG_ASSERT_POINTER(session); MALI_DEBUG_ASSERT(MALI_MEM_COW == backend->type); MALI_DEBUG_ASSERT(((range_start + range_size) / _MALI_OSK_MALI_PAGE_SIZE) <= cow->count); mutex_lock(&backend->mutex); /* free pages*/ list_for_each_entry_safe(m_page, m_tmp, &cow->pages, list) { /* check if in the modified range*/ if ((count >= range_start / _MALI_OSK_MALI_PAGE_SIZE) && (count < (range_start + range_size) / _MALI_OSK_MALI_PAGE_SIZE)) { if (MALI_PAGE_NODE_SWAP != m_page->type) { new_page = mali_mem_cow_alloc_page(); if (NULL == new_page) { goto error; } if (1 != _mali_page_node_get_ref_count(m_page)) change_pages_nr++; /* unref old page*/ _mali_osk_mutex_wait(session->cow_lock); if (_mali_mem_put_page_node(m_page)) { __free_page(new_page); _mali_osk_mutex_signal(session->cow_lock); goto error; } _mali_osk_mutex_signal(session->cow_lock); /* add new page*/ /* always use OS for COW*/ m_page->type = MALI_PAGE_NODE_OS; _mali_page_node_add_page(m_page, new_page); } else { struct mali_swap_item *swap_item; swap_item = mali_mem_swap_alloc_swap_item(); if (NULL == swap_item) { goto error; } swap_item->idx = mali_mem_swap_idx_alloc(); if (_MALI_OSK_BITMAP_INVALIDATE_INDEX == swap_item->idx) { MALI_DEBUG_PRINT(1, ("Failed to allocate swap index in swap CoW modify range.\n")); kfree(swap_item); goto error; } if (1 != _mali_page_node_get_ref_count(m_page)) { change_pages_nr++; } if (_mali_mem_put_page_node(m_page)) { mali_mem_swap_free_swap_item(swap_item); goto error; } _mali_page_node_add_swap_item(m_page, swap_item); } } count++; } cow->change_pages_nr = change_pages_nr; MALI_DEBUG_ASSERT(MALI_MEM_COW == alloc->type); /* ZAP cpu mapping(modified range), and do cpu mapping here if need */ if (NULL != alloc->cpu_mapping.vma) { MALI_DEBUG_ASSERT(0 != alloc->backend_handle); MALI_DEBUG_ASSERT(NULL != alloc->cpu_mapping.vma); MALI_DEBUG_ASSERT(alloc->cpu_mapping.vma->vm_end - alloc->cpu_mapping.vma->vm_start >= range_size); if (MALI_MEM_BACKEND_FLAG_SWAP_COWED != (backend->flags & MALI_MEM_BACKEND_FLAG_SWAP_COWED)) { zap_vma_ptes(alloc->cpu_mapping.vma, alloc->cpu_mapping.vma->vm_start + range_start, range_size); ret = mali_mem_cow_cpu_map_pages_locked(backend, alloc->cpu_mapping.vma, alloc->cpu_mapping.vma->vm_start + range_start, range_size / _MALI_OSK_MALI_PAGE_SIZE); if (unlikely(ret != _MALI_OSK_ERR_OK)) { MALI_DEBUG_PRINT(2, ("mali_memory_cow_modify_range: cpu mapping failed !\n")); ret = _MALI_OSK_ERR_FAULT; } } else { /* used to trigger page fault for swappable cowed memory. */ alloc->cpu_mapping.vma->vm_flags |= VM_PFNMAP; alloc->cpu_mapping.vma->vm_flags |= VM_MIXEDMAP; zap_vma_ptes(alloc->cpu_mapping.vma, alloc->cpu_mapping.vma->vm_start + range_start, range_size); /* delete this flag to let swappble is ummapped regard to stauct page not page frame. */ alloc->cpu_mapping.vma->vm_flags &= ~VM_PFNMAP; alloc->cpu_mapping.vma->vm_flags &= ~VM_MIXEDMAP; } } error: mutex_unlock(&backend->mutex); return ret; } /** * Allocate pages for COW backend * @alloc -allocation for COW allocation * @target_bk - target allocation's backend(the allocation need to do COW) * @target_offset - the offset in target allocation to do COW(for support COW a memory allocated from memory_bank, 4K align) * @target_size - size of target allocation to do COW (for support memory bank)(in byte) * @backend -COW backend * @range_start - offset of modified range (4K align) * @range_size - size of modified range(in byte) */ _mali_osk_errcode_t mali_memory_do_cow(mali_mem_backend *target_bk, u32 target_offset, u32 target_size, mali_mem_backend *backend, u32 range_start, u32 range_size) { struct mali_session_data *session = backend->mali_allocation->session; MALI_CHECK_NON_NULL(session, _MALI_OSK_ERR_INVALID_ARGS); /* size & offset must be a multiple of the system page size */ if (target_size % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); if (range_size % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); if (target_offset % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); if (range_start % _MALI_OSK_MALI_PAGE_SIZE) MALI_ERROR(_MALI_OSK_ERR_INVALID_ARGS); /* check backend type */ MALI_DEBUG_ASSERT(MALI_MEM_COW == backend->type); switch (target_bk->type) { case MALI_MEM_OS: case MALI_MEM_BLOCK: return mali_memory_cow_os_memory(target_bk, target_offset, target_size, backend, range_start, range_size); break; case MALI_MEM_COW: if (backend->flags & MALI_MEM_BACKEND_FLAG_SWAP_COWED) { return mali_memory_cow_swap_memory(target_bk, target_offset, target_size, backend, range_start, range_size); } else { return mali_memory_cow_os_memory(target_bk, target_offset, target_size, backend, range_start, range_size); } break; case MALI_MEM_SWAP: return mali_memory_cow_swap_memory(target_bk, target_offset, target_size, backend, range_start, range_size); break; case MALI_MEM_EXTERNAL: /*NOT support yet*/ MALI_DEBUG_PRINT_ERROR(("External physical memory not supported ! \n")); return _MALI_OSK_ERR_UNSUPPORTED; break; case MALI_MEM_DMA_BUF: /*NOT support yet*/ MALI_DEBUG_PRINT_ERROR(("DMA buffer not supported ! \n")); return _MALI_OSK_ERR_UNSUPPORTED; break; case MALI_MEM_UMP: /*NOT support yet*/ MALI_DEBUG_PRINT_ERROR(("UMP buffer not supported ! \n")); return _MALI_OSK_ERR_UNSUPPORTED; break; default: /*Not support yet*/ MALI_DEBUG_PRINT_ERROR(("Invalid memory type not supported ! \n")); return _MALI_OSK_ERR_UNSUPPORTED; break; } return _MALI_OSK_ERR_OK; } /** * Map COW backend memory to mali * Support OS/BLOCK for mali_page_node */ int mali_mem_cow_mali_map(mali_mem_backend *mem_bkend, u32 range_start, u32 range_size) { mali_mem_allocation *cow_alloc; struct mali_page_node *m_page; struct mali_session_data *session; struct mali_page_directory *pagedir; u32 virt, start; cow_alloc = mem_bkend->mali_allocation; virt = cow_alloc->mali_vma_node.vm_node.start; start = virt; MALI_DEBUG_ASSERT_POINTER(mem_bkend); MALI_DEBUG_ASSERT(MALI_MEM_COW == mem_bkend->type); MALI_DEBUG_ASSERT_POINTER(cow_alloc); session = cow_alloc->session; pagedir = session->page_directory; MALI_CHECK_NON_NULL(session, _MALI_OSK_ERR_INVALID_ARGS); list_for_each_entry(m_page, &mem_bkend->cow_mem.pages, list) { if ((virt - start >= range_start) && (virt - start < range_start + range_size)) { dma_addr_t phys = _mali_page_node_get_dma_addr(m_page); #if defined(CONFIG_ARCH_DMA_ADDR_T_64BIT) MALI_DEBUG_ASSERT(0 == (phys >> 32)); #endif mali_mmu_pagedir_update(pagedir, virt, (mali_dma_addr)phys, MALI_MMU_PAGE_SIZE, MALI_MMU_FLAGS_DEFAULT); } virt += MALI_MMU_PAGE_SIZE; } return 0; } /** * Map COW backend to cpu * support OS/BLOCK memory */ int mali_mem_cow_cpu_map(mali_mem_backend *mem_bkend, struct vm_area_struct *vma) { mali_mem_cow *cow = &mem_bkend->cow_mem; struct mali_page_node *m_page; int ret; unsigned long addr = vma->vm_start; MALI_DEBUG_ASSERT(mem_bkend->type == MALI_MEM_COW); list_for_each_entry(m_page, &cow->pages, list) { /* We should use vm_insert_page, but it does a dcache * flush which makes it way slower than remap_pfn_range or vm_insert_pfn. ret = vm_insert_page(vma, addr, page); */ ret = vm_insert_pfn(vma, addr, _mali_page_node_get_pfn(m_page)); if (unlikely(0 != ret)) { return ret; } addr += _MALI_OSK_MALI_PAGE_SIZE; } return 0; } /** * Map some pages(COW backend) to CPU vma@vaddr *@ mem_bkend - COW backend *@ vma *@ vaddr -start CPU vaddr mapped to *@ num - max number of pages to map to CPU vaddr */ _mali_osk_errcode_t mali_mem_cow_cpu_map_pages_locked(mali_mem_backend *mem_bkend, struct vm_area_struct *vma, unsigned long vaddr, int num) { mali_mem_cow *cow = &mem_bkend->cow_mem; struct mali_page_node *m_page; int ret; int offset; int count ; unsigned long vstart = vma->vm_start; count = 0; MALI_DEBUG_ASSERT(mem_bkend->type == MALI_MEM_COW); MALI_DEBUG_ASSERT(0 == vaddr % _MALI_OSK_MALI_PAGE_SIZE); MALI_DEBUG_ASSERT(0 == vstart % _MALI_OSK_MALI_PAGE_SIZE); offset = (vaddr - vstart) / _MALI_OSK_MALI_PAGE_SIZE; list_for_each_entry(m_page, &cow->pages, list) { if ((count >= offset) && (count < offset + num)) { ret = vm_insert_pfn(vma, vaddr, _mali_page_node_get_pfn(m_page)); if (unlikely(0 != ret)) { if (count == offset) { return _MALI_OSK_ERR_FAULT; } else { /* ret is EBUSY when page isn't in modify range, but now it's OK*/ return _MALI_OSK_ERR_OK; } } vaddr += _MALI_OSK_MALI_PAGE_SIZE; } count++; } return _MALI_OSK_ERR_OK; } /** * Release COW backend memory * free it directly(put_page--unref page), not put into pool */ u32 mali_mem_cow_release(mali_mem_backend *mem_bkend, mali_bool is_mali_mapped) { mali_mem_allocation *alloc; struct mali_session_data *session; u32 free_pages_nr = 0; MALI_DEBUG_ASSERT_POINTER(mem_bkend); MALI_DEBUG_ASSERT(MALI_MEM_COW == mem_bkend->type); alloc = mem_bkend->mali_allocation; MALI_DEBUG_ASSERT_POINTER(alloc); session = alloc->session; MALI_DEBUG_ASSERT_POINTER(session); if (MALI_MEM_BACKEND_FLAG_SWAP_COWED != (MALI_MEM_BACKEND_FLAG_SWAP_COWED & mem_bkend->flags)) { /* Unmap the memory from the mali virtual address space. */ if (MALI_TRUE == is_mali_mapped) mali_mem_os_mali_unmap(alloc); /* free cow backend list*/ _mali_osk_mutex_wait(session->cow_lock); free_pages_nr = mali_mem_os_free(&mem_bkend->cow_mem.pages, mem_bkend->cow_mem.count, MALI_TRUE); _mali_osk_mutex_signal(session->cow_lock); free_pages_nr += mali_mem_block_free_list(&mem_bkend->cow_mem.pages); MALI_DEBUG_ASSERT(list_empty(&mem_bkend->cow_mem.pages)); } else { free_pages_nr = mali_mem_swap_release(mem_bkend, is_mali_mapped); } MALI_DEBUG_PRINT(4, ("COW Mem free : allocated size = 0x%x, free size = 0x%x\n", mem_bkend->cow_mem.count * _MALI_OSK_MALI_PAGE_SIZE, free_pages_nr * _MALI_OSK_MALI_PAGE_SIZE)); mem_bkend->cow_mem.count = 0; return free_pages_nr; } /* Dst node could os node or swap node. */ void _mali_mem_cow_copy_page(mali_page_node *src_node, mali_page_node *dst_node) { void *dst, *src; struct page *dst_page; dma_addr_t dma_addr; MALI_DEBUG_ASSERT(src_node != NULL); MALI_DEBUG_ASSERT(dst_node != NULL); MALI_DEBUG_ASSERT(dst_node->type == MALI_PAGE_NODE_OS || dst_node->type == MALI_PAGE_NODE_SWAP); if (dst_node->type == MALI_PAGE_NODE_OS) { dst_page = dst_node->page; } else { dst_page = dst_node->swap_it->page; } dma_unmap_page(&mali_platform_device->dev, _mali_page_node_get_dma_addr(dst_node), _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL); /* map it , and copy the content*/ dst = kmap_atomic(dst_page); if (src_node->type == MALI_PAGE_NODE_OS || src_node->type == MALI_PAGE_NODE_SWAP) { struct page *src_page; if (src_node->type == MALI_PAGE_NODE_OS) { src_page = src_node->page; } else { src_page = src_node->swap_it->page; } /* Clear and invaliate cache */ /* In ARM architecture, speculative read may pull stale data into L1 cache * for kernel linear mapping page table. DMA_BIDIRECTIONAL could * invalidate the L1 cache so that following read get the latest data */ dma_unmap_page(&mali_platform_device->dev, _mali_page_node_get_dma_addr(src_node), _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL); src = kmap_atomic(src_page); memcpy(dst, src , _MALI_OSK_MALI_PAGE_SIZE); kunmap_atomic(src); dma_addr = dma_map_page(&mali_platform_device->dev, src_page, 0, _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL); if (src_node->type == MALI_PAGE_NODE_SWAP) { src_node->swap_it->dma_addr = dma_addr; } } else if (src_node->type == MALI_PAGE_NODE_BLOCK) { /* * use ioremap to map src for BLOCK memory */ src = ioremap(_mali_page_node_get_dma_addr(src_node), _MALI_OSK_MALI_PAGE_SIZE); memcpy(dst, src , _MALI_OSK_MALI_PAGE_SIZE); iounmap(src); } kunmap_atomic(dst); dma_addr = dma_map_page(&mali_platform_device->dev, dst_page, 0, _MALI_OSK_MALI_PAGE_SIZE, DMA_BIDIRECTIONAL); if (dst_node->type == MALI_PAGE_NODE_SWAP) { dst_node->swap_it->dma_addr = dma_addr; } } /* * allocate page on demand when CPU access it, * THis used in page fault handler */ _mali_osk_errcode_t mali_mem_cow_allocate_on_demand(mali_mem_backend *mem_bkend, u32 offset_page) { struct page *new_page = NULL; struct mali_page_node *new_node = NULL; int i = 0; struct mali_page_node *m_page, *found_node = NULL; struct mali_session_data *session = NULL; mali_mem_cow *cow = &mem_bkend->cow_mem; MALI_DEBUG_ASSERT(MALI_MEM_COW == mem_bkend->type); MALI_DEBUG_ASSERT(offset_page < mem_bkend->size / _MALI_OSK_MALI_PAGE_SIZE); MALI_DEBUG_PRINT(4, ("mali_mem_cow_allocate_on_demand !, offset_page =0x%x\n", offset_page)); /* allocate new page here */ new_page = mali_mem_cow_alloc_page(); if (!new_page) return _MALI_OSK_ERR_NOMEM; new_node = _mali_page_node_allocate(MALI_PAGE_NODE_OS); if (!new_node) { __free_page(new_page); return _MALI_OSK_ERR_NOMEM; } /* find the page in backend*/ list_for_each_entry(m_page, &cow->pages, list) { if (i == offset_page) { found_node = m_page; break; } i++; } MALI_DEBUG_ASSERT(found_node); if (NULL == found_node) { __free_page(new_page); kfree(new_node); return _MALI_OSK_ERR_ITEM_NOT_FOUND; } _mali_page_node_add_page(new_node, new_page); /* Copy the src page's content to new page */ _mali_mem_cow_copy_page(found_node, new_node); MALI_DEBUG_ASSERT_POINTER(mem_bkend->mali_allocation); session = mem_bkend->mali_allocation->session; MALI_DEBUG_ASSERT_POINTER(session); if (1 != _mali_page_node_get_ref_count(found_node)) { atomic_add(1, &session->mali_mem_allocated_pages); if (atomic_read(&session->mali_mem_allocated_pages) * MALI_MMU_PAGE_SIZE > session->max_mali_mem_allocated_size) { session->max_mali_mem_allocated_size = atomic_read(&session->mali_mem_allocated_pages) * MALI_MMU_PAGE_SIZE; } mem_bkend->cow_mem.change_pages_nr++; } _mali_osk_mutex_wait(session->cow_lock); if (_mali_mem_put_page_node(found_node)) { __free_page(new_page); kfree(new_node); _mali_osk_mutex_signal(session->cow_lock); return _MALI_OSK_ERR_NOMEM; } _mali_osk_mutex_signal(session->cow_lock); list_replace(&found_node->list, &new_node->list); kfree(found_node); /* map to GPU side*/ _mali_osk_mutex_wait(session->memory_lock); mali_mem_cow_mali_map(mem_bkend, offset_page * _MALI_OSK_MALI_PAGE_SIZE, _MALI_OSK_MALI_PAGE_SIZE); _mali_osk_mutex_signal(session->memory_lock); return _MALI_OSK_ERR_OK; }