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

#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <asm/page.h>
#include <linux/io.h>

#include <generated/autoconf.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <asm/cacheflush.h>
#include <linux/io.h>

#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/hardirq.h>
#include <linux/sched.h>
#include <linux/debugfs.h>

#include <linux/ftrace.h>
#include <linux/trace_events.h>
#include <linux/bug.h>
#include <linux/sched/clock.h>

#if IS_ENABLED(CONFIG_MTK_AEE_FEATURE)
#include "mt-plat/aee.h"
#endif

#define MMPROFILE_INTERNAL
#include <mmprofile_internal.h>

#ifdef CONFIG_MTK_USE_RESERVED_EXT_MEM
#include <linux/exm_driver.h>
#endif

/* #pragma GCC optimize ("O0") */
#define MMP_DEVNAME "mmp"

/* min buffer size is 0x400*32byte = 32KB */
#define MMPROFILE_MIN_BUFFER_SIZE 0x400
/* default buffer size is 0x18000*32byte = 3MB */
#define MMPROFILE_DEFAULT_BUFFER_SIZE 0x18000
/* max buffer size is 0x100000*32byte = 32MB */
#define MMPROFILE_MAX_BUFFER_SIZE 0x100000
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
/* min meta buffer size is 0x10000byte = 64KB */
#define MMPROFILE_MIN_META_BUFFER_SIZE 0x10000
/* default meta buffer size is 0x800000byte = 8MB */
#define MMPROFILE_DEFAULT_META_BUFFER_SIZE 0x800000
/* max meta buffer size is 0x800000byte = 64MB */
#define MMPROFILE_MAX_META_BUFFER_SIZE 0x4000000
static unsigned int mmprofile_meta_datacookie = 1;
#else
#define MMPROFILE_MIN_META_BUFFER_SIZE 0x0
#define MMPROFILE_DEFAULT_META_BUFFER_SIZE 0x0
#define MMPROFILE_MAX_META_BUFFER_SIZE 0x0
#endif

#define MMPROFILE_DUMP_BLOCK_SIZE (1024*4)

#define TAG_MMPROFILE "mmprofile"

#ifdef CONFIG_TRACING

//#define ENABLE_MMP_TRACING
#ifdef ENABLE_MMP_TRACING
#define MMP_TRACING
#endif

#endif /* CONFIG_TRACING */

static bool mmp_log_on;
static bool mmp_trace_log_on;

