/*
Copyright (c) 2012, Broadcom Europe Ltd
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>


#include <vmcs_sm_ioctl.h>
#include "vc_sm_cma_ioctl.h"
#include "dma-buf.h"
#include "user-vcsm.h"
#include "interface/vcos/vcos.h"

typedef struct
{
   VCSM_CACHE_TYPE_T cur;  /* Current pattern. */
   VCSM_CACHE_TYPE_T new;  /* New pattern. */
   VCSM_CACHE_TYPE_T res;  /* End result. */

} VCSM_CACHE_MUTEX_LKUP_T;

// /dev/vcsm-cma is the new device that is using CMA and importing it to the VPU.
#define VCSM_CMA_DEVICE_NAME  "/dev/vcsm-cma"
// /dev/vcsm is the older driver that maps gpu_mem into the ARM.
#define VCSM_DEVICE_NAME      "/dev/vcsm"
#define VCSM_INVALID_HANDLE   (-1)

static VCOS_LOG_CAT_T usrvcsm_log_category;
#define VCOS_LOG_CATEGORY (&usrvcsm_log_category)
static int using_vc_sm_cma = 0;
static int vcsm_handle = VCSM_INVALID_HANDLE;
static int vcsm_refcount;
static unsigned int vcsm_page_size = 0;

static VCOS_ONCE_T vcsm_once = VCOS_ONCE_INIT;
static VCOS_MUTEX_T vcsm_mutex;

#define VCSM_PAYLOAD_ELEM_MAX 512

typedef struct VCSM_PAYLOAD_ELEM_T
{
   unsigned int handle;    // User handle
   int fd;                 // vcsm-cma / dmabuf fd (= user handle-1)
   uint32_t vc_handle;     // VPU reloc heap handle
   uint8_t *mem;           // mmap'ed address
   unsigned int size;      // size of mmap
   uint32_t dma_addr;      // VPU address for the buffer
   int in_use;
} VCSM_PAYLOAD_ELEM_T;

typedef struct VCSM_PAYLOAD_LIST_T
{
   VCSM_PAYLOAD_ELEM_T list[VCSM_PAYLOAD_ELEM_MAX];
   VCOS_MUTEX_T lock;
   struct VCSM_PAYLOAD_LIST_T *next;
} VCSM_PAYLOAD_LIST_T;

static VCSM_PAYLOAD_LIST_T vcsm_payload_list;

static void vcsm_payload_list_init(void)
{
   vcos_mutex_create(&vcsm_payload_list.lock, "vcsm_payload_list");
}

static VCSM_PAYLOAD_ELEM_T *vcsm_payload_list_get()
{
   VCSM_PAYLOAD_ELEM_T *elem = 0;
   unsigned int i;

   vcos_mutex_lock(&vcsm_payload_list.lock);
   for (i = 0; i < VCSM_PAYLOAD_ELEM_MAX; i++)
   {
      if (vcsm_payload_list.list[i].in_use)
         continue;
      elem = &vcsm_payload_list.list[i];
      elem->in_use = 1;
      break;
   }
   vcos_mutex_unlock(&vcsm_payload_list.lock);

   return elem;
}

static void vcsm_payload_list_release(VCSM_PAYLOAD_ELEM_T *elem)
{
   vcos_mutex_lock(&vcsm_payload_list.lock);
   elem->handle = elem->vc_handle = elem->fd = 0;
   elem->mem = NULL;
   elem->in_use = 0;
   vcos_mutex_unlock(&vcsm_payload_list.lock);
}

static VCSM_PAYLOAD_ELEM_T *vcsm_payload_list_find_mem(void *mem)
{
   VCSM_PAYLOAD_ELEM_T *elem = 0;
   unsigned int i;

   vcos_mutex_lock(&vcsm_payload_list.lock);
   for (i = 0; i < VCSM_PAYLOAD_ELEM_MAX; i++)
   {
      if (!vcsm_payload_list.list[i].in_use)
         continue;
      if (vcsm_payload_list.list[i].mem != mem)
         continue;
      elem = &vcsm_payload_list.list[i];
      break;
   }
   vcos_mutex_unlock(&vcsm_payload_list.lock);

   return elem;
}

static VCSM_PAYLOAD_ELEM_T *vcsm_payload_list_find_handle(unsigned int handle)
{
   VCSM_PAYLOAD_ELEM_T *elem = 0;
   unsigned int i;

   vcos_mutex_lock(&vcsm_payload_list.lock);
   for (i = 0; i < VCSM_PAYLOAD_ELEM_MAX; i++)
   {
      if (!vcsm_payload_list.list[i].in_use)
         continue;
      if (vcsm_payload_list.list[i].handle != handle)
         continue;
      elem = &vcsm_payload_list.list[i];
      break;
   }
   vcos_mutex_unlock(&vcsm_payload_list.lock);

   return elem;
}

/*static VCSM_PAYLOAD_ELEM_T *vcsm_payload_list_find_vc_handle(uint32_t vc_handle)
{
   VCSM_PAYLOAD_ELEM_T *elem = 0;
   unsigned int i;

   vcos_mutex_lock(&vcsm_payload_list.lock);
   for (i = 0; i < VCSM_PAYLOAD_ELEM_MAX; i++)
   {
      if (!vcsm_payload_list.list[i].in_use)
         continue;
      if (vcsm_payload_list.list[i].vc_handle != vc_handle)
         continue;
      elem = &vcsm_payload_list.list[i];
      break;
   }
   vcos_mutex_unlock(&vcsm_payload_list.lock);

   return elem;
}*/


/* Cache [(current, new) -> outcome] mapping table, ignoring identity.
**
** Note: Videocore cache mode cannot be udpated 'lock' time.
*/
static VCSM_CACHE_MUTEX_LKUP_T vcsm_cache_mutex_table[] =
{
     /* ------ CURRENT ------- *//* ---------- NEW --------- *//* --------- RESULT --------- */
   { VCSM_CACHE_TYPE_NONE,       VCSM_CACHE_TYPE_HOST,         VCSM_CACHE_TYPE_HOST },
   { VCSM_CACHE_TYPE_NONE,       VCSM_CACHE_TYPE_VC,           VCSM_CACHE_TYPE_NONE },
   { VCSM_CACHE_TYPE_NONE,       VCSM_CACHE_TYPE_HOST_AND_VC,  VCSM_CACHE_TYPE_HOST },

   { VCSM_CACHE_TYPE_HOST,       VCSM_CACHE_TYPE_NONE,         VCSM_CACHE_TYPE_NONE },
   { VCSM_CACHE_TYPE_HOST,       VCSM_CACHE_TYPE_VC,           VCSM_CACHE_TYPE_HOST },
   { VCSM_CACHE_TYPE_HOST,       VCSM_CACHE_TYPE_HOST_AND_VC,  VCSM_CACHE_TYPE_HOST },

   { VCSM_CACHE_TYPE_VC,         VCSM_CACHE_TYPE_NONE,         VCSM_CACHE_TYPE_NONE },
   { VCSM_CACHE_TYPE_VC,         VCSM_CACHE_TYPE_HOST,         VCSM_CACHE_TYPE_HOST_AND_VC },
   { VCSM_CACHE_TYPE_VC,         VCSM_CACHE_TYPE_HOST_AND_VC,  VCSM_CACHE_TYPE_HOST_AND_VC },

   { VCSM_CACHE_TYPE_HOST_AND_VC, VCSM_CACHE_TYPE_NONE,        VCSM_CACHE_TYPE_VC },
   { VCSM_CACHE_TYPE_HOST_AND_VC, VCSM_CACHE_TYPE_HOST,        VCSM_CACHE_TYPE_HOST_AND_VC },
   { VCSM_CACHE_TYPE_HOST_AND_VC, VCSM_CACHE_TYPE_VC,          VCSM_CACHE_TYPE_VC },

   /* Used for lookup termination. */
   { VCSM_CACHE_TYPE_NONE,        VCSM_CACHE_TYPE_NONE,        VCSM_CACHE_TYPE_NONE },
};

