/**************************************************************************
 * 
 * Copyright 2006 Tungsten Graphics, Inc., Bismarck, ND., USA
 * All Rights Reserved.
 * 
 * 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, sub license, 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 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 NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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 above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 * 
 * 
 **************************************************************************/
/*
 * Authors: Thomas Hellstr�m <thomas-at-tungstengraphics-dot-com>
 *          Keith Whitwell <keithw-at-tungstengraphics-dot-com>
 */

#include <xf86drm.h>
#include <stdlib.h>
#include "glthread.h"
#include "errno.h"
#include "dri_bufmgr.h"
#include "string.h"
#include "imports.h"
#include "dri_bufpool.h"

_glthread_DECLARE_STATIC_MUTEX(bmMutex);

/*
 * TODO: Introduce fence pools in the same way as 
 * buffer object pools.
 */



typedef struct _DriFenceObject
{
   int fd;
   _glthread_Mutex mutex;
   int refCount;
   const char *name;
   drmFence fence;
} DriFenceObject;

typedef struct _DriBufferObject
{
   DriBufferPool *pool;
   _glthread_Mutex mutex;
   int refCount;
   const char *name;
   unsigned flags;
   unsigned hint;
   unsigned alignment;
   void *private;
   /* user-space buffer: */
   unsigned userBuffer;
   void *userData;
   unsigned userSize;
} DriBufferObject;


void
bmError(int val, const char *file, const char *function, int line)
{
   _mesa_printf("Fatal video memory manager error \"%s\".\n"
                "Check kernel logs or set the LIBGL_DEBUG\n"
                "environment variable to \"verbose\" for more info.\n"
                "Detected in file %s, line %d, function %s.\n",
                strerror(-val), file, line, function);
#ifndef NDEBUG
   abort();
#else
   abort();
#endif
}

DriFenceObject *
driFenceBuffers(int fd, char *name, unsigned flags)
{
   DriFenceObject *fence = (DriFenceObject *) malloc(sizeof(*fence));
   int ret;

   if (!fence)
      BM_CKFATAL(-EINVAL);

   _glthread_LOCK_MUTEX(bmMutex);
   fence->refCount = 1;
   fence->name = name;
   fence->fd = fd;
   _glthread_INIT_MUTEX(fence->mutex);
   ret = drmFenceBuffers(fd, flags, &fence->fence);
   _glthread_UNLOCK_MUTEX(bmMutex);
   if (ret) {
      free(fence);
      BM_CKFATAL(ret);
   }
   return fence;
}


unsigned 
driFenceType(DriFenceObject * fence)
{
    unsigned ret;

    _glthread_LOCK_MUTEX(bmMutex);
    ret = fence->fence.flags;
    _glthread_UNLOCK_MUTEX(bmMutex);
    
    return ret;
}


DriFenceObject *
driFenceReference(DriFenceObject * fence)
{
   _glthread_LOCK_MUTEX(bmMutex);
   ++fence->refCount;
   _glthread_UNLOCK_MUTEX(bmMutex);
   return fence;
}

void
driFenceUnReference(DriFenceObject * fence)
{
   if (!fence)
      return;

   _glthread_LOCK_MUTEX(bmMutex);
   if (--fence->refCount == 0) {
      drmFenceDestroy(fence->fd, &fence->fence);
      free(fence);
   }
   _glthread_UNLOCK_MUTEX(bmMutex);
}

void
driFenceFinish(DriFenceObject * fence, unsigned type, int lazy)
{
   int ret;
   unsigned flags = (lazy) ? DRM_FENCE_FLAG_WAIT_LAZY : 0;

   _glthread_LOCK_MUTEX(fence->mutex);
   ret = drmFenceWait(fence->fd, flags, &fence->fence, type);
   _glthread_UNLOCK_MUTEX(fence->mutex);
   BM_CKFATAL(ret);
}

int
driFenceSignaled(DriFenceObject * fence, unsigned type)
{
   int signaled;
   int ret;

   if (fence == NULL)
      return GL_TRUE;

   _glthread_LOCK_MUTEX(fence->mutex);
   ret = drmFenceSignaled(fence->fd, &fence->fence, type, &signaled);
   _glthread_UNLOCK_MUTEX(fence->mutex);
   BM_CKFATAL(ret);
   return signaled;
}


extern drmBO *
driBOKernel(struct _DriBufferObject *buf)
{
   drmBO *ret;

   assert(buf->private != NULL);
   ret = buf->pool->kernel(buf->pool, buf->private);
   if (!ret)
      BM_CKFATAL(-EINVAL);

   return ret;
}

void
driBOWaitIdle(struct _DriBufferObject *buf, int lazy)
{
   struct _DriBufferPool *pool;
   void *priv;

   _glthread_LOCK_MUTEX(buf->mutex);
   pool = buf->pool;
   priv = buf->private;
   _glthread_UNLOCK_MUTEX(buf->mutex);
   
   assert(priv != NULL);
   BM_CKFATAL(buf->pool->waitIdle(pool, priv, lazy));
}

