diff options
Diffstat (limited to 'drivers/mxc/gpu-viv/hal/os/linux/kernel/allocator/freescale/gc_hal_kernel_allocator_cma.c')
-rw-r--r-- | drivers/mxc/gpu-viv/hal/os/linux/kernel/allocator/freescale/gc_hal_kernel_allocator_cma.c | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/drivers/mxc/gpu-viv/hal/os/linux/kernel/allocator/freescale/gc_hal_kernel_allocator_cma.c b/drivers/mxc/gpu-viv/hal/os/linux/kernel/allocator/freescale/gc_hal_kernel_allocator_cma.c new file mode 100644 index 000000000000..6e787c9d3e87 --- /dev/null +++ b/drivers/mxc/gpu-viv/hal/os/linux/kernel/allocator/freescale/gc_hal_kernel_allocator_cma.c @@ -0,0 +1,588 @@ +/**************************************************************************** +* +* The MIT License (MIT) +* +* Copyright (c) 2014 - 2018 Vivante Corporation +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +* DEALINGS IN THE SOFTWARE. +* +***************************************************************************** +* +* The GPL License (GPL) +* +* Copyright (C) 2014 - 2018 Vivante Corporation +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +***************************************************************************** +* +* Note: This software is released under dual MIT and GPL licenses. A +* recipient may use this file under the terms of either the MIT license or +* GPL License. If you wish to use only one license not the other, you can +* indicate your decision by deleting one of the above license notices in your +* version of this file. +* +*****************************************************************************/ + + +#include "gc_hal_kernel_linux.h" +#include "gc_hal_kernel_allocator.h" + +#include <linux/pagemap.h> +#include <linux/seq_file.h> +#include <linux/mman.h> +#include <asm/atomic.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> + +#define _GC_OBJ_ZONE gcvZONE_OS + +typedef struct _gcsCMA_PRIV * gcsCMA_PRIV_PTR; +typedef struct _gcsCMA_PRIV { + atomic_t cmasize; + gctBOOL cmaLimitRequest; +} +gcsCMA_PRIV; + +struct mdl_cma_priv { + gctPOINTER kvaddr; + dma_addr_t physical; +}; + +static int gc_cma_usage_show(struct seq_file* m, void* data) +{ + gcsINFO_NODE *node = m->private; + gckALLOCATOR Allocator = node->device; + gcsCMA_PRIV_PTR priv = Allocator->privateData; + long long size = (long long)atomic_read(&priv->cmasize); + + seq_printf(m, "type n pages bytes\n"); + seq_printf(m, "cma %10llu %12llu\n", size, size * PAGE_SIZE); + + return 0; +} + +static gcsINFO InfoList[] = +{ + {"cmausage", gc_cma_usage_show}, +}; + +static void +_CMAAllocatorDebugfsInit( + IN gckALLOCATOR Allocator, + IN gckDEBUGFS_DIR Root + ) +{ + gcmkVERIFY_OK( + gckDEBUGFS_DIR_Init(&Allocator->debugfsDir, Root->root, "cma")); + + gcmkVERIFY_OK(gckDEBUGFS_DIR_CreateFiles( + &Allocator->debugfsDir, + InfoList, + gcmCOUNTOF(InfoList), + Allocator + )); +} + +static void +_CMAAllocatorDebugfsCleanup( + IN gckALLOCATOR Allocator + ) +{ + gcmkVERIFY_OK(gckDEBUGFS_DIR_RemoveFiles( + &Allocator->debugfsDir, + InfoList, + gcmCOUNTOF(InfoList) + )); + + gckDEBUGFS_DIR_Deinit(&Allocator->debugfsDir); +} + +static gceSTATUS +_CMAFSLAlloc( + IN gckALLOCATOR Allocator, + INOUT PLINUX_MDL Mdl, + IN gctSIZE_T NumPages, + IN gctUINT32 Flags + ) +{ + gceSTATUS status; + gcsCMA_PRIV_PTR priv = (gcsCMA_PRIV_PTR)Allocator->privateData; + + struct mdl_cma_priv *mdl_priv=gcvNULL; + gckOS os = Allocator->os; + + gcmkHEADER_ARG("Mdl=%p NumPages=0x%zx", Mdl, NumPages); + + if (os->allocatorLimitMarker) + { + if ((Flags & gcvALLOC_FLAG_CMA_LIMIT) && !(Flags & gcvALLOC_FLAG_CMA_PREEMPT)) + { + priv->cmaLimitRequest = gcvTRUE; + } + else if (priv->cmaLimitRequest == gcvTRUE) + { + gcmkONERROR(gcvSTATUS_NOT_SUPPORTED); + } + } + + gcmkONERROR(gckOS_Allocate(os, sizeof(struct mdl_cma_priv), (gctPOINTER *)&mdl_priv)); + mdl_priv->kvaddr = gcvNULL; + + mdl_priv->kvaddr = dma_alloc_writecombine(&os->device->platform->device->dev, + NumPages * PAGE_SIZE, + &mdl_priv->physical, + GFP_KERNEL | gcdNOWARN); + + if (mdl_priv->kvaddr == gcvNULL) + { + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + + Mdl->priv = mdl_priv; + Mdl->dmaHandle = mdl_priv->physical; + atomic_add(NumPages, &priv->cmasize); + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (mdl_priv) + { + gckOS_Free(os, mdl_priv); + } + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_CMAFSLGetSGT( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctSIZE_T Offset, + IN gctSIZE_T Bytes, + OUT gctPOINTER *SGT + ) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0) + struct page ** pages = gcvNULL; + struct page * page = gcvNULL; + struct sg_table *sgt = NULL; + struct mdl_cma_priv *mdl_priv = (struct mdl_cma_priv*)Mdl->priv; + + gceSTATUS status = gcvSTATUS_OK; + gctSIZE_T offset = Offset & ~PAGE_MASK; /* Offset to the first page */ + gctINT skipPages = Offset >> PAGE_SHIFT; /* skipped pages */ + gctINT numPages = (PAGE_ALIGN(Offset + Bytes) >> PAGE_SHIFT) - skipPages; + gctINT i; + + gcmkASSERT(Offset + Bytes <= Mdl->numPages << PAGE_SHIFT); + + sgt = kmalloc(sizeof(struct sg_table), GFP_KERNEL | gcdNOWARN); + if (!sgt) + { + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + + pages = kmalloc(sizeof(struct page*) * numPages, GFP_KERNEL | gcdNOWARN); + if (!pages) + { + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0) + page = phys_to_page (mdlPriv->physical); +#else + page = phys_to_page(dma_to_phys(&Allocator->os->device->platform->device->dev, mdl_priv->physical)); +#endif + + for (i = 0; i < numPages; ++i) + { + pages[i] = nth_page(page, i + skipPages); + } + + if (sg_alloc_table_from_pages(sgt, pages, numPages, offset, Bytes, GFP_KERNEL) < 0) + { + gcmkONERROR(gcvSTATUS_GENERIC_IO); + } + + *SGT = (gctPOINTER)sgt; + +OnError: + if (pages) + { + kfree(pages); + } + + if (gcmIS_ERROR(status) && sgt) + { + kfree(sgt); + } + + return status; +#else + return gcvSTATUS_NOT_SUPPORTED; +#endif +} + +static void +_CMAFSLFree( + IN gckALLOCATOR Allocator, + IN OUT PLINUX_MDL Mdl + ) +{ + gckOS os = Allocator->os; + struct mdl_cma_priv *mdlPriv=(struct mdl_cma_priv *)Mdl->priv; + gcsCMA_PRIV_PTR priv = (gcsCMA_PRIV_PTR)Allocator->privateData; + dma_free_writecombine(&os->device->platform->device->dev, + Mdl->numPages * PAGE_SIZE, + mdlPriv->kvaddr, + mdlPriv->physical); + gckOS_Free(os, mdlPriv); + atomic_sub(Mdl->numPages, &priv->cmasize); +} + +static gceSTATUS +_CMAFSLMmap( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctSIZE_T skipPages, + IN gctSIZE_T numPages, + INOUT struct vm_area_struct *vma + ) +{ + gckOS os = Allocator->os; + struct mdl_cma_priv *mdlPriv = (struct mdl_cma_priv*)Mdl->priv; + gceSTATUS status = gcvSTATUS_OK; + + gcmkHEADER_ARG("Allocator=%p Mdl=%p vma=%p", Allocator, Mdl, vma); + + gcmkASSERT(skipPages + numPages <= Mdl->numPages); + + /* Now map all the vmalloc pages to this user address. */ + if (Mdl->contiguous) + { + /* map kernel memory to user space.. */ + if (dma_mmap_writecombine(&os->device->platform->device->dev, + vma, + (gctINT8_PTR)mdlPriv->kvaddr + (skipPages << PAGE_SHIFT), + mdlPriv->physical + (skipPages << PAGE_SHIFT), + numPages << PAGE_SHIFT) < 0) + { + gcmkTRACE_ZONE( + gcvLEVEL_WARNING, gcvZONE_OS, + "%s(%d): dma_mmap_attrs error", + __FUNCTION__, __LINE__ + ); + + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + } + else + { + gcmkFATAL("%s(%d): unexpected noncontiguous mdl\n"); + gcmkONERROR(gcvSTATUS_HEAP_CORRUPTED); + } + +OnError: + gcmkFOOTER(); + return status; +} + +static void +_CMAFSLUnmapUser( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctPOINTER Logical, + IN gctUINT32 Size + ) +{ + if (unlikely(current->mm == gcvNULL)) + { + /* Do nothing if process is exiting. */ + return; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) + if (vm_munmap((unsigned long)Logical, Size) < 0) + { + gcmkTRACE_ZONE( + gcvLEVEL_WARNING, gcvZONE_OS, + "%s(%d): vm_munmap failed", + __FUNCTION__, __LINE__ + ); + } +#else + down_write(¤t->mm->mmap_sem); + if (do_munmap(current->mm, (unsigned long)Logical, Size) < 0) + { + gcmkTRACE_ZONE( + gcvLEVEL_WARNING, gcvZONE_OS, + "%s(%d): do_munmap failed", + __FUNCTION__, __LINE__ + ); + } + up_write(¤t->mm->mmap_sem); +#endif +} + +static gceSTATUS +_CMAFSLMapUser( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctBOOL Cacheable, + OUT gctPOINTER * UserLogical + ) +{ + gctPOINTER userLogical = gcvNULL; + gceSTATUS status = gcvSTATUS_OK; + + gcmkHEADER_ARG("Allocator=%p Mdl=%p Cacheable=%d", Allocator, Mdl, Cacheable); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + userLogical = (gctPOINTER)vm_mmap(gcvNULL, + 0L, + Mdl->numPages * PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_NORESERVE, + 0); +#else + down_write(¤t->mm->mmap_sem); + userLogical = (gctPOINTER)do_mmap_pgoff(gcvNULL, + 0L, + Mdl->numPages * PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, + 0); + up_write(¤t->mm->mmap_sem); +#endif + + gcmkTRACE_ZONE( + gcvLEVEL_INFO, gcvZONE_OS, + "%s(%d): vmaAddr->%p for phys_addr->%p", + __FUNCTION__, __LINE__, userLogical, Mdl + ); + + if (IS_ERR(userLogical)) + { + gcmkTRACE_ZONE( + gcvLEVEL_INFO, gcvZONE_OS, + "%s(%d): do_mmap_pgoff error", + __FUNCTION__, __LINE__ + ); + + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + + down_write(¤t->mm->mmap_sem); + do + { + struct vm_area_struct *vma = find_vma(current->mm, (unsigned long)userLogical); + if (vma == gcvNULL) + { + gcmkTRACE_ZONE( + gcvLEVEL_INFO, gcvZONE_OS, + "%s(%d): find_vma error", + __FUNCTION__, __LINE__ + ); + + gcmkERR_BREAK(gcvSTATUS_OUT_OF_RESOURCES); + } + + gcmkERR_BREAK(_CMAFSLMmap(Allocator, Mdl, 0, Mdl->numPages, vma)); + } + while (gcvFALSE); + up_write(¤t->mm->mmap_sem); + + if (gcmIS_SUCCESS(status)) + { + gcmkONERROR(gckOS_CacheFlush( + Allocator->os, + _GetProcessID(), + Mdl, + gcvINVALID_ADDRESS, + userLogical, + Mdl->numPages * PAGE_SIZE + )); + + *UserLogical = userLogical; + } + +OnError: + if (gcmIS_ERROR(status) && userLogical) + { + _CMAFSLUnmapUser(Allocator, Mdl, userLogical, Mdl->numPages * PAGE_SIZE); + } + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_CMAMapKernel( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + OUT gctPOINTER *Logical + ) +{ + struct mdl_cma_priv *mdl_priv=(struct mdl_cma_priv *)Mdl->priv; + *Logical =mdl_priv->kvaddr; + return gcvSTATUS_OK; +} + +static gceSTATUS +_CMAUnmapKernel( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctPOINTER Logical + ) +{ + return gcvSTATUS_OK; +} + +static gceSTATUS +_CMACache( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctPOINTER Logical, + IN gctUINT32 Physical, + IN gctUINT32 Bytes, + IN gceCACHEOPERATION Operation + ) +{ + return gcvSTATUS_OK; +} + +static gceSTATUS +_CMAPhysical( + IN gckALLOCATOR Allocator, + IN PLINUX_MDL Mdl, + IN gctUINT32 Offset, + OUT gctPHYS_ADDR_T * Physical + ) +{ + struct mdl_cma_priv *mdl_priv=(struct mdl_cma_priv *)Mdl->priv; + + *Physical = mdl_priv->physical + Offset; + + return gcvSTATUS_OK; +} + +static void +_CMAAllocatorDestructor( + gcsALLOCATOR *Allocator + ) +{ + _CMAAllocatorDebugfsCleanup(Allocator); + + if (Allocator->privateData) + { + kfree(Allocator->privateData); + } + + kfree(Allocator); +} + +/* Default allocator operations. */ +static gcsALLOCATOR_OPERATIONS CMAFSLAllocatorOperations = +{ + .Alloc = _CMAFSLAlloc, + .Free = _CMAFSLFree, + .Mmap = _CMAFSLMmap, + .MapUser = _CMAFSLMapUser, + .UnmapUser = _CMAFSLUnmapUser, + .MapKernel = _CMAMapKernel, + .UnmapKernel = _CMAUnmapKernel, + .Cache = _CMACache, + .Physical = _CMAPhysical, + .GetSGT = _CMAFSLGetSGT, +}; + +/* Default allocator entry. */ +gceSTATUS +_CMAFSLAlloctorInit( + IN gckOS Os, + IN gcsDEBUGFS_DIR *Parent, + OUT gckALLOCATOR * Allocator + ) +{ + gceSTATUS status; + gckALLOCATOR allocator = gcvNULL; + gcsCMA_PRIV_PTR priv = gcvNULL; + + gcmkONERROR( + gckALLOCATOR_Construct(Os, &CMAFSLAllocatorOperations, &allocator)); + + priv = kzalloc(gcmSIZEOF(gcsCMA_PRIV), GFP_KERNEL | gcdNOWARN); + + if (!priv) + { + gcmkONERROR(gcvSTATUS_OUT_OF_MEMORY); + } + + atomic_set(&priv->cmasize, 0); + + /* Register private data. */ + allocator->privateData = priv; + allocator->destructor = _CMAAllocatorDestructor; + + _CMAAllocatorDebugfsInit(allocator, Parent); + + allocator->capability = gcvALLOC_FLAG_CONTIGUOUS + | gcvALLOC_FLAG_DMABUF_EXPORTABLE + ; + +#if defined(CONFIG_ARM64) + Os->allocatorLimitMarker = (Os->device->baseAddress + totalram_pages * PAGE_SIZE) > 0x100000000; +#else + Os->allocatorLimitMarker = gcvFALSE; +#endif + priv->cmaLimitRequest = gcvFALSE; + + if (Os->allocatorLimitMarker) + { + allocator->capability |= gcvALLOC_FLAG_CMA_LIMIT; + allocator->capability |= gcvALLOC_FLAG_CMA_PREEMPT; + } + + *Allocator = allocator; + + return gcvSTATUS_OK; + +OnError: + if (allocator) + { + kfree(allocator); + } + return status; +} + |