static VCSM_CACHE_TYPE_T vcsm_cache_table_lookup( VCSM_CACHE_TYPE_T current,
                                                  VCSM_CACHE_TYPE_T new )
{
   VCSM_CACHE_MUTEX_LKUP_T *p_map = vcsm_cache_mutex_table;   

   while ( !( (p_map->cur == VCSM_CACHE_TYPE_NONE) &&
              (p_map->new == VCSM_CACHE_TYPE_NONE) ) )
   {
      if ( (p_map->cur == current) && (p_map->new == new) )
      {
         return p_map->res;
      }

      p_map++;
   };

   vcos_log_error( "[%s]: [%d]: no mapping found for current %d - new %d",
                   __func__,
                   getpid(),
                   current,
                   new );
   return current;
}

/* A one off vcsm initialization routine
*/
static void vcsm_init_once(void)
{
   vcos_mutex_create(&vcsm_mutex, VCOS_FUNCTION);
   vcos_log_set_level(&usrvcsm_log_category, VCOS_LOG_ERROR);
   usrvcsm_log_category.flags.want_prefix = 0;
   vcos_log_register( "usrvcsm", &usrvcsm_log_category );
   vcsm_payload_list_init();
}


/* Initialize the vcsm processing.
**
** Must be called once before attempting to do anything else.
**
** Returns 0 on success, -1 on error.
*/
int vcsm_init_ex( int want_export, int fd )
{
   int result  = VCSM_INVALID_HANDLE;
   vcos_once(&vcsm_once, vcsm_init_once);

   /* Only open the VCSM device once per process.
   */
   vcos_mutex_lock( &vcsm_mutex );
   if ( vcsm_refcount != 0 )
   {
      // Note that in this case the external fd is ignored.

      if (want_export && !using_vc_sm_cma)
      {
         vcos_log_trace( "[%s]: fail as already open and export not available",
                        __func__);
         vcos_mutex_unlock( &vcsm_mutex );
         return -1;
      }
      goto out; /* VCSM already opened. Nothing to do. */
   }

   if (fd != -1)
   {
      vcsm_handle = dup(fd);

      // FIXME: Sanity check which device that the fd actually relates to.
      // For now we have to guess based on whether export is requested.
      // (the main use case is from Chromium which will be requesting export).
      if (want_export)
         using_vc_sm_cma = 1;

      goto out;
   }

   if (want_export)
   {
      vcsm_handle = open( VCSM_CMA_DEVICE_NAME, O_RDWR, 0 );

      if (vcsm_handle >= 0)
      {
         using_vc_sm_cma = 1;
         vcos_log_trace( "[%s]: Using vc-sm-cma, handle %d",
                        __func__, vcsm_handle);
      }
   }

   if (vcsm_handle < 0)
   {
      vcos_log_trace( "[%s]: NOT using vc-sm-cma as handle was %d",
                      __func__, vcsm_handle);
      vcsm_handle = open( VCSM_DEVICE_NAME, O_RDWR, 0 );
   }

   if (vcsm_handle < 0 && !want_export)
   {
      // vcsm failed and not tried vcsm-cma yet.
      vcsm_handle = open( VCSM_CMA_DEVICE_NAME, O_RDWR, 0 );

      if (vcsm_handle >= 0)
      {
         using_vc_sm_cma = 1;
         vcos_log_trace( "[%s]: Using vc-sm-cma, handle %d",
                        __func__, vcsm_handle);
      }
   }

out:
   if ( vcsm_handle >= 0 )
   {
      vcsm_page_size = getpagesize();

      result = 0;
      vcsm_refcount++;

      vcos_log_trace( "[%s]: [%d]: %d (align: %u) - ref-cnt %u",
                      __func__,
                      getpid(),
                      vcsm_handle,
                      vcsm_page_size,
                      vcsm_refcount );
   }
   vcos_mutex_unlock( &vcsm_mutex );
   return result;
}

/* Initialize the vcsm processing.
**
** Must be called once before attempting to do anything else.
**
** Returns 0 on success, -1 on error.
*/
int vcsm_init( void )
{
  return vcsm_init_ex(0, -1);
}

/* Terminates the vcsm processing.
**
** Must be called vcsm services are no longer needed, it will
** take care of removing any allocation under the current process
** control if deemed necessary.
*/
void vcsm_exit( void )
{
   vcos_mutex_lock( &vcsm_mutex );

   if ( vcsm_refcount == 0 )
   {
      goto out; /* Shouldn't really happen. */
   }

   if ( --vcsm_refcount != 0 )
   {
      vcos_log_trace( "[%s]: [%d]: %d - ref-cnt: %u",
                      __func__,
                      getpid(),
                      vcsm_handle,
                      vcsm_refcount );

      goto out; /* We're done. */
   }

   close( vcsm_handle );
   vcsm_handle = VCSM_INVALID_HANDLE;

out:
   vcos_mutex_unlock( &vcsm_mutex );
}


/* Allocates a cached block of memory of size 'size' via the vcsm memory
** allocator, the type of caching requested is passed as argument of the
** function call.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** On success, the user must invoke vcsm_lock with the returned opaque
** handle to gain access to the memory associated with the opaque handle.
** When finished using the memory, the user calls vcsm_unlock_xx (see those
** function definition for more details on the one that can be used).
** 
** A well behaved application should make every attempt to lock/unlock
** only for the duration it needs to access the memory data associated with
** the opaque handle.
*/
unsigned int vcsm_malloc_cache( unsigned int size, VCSM_CACHE_TYPE_T cache, const char *name )
{
   unsigned int size_aligned = size;
   void *usr_ptr = NULL;
   int rc;
   unsigned int handle = VCSM_INVALID_HANDLE;

   if ( (size == 0) || (vcsm_handle == VCSM_INVALID_HANDLE) ) 
   {
      vcos_log_error( "[%s]: [%d] [%s]: NULL size or invalid device!",
                      __func__,
                      getpid(),
                      name );
      return 0;
   }

   /* Ask for page aligned.
   */
   size_aligned = (size + vcsm_page_size - 1) & ~(vcsm_page_size - 1);

   if (using_vc_sm_cma)
   {
      struct vc_sm_cma_ioctl_alloc alloc;
      VCSM_PAYLOAD_ELEM_T *payload;

      memset( &alloc, 0, sizeof(alloc));

      alloc.size   = size_aligned;
      alloc.num    = 1;
      alloc.cached = (enum vmcs_sm_cache_e) cache;   /* Convenient one to one mapping. */
      alloc.handle = 0;
      if ( name != NULL )
      {
         memcpy ( alloc.name, name, 32 );
      }
      rc = ioctl( vcsm_handle,
                  VC_SM_CMA_IOCTL_MEM_ALLOC,
                  &alloc );

      if ( rc < 0 || alloc.handle < 0 )
      {
         vcos_log_error( "[%s]: [%d] [%s]: ioctl mem-alloc FAILED [%d] (hdl: %x)",
                         __func__,
                         getpid(),
                         alloc.name,
                         rc,
                         alloc.handle );
         return 0;
      }

      vcos_log_trace( "[%s]: [%d] [%s]: ioctl mem-alloc %d (hdl: %x)",
                      __func__,
                      getpid(),
                      alloc.name,
                      rc,
                      alloc.handle );

      /* Map the buffer into user space.
      */
      usr_ptr = mmap( 0,
                      alloc.size,
                      PROT_READ | PROT_WRITE,
                      MAP_SHARED,
                      alloc.handle,
                      0 );

      if ( usr_ptr == MAP_FAILED )
      {
         vcos_log_error( "[%s]: [%d]: mmap FAILED (hdl: %x)",
                      __func__,
                      getpid(),
                      alloc.handle );
         vcsm_free( alloc.handle );
         return 0;
      }

      // vc-sm-cma now hands out file handles (signed int), whilst libvcsm is
      // handling unsigned int handles. Already checked the handle >=0, so
      // add one to make it a usable handle.
      handle = alloc.handle + 1;

      vcos_log_trace( "[%s]: mmap to %p",
                      __func__,
                      usr_ptr
                      );

      payload = vcsm_payload_list_get();
      payload->handle = handle;
      payload->fd = alloc.handle;
      payload->vc_handle = alloc.vc_handle;
      payload->mem = usr_ptr;
      payload->size = size_aligned;
      if (alloc.dma_addr & 0xFFFFFFFF00000000ULL)
      {
         vcos_log_error("[%s]: dma address returned > 32bit 0x%llx", __func__, alloc.dma_addr);
         payload->dma_addr = 0;
      }
      else
         payload->dma_addr = (uint32_t)alloc.dma_addr;
   }
   else
   {
      struct vmcs_sm_ioctl_alloc alloc;

      memset( &alloc, 0, sizeof(alloc));
      /* Allocate the buffer on videocore via the VCSM (Videocore Shared Memory)
      ** interface.
      */
      alloc.size   = size_aligned;
      alloc.num    = 1;
      alloc.cached = (enum vmcs_sm_cache_e) cache;   /* Convenient one to one mapping. */
      alloc.handle = 0;
      if ( name != NULL )
      {
         memcpy ( alloc.name, name, 32 );
      }
      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_ALLOC,
                  &alloc );

      if ( rc < 0 || alloc.handle == 0 )
      {
         vcos_log_error( "[%s]: [%d] [%s]: ioctl mem-alloc FAILED [%d] (hdl: %x)",
                         __func__,
                         getpid(),
                         alloc.name,
                         rc,
                         alloc.handle );
         return 0;
      }

      vcos_log_trace( "[%s]: [%d] [%s]: ioctl mem-alloc %d (hdl: %x)",
                      __func__,
                      getpid(),
                      alloc.name,
                      rc,
                      alloc.handle );

      /* Map the buffer into user space.
      */
      usr_ptr = mmap( 0,
                      alloc.size,
                      PROT_READ | PROT_WRITE,
                      MAP_SHARED,
                      vcsm_handle,
                      alloc.handle );

      if ( usr_ptr == NULL )
      {
         vcos_log_error( "[%s]: [%d]: mmap FAILED (hdl: %x)",
                      __func__,
                      getpid(),
                      alloc.handle );
         vcsm_free( alloc.handle );
         return 0;
      }

      handle = alloc.handle;
   }

   return handle;
}


