/**************************************************************************
 *
 * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Tx., USA
 * All Rights Reserved.
 * Copyright 2009 VMware, Inc., Palo Alto, CA., 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 above copyright notice and this permission notice (including the
 * next paragraph) 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 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.
 *
 **************************************************************************/
/*
 * Authors: Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "wsbm_fencemgr.h"
#include "wsbm_pool.h"
#include "wsbm_manager.h"
#include <xf86drm.h>
#include <drm/psb_ttm_fence_user.h>
#include <string.h>
#include <unistd.h>

struct _WsbmFenceClass
{
    struct _WsbmListHead head;
    struct _WsbmMutex mutex;
    struct _WsbmMutex cmd_mutex;
};

/*
 * Note: The struct _WsbmFenceMgr::Mutex should never be held
 * during sleeps, since that may block fast concurrent access to
 * fence data.
 */

struct _WsbmFenceMgr
{
    /*
     * Constant members. Need no mutex protection.
     */
    struct _WsbmFenceMgrCreateInfo info;
    void *private;

    /*
     * Atomic members. No mutex protection.
     */

    struct _WsbmAtomic count;

    /*
     * These members are protected by this->mutex
     */

    struct _WsbmFenceClass *classes;
    uint32_t num_classes;
};

struct _WsbmFenceObject
{

    /*
     * These members are constant and need no mutex protection.
     * Note that @private may point to a structure with its own
     * mutex protection, that we don't care about.
     */

    struct _WsbmFenceMgr *mgr;
    uint32_t fence_class;
    uint32_t fence_type;
    void *private;

    /*
     * Atomic members. No mutex protection. note that
     * @signaled types is updated using a compare-and-swap
     * scheme to guarantee atomicity.
     */

    struct _WsbmAtomic refCount;
    struct _WsbmAtomic signaled_types;

    /*
     * These members are protected by mgr->mutex.
     */
    struct _WsbmListHead head;
};

uint32_t
wsbmFenceType(struct _WsbmFenceObject *fence)
{
    return fence->fence_type;
}

struct _WsbmFenceMgr *
wsbmFenceMgrCreate(const struct _WsbmFenceMgrCreateInfo *info)
{
    struct _WsbmFenceMgr *tmp;
    uint32_t i, j;
    int ret;

    tmp = calloc(1, sizeof(*tmp));
    if (!tmp)
	return NULL;

    tmp->info = *info;
    tmp->classes = calloc(tmp->info.num_classes, sizeof(*tmp->classes));
    if (!tmp->classes)
	goto out_err;

    for (i = 0; i < tmp->info.num_classes; ++i) {
	struct _WsbmFenceClass *fc = &tmp->classes[i];

	WSBMINITLISTHEAD(&fc->head);
	ret = WSBM_MUTEX_INIT(&fc->mutex);
	if (ret)
	    goto out_err1;
	ret = WSBM_MUTEX_INIT(&fc->cmd_mutex);
	if (ret) {
	    WSBM_MUTEX_FREE(&fc->mutex);
	    goto out_err1;
	}
    }
    wsbmAtomicSet(&tmp->count, 0);

    return tmp;

  out_err1:
    for (j = 0; j < i; ++j) {
	WSBM_MUTEX_FREE(&tmp->classes[j].mutex);
	WSBM_MUTEX_FREE(&tmp->classes[j].cmd_mutex);
    }
    free(tmp->classes);
  out_err:
    if (tmp)
	free(tmp);
    return NULL;
}

void
wsbmFenceUnreference(struct _WsbmFenceObject **pFence)
{
    struct _WsbmFenceObject *fence = *pFence;
    struct _WsbmFenceMgr *mgr;

    *pFence = NULL;
    if (fence == NULL)
	return;

    mgr = fence->mgr;
    if (wsbmAtomicDecZero(&fence->refCount)) {
	struct _WsbmFenceClass *fc = &mgr->classes[fence->fence_class];

	WSBM_MUTEX_LOCK(&fc->mutex);
	WSBMLISTDELINIT(&fence->head);
	WSBM_MUTEX_UNLOCK(&fc->mutex);
	if (fence->private)
	    mgr->info.unreference(mgr, &fence->private);
	fence->mgr = NULL;
	wsbmAtomicDecZero(&mgr->count);
	free(fence);
    }
}