void *
driBOMap(struct _DriBufferObject *buf, unsigned flags, unsigned hint)
{
   if (buf->userBuffer) {
      return buf->userData;
   }
   else {
      void *virtual;

      assert(buf->private != NULL);

      _glthread_LOCK_MUTEX(buf->mutex);
      BM_CKFATAL(buf->pool->map(buf->pool, buf->private, flags, hint, &virtual));
      _glthread_UNLOCK_MUTEX(buf->mutex);
      return virtual;
   }
}

void
driBOUnmap(struct _DriBufferObject *buf)
{
   if (!buf->userBuffer) {
      assert(buf->private != NULL);

      buf->pool->unmap(buf->pool, buf->private);
   }
}

unsigned long
driBOOffset(struct _DriBufferObject *buf)
{
   unsigned long ret;

   assert(buf->private != NULL);

   _glthread_LOCK_MUTEX(buf->mutex);
   ret = buf->pool->offset(buf->pool, buf->private);
   _glthread_UNLOCK_MUTEX(buf->mutex);
   return ret;
}

unsigned
driBOFlags(struct _DriBufferObject *buf)
{
   unsigned ret;

   assert(buf->private != NULL);

   _glthread_LOCK_MUTEX(buf->mutex);
   ret = buf->pool->flags(buf->pool, buf->private);
   _glthread_UNLOCK_MUTEX(buf->mutex);
   return ret;
}

struct _DriBufferObject *
driBOReference(struct _DriBufferObject *buf)
{
   _glthread_LOCK_MUTEX(bmMutex);
   if (++buf->refCount == 1) {
      BM_CKFATAL(-EINVAL);
   }
   _glthread_UNLOCK_MUTEX(bmMutex);
   return buf;
}

void
driBOUnReference(struct _DriBufferObject *buf)
{
   int tmp;

   if (!buf)
      return;

   _glthread_LOCK_MUTEX(bmMutex);
   tmp = --buf->refCount;
   _glthread_UNLOCK_MUTEX(bmMutex);
   if (!tmp) {
      if (buf->private)
         buf->pool->destroy(buf->pool, buf->private);
      free(buf);
   }
}

void
driBOData(struct _DriBufferObject *buf,
          unsigned size, const void *data, unsigned flags)
{
   void *virtual;
   int newBuffer;
   struct _DriBufferPool *pool;

   assert(!buf->userBuffer); /* XXX just do a memcpy? */

   _glthread_LOCK_MUTEX(buf->mutex);
   pool = buf->pool;
   if (!pool->create) {
      _mesa_error(NULL, GL_INVALID_OPERATION,
                  "driBOData called on invalid buffer\n");
      BM_CKFATAL(-EINVAL);
   }
   newBuffer = !buf->private || (pool->size(pool, buf->private) < size) ||
      pool->map(pool, buf->private, DRM_BO_FLAG_WRITE,
                DRM_BO_HINT_DONT_BLOCK, &virtual);

   if (newBuffer) {
      if (buf->private)
         pool->destroy(pool, buf->private);
      if (!flags)
         flags = buf->flags;
      buf->private = pool->create(pool, size, flags, DRM_BO_HINT_DONT_FENCE, 
				  buf->alignment);
      if (!buf->private)
         BM_CKFATAL(-ENOMEM);
      BM_CKFATAL(pool->map(pool, buf->private,
                           DRM_BO_FLAG_WRITE,
                           DRM_BO_HINT_DONT_BLOCK, &virtual));
   }

   if (data != NULL)
      memcpy(virtual, data, size);

   BM_CKFATAL(pool->unmap(pool, buf->private));
   _glthread_UNLOCK_MUTEX(buf->mutex);
}

void
driBOSubData(struct _DriBufferObject *buf,
             unsigned long offset, unsigned long size, const void *data)
{
   void *virtual;

   assert(!buf->userBuffer); /* XXX just do a memcpy? */

   _glthread_LOCK_MUTEX(buf->mutex);
   if (size && data) {
      BM_CKFATAL(buf->pool->map(buf->pool, buf->private,
                                DRM_BO_FLAG_WRITE, 0, &virtual));
      memcpy((unsigned char *) virtual + offset, data, size);
      BM_CKFATAL(buf->pool->unmap(buf->pool, buf->private));
   }
   _glthread_UNLOCK_MUTEX(buf->mutex);
}

void
driBOGetSubData(struct _DriBufferObject *buf,
                unsigned long offset, unsigned long size, void *data)
{
   void *virtual;

   assert(!buf->userBuffer); /* XXX just do a memcpy? */

   _glthread_LOCK_MUTEX(buf->mutex);
   if (size && data) {
      BM_CKFATAL(buf->pool->map(buf->pool, buf->private,
                                DRM_BO_FLAG_READ, 0, &virtual));
      memcpy(data, (unsigned char *) virtual + offset, size);
      BM_CKFATAL(buf->pool->unmap(buf->pool, buf->private));
   }
   _glthread_UNLOCK_MUTEX(buf->mutex);
}