/* Allocates a non-cached block of memory of size 'size' via the vcsm memory
** allocator.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** On success, the user must invoke vcsm_lock with the returned opaque
** handle to gain access to the memory associated with the opaque handle.
** When finished using the memory, the user calls vcsm_unlock_xx (see those
** function definition for more details on the one that can be used).
** 
** A well behaved application should make every attempt to lock/unlock
** only for the duration it needs to access the memory data associated with
** the opaque handle.
*/
unsigned int vcsm_malloc( unsigned int size, const char *name )
{
   return vcsm_malloc_cache( size, VCSM_CACHE_TYPE_NONE, name );
}

/* Shares an allocated block of memory.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** On success, the user must invoke vcsm_lock with the returned opaque
** handle to gain access to the memory associated with the opaque handle.
** When finished using the memory, the user calls vcsm_unlock_xx (see those
** function definition for more details on the one that can be used).
**
** A well behaved application should make every attempt to lock/unlock
** only for the duration it needs to access the memory data associated with
** the opaque handle.
*/
unsigned int vcsm_malloc_share( unsigned int handle )
{
   struct vmcs_sm_ioctl_alloc_share alloc;
   int rc;

   if ( vcsm_handle == VCSM_INVALID_HANDLE )
   {
      vcos_log_error( "[%s]: [%d]: NULL size or invalid device!",
                      __func__,
                      getpid() );
      return 0;
   }

   if (using_vc_sm_cma)
   {
      return 0;
   }

   memset( &alloc, 0, sizeof(alloc) );

   /* Share the buffer on videocore via the VCSM (Videocore Shared Memory)
   ** interface.
   */
   alloc.handle = handle;
   rc = ioctl( vcsm_handle,
               VMCS_SM_IOCTL_MEM_ALLOC_SHARE,
               &alloc );

   if ( rc < 0 || alloc.handle == 0 )
   {
      vcos_log_error( "[%s]: [%d]: ioctl mem-share FAILED [%d] (hdl: %x->%x)",
                      __func__,
                      getpid(),
                      rc,
                      handle,
                      alloc.handle );
      goto error;
   }

   vcos_log_trace( "[%s]: [%d]: ioctl mem-share %d (hdl: %x->%x)",
                   __func__,
                   getpid(),
                   rc,
                   handle,
                   alloc.handle );

   return alloc.handle;

 error:
   if ( alloc.handle )
   {
      vcsm_free( alloc.handle );
   }
   return 0;
}

/* Frees a block of memory that was successfully allocated by
** a prior call the vcms_alloc.
**
** The handle should be considered invalid upon return from this
** call.
**
** Whether any memory is actually freed up or not as the result of
** this call will depends on many factors, if all goes well it will
** be freed.  If something goes wrong, the memory will likely end up
** being freed up as part of the vcsm_exit process.  In the end the
** memory is guaranteed to be freed one way or another.
*/
void vcsm_free( unsigned int handle )
{
   int rc;
   void *usr_ptr = NULL;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or handle!",
                      __func__,
                      getpid() );

      goto out;
   }

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. elem %p\n",
                      __func__, handle, elem);
         goto out;
      }

      rc = munmap( elem->mem, elem->size );

      vcos_log_trace( "[%s]: ioctl unmap fd: %d, addr %p, size %u. rc %d",
                      __func__,
                      elem->fd,
                      elem->mem,
                      elem->size,
                      rc
                        );

      close(elem->fd);

      vcsm_payload_list_release(elem);
   }
   else
   {
      struct vmcs_sm_ioctl_free alloc_free;
      struct vmcs_sm_ioctl_size sz;
      struct vmcs_sm_ioctl_map map;

      memset( &sz, 0, sizeof(sz) );
      memset( &alloc_free, 0, sizeof(alloc_free) );
      memset( &map, 0, sizeof(map) );

      /* Verify what we want is valid.
      */
      sz.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_SIZE_USR_HDL,
                  &sz );

      vcos_log_trace( "[%s]: [%d]: ioctl size-usr-hdl %d (hdl: %x) - size %u",
                      __func__,
                      getpid(),
                      rc,
                      sz.handle,
                      sz.size );

      /* We will not be able to free up the resource!
      **
      ** However, the driver will take care of it eventually once the device is
      ** closed (or dies), so this is not such a dramatic event...
      */
      if ( (rc < 0) || (sz.size == 0) )
      {
         goto out;
      }

      /* Un-map the buffer from user space, using the last known mapped
      ** address valid.
      */
      usr_ptr = (void *) vcsm_usr_address( sz.handle );
      if ( usr_ptr != NULL )
      {
         munmap( usr_ptr, sz.size );

         vcos_log_trace( "[%s]: [%d]: ioctl unmap hdl: %x",
                         __func__,
                         getpid(),
                         sz.handle );
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: freeing unmapped area (hdl: %x)",
                         __func__,
                         getpid(),
                         map.handle );
      }

      /* Free the allocated buffer all the way through videocore.
      */
      alloc_free.handle    = sz.handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_FREE,
                  &alloc_free );

      vcos_log_trace( "[%s]: [%d]: ioctl mem-free %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      alloc_free.handle );
   }

out:
   return;
}