#define MMP_LOG(prio, fmt, arg...) \
	do { \
		if (mmp_log_on) \
			pr_debug("MMP:%s(): "fmt"\n", __func__, ##arg); \
	} while (0)

#define MMP_MSG(fmt, arg...) pr_info("MMP: %s(): "fmt"\n", __func__, ##arg)

#if IS_ENABLED(CONFIG_MTK_AEE_FEATURE)
#define mmp_aee(string, args...) do {	\
	char disp_name[100];						\
	snprintf(disp_name, 100, "[MMP]"string, ##args); \
	aee_kernel_warning_api(__FILE__, __LINE__, \
		DB_OPT_DEFAULT | DB_OPT_MMPROFILE_BUFFER | \
		DB_OPT_DISPLAY_HANG_DUMP | DB_OPT_DUMP_DISPLAY, \
		disp_name, "[MMP] error"string, ##args);		\
	pr_info("MMP error: "string, ##args);				\
} while (0)

#else /* !CONFIG_MTK_AEE_FEATURE */
#define mmp_aee(string, args...)                                               \
	do {                                                                   \
		char str[200];                                                 \
		snprintf(str, 199, "MMP:" string, ##args);                     \
		pr_err("[DDP Error]" string, ##args);                          \
	} while (0)
#endif /* CONFIG_MTK_AEE_FEATURE */

struct mmprofile_regtable_t {
	struct mmprofile_eventinfo_t event_info;
	struct list_head list;
};

struct mmprofile_meta_datablock_t {
	struct list_head list;
	unsigned int block_size;
	unsigned int cookie;
	enum mmp_metadata_type data_type;
	unsigned int data_size;
	unsigned char meta_data[1];
};

static int bmmprofile_init_buffer;
static DEFINE_MUTEX(mmprofile_buffer_init_mutex);
static DEFINE_MUTEX(mmprofile_regtable_mutex);
static DEFINE_MUTEX(mmprofile_meta_buffer_mutex);
static struct mmprofile_event_t *p_mmprofile_ring_buffer;
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
static unsigned char *p_mmprofile_meta_buffer;
#endif

static struct mmprofile_global_t mmprofile_globals
__aligned(PAGE_SIZE) = {
	.buffer_size_record = MMPROFILE_DEFAULT_BUFFER_SIZE,
	.new_buffer_size_record = MMPROFILE_DEFAULT_BUFFER_SIZE,
	.buffer_size_bytes =
		((sizeof(struct mmprofile_event_t) *
		MMPROFILE_DEFAULT_BUFFER_SIZE +
	    (PAGE_SIZE - 1)) & (~(PAGE_SIZE - 1))),
	.record_size = sizeof(struct mmprofile_event_t),
	.meta_buffer_size = MMPROFILE_DEFAULT_META_BUFFER_SIZE,
	.new_meta_buffer_size = MMPROFILE_DEFAULT_META_BUFFER_SIZE,
	.selected_buffer = MMPROFILE_PRIMARY_BUFFER,
	.reg_event_index =
		sizeof(mmprofile_static_events) /
			sizeof(struct mmp_static_event_t),
	.max_event_count = MMPROFILE_MAX_EVENT_COUNT,
};

static struct mmprofile_regtable_t mmprofile_regtable = {
	.list = LIST_HEAD_INIT(mmprofile_regtable.list),
};

static struct list_head mmprofile_meta_buffer_list =
	LIST_HEAD_INIT(mmprofile_meta_buffer_list);
static unsigned char mmprofile_dump_block[MMPROFILE_DUMP_BLOCK_SIZE];

/* Internal functions begin */
static int mmprofile_register_static_events(int sync);

static void mmprofile_force_start(int start);

unsigned int mmprofile_get_dump_size(void)
{
	unsigned int size;

	MMP_LOG(ANDROID_LOG_DEBUG, "+enable %u, start %u",
		mmprofile_globals.enable, mmprofile_globals.start);
	mmprofile_force_start(0);
	if (mmprofile_register_static_events(0) == 0)
		return 0;
	size = sizeof(struct mmprofile_global_t);
	size += sizeof(struct mmprofile_eventinfo_t) *
		(mmprofile_globals.reg_event_index + 1);
	size += mmprofile_globals.buffer_size_bytes;
	MMP_LOG(ANDROID_LOG_DEBUG, "-size %u", size);
	return size;
}

static unsigned int mmprofile_fill_dump_block(void *p_src, void *p_dst,
	unsigned int *p_src_pos, unsigned int *p_dst_pos,
	unsigned int src_size, unsigned int dst_size)
{
	unsigned int src_left = src_size - *p_src_pos;
	unsigned int dst_left = dst_size - *p_dst_pos;

	if ((src_left == 0) || (dst_left == 0))
		return 0;
	if (src_left < dst_left) {
		memcpy(((unsigned char *)p_dst) + *p_dst_pos,
			((unsigned char *)p_src) + *p_src_pos, src_left);
		*p_src_pos += src_left;
		*p_dst_pos += src_left;
		return src_left;
	}

	memcpy(((unsigned char *)p_dst) + *p_dst_pos,
		((unsigned char *)p_src) + *p_src_pos, dst_left);
	*p_src_pos += dst_left;
	*p_dst_pos += dst_left;
	return dst_left;
}

void mmprofile_get_dump_buffer(unsigned int start, unsigned long *p_addr,
	unsigned int *p_size)
{
	unsigned int total_pos = start;
	unsigned int region_pos;
	unsigned int block_pos = 0;
	unsigned int region_base = 0;
	unsigned int copy_size;
	*p_addr = (unsigned long)mmprofile_dump_block;
	*p_size = MMPROFILE_DUMP_BLOCK_SIZE;
	if (!bmmprofile_init_buffer) {
		MMP_LOG(ANDROID_LOG_DEBUG, "Ringbuffer is not initialized");
		*p_size = 0;
		return;
	}
	if (total_pos < (region_base + sizeof(struct mmprofile_global_t))) {
		/* Global structure */
		region_pos = total_pos;
		copy_size =
			mmprofile_fill_dump_block(&mmprofile_globals,
			mmprofile_dump_block,
			&region_pos, &block_pos,
			sizeof(struct mmprofile_global_t),
			MMPROFILE_DUMP_BLOCK_SIZE);
		if (block_pos == MMPROFILE_DUMP_BLOCK_SIZE)
			return;
		total_pos = region_base + sizeof(struct mmprofile_global_t);
	}
	region_base += sizeof(struct mmprofile_global_t);
	if (mmprofile_register_static_events(0) == 0) {
		MMP_LOG(ANDROID_LOG_DEBUG, "static event not register");
		*p_size = 0;
		return;
	}
	if (total_pos <
	    (region_base + sizeof(struct mmprofile_eventinfo_t) *
	    (mmprofile_globals.reg_event_index + 1))) {
		/* Register table */
		mmp_event index;
		struct mmprofile_regtable_t *p_regtable;
		struct mmprofile_eventinfo_t event_info_dummy = { 0, "" };
		unsigned int src_pos;
		unsigned int pos = 0;

		region_pos = total_pos - region_base;
		if (mutex_trylock(&mmprofile_regtable_mutex) == 0) {
			MMP_LOG(ANDROID_LOG_DEBUG, "fail to get reg lock");
			*p_size = 0;
			return;
		}
		if (pos + sizeof(struct mmprofile_eventinfo_t) > region_pos) {
			if (region_pos > pos)
				src_pos = region_pos - pos;
			else
				src_pos = 0;
			copy_size =
			    mmprofile_fill_dump_block(&event_info_dummy,
			    mmprofile_dump_block, &src_pos,
				&block_pos,
				sizeof(struct mmprofile_eventinfo_t),
				MMPROFILE_DUMP_BLOCK_SIZE);
			if (block_pos == MMPROFILE_DUMP_BLOCK_SIZE) {
				mutex_unlock(&mmprofile_regtable_mutex);
				return;
			}
		}
		pos += sizeof(struct mmprofile_eventinfo_t);
		index = MMP_ROOT_EVENT;
		list_for_each_entry(p_regtable,
			&(mmprofile_regtable.list), list) {
			if (pos + sizeof(struct mmprofile_eventinfo_t) >
				region_pos) {

				if (region_pos > pos)
					src_pos = region_pos - pos;
				else
					src_pos = 0;
				if (!virt_addr_valid(
					&(p_regtable->event_info))) {
					mmp_aee("pos=0x%x, src_pos=0x%x\n",
						pos, src_pos);
					pr_info("region_pos=0x%x, block_pos=0x%x\n",
						region_pos, block_pos);
					return;
				}
				copy_size =
				    mmprofile_fill_dump_block(
				    &(p_regtable->event_info),
					mmprofile_dump_block,
					&src_pos, &block_pos,
					sizeof(struct mmprofile_eventinfo_t),
					MMPROFILE_DUMP_BLOCK_SIZE);
				if (block_pos == MMPROFILE_DUMP_BLOCK_SIZE) {
					mutex_unlock(&mmprofile_regtable_mutex);
					return;
				}
			}
			pos += sizeof(struct mmprofile_eventinfo_t);
			index++;
		}
		mutex_unlock(&mmprofile_regtable_mutex);
		total_pos =
		    region_base + sizeof(struct mmprofile_eventinfo_t) *
		    (mmprofile_globals.reg_event_index + 1);
	}
	region_base += sizeof(struct mmprofile_eventinfo_t) *
		(mmprofile_globals.reg_event_index + 1);
	if (total_pos < (region_base + mmprofile_globals.buffer_size_bytes)) {
		/* Primary buffer */
		region_pos = total_pos - region_base;
		copy_size =
		    mmprofile_fill_dump_block(p_mmprofile_ring_buffer,
			    mmprofile_dump_block, &region_pos, &block_pos,
			    mmprofile_globals.buffer_size_bytes,
				MMPROFILE_DUMP_BLOCK_SIZE);
		if (block_pos == MMPROFILE_DUMP_BLOCK_SIZE)
			return;
	} else {
		*p_size = 0;
	}
	MMP_LOG(ANDROID_LOG_DEBUG, "end t=%u,r =%u,block_pos=%u",
		total_pos, region_base,	block_pos);
	*p_size = block_pos;
}

static void mmprofile_init_buffer(void)
{
	unsigned int b_reset_ring_buffer = 0;
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
	unsigned int b_reset_meta_buffer = 0;
#endif

	if (!mmprofile_globals.enable)
		return;
	if (in_interrupt())
		return;
	mutex_lock(&mmprofile_buffer_init_mutex);
	if (bmmprofile_init_buffer &&
		(mmprofile_globals.buffer_size_record ==
			mmprofile_globals.new_buffer_size_record) &&
	    (mmprofile_globals.meta_buffer_size ==
			mmprofile_globals.new_meta_buffer_size)) {
		mutex_unlock(&mmprofile_buffer_init_mutex);
		return;
	}

	bmmprofile_init_buffer = 0;

	/* Initialize */
	/* Allocate memory. */

	if (!p_mmprofile_ring_buffer) {
		mmprofile_globals.buffer_size_record =
		    mmprofile_globals.new_buffer_size_record;
		mmprofile_globals.buffer_size_bytes =
		    ((sizeof(struct mmprofile_event_t) *
				mmprofile_globals.buffer_size_record +
				(PAGE_SIZE - 1)) & (~(PAGE_SIZE - 1)));
		b_reset_ring_buffer = 1;
	} else if (mmprofile_globals.buffer_size_record !=
		   mmprofile_globals.new_buffer_size_record) {
		vfree(p_mmprofile_ring_buffer);
		p_mmprofile_ring_buffer = NULL;
		mmprofile_globals.buffer_size_record =
		    mmprofile_globals.new_buffer_size_record;
		mmprofile_globals.buffer_size_bytes =
		    ((sizeof(struct mmprofile_event_t) *
				mmprofile_globals.buffer_size_record +
				(PAGE_SIZE - 1)) & (~(PAGE_SIZE - 1)));
		b_reset_ring_buffer = 1;
	}
	if (b_reset_ring_buffer) {
		p_mmprofile_ring_buffer =
#ifdef CONFIG_MTK_USE_RESERVED_EXT_MEM
		    (struct mmprofile_event_t *)
		    extmem_malloc_page_align(
				mmprofile_globals.buffer_size_bytes);
#else
		    vmalloc(mmprofile_globals.buffer_size_bytes);
#endif
	}
	MMP_LOG(ANDROID_LOG_DEBUG, "p_mmprofile_ring_buffer=0x%08lx",
		(unsigned long)p_mmprofile_ring_buffer);

#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
	if (!p_mmprofile_meta_buffer) {
		mmprofile_globals.meta_buffer_size =
			mmprofile_globals.new_meta_buffer_size;
		b_reset_meta_buffer = 1;
	} else if (mmprofile_globals.meta_buffer_size !=
		   mmprofile_globals.new_meta_buffer_size) {
		vfree(p_mmprofile_meta_buffer);
		p_mmprofile_meta_buffer = NULL;
		mmprofile_globals.meta_buffer_size =
			mmprofile_globals.new_meta_buffer_size;
		b_reset_meta_buffer = 1;
	}
	if (b_reset_meta_buffer) {
		p_mmprofile_meta_buffer =
#ifdef CONFIG_MTK_USE_RESERVED_EXT_MEM
	    (unsigned char *)
	    extmem_malloc_page_align(
			mmprofile_globals.meta_buffer_size);
#else
	    vmalloc(mmprofile_globals.meta_buffer_size);
#endif
	}
	MMP_LOG(ANDROID_LOG_DEBUG,
		"p_mmprofile_meta_buffer=0x%08lx",
		(unsigned long)p_mmprofile_meta_buffer);

	if ((!p_mmprofile_ring_buffer) || (!p_mmprofile_meta_buffer)) {
		if (p_mmprofile_ring_buffer) {
			vfree(p_mmprofile_ring_buffer);
			p_mmprofile_ring_buffer = NULL;
		}
		if (p_mmprofile_meta_buffer) {
			vfree(p_mmprofile_meta_buffer);
			p_mmprofile_meta_buffer = NULL;
		}
		bmmprofile_init_buffer = 0;
		mutex_unlock(&mmprofile_buffer_init_mutex);
		MMP_LOG(ANDROID_LOG_DEBUG, "Cannot allocate buffer");
		return;
	}
#else
	if (!p_mmprofile_ring_buffer) {
		bmmprofile_init_buffer = 0;
		mutex_unlock(&mmprofile_buffer_init_mutex);
		MMP_LOG(ANDROID_LOG_DEBUG, "Cannot allocate buffer");
		return;
	}
#endif

	if (b_reset_ring_buffer)
		memset((void *)(p_mmprofile_ring_buffer), 0,
		       mmprofile_globals.buffer_size_bytes);
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
	if (b_reset_meta_buffer) {
		struct mmprofile_meta_datablock_t *p_block;

		memset((void *)(p_mmprofile_meta_buffer), 0,
		       mmprofile_globals.meta_buffer_size);
		/* Initialize the first block in meta buffer. */
		p_block =
			(struct mmprofile_meta_datablock_t *)
			p_mmprofile_meta_buffer;
		p_block->block_size = mmprofile_globals.meta_buffer_size;
		INIT_LIST_HEAD(&mmprofile_meta_buffer_list);
		list_add_tail(&(p_block->list), &mmprofile_meta_buffer_list);
	}
#endif
	bmmprofile_init_buffer = 1;

	mutex_unlock(&mmprofile_buffer_init_mutex);
}

static void mmprofile_reset_buffer(void)
{
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)

	if (!mmprofile_globals.enable ||
		(mmprofile_globals.buffer_size_record !=
			mmprofile_globals.new_buffer_size_record))
		return;
	if (bmmprofile_init_buffer) {
		struct mmprofile_meta_datablock_t *p_block;

		memset((void *)(p_mmprofile_ring_buffer), 0,
			mmprofile_globals.buffer_size_bytes);
		mmprofile_globals.write_pointer = 0;

		mutex_lock(&mmprofile_meta_buffer_mutex);
		mmprofile_meta_datacookie = 1;
		memset((void *)(p_mmprofile_meta_buffer), 0,
			mmprofile_globals.meta_buffer_size);
		/* Initialize the first block in meta buffer. */
		p_block =
		    (struct mmprofile_meta_datablock_t *)
		    p_mmprofile_meta_buffer;
		p_block->block_size = mmprofile_globals.meta_buffer_size;
		INIT_LIST_HEAD(&mmprofile_meta_buffer_list);
		list_add_tail(&(p_block->list), &mmprofile_meta_buffer_list);

		mutex_unlock(&mmprofile_meta_buffer_mutex);

	}
#endif
}

static void mmprofile_force_start(int start)
{
	MMP_MSG("start: %d", start);
	if (!mmprofile_globals.enable)
		return;
	MMP_LOG(ANDROID_LOG_DEBUG, "+start %d", start);
	if (start && (!mmprofile_globals.start)) {
		mmprofile_init_buffer();
		mmprofile_reset_buffer();
	}
	mmprofile_globals.start = start;
	MMP_LOG(ANDROID_LOG_DEBUG, "-start=%d", mmprofile_globals.start);
}

/* this function only used by other kernel modules. */
void mmprofile_start(int start)
{
#ifndef FORBID_MMP_START
	mmprofile_force_start(start);
#endif
}
EXPORT_SYMBOL(mmprofile_start);

void mmprofile_enable(int enable)
{
	MMP_MSG("enable: %d", enable);
	if (enable)
		mmprofile_register_static_events(1);
	mmprofile_globals.enable = enable;
	if (enable == 0)
		mmprofile_force_start(0);
}
EXPORT_SYMBOL(mmprofile_enable);

/* if using remote tool (PC side) or adb shell command, can always start mmp */
static void mmprofile_remote_start(int start)
{
	MMP_MSG("remote start: %d", start);
	if (!mmprofile_globals.enable)
		return;
	MMP_LOG(ANDROID_LOG_DEBUG, "remote +start %d", start);
	if (start && (!mmprofile_globals.start)) {
		mmprofile_init_buffer();
		mmprofile_reset_buffer();
	}
	mmprofile_globals.start = start;
	MMP_LOG(ANDROID_LOG_DEBUG, "remote -start=%d",
		mmprofile_globals.start);
}

static mmp_event mmprofile_find_event_int(mmp_event parent, const char *name)
{
	mmp_event index;
	struct mmprofile_regtable_t *p_regtable;

	index = MMP_ROOT_EVENT;
	list_for_each_entry(p_regtable, &(mmprofile_regtable.list), list) {
		if ((parent == 0) ||
			(p_regtable->event_info.parent_id == parent)) {

			if (strncmp(p_regtable->event_info.name, name,
				MMPROFILE_EVENT_NAME_MAX_LEN) == 0) {
				return index;
			}
		}
		index++;
	}
	return 0;
}

static int mmprofile_get_event_name(mmp_event event, char *name, size_t *size)
{
	/* current event for seraching */
	mmp_event curr_event = event;
	/* event info for all level of the event */
	struct mmprofile_eventinfo_t *event_info[32];
	int info_cnt = 0;
	int found = 0;
	int ret = -1;

	if ((name == NULL) || (size == NULL)) {
		/* parameters invalid */
		return ret;
	}

	while (1) {
		struct mmprofile_regtable_t *p_regtable;
		int curr_found = 0;
		mmp_event index = MMP_ROOT_EVENT;

		/* check the event */
		if ((curr_event == MMP_INVALID_EVENT)
		    || (curr_event > mmprofile_globals.reg_event_index)) {
			/* the event invalid */
			break;
		}

		if (info_cnt >= ARRAY_SIZE(event_info)) {
			/* the level of event is out of limite */
			found = 1;
			break;
		}

		/* search the info for the event */
		list_for_each_entry(p_regtable,
			&(mmprofile_regtable.list), list) {
			if (index == curr_event) {
				/* find this event */
				curr_found = 1;
				event_info[info_cnt] = &p_regtable->event_info;
				break;
			}
			index++;
		}

		if (!curr_found) {
			/* can not find the event */
			break;
		}


		if ((event_info[info_cnt]->parent_id == MMP_ROOT_EVENT) ||
			(event_info[info_cnt]->parent_id ==
				MMP_INVALID_EVENT)) {
			/* find all path for the event */
			found = 1;
			info_cnt++;
			break;
		}

		/* search the parent of the event */
		curr_event = event_info[info_cnt]->parent_id;
		info_cnt++;
	}

	if (found) {
		size_t need_len = 0;
		size_t actual_len = 0;
		int info_cnt_used = 0;
		int i;

		WARN_ON(!(info_cnt > 0));

		for (i = 0; i < info_cnt; i++) {
			/* after each name has a ':' or '\0' */
			need_len += strlen(event_info[i]->name) + 1;
			if (need_len <= *size) {
				/* buffer size is ok */
				info_cnt_used = i + 1;
			}
		}

		for (i = info_cnt_used - 1; i >= 0; i--) {
			strncpy(&name[actual_len], event_info[i]->name,
				strlen(event_info[i]->name) + 1);
			actual_len += strlen(event_info[i]->name);
			if (i > 0) {
				/* not the last name */
				name[actual_len] = ':';
			}
			actual_len++;
		}

		ret = (int)actual_len;
		*size = need_len;
	}

	return ret;
}

static int mmprofile_config_event(mmp_event event, char *name,
	mmp_event parent, int sync)
{
	mmp_event index;
	struct mmprofile_regtable_t *p_regtable;

	if (in_interrupt())
		return 0;
	if ((event >= MMPROFILE_MAX_EVENT_COUNT) ||
		(event == MMP_INVALID_EVENT))
		return 0;
	if (sync) {
		mutex_lock(&mmprofile_regtable_mutex);
	} else {
		if (mutex_trylock(&mmprofile_regtable_mutex) == 0)
			return 0;
	}
	index = mmprofile_find_event_int(parent, name);
	if (index) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return 1;
	}
	p_regtable = kmalloc(sizeof(struct mmprofile_regtable_t), GFP_KERNEL);
	if (!p_regtable) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return 0;
	}
	strncpy(p_regtable->event_info.name, name,
		MMPROFILE_EVENT_NAME_MAX_LEN);
	p_regtable->event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
	p_regtable->event_info.parent_id = parent;
	list_add_tail(&(p_regtable->list), &(mmprofile_regtable.list));

	mutex_unlock(&mmprofile_regtable_mutex);
	return 1;
}

static int mmprofile_register_static_events(int sync)
{
	static unsigned int b_static_event_registered;
	unsigned int static_event_count = 0;
	unsigned int i;
	int ret = 1;

	if (in_interrupt())
		return 0;
	if (b_static_event_registered)
		return 1;
	static_event_count =
		sizeof(mmprofile_static_events) /
		sizeof(struct mmp_static_event_t);
	for (i = 0; i < static_event_count; i++) {
		ret = ret
		    && mmprofile_config_event(mmprofile_static_events[i].event,
				mmprofile_static_events[i].name,
				mmprofile_static_events[i].parent, sync);
	}
	b_static_event_registered = 1;
	return ret;
}

/* the MMP_TRACING is defined only when CONFIG_TRACING is defined
 * and we enable mmp to trace its API.
 */
#ifdef MMP_TRACING
static unsigned long __read_mostly tracing_mark_write_addr;
static inline void __mt_update_tracing_mark_write_addr(void)
{
	if (unlikely(tracing_mark_write_addr == 0))
		tracing_mark_write_addr =
			kallsyms_lookup_name("tracing_mark_write");
}

static inline void mmp_kernel_trace_begin(char *name)
{
	if (mmp_trace_log_on) {
		__mt_update_tracing_mark_write_addr();
		event_trace_printk(tracing_mark_write_addr,
			"B|%d|%s\n", current->tgid, name);
	}
}

static inline void mmp_kernel_trace_counter(char *name, int count)
{
	if (mmp_trace_log_on) {
		__mt_update_tracing_mark_write_addr();
		event_trace_printk(tracing_mark_write_addr,
			"C|%d|%s|%d\n",
			in_interrupt() ? -1 : current->tgid, name, count);
	}
}

static inline void mmp_kernel_trace_end(void)
{
	if (mmp_trace_log_on) {
		__mt_update_tracing_mark_write_addr();
		event_trace_printk(tracing_mark_write_addr, "E\n");
	}
}
#else
static inline void mmp_kernel_trace_begin(char *name)
{
}

static inline void mmp_kernel_trace_end(void)
{
}

static inline void mmp_kernel_trace_counter(char *name, int count)
{
}
#endif

/* continue to use 32-bit value to store time value (separate into 2) */
static void system_time(unsigned int *low, unsigned int *high)
{
	unsigned long long temp;

	temp = sched_clock();
	*low = (unsigned int)(temp & 0xffffffff);
	*high = (unsigned int)((temp >> 32) & 0xffffffff);
}

static bool is_mmp_valid(mmp_event event)
{
	if (!bmmprofile_init_buffer || !mmprofile_globals.start)
		return false;

	if (!(mmprofile_globals.event_state[event] & MMP_EVENT_STATE_ENABLED))
		return false;
	if ((mmprofile_globals.buffer_size_record !=
			mmprofile_globals.new_buffer_size_record))
		return false;

	return true;
}

static void mmprofile_log_int(mmp_event event, enum mmp_log_type type,
	unsigned long data1, unsigned long data2,
	unsigned int meta_data_cookie)
{
	char name[256];
	size_t prefix_len;
	size_t size;
	struct mmprofile_event_t *p_event = NULL;
	unsigned int index;
	unsigned int lock;

	if (!mmprofile_globals.enable)
		return;
	if ((event >= MMPROFILE_MAX_EVENT_COUNT) ||
		(event == MMP_INVALID_EVENT))
		return;
	if (!is_mmp_valid(event))
		return;


	/* Event ID 0 and 1 are protected.
	 * They are not allowed for logging.
	 */
	if (unlikely(event < 2))
		return;
	index = (atomic_inc_return((atomic_t *)
			&(mmprofile_globals.write_pointer)) - 1)
	    % (mmprofile_globals.buffer_size_record);
	/*check vmalloc address is valid or not*/
	if (!pfn_valid(vmalloc_to_pfn((struct mmprofile_event_t *)
		&(p_mmprofile_ring_buffer[index])))) {
		mmp_aee("write_pointer:0x%x,index:0x%x,line:%d\n",
			mmprofile_globals.write_pointer, index, __LINE__);
		pr_info("buffer_size_record:0x%x,new_buffer_size_record:0x%x\n",
			mmprofile_globals.buffer_size_record,
			mmprofile_globals.new_buffer_size_record);
		return;
	}
	lock = atomic_inc_return((atomic_t *)
		&(p_mmprofile_ring_buffer[index].lock));
	/*atomic_t is INT, write_pointer is UINT, avoid convert error*/
	if (mmprofile_globals.write_pointer ==
		mmprofile_globals.buffer_size_record)
		mmprofile_globals.write_pointer = 0;
	if (unlikely(lock > 1)) {
		/* Do not reduce lock count since it need
		 * to be marked as invalid.
		 */
		while (1) {
			index =
				(atomic_inc_return((atomic_t *)
				&(mmprofile_globals.write_pointer)) - 1) %
				(mmprofile_globals.buffer_size_record);
			if (!pfn_valid(vmalloc_to_pfn
				((struct mmprofile_event_t *)
					&(p_mmprofile_ring_buffer[index])))) {
				mmp_aee("write_pt:0x%x,index:0x%x,line:%d\n",
					mmprofile_globals.write_pointer,
					index, __LINE__);
				pr_info("buf_size:0x%x,new_buf_size:0x%x\n",
					mmprofile_globals.buffer_size_record,
				mmprofile_globals.new_buffer_size_record);
				return;
			}
			lock =
			    atomic_inc_return((atomic_t *) &
					(p_mmprofile_ring_buffer[index].lock));
			/*avoid convert error*/
			if (mmprofile_globals.write_pointer ==
				mmprofile_globals.buffer_size_record)
				mmprofile_globals.write_pointer = 0;
			/* Do not reduce lock count since it need to be
			 * marked as invalid.
			 */
			if (likely(lock == 1))
				break;
		}
	}
	p_event = (struct mmprofile_event_t *)
		&(p_mmprofile_ring_buffer[index]);
	system_time(&(p_event->time_low), &(p_event->time_high));
	p_event->id = event;
	p_event->flag = type;
	p_event->data1 = (unsigned int)data1;
	p_event->data2 = (unsigned int)data2;
	p_event->meta_data_cookie = meta_data_cookie;
	lock = atomic_dec_return((atomic_t *) &(p_event->lock));
	if (unlikely(lock > 0)) {
		/* Someone has marked this record as invalid.
		 * Kill this record.
		 */
		p_event->id = 0;
		p_event->lock = 0;
	}

	if ((mmprofile_globals.event_state[event] & MMP_EVENT_STATE_FTRACE)
	    || (type & MMPROFILE_FLAG_SYSTRACE)) {

		/* ignore interrupt */
		if (in_interrupt())
			return;

		memset((void *)name, 0, 256);
		name[0] = 'M';
		name[1] = 'M';
		name[2] = 'P';
		name[3] = ':';
		prefix_len = strlen(name);
		size = sizeof(name) - prefix_len;

		if (mmprofile_get_event_name(
			event, &name[prefix_len], &size) > 0) {

			if (type & MMPROFILE_FLAG_START) {
				mmp_kernel_trace_begin(name);
			} else if (type & MMPROFILE_FLAG_END) {
				mmp_kernel_trace_end();
			} else if (type & MMPROFILE_FLAG_PULSE) {
				mmp_kernel_trace_counter(name, 1);
				mmp_kernel_trace_counter(name, 0);
			}
		}
	}
}

static long mmprofile_log_meta_int(mmp_event event, enum mmp_log_type type,
	struct mmp_metadata_t *p_meta_data, long b_from_user)
{
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)
	unsigned long retn;
	void __user *p_data;
	struct mmprofile_meta_datablock_t *p_node = NULL;
	unsigned long block_size;

	if (!mmprofile_globals.enable)
		return 0;
	if ((event >= MMPROFILE_MAX_EVENT_COUNT) ||
		(event == MMP_INVALID_EVENT))
		return -3;

	if (!is_mmp_valid(event))
		return 0;

	if (unlikely(!p_meta_data))
		return -1;
	block_size =
	    ((offsetof(struct mmprofile_meta_datablock_t, meta_data) +
	    p_meta_data->size) + 3) & (~3);
	if (block_size > mmprofile_globals.meta_buffer_size)
		return -2;
	mutex_lock(&mmprofile_meta_buffer_mutex);
	p_node = list_entry(mmprofile_meta_buffer_list.prev,
		struct mmprofile_meta_datablock_t, list);
	/* If the tail block has been used,
	 * move the first block to tail and use it for new meta data.
	 */
	if (p_node->data_size > 0) {
		list_move_tail(mmprofile_meta_buffer_list.next,
			&mmprofile_meta_buffer_list);
		p_node = list_entry(mmprofile_meta_buffer_list.prev,
			struct mmprofile_meta_datablock_t, list);
	}
	/* Migrate a block with enough size.
	 * The room is collected by sacrificing
	 * least recent used blocks.
	 */
	while (p_node->block_size < block_size) {
		struct mmprofile_meta_datablock_t *p_next_node =
		    list_entry(p_node->list.next,
				struct mmprofile_meta_datablock_t, list);
		if (&(p_next_node->list) == &mmprofile_meta_buffer_list)
			p_next_node =  list_entry(p_next_node->list.next,
				struct mmprofile_meta_datablock_t, list);

		list_del(&(p_next_node->list));
		p_node->block_size += p_next_node->block_size;
	}
	/* Split the block if left memory is enough for a new block. */
	if (((unsigned long)p_node + block_size) <
	    ((unsigned long)p_mmprofile_meta_buffer +
			mmprofile_globals.meta_buffer_size)
	    && ((unsigned long)p_node + block_size) >
	    ((unsigned long)p_mmprofile_meta_buffer +
			mmprofile_globals.meta_buffer_size -
	     offsetof(struct mmprofile_meta_datablock_t, meta_data))) {
		block_size =
		    (unsigned long)p_mmprofile_meta_buffer +
		    mmprofile_globals.meta_buffer_size -
		    (unsigned long)p_node;
	}
	if ((p_node->block_size - block_size) >=
	    offsetof(struct mmprofile_meta_datablock_t, meta_data)) {
		struct mmprofile_meta_datablock_t *p_new_node =
		    (struct mmprofile_meta_datablock_t *)
			((unsigned long)p_node + block_size);
		if ((unsigned long)p_new_node >=
		    ((unsigned long)p_mmprofile_meta_buffer +
		     mmprofile_globals.meta_buffer_size))
			p_new_node =
			    (struct mmprofile_meta_datablock_t *)
				((unsigned long)p_new_node -
				mmprofile_globals.meta_buffer_size);
		p_new_node->block_size = p_node->block_size - block_size;
		p_new_node->data_size = 0;
		list_add(&(p_new_node->list), &(p_node->list));
		p_node->block_size = block_size;
	}
	/* Fill data */
	p_node->data_size = p_meta_data->size;
	p_node->data_type = p_meta_data->data_type;
	p_node->cookie = mmprofile_meta_datacookie;
	mmprofile_log_int(event, type, p_meta_data->data1,
		p_meta_data->data2, mmprofile_meta_datacookie);
	mmprofile_meta_datacookie++;
	if (mmprofile_meta_datacookie == 0)
		mmprofile_meta_datacookie++;
	p_data = (void __user *)(p_meta_data->p_data);
	if (((unsigned long)(p_node->meta_data) + p_meta_data->size) >
	    ((unsigned long)p_mmprofile_meta_buffer +
	    mmprofile_globals.meta_buffer_size)) {

		unsigned long left_size =
		    (unsigned long)p_mmprofile_meta_buffer +
		    mmprofile_globals.meta_buffer_size -
		    (unsigned long)(p_node->meta_data);
		if (b_from_user) {
			retn =
				copy_from_user(p_node->meta_data,
					p_data, left_size);
			retn =
			    copy_from_user(p_mmprofile_meta_buffer,
					(void *)((unsigned long)p_data +
					    left_size),
					p_meta_data->size - left_size);
		} else {
			memcpy(p_node->meta_data, p_data, left_size);
			memcpy(p_mmprofile_meta_buffer,
			       (void *)((unsigned long)p_data + left_size),
			       p_meta_data->size - left_size);
		}
	} else {
		if (b_from_user)
			retn =
			    copy_from_user(p_node->meta_data, p_data,
					   p_meta_data->size);
		else
			memcpy(p_node->meta_data, p_data, p_meta_data->size);
	}
	mutex_unlock(&mmprofile_meta_buffer_mutex);
#endif

	return 0;
}

/* Internal functions end */

/* Exposed APIs begin */
mmp_event mmprofile_register_event(mmp_event parent, const char *name)
{
	mmp_event index;
	struct mmprofile_regtable_t *p_regtable;

	if (!mmprofile_globals.enable)
		return 0;
	if (in_interrupt())
		return 0;
	mutex_lock(&mmprofile_regtable_mutex);
	if (mmprofile_globals.reg_event_index >=
		(MMPROFILE_MAX_EVENT_COUNT - 1)) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return 0;
	}
	/* Check if this event has already been registered. */
	index = mmprofile_find_event_int(parent, name);
	if (index) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return index;
	}
	/* Check if the parent exists. */
	if ((parent == 0) || (parent > mmprofile_globals.reg_event_index)) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return 0;
	}
	/* Now register the new event. */
	p_regtable = kmalloc(sizeof(struct mmprofile_regtable_t), GFP_KERNEL);
	if (!p_regtable) {
		mutex_unlock(&mmprofile_regtable_mutex);
		return 0;
	}
	index = ++(mmprofile_globals.reg_event_index);
	if (strlen(name) > MMPROFILE_EVENT_NAME_MAX_LEN) {
		memcpy(p_regtable->event_info.name, name,
			MMPROFILE_EVENT_NAME_MAX_LEN);
		p_regtable->event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
	} else
		strncpy(p_regtable->event_info.name, name, strlen(name) + 1);
	p_regtable->event_info.parent_id = parent;
	list_add_tail(&(p_regtable->list), &(mmprofile_regtable.list));
	mmprofile_globals.event_state[index] = 0;
	mutex_unlock(&mmprofile_regtable_mutex);
	return index;
}
EXPORT_SYMBOL(mmprofile_register_event);

