/*
 * Copyright (C) 2017-2019 Lima Project
 *
 * 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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 <stdlib.h>
#include <string.h>

#include "xf86drm.h"
#include "libsync.h"
#include "drm-uapi/lima_drm.h"

#include "util/ralloc.h"
#include "util/u_dynarray.h"
#include "util/os_time.h"

#include "lima_screen.h"
#include "lima_context.h"
#include "lima_submit.h"
#include "lima_bo.h"
#include "lima_util.h"

struct lima_submit {
   struct lima_screen *screen;
   uint32_t pipe;
   uint32_t ctx;

   int in_sync_fd;
   uint32_t in_sync;
   uint32_t out_sync;

   struct util_dynarray gem_bos;
   struct util_dynarray bos;
};


#define VOID2U64(x) ((uint64_t)(unsigned long)(x))

struct lima_submit *lima_submit_create(struct lima_context *ctx, uint32_t pipe)
{
   struct lima_submit *s;

   s = rzalloc(ctx, struct lima_submit);
   if (!s)
      return NULL;

   s->screen = lima_screen(ctx->base.screen);
   s->pipe = pipe;
   s->ctx = ctx->id;
   s->in_sync_fd = -1;

   int err = drmSyncobjCreate(s->screen->fd, DRM_SYNCOBJ_CREATE_SIGNALED,
                              &s->out_sync);
   if (err)
      goto err_out0;

   err = drmSyncobjCreate(s->screen->fd, DRM_SYNCOBJ_CREATE_SIGNALED,
                          &s->in_sync);
   if (err)
      goto err_out1;

   util_dynarray_init(&s->gem_bos, s);

   return s;

err_out1:
   drmSyncobjDestroy(s->screen->fd, s->out_sync);
err_out0:
   ralloc_free(s);
   return NULL;
}

void lima_submit_free(struct lima_submit *submit)
{
   if (submit->in_sync_fd >= 0)
      close(submit->in_sync_fd);
   drmSyncobjDestroy(submit->screen->fd, submit->in_sync);
   drmSyncobjDestroy(submit->screen->fd, submit->out_sync);
}

bool lima_submit_add_bo(struct lima_submit *submit, struct lima_bo *bo, uint32_t flags)
{
   util_dynarray_foreach(&submit->gem_bos, struct drm_lima_gem_submit_bo, gem_bo) {
      if (bo->handle == gem_bo->handle) {
         gem_bo->flags |= flags;
         return true;
      }
   }

   struct drm_lima_gem_submit_bo *submit_bo =
      util_dynarray_grow(&submit->gem_bos, sizeof(*submit_bo));
   submit_bo->handle = bo->handle;
   submit_bo->flags = flags;

   struct lima_bo **jbo = util_dynarray_grow(&submit->bos, sizeof(*jbo));
   *jbo = bo;

   /* prevent bo from being freed when submit start */
   lima_bo_reference(bo);

   return true;
}

bool lima_submit_start(struct lima_submit *submit, void *frame, uint32_t size)
{
   struct drm_lima_gem_submit req = {
      .ctx = submit->ctx,
      .pipe = submit->pipe,
      .nr_bos = submit->gem_bos.size / sizeof(struct drm_lima_gem_submit_bo),
      .bos = VOID2U64(util_dynarray_begin(&submit->gem_bos)),
      .frame = VOID2U64(frame),
      .frame_size = size,
   };

   if (submit->in_sync_fd >= 0) {
      int err = drmSyncobjImportSyncFile(submit->screen->fd, submit->in_sync,
                                         submit->in_sync_fd);
      if (err)
         return false;

      req.in_sync[0] = submit->in_sync;
      close(submit->in_sync_fd);
      submit->in_sync_fd = -1;
   }

   bool ret = drmIoctl(submit->screen->fd, DRM_IOCTL_LIMA_GEM_SUBMIT, &req) == 0;

   util_dynarray_foreach(&submit->bos, struct lima_bo *, bo) {
      lima_bo_free(*bo);
   }

   util_dynarray_clear(&submit->gem_bos);
   util_dynarray_clear(&submit->bos);
   return ret;
}

bool lima_submit_wait(struct lima_submit *submit, uint64_t timeout_ns)
{
   int64_t abs_timeout = os_time_get_absolute_timeout(timeout_ns);

   return !drmSyncobjWait(submit->screen->fd, &submit->out_sync, 1, abs_timeout, 0, NULL);
}

bool lima_submit_has_bo(struct lima_submit *submit, struct lima_bo *bo, bool all)
{
   util_dynarray_foreach(&submit->gem_bos, struct drm_lima_gem_submit_bo, gem_bo) {
      if (bo->handle == gem_bo->handle) {
         if (all)
            return true;
         else
            return gem_bo->flags & LIMA_SUBMIT_BO_WRITE;
      }
   }

   return false;
}

bool lima_submit_add_in_sync(struct lima_submit *submit, int fd)
{
   return !sync_accumulate("lima", &submit->in_sync_fd, fd);
}

bool lima_submit_get_out_sync(struct lima_submit *submit, int *fd)
{
   return !drmSyncobjExportSyncFile(submit->screen->fd, submit->out_sync, fd);
}