/*
 * Copyright © 2016 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:
 *    Tiago Vignatti <tiago.vignatti at intel.com>
 */

/*
 * Testcase: show case dma-buf new API and processes restrictions. Most likely
 * you want to run like ./prime_mmap_kms --interactive-debug=paint, to see the
 * actual rectangle painted on the screen.
 */

#include "igt.h"

IGT_TEST_DESCRIPTION(
   "Efficiently sharing CPU and GPU buffers");

/*
 * render_process_t:
 *
 * Render is basically a user-space regular client. It's the unprivileged
 * process with limited system accesses.
 *
 * Worth note the vendor-independent characteristic, meaning that the
 * client doesn't need to perform any vendor specific calls for buffer
 * handling. Mesa GBM library is a counter-example because, even though its API
 * is vendor-independent, under-the-hood the library actually calls vendor
 * specific ioctls, which is not really sandboxable and not the goal here.
 */
typedef struct {
	int prime_fd;
	size_t size;
	int width;
	int height;
} render_process_t;

typedef struct {
	int x;
	int y;
	int w;
	int h;
} rect_t;

/* set ptr in a linear view */
static void set_pixel(void *_ptr, int index, uint32_t color, int bpp)
{
	if (bpp == 16) {
		uint16_t *ptr = _ptr;
		ptr[index] = color;
	} else if (bpp == 32) {
		uint32_t *ptr = _ptr;
		ptr[index] = color;
	} else {
		igt_assert_f(false, "bpp: %d\n", bpp);
	}
}

static void paint(render_process_t *render)
{
	void *frame;
	rect_t rect = {
		.x = 200,
		.y = 200,
		.w = render->width / 4,
		.h = render->height / 4,
	};
	uint32_t color = 0xFF;
	int stride, bpp;
	int x, y, line_begin;

	frame = mmap(NULL, render->size, PROT_READ | PROT_WRITE, MAP_SHARED,
		     render->prime_fd, 0);
	igt_assert(frame != MAP_FAILED);

	/* TODO: what's the mmap'ed buffer semantics on tiling, format etc. How
	 * does the client know whether that the BO was created X-tiled,
	 * Y-tiled and how it will map back? This is something we need to
	 * address in this API still. */
	stride = render->width * 4;
	bpp = 32;

	/* ioctls to keep up the GPU <-> CPU coherency */
	prime_sync_start(render->prime_fd, true);

	/* the actual painting phase happens here */
	for (y = rect.y; y < rect.y + rect.h; y++) {
		line_begin = y * stride / (bpp / 8);
		for (x = rect.x; x < rect.x + rect.w; x++)
			set_pixel(frame, line_begin + x, color, bpp);
	}

	prime_sync_end(render->prime_fd, true);
	munmap(frame, render->size);
}

static void init_renderer(int prime_fd, int fb_size, int width, int height)
{
	render_process_t render;

	render.prime_fd = prime_fd;
	render.size = fb_size;
	render.width = width;
	render.height = height;
	paint(&render);
}

/*
 * gpu_process_t:
 *
 * GPU process is the privileged process and has access to the system graphics
 * routines, like DRM, display management and driver accesses.
 */
typedef struct {
	int drm_fd;
	igt_display_t display;
	struct igt_fb fb;
	igt_output_t *output;
	igt_plane_t *primary;
	enum pipe pipe;
} gpu_process_t;

static void cleanup_crtc(gpu_process_t *gpu)
{
	igt_display_t *display = &gpu->display;
	igt_output_t *output = gpu->output;

	igt_plane_set_fb(gpu->primary, NULL);

	igt_output_set_pipe(output, PIPE_ANY);
	igt_display_commit(display);

	igt_remove_fb(gpu->drm_fd, &gpu->fb);
}

static void prepare_crtc(gpu_process_t *gpu)
{
	igt_display_t *display = &gpu->display;
	igt_output_t *output = gpu->output;
	drmModeModeInfo *mode;

	/* select the pipe we want to use */
	igt_output_set_pipe(output, gpu->pipe);

	mode = igt_output_get_mode(output);

	/* create a white fb and flip to it */
	igt_create_color_fb(gpu->drm_fd, mode->hdisplay, mode->vdisplay,
			DRM_FORMAT_XRGB8888, LOCAL_DRM_FORMAT_MOD_NONE,
			1.0, 1.0, 1.0, &gpu->fb);

	gpu->primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);

	igt_plane_set_fb(gpu->primary, &gpu->fb);
	igt_display_commit(display);
}

/*
 * The idea is to create a BO (in this case the framebuffer's) in one process,
 * export and pass its prime fd to another process, which in turn uses the fd
 * to map and write. This is Chrome-like architectures, where the Web content
 * (a "tab" or the "unprivileged process") maps and CPU-paints a buffer, which
 * was previously allocated in the GPU process ("privileged process").
 */
static void run_test(gpu_process_t *gpu)
{
	igt_display_t *display = &gpu->display;
	igt_output_t *output;
	enum pipe pipe;
	int prime_fd;

	for_each_pipe_with_valid_output(display, pipe, output) {
		gpu->output = output;
		gpu->pipe = pipe;

		prepare_crtc(gpu);

		prime_fd = prime_handle_to_fd_for_mmap(gpu->drm_fd,
							gpu->fb.gem_handle);
		igt_skip_on(prime_fd == -1 && errno == EINVAL);

		/* Note that it only shares the dma-buf fd and some
		  * other basic info */
		igt_fork(renderer_no, 1) {
			init_renderer(prime_fd, gpu->fb.size, gpu->fb.width,
				      gpu->fb.height);
		}
		igt_waitchildren();

		igt_debug_wait_for_keypress("paint");
		cleanup_crtc(gpu);

		/* once is enough */
		return;
	}

	igt_skip("no valid crtc/connector combinations found\n");
}

static int
check_for_dma_buf_mmap(int fd)
{
	int dma_buf_fd;
	char *ptr;
	uint32_t handle;
	int ret = 1;

	handle = gem_create(fd, 4096);
	dma_buf_fd = prime_handle_to_fd(fd, handle);
	ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, dma_buf_fd, 0);
	if (ptr != MAP_FAILED)
		ret = 0;
	munmap(ptr, 4096);
	gem_close(fd, handle);
	close(dma_buf_fd);
	return ret;
}

igt_main
{
	gpu_process_t gpu;

	igt_skip_on_simulation();

	igt_fixture {
		gpu.drm_fd = drm_open_driver_master(DRIVER_INTEL);
		igt_skip_on((check_for_dma_buf_mmap(gpu.drm_fd) != 0));
		kmstest_set_vt_graphics_mode();

		igt_require_pipe_crc(gpu.drm_fd);

		igt_display_require(&gpu.display, gpu.drm_fd);
	}

	igt_subtest("buffer-sharing")
		run_test(&gpu);

	igt_fixture {
		igt_display_fini(&gpu.display);
		close(gpu.drm_fd);
	}
}