mmp_event mmprofile_find_event(mmp_event parent, const char *name)
{
	mmp_event event;

	if (!mmprofile_globals.enable)
		return 0;
	if (in_interrupt())
		return 0;
	mutex_lock(&mmprofile_regtable_mutex);
	event = mmprofile_find_event_int(parent, name);
	mutex_unlock(&mmprofile_regtable_mutex);
	return event;
}
EXPORT_SYMBOL(mmprofile_find_event);

void mmprofile_enable_ftrace_event(mmp_event event, long enable, long ftrace)
{
	unsigned int state;

	if (!mmprofile_globals.enable)
		return;
	if ((event < 2) || (event >= MMPROFILE_MAX_EVENT_COUNT))
		return;
	state = enable ? MMP_EVENT_STATE_ENABLED : 0;
	if (enable && ftrace)
		state |= MMP_EVENT_STATE_FTRACE;
	mmprofile_globals.event_state[event] = state;
}
EXPORT_SYMBOL(mmprofile_enable_ftrace_event);

void mmprofile_enable_event(mmp_event event, long enable)
{
	mmprofile_enable_ftrace_event(event, enable, 0);
}
EXPORT_SYMBOL(mmprofile_enable_event);

void mmprofile_enable_ftrace_event_recursive(mmp_event event, long enable,
	long ftrace)
{
	mmp_event index;
	struct mmprofile_regtable_t *p_regtable;

	index = MMP_ROOT_EVENT;
	mmprofile_enable_ftrace_event(event, enable, ftrace);
	list_for_each_entry(p_regtable, &(mmprofile_regtable.list), list) {
		if (p_regtable->event_info.parent_id != event)
			continue;
		mmprofile_enable_ftrace_event_recursive(index, enable, ftrace);

		index++;
	}
}
EXPORT_SYMBOL(mmprofile_enable_ftrace_event_recursive);

