// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */
#include <linux/sched/clock.h>
#include <linux/sched/mm.h>
#include <linux/sched/numa_balancing.h>
#include <linux/sched/task_stack.h>
#include <linux/sched/task.h>
#include <linux/sched/cputime.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/kallsyms.h>
#include <linux/trace_events.h>

#include <linux/workqueue.h>
#include <linux/unistd.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "mt-plat/fpsgo_common.h"
#include "gbe1_usedext.h"
#include "fstb_usedext.h"
#include "gbe_common.h"
#include "gbe_sysfs.h"
#include "fpsgo_base.h"

enum GBE_NOTIFIER_PUSH_TYPE {
	GBE_NOTIFIER_SWITCH_GBE			= 0x00,
	GBE_NOTIFIER_RTID		= 0x01,
};

struct GBE_NOTIFIER_PUSH_TAG {
	enum GBE_NOTIFIER_PUSH_TYPE ePushType;

	struct hlist_head *list;
	int enable;

	struct work_struct sWork;
};

static DEFINE_MUTEX(gbe_list_lock);
static DEFINE_MUTEX(gbe_enable1_lock);
static struct workqueue_struct *g_psNotifyWorkQueue;
static int gbe_enable;

/* TODO: event register & dispatch */
int gbe_is_enable(void)
{
	int enable;

	mutex_lock(&gbe_enable1_lock);
	enable = gbe_enable;
	mutex_unlock(&gbe_enable1_lock);

	return enable;
}

void enable_gbe(int enable)
{
	pr_debug("[GBE] enable:%d %p\n", enable, &gbe_enable1_lock);
	mutex_lock(&gbe_enable1_lock);
	gbe_enable = !!enable;
	mutex_unlock(&gbe_enable1_lock);
}

static unsigned long long gbe_get_time(void)
{
	unsigned long long temp;

	preempt_disable();
	temp = cpu_clock(smp_processor_id());
	preempt_enable();

	return temp;
}

/***********************************************************/
/*main logic*/
static void gbe_ctrl2comp_fstb_poll(struct hlist_head *list)
{
	struct GBE_FSTB_TID_LIST *iter;
	struct task_struct *tsk, *gtsk, *sib;
	struct GBE_BOOST_LIST *gbe_list_iter = NULL;
	struct hlist_node *t;
	int tid = 0;
	int tgid = 0;
	int boost = 0, should_boost;

	if (!gbe_is_enable()) {
		gbe_boost(KIR_GBE1, 0);
		return;
	}

	mutex_lock(&gbe_list_lock);

	hlist_for_each_entry(iter, list, hlist) {
		tid = iter->tid;

		rcu_read_lock();
		tsk = find_task_by_vpid(tid);

		if (!tsk) {
			rcu_read_unlock();
			continue;
		}

		get_task_struct(tsk);
		gtsk = tsk->group_leader;

		if (!gtsk) {
			put_task_struct(tsk);
			rcu_read_unlock();
			continue;
		}

		get_task_struct(gtsk);
		tgid = gtsk->pid;
		list_for_each_entry(sib, &gtsk->thread_group, thread_group) {
			if (!sib)
				continue;

			if (boost)
				break;

			get_task_struct(sib);

			hlist_for_each_entry(gbe_list_iter,
					&gbe_boost_list, hlist) {

				should_boost = 1;

				if (boost)
					break;

				if ((!strncmp("*",
					gbe_list_iter->process_name, 1) ||
					!strncmp(gtsk->comm,
					gbe_list_iter->process_name, 15)) &&
					!strncmp(sib->comm,
					gbe_list_iter->thread_name, 15)) {

					if (fpsgo_task_sched_runtime(sib) ==
							gbe_list_iter->last_task_runtime &&
							gbe_list_iter->tid == sib->pid) {

						gbe_list_iter->last_task_runtime = 0;
						continue;
					}

					gbe_list_iter->pid = tgid;
					gbe_list_iter->tid = sib->pid;
					gbe_list_iter->now_task_runtime =
						fpsgo_task_sched_runtime(sib);

					gbe_list_iter->cur_ts = gbe_get_time();
					gbe_list_iter->runtime_percent =
						1000ULL *
#if BITS_PER_LONG == 32
						div_u64((gbe_list_iter->now_task_runtime -
						gbe_list_iter->last_task_runtime),
						(gbe_list_iter->cur_ts - gbe_list_iter->last_ts));
#else
					(gbe_list_iter->now_task_runtime -
					 gbe_list_iter->last_task_runtime) /
						(gbe_list_iter->cur_ts - gbe_list_iter->last_ts);
#endif
					if (gbe_list_iter->now_task_runtime <
						gbe_list_iter->last_task_runtime)
						gbe_list_iter->runtime_percent = 0;

					if (gbe_list_iter->runtime_percent > 0)
						gbe_trace_count(gbe_list_iter->tid, 0,
								gbe_list_iter->runtime_percent,
								"runtime_percent");

					if (gbe_list_iter->last_task_runtime == 0 &&
						gbe_list_iter->boost_cnt)
						should_boost = 0;

					gbe_list_iter->last_task_runtime =
						gbe_list_iter->now_task_runtime;
					gbe_list_iter->last_ts = gbe_list_iter->cur_ts;

					if (gbe_list_iter->runtime_percent >
						gbe_list_iter->runtime_thrs &&
						should_boost) {
						boost = 1;
						gbe_list_iter->boost_cnt++;
						gbe_trace_count(gbe_list_iter->tid,
							0, 1, "gbe_boost");
					}
				}
			}

			put_task_struct(sib);
		}
		put_task_struct(gtsk);
		put_task_struct(tsk);

		rcu_read_unlock();
	}

	hlist_for_each_entry_safe(iter, t,
			&gbe_fstb_tid_list, hlist) {
		hlist_del(&iter->hlist);
		kfree(iter);
	}

	gbe_boost(KIR_GBE1, boost);

	mutex_unlock(&gbe_list_lock);
}
/***********************************************************/