static void
wsbmSignalPreviousFences(struct _WsbmFenceMgr *mgr,
			 struct _WsbmListHead *list,
			 uint32_t fence_class, uint32_t signaled_types)
{
    struct _WsbmFenceClass *fc = &mgr->classes[fence_class];
    struct _WsbmFenceObject *entry;
    struct _WsbmListHead *prev;
    uint32_t old_signaled_types;
    uint32_t ret_st;

    WSBM_MUTEX_LOCK(&fc->mutex);
    while (list != &fc->head && list->next != list) {
	entry = WSBMLISTENTRY(list, struct _WsbmFenceObject, head);

	prev = list->prev;

	do {
	    old_signaled_types = wsbmAtomicRead(&entry->signaled_types);
	    signaled_types =
		old_signaled_types | (signaled_types & entry->fence_type);
	    if (signaled_types == old_signaled_types)
		break;

	    ret_st =
		wsbmAtomicCmpXchg(&entry->signaled_types, old_signaled_types,
				  signaled_types);
	} while (ret_st != old_signaled_types);

	if (signaled_types == entry->fence_type)
	    WSBMLISTDELINIT(list);

	list = prev;
    }
    WSBM_MUTEX_UNLOCK(&fc->mutex);
}

int
wsbmFenceFinish(struct _WsbmFenceObject *fence, uint32_t fence_type,
		int lazy_hint)
{
    struct _WsbmFenceMgr *mgr = fence->mgr;
    int ret = 0;

    if ((wsbmAtomicRead(&fence->signaled_types) & fence_type) == fence_type)
	goto out;

    ret = mgr->info.finish(mgr, fence->private, fence_type, lazy_hint);
    if (ret)
	goto out;

    wsbmSignalPreviousFences(mgr, &fence->head, fence->fence_class,
			     fence_type);
  out:
    return ret;
}

uint32_t
wsbmFenceSignaledTypeCached(struct _WsbmFenceObject * fence)
{
    return wsbmAtomicRead(&fence->signaled_types);
}

int
wsbmFenceSignaledType(struct _WsbmFenceObject *fence, uint32_t flush_type,
		      uint32_t * signaled)
{
    int ret = 0;
    struct _WsbmFenceMgr *mgr;
    uint32_t signaled_types;
    uint32_t old_signaled_types;
    uint32_t ret_st;

    mgr = fence->mgr;
    *signaled = wsbmAtomicRead(&fence->signaled_types);
    if ((*signaled & flush_type) == flush_type)
	goto out0;

    ret = mgr->info.signaled(mgr, fence->private, flush_type, signaled);
    if (ret) {
	*signaled = wsbmAtomicRead(&fence->signaled_types);
	goto out0;
    }

    do {
	old_signaled_types = wsbmAtomicRead(&fence->signaled_types);
	signaled_types = old_signaled_types | *signaled;
	if (signaled_types == old_signaled_types)
	    break;

	ret_st = wsbmAtomicCmpXchg(&fence->signaled_types, old_signaled_types,
				   signaled_types);
	if (old_signaled_types == ret_st)
	    wsbmSignalPreviousFences(mgr, &fence->head, fence->fence_class,
				     *signaled);
    } while (old_signaled_types != ret_st);

    return 0;
  out0:
    return ret;
}

struct _WsbmFenceObject *
wsbmFenceReference(struct _WsbmFenceObject *fence)
{
    if (fence == NULL)
	return NULL;
    wsbmAtomicInc(&fence->refCount);
    return fence;
}

struct _WsbmFenceObject *
wsbmFenceCreateSig(struct _WsbmFenceMgr *mgr, uint32_t fence_class,
		   uint32_t fence_type, uint32_t signaled_types, 
		   void *private, size_t private_size)
{
    struct _WsbmFenceClass *fc = &mgr->classes[fence_class];
    struct _WsbmFenceObject *fence;
    size_t fence_size = sizeof(*fence);

    if (private_size)
	fence_size = ((fence_size + 15) & ~15);

    fence = calloc(1, fence_size + private_size);

    if (!fence)
	goto out_err;

    wsbmAtomicSet(&fence->refCount, 1);
    fence->mgr = mgr;
    fence->fence_class = fence_class;
    fence->fence_type = fence_type;
    wsbmAtomicSet(&fence->signaled_types, signaled_types);
    fence->private = private;
    if (private_size) {
	fence->private = (void *)(((uint8_t *) fence) + fence_size);
	memcpy(fence->private, private, private_size);
    }

    WSBM_MUTEX_LOCK(&fc->mutex);
    WSBMLISTADDTAIL(&fence->head, &fc->head);
    WSBM_MUTEX_UNLOCK(&fc->mutex);
    wsbmAtomicInc(&mgr->count);
    return fence;

  out_err:
    {
	int ret = mgr->info.finish(mgr, private, fence_type, 0);

	if (ret)
	    usleep(10000000);
    }
    if (fence)
	free(fence);

    mgr->info.unreference(mgr, &private);
    return NULL;
}