void mmprofile_enable_event_recursive(mmp_event event, long enable)
{
	mmp_event index;
	struct mmprofile_regtable_t *p_regtable;

	index = MMP_ROOT_EVENT;
	mmprofile_enable_event(event, enable);
	list_for_each_entry(p_regtable, &(mmprofile_regtable.list), list) {
		if (p_regtable->event_info.parent_id == event)
			mmprofile_enable_event_recursive(index, enable);

		index++;
	}
}
EXPORT_SYMBOL(mmprofile_enable_event_recursive);

long mmprofile_query_enable(mmp_event event)
{
	if (!mmprofile_globals.enable)
		return 0;
	if (event >= MMPROFILE_MAX_EVENT_COUNT)
		return 0;
	if (event == MMP_INVALID_EVENT)
		return mmprofile_globals.enable;
	return !!(mmprofile_globals.event_state[event] &
			MMP_EVENT_STATE_ENABLED);
}
EXPORT_SYMBOL(mmprofile_query_enable);

void mmprofile_log_ex(mmp_event event, enum mmp_log_type type,
	unsigned long data1, unsigned long data2)
{
	mmprofile_log_int(event, type, data1, data2, 0);
}
EXPORT_SYMBOL(mmprofile_log_ex);

void mmprofile_log(mmp_event event, enum mmp_log_type type)
{
	mmprofile_log_ex(event, type, 0, 0);
}
EXPORT_SYMBOL(mmprofile_log);

long mmprofile_log_meta(mmp_event event, enum mmp_log_type type,
	struct mmp_metadata_t *p_meta_data)
{
	if (!mmprofile_globals.enable)
		return 0;
	if (in_interrupt())
		return 0;
	return mmprofile_log_meta_int(event, type, p_meta_data, 0);
}
EXPORT_SYMBOL(mmprofile_log_meta);

long mmprofile_log_meta_structure(mmp_event event, enum mmp_log_type type,
	struct mmp_metadata_structure_t *p_meta_data)
{
	int ret = 0;
	struct mmp_metadata_t meta_data;