/* Queries the status of the the vcsm.
**
** Triggers dump of various kind of information, see the
** different variants specified in VCSM_STATUS_T.
**
** Pid is optional.
*/
void vcsm_status( VCSM_STATUS_T status, int pid )
{
   struct vmcs_sm_ioctl_walk walk;

   if ( vcsm_handle == VCSM_INVALID_HANDLE ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device!",
                      __func__,
                      getpid() );

      return;
   }

   if (using_vc_sm_cma)
   {
      return;
   }

   memset( &walk, 0, sizeof(walk) );

   /* Allow user to specify the pid of interest if desired, otherwise
   ** assume the current one.
   */
   walk.pid  = (pid == VCSM_INVALID_HANDLE) ? getpid() : pid;

   switch ( status )
   {
      case VCSM_STATUS_VC_WALK_ALLOC:
      {
         ioctl( vcsm_handle,
                VMCS_SM_IOCTL_VC_WALK_ALLOC,
                NULL );
      }
      break;

      case VCSM_STATUS_HOST_WALK_MAP:
      {
         ioctl( vcsm_handle,
                VMCS_SM_IOCTL_HOST_WALK_MAP,
                NULL );
      }
      break;

      case VCSM_STATUS_HOST_WALK_PID_MAP:
      {
         ioctl( vcsm_handle,
                VMCS_SM_IOCTL_HOST_WALK_PID_MAP,
                &walk );
      }
      break;

      case VCSM_STATUS_HOST_WALK_PID_ALLOC:
      {
         ioctl( vcsm_handle,
                VMCS_SM_IOCTL_HOST_WALK_PID_ALLOC,
                &walk );
      }
      break;

      case VCSM_STATUS_NONE:
      default:
         vcos_log_error( "[%s]: [%d]: invalid argument %d",
                         __func__,
                         getpid(),
                         status );
      break;
   }
}


/* Retrieves a videocore opaque handle from a mapped user address
** pointer.  The videocore handle will correspond to the actual
** memory mapped in videocore.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** Note: the videocore opaque handle is distinct from the user
**       opaque handle (allocated via vcsm_malloc) and it is only
**       significant for such application which knows what to do
**       with it, for the others it is just a number with little
**       use since nothing can be done with it (in particular
**       for safety reason it cannot be used to map anything).
*/
unsigned int vcsm_vc_hdl_from_ptr( void *usr_ptr )
{
   int rc;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (usr_ptr == NULL) )
   {
      vcos_log_error( "[%s]: [%d]: invalid device!",
                      __func__,
                      getpid() );

      return 0;
   }

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_mem(usr_ptr);

      if (!elem)
      {
         vcos_log_trace( "[%s]: addr %p not tracked, or not mapped. elem %p\n",
                      __func__, usr_ptr, elem);
         return 0;
      }
      return elem->vc_handle;
   }
   else
   {
      struct vmcs_sm_ioctl_map map;

      memset( &map, 0, sizeof(map) );

      map.pid  = getpid();
      map.addr = (uintptr_t) usr_ptr;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_VC_HDL_FR_ADDR,
                  &map );

      if ( rc < 0 )
      {
         vcos_log_error( "[%s]: [%d]: ioctl mapped-usr-hdl FAILED [%d] (pid: %d, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.pid,
                         map.addr );

         return 0;
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-hdl %d (hdl: %x, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.handle,
                         map.addr );

         return map.handle;
      }
   }
}


/* Retrieves a videocore opaque handle from a opaque handle
** pointer.  The videocore handle will correspond to the actual
** memory mapped in videocore.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** Note: the videocore opaque handle is distinct from the user
**       opaque handle (allocated via vcsm_malloc) and it is only
**       significant for such application which knows what to do
**       with it, for the others it is just a number with little
**       use since nothing can be done with it (in particular
**       for safety reason it cannot be used to map anything).
*/
unsigned int vcsm_vc_hdl_from_hdl( unsigned int handle )
{
   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) )
   {
      vcos_log_error( "[%s]: [%d]: invalid device or handle!",
                      __func__,
                      getpid() );

      return 0;
   }

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. \n",
                      __func__, handle);
         return 0;
      }
      return elem->vc_handle;
   }
   else
   {
      int rc;
      struct vmcs_sm_ioctl_map map;

      memset( &map, 0, sizeof(map) );

      map.pid    = getpid();
      map.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_VC_HDL_FR_HDL,
                  &map );

      if ( rc < 0 )
      {
         vcos_log_error( "[%s]: [%d]: ioctl mapped-usr-hdl FAILED [%d] (pid: %d, hdl: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.pid,
                         map.handle );

         return 0;
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-hdl %d (hdl: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.handle );

         return map.handle;
      }
   }
}


/* Retrieves a videocore (bus) address from a opaque handle
** pointer.
**
** Returns:        0 on error
**                 a non-zero videocore address on success.
*/
unsigned int vcsm_vc_addr_from_hdl( unsigned int handle )
{
   int rc;
   struct vmcs_sm_ioctl_map map;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) )
   {
      vcos_log_error( "[%s]: [%d]: invalid device or handle!",
                      __func__,
                      getpid() );

      return 0;
   }

   if (using_vc_sm_cma)
   {
      // The API is broken here if we're looking for 64bit support.
      // Need to return a dma_addr_t instead, and the value is platform
      // dependent.
      // Admittedly VideoCore is only 32-bit, so there could be an
      // implementation returning a VPU bus address which would fit in an
      // unsigned int. TODO.
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. \n",
                      __func__, handle);
         return 0;
      }
      return elem->dma_addr;
   }
   else
   {
      memset( &map, 0, sizeof(map) );

      map.pid    = getpid();
      map.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_VC_ADDR_FR_HDL,
                  &map );

      if ( rc < 0 )
      {
         vcos_log_error( "[%s]: [%d]: ioctl mapped-usr-hdl FAILED [%d] (pid: %d, hdl: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.pid,
                         map.handle );

         return 0;
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-hdl %d (hdl: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.handle );

         return map.addr;
      }
   }
}


/* Retrieves a mapped user address from an opaque user
** handle.
**
** Returns:        0 on error
**                 a non-zero address on success.
**
** On success, the address corresponds to the pointer
** which can access the data allocated via the vcsm_malloc
** call.
*/
void *vcsm_usr_address( unsigned int handle )
{
   int rc;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or handle!",
                      __func__,
                      getpid() );

      return NULL;
   }

   if (using_vc_sm_cma)
   {
      //No need to lock the buffer, but need to retrieve the user address
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem || !elem->mem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. elem %p\n",
                      __func__, handle, elem);
         return NULL;
      }

      return elem->mem;
   }
   else
   {
      struct vmcs_sm_ioctl_map map;
      memset( &map, 0, sizeof(map) );

      map.pid    = getpid();
      map.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_USR_ADDRESS,
                  &map );

      if ( rc < 0 )
      {
         vcos_log_error( "[%s]: [%d]: ioctl mapped-usr-address FAILED [%d] (pid: %d, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.pid,
                         map.addr );

         return NULL;
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-address %d (hdl: %x, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.handle,
                         map.addr );

         return (void*)(uintptr_t)map.addr;
      }
   }
}


/* Retrieves a user opaque handle from a mapped user address
** pointer.
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
*/
unsigned int vcsm_usr_handle( void *usr_ptr )
{
   int rc;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (usr_ptr == NULL) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or null usr-ptr!",
                      __func__,
                      getpid() );

      return 0;
   }

   if (using_vc_sm_cma)
   {
      //No need to lock the buffer, but need to retrieve the user address
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_mem(usr_ptr);

      if (!elem || !elem->mem)
      {
         vcos_log_trace( "[%s]: usr_ptr %p not tracked, or not mapped. elem %p\n",
                      __func__, usr_ptr, elem);
         return 0;
      }

      return elem->handle;
   }
   else
   {
      struct vmcs_sm_ioctl_map map;

      memset( &map, 0, sizeof(map) );

      map.pid = getpid();
      map.addr = (uintptr_t) usr_ptr;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_USR_HDL,
                  &map );

      if ( rc < 0 )
      {
         vcos_log_error( "[%s]: [%d]: ioctl mapped-usr-hdl FAILED [%d] (pid: %d, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.pid,
                         map.addr );

         return 0;
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-hdl %d (hdl: %x, addr: %x)",
                         __func__,
                         getpid(),
                         rc,
                         map.handle,
                         map.addr );

         return map.handle;
      }
   }
}


