/*
 * Copyright (c) 2016, NVIDIA CORPORATION. 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, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "conftest.h"

#if defined(NV_DRM_AVAILABLE)

#include "nvidia-drm-priv.h"
#include "nvidia-drm-ioctl.h"
#include "nvidia-drm-gem.h"
#include "nvidia-drm-fence.h"

#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
struct nv_fence {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence base;
#else
    struct dma_fence base;
#endif
    spinlock_t lock;

    struct nvidia_drm_device *nv_dev;
    struct nvidia_drm_gem_object *nv_gem;
};
static inline bool
nv_fence_ready_to_signal(struct nv_fence *nv_fence)
{
    struct nvidia_drm_gem_object *nv_gem = nv_fence->nv_gem;

    /* Index into surface with 16 byte stride */
    uint32_t semVal = *(nv_gem->fenceContext.pLinearAddress +
                        (nv_gem->fenceContext.fenceSemIndex * 4));

    return (nv_fence->base.seqno <= semVal);
}

static const char *nvidia_drm_gem_prime_fence_op_get_driver_name
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence
#else
    struct dma_fence *fence
#endif
)
{
    return "NVIDIA";
}

static const char *nvidia_drm_gem_prime_fence_op_get_timeline_name
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence
#else
    struct dma_fence *fence
#endif
)
{
    return "nvidia.prime";
}

static bool nvidia_drm_gem_prime_fence_op_signaled
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence
#else
    struct dma_fence *fence
#endif
)
{
    struct nv_fence *nv_fence = container_of(fence, struct nv_fence, base);
    struct nvidia_drm_gem_object *nv_gem = nv_fence->nv_gem;
    struct nvidia_drm_device *nv_dev = nv_fence->nv_dev;
    bool ret;

    mutex_lock(&nv_dev->dev->struct_mutex);

    if (fence->seqno == 0)
    {
        ret = true;
        goto unlock_struct_mutex;
    }

    if (!nv_gem->fenceContext.mapped)
    {
        ret = false;
        goto unlock_struct_mutex;
    }

    ret = nv_fence_ready_to_signal(nv_fence);

unlock_struct_mutex:
    mutex_unlock(&nv_dev->dev->struct_mutex);

    return ret;
}

static bool nvidia_drm_gem_prime_fence_op_enable_signaling
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence
#else
    struct dma_fence *fence
#endif
)
{
    bool ret = true;
    struct nv_fence *nv_fence = container_of(fence, struct nv_fence, base);
    struct nvidia_drm_gem_object *nv_gem = nv_fence->nv_gem;
    struct nvidia_drm_device *nv_dev = nv_fence->nv_dev;

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    if (fence_is_signaled(fence))
#else
    if (dma_fence_is_signaled(fence))
#endif
    {
        return false;
    }

    /*
     * Enable software signaling for a fence if someone tries to wait on it
     * before it is ready to be signaled
     */

    mutex_lock(&nv_dev->dev->struct_mutex);

    if (fence == nv_gem->fenceContext.softFence) { /* Already enabled */
        goto unlock_struct_mutex;
    }

    WARN_ON(nv_gem->fenceContext.softFence != NULL);

    if (!nvKms->enableChannelEvent(nv_dev->pDevice, nv_gem->fenceContext.cb))
    {
        ret = false;

        NV_DRM_DEV_LOG_ERR(nv_dev,
                           "Failed to enable fence signaling event");

        goto unlock_struct_mutex;
    }

    nv_gem->fenceContext.softFence = fence;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    fence_get(fence);
#else
    dma_fence_get(fence);
#endif

unlock_struct_mutex:
    mutex_unlock(&nv_dev->dev->struct_mutex);

    return ret;
}

static void nvidia_drm_gem_prime_fence_op_release
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence
#else
    struct dma_fence *fence
#endif
)
{
    struct nv_fence *nv_fence = container_of(fence, struct nv_fence, base);
    nvidia_drm_free(nv_fence);
}