struct _WsbmFenceObject *
wsbmFenceCreate(struct _WsbmFenceMgr *mgr, uint32_t fence_class,
		uint32_t fence_type, void *private, size_t private_size)
{
  return wsbmFenceCreateSig(mgr, fence_class, fence_type, 0, private,
			    private_size);
}

struct _WsbmTTMFenceMgrPriv
{
    int fd;
    unsigned int devOffset;
};

static int
tSignaled(struct _WsbmFenceMgr *mgr, void *private, uint32_t flush_type,
	  uint32_t * signaled_type)
{
    struct _WsbmTTMFenceMgrPriv *priv =
	(struct _WsbmTTMFenceMgrPriv *)mgr->private;
    union ttm_fence_signaled_arg arg;
    int ret;

    arg.req.handle = (unsigned long)private;
    arg.req.fence_type = flush_type;
    arg.req.flush = 1;
    *signaled_type = 0;

    ret = drmCommandWriteRead(priv->fd, priv->devOffset + TTM_FENCE_SIGNALED,
			      &arg, sizeof(arg));
    if (ret)
	return ret;

    *signaled_type = arg.rep.signaled_types;
    return 0;
}

static int
tFinish(struct _WsbmFenceMgr *mgr, void *private, uint32_t fence_type,
	int lazy_hint)
{
    struct _WsbmTTMFenceMgrPriv *priv =
	(struct _WsbmTTMFenceMgrPriv *)mgr->private;
    union ttm_fence_finish_arg arg =
	{.req = {.handle = (unsigned long)private,
		 .fence_type = fence_type,
		 .mode = (lazy_hint) ? TTM_FENCE_FINISH_MODE_LAZY : 0}
    };
    int ret;

    do {
	ret = drmCommandWriteRead(priv->fd, priv->devOffset + TTM_FENCE_FINISH,
				  &arg, sizeof(arg));
    } while (ret == -EAGAIN || ret == -ERESTART);

    return ret;
}

static int
tUnref(struct _WsbmFenceMgr *mgr, void **private)
{
    struct _WsbmTTMFenceMgrPriv *priv =
	(struct _WsbmTTMFenceMgrPriv *)mgr->private;
    struct ttm_fence_unref_arg arg = {.handle = (unsigned long)*private };

    *private = NULL;

    return drmCommandWrite(priv->fd, priv->devOffset + TTM_FENCE_UNREF,
			   &arg, sizeof(arg));
}

struct _WsbmFenceMgr *
wsbmFenceMgrTTMInit(int fd, unsigned int numClass, unsigned int devOffset)
{
    struct _WsbmFenceMgrCreateInfo info;
    struct _WsbmFenceMgr *mgr;
    struct _WsbmTTMFenceMgrPriv *priv = malloc(sizeof(*priv));

    if (!priv)
	return NULL;

    priv->fd = fd;
    priv->devOffset = devOffset;

    info.flags = WSBM_FENCE_CLASS_ORDERED;
    info.num_classes = numClass;
    info.signaled = tSignaled;
    info.finish = tFinish;
    info.unreference = tUnref;

    mgr = wsbmFenceMgrCreate(&info);
    if (mgr == NULL) {
	free(priv);
	return NULL;
    }

    mgr->private = (void *)priv;
    return mgr;
}

void
wsbmFenceCmdLock(struct _WsbmFenceMgr *mgr, uint32_t fence_class)
{
    WSBM_MUTEX_LOCK(&mgr->classes[fence_class].cmd_mutex);
}

void
wsbmFenceCmdUnlock(struct _WsbmFenceMgr *mgr, uint32_t fence_class)
{
    WSBM_MUTEX_UNLOCK(&mgr->classes[fence_class].cmd_mutex);
}

void
wsbmFenceMgrTTMTakedown(struct _WsbmFenceMgr *mgr)
{
    unsigned int i;

    if (!mgr)
	return;

    if (mgr->private)
	free(mgr->private);

    for (i = 0; i < mgr->info.num_classes; ++i) {
	WSBM_MUTEX_FREE(&mgr->classes[i].mutex);
	WSBM_MUTEX_FREE(&mgr->classes[i].cmd_mutex);
    }
    free(mgr);

    return;
}