/* Locks the memory associated with this opaque handle.
**
** Returns:        NULL on error
**                 a valid pointer on success.
**
** A user MUST lock the handle received from vcsm_malloc
** in order to be able to use the memory associated with it.
**
** On success, the pointer returned is only valid within
** the lock content (ie until a corresponding vcsm_unlock_xx
** is invoked).
*/
void *vcsm_lock( unsigned int handle )
{
   int rc;
   void *usr_ptr = NULL;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      goto out;
   }

   if (using_vc_sm_cma)
   {
      //No need to lock the buffer, but need to retrieve the user address
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem || !elem->mem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. elem %p\n",
                      __func__, handle, elem);
         goto out;
      }

      usr_ptr = elem->mem;

      {
         struct dma_buf_sync sync;

         //Now sync the buffer
         sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;
         rc = ioctl( elem->fd,
                     DMA_BUF_IOCTL_SYNC,
                     &sync );
         if ( rc < 0 )
         {
            vcos_log_trace( "[%s]: [%d]: ioctl DMA_BUF_IOCTL_SYNC failed, rc %d",
                      __func__,
                      getpid(),
                      rc);
         }
      }
      vcos_log_trace( "[%s]: [%d]: ioctl size-usr-hdl %d (hdl: %x) - addr %p",
                      __func__,
                      getpid(),
                      rc,
                      handle,
                      usr_ptr);
   }
   else
   {
      struct vmcs_sm_ioctl_lock_unlock lock_unlock;
      struct vmcs_sm_ioctl_size sz;
      struct vmcs_sm_ioctl_map map;
      struct vmcs_sm_ioctl_cache cache;

      memset( &sz, 0, sizeof(sz) );
      memset( &lock_unlock, 0, sizeof(lock_unlock) );
      memset( &map, 0, sizeof(map) );
      memset( &cache, 0, sizeof(cache) );

      /* Verify what we want is valid. */
      sz.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_SIZE_USR_HDL,
                  &sz );

      vcos_log_trace( "[%s]: [%d]: ioctl size-usr-hdl %d (hdl: %x) - size %u",
                      __func__,
                      getpid(),
                      rc,
                      sz.handle,
                      sz.size );

      /* We will not be able to lock the resource! */
      if ( (rc < 0) || (sz.size == 0) )
      {
         goto out;
      }

      /* Lock the allocated buffer all the way through videocore. */
      lock_unlock.handle    = sz.handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_LOCK,
                  &lock_unlock );

      vcos_log_trace( "[%s]: [%d]: ioctl mem-lock %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      lock_unlock.handle );

      /* We will not be able to lock the resource!
      */
      if ( rc < 0 )
      {
         goto out;
      }

      usr_ptr = (void *) (uintptr_t)lock_unlock.addr;

      /* If applicable, invalidate the cache now.
      */
      if ( usr_ptr && sz.size )
      {
         cache.handle = sz.handle;
         cache.addr   = (uintptr_t) usr_ptr;
         cache.size   = sz.size;

         rc = ioctl( vcsm_handle,
                     VMCS_SM_IOCTL_MEM_INVALID,
                     &cache );

         vcos_log_trace( "[%s]: [%d]: ioctl invalidate (cache) %d (hdl: %x, addr: %x, size: %u)",
                         __func__,
                         getpid(),
                         rc,
                         cache.handle,
                         cache.addr,
                         cache.size );

         if ( rc < 0 )
         {
            vcos_log_error( "[%s]: [%d]: invalidate failed (rc: %d) - [%x;%x] - size: %u (hdl: %x) - cache incoherency",
                            __func__,
                            getpid(),
                            rc,
                            (unsigned int) cache.addr,
                            (unsigned int) (cache.addr + cache.size),
                            (unsigned int) (cache.addr + cache.size) - (unsigned int) cache.addr,
                            cache.handle );
         }
      }
   }

out:
   return usr_ptr;
}


