// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */

#define TMEM_PROFILE_FMT
#define PR_FMT_HEADER_MUST_BE_INCLUDED_BEFORE_ALL_HDRS
#include "private/tmem_pr_fmt.h" PR_FMT_HEADER_MUST_BE_INCLUDED_BEFORE_ALL_HDRS

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/time.h>
#include <linux/slab.h>

#include "private/mld_helper.h"
#include "private/tmem_error.h"
#include "private/tmem_device.h"
#include "private/tmem_utils.h"
#include "private/tmem_priv.h"

struct profile_entry_string {
	int idx;
	const char *str;
};

#define STR(a) #a
struct profile_entry_string profile_entry_str[] = {
	{PROFILE_ENTRY_SSMR_GET, STR(SSMR_GET)},
	{PROFILE_ENTRY_SSMR_PUT, STR(SSMR_PUT)},
	{PROFILE_ENTRY_CHUNK_ALLOC, STR(CHUNK_ALLOC)},
	{PROFILE_ENTRY_CHUNK_FREE, STR(CHUNK_FREE)},
	{PROFILE_ENTRY_MEM_ADD, STR(MEM_ADD)},
	{PROFILE_ENTRY_MEM_REMOVE, STR(MEM_REMOVE)},
	{PROFILE_ENTRY_SESSION_OPEN, STR(SESSION_OPEN)},
	{PROFILE_ENTRY_SESSION_CLOSE, STR(SESSION_CLOSE)},
	{PROFILE_ENTRY_INVOKE_COMMAND, STR(INVOKE_COMMAND)},
};

#define GET_START_TIME() tmem_do_gettimeofday(&start_time)
#define GET_END_TIME() tmem_do_gettimeofday(&end_time)
static void tmem_do_gettimeofday(struct timespec64 *tv)
{
	struct timespec64 now;

	ktime_get_ts64(&now);

	tv->tv_sec = now.tv_sec;
	tv->tv_nsec = now.tv_nsec;
}

#define SEC_TO_US(s) (s * 1000000)
#define NSEC_TO_US(s) ((long)s/1000)

#include <asm/div64.h>
static inline u64 u64_div(u64 n, u64 base)
{
	do_div(n, base);
	return n;
}
static inline u64 us_to_ms(u64 us)
{
	return u64_div(us, 1000);
}

static bool is_valid_profile_entry(enum PROFILE_ENTRY_TYPE entry)
{
	return ((entry >= PROFILE_ENTRY_SSMR_GET)
		&& (entry < PROFILE_ENTRY_MAX));
}

void trusted_mem_core_profile_dump(struct trusted_mem_device *mem_device)
{
	int idx;
	u64 average_one_time_us;
	u64 average_one_time_ms;

	struct profile_data_context *data = &mem_device->profile_mgr->data;

	pr_info("=================================================\n");
	pr_info("[%d] Profiling Summary:\n", mem_device->mem_type);

	for (idx = 0; idx < PROFILE_ENTRY_MAX; idx++) {
		average_one_time_us = SEC_TO_US(data->item[idx].sec);
		average_one_time_us += NSEC_TO_US(data->item[idx].nsec);
		average_one_time_us =
			u64_div(average_one_time_us, data->item[idx].count);
		average_one_time_ms = us_to_ms(average_one_time_us);

		pr_info("[%d] Entry: %s\n", mem_device->mem_type,
			profile_entry_str[idx].str);
		pr_info("[%d]   invoke count: %lld\n", mem_device->mem_type,
			data->item[idx].count);
		pr_info("[%d]   spend time: %lld.%06lld sec\n",
			mem_device->mem_type, data->item[idx].sec,
			NSEC_TO_US(data->item[idx].nsec));
		pr_info("[%d]   average one time: %lld msec (%06lld usec)\n",
			mem_device->mem_type, average_one_time_ms,
			average_one_time_us);
	}

	pr_info("=================================================\n");
}

static void increase_enter_count(enum PROFILE_ENTRY_TYPE entry,
				 struct profile_data_context *data)
{
	if (!is_valid_profile_entry(entry))
		return;

	mutex_lock(&data->item[entry].lock);
	data->item[entry].count++;
	mutex_unlock(&data->item[entry].lock);
}

static void add_exec_time(enum PROFILE_ENTRY_TYPE entry,
			  struct profile_data_context *data,
			  struct timespec64 *start, struct timespec64 *end)
{
	int time_diff_sec = GET_TIME_DIFF_SEC_P(start, end);
	int time_diff_nsec = GET_TIME_DIFF_NSEC_P(start, end);

