/*
 * Copyright © 2011 Intel Corporation
 *
 * 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 (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
 * 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:
 *    Benjamin Franzke <benjaminfranzke@googlemail.com>
 */

#include "util/u_memory.h"
#include "util/u_inlines.h"

#include "pipe-loader/pipe_loader.h"
#include "state_tracker/drm_driver.h"

#include <unistd.h>
#include <sys/types.h>

#include "gbm_gallium_drmint.h"

/* For importing wl_buffer */
#if HAVE_WAYLAND_PLATFORM
#include "../../../egl/wayland/wayland-drm/wayland-drm.h"
#endif

static INLINE enum pipe_format
gbm_format_to_gallium(enum gbm_bo_format format)
{
   switch (format) {
   case GBM_BO_FORMAT_XRGB8888:
      return PIPE_FORMAT_BGRX8888_UNORM;
   case GBM_BO_FORMAT_ARGB8888:
      return PIPE_FORMAT_BGRA8888_UNORM;
   default:
      return PIPE_FORMAT_NONE;
   }

   return PIPE_FORMAT_NONE;
}

static INLINE uint
gbm_usage_to_gallium(uint usage)
{
   uint resource_usage = 0;

   if (usage & GBM_BO_USE_SCANOUT)
      resource_usage |= PIPE_BIND_SCANOUT;

   if (usage & GBM_BO_USE_RENDERING)
      resource_usage |= PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW;

   if (usage & GBM_BO_USE_CURSOR)
      resource_usage |= PIPE_BIND_CURSOR;

   return resource_usage;
}

static int
gbm_gallium_drm_is_format_supported(struct gbm_device *gbm,
                                    enum gbm_bo_format format,
                                    uint32_t usage)
{
   struct gbm_gallium_drm_device *gdrm = gbm_gallium_drm_device(gbm);
   enum pipe_format pf;

   pf = gbm_format_to_gallium(format);
   if (pf == PIPE_FORMAT_NONE)
      return 0;

   if (!gdrm->screen->is_format_supported(gdrm->screen, PIPE_TEXTURE_2D, pf, 0,
                                          gbm_usage_to_gallium(usage)))
      return 0;

   if (usage & GBM_BO_USE_SCANOUT && format != GBM_BO_FORMAT_XRGB8888)
      return 0;

   return 1;
}

static void
gbm_gallium_drm_bo_destroy(struct gbm_bo *_bo)
{
   struct gbm_gallium_drm_bo *bo = gbm_gallium_drm_bo(_bo);

   pipe_resource_reference(&bo->resource, NULL);
   free(bo);
}

static struct gbm_bo *
gbm_gallium_drm_bo_import(struct gbm_device *gbm,
                          uint32_t type, void *buffer, uint32_t usage)
{
   struct gbm_gallium_drm_device *gdrm = gbm_gallium_drm_device(gbm);
   struct gbm_gallium_drm_bo *bo;
   struct winsys_handle whandle;
   struct pipe_resource *resource;

   switch (type) {
#if HAVE_WAYLAND_PLATFORM
   case GBM_BO_IMPORT_WL_BUFFER:
   {
      struct wl_drm_buffer *wb = (struct wl_drm_buffer *) buffer;

      resource = wb->driver_buffer;
      break;
   }
#endif

   case GBM_BO_IMPORT_EGL_IMAGE:
      if (!gdrm->lookup_egl_image)
         return NULL;

      resource = gdrm->lookup_egl_image(gdrm->lookup_egl_image_data, buffer);
      if (resource == NULL)
         return NULL;
      break;

   default:
      return NULL;
   }

   bo = CALLOC_STRUCT(gbm_gallium_drm_bo);
   if (bo == NULL)
      return NULL;

   bo->base.base.gbm = gbm;
   bo->base.base.width = resource->width0;
   bo->base.base.height = resource->height0;

   switch (resource->format) {
   case PIPE_FORMAT_BGRX8888_UNORM:
      bo->base.base.format = GBM_BO_FORMAT_XRGB8888;
      break;
   case PIPE_FORMAT_BGRA8888_UNORM:
      bo->base.base.format = GBM_BO_FORMAT_ARGB8888;
      break;
   default:
      FREE(bo);
      return NULL;
   }

   pipe_resource_reference(&bo->resource, resource);

   memset(&whandle, 0, sizeof(whandle));
   whandle.type = DRM_API_HANDLE_TYPE_KMS;
   gdrm->screen->resource_get_handle(gdrm->screen, bo->resource, &whandle);

   bo->base.base.handle.u32 = whandle.handle;
   bo->base.base.stride      = whandle.stride;

   return &bo->base.base;
}