	if (!mmprofile_globals.enable)
		return 0;
	if (event >= MMPROFILE_MAX_EVENT_COUNT)
		return -3;
	if (in_interrupt())
		return 0;
	if (event == MMP_INVALID_EVENT)
		return 0;

	if (!is_mmp_valid(event))
		return 0;

	meta_data.data1 = p_meta_data->data1;
	meta_data.data2 = p_meta_data->data2;
	meta_data.data_type = MMPROFILE_META_STRUCTURE;
	meta_data.size = 32 + p_meta_data->struct_size;
	meta_data.p_data = vmalloc(meta_data.size);
	if (!meta_data.p_data)
		return -1;
	memcpy(meta_data.p_data, p_meta_data->struct_name, 32);
	memcpy((void *)((unsigned long)(meta_data.p_data) + 32),
		p_meta_data->p_data, p_meta_data->struct_size);
	ret = mmprofile_log_meta(event, type, &meta_data);
	vfree(meta_data.p_data);

	return ret;
}
EXPORT_SYMBOL(mmprofile_log_meta_structure);

long mmprofile_log_meta_string_ex(mmp_event event, enum mmp_log_type type,
	unsigned long data1, unsigned long data2, const char *str)
{
	long ret = 0;
	struct mmp_metadata_t meta_data;

	if (!mmprofile_globals.enable)
		return 0;
	if (event >= MMPROFILE_MAX_EVENT_COUNT)
		return -3;
	if (in_interrupt())
		return 0;
	if (event == MMP_INVALID_EVENT)
		return 0;
	if (!is_mmp_valid(event))
		return 0;

	meta_data.data1 = data1;
	meta_data.data2 = data2;
	meta_data.data_type = MMPROFILE_META_STRING_MBS;
	meta_data.size = strlen(str) + 1;
	meta_data.p_data = vmalloc(meta_data.size);
	if (!meta_data.p_data)
		return -1;
	strncpy((char *)meta_data.p_data, str, strlen(str) + 1);
	ret = mmprofile_log_meta(event, type, &meta_data);
	vfree(meta_data.p_data);

	return ret;
}
EXPORT_SYMBOL(mmprofile_log_meta_string_ex);

long mmprofile_log_meta_string(mmp_event event, enum mmp_log_type type,
	const char *str)
{
	return mmprofile_log_meta_string_ex(event, type, 0, 0, str);
}
EXPORT_SYMBOL(mmprofile_log_meta_string);

long mmprofile_log_meta_bitmap(mmp_event event, enum mmp_log_type type,
	struct mmp_metadata_bitmap_t *p_meta_data)
{
	int ret = 0;
	struct mmp_metadata_t meta_data;
	char *p_src, *p_dst;
	long pitch;

	if (!mmprofile_globals.enable)
		return 0;
	if (event >= MMPROFILE_MAX_EVENT_COUNT)
		return -3;
	if (in_interrupt())
		return 0;
	if (event == MMP_INVALID_EVENT)
		return 0;
	if (!is_mmp_valid(event))
		return 0;

	meta_data.data1 = p_meta_data->data1;
	meta_data.data2 = p_meta_data->data2;
	meta_data.data_type = MMPROFILE_META_BITMAP;
	meta_data.size = sizeof(struct mmp_metadata_bitmap_t) +
		p_meta_data->data_size;
	meta_data.p_data = vmalloc(meta_data.size);
	if (!meta_data.p_data)
		return -1;
	p_src = (char *)p_meta_data->p_data + p_meta_data->start_pos;
	p_dst = (char *)((unsigned long)(meta_data.p_data) +
		sizeof(struct mmp_metadata_bitmap_t));
	pitch = p_meta_data->pitch;
	memcpy(meta_data.p_data, p_meta_data,
		sizeof(struct mmp_metadata_bitmap_t));
	if (pitch < 0)
		((struct mmp_metadata_bitmap_t *)(meta_data.p_data))->pitch
			= -pitch;
	if ((pitch > 0) && (p_meta_data->down_sample_x == 1)
	    && (p_meta_data->down_sample_y == 1))
		memcpy(p_dst, p_src, p_meta_data->data_size);
	else {
		unsigned int x, y, x0, y0;
		unsigned int new_width, new_height;
		unsigned int bpp = p_meta_data->bpp / 8;
		unsigned int src_offset, dst_offset;

		new_width = (p_meta_data->width - 1) /
			p_meta_data->down_sample_x + 1;
		new_height = (p_meta_data->height - 1) /
			p_meta_data->down_sample_y + 1;
		MMP_LOG(ANDROID_LOG_DEBUG, "n(%u,%u),o(%u, %u,%d,%u) ",
			new_width, new_height, p_meta_data->width,
			p_meta_data->height, p_meta_data->pitch,
			p_meta_data->bpp);
		for (y = 0, y0 = 0; y < p_meta_data->height;
		     y0++, y += p_meta_data->down_sample_y) {
			if (p_meta_data->down_sample_x == 1)
				memcpy(p_dst + new_width * bpp * y0,
					p_src + p_meta_data->pitch * y,
					p_meta_data->width * bpp);
			else {
				for (x = 0, x0 = 0; x < p_meta_data->width;
				     x0++, x += p_meta_data->down_sample_x) {
					dst_offset =
						(new_width * y0 + x0) * bpp;
					src_offset = p_meta_data->pitch *
						y + x * bpp;
					memcpy(p_dst + dst_offset,
						p_src + src_offset, bpp);
				}
			}
		}
		meta_data.size = sizeof(struct mmp_metadata_bitmap_t) +
			new_width * bpp * new_height;
	}
	ret = mmprofile_log_meta(event, type, &meta_data);
	vfree(meta_data.p_data);

	return ret;
}
EXPORT_SYMBOL(mmprofile_log_meta_bitmap);

long mmprofile_log_meta_yuv_bitmap(mmp_event event, enum mmp_log_type type,
	struct mmp_metadata_bitmap_t *p_meta_data)
{
	int ret = 0;
	struct mmp_metadata_t meta_data;
	char *p_src, *p_dst;
	long pitch;

	if (!mmprofile_globals.enable)
		return 0;
	if (event >= MMPROFILE_MAX_EVENT_COUNT)
		return -3;
	if (in_interrupt())
		return 0;
	if (event == MMP_INVALID_EVENT)
		return 0;
	if (!is_mmp_valid(event))
		return 0;


	meta_data.data1 = p_meta_data->data1;
	meta_data.data2 = p_meta_data->data2;
	meta_data.data_type = MMPROFILE_META_RAW;
	meta_data.size = p_meta_data->data_size;
	meta_data.p_data = vmalloc(meta_data.size);
	if (!meta_data.p_data)
		return -1;
	p_src = (char *)p_meta_data->p_data + p_meta_data->start_pos;
	p_dst = (char *)((unsigned long)(meta_data.p_data));
	pitch = p_meta_data->pitch;

	if (pitch < 0)
		((struct mmp_metadata_bitmap_t *)(meta_data.p_data))->pitch
			= -pitch;
	if ((pitch > 0) && (p_meta_data->down_sample_x == 1)
	    && (p_meta_data->down_sample_y == 1))
		memcpy(p_dst, p_src, p_meta_data->data_size);
	else {
		unsigned int x, y, x0, y0;
		unsigned int new_width, new_height, new_pitch;
		unsigned int bpp = p_meta_data->bpp / 8;
		unsigned int x_offset = p_meta_data->down_sample_x * 2;
		unsigned int src_offset, dst_offset;

		new_width = (p_meta_data->width - 1) /
			p_meta_data->down_sample_x + 1;
		new_height = (p_meta_data->height - 1) /
			p_meta_data->down_sample_y + 1;
		new_pitch = new_width * bpp;
		MMP_LOG(ANDROID_LOG_DEBUG, "n(%u,%u,%u),o(%u, %u,%d,%u) ",
			new_width, new_height, new_pitch,
			p_meta_data->width, p_meta_data->height,
			p_meta_data->pitch, p_meta_data->bpp);
		for (y = 0, y0 = 0; y < p_meta_data->height;
		     y0++, y += p_meta_data->down_sample_y) {
			if (x_offset == 2)
				memcpy(p_dst + new_pitch * y0,
					p_src + p_meta_data->pitch * y,
					p_meta_data->pitch);
			else {
				for (x = 0, x0 = 0; x < p_meta_data->width;
				     x0 += 2, x += x_offset) {
					src_offset = p_meta_data->pitch * y +
						x * bpp;
					dst_offset = new_pitch * y0 + x0 * bpp;
					memcpy(p_dst + dst_offset,
						p_src + src_offset, bpp * 2);
				}
			}
		}
		meta_data.size = new_pitch * new_height;
	}
	ret = mmprofile_log_meta(event, type, &meta_data);
	vfree(meta_data.p_data);

	return ret;
}
EXPORT_SYMBOL(mmprofile_log_meta_yuv_bitmap);

/* Exposed APIs end */

/* Debug FS begin */
static struct dentry *g_p_debug_fs_dir;
static struct dentry *g_p_debug_fs_start;
static struct dentry *g_p_debug_fs_buffer;
static struct dentry *g_p_debug_fs_global;
static struct dentry *g_p_debug_fs_reset;
static struct dentry *g_p_debug_fs_enable;

static ssize_t mmprofile_dbgfs_reset_write(struct file *file,
	const char __user *buf, size_t size, loff_t *ppos)
{
	mmprofile_reset_buffer();
	return 1;
}

static ssize_t mmprofile_dbgfs_start_read(struct file *file, char __user *buf,
	size_t size, loff_t *ppos)
{
	char str[32];
	int r;

	MMP_LOG(ANDROID_LOG_DEBUG, "start=%d", mmprofile_globals.start);
	r = sprintf(str, "start = %d\n", mmprofile_globals.start);
	if (r < 0)
		MMP_MSG("%s snprintf fail\n", __func__);
	return simple_read_from_buffer(buf, size, ppos, str, r);
}

static ssize_t mmprofile_dbgfs_start_write(struct file *file,
	const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned int str = 0;
	int start;
	ssize_t ret;

	ret = simple_write_to_buffer(&str, 4, ppos, buf, size);
	if ((str & 0xFF) == 0x30)
		start = 0;
	else
		start = 1;
	MMP_LOG(ANDROID_LOG_DEBUG, "start=%d", start);
	mmprofile_force_start(start);
	return ret;
}

static ssize_t mmprofile_dbgfs_enable_read(struct file *file, char __user *buf,
	size_t size, loff_t *ppos)
{
	char str[32];
	int r;

	MMP_LOG(ANDROID_LOG_DEBUG, "enable=%d", mmprofile_globals.enable);
	r = sprintf(str, "enable = %d\n", mmprofile_globals.enable);
	if (r < 0)
		MMP_MSG("%s snprintf fail\n", __func__);
	return simple_read_from_buffer(buf, size, ppos, str, r);
}

