// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2015 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/tracepoint.h>

#include <mt-plat/aee.h>
#define CREATE_TRACE_POINTS
#include <mt-plat/slog.h>

/**
 * Data structures to store tracepoints information
 */
struct tracepoints_table {
	const char *name;
	const char *mod_name;
	bool module;
	void *func;
	struct tracepoint *tp;
	bool init;
};

static unsigned int ufs_count;
static unsigned int ccci_count;
static unsigned int gpu_count;
static unsigned int threshold;
static struct proc_dir_entry *dir;

void slog(const char *fmt, ...)
{
	struct va_format vaf;
	va_list args;

	va_start(args, fmt);
	vaf.fmt = fmt;
	vaf.va = &args;
	trace_slog(&vaf);
	va_end(args);
}

static void probe_ufs_mtk_event(void *data, unsigned int type,
	unsigned int val)
{
	ufs_count++;
	slog("#$#ufs#@#event#%lu:%lu(0x%x)#%d", type, val, val, ufs_count);
	if ((threshold > 0) && ((ufs_count % threshold) == 0))
		aee_kernel_exception("ufs", "occur:%d, threshold:%d\n", ufs_count, threshold);
}

static void probe_ccci_event(void *data, char *string, char *sub_string,
	unsigned int sub_type, unsigned int resv)
{
	ccci_count++;
	slog("#$#%s#@#%s#%d:%d#%d", string, sub_string, sub_type, resv, ccci_count);
}

static void probe_gpu_hardstop_event(void *data, char *string, char *sub_string,
	unsigned int gpu_freq, unsigned int gpu_volt, unsigned int gpu_vsram,
	unsigned int stack_freq, unsigned int stack_volt, unsigned int stack_vsram)
{
	gpu_count++;
	slog("#$#%s#@#%s#%d:%d:%d:%d:%d:%d#%d",
		string, sub_string, gpu_freq, gpu_volt, gpu_vsram,
		stack_freq, stack_volt, stack_vsram, gpu_count);
}

static struct tracepoints_table interests[] = {
	{.name = "ufs_mtk_event", .mod_name = NULL, .module = false, .func = probe_ufs_mtk_event},
	{.name = "ccci_event", .mod_name = NULL, .module = false, .func = probe_ccci_event},
	{.name = "gpu_hardstop", .mod_name = "mtk_gpufreq_wrapper", .module = true,
	 .func = probe_gpu_hardstop_event},
};

/**
 * Find the struct tracepoint* associated with a given tracepoint
 * name.
 */
static void lookup_tracepoints(struct tracepoint *tp, void *ignore)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(interests); i++) {
		if ((interests[i].module == false) && (strcmp(interests[i].name, tp->name) == 0)) {
			interests[i].tp = tp;
			tracepoint_probe_register(interests[i].tp, interests[i].func, NULL);
			interests[i].init = true;
		}
	}
}

static void cleanup(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(interests); i++) {
		if (interests[i].init) {
			tracepoint_probe_unregister(interests[i].tp,
						    interests[i].func, NULL);
		}
	}
}

#ifdef MODULE
static int slog_module_callback(struct notifier_block *nb,
				unsigned long val, void *data)
{
	struct module *mod = data;
	tracepoint_ptr_t *iter, *begin, *end;
	struct tracepoint *tp;
	int i;

	if (val != MODULE_STATE_LIVE)
		return NOTIFY_DONE;

	for (i = 0; i < ARRAY_SIZE(interests); i++) {
		if ((interests[i].module == true) && (interests[i].init == false)) {
			begin = mod->tracepoints_ptrs;
			end = mod->tracepoints_ptrs + mod->num_tracepoints;
			for (iter = begin; iter < end; iter++) {
				tp = tracepoint_ptr_deref(iter);
				if (strcmp(interests[i].name, tp->name) == 0) {
					interests[i].tp = tp;
					tracepoint_probe_register(interests[i].tp,
							interests[i].func, NULL);
					interests[i].init = true;
				}
			}
		}
	}

	return NOTIFY_OK;
}

static struct notifier_block slog_module_nb = {
	.notifier_call = slog_module_callback,
};
#endif

static int slog_threshold_proc_show(struct seq_file *m, void *v)
{
	seq_printf(m, "slog threshold: %d\n", threshold);
	return 0;
}

static char *_copy_from_user_for_proc(const char __user *buffer, size_t count)
{
	char *buf = (char *)__get_free_page(GFP_USER);

	if (!buf)
		return NULL;

	if (count >= PAGE_SIZE)
		goto out;

	if (copy_from_user(buf, buffer, count))
		goto out;

	buf[count] = '\0';

	return buf;

out:
	free_page((unsigned long)buf);

	return NULL;
}

static ssize_t slog_threshold_proc_write(struct file *file,
	const char __user *buffer, size_t count, loff_t *pos)
{
	unsigned int num;
	int rc;

	char *buf = _copy_from_user_for_proc(buffer, count);

	if (!buf)
		return -EINVAL;

	rc = kstrtouint(buf, 10, &num);

	if (rc < 0)
		pr_info("transfer string to int error\n");
	else
		threshold = num;

	free_page((unsigned long)buf);

	pr_info("set threshold to %d\n", threshold);
	return count;
}

#define PROC_FOPS_RW(name)                                              \
static int name ## _proc_open(struct inode *inode, struct file *file)	\
{                                                                       \
	return single_open(file, name ## _proc_show, PDE_DATA(inode));  \
}                                                                       \
static const struct proc_ops name ## _proc_fops = {			\
	.proc_open           = name ## _proc_open,                           \
	.proc_read           = seq_read,                                     \
	.proc_lseek         = seq_lseek,                                    \
	.proc_release        = single_release,                               \
	.proc_write          = name ## _proc_write,                          \
}

#define PROC_ENTRY(name)        {__stringify(name), &name ## _proc_fops}

PROC_FOPS_RW(slog_threshold);

int slog_procfs_init(void)
{
	int i = 0;

	struct pentry {
		const char *name;
		const struct proc_ops *fops;
	};

	const struct pentry entries[] = {
		PROC_ENTRY(slog_threshold),
	};

	dir = proc_mkdir("slog", NULL);

	if (!dir) {
		pr_info("mkdir /proc/slog failed\n");
		return -ENOMEM;
	}

	for (i = 0; i < ARRAY_SIZE(entries); i++) {
		if (!proc_create(entries[i].name, 0640, dir, entries[i].fops))
			pr_info("create /proc/slog/%s failed\n", entries[i].name);
	}

	return 0;
}

int mtk_slog_init(void)
{
#ifdef MODULE
	register_module_notifier(&slog_module_nb);
#endif
	for_each_kernel_tracepoint(lookup_tracepoints, NULL);
	slog_procfs_init();

	return 0;
}

void mtk_slog_exit(void)
{
#ifdef MODULE
	unregister_module_notifier(&slog_module_nb);
#endif
	remove_proc_entry("slog_threshold", dir);
	cleanup();
}