static void gbe_notifier_wq_cb_rtid(struct hlist_head *list)
{

	if (!gbe_is_enable()) {
		gbe_boost(KIR_GBE1, 0);
		return;
	}

	gbe_ctrl2comp_fstb_poll(list);
}

#define GBE_CONTAINER_OF(ptr, type, member) \
	((type *)(((char *)ptr) - offsetof(type, member)))

static void gbe_notifier_wq_cb(struct work_struct *psWork)
{
	struct GBE_NOTIFIER_PUSH_TAG *vpPush =
		GBE_CONTAINER_OF(psWork,
				struct GBE_NOTIFIER_PUSH_TAG, sWork);

	if (!vpPush)
		return;

	switch (vpPush->ePushType) {
	case GBE_NOTIFIER_RTID:
		gbe_notifier_wq_cb_rtid(vpPush->list);
		break;
	default:
		break;
	}

	kfree(vpPush);
}

void gbe_notify_fstb_poll(struct hlist_head *list)
{
	struct GBE_NOTIFIER_PUSH_TAG *vpPush;
	struct GBE_FSTB_TID_LIST *gbe_iter;
	struct FSTB_FRAME_INFO *fstb_iter;


	if (!gbe_is_enable())
		return;

	vpPush =
		(struct GBE_NOTIFIER_PUSH_TAG *)
		kmalloc(sizeof(struct GBE_NOTIFIER_PUSH_TAG), GFP_ATOMIC);

	if (!vpPush)
		return;

	if (!g_psNotifyWorkQueue) {
		kfree(vpPush);
		return;
	}


	hlist_for_each_entry(fstb_iter, list, hlist) {

		gbe_iter = kzalloc(sizeof(*gbe_iter), GFP_KERNEL);

		if (gbe_iter) {
			gbe_iter->tid = fstb_iter->pid;
			hlist_add_head(&gbe_iter->hlist, &gbe_fstb_tid_list);
		}
	}

	vpPush->ePushType = GBE_NOTIFIER_RTID;
	vpPush->list = &gbe_fstb_tid_list;

	INIT_WORK(&vpPush->sWork, gbe_notifier_wq_cb);
	queue_work(g_psNotifyWorkQueue, &vpPush->sWork);
}

#define MAX_GBE_BOOST_LIST_LENGTH 20
static int gbe_boost_list_length;
int set_gbe_boost_list(char *proc_name,
		char *thrd_name, unsigned long long runtime_thrs)
{
	struct GBE_BOOST_LIST *new_gbe_boost_list;
	int retval = 0;

	mutex_lock(&gbe_list_lock);

	if (!strncmp("0", proc_name, 1) &&
			!strncmp("0", thrd_name, 1) &&
			runtime_thrs == 0) {

		struct GBE_BOOST_LIST *iter;
		struct hlist_node *t;

		hlist_for_each_entry_safe(iter, t,
				&gbe_boost_list, hlist) {
			hlist_del(&iter->hlist);
			kfree(iter);
		}

		gbe_boost_list_length = 0;
		goto out;
	}

	if (gbe_boost_list_length >= MAX_GBE_BOOST_LIST_LENGTH) {
		retval = -ENOMEM;
		goto out;
	}

	new_gbe_boost_list =
		kzalloc(sizeof(*new_gbe_boost_list), GFP_KERNEL);
	if (new_gbe_boost_list == NULL) {
		retval = -ENOMEM;
		goto out;
	}

	if (!strncpy(
				new_gbe_boost_list->process_name,
				proc_name, 16)) {
		kfree(new_gbe_boost_list);
		retval = -ENOMEM;
		goto out;
	}
	new_gbe_boost_list->process_name[15] = '\0';

	if (!strncpy(
				new_gbe_boost_list->thread_name,
				thrd_name, 16)) {
		kfree(new_gbe_boost_list);
		retval = -ENOMEM;
		goto out;
	}
	new_gbe_boost_list->thread_name[15] = '\0';

	new_gbe_boost_list->runtime_thrs = runtime_thrs;

	hlist_add_head(&new_gbe_boost_list->hlist,
			&gbe_boost_list);

	gbe_boost_list_length++;

out:
	mutex_unlock(&gbe_list_lock);

	return retval;
}