static ssize_t mmprofile_dbgfs_enable_write(struct file *file,
	const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned int str = 0;
	int enable;
	ssize_t ret;

	ret = simple_write_to_buffer(&str, 4, ppos, buf, size);
	if ((str & 0xFF) == 0x30)
		enable = 0;
	else
		enable = 1;
	MMP_LOG(ANDROID_LOG_DEBUG, "enable=%d", enable);
	mmprofile_enable(enable);
	return ret;
}

static ssize_t mmprofile_dbgfs_buffer_read(struct file *file, char __user *buf,
	size_t size, loff_t *ppos)
{
	static unsigned int backup_state;
	unsigned int copy_size = 0;
	unsigned int total_copy = 0;
	unsigned long addr;

	if (!bmmprofile_init_buffer)
		return -EFAULT;
	MMP_LOG(ANDROID_LOG_VERBOSE, "size=%ld ppos=%d",
		(unsigned long)size, (int)(*ppos));
	if (*ppos == 0) {
		backup_state = mmprofile_globals.start;
		mmprofile_force_start(0);
	}
	while (size > 0) {
		mmprofile_get_dump_buffer(*ppos, &addr, &copy_size);
		if (copy_size == 0) {
			if (backup_state)
				mmprofile_force_start(1);
			break;
		}
		if (size >= copy_size) {
			size -= copy_size;
		} else {
			copy_size = size;
			size = 0;
		}
		if (copy_to_user(buf + total_copy, (void *)addr, copy_size)) {
			MMP_LOG(ANDROID_LOG_DEBUG,
				"fail to copytouser total_copy=%d",
				total_copy);
			break;
		}
		*ppos += copy_size;
		total_copy += copy_size;
	}
	return total_copy;
}

static ssize_t mmprofile_dbgfs_global_read(struct file *file, char __user *buf,
	size_t size, loff_t *ppos)
{
	return simple_read_from_buffer(buf, size, ppos, &mmprofile_globals,
		MMPROFILE_GLOBALS_SIZE);
}

#ifdef MMP_USE
static ssize_t mmprofile_dbgfs_global_write(struct file *file,
	const char __user *buf, size_t size, loff_t *ppos)
{
	return simple_write_to_buffer(&mmprofile_globals,
		MMPROFILE_GLOBALS_SIZE, ppos, buf, size);
}
#endif

static const struct file_operations mmprofile_dbgfs_enable_fops = {
	.read = mmprofile_dbgfs_enable_read,
	.write = mmprofile_dbgfs_enable_write,
	.llseek = generic_file_llseek,
};

static const struct file_operations mmprofile_dbgfs_start_fops = {
	.read = mmprofile_dbgfs_start_read,
	.write = mmprofile_dbgfs_start_write,
	.llseek = generic_file_llseek,
};

static const struct file_operations mmprofile_dbgfs_reset_fops = {
	.write = mmprofile_dbgfs_reset_write,
	.llseek = generic_file_llseek,
};

static const struct file_operations mmprofile_dbgfs_buffer_fops = {
	.read = mmprofile_dbgfs_buffer_read,
	.llseek = generic_file_llseek,
};

static const struct file_operations mmprofile_dbgfs_global_fops = {
	.read = mmprofile_dbgfs_global_read,
#ifdef MMP_USE
	.write = mmprofile_dbgfs_global_write,
#endif
	.llseek = generic_file_llseek,
};


/* Debug FS end */

static char cmd_buf[128];
static void process_dbg_cmd(char *cmd)
{
	if (strncmp(cmd, "mmp_log_on:", 11) == 0) {
		char *p = (char *)cmd + 11;
		unsigned long value;

		if (0 == kstrtoul(p, 10, &value) && 0 != value)
			mmp_log_on = 1;
		else
			mmp_log_on = 0;
		MMP_MSG("mmp_log_on=%d\n", mmp_log_on);
	} else if (strncmp(cmd, "mmp_trace_log_on:", 17) == 0) {
		char *p = (char *)cmd + 17;
		unsigned long value;

		if (0 == kstrtoul(p, 10, &value) && 0 != value)
			mmp_trace_log_on = 1;
		else
			mmp_trace_log_on = 0;
		MMP_MSG("mmp_trace_log_on=%d\n", mmp_trace_log_on);
	} else {
		MMP_MSG("invalid mmp debug command: %s\n",
			cmd != NULL ? cmd : "(empty)");
	}
}

/* Driver specific begin */
#ifdef MMP_USE
static dev_t mmprofile_devno;
static struct cdev *mmprofile_cdev;
static struct class *mmprofile_class;
#endif
static int mmprofile_release(struct inode *inode, struct file *file)
{
	return 0;
}