	if (!is_valid_profile_entry(entry))
		return;

	mutex_lock(&data->item[entry].lock);

	data->item[entry].sec += time_diff_sec;
	data->item[entry].nsec += time_diff_nsec;
	if (data->item[entry].nsec > 1000000000) {
		data->item[entry].sec += 1;
		data->item[entry].nsec -= 1000000000;
	}

	mutex_unlock(&data->item[entry].lock);
}

static int profile_ssmr_get(u64 *pa, u32 *size, u32 feat, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_SSMR_GET, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_ssmr_ops->offline(pa, size, feat,
						   prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_SSMR_GET, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_ssmr_put(u32 feat, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_SSMR_PUT, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_ssmr_ops->online(feat,
						  prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_SSMR_PUT, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_chunk_alloc(u32 alignment, u32 size, u32 *refcount,
			       u32 *sec_handle, u8 *owner, u32 id, u32 clean,
			       void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_CHUNK_ALLOC, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->memory_alloc(
		alignment, size, refcount, sec_handle, owner, id, clean,
		peer_data, prof_mgr->profiled_dev_desc);
	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_CHUNK_ALLOC, &prof_mgr->data, &start_time,
		      &end_time);

	return ret;
}

static int profile_chunk_free(u32 sec_handle, u8 *owner, u32 id,
			      void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_CHUNK_FREE, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->memory_free(
		sec_handle, owner, id, peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_CHUNK_FREE, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_mem_add(u64 pa, u32 size, void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_MEM_ADD, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->memory_grant(
		pa, size, peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_MEM_ADD, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_mem_remove(void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_MEM_REMOVE, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->memory_reclaim(
		peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_MEM_REMOVE, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_session_open(void **peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_SESSION_OPEN, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->session_open(
		peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_SESSION_OPEN, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int profile_session_close(void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_SESSION_CLOSE, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->session_close(
		peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_SESSION_CLOSE, &prof_mgr->data, &start_time,
		      &end_time);
	return ret;
}

static int
profile_invoke_command(struct trusted_driver_cmd_params *invoke_params,
		       void *peer_data, void *dev_desc)
{
	int ret;
	struct timespec64 start_time, end_time;
	struct profile_mgr_desc *prof_mgr = (struct profile_mgr_desc *)dev_desc;

	increase_enter_count(PROFILE_ENTRY_INVOKE_COMMAND, &prof_mgr->data);
	GET_START_TIME();

	ret = prof_mgr->profiled_peer_ops->invoke_cmd(
		invoke_params, peer_data, prof_mgr->profiled_dev_desc);

	GET_END_TIME();
	add_exec_time(PROFILE_ENTRY_INVOKE_COMMAND, &prof_mgr->data,
		      &start_time, &end_time);
	return ret;
}

static struct trusted_driver_operations profiler_peer_ops = {
	.session_open = profile_session_open,
	.session_close = profile_session_close,
	.memory_alloc = profile_chunk_alloc,
	.memory_free = profile_chunk_free,
	.memory_grant = profile_mem_add,
	.memory_reclaim = profile_mem_remove,
	.invoke_cmd = profile_invoke_command,
};

static struct ssmr_operations profiler_ssmr_ops = {
	.offline = profile_ssmr_get,
	.online = profile_ssmr_put,
};

struct profile_mgr_desc *create_profile_mgr_desc(void)
{
	int idx;
	struct profile_mgr_desc *t_profile_desc;

	pr_info("TMEM_PROFILE_ENABLED\n");

	t_profile_desc =
		mld_kmalloc(sizeof(struct profile_mgr_desc), GFP_KERNEL);
	if (INVALID(t_profile_desc)) {
		pr_err("%s:%d out of memory!\n", __func__, __LINE__);
		return NULL;
	}

	for (idx = 0; idx < PROFILE_ENTRY_MAX; idx++) {
		mutex_init(&t_profile_desc->data.item[idx].lock);
		t_profile_desc->data.item[idx].count = 0;
		t_profile_desc->data.item[idx].sec = 0;
		t_profile_desc->data.item[idx].nsec = 0;
	}

	t_profile_desc->profiled_peer_ops = &profiler_peer_ops;
	t_profile_desc->profiled_ssmr_ops = &profiler_ssmr_ops;
	t_profile_desc->profiled_dev_desc = t_profile_desc;

	return t_profile_desc;
}