static signed long nvidia_drm_gem_prime_fence_op_wait
(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence,
#else
    struct dma_fence *fence,
#endif
    bool intr,
    signed long timeout
)
{
    /*
     * If the waiter requests to wait with no timeout, force a timeout to ensure
     * that it won't get stuck forever in the kernel if something were to go
     * wrong with signaling, such as a malicious userspace not releasing the
     * semaphore.
     *
     * 96 ms (roughly 6 frames @ 60 Hz) is arbitrarily chosen to be long enough
     * that it should never get hit during normal operation, but not so long
     * that the system becomes unresponsive.
     */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    return fence_default_wait(fence, intr,
#else
    return dma_fence_default_wait(fence, intr,
#endif
                              (timeout == MAX_SCHEDULE_TIMEOUT) ?
                                  msecs_to_jiffies(96) : timeout);
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
static const struct fence_ops nvidia_drm_gem_prime_fence_ops = {
#else
static const struct dma_fence_ops nvidia_drm_gem_prime_fence_ops = {
#endif
    .get_driver_name = nvidia_drm_gem_prime_fence_op_get_driver_name,
    .get_timeline_name = nvidia_drm_gem_prime_fence_op_get_timeline_name,
    .signaled = nvidia_drm_gem_prime_fence_op_signaled,
    .enable_signaling = nvidia_drm_gem_prime_fence_op_enable_signaling,
    .release = nvidia_drm_gem_prime_fence_op_release,
    .wait = nvidia_drm_gem_prime_fence_op_wait,
};

static void nvidia_drm_gem_prime_fence_destroy_semaphore
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem
)
{
    WARN_ON(!mutex_is_locked(&nv_dev->dev->struct_mutex));

    if (nv_gem->fenceContext.mapped)
    {
        nvKms->unmapMemory(nv_dev->pDevice,
                           nv_gem->fenceContext.pSemSurface,
                           NVKMS_KAPI_MAPPING_TYPE_KERNEL,
                           (void *) nv_gem->fenceContext.pLinearAddress);

        nv_gem->fenceContext.pLinearAddress = NULL;
        nv_gem->fenceContext.mapped = false;
    }

    if (nv_gem->fenceContext.pSemSurface)
    {
        nvKms->freeMemory(nv_dev->pDevice, nv_gem->fenceContext.pSemSurface);
        nv_gem->fenceContext.pSemSurface = NULL;
    }

    nv_gem->fenceContext.fenceSemIndex = ~0;
}

static NvU32 nvidia_drm_gem_prime_fence_import_semaphore
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem,
    NvU32 index,
    NvU64 size,
    NvU64 nvkms_params_ptr,
    NvU64 nvkms_params_size
)
{
    int ret = 0;

    WARN_ON(!mutex_is_locked(&nv_dev->dev->struct_mutex));

    if (nv_gem->fenceContext.pSemSurface)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Double import of fence semaphore surface: 0x%08x",
            nv_gem->handle);

        return ret;
    }

    nv_gem->fenceContext.pSemSurface = nvKms->importMemory(nv_dev->pDevice,
                                                           size,
                                                           nvkms_params_ptr,
                                                           nvkms_params_size);
    if (!nv_gem->fenceContext.pSemSurface)
    {
        ret = -ENOMEM;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to import fence semaphore surface: 0x%08x",
            nv_gem->handle);

        goto fail;
    }

    if (!nvKms->mapMemory(nv_dev->pDevice,
                          nv_gem->fenceContext.pSemSurface,
                          NVKMS_KAPI_MAPPING_TYPE_KERNEL,
                          (void **) &nv_gem->fenceContext.pLinearAddress))
    {
        ret = -ENOMEM;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to map fence semaphore surface: 0x%08x",
            nv_gem->handle);

        goto fail;
    }
    nv_gem->fenceContext.mapped = true;

    nv_gem->fenceContext.fenceSemIndex = index;

    return ret;
fail:
    nvidia_drm_gem_prime_fence_destroy_semaphore(nv_dev, nv_gem);
    return ret;
}

static void nvidia_drm_gem_prime_fence_signal
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem,
    bool force
)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    struct fence *fence = nv_gem->fenceContext.softFence;
#else
    struct dma_fence *fence = nv_gem->fenceContext.softFence;