static ssize_t gbe_enable1_show(struct kobject *kobj,
		struct kobj_attribute *attr,
		char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", gbe_is_enable());
}

static ssize_t gbe_enable1_store(struct kobject *kobj,
		struct kobj_attribute *attr,
		const char *buf, size_t count)
{
	char acBuffer[GBE_SYSFS_MAX_BUFF_SIZE];
	int arg;
	int val = 0;

	if ((count > 0) && (count < GBE_SYSFS_MAX_BUFF_SIZE)) {
		if (scnprintf(acBuffer, GBE_SYSFS_MAX_BUFF_SIZE, "%s", buf)) {
			if (kstrtoint(acBuffer, 0, &arg) == 0)
				val = arg;
			else
				return count;
		}
	}

	enable_gbe(val);

	return count;
}

static KOBJ_ATTR_RW(gbe_enable1);

static ssize_t gbe_boost_list1_show(struct kobject *kobj,
	struct kobj_attribute *attr,
	char *buf)
{
	struct GBE_BOOST_LIST *gbe_list_iter = NULL;
	char temp[GBE_SYSFS_MAX_BUFF_SIZE] = "";
	int pos = 0;
	int length;

	length = scnprintf(temp + pos, GBE_SYSFS_MAX_BUFF_SIZE - pos,
			"%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
			"process_name",
			"thread_name",
			"pid",
			"tid",
			"runtime_thrs",
			"runtime_percent",
			"now_task_runtime",
			"boost_cnt");
	pos += length;

	hlist_for_each_entry(gbe_list_iter, &gbe_boost_list, hlist) {
		length = scnprintf(temp + pos, GBE_SYSFS_MAX_BUFF_SIZE - pos,
				"%s\t%s\t%d\t%d\t%llu\t\t%llu\t\t%llu\t\t%llu\n",
				gbe_list_iter->process_name,
				gbe_list_iter->thread_name,
				gbe_list_iter->pid,
				gbe_list_iter->tid,
				gbe_list_iter->runtime_thrs,
				gbe_list_iter->runtime_percent,
				gbe_list_iter->now_task_runtime,
				gbe_list_iter->boost_cnt);
		pos += length;
	}

	return scnprintf(buf, PAGE_SIZE, "%s", temp);
}

static ssize_t gbe_boost_list1_store(struct kobject *kobj,
		struct kobj_attribute *attr,
		const char *buf, size_t count)
{
	char acBuffer[GBE_SYSFS_MAX_BUFF_SIZE];
	int ret = count;
	char proc_name[16], thrd_name[16];
	unsigned long long runtime_thrs;

	if ((count > 0) && (count < GBE_SYSFS_MAX_BUFF_SIZE)) {
		if (scnprintf(acBuffer, GBE_SYSFS_MAX_BUFF_SIZE, "%s", buf)) {
			acBuffer[count] = '\0';
			if (sscanf(acBuffer, "%15s %15s %llu",
						proc_name,
						thrd_name,
						&runtime_thrs) != 3) {
				goto err;
			}

			if (set_gbe_boost_list(proc_name,
					thrd_name, runtime_thrs))
				goto err;
		}
	}

err:
	return ret;
}

static KOBJ_ATTR_RW(gbe_boost_list1);

void gbe1_exit(void)
{
	gbe_sysfs_remove_file(&kobj_attr_gbe_enable1);
	gbe_sysfs_remove_file(&kobj_attr_gbe_boost_list1);
}

int gbe1_init(void)
{
	gbe_fstb2gbe_poll_fp = gbe_notify_fstb_poll;
	g_psNotifyWorkQueue =
		create_singlethread_workqueue("gbe_notifier_wq");

	gbe_sysfs_create_file(&kobj_attr_gbe_enable1);
	gbe_sysfs_create_file(&kobj_attr_gbe_boost_list1);

	return 0;
}

