/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2012-2013 LunarG, Inc.
 *
 * 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.
 *
 * Authors:
 *    Chia-I Wu <olv@lunarg.com>
 */

#include "intel_reg.h" /* for MI_xxx */
#include "intel_winsys.h"

#include "ilo_cp.h"

/* the size of the private space */
static const int ilo_cp_private = 2;

/**
 * Dump the contents of the parser bo.  This can only be called in the flush
 * callback.
 */
void
ilo_cp_dump(struct ilo_cp *cp)
{
   ilo_printf("dumping %d bytes\n", cp->used * 4);
   if (cp->used)
      intel_winsys_decode_commands(cp->winsys, cp->bo, cp->used * 4);
}

/**
 * Save the command parser state for rewind.
 *
 * Note that this cannot rewind a flush, and the caller must make sure
 * that does not happend.
 */
void
ilo_cp_setjmp(struct ilo_cp *cp, struct ilo_cp_jmp_buf *jmp)
{
   jmp->id = pointer_to_intptr(cp->bo);

   jmp->size = cp->size;
   jmp->used = cp->used;
   jmp->stolen = cp->stolen;
   /* save reloc count to rewind ilo_cp_write_bo() */
   jmp->reloc_count = intel_bo_get_reloc_count(cp->bo);
}

/**
 * Rewind to the saved state.
 */
void
ilo_cp_longjmp(struct ilo_cp *cp, const struct ilo_cp_jmp_buf *jmp)
{
   if (jmp->id != pointer_to_intptr(cp->bo)) {
      assert(!"invalid use of CP longjmp");
      return;
   }

   cp->size = jmp->size;
   cp->used = jmp->used;
   cp->stolen = jmp->stolen;
   intel_bo_clear_relocs(cp->bo, jmp->reloc_count);
}

/**
 * Clear the parser buffer.
 */
static void
ilo_cp_clear_buffer(struct ilo_cp *cp)
{
   cp->cmd_cur = 0;
   cp->cmd_end = 0;

   cp->used = 0;
   cp->stolen = 0;

   /*
    * Recalculate cp->size.  This is needed not only because cp->stolen is
    * reset above, but also that ilo_cp_private are added to cp->size in
    * ilo_cp_end_buffer().
    */
   cp->size = cp->bo_size - ilo_cp_private;
}

/**
 * Add MI_BATCH_BUFFER_END to the private space of the parser buffer.
 */
static void
ilo_cp_end_buffer(struct ilo_cp *cp)
{
   /* make the private space available */
   cp->size += ilo_cp_private;

   assert(cp->used + 2 <= cp->size);

   cp->ptr[cp->used++] = MI_BATCH_BUFFER_END;

   /*
    * From the Sandy Bridge PRM, volume 1 part 1, page 107:
    *
    *     "The batch buffer must be QWord aligned and a multiple of QWords in
    *      length."
    */
   if (cp->used & 1)
      cp->ptr[cp->used++] = MI_NOOP;
}

/**
 * Upload the parser buffer to the bo.
 */
static int
ilo_cp_upload_buffer(struct ilo_cp *cp)
{
   int err;

   if (!cp->sys) {
      intel_bo_unmap(cp->bo);
      return 0;
   }

   err = intel_bo_pwrite(cp->bo, 0, cp->used * 4, cp->ptr);
   if (likely(!err && cp->stolen)) {
      const int offset = cp->bo_size - cp->stolen;

      err = intel_bo_pwrite(cp->bo, offset * 4,
            cp->stolen * 4, &cp->ptr[offset]);
   }

   return err;
}

/**
 * Reallocate the parser bo.
 */
static void
ilo_cp_realloc_bo(struct ilo_cp *cp)
{
   struct intel_bo *bo;

   /*
    * allocate the new bo before unreferencing the old one so that they
    * won't point at the same address, which is needed for jmpbuf
    */
   bo = intel_winsys_alloc_buffer(cp->winsys,
         "batch buffer", cp->bo_size * 4, 0);
   if (unlikely(!bo)) {
      /* reuse the old one */
      bo = cp->bo;
      intel_bo_reference(bo);
   }

   if (cp->bo)
      intel_bo_unreference(cp->bo);
   cp->bo = bo;

   if (!cp->sys) {
      intel_bo_map(cp->bo, true);
      cp->ptr = intel_bo_get_virtual(cp->bo);
   }
}

/**
 * Execute the parser bo.
 */
static int
ilo_cp_exec_bo(struct ilo_cp *cp)
{
   const bool do_exec = !(ilo_debug & ILO_DEBUG_NOHW);
   struct intel_context *ctx;
   unsigned long flags;
   int err;

   switch (cp->ring) {
   case ILO_CP_RING_RENDER:
      ctx = cp->render_ctx;
      flags = INTEL_EXEC_RENDER;
      break;
   case ILO_CP_RING_BLT:
      ctx = NULL;
      flags = INTEL_EXEC_BLT;
      break;
   default:
      ctx = NULL;
      flags = 0;
      break;
   }

   flags |= cp->one_off_flags;

   if (likely(do_exec))
      err = intel_bo_exec(cp->bo, cp->used * 4, ctx, flags);
   else
      err = 0;

   cp->one_off_flags = 0;

   return err;
}

/**
 * Flush the command parser and execute the commands.  When the parser buffer
 * is empty, the callback is not invoked.
 */
void
ilo_cp_flush(struct ilo_cp *cp)
{
   int err;

   ilo_cp_set_owner(cp, NULL, 0);

   /* sanity check */
   assert(cp->bo_size == cp->size + cp->stolen + ilo_cp_private);

   if (!cp->used) {
      /* return the space stolen and etc. */
      ilo_cp_clear_buffer(cp);

      return;
   }

   ilo_cp_end_buffer(cp);

   /* upload and execute */
   err = ilo_cp_upload_buffer(cp);
   if (likely(!err))
      err = ilo_cp_exec_bo(cp);

   if (likely(!err && cp->flush_callback))
      cp->flush_callback(cp, cp->flush_callback_data);

   ilo_cp_clear_buffer(cp);
   ilo_cp_realloc_bo(cp);
}

/**
 * Destroy the command parser.
 */
void
ilo_cp_destroy(struct ilo_cp *cp)
{
   if (cp->bo) {
      if (!cp->sys)
         intel_bo_unmap(cp->bo);

      intel_bo_unreference(cp->bo);
   }

   if (cp->render_ctx)
      intel_winsys_destroy_context(cp->winsys, cp->render_ctx);

   FREE(cp->sys);
   FREE(cp);
}

/**
 * Create a command parser.
 */
struct ilo_cp *
ilo_cp_create(struct intel_winsys *winsys, bool direct_map)
{
   struct ilo_cp *cp;

   cp = CALLOC_STRUCT(ilo_cp);
   if (!cp)
      return NULL;

   cp->winsys = winsys;
   cp->render_ctx = intel_winsys_create_context(winsys);

   cp->ring = ILO_CP_RING_RENDER;
   cp->no_implicit_flush = false;

   cp->bo_size = 8192;

   if (!direct_map) {
      cp->sys = MALLOC(cp->bo_size * 4);
      if (!cp->sys) {
         FREE(cp);
         return NULL;
      }

      cp->ptr = cp->sys;
   }

   ilo_cp_realloc_bo(cp);
   if (!cp->bo) {
      FREE(cp->sys);
      FREE(cp);
      return NULL;
   }

   ilo_cp_clear_buffer(cp);

   return cp;
}