#endif

    WARN_ON(!mutex_is_locked(&nv_dev->dev->struct_mutex));

    /*
     * Only signal fence that has software signaling enabled and is ready to
     * signal based on the semaphore value, unless force == TRUE, then ignore
     * the semaphore value and signal the fence even if software signaling isn't
     * enabled.
     */
    if (fence) /* Software signaling enabled */
    {
        struct nv_fence *nv_fence = container_of(fence, struct nv_fence, base);

        if (force || nv_fence_ready_to_signal(nv_fence))
        {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
            fence_signal(&nv_fence->base);
#else
            dma_fence_signal(&nv_fence->base);
#endif

            nv_gem->fenceContext.softFence = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
            fence_put(&nv_fence->base);
#else
            dma_fence_put(&nv_fence->base);
#endif

            nvKms->disableChannelEvent(nv_dev->pDevice,
                                       nv_gem->fenceContext.cb);
        }
    }
    else if (force) /* Software signaling disabled */
    {
        struct nv_fence *nv_fence;

        fence = reservation_object_get_excl(&nv_gem->fenceContext.resv);
        if (!fence)
            return;

        nv_fence = container_of(fence, struct nv_fence, base);

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
        fence_signal(&nv_fence->base);
#else
        dma_fence_signal(&nv_fence->base);
#endif
    }
}

struct nvidia_drm_gem_prime_soft_fence_event_args {
    struct nvidia_drm_device *nv_dev;
    struct nvidia_drm_gem_object *nv_gem;
};

static void NVKMS_KAPI_CALL nvidia_drm_gem_prime_soft_fence_event
(
    void *dataPtr,
    NvU32 dataU32
)
{
    struct nvidia_drm_gem_prime_soft_fence_event_args *cbArgs = dataPtr;

    struct nvidia_drm_device *nv_dev = cbArgs->nv_dev;
    struct nvidia_drm_gem_object *nv_gem = cbArgs->nv_gem;

    mutex_lock(&nv_dev->dev->struct_mutex);

    nvidia_drm_gem_prime_fence_signal(nv_dev, nv_gem, false);

    mutex_unlock(&nv_dev->dev->struct_mutex);
}

static void nvidia_drm_gem_prime_destroy_soft_fence_event
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem
)
{
    WARN_ON(!mutex_is_locked(&nv_dev->dev->struct_mutex));

    nvidia_drm_gem_prime_fence_signal(nv_dev, nv_gem, true);

    if (nv_gem->fenceContext.cb)
    {
        nvKms->freeChannelEvent(nv_dev->pDevice, nv_gem->fenceContext.cb);
        nv_gem->fenceContext.cb = NULL;
    }

    if (nv_gem->fenceContext.cbArgs)
    {
        if (nv_gem->fenceContext.cbArgs->nv_gem)
        {
            drm_gem_object_unreference(&nv_gem->base);
        }

        nvidia_drm_free(nv_gem->fenceContext.cbArgs);
        nv_gem->fenceContext.cbArgs = NULL;
    }
}

static NvU32 nvidia_drm_gem_prime_create_soft_fence_event
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem,
    NvU64 nvkms_params_ptr,
    NvU64 nvkms_params_size
)
{
    int ret = 0;

    WARN_ON(!mutex_is_locked(&nv_dev->dev->struct_mutex));

    if (nv_gem->fenceContext.cb)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(nv_dev, "Double creation of fence event");

        return ret;
    }

    nv_gem->fenceContext.cbArgs =
        nvidia_drm_calloc(1, sizeof(*nv_gem->fenceContext.cbArgs));
    if (!nv_gem->fenceContext.cbArgs)
    {
        ret = -ENOMEM;

        NV_DRM_DEV_LOG_ERR(nv_dev,
                           "Failed to allocate fence signaling event args");

        goto fail;
    }

    drm_gem_object_reference(&nv_gem->base);

    nv_gem->fenceContext.cbArgs->nv_dev = nv_dev;
    nv_gem->fenceContext.cbArgs->nv_gem = nv_gem;

    nv_gem->fenceContext.cb =
        nvKms->allocateChannelEvent(nv_dev->pDevice,
                                    nvidia_drm_gem_prime_soft_fence_event,
                                    nv_gem->fenceContext.cbArgs,
                                    nvkms_params_ptr, nvkms_params_size);
    if (!nv_gem->fenceContext.cb)
    {
        ret = -ENOMEM;

        NV_DRM_DEV_LOG_ERR(nv_dev,
                           "Failed to allocate fence signaling event");

        goto fail;
    }

    return ret;