/* Locks the memory associated with this opaque handle.  The lock
** also gives a chance to update the *host* cache behavior of the
** allocated buffer if so desired.  The *videocore* cache behavior
** of the allocated buffer cannot be changed by this call and such
** attempt will be ignored.
**
** The system will attempt to honour the cache_update mode request,
** the cache_result mode will provide the final answer on which cache
** mode is really in use.  Failing to change the cache mode will not
** result in a failure to lock the buffer as it is an application
** decision to choose what to do if (cache_result != cache_update)
**
** The value returned in cache_result can only be considered valid if
** the returned pointer is non NULL.  The cache_result pointer may be
** NULL if the application does not care about the actual outcome of
** its action with regards to the cache behavior change.
**
** Returns:        NULL on error
**                 a valid pointer on success.
**
** A user MUST lock the handle received from vcsm_malloc
** in order to be able to use the memory associated with it.
**
** On success, the pointer returned is only valid within
** the lock content (ie until a corresponding vcsm_unlock_xx
** is invoked).
*/
void *vcsm_lock_cache( unsigned int handle,
                       VCSM_CACHE_TYPE_T cache_update,
                       VCSM_CACHE_TYPE_T *cache_result )
{
   int rc;
   struct vmcs_sm_ioctl_lock_cache lock_cache;
   struct vmcs_sm_ioctl_chk chk;
   struct vmcs_sm_ioctl_map map;
   struct vmcs_sm_ioctl_cache cache;
   struct vmcs_sm_ioctl_size sz;
   void *usr_ptr = NULL;
   VCSM_CACHE_TYPE_T new_cache;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      goto out;
   }

   if (using_vc_sm_cma)
   {
      //FIXME: IMPLEMENT THIS
      vcos_log_error("[%s]: IMPLEMENT ME", __func__);
      return NULL;
   }
   else
   {
      memset( &chk, 0, sizeof(chk) );
      memset( &sz, 0, sizeof(sz) );
      memset( &lock_cache, 0, sizeof(lock_cache) );
      memset( &map, 0, sizeof(map) );
      memset( &cache, 0, sizeof(cache) );

      /* Verify what we want is valid.
      */
      chk.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_CHK_USR_HDL,
                  &chk );

      vcos_log_trace( "[%s]: [%d]: ioctl chk-usr-hdl %d (hdl: %x, addr: %x, sz: %u, cache: %d)",
                      __func__,
                      getpid(),
                      rc,
                      chk.handle,
                      chk.addr,
                      chk.size,
                      chk.cache );

      /* We will not be able to lock the resource!
      */
      if ( rc < 0 )
      {
         goto out;
      }

      /* Validate cache requirements.
      */
      if ( cache_update != (VCSM_CACHE_TYPE_T)chk.cache )
      {
         new_cache = vcsm_cache_table_lookup( (VCSM_CACHE_TYPE_T) chk.cache,
                                              cache_update );
         vcos_log_trace( "[%s]: [%d]: cache lookup hdl: %x: [cur %d ; req %d] -> new %d ",
                         __func__,
                         getpid(),
                         chk.handle,
                         (VCSM_CACHE_TYPE_T)chk.cache,
                         cache_update,
                         new_cache );

         if ( (enum vmcs_sm_cache_e)new_cache == chk.cache )
         {
            /* Effectively no change.
            */
            if ( cache_result != NULL )
            {
               *cache_result = new_cache;
            }
            goto lock_default;
         }
      }
      else
      {
         if ( cache_result != NULL )
         {
            *cache_result = (VCSM_CACHE_TYPE_T)chk.cache;
         }
         goto lock_default;
      }

      /* At this point we know we want to lock the buffer and apply a cache
      ** behavior change.  Start by cleaning out whatever is already setup.
      */
      if ( chk.addr && chk.size )
      {
         munmap( (void *)(uintptr_t)chk.addr, chk.size );

         vcos_log_trace( "[%s]: [%d]: ioctl unmap hdl: %x",
                         __func__,
                         getpid(),
                         chk.handle );
      }

      /* Lock and apply cache behavior change to the allocated buffer all the
      ** way through videocore.
      */
      lock_cache.handle    = chk.handle;
      lock_cache.cached    = (enum vmcs_sm_cache_e) new_cache;   /* Convenient one to one mapping. */

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_LOCK_CACHE,
                  &lock_cache );

      vcos_log_trace( "[%s]: [%d]: ioctl mem-lock-cache %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      lock_cache.handle );

      /* We will not be able to lock the resource!
      */
      if ( rc < 0 )
      {
         goto out;
      }

      /* It is possible that this size was zero if the resource was
      ** already un-mapped when we queried it, in such case we need
      ** to figure out the size now to allow mapping to work.
      */
      if ( chk.size == 0 )
      {
         sz.handle = chk.handle;

         rc = ioctl( vcsm_handle,
                     VMCS_SM_IOCTL_SIZE_USR_HDL,
                     &sz );

         vcos_log_trace( "[%s]: [%d]: ioctl size-usr-hdl %d (hdl: %x) - size %u",
                         __func__,
                         getpid(),
                         rc,
                         sz.handle,
                         sz.size );

         /* We will not be able to map again the resource!
         */
         if ( (rc < 0) || (sz.size == 0) )
         {
            goto out;
         }
      }

      /* Map the locked buffer into user space.
      */
      usr_ptr = mmap( 0,
                      (chk.size != 0) ? chk.size : sz.size,
                      PROT_READ | PROT_WRITE,
                      MAP_SHARED,
                      vcsm_handle,
                      chk.handle );

      if ( usr_ptr == NULL )
      {
         vcos_log_error( "[%s]: [%d]: mmap FAILED (hdl: %x)",
                         __func__,
                         getpid(),
                         chk.handle );
      }

      /* If applicable, invalidate the cache now.
      */
      cache.size   = (chk.size != 0) ? chk.size : sz.size;
      if ( usr_ptr && cache.size )
      {
         cache.handle = chk.handle;
         cache.addr   = (uintptr_t) usr_ptr;

         rc = ioctl( vcsm_handle,
                     VMCS_SM_IOCTL_MEM_INVALID,
                     &cache );

         vcos_log_trace( "[%s]: [%d]: ioctl invalidate (cache) %d (hdl: %x, addr: %x, size: %u)",
                         __func__,
                         getpid(),
                         rc,
                         cache.handle,
                         cache.addr,
                         cache.size );

         if ( rc < 0 )
         {
            vcos_log_error( "[%s]: [%d]: invalidate failed (rc: %d) - [%x;%x] - size: %u (hdl: %x) - cache incoherency",
                            __func__,
                            getpid(),
                            rc,
                            (unsigned int) cache.addr,
                            (unsigned int) (cache.addr + cache.size),
                            (unsigned int) (cache.addr + cache.size) - (unsigned int) cache.addr,
                            cache.handle );
         }
      }

      /* Update the caller with the information it expects to see.
      */
      if ( cache_result != NULL )
      {
         *cache_result = new_cache;
      }

      /* Done.
      */
      goto out;
   }


lock_default:
   usr_ptr = vcsm_lock ( handle );

out:
   return usr_ptr;
}


/* Unlocks the memory associated with this user mapped address.
** Apply special processing that would override the otherwise
** default behavior.
**
** If 'cache_no_flush' is specified:
**    Do not flush cache as the result of the unlock (if cache
**    flush was otherwise applicable in this case).
**
** Returns:        0 on success
**                 -errno on error.
**
** After unlocking a mapped address, the user should no longer
** attempt to reference it.
*/
int vcsm_unlock_ptr_sp( void *usr_ptr, int cache_no_flush )
{
   int rc;
   struct vmcs_sm_ioctl_lock_unlock lock_unlock;
   struct vmcs_sm_ioctl_map map;
   struct vmcs_sm_ioctl_cache cache;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (usr_ptr == NULL) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid user-ptr!",
                      __func__,
                      getpid() );

      rc = -EIO;
      goto out;
   }

   if (using_vc_sm_cma)
   {
      struct dma_buf_sync sync;
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_mem(usr_ptr);

      if (!elem)
      {
         vcos_log_trace( "[%s]: addr %p not tracked, or not mapped. elem %p\n",
                      __func__, usr_ptr, elem);
         rc = -EINVAL;
         goto out;
      }

      if (!cache_no_flush)
      {
         sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;
         rc = ioctl( elem->fd,
                     DMA_BUF_IOCTL_SYNC,
                     &sync );
         if ( rc < 0 )
         {
            vcos_log_trace( "[%s]: [%d]: ioctl DMA_BUF_IOCTL_SYNC failed, rc %d",
                      __func__,
                      getpid(),
                      rc);
         }
      }
      else
         rc = 0;
   }
   else
   {
      memset( &map, 0, sizeof(map) );
      memset( &lock_unlock, 0, sizeof(lock_unlock) );
      memset( &cache, 0, sizeof(cache) );

      /* Retrieve the handle of the memory we want to unlock.
      */
      map.pid = getpid();
      map.addr = (uintptr_t) usr_ptr;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MAP_USR_HDL,
                  &map );

      vcos_log_trace( "[%s]: [%d]: ioctl mapped-usr-hdl %d (hdl: %x, addr: %x, sz: %u)",
                      __func__,
                      getpid(),
                      rc,
                      map.handle,
                      map.addr,
                      map.size );

      /* We will not be able to flush/unlock the resource!
      */
      if ( rc < 0 )
      {
         goto out;
      }

      /* If applicable, flush the cache now.
      */
      if ( !cache_no_flush && map.addr && map.size )
      {
         cache.handle = map.handle;
         cache.addr   = map.addr;
         cache.size   = map.size;

         rc = ioctl( vcsm_handle,
                     VMCS_SM_IOCTL_MEM_FLUSH,
                     &cache );

         vcos_log_trace( "[%s]: [%d]: ioctl flush (cache) %d (hdl: %x, addr: %x, size: %u)",
                         __func__,
                         getpid(),
                         rc,
                         cache.handle,
                         cache.addr,
                         cache.size );

         if ( rc < 0 )
         {
            vcos_log_error( "[%s]: [%d]: flush failed (rc: %d) - [%x;%x] - size: %u (hdl: %x) - cache incoherency",
                            __func__,
                            getpid(),
                            rc,
                            (unsigned int) cache.addr,
                            (unsigned int) (cache.addr + cache.size),
                            (unsigned int) (cache.addr + cache.size) - (unsigned int) cache.addr,
                            cache.handle );
         }
      }

      /* Unock the allocated buffer all the way through videocore.
      */
      lock_unlock.handle    = map.handle;  /* From above ioctl. */

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_UNLOCK,
                  &lock_unlock );

      vcos_log_trace( "[%s]: [%d]: ioctl mem-unlock %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      lock_unlock.handle );
   }

out:
   return rc;
}


/* Unlocks the memory associated with this user mapped address.
**
** Returns:        0 on success
**                 -errno on error.
**
** After unlocking a mapped address, the user should no longer
** attempt to reference it.
*/
int vcsm_unlock_ptr( void *usr_ptr )
{
   return vcsm_unlock_ptr_sp( usr_ptr, 0 );
}