static struct gbm_bo *
gbm_gallium_drm_bo_create(struct gbm_device *gbm,
                          uint32_t width, uint32_t height,
                          enum gbm_bo_format format, uint32_t usage)
{
   struct gbm_gallium_drm_device *gdrm = gbm_gallium_drm_device(gbm);
   struct gbm_gallium_drm_bo *bo;
   struct pipe_resource templ;
   struct winsys_handle whandle;
   enum pipe_format pf;

   bo = CALLOC_STRUCT(gbm_gallium_drm_bo);
   if (bo == NULL)
      return NULL;

   bo->base.base.gbm = gbm;
   bo->base.base.width = width;
   bo->base.base.height = height;
   bo->base.base.format = format;

   pf = gbm_format_to_gallium(format);
   if (pf == PIPE_FORMAT_NONE)
      return NULL;

   memset(&templ, 0, sizeof(templ));
   templ.bind = gbm_usage_to_gallium(usage);
   templ.format = pf;
   templ.target = PIPE_TEXTURE_2D;
   templ.last_level = 0;
   templ.width0 = width;
   templ.height0 = height;
   templ.depth0 = 1;
   templ.array_size = 1;

   bo->resource = gdrm->screen->resource_create(gdrm->screen, &templ);
   if (bo->resource == NULL) {
      FREE(bo);
      return NULL;
   }

   memset(&whandle, 0, sizeof(whandle));
   whandle.type = DRM_API_HANDLE_TYPE_KMS;
   gdrm->screen->resource_get_handle(gdrm->screen, bo->resource, &whandle);

   bo->base.base.handle.u32 = whandle.handle;
   bo->base.base.stride      = whandle.stride;

   return &bo->base.base;
}

static void
gbm_gallium_drm_destroy(struct gbm_device *gbm)
{
   struct gbm_gallium_drm_device *gdrm = gbm_gallium_drm_device(gbm);

   free(gdrm->base.driver_name);
   gdrm->screen->destroy(gdrm->screen);
#if !GALLIUM_STATIC_TARGETS
   pipe_loader_release(&gdrm->dev, 1);
#endif
   free(gdrm);
}

#if !GALLIUM_STATIC_TARGETS
#ifdef HAVE_PIPE_LOADER_DRM
static const char *
get_library_search_path(void)
{
   const char *search_path = NULL;

   /* don't allow setuid apps to use GBM_BACKENDS_PATH */
   if (geteuid() == getuid())
      search_path = getenv("GBM_BACKENDS_PATH");
   if (search_path == NULL)
      search_path = PIPE_SEARCH_DIR;

   return search_path;
}
#endif
#endif

static struct gbm_device *
gbm_gallium_drm_device_create(int fd)
{
   struct gbm_gallium_drm_device *gdrm;

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

   gdrm->base.base.fd = fd;
   gdrm->base.base.bo_create = gbm_gallium_drm_bo_create;
   gdrm->base.base.bo_import = gbm_gallium_drm_bo_import;
   gdrm->base.base.bo_destroy = gbm_gallium_drm_bo_destroy;
   gdrm->base.base.is_format_supported = gbm_gallium_drm_is_format_supported;
   gdrm->base.base.destroy = gbm_gallium_drm_destroy;

   gdrm->base.type = GBM_DRM_DRIVER_TYPE_GALLIUM;
   gdrm->base.base.name = "drm";

#if GALLIUM_STATIC_TARGETS
   gdrm->screen = dd_create_screen(gdrm->base.base.fd);
#else
#ifdef HAVE_PIPE_LOADER_DRM
   if (pipe_loader_drm_probe_fd(&gdrm->dev, gdrm->base.base.fd, true))
      gdrm->screen = pipe_loader_create_screen(gdrm->dev,
                                               get_library_search_path());
#endif /* HAVE_PIPE_LOADER_DRM */
#endif

   if (gdrm->screen == NULL)
      goto out_no_screen;

#if GALLIUM_STATIC_TARGETS
   gdrm->base.driver_name = strdup(dd_driver_name());
#else
#ifdef HAVE_PIPE_LOADER_DRM
   gdrm->base.driver_name = strdup(gdrm->dev->driver_name);
#endif /* HAVE_PIPE_LOADER_DRM */
#endif
   return &gdrm->base.base;

out_no_screen:
   debug_printf("failed to load gallium_gbm\n");
#if !GALLIUM_STATIC_TARGETS
   if (gdrm->dev)
      pipe_loader_release(&gdrm->dev, 1);
#endif
   free(gdrm);
   return NULL;
}


GBM_EXPORT struct gbm_backend gbm_backend = {
   .backend_name = "gallium_drm",
   .create_device = gbm_gallium_drm_device_create,
};