void
driBOSetStatic(struct _DriBufferObject *buf,
               unsigned long offset,
               unsigned long size, void *virtual, unsigned flags)
{
   assert(!buf->userBuffer); /* XXX what to do? */

   _glthread_LOCK_MUTEX(buf->mutex);
   if (buf->private != NULL) {
      _mesa_error(NULL, GL_INVALID_OPERATION,
                  "Invalid buffer for setStatic\n");
      BM_CKFATAL(-EINVAL);
   }
   if (buf->pool->setstatic == NULL) {
      _mesa_error(NULL, GL_INVALID_OPERATION,
                  "Invalid buffer pool for setStatic\n");
      BM_CKFATAL(-EINVAL);
   }

   if (!flags)
      flags = buf->flags;

   buf->private = buf->pool->setstatic(buf->pool, offset, size,
                                       virtual, flags);
   if (!buf->private) {
      _mesa_error(NULL, GL_OUT_OF_MEMORY,
                  "Invalid buffer pool for setStatic\n");
      BM_CKFATAL(-ENOMEM);
   }
   _glthread_UNLOCK_MUTEX(buf->mutex);
}



void
driGenBuffers(struct _DriBufferPool *pool,
              const char *name,
              unsigned n,
              struct _DriBufferObject *buffers[],
              unsigned alignment, unsigned flags, unsigned hint)
{
   struct _DriBufferObject *buf;
   int i;

   flags = (flags) ? flags : DRM_BO_FLAG_MEM_TT | DRM_BO_FLAG_MEM_VRAM |
      DRM_BO_FLAG_MEM_LOCAL | DRM_BO_FLAG_READ | DRM_BO_FLAG_WRITE;


   for (i = 0; i < n; ++i) {
      buf = (struct _DriBufferObject *) calloc(1, sizeof(*buf));
      if (!buf)
         BM_CKFATAL(-ENOMEM);

      _glthread_INIT_MUTEX(buf->mutex);
      _glthread_LOCK_MUTEX(buf->mutex);
      _glthread_LOCK_MUTEX(bmMutex);
      buf->refCount = 1;
      _glthread_UNLOCK_MUTEX(bmMutex);
      buf->flags = flags;
      buf->hint = hint;
      buf->name = name;
      buf->alignment = alignment;
      buf->pool = pool;
      _glthread_UNLOCK_MUTEX(buf->mutex);
      buffers[i] = buf;
   }
}

void
driGenUserBuffer(struct _DriBufferPool *pool,
                 const char *name,
                 struct _DriBufferObject **buffers,
                 void *ptr, unsigned bytes)
{
   const unsigned alignment = 1, flags = 0, hint = 0;

   driGenBuffers(pool, name, 1, buffers, alignment, flags, hint);

   (*buffers)->userBuffer = 1;
   (*buffers)->userData = ptr;
   (*buffers)->userSize = bytes;
}


void
driDeleteBuffers(unsigned n, struct _DriBufferObject *buffers[])
{
   int i;

   for (i = 0; i < n; ++i) {
      driBOUnReference(buffers[i]);
   }
}


void
driInitBufMgr(int fd)
{
   ;
}


void
driBOCreateList(int target, drmBOList * list)
{
   _glthread_LOCK_MUTEX(bmMutex);
   BM_CKFATAL(drmBOCreateList(target, list));
   _glthread_UNLOCK_MUTEX(bmMutex);
}

void
driBOResetList(drmBOList * list)
{
   _glthread_LOCK_MUTEX(bmMutex);
   BM_CKFATAL(drmBOResetList(list));
   _glthread_UNLOCK_MUTEX(bmMutex);
}

void
driBOAddListItem(drmBOList * list, struct _DriBufferObject *buf,
                 unsigned flags, unsigned mask)
{
   int newItem;

   _glthread_LOCK_MUTEX(buf->mutex);
   _glthread_LOCK_MUTEX(bmMutex);
   BM_CKFATAL(drmAddValidateItem(list, driBOKernel(buf),
                                 flags, mask, &newItem));
   _glthread_UNLOCK_MUTEX(bmMutex);

   /*
    * Tell userspace pools to validate the buffer. This should be a 
    * noop if the pool is already validated.
    * FIXME: We should have a list for this as well.
    */

   if (buf->pool->validate) {
      BM_CKFATAL(buf->pool->validate(buf->pool, buf->private));
   }

   _glthread_UNLOCK_MUTEX(buf->mutex);
}

void
driBOFence(struct _DriBufferObject *buf, struct _DriFenceObject *fence)
{
   _glthread_LOCK_MUTEX(buf->mutex);
   BM_CKFATAL(buf->pool->fence(buf->pool, buf->private, fence));
   _glthread_UNLOCK_MUTEX(buf->mutex);

}

void
driBOValidateList(int fd, drmBOList * list)
{
   _glthread_LOCK_MUTEX(bmMutex);
   BM_CKFATAL(drmBOValidateList(fd, list));
   _glthread_UNLOCK_MUTEX(bmMutex);
}

void
driPoolTakeDown(struct _DriBufferPool *pool)
{
   pool->takeDown(pool);

}