/* Unlocks the memory associated with this user opaque handle.
** Apply special processing that would override the otherwise
** default behavior.
**
** If 'cache_no_flush' is specified:
**    Do not flush cache as the result of the unlock (if cache
**    flush was otherwise applicable in this case).
**
** Returns:        0 on success
**                 -errno on error.
**
** After unlocking an opaque handle, the user should no longer
** attempt to reference the mapped addressed once associated
** with it.
*/
int vcsm_unlock_hdl_sp( unsigned int handle, int cache_no_flush )
{
   int rc;
   struct vmcs_sm_ioctl_lock_unlock lock_unlock;
   struct vmcs_sm_ioctl_chk chk;
   struct vmcs_sm_ioctl_cache cache;
   struct vmcs_sm_ioctl_map map;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      rc = -EIO;
      goto out;
   }

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *elem;
      struct dma_buf_sync sync;

      elem = vcsm_payload_list_find_handle(handle);

      if (!elem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. elem %p\n",
                      __func__, handle, elem);
         rc = -EINVAL;
         goto out;
      }

      sync.flags = DMA_BUF_SYNC_END;
      if (!cache_no_flush)
      {
         sync.flags |= DMA_BUF_SYNC_RW;
      }

      rc = ioctl( elem->fd, DMA_BUF_IOCTL_SYNC, &sync );
      if ( rc < 0 )
      {
         vcos_log_trace( "[%s]: [%d]: ioctl DMA_BUF_IOCTL_SYNC failed, rc %d",
                   __func__,
                   getpid(),
                   rc);
      }
   }
   else
   {
      memset( &chk, 0, sizeof(chk) );
      memset( &lock_unlock, 0, sizeof(lock_unlock) );
      memset( &cache, 0, sizeof(cache) );
      memset( &map, 0, sizeof(map) );

      /* Retrieve the handle of the memory we want to lock.
      */
      chk.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_CHK_USR_HDL,
                  &chk );

      vcos_log_trace( "[%s]: [%d]: ioctl chk-usr-hdl %d (hdl: %x, addr: %x, sz: %u) nf %d",
                      __func__,
                      getpid(),
                      rc,
                      chk.handle,
                      chk.addr,
                      chk.size,
                      cache_no_flush);

      /* We will not be able to flush/unlock the resource!
      */
      if ( rc < 0 )
      {
         goto out;
      }

      /* If applicable, flush the cache now.
      */
      if ( !cache_no_flush && chk.addr && chk.size )
      {
         cache.handle = chk.handle;
         cache.addr   = chk.addr;
         cache.size   = chk.size;

         rc = ioctl( vcsm_handle,
                     VMCS_SM_IOCTL_MEM_FLUSH,
                     &cache );

         vcos_log_trace( "[%s]: [%d]: ioctl flush (cache) %d (hdl: %x)",
                         __func__,
                         getpid(),
                         rc,
                         cache.handle );

         if ( rc < 0 )
         {
            vcos_log_error( "[%s]: [%d]: flush failed (rc: %d) - [%x;%x] - size: %u (hdl: %x) - cache incoherency",
                            __func__,
                            getpid(),
                            rc,
                            (unsigned int) cache.addr,
                            (unsigned int) (cache.addr + cache.size),
                            (unsigned int) (cache.addr + cache.size) - (unsigned int) cache.addr,
                            cache.handle );
         }
      }

      /* Unlock the allocated buffer all the way through videocore.
      */
      lock_unlock.handle    = chk.handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_UNLOCK,
                  &lock_unlock );

      vcos_log_trace( "[%s]: [%d]: ioctl mem-unlock %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      lock_unlock.handle );
   }

out:
   return rc;
}


/* Unlocks the memory associated with this user opaque handle.
**
** Returns:        0 on success
**                 -errno on error.
**
** After unlocking an opaque handle, the user should no longer
** attempt to reference the mapped addressed once associated
** with it.
*/
int vcsm_unlock_hdl( unsigned int handle )
{
   return vcsm_unlock_hdl_sp( handle, 0 );
}

/* Resizes a block of memory allocated previously by vcsm_alloc.
**
** Returns:        0 on success
**                 -errno on error.
**
** The handle must be unlocked by user prior to attempting any
** resize action.
**
** On error, the original size allocated against the handle
** remains available the same way it would be following a
** successful vcsm_malloc.
*/
int vcsm_resize( unsigned int handle, unsigned int new_size )
{
   int rc;
   struct vmcs_sm_ioctl_size sz;
   struct vmcs_sm_ioctl_resize resize;
   struct vmcs_sm_ioctl_lock_unlock lock_unlock;
   struct vmcs_sm_ioctl_map map;
   unsigned int size_aligned = new_size;
   void *usr_ptr = NULL;

   if ( (vcsm_handle == VCSM_INVALID_HANDLE) || (handle == 0) ) 
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      rc = -EIO;
      goto out;
   }

   if (using_vc_sm_cma)
   {
      //Not supported with CMA
      rc = -EFAULT;
   }
   else
   {
      memset( &sz, 0, sizeof(sz) );
      memset( &resize, 0, sizeof(resize) );
      memset( &lock_unlock, 0, sizeof(lock_unlock) );
      memset( &map, 0, sizeof(map) );

      /* Ask for page aligned.
      */
      size_aligned = (new_size + vcsm_page_size - 1) & ~(vcsm_page_size - 1);

      /* Verify what we want is valid.
      */
      sz.handle = handle;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_SIZE_USR_HDL,
                  &sz );

      vcos_log_trace( "[%s]: [%d]: ioctl size-usr-hdl %d (hdl: %x) - size %u",
                      __func__,
                      getpid(),
                      rc,
                      sz.handle,
                      sz.size );

      /* We will not be able to free up the resource!
      **
      ** However, the driver will take care of it eventually once the device is
      ** closed (or dies), so this is not such a dramatic event...
      */
      if ( (rc < 0) || (sz.size == 0) )
      {
         goto out;
      }

      /* We first need to unmap the resource
      */
      usr_ptr = (void *) vcsm_usr_address( sz.handle );
      if ( usr_ptr != NULL )
      {
         munmap( usr_ptr, sz.size );

         vcos_log_trace( "[%s]: [%d]: ioctl unmap hdl: %x",
                         __func__,
                         getpid(),
                         sz.handle );
      }
      else
      {
         vcos_log_trace( "[%s]: [%d]: freeing unmapped area (hdl: %x)",
                         __func__,
                         getpid(),
                         map.handle );
      }

      /* Resize the allocated buffer all the way through videocore.
      */
      resize.handle    = sz.handle;
      resize.new_size  = size_aligned;

      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_RESIZE,
                  &resize );

      vcos_log_trace( "[%s]: [%d]: ioctl resize %d (hdl: %x)",
                      __func__,
                      getpid(),
                      rc,
                      resize.handle );

      /* Although resized, the resource will not be usable.
      */
      if ( rc < 0 )
      {
         goto out;
      }

      /* Remap the resource
      */
      if (  mmap( 0,
                  resize.new_size,
                  PROT_READ | PROT_WRITE,
                  MAP_SHARED,
                  vcsm_handle,
                  resize.handle ) == NULL )
      {
         vcos_log_error( "[%s]: [%d]: mmap FAILED (hdl: %x)",
                         __func__,
                         getpid(),
                         resize.handle );

         /* At this point, it is not yet a problem that we failed to
         ** map the buffer because it will not be used right away.
         **
         ** Possibly the mapping may work the next time the user tries
         ** to lock the buffer for usage, and if it still fails, it will
         ** be up to the user to deal with it.
         */
      }
   }

out:
   return rc;
}


