/*
 * Copyright (C) 2016 Red Hat, Inc.
 * Author: Michael S. Tsirkin <mst@redhat.com>
 * This work is licensed under the terms of the GNU GPL, version 2.
 *
 * Command line processing and common functions for ring benchmarking.
 */
#define _GNU_SOURCE
#include <getopt.h>
#include <pthread.h>
#include <assert.h>
#include <sched.h>
#include "main.h"
#include <sys/eventfd.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

int runcycles = 10000000;
int max_outstanding = INT_MAX;
int batch = 1;

bool do_sleep = false;
bool do_relax = false;
bool do_exit = true;

unsigned ring_size = 256;

static int kickfd = -1;
static int callfd = -1;

void notify(int fd)
{
	unsigned long long v = 1;
	int r;

	vmexit();
	r = write(fd, &v, sizeof v);
	assert(r == sizeof v);
	vmentry();
}

void wait_for_notify(int fd)
{
	unsigned long long v = 1;
	int r;

	vmexit();
	r = read(fd, &v, sizeof v);
	assert(r == sizeof v);
	vmentry();
}

void kick(void)
{
	notify(kickfd);
}

void wait_for_kick(void)
{
	wait_for_notify(kickfd);
}

void call(void)
{
	notify(callfd);
}

void wait_for_call(void)
{
	wait_for_notify(callfd);
}

void set_affinity(const char *arg)
{
	cpu_set_t cpuset;
	int ret;
	pthread_t self;
	long int cpu;
	char *endptr;

	if (!arg)
		return;

	cpu = strtol(arg, &endptr, 0);
	assert(!*endptr);

	assert(cpu >= 0 || cpu < CPU_SETSIZE);

	self = pthread_self();
	CPU_ZERO(&cpuset);
	CPU_SET(cpu, &cpuset);

	ret = pthread_setaffinity_np(self, sizeof(cpu_set_t), &cpuset);
	assert(!ret);
}

void poll_used(void)
{
	while (used_empty())
		busy_wait();
}

static void __attribute__((__flatten__)) run_guest(void)
{
	int completed_before;
	int completed = 0;
	int started = 0;
	int bufs = runcycles;
	int spurious = 0;
	int r;
	unsigned len;
	void *buf;
	int tokick = batch;

	for (;;) {
		if (do_sleep)
			disable_call();
		completed_before = completed;
		do {
			if (started < bufs &&
			    started - completed < max_outstanding) {
				r = add_inbuf(0, "Buffer\n", "Hello, world!");
				if (__builtin_expect(r == 0, true)) {
					++started;
					if (!--tokick) {
						tokick = batch;
						if (do_sleep)
							kick_available();
					}

				}
			} else
				r = -1;

			/* Flush out completed bufs if any */
			if (get_buf(&len, &buf)) {
				++completed;
				if (__builtin_expect(completed == bufs, false))
					return;
				r = 0;
			}
		} while (r == 0);
		if (completed == completed_before)
			++spurious;
		assert(completed <= bufs);
		assert(started <= bufs);
		if (do_sleep) {
			if (used_empty() && enable_call())
				wait_for_call();
		} else {
			poll_used();
		}
	}
}

void poll_avail(void)
{
	while (avail_empty())
		busy_wait();
}

static void __attribute__((__flatten__)) run_host(void)
{
	int completed_before;
	int completed = 0;
	int spurious = 0;
	int bufs = runcycles;
	unsigned len;
	void *buf;

	for (;;) {
		if (do_sleep) {
			if (avail_empty() && enable_kick())
				wait_for_kick();
		} else {
			poll_avail();
		}
		if (do_sleep)
			disable_kick();
		completed_before = completed;
		while (__builtin_expect(use_buf(&len, &buf), true)) {
			if (do_sleep)
				call_used();
			++completed;
			if (__builtin_expect(completed == bufs, false))
				return;
		}
		if (completed == completed_before)
			++spurious;
		assert(completed <= bufs);
		if (completed == bufs)
			break;
	}
}

