/*
 * Copyright © 2012 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:
 *    Chris Wilson <chris@chris-wilson.co.uk>
 *
 */

/* Exercises the basic execbuffer using the handle LUT interface */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>

#include "drm.h"
#include "drmtest.h"
#include "i915/gem.h"
#include "i915/gem_mman.h"
#include "igt_debugfs.h"
#include "intel_reg.h"
#include "ioctl_wrappers.h"

#define SKIP_RELOC 0x1
#define NO_RELOC 0x2
#define CYCLE_BATCH 0x4
#define LUT 0x10
#define SEQUENTIAL_OFFSET 0x20
#define REVERSE_OFFSET 0x40
#define RANDOM_OFFSET 0x80

static uint32_t
hars_petruska_f54_1_random (void)
{
	static uint32_t state = 0x12345678;

#define rol(x,k) ((x << k) | (x >> (32-k)))
	return state = (state ^ rol (state, 5) ^ rol (state, 24)) + 0x37798849;
#undef rol
}

#define ELAPSED(a,b) (1e6*((b)->tv_sec - (a)->tv_sec) + ((b)->tv_usec - (a)->tv_usec))
static int run(unsigned batch_size,
	       unsigned flags,
	       int num_objects,
	       int num_relocs, int reps)
{
	uint32_t batch[2] = {MI_BATCH_BUFFER_END};
	uint32_t cycle[16];
	int fd, n, count, c, size = 0;
	struct drm_i915_gem_relocation_entry *reloc = NULL;
	struct drm_i915_gem_execbuffer2 execbuf;
	struct drm_i915_gem_exec_object2 *objects;
	struct timeval start, end;
	uint32_t reloc_handle = 0;
	struct drm_i915_gem_exec_object2 *gem_exec;
	struct drm_i915_gem_relocation_entry *mem_reloc = NULL;
	int *target;

	gem_exec = calloc(sizeof(*gem_exec), num_objects + 1);
	mem_reloc = calloc(sizeof(*mem_reloc), num_relocs);
	target = calloc(sizeof(*target), num_relocs);

	fd = drm_open_driver(DRIVER_INTEL);

	for (n = 0; n < num_objects; n++)
		gem_exec[n].handle = gem_create(fd, 4096);

	for (n = 0; n < 16; n++) {
		cycle[n] = gem_create(fd, batch_size);
		gem_write(fd, cycle[n], 0, batch, sizeof(batch));
	}
	gem_exec[num_objects].handle = cycle[c = 0];

	for (n = 0; n < num_relocs; n++) {
		mem_reloc[n].offset = 1024;
		mem_reloc[n].read_domains = I915_GEM_DOMAIN_RENDER;
	}
	for (n = 0; n < num_relocs; n++) {
		if (flags & SEQUENTIAL_OFFSET)
			mem_reloc[n].offset = 8 + (8*n % (batch_size - 16));
		else if (flags & REVERSE_OFFSET)
			mem_reloc[n].offset = batch_size - 8 - (8*n % (batch_size - 16));
		else if (flags & RANDOM_OFFSET)
			mem_reloc[n].offset = 8 +
				8*hars_petruska_f54_1_random() % (batch_size - 16);
		else
			mem_reloc[n].offset = 1024;
		mem_reloc[n].read_domains = I915_GEM_DOMAIN_RENDER;
	}

	if (num_relocs) {
		size = ALIGN(sizeof(*mem_reloc)*num_relocs, 4096);
		reloc_handle = gem_create(fd, size);
		reloc = __gem_mmap__cpu(fd, reloc_handle, 0, size, PROT_READ | PROT_WRITE);
		memcpy(reloc, mem_reloc, sizeof(*mem_reloc)*num_relocs);
		munmap(reloc, size);

		reloc = mem_reloc;
	}

	gem_exec[num_objects].relocation_count = num_relocs;
	gem_exec[num_objects].relocs_ptr = (uintptr_t)reloc;
	objects = gem_exec;

	memset(&execbuf, 0, sizeof(execbuf));
	execbuf.buffers_ptr = (uintptr_t)objects;
	execbuf.buffer_count = num_objects + 1;
	if (flags & LUT)
		execbuf.flags |= I915_EXEC_HANDLE_LUT;
	if (flags & NO_RELOC)
		execbuf.flags |= I915_EXEC_NO_RELOC;

	for (n = 0; n < num_relocs; n++) {
		target[n] = hars_petruska_f54_1_random() % num_objects;
		if (flags & LUT)
			reloc[n].target_handle = target[n];
		else
			reloc[n].target_handle = objects[target[n]].handle;
		reloc[n].presumed_offset = -1;
	}

	gem_execbuf(fd, &execbuf);

	while (reps--) {
		gettimeofday(&start, NULL);
		for (count = 0; count < 1000; count++) {
			if ((flags & SKIP_RELOC) == 0) {
				for (n = 0; n < num_relocs; n++)
					reloc[n].presumed_offset = -1;
				if (flags & CYCLE_BATCH) {
					c = (c + 1) % 16;
					gem_exec[num_objects].handle = cycle[c];
				}
			}
			gem_execbuf(fd, &execbuf);
		}
		gettimeofday(&end, NULL);
		printf("%.3f\n", ELAPSED(&start, &end));
	}

	return 0;
}

int main(int argc, char **argv)
{
	unsigned num_objects = 1, num_relocs = 0, flags = 0;
	unsigned size = 4096;
	int reps = 13;
	int c;

	while ((c = getopt (argc, argv, "b:r:s:e:l:m:o:")) != -1) {
		switch (c) {
		case 'l':
			reps = atoi(optarg);
			if (reps < 1)
				reps = 1;
			break;

		case 's':
			size = atoi(optarg);
			if (size < 4096)
				size = 4096;
			size = ALIGN(size, 4096);
			break;

		case 'e':
			if (strcmp(optarg, "busy") == 0) {
				flags |= 0;
			} else if (strcmp(optarg, "cyclic") == 0) {
				flags |= CYCLE_BATCH;
			} else if (strcmp(optarg, "skip") == 0) {
				flags |= SKIP_RELOC;
			} else if (strcmp(optarg, "none") == 0) {
				flags |= SKIP_RELOC | NO_RELOC;
			} else {
				abort();
			}
			break;

		case 'm':
			if (strcmp(optarg, "old") == 0) {
				flags |= 0;
			} else if (strcmp(optarg, "lut") == 0) {
				flags |= LUT;
			} else {
				abort();
			}
			break;

		case 'o':
			if (strcmp(optarg, "constant") == 0) {
				flags |= 0;
			} else if (strcmp(optarg, "sequential") == 0) {
				flags |= SEQUENTIAL_OFFSET;
			} else if (strcmp(optarg, "reverse") == 0) {
				flags |= REVERSE_OFFSET;
			} else if (strcmp(optarg, "random") == 0) {
				flags |= RANDOM_OFFSET;
			} else {
				abort();
			}
			break;

		case 'b':
			num_objects = atoi(optarg);
			if (num_objects < 1)
				num_objects = 1;
			break;

		case 'r':
			num_relocs = atoi(optarg);
			break;
		}
	}

	return run(size, flags, num_objects, num_relocs, reps);
}