fail:
    nvidia_drm_gem_prime_destroy_soft_fence_event(nv_dev, nv_gem);
    return ret;
}

void nvidia_drm_gem_prime_fence_teardown
(
    struct nvidia_drm_device *nv_dev,
    struct nvidia_drm_gem_object *nv_gem
)
{
    nvidia_drm_gem_prime_fence_signal(nv_dev, nv_gem, true);
    nvidia_drm_gem_prime_fence_destroy_semaphore(nv_dev, nv_gem);
    nvidia_drm_gem_prime_destroy_soft_fence_event(nv_dev, nv_gem);

    nv_gem->fenceContext.init = false;
}
#endif /* NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ */

int nvidia_drm_gem_prime_fence_supported
(
    struct drm_device *dev,
    void *data,
    struct drm_file *file_priv
)
{
#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
    struct nvidia_drm_device *nv_dev = dev->dev_private;
    return nv_dev->pDevice ? 0 : -EINVAL;
#else
    return -EINVAL;
#endif
}

int nvidia_drm_gem_prime_fence_init
(
    struct drm_device *dev,
    void *data,
    struct drm_file *file_priv
)
{
    int ret = -EINVAL;
#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
    struct nvidia_drm_device *nv_dev = dev->dev_private;
    struct drm_nvidia_gem_prime_fence_init_params *p = data;

    struct drm_gem_object *gem;
    struct nvidia_drm_gem_object *nv_gem;

    mutex_lock(&dev->struct_mutex);

    gem = nvidia_drm_gem_object_lookup(nv_dev->dev, file_priv, p->handle);

    if (!gem)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to lookup gem object for fencing init: 0x%08x",
            p->handle);

        goto unlock_struct_mutex;
    }

    nv_gem = DRM_GEM_OBJECT_TO_NV_GEM_OBJECT(gem);

    if (nv_gem->fenceContext.init)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Attempt to init a gem object already "
            "initialized for fencing : 0x%08x",
            p->handle);

        goto unlock_gem_object;
    }

    /*
     * fence_context_alloc() cannot fail, so we do not need to check a return
     * value.
     */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    nv_gem->fenceContext.context = fence_context_alloc(1);
#else
    nv_gem->fenceContext.context = dma_fence_context_alloc(1);
#endif

    ret = nvidia_drm_gem_prime_fence_import_semaphore(
              nv_dev, nv_gem, p->index,
              p->size,
              p->import_mem_nvkms_params_ptr,
              p->import_mem_nvkms_params_size);
    if (ret < 0)
    {
        goto unlock_gem_object;
    }

    ret = nvidia_drm_gem_prime_create_soft_fence_event(
            nv_dev, nv_gem,
            p->event_nvkms_params_ptr,
            p->event_nvkms_params_size);
    if (ret < 0)
    {
        nvidia_drm_gem_prime_fence_destroy_semaphore(nv_dev, nv_gem);
        goto unlock_gem_object;
    }

    ret = 0;

    nv_gem->fenceContext.init = true;

unlock_gem_object:
    drm_gem_object_unreference(gem);
unlock_struct_mutex:
    mutex_unlock(&dev->struct_mutex);
#endif /* NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ */
    return ret;
}

int nvidia_drm_gem_prime_fence_fini
(
    struct drm_device *dev,
    void *data,
    struct drm_file *file_priv
)
{
    int ret = -EINVAL;
#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
    struct nvidia_drm_device *nv_dev = dev->dev_private;
    struct drm_nvidia_gem_prime_fence_fini_params *p = data;

    struct drm_gem_object *gem;
    struct nvidia_drm_gem_object *nv_gem;

    mutex_lock(&dev->struct_mutex);

    gem = nvidia_drm_gem_object_lookup(nv_dev->dev, file_priv, p->handle);

    if (!gem)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to lookup gem object for fencing fini: 0x%08x",
            p->handle);

        goto unlock_struct_mutex;
    }

    nv_gem = DRM_GEM_OBJECT_TO_NV_GEM_OBJECT(gem);

    if (!nv_gem->fenceContext.init)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Attempt to fini a gem object not initialized for fencing : 0x%08x",
            p->handle);

        goto unlock_gem_object;
    }

    nv_gem->fenceContext.context = 0;

    nvidia_drm_gem_prime_fence_teardown(nv_dev, nv_gem);

    ret = 0;