/* Flush or invalidate the memory associated with this user opaque handle
**
** Returns:        non-zero on error
**
** structure contains a list of flush/invalidate commands
** See header file
*/
int vcsm_clean_invalid( struct vcsm_user_clean_invalid_s *s )
{
   int rc = 0;
   struct vmcs_sm_ioctl_clean_invalid cache;

   if ( vcsm_handle == VCSM_INVALID_HANDLE )
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      return -1;
   }

   memcpy( &cache, s, sizeof cache );

   if (using_vc_sm_cma)
   {
      // Deprecated. The API is not compatible with 64-bit support (addr is an
      // unsigned int), therefore please update to vcsm_clean_invalid2.
      rc = -1;
   }
   else
   {
      rc = ioctl( vcsm_handle,
                   VMCS_SM_IOCTL_MEM_CLEAN_INVALID,
                   &cache );
   }

   return rc;
}

/* Flush or invalidate the memory associated with this user opaque handle
**
** Returns:        non-zero on error
**
** structure contains a list of flush/invalidate commands
** See header file
*/
int vcsm_clean_invalid2( struct vcsm_user_clean_invalid2_s *s )
{
   int rc = 0;

   if ( vcsm_handle == VCSM_INVALID_HANDLE )
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      return -1;
   }

   if (using_vc_sm_cma)
   {
      struct vc_sm_cma_ioctl_clean_invalid2 *vcsm_s;
      int i;

      vcsm_s = (struct vc_sm_cma_ioctl_clean_invalid2 *)malloc(sizeof(*vcsm_s) + sizeof(struct vc_sm_cma_ioctl_clean_invalid_block)*s->op_count);
      if (!vcsm_s)
         return -1;

      vcsm_s->op_count = s->op_count;
      for (i = 0; i < vcsm_s->op_count; i++)
      {
         vcsm_s->s[i].invalidate_mode = s->s[i].invalidate_mode;
         vcsm_s->s[i].block_count = s->s[i].block_count;
         vcsm_s->s[i].start_address = s->s[i].start_address;
         vcsm_s->s[i].block_size = s->s[i].block_size;
         vcsm_s->s[i].inter_block_stride = s->s[i].inter_block_stride;
      }

      rc = ioctl( vcsm_handle,
                   VC_SM_CMA_IOCTL_MEM_CLEAN_INVALID2,
                   vcsm_s );
      free(vcsm_s);
   }
   else
   {
      rc = ioctl( vcsm_handle,
                   VMCS_SM_IOCTL_MEM_CLEAN_INVALID2,
                   s );
   }

   return rc;
}

/* Imports a dmabuf, and binds it to a VCSM handle and MEM_HANDLE_T
**
** Returns:        0 on error
**                 a non-zero opaque handle on success.
**
** On success, the user must invoke vcsm_lock with the returned opaque
** handle to gain access to the memory associated with the opaque handle.
** When finished using the memory, the user calls vcsm_unlock_xx (see those
** function definition for more details on the one that can be used).
** Use vcsm_release to detach from the dmabuf (VideoCore may still hold
** a reference to the buffer until it has finished with the buffer).
**
*/
unsigned int vcsm_import_dmabuf( int dmabuf, const char *name )
{
   int rc;
   unsigned int handle = 0;

   if ( vcsm_handle == VCSM_INVALID_HANDLE )
   {
      vcos_log_error( "[%s]: [%d]: invalid device or invalid handle!",
                      __func__,
                      getpid() );

      return -1;
   }

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *payload;
      struct vc_sm_cma_ioctl_import_dmabuf import;
      memset( &import, 0, sizeof(import) );

      /* Map the buffer on videocore via the VCSM (Videocore Shared Memory) interface. */
      import.dmabuf_fd = dmabuf;
      import.cached = VMCS_SM_CACHE_NONE; //Support no caching for now - makes it easier for cache management
      if ( name != NULL )
      {
         memcpy ( import.name, name, 32 );
      }
      rc = ioctl( vcsm_handle,
                  VC_SM_CMA_IOCTL_MEM_IMPORT_DMABUF,
                  &import );

      if ( rc < 0 || import.handle < 0 )
      {
         vcos_log_error( "[%s]: [%d] [%s]: ioctl mem-import-dmabuf FAILED [%d] (hdl: %x)",
                         __func__,
                         getpid(),
                         import.name,
                         rc,
                         import.handle );
      }
      else
      {
         /* Map the buffer into user space.
         */
         vcos_log_trace( "[%s]: mapping fd %d, imported from fd %d\n", __func__, import.handle, dmabuf);
         void *usr_ptr = mmap( 0,
                         import.size,
                         PROT_READ | PROT_WRITE,
                         MAP_SHARED,
                         import.handle,
                         0 );

         if ( usr_ptr == MAP_FAILED )
         {
            vcos_log_error( "[%s]: [%d]: mmap FAILED (hdl: %x), size %u",
                         __func__,
                         getpid(),
                         import.handle, import.size );
            vcsm_free( import.handle );
            return 0;
         }


         vcos_log_trace( "[%s]: mmap to %p",
                         __func__,
                         usr_ptr
                         );

         //vc-sm-cma now hands out file handles (signed int), whilst libvcsm is 
         //handling unsigned int handles. Already checked the handle >=0, so
         //add one to make it a usable handle.
         handle = import.handle + 1;


        vcos_log_trace( "[%s]: [%d] [%s]: ioctl mem-import-dmabuf %d (dmabuf %d imported as hdl: %x)",
                        __func__,
                        getpid(),
                        import.name,
                        rc,
                        dmabuf,
                        import.handle );

         payload = vcsm_payload_list_get();
         payload->handle = handle;
         payload->fd = import.handle;
         payload->vc_handle = import.vc_handle;
         payload->mem = usr_ptr;
         payload->size = import.size;
         if (import.dma_addr & 0xFFFFFFFF00000000ULL)
         {
            vcos_log_error("[%s]: dma address returned > 32bit 0x%llx", __func__, import.dma_addr);
            payload->dma_addr = 0;
         }
         else
            payload->dma_addr = (uint32_t)import.dma_addr;
      }
   }
   else
   {
      struct vmcs_sm_ioctl_import_dmabuf import;
      memset( &import, 0, sizeof(import) );

      /* Map the buffer on videocore via the VCSM (Videocore Shared Memory) interface. */
      import.dmabuf_fd = dmabuf;
      import.cached = VMCS_SM_CACHE_NONE; //Support no caching for now - makes it easier for cache management
      if ( name != NULL )
      {
         memcpy ( import.name, name, 32 );
      }
      rc = ioctl( vcsm_handle,
                  VMCS_SM_IOCTL_MEM_IMPORT_DMABUF,
                  &import );

      if ( rc < 0 || import.handle == 0 )
      {
         vcos_log_error( "[%s]: [%d] [%s]: ioctl mem-import-dmabuf FAILED [%d] (hdl: %x)",
                         __func__,
                         getpid(),
                         import.name,
                         rc,
                         import.handle );
      }
      else
      {
         handle = import.handle;
      }

      vcos_log_trace( "[%s]: [%d] [%s]: ioctl mem-import-dmabuf hdl %d rc %d (vcsm hdl: %x)",
                      __func__,
                      getpid(),
                      import.name,
                      dmabuf,
                      rc,
                      import.handle );
   }

   return handle;
}

/* Exports a vcsm handle as a dmabuf.
**
** Returns:        <0 on error
**                 a file descriptor to the dmabuf on success.
**
** The allocation will persist until the file descriptor is closed,
** even if the vcsm handle is released.
**
*/
int vcsm_export_dmabuf( unsigned int vcsm_handle )
{
   int handle = -1;

   if (using_vc_sm_cma)
   {
      VCSM_PAYLOAD_ELEM_T *elem;

      elem = vcsm_payload_list_find_handle(vcsm_handle);

      if (!elem)
      {
         vcos_log_trace( "[%s]: handle %u not tracked, or not mapped. elem %p\n",
                      __func__, handle, elem);
         return -1;
      }

      //Duplicate the existing fd.
      handle = dup(elem->fd);
   }
   else
   {
      //Not supported on the old vcsm kernel driver.
      handle = -1;
   }
   return handle;
}