void *start_guest(void *arg)
{
	set_affinity(arg);
	run_guest();
	pthread_exit(NULL);
}

void *start_host(void *arg)
{
	set_affinity(arg);
	run_host();
	pthread_exit(NULL);
}

static const char optstring[] = "";
static const struct option longopts[] = {
	{
		.name = "help",
		.has_arg = no_argument,
		.val = 'h',
	},
	{
		.name = "host-affinity",
		.has_arg = required_argument,
		.val = 'H',
	},
	{
		.name = "guest-affinity",
		.has_arg = required_argument,
		.val = 'G',
	},
	{
		.name = "ring-size",
		.has_arg = required_argument,
		.val = 'R',
	},
	{
		.name = "run-cycles",
		.has_arg = required_argument,
		.val = 'C',
	},
	{
		.name = "outstanding",
		.has_arg = required_argument,
		.val = 'o',
	},
	{
		.name = "batch",
		.has_arg = required_argument,
		.val = 'b',
	},
	{
		.name = "sleep",
		.has_arg = no_argument,
		.val = 's',
	},
	{
		.name = "relax",
		.has_arg = no_argument,
		.val = 'x',
	},
	{
		.name = "exit",
		.has_arg = no_argument,
		.val = 'e',
	},
	{
	}
};

static void help(void)
{
	fprintf(stderr, "Usage: <test> [--help]"
		" [--host-affinity H]"
		" [--guest-affinity G]"
		" [--ring-size R (default: %d)]"
		" [--run-cycles C (default: %d)]"
		" [--batch b]"
		" [--outstanding o]"
		" [--sleep]"
		" [--relax]"
		" [--exit]"
		"\n",
		ring_size,
		runcycles);
}

int main(int argc, char **argv)
{
	int ret;
	pthread_t host, guest;
	void *tret;
	char *host_arg = NULL;
	char *guest_arg = NULL;
	char *endptr;
	long int c;

	kickfd = eventfd(0, 0);
	assert(kickfd >= 0);
	callfd = eventfd(0, 0);
	assert(callfd >= 0);

	for (;;) {
		int o = getopt_long(argc, argv, optstring, longopts, NULL);
		switch (o) {
		case -1:
			goto done;
		case '?':
			help();
			exit(2);
		case 'H':
			host_arg = optarg;
			break;
		case 'G':
			guest_arg = optarg;
			break;
		case 'R':
			ring_size = strtol(optarg, &endptr, 0);
			assert(ring_size && !(ring_size & (ring_size - 1)));
			assert(!*endptr);
			break;
		case 'C':
			c = strtol(optarg, &endptr, 0);
			assert(!*endptr);
			assert(c > 0 && c < INT_MAX);
			runcycles = c;
			break;
		case 'o':
			c = strtol(optarg, &endptr, 0);
			assert(!*endptr);
			assert(c > 0 && c < INT_MAX);
			max_outstanding = c;
			break;
		case 'b':
			c = strtol(optarg, &endptr, 0);
			assert(!*endptr);
			assert(c > 0 && c < INT_MAX);
			batch = c;
			break;
		case 's':
			do_sleep = true;
			break;
		case 'x':
			do_relax = true;
			break;
		case 'e':
			do_exit = true;
			break;
		default:
			help();
			exit(4);
			break;
		}
	}

	/* does nothing here, used to make sure all smp APIs compile */
	smp_acquire();
	smp_release();
	smp_mb();
done:

	if (batch > max_outstanding)
		batch = max_outstanding;

	if (optind < argc) {
		help();
		exit(4);
	}
	alloc_ring();

	ret = pthread_create(&host, NULL, start_host, host_arg);
	assert(!ret);
	ret = pthread_create(&guest, NULL, start_guest, guest_arg);
	assert(!ret);

	ret = pthread_join(guest, &tret);
	assert(!ret);
	ret = pthread_join(host, &tret);
	assert(!ret);
	return 0;
}