static int mmprofile_open(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t mmprofile_read(struct file *file, char __user *data,
	size_t len, loff_t *ppos)
{
	return 0;
}

static ssize_t mmprofile_write(struct file *file, const char __user *data,
	size_t len, loff_t *ppos)
{
	ssize_t ret;
	size_t length = len;

	if (length > 127)
		length = 127;
	ret = length;

	if (copy_from_user(&cmd_buf, data, length))
		return -EFAULT;

	cmd_buf[length] = 0;
	process_dbg_cmd(cmd_buf);
	return ret;
}

static long mmprofile_ioctl(struct file *file, unsigned int cmd,
	unsigned long arg)
{
	long ret = 0;
	unsigned long retn = 0;

	switch (cmd) {
	case MMP_IOC_ENABLE:
		if ((arg == 0) || (arg == 1))
			mmprofile_enable((int)arg);
		else
			ret = -EINVAL;
		break;
	case MMP_IOC_REMOTESTART:
		/* if using remote tool (PC side) or adb shell command,
		 * can always start mmp
		 */
		if ((arg == 0) || (arg == 1))
			mmprofile_remote_start((int)arg);
		else
			ret = -EINVAL;
		break;
	case MMP_IOC_START:
		if ((arg == 0) || (arg == 1))
			mmprofile_force_start((int)arg);
		else
			ret = -EINVAL;
		break;
	case MMP_IOC_TIME:
	{
		unsigned int time_low;
		unsigned int time_high;
		unsigned long long time;
		unsigned long long *p_time_user =
			(unsigned long long __user *)arg;

		system_time(&time_low, &time_high);
		time = time_low + ((unsigned long long)time_high << 32);
		put_user(time, p_time_user);
	}
	break;
	case MMP_IOC_REGEVENT:
	{
		struct mmprofile_eventinfo_t event_info;
		struct mmprofile_eventinfo_t __user *p_event_info_user =
			(struct mmprofile_eventinfo_t __user *)arg;

		retn = copy_from_user(&event_info, p_event_info_user,
		    sizeof(struct mmprofile_eventinfo_t));
		event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
		event_info.parent_id =
		    mmprofile_register_event(event_info.parent_id,
		    event_info.name);
		retn = copy_to_user(p_event_info_user, &event_info,
			sizeof(struct mmprofile_eventinfo_t));
	}
	break;
	case MMP_IOC_FINDEVENT:
	{
		struct mmprofile_eventinfo_t event_info;
		struct mmprofile_eventinfo_t __user *p_event_info_user =
			(struct mmprofile_eventinfo_t __user *)arg;

		retn = copy_from_user(&event_info, p_event_info_user,
		    sizeof(struct mmprofile_eventinfo_t));
		event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
		mutex_lock(&mmprofile_regtable_mutex);
		event_info.parent_id =
		    mmprofile_find_event_int(event_info.parent_id,
		    event_info.name);
		mutex_unlock(&mmprofile_regtable_mutex);
		retn = copy_to_user(p_event_info_user, &event_info,
			sizeof(struct mmprofile_eventinfo_t));
	}
	break;
	case MMP_IOC_ENABLEEVENT:
	{
		mmp_event event;
		unsigned int enable;
		unsigned int recursive;
		unsigned int ftrace;
		struct mmprofile_eventsetting_t __user *p_event_setting_user =
			(struct mmprofile_eventsetting_t __user *)arg;

		get_user(event, &p_event_setting_user->event);
		get_user(enable, &p_event_setting_user->enable);
		get_user(recursive, &p_event_setting_user->recursive);
		get_user(ftrace, &p_event_setting_user->ftrace);
		if (recursive) {
			mutex_lock(&mmprofile_regtable_mutex);
			mmprofile_enable_ftrace_event_recursive(event,
				enable, ftrace);
			mutex_unlock(&mmprofile_regtable_mutex);
		} else
			mmprofile_enable_ftrace_event(event, enable, ftrace);
	}
	break;
	case MMP_IOC_LOG:
	{
		mmp_event event;
		enum mmp_log_type type;
		unsigned int data1;
		unsigned int data2;
		struct mmprofile_eventlog_t __user *p_event_log_user =
			(struct mmprofile_eventlog_t __user *)arg;

		get_user(event, &p_event_log_user->event);
		get_user(type, &p_event_log_user->type);
		get_user(data1, &p_event_log_user->data1);
		get_user(data2, &p_event_log_user->data2);
		mmprofile_log_ex(event, type, data1, data2);
	}
	break;
	case MMP_IOC_DUMPEVENTINFO:
	{
		mmp_event index;
		struct mmprofile_regtable_t *p_regtable;
		struct mmprofile_eventinfo_t __user *p_event_info_user =
			(struct mmprofile_eventinfo_t __user *)arg;
		struct mmprofile_eventinfo_t event_info_dummy = { 0, "" };

		mmprofile_register_static_events(1);
		mutex_lock(&mmprofile_regtable_mutex);
		retn =
		    copy_to_user(p_event_info_user, &event_info_dummy,
				 sizeof(struct mmprofile_eventinfo_t));
		index = MMP_ROOT_EVENT;
		list_for_each_entry(p_regtable,
			&(mmprofile_regtable.list), list) {
			retn = copy_to_user(&p_event_info_user[index],
			    &(p_regtable->event_info),
				sizeof(struct mmprofile_eventinfo_t));
			index++;
		}
		for (; index < MMPROFILE_MAX_EVENT_COUNT; index++) {
			retn = copy_to_user(&p_event_info_user[index],
				&event_info_dummy,
				sizeof(struct mmprofile_eventinfo_t));
		}
		mutex_unlock(&mmprofile_regtable_mutex);
	}
	break;
	case MMP_IOC_METADATALOG:
	{
		struct mmprofile_metalog_t meta_log;
		struct mmprofile_metalog_t __user *p_meta_log_user =
			(struct mmprofile_metalog_t __user *)arg;
		struct mmp_metadata_t meta_data;
		struct mmp_metadata_t __user *p_meta_data_user;

		retn = copy_from_user(&meta_log, p_meta_log_user,
			sizeof(struct mmprofile_metalog_t));
		if (retn) {
			pr_debug("[MMPROFILE]: copy_from_user failed! line:%d\n",
			 __LINE__);
			return -EFAULT;
		}
		p_meta_data_user = (struct mmp_metadata_t __user *)
			&(p_meta_log_user->meta_data);
		retn = copy_from_user(&meta_data, p_meta_data_user,
			sizeof(struct mmp_metadata_t));

		if (retn) {
			pr_debug("[MMPROFILE]: copy_from_user failed! line:%d\n",
			 __LINE__);
			return -EFAULT;
		}

		if (meta_data.size == 0 || meta_data.size > 0x3000000) {
			pr_debug("[MMPROFILE]: meta_data.size Invalid! line:%d\n",
			 __LINE__);
			return -EFAULT;
		}

		mmprofile_log_meta_int(meta_log.id, meta_log.type,
			&meta_data, 1);
	}
	break;
	case MMP_IOC_DUMPMETADATA:
	{
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)

		unsigned int meta_data_count = 0;
		unsigned int offset = 0;
		unsigned int index;
		unsigned int buffer_size = 0;
		struct mmprofile_meta_datablock_t *p_meta_data_block;
		struct mmprofile_metadata_t __user *p_meta_data =
			(struct mmprofile_metadata_t __user *)(arg + 8);

		mutex_lock(&mmprofile_meta_buffer_mutex);
		list_for_each_entry(p_meta_data_block,
			&mmprofile_meta_buffer_list, list) {
			if (p_meta_data_block->data_size <= 0)
				continue;

			put_user(p_meta_data_block->cookie,
				 &(p_meta_data[meta_data_count].cookie));
			put_user(p_meta_data_block->data_size,
				 &(p_meta_data[meta_data_count].data_size));
			put_user(p_meta_data_block->data_type,
				 &(p_meta_data[meta_data_count].data_type));
			buffer_size += p_meta_data_block->data_size;
			meta_data_count++;
		}
		put_user(meta_data_count, (unsigned int __user *)arg);
		/* meta_data_count, buffer_size); */
		offset = 8 + sizeof(struct mmprofile_metadata_t) *
			meta_data_count;
		index = 0;
		list_for_each_entry(p_meta_data_block,
			&mmprofile_meta_buffer_list, list) {
			if (p_meta_data_block->data_size <= 0)
				continue;
			put_user(offset - 8, &(p_meta_data[index].data_offset));
			/* offset-8, p_meta_data_block->data_size); */
			if (((unsigned long)(p_meta_data_block->meta_data) +
			     p_meta_data_block->data_size) >
			    ((unsigned long)p_mmprofile_meta_buffer +
			     mmprofile_globals.meta_buffer_size)) {
				unsigned long left_size =
				    (unsigned long)p_mmprofile_meta_buffer +
				    mmprofile_globals.meta_buffer_size -
				    (unsigned long)
						(p_meta_data_block->meta_data);
				retn =
				    copy_to_user((void __user *)(arg + offset),
						 p_meta_data_block->meta_data,
						 left_size);
				retn =
				    copy_to_user((void __user *)
						(arg + offset + left_size),
						 p_mmprofile_meta_buffer,
						 p_meta_data_block->data_size -
						 left_size);
			} else
				retn =
				    copy_to_user((void __user *)(arg + offset),
						 p_meta_data_block->meta_data,
						 p_meta_data_block->data_size);
			offset = (offset + p_meta_data_block->data_size + 3)
				& (~3);
			index++;
		}
		put_user(offset - 8, (unsigned int __user *)(arg + 4));
		mutex_unlock(&mmprofile_meta_buffer_mutex);
#endif
	}

	break;
	case MMP_IOC_SELECTBUFFER:
		mmprofile_globals.selected_buffer = arg;
		break;
	case MMP_IOC_SETRECORDCNT:
	{
		if (arg > MMPROFILE_MAX_BUFFER_SIZE) {
			arg = MMPROFILE_MAX_BUFFER_SIZE;
			ret = -EINVAL;
		} else if (arg < MMPROFILE_MIN_BUFFER_SIZE)
			arg = MMPROFILE_MIN_BUFFER_SIZE;
		mmprofile_globals.new_buffer_size_record = arg;
		break;
	}
	case MMP_IOC_SETMETABUFSIZE:
	{
		if (arg > MMPROFILE_MAX_META_BUFFER_SIZE) {
			arg = MMPROFILE_MAX_META_BUFFER_SIZE;
			ret = -EINVAL;
		} else if (arg < MMPROFILE_MIN_META_BUFFER_SIZE)
			arg = MMPROFILE_MIN_META_BUFFER_SIZE;
		mmprofile_globals.new_meta_buffer_size = arg;
		break;
	}
	case MMP_IOC_TRYLOG:
		if ((!mmprofile_globals.enable) ||
		    (!bmmprofile_init_buffer) ||
		    (!mmprofile_globals.start) ||
		    (arg >= MMPROFILE_MAX_EVENT_COUNT) ||
		    (arg == MMP_INVALID_EVENT))
			ret = -EINVAL;
		else if (!(mmprofile_globals.event_state[arg] &
				MMP_EVENT_STATE_ENABLED))
			ret = -EINVAL;
		break;
	case MMP_IOC_ISENABLE:
	{
		unsigned int is_enable;
		unsigned int __user *p_user = (unsigned int __user *)arg;
		mmp_event event;

		get_user(event, p_user);
		is_enable = (unsigned int)mmprofile_query_enable(event);
		put_user(is_enable, p_user);
	}
	break;
	case MMP_IOC_TEST:
		{
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

#ifdef CONFIG_COMPAT
#define COMPAT_MMP_IOC_METADATALOG \
	_IOW(MMP_IOC_MAGIC, 9, struct compat_mmprofile_metalog_t)
#define COMPAT_MMP_IOC_DUMPMETADATA \
	_IOR(MMP_IOC_MAGIC, 10, struct compat_mmprofile_metalog_t)
static long mmprofile_ioctl_compat(struct file *file, unsigned int cmd,
	unsigned long arg)
{
	long ret = 0;
	unsigned long retn;

	switch (cmd) {
	case MMP_IOC_ENABLE:
		ret = mmprofile_ioctl(file, MMP_IOC_ENABLE, arg);
		break;
	case MMP_IOC_REMOTESTART:
		/* if using remote tool (PC side) or adb shell command,
		 * can always start mmp
		 */
		ret = mmprofile_ioctl(file, MMP_IOC_REMOTESTART, arg);
		break;
	case MMP_IOC_START:
		ret = mmprofile_ioctl(file, MMP_IOC_START, arg);
		break;
	case MMP_IOC_TIME:
	{
		unsigned int time_low;
		unsigned int time_high;
		unsigned long long time;
		unsigned long long __user *p_time_user;

		p_time_user = compat_ptr(arg);

		system_time(&time_low, &time_high);
		time = time_low + ((unsigned long long)time_high << 32);
		put_user(time, p_time_user);
	}
	break;
	case MMP_IOC_REGEVENT:
	{
		struct mmprofile_eventinfo_t event_info;
		struct mmprofile_eventinfo_t __user *p_event_info_user;

		p_event_info_user = compat_ptr(arg);

		retn = copy_from_user(&event_info, p_event_info_user,
			sizeof(struct mmprofile_eventinfo_t));
		event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
		event_info.parent_id =
		    mmprofile_register_event(event_info.parent_id,
		    event_info.name);
		retn = copy_to_user(p_event_info_user, &event_info,
		    sizeof(struct mmprofile_eventinfo_t));
	}
	break;
	case MMP_IOC_FINDEVENT:
	{
		struct mmprofile_eventinfo_t event_info;
		struct mmprofile_eventinfo_t __user *p_event_info_user;

		p_event_info_user = compat_ptr(arg);

		retn = copy_from_user(&event_info, p_event_info_user,
		    sizeof(struct mmprofile_eventinfo_t));
		event_info.name[MMPROFILE_EVENT_NAME_MAX_LEN] = 0;
		mutex_lock(&mmprofile_regtable_mutex);
		event_info.parent_id =
		    mmprofile_find_event_int(event_info.parent_id,
		    event_info.name);
		mutex_unlock(&mmprofile_regtable_mutex);
		retn = copy_to_user(p_event_info_user, &event_info,
		    sizeof(struct mmprofile_eventinfo_t));
	}
	break;
	case MMP_IOC_ENABLEEVENT:
	{
		mmp_event event;
		unsigned int enable;
		unsigned int recursive;
		unsigned int ftrace;
		struct mmprofile_eventsetting_t __user *p_event_setting_user;

		p_event_setting_user = compat_ptr(arg);

		get_user(event, &p_event_setting_user->event);
		get_user(enable, &p_event_setting_user->enable);
		get_user(recursive, &p_event_setting_user->recursive);
		get_user(ftrace, &p_event_setting_user->ftrace);
		if (recursive) {
			mutex_lock(&mmprofile_regtable_mutex);
			mmprofile_enable_ftrace_event_recursive(event,
				enable, ftrace);
			mutex_unlock(&mmprofile_regtable_mutex);
		} else
			mmprofile_enable_ftrace_event(event, enable, ftrace);
	}
	break;
	case MMP_IOC_LOG:
	{
		mmp_event event;
		enum mmp_log_type type;
		unsigned int data1;
		unsigned int data2;
		struct mmprofile_eventlog_t __user *p_event_log_user;

		p_event_log_user = compat_ptr(arg);

		get_user(event, &p_event_log_user->event);
		get_user(type, &p_event_log_user->type);
		get_user(data1, &p_event_log_user->data1);
		get_user(data2, &p_event_log_user->data2);
		mmprofile_log_ex(event, type, data1, data2);
	}
	break;
	case MMP_IOC_DUMPEVENTINFO:
	{
		mmp_event index;
		struct mmprofile_regtable_t *p_regtable;
		struct mmprofile_eventinfo_t __user *p_event_info_user;
		struct mmprofile_eventinfo_t event_info_dummy = { 0, "" };

		p_event_info_user = compat_ptr(arg);

		mmprofile_register_static_events(1);
		mutex_lock(&mmprofile_regtable_mutex);
		retn =
		    copy_to_user(p_event_info_user, &event_info_dummy,
				 sizeof(struct mmprofile_eventinfo_t));
		index = MMP_ROOT_EVENT;
		list_for_each_entry(p_regtable,
			&(mmprofile_regtable.list), list) {

			retn = copy_to_user(&p_event_info_user[index],
				&(p_regtable->event_info),
				sizeof(struct mmprofile_eventinfo_t));
			index++;
		}
		for (; index < MMPROFILE_MAX_EVENT_COUNT; index++) {
			retn = copy_to_user(&p_event_info_user[index],
				&event_info_dummy,
				sizeof(struct mmprofile_eventinfo_t));
		}
		mutex_unlock(&mmprofile_regtable_mutex);
	}
	break;
	case COMPAT_MMP_IOC_METADATALOG:
	{
		struct mmprofile_metalog_t meta_log;
		struct compat_mmprofile_metalog_t compat_meta_log;
		struct compat_mmprofile_metalog_t __user
			*p_compat_meta_log_user;

		p_compat_meta_log_user = compat_ptr(arg);

		if (copy_from_user(&compat_meta_log, p_compat_meta_log_user,
			sizeof(struct compat_mmprofile_metalog_t))) {
			pr_debug("[MMPROFILE]: copy_from_user failed! line:%d\n",
			 __LINE__);
			return -EFAULT;
		}
		meta_log.id = compat_meta_log.id;
		meta_log.type = compat_meta_log.type;
		meta_log.meta_data.data1 = compat_meta_log.meta_data.data1;
		meta_log.meta_data.data2 = compat_meta_log.meta_data.data2;
		meta_log.meta_data.data_type =
			compat_meta_log.meta_data.data_type;
		meta_log.meta_data.size = compat_meta_log.meta_data.size;
		meta_log.meta_data.p_data =
			compat_ptr(compat_meta_log.meta_data.p_data);

		if (meta_log.meta_data.size == 0 ||
			meta_log.meta_data.size > 0x3000000) {
			pr_debug("[MMPROFILE]: meta_log.meta_data.size Invalid! line:%d\n",
			 __LINE__);
			return -EFAULT;
		}
		mmprofile_log_meta_int(meta_log.id, meta_log.type,
			&(meta_log.meta_data), 1);
	}
	break;
	case COMPAT_MMP_IOC_DUMPMETADATA:
	{
#if IS_ENABLED(CONFIG_MTK_MMPROFILE_DEBUG)

		unsigned int meta_data_count = 0;
		unsigned int offset = 0;
		unsigned int index;
		unsigned int buffer_size = 0;
		struct mmprofile_meta_datablock_t *p_meta_data_block;
		struct mmprofile_metadata_t __user *p_meta_data;
		unsigned int __user *p_user;

		p_meta_data = compat_ptr(arg + 8);

		mutex_lock(&mmprofile_meta_buffer_mutex);
		list_for_each_entry(p_meta_data_block,
			&mmprofile_meta_buffer_list, list) {
			if (p_meta_data_block->data_size <= 0)
				continue;

			put_user(p_meta_data_block->cookie,
				 &(p_meta_data[meta_data_count].cookie));
			put_user(p_meta_data_block->data_size,
				 &(p_meta_data[meta_data_count].data_size));
			put_user(p_meta_data_block->data_type,
				 &(p_meta_data[meta_data_count].data_type));
			buffer_size += p_meta_data_block->data_size;
			meta_data_count++;
		}
		p_user = compat_ptr(arg);
		put_user(meta_data_count, p_user);
		/* meta_data_count, buffer_size); */
		offset = 8 + sizeof(struct mmprofile_metadata_t) *
			meta_data_count;
		index = 0;
		list_for_each_entry(p_meta_data_block,
			&mmprofile_meta_buffer_list, list) {
			if (p_meta_data_block->data_size <= 0)
				continue;

			put_user(offset - 8, &(p_meta_data[index].data_offset));
			/* offset-8, p_meta_data_block->data_size); */
			if (((unsigned long)(p_meta_data_block->meta_data) +
			     p_meta_data_block->data_size) >
			    ((unsigned long)p_mmprofile_meta_buffer +
			     mmprofile_globals.meta_buffer_size)) {
				unsigned long left_size =
				    (unsigned long)p_mmprofile_meta_buffer +
				    mmprofile_globals.meta_buffer_size -
				    (unsigned long)
						(p_meta_data_block->meta_data);
				p_user = compat_ptr(arg + offset);
				retn =
				    copy_to_user(p_user,
						 p_meta_data_block->meta_data,
						 left_size);
				p_user = compat_ptr(arg + offset + left_size);
				retn =
				    copy_to_user(p_user,
						 p_mmprofile_meta_buffer,
						 p_meta_data_block->data_size -
						 left_size);
			} else {
				p_user = compat_ptr(arg + offset);
				retn =
					copy_to_user(p_user,
						p_meta_data_block->meta_data,
						p_meta_data_block->data_size);
			}
			offset = (offset + p_meta_data_block->data_size
				+ 3) & (~3);
			index++;
		}
		p_user = compat_ptr(arg + 4);
		put_user(offset - 8, p_user);
		mutex_unlock(&mmprofile_meta_buffer_mutex);
#endif
	}
	break;
	case MMP_IOC_SELECTBUFFER:
		ret = mmprofile_ioctl(file, MMP_IOC_SELECTBUFFER, arg);
		break;
	case MMP_IOC_SETRECORDCNT:
		ret = mmprofile_ioctl(file, MMP_IOC_SETRECORDCNT, arg);
		break;
	case MMP_IOC_SETMETABUFSIZE:
		ret = mmprofile_ioctl(file, MMP_IOC_SETMETABUFSIZE, arg);
		break;
	case MMP_IOC_TRYLOG:
		if ((!mmprofile_globals.enable) ||
		    (!bmmprofile_init_buffer) ||
		    (!mmprofile_globals.start) ||
		    (arg >= MMPROFILE_MAX_EVENT_COUNT) ||
		    (!(mmprofile_globals.event_state[arg] &
				MMP_EVENT_STATE_ENABLED)))
			ret = -EINVAL;
		break;
	case MMP_IOC_ISENABLE:
		{
			unsigned int is_enable;
			unsigned int __user *p_user;
			mmp_event event;

			p_user = compat_ptr(arg);

			get_user(event, p_user);
			is_enable = (unsigned int)mmprofile_query_enable(event);
			put_user(is_enable, p_user);
		}
		break;
	case MMP_IOC_TEST:
		{
		}
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}
#endif

static int mmprofile_mmap(struct file *file, struct vm_area_struct *vma)
{
	unsigned long pos = 0;
	unsigned long i = 0;

	if (mmprofile_globals.selected_buffer == MMPROFILE_GLOBALS_BUFFER) {

		/* check user space buffer length */
		if ((vma->vm_end - vma->vm_start) != MMPROFILE_GLOBALS_SIZE)
			return -EINVAL;

		pos = vma->vm_start;
		for (i = 0; i < MMPROFILE_GLOBALS_SIZE;
			i += PAGE_SIZE, pos += PAGE_SIZE) {
			if (remap_pfn_range
			    (vma, pos,
					vmalloc_to_pfn((void *)((unsigned long)
					(&mmprofile_globals) + i)),
			     PAGE_SIZE, PAGE_READONLY))
				return -EAGAIN;
			/* pr_debug("pfn: 0x%08x\n", pfn); */
		}
	} else if (mmprofile_globals.selected_buffer ==
		MMPROFILE_PRIMARY_BUFFER) {

		/* check user space buffer length */
		if ((vma->vm_end - vma->vm_start) !=
			mmprofile_globals.buffer_size_bytes)
			return -EINVAL;

		mmprofile_init_buffer();

		if (!bmmprofile_init_buffer)
			return -EAGAIN;

		pos = vma->vm_start;

		for (i = 0; i < mmprofile_globals.buffer_size_bytes;
		     i += PAGE_SIZE, pos += PAGE_SIZE) {
			if (remap_pfn_range
			    (vma, pos,
					vmalloc_to_pfn((void *)((unsigned long)
					p_mmprofile_ring_buffer + i)),
			     PAGE_SIZE, PAGE_READONLY))
				return -EAGAIN;
		}
	} else
		return -EINVAL;
	return 0;
}

const struct file_operations mmprofile_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = mmprofile_ioctl,
	.open = mmprofile_open,
	.release = mmprofile_release,
	.read = mmprofile_read,
	.write = mmprofile_write,
	.mmap = mmprofile_mmap,
#ifdef CONFIG_COMPAT
	.compat_ioctl = mmprofile_ioctl_compat,
#endif
};

struct miscdevice *mmp_dev;


static int mmprofile_probe(void)
{

	int ret;

	mmp_log_on = false;
	mmp_trace_log_on = false;
	/* Create debugfs */
	g_p_debug_fs_dir = debugfs_create_dir("mmprofile", NULL);
	if (g_p_debug_fs_dir) {
		/* Create debugfs files. */
		g_p_debug_fs_enable =
		    debugfs_create_file("enable", 0600,
				g_p_debug_fs_dir, NULL,
				&mmprofile_dbgfs_enable_fops);
		g_p_debug_fs_start =
		    debugfs_create_file("start", 0600,
				g_p_debug_fs_dir, NULL,
				&mmprofile_dbgfs_start_fops);
		g_p_debug_fs_buffer =
		    debugfs_create_file("buffer", 0400,
				g_p_debug_fs_dir, NULL,
				&mmprofile_dbgfs_buffer_fops);
		g_p_debug_fs_global =
		    debugfs_create_file("global", 0400,
				g_p_debug_fs_dir, NULL,
				&mmprofile_dbgfs_global_fops);
		g_p_debug_fs_reset =
		    debugfs_create_file("reset", 0200,
				g_p_debug_fs_dir, NULL,
				&mmprofile_dbgfs_reset_fops);
	}

	mmp_dev = kzalloc(sizeof(*mmp_dev), GFP_KERNEL);
	if (!mmp_dev)
		return -ENOMEM;

	mmp_dev->minor = MISC_DYNAMIC_MINOR;
	mmp_dev->name = "mmp";
	mmp_dev->fops = &mmprofile_fops;
	mmp_dev->parent = NULL;
	ret = misc_register(mmp_dev);
	if (ret) {
		pr_err("mmp: failed to register misc device.\n");
		kfree(mmp_dev);
		return ret;
	}

	return 0;
}

static int mmprofile_remove(void)
{
	kfree(mmp_dev);
	debugfs_remove(g_p_debug_fs_dir);
	debugfs_remove(g_p_debug_fs_enable);
	debugfs_remove(g_p_debug_fs_start);
	debugfs_remove(g_p_debug_fs_global);
	debugfs_remove(g_p_debug_fs_buffer);
	debugfs_remove(g_p_debug_fs_reset);
	return 0;
}

static int __init mmprofile_init(void)
{
	mmprofile_probe();
	return 0;
}

static void __exit mmprofile_exit(void)
{
	mmprofile_remove();
}

/* Driver specific end */

module_init(mmprofile_init);
module_exit(mmprofile_exit);
MODULE_AUTHOR("Tianshu Qiu <tianshu.qiu@mediatek.com>");
MODULE_DESCRIPTION("MMProfile Driver");
MODULE_LICENSE("GPL");