unlock_gem_object:
    drm_gem_object_unreference(gem);
unlock_struct_mutex:
    mutex_unlock(&dev->struct_mutex);
#endif /* NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ */
    return ret;
}

int nvidia_drm_gem_prime_fence_attach
(
    struct drm_device *dev,
    void *data,
    struct drm_file *file_priv
)
{
    int ret = -EINVAL;
#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
    struct nvidia_drm_device *nv_dev = dev->dev_private;
    struct drm_nvidia_gem_prime_fence_attach_params *p = data;

    struct drm_gem_object *gem;
    struct nvidia_drm_gem_object *nv_gem;

    struct nv_fence *nv_fence;

    mutex_lock(&dev->struct_mutex);

    gem = nvidia_drm_gem_object_lookup(nv_dev->dev, file_priv, p->handle);

    if (!gem)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to lookup gem object for fence attach: 0x%08x",
            p->handle);

        goto unlock_struct_mutex;
    }

    nv_gem = DRM_GEM_OBJECT_TO_NV_GEM_OBJECT(gem);

    if (!nv_gem->fenceContext.init)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Attempt to attach a fence to a gem "
            "object not initialized for fencing : 0x%08x",
            p->handle);

        goto unlock_struct_mutex;
    }

    nv_fence = nvidia_drm_calloc(1, sizeof(*nv_fence));
    if (!nv_fence)
    {
        ret = -ENOMEM;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to allocate fence: 0x%08x", p->handle);

        goto unlock_gem_object;
    }

    nv_fence->nv_dev = nv_dev;
    nv_fence->nv_gem = nv_gem;

    spin_lock_init(&nv_fence->lock);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    fence_init(&nv_fence->base, &nvidia_drm_gem_prime_fence_ops,
#else
    dma_fence_init(&nv_fence->base, &nvidia_drm_gem_prime_fence_ops,
#endif
               &nv_fence->lock, nv_gem->fenceContext.context,
               p->sem_thresh);

    /* We could be replacing an existing exclusive fence; force signal it to
     * unblock anyone waiting on it and clean up software signaling. */
    nvidia_drm_gem_prime_fence_signal(nv_dev, nv_gem, true);

    reservation_object_add_excl_fence(&nv_gem->fenceContext.resv,
                                      &nv_fence->base);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    fence_put(&nv_fence->base); /* Reservation object has reference */
#else
    dma_fence_put(&nv_fence->base);
#endif

    ret = 0;

unlock_gem_object:
    drm_gem_object_unreference(gem);
unlock_struct_mutex:
    mutex_unlock(&dev->struct_mutex);
#endif /* NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ */
    return ret;
}

int nvidia_drm_gem_prime_fence_force_signal
(
    struct drm_device *dev,
    void *data,
    struct drm_file *file_priv
)
{
    int ret = -EINVAL;
#if defined(NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ)
    struct nvidia_drm_device *nv_dev = dev->dev_private;
    struct drm_nvidia_gem_prime_fence_force_signal_params *p = data;

    struct drm_gem_object *gem;
    struct nvidia_drm_gem_object *nv_gem;

    mutex_lock(&dev->struct_mutex);

    gem = nvidia_drm_gem_object_lookup(nv_dev->dev, file_priv, p->handle);

    if (!gem)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Failed to lookup gem object for fence attach: 0x%08x",
            p->handle);

        goto unlock_struct_mutex;
    }

    nv_gem = DRM_GEM_OBJECT_TO_NV_GEM_OBJECT(gem);

    if (!nv_gem->fenceContext.init)
    {
        ret = -EINVAL;

        NV_DRM_DEV_LOG_ERR(
            nv_dev,
            "Attempt to force signal a gem object "
            "not initialized for fencing : 0x%08x",
            p->handle);

        goto unlock_gem_object;
    }

    nvidia_drm_gem_prime_fence_signal(nv_dev, nv_gem, true);

    ret = 0;

unlock_gem_object:
    drm_gem_object_unreference(gem);
unlock_struct_mutex:
    mutex_unlock(&dev->struct_mutex);
#endif /* NV_DRM_DRIVER_HAS_GEM_PRIME_RES_OBJ */
    return ret;
}

#endif /* NV_DRM_AVAILABLE */
