/*
 * Copyright (c) 2017, Linaro Limited
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <err.h>
#include <fcntl.h>
#include <math.h>
#include <pthread.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <tee_bench.h>
#include <tee_client_api.h>
#include <unistd.h>

#include "teec_benchmark.h"

struct tee_ts_global *bench_ts_global;
static const TEEC_UUID pta_benchmark_uuid = PTA_BENCHMARK_UUID;

static TEEC_Context bench_ctx;
static TEEC_Session bench_sess;

static pthread_mutex_t teec_bench_mu = PTHREAD_MUTEX_INITIALIZER;

/* Cycle counter */
static inline uint64_t read_ccounter(void)
{
	uint64_t ccounter = 0;
#ifdef __aarch64__
	asm volatile("mrs %0, PMCCNTR_EL0" : "=r"(ccounter));
#else
	asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(ccounter));
#endif
	return ccounter * TEE_BENCH_DIVIDER;
}

static TEEC_Result benchmark_pta_open(void)
{
	TEEC_Result res = TEEC_ERROR_GENERIC;
	uint32_t ret_orig = 0;

	res = TEEC_InitializeContext(NULL, &bench_ctx);
	if (res != TEEC_SUCCESS)
		return res;

	res = TEEC_OpenSession(&bench_ctx, &bench_sess,
			&pta_benchmark_uuid,
			TEEC_LOGIN_PUBLIC, NULL, NULL, &ret_orig);
	if (res != TEEC_SUCCESS) {
		TEEC_FinalizeContext(&bench_ctx);
		return res;
	}

	return res;
}

static void benchmark_pta_close(void)
{
	TEEC_CloseSession(&bench_sess);
	TEEC_FinalizeContext(&bench_ctx);
}

static TEEC_Result benchmark_get_bench_buf_paddr(uint64_t *paddr_ts_buf,
				uint64_t *size)
{
	TEEC_Result res = TEEC_ERROR_GENERIC;
	uint32_t ret_orig = 0;
	TEEC_Operation op;

	memset(&op, 0, sizeof(op));

	res = benchmark_pta_open();
	if (res != TEEC_SUCCESS)
		return res;

	op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE,
					TEEC_NONE, TEEC_NONE);

	res = TEEC_InvokeCommand(&bench_sess, BENCHMARK_CMD_GET_MEMREF,
					&op, &ret_orig);
	if (res != TEEC_SUCCESS)
		return res;

	*paddr_ts_buf = op.params[0].value.a;
	*size = op.params[0].value.b;

	benchmark_pta_close();

	return res;
}

static void *mmap_paddr(intptr_t paddr, uint64_t size)
{
	int devmem = 0;
	off_t offset = 0;
	off_t page_addr = 0;
	intptr_t *hw_addr = NULL;

	devmem = open("/dev/mem", O_RDWR);
	if (!devmem)
		return NULL;

	offset = (off_t)paddr % getpagesize();
	page_addr = (off_t)(paddr - offset);

	hw_addr = (intptr_t *)mmap(0, size + offset, PROT_READ|PROT_WRITE,
					MAP_SHARED, devmem, page_addr);
	if (hw_addr == MAP_FAILED) {
		close(devmem);
		return NULL;
	}

	close(devmem);
	return (hw_addr + offset);
}

/* check if we are in benchmark mode */
static bool benchmark_check_mode(void)
{
	uint64_t ts_buf_raw = 0;
	uint64_t ts_buf_size = 0;
	bool res = true;

	if (!bench_ts_global) {
		/* receive buffer from Benchmark PTA and register it */
		benchmark_get_bench_buf_paddr(&ts_buf_raw, &ts_buf_size);
		if (ts_buf_raw && ts_buf_size) {
			bench_ts_global = mmap_paddr(ts_buf_raw, ts_buf_size);
			res = (bench_ts_global) ? true : false;
		} else {
			res = false;
		}
	}

	return res;
}

/* Adding timestamp to buffer */
void bm_timestamp(void)
{
	struct tee_ts_cpu_buf *cpu_buf = NULL;
	uint64_t ts_i = 0;
	void *ret_addr = NULL;
	uint32_t cur_cpu = 0;
	int ret = 0;
	cpu_set_t cpu_set_old;
	cpu_set_t cpu_set_tmp;
	struct tee_time_st ts_data;

	memset(&cpu_set_old, 0, sizeof(cpu_set_old));
	memset(&cpu_set_tmp, 0, sizeof(cpu_set_tmp));
	memset(&ts_data, 0, sizeof(ts_data));

	if (pthread_mutex_trylock(&teec_bench_mu))
		return;

	if (!benchmark_check_mode())
		goto error;

	CPU_ZERO(&cpu_set_old);
	ret = sched_getaffinity(0, sizeof(cpu_set_old), &cpu_set_old);
	if (ret)
		goto error;

	/* stick to the same core while putting timestamp */
	cur_cpu = sched_getcpu();
	CPU_ZERO(&cpu_set_tmp);
	CPU_SET(cur_cpu, &cpu_set_tmp);
	ret = sched_setaffinity(0, sizeof(cpu_set_tmp), &cpu_set_tmp);
	if (ret)
		goto error;

	/* fill timestamp data */
	if (cur_cpu >= bench_ts_global->cores) {
		ret = sched_setaffinity(0, sizeof(cpu_set_old), &cpu_set_old);
		goto error;
	}

	ret_addr = __builtin_return_address(0);

	cpu_buf = &bench_ts_global->cpu_buf[cur_cpu];
	ts_i = __sync_fetch_and_add(&cpu_buf->head, 1);
	ts_data.cnt = read_ccounter();
	ts_data.addr = (uintptr_t)ret_addr;
	ts_data.src = TEE_BENCH_CLIENT;
	cpu_buf->stamps[ts_i & TEE_BENCH_MAX_MASK] = ts_data;

	/* set back affinity mask */
	sched_setaffinity(0, sizeof(cpu_set_old), &cpu_set_old);

error:
	pthread_mutex_unlock(&teec_bench_mu);
}

