/* 2008-05-13: File added and changed by Sony Corporation */
/*
 * PARTNER-Jet Linux support patch by KMC
 *
 * 2007.02.08 BUGFIX for Application-Mode Debug
 * __kmc_sys_getpidi() and __kmc_do_exit(), changed Label to function
 * 2007.04.20 change module debug(The addition of the new method)
 * 2007.10.01 support SMP
 * 2007.10.19 support Process Trace
 * 2008.03.10 support PARTNER-Jet M40 & EventTracker
 * Ver2.0	08.04.14
 */

#include <linux/kmc/kmc.h>

#ifdef CONFIG_KMC_PATCH

#ifdef CONFIG_KMC_EVENTTRACKER_SUPPORT

#include <linux/spinlock.h>
#include <linux/version.h>

#define KMC_LINUX_EVENTLOG_VERSION	(0x00000002)

#ifdef CONFIG_KMC_EVENTTRACKER_RTM
#define	KMC_LINUX_EVENTLOG_MAXLOG	0
#else
#define KMC_LINUX_EVENTLOG_MAXLOG	(CONFIG_KMC_EVENTTRACKER_LOG_SIZE * 1024)	// must be 2^n
#endif
#define KMC_LINUX_EVENTLOG_MAXPNAME	(1024)
#define KMC_LINUX_EVENTLOG_MAXPNAMELENGTH	(32)
#define	KMC_LINUX_EVENTLOG_MAXTHREADINFO	(1024)	// must be 2^n

typedef struct {
	u64 evtTime;
	int evtId;
	int evtPid;
	int evtParam;
#ifdef CONFIG_SMP
	int cpuId;
#endif
} T_KMC_LINUX_EVENTLOG;

typedef struct {
	int	pid;
	char	name[KMC_LINUX_EVENTLOG_MAXPNAMELENGTH];
} T_KMC_LINUX_PNAME;

typedef struct {
	unsigned short	thid;
	unsigned short	pid;
} T_KMC_LINUX_THINFO;

typedef struct {
	int	version;
	int	ticks_per_10msec;

	/* EventLog Info. */
	int	log_offset;
	int	log_max;
	int	log_size;
	int	log_index;

	/* Process Name Table */
	int	pname_offset;
	int	pname_max;
	int	pname_size;
	int	pname_index;

	/* Thread Info Table */
	int	thinfo_offset;
	int	thinfo_max;
	int	thinfo_size;
	int	thinfo_index;

	/* EventLog */
	T_KMC_LINUX_EVENTLOG	log[KMC_LINUX_EVENTLOG_MAXLOG];

	/* Process Name Table */
	T_KMC_LINUX_PNAME	pname[KMC_LINUX_EVENTLOG_MAXPNAME];

	/* Thread Info Table */
	T_KMC_LINUX_THINFO	thinfo[KMC_LINUX_EVENTLOG_MAXPNAME];
} T_KMC_LINUX_LOGAREA;

static spinlock_t __kmc_linux_event_lock = SPIN_LOCK_UNLOCKED;

T_KMC_LINUX_LOGAREA	*__kmc_linux_event_data = NULL;
unsigned long		__kmc_linux_event_data_size = sizeof(T_KMC_LINUX_LOGAREA);
int __kmc_linux_event_errcount;

#include <linux/vmalloc.h>

void __kmc_linux_event_log_init(void)
{
#ifdef CONFIG_SMP
	unsigned long flags;
	spin_lock_irqsave(&__kmc_linux_event_lock, flags);
	if (!__kmc_linux_event_data) {
#endif

	__kmc_linux_event_data = vmalloc(sizeof(T_KMC_LINUX_LOGAREA));
	if (__kmc_linux_event_data) {
		memset(__kmc_linux_event_data, 0, sizeof(T_KMC_LINUX_LOGAREA));
		__kmc_linux_event_data->version = KMC_LINUX_EVENTLOG_VERSION;
		__kmc_linux_event_data->ticks_per_10msec = 10000000; /* nsec to 10ms */
		__kmc_linux_event_data->log_offset 	= (int)&__kmc_linux_event_data->log[0] - (int)&__kmc_linux_event_data->version;
		__kmc_linux_event_data->log_max		= KMC_LINUX_EVENTLOG_MAXLOG;
		__kmc_linux_event_data->log_size	= (int)&__kmc_linux_event_data->log[1] - (int)&__kmc_linux_event_data->log[0];
#ifdef CONFIG_KMC_EVENTTRACKER_RTM
		__kmc_linux_event_data->log_index	= 1;
#endif
		__kmc_linux_event_data->pname_offset	= (int)&__kmc_linux_event_data->pname[0] - (int)&__kmc_linux_event_data->version;
		__kmc_linux_event_data->pname_max	= KMC_LINUX_EVENTLOG_MAXPNAME;
		__kmc_linux_event_data->pname_size	= (int)&__kmc_linux_event_data->pname[1] - (int)&__kmc_linux_event_data->pname[0];

		__kmc_linux_event_data->thinfo_offset	= (int)&__kmc_linux_event_data->thinfo[0] - (int)&__kmc_linux_event_data->version;
		__kmc_linux_event_data->thinfo_max	= KMC_LINUX_EVENTLOG_MAXTHREADINFO;
		__kmc_linux_event_data->thinfo_size	= (int)&__kmc_linux_event_data->thinfo[1] - (int)&__kmc_linux_event_data->thinfo[0];
	}

#ifdef CONFIG_SMP
	}
	spin_unlock_irqrestore(&__kmc_linux_event_lock, flags);
#endif

	return;
}

#ifdef CONFIG_KMC_EVENTTRACKER_RTM
void __kmc_linux_event_set_logI(int evtid, int pid, int param)
{
	if (!__kmc_linux_event_data) __kmc_linux_event_log_init();

	if ((__kmc_linux_event_data)&&(evtid == KMC_LINUX_EVENTID_NEWTHREAD)) {
		unsigned long flags;
		int thindex;

		spin_lock_irqsave(&__kmc_linux_event_lock, flags);
		thindex = (__kmc_linux_event_data->thinfo_index++) & (KMC_LINUX_EVENTLOG_MAXTHREADINFO - 1);
		spin_unlock_irqrestore(&__kmc_linux_event_lock, flags);
		__kmc_linux_event_data->thinfo[thindex].thid = (unsigned short)pid;
		__kmc_linux_event_data->thinfo[thindex].pid  = (unsigned short)param;
	}
}
#else
void __kmc_linux_event_set_logI(int evtid, int pid, int param)
{
	if (!__kmc_linux_event_data) __kmc_linux_event_log_init();

	if (__kmc_linux_event_data) {
		T_KMC_LINUX_EVENTLOG *pLog;
		unsigned long flags;
		int	index;
		u64	evtTime;
		int	thindex;

		thindex = KMC_LINUX_EVENTLOG_MAXTHREADINFO;

		spin_lock_irqsave(&__kmc_linux_event_lock, flags);
		evtTime = sched_clock();
		index = (__kmc_linux_event_data->log_index++) & (KMC_LINUX_EVENTLOG_MAXLOG - 1);
		if (evtid == KMC_LINUX_EVENTID_NEWTHREAD) {
			thindex = (__kmc_linux_event_data->thinfo_index++) & (KMC_LINUX_EVENTLOG_MAXTHREADINFO - 1);
		}

		spin_unlock_irqrestore(&__kmc_linux_event_lock, flags);

		pLog = &(__kmc_linux_event_data->log[index]);

		pLog->evtTime = evtTime;
		pLog->evtId = evtid;
		pLog->evtPid = pid;
		pLog->evtParam = param;
#ifdef CONFIG_SMP
		pLog->cpuId = smp_processor_id();
#endif
		if (thindex < KMC_LINUX_EVENTLOG_MAXTHREADINFO) {
			__kmc_linux_event_data->thinfo[thindex].thid = (unsigned short)pid;
			__kmc_linux_event_data->thinfo[thindex].pid  = (unsigned short)param;
		}
	}
}
#endif

static int RegisterPname(int pid, const char *pname)
{
	int ret = -1;

	if (!__kmc_linux_event_data) __kmc_linux_event_log_init();

	if (__kmc_linux_event_data) {
		int i;
		unsigned long flags;

		spin_lock_irqsave(&__kmc_linux_event_lock, flags);

		//search slot
		for (i = 0; i < KMC_LINUX_EVENTLOG_MAXPNAME; i++) {
			int ppid = __kmc_linux_event_data->pname[i].pid;
			if (ppid == 0) {
				i = KMC_LINUX_EVENTLOG_MAXPNAME;
				break;
			} else if (ppid == pid) {
				break;
			}
		}
		if (i >= KMC_LINUX_EVENTLOG_MAXPNAME) {
			// overwrite slot
			i = __kmc_linux_event_data->pname_index++ & (KMC_LINUX_EVENTLOG_MAXPNAME - 1);
			__kmc_linux_event_data->pname[i].pid = pid;
		}

		spin_unlock_irqrestore(&__kmc_linux_event_lock, flags);

		strncpy(__kmc_linux_event_data->pname[i].name, pname, KMC_LINUX_EVENTLOG_MAXPNAMELENGTH-1);
		ret = i;
	}
	return ret;
}

void __kmc_linux_event_set_logS(int evtid, int pid, const char* pname)
{
	__kmc_linux_event_set_logI(evtid, pid, RegisterPname(pid, pname));
}

void __kmc_linux_event_set_logT(int evtid, struct task_struct* t)
{
	__kmc_linux_event_set_logI(evtid, t->pid, 0);
}

#endif

// Add 2007.02.08
/* ########## kernel/exit.c { ########## */
#ifdef noinline
#undef noinline
#define noinline	noinline
#endif
__attribute__ ((noinline))
void __kmc_do_exit(void)
{
	__asm__("nop");

}
/* ########## kernel/exit.c } ########## */

#ifdef CONFIG_KMC_PARTNER_COLLECT_THREAD_OFF /* { */
struct task_struct *__kmc_tss_list[__KMC_MAX_PT_COUNT + 1];
#else /* }{ !CONFIG_KMC_PARTNER_COLLECT_THREAD_OFF */
struct task_struct *__kmc_tss_list_array[__KMC_MAX_PT_COUNT + 1][__KMC_MAX_THREAD_COUNT];
#endif /* } CONFIG_KMC_PARTNER_COLLECT_THREAD_OFF */

#ifndef CONFIG_KMC_PARTNER_VIRTUAL_ICE_OFF /* { */
int __kmc_sys_getpid_flag;
// Add 2007.02.08
/* ########## kernel/timer.c { ########## */
__attribute__ ((noinline))
void __kmc_sys_getpid(void)
{
	__asm__("nop");
}
/* ########## kernel/timer.c } ########## */
#endif /* } CONFIG_KMC_PARTNER_VIRTUAL_ICE_OFF */

#ifdef CONFIG_KMC_LINUX_PROFILE
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
unsigned int    pid_err_count;

static inline unsigned int jtag_com_stat11(void)
{
	unsigned int    stat;
	__asm__("mrc p14,0,%0,c0,c1":"=r"(stat));
	return stat;
}

static inline void jtag_com_put11(unsigned int data)
{
	__asm__("mcr p14,0,%0,c0,c5"::"r"(data));
	return;
}
#endif
#endif  // LINUX_PROFILE(ARM)

/* ########## kernel/sched.c { ########## */
#ifndef CONFIG_KMC_TRACE_EXT_OFF /* { */

#ifdef CONFIG_SMP

#define	CORE_SMP_NUMBER	CONFIG_NR_CPUS
int __kmc_schedules_list_pid_smp[CORE_SMP_NUMBER][KMC_MAX_SHEDULE_LIST];
int __kmc_schedules_index_smp[CORE_SMP_NUMBER];
int __kmc_schedules_index_max=KMC_MAX_SHEDULE_LIST;

#else

int __kmc_schedules_index_max=KMC_MAX_SHEDULE_LIST;
int __kmc_schedules_index;
int __kmc_schedules_list_pid[KMC_MAX_SHEDULE_LIST];

#endif

#ifdef CONFIG_KMC_USE_BT
int  __kmc_schedule_trace_point;
void __kmc_code_data(int);
#ifdef CONFIG_ARM
#include	"kmc_dt_arm.c"
#endif
#ifdef  CONFIG_MIPS
#include	"kmc_dt_mips.c"
#endif
#ifdef CONFIG_SUPERH
#include	"kmc_dt_sh.c"
#endif
#ifdef CONGIG_AM33
#include	"kmc_dt_am33.c"
#endif
#endif	// CONFIG_KMC_USE_BT

#ifdef CONFIG_KMC_USE_BT
static spinlock_t __kmc_linux_schedule_lock = SPIN_LOCK_UNLOCKED;
#endif
void __kmc_schedule(struct task_struct *prev,struct task_struct *next)
{
#ifdef CONFIG_KMC_USE_BT
    unsigned long flags;
#endif /* CONFIG_KMC_USE_BT */
    int index_next;
#ifdef CONFIG_KMC_LINUX_PROFILE
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
    int i = 200;
#endif
#endif
#ifdef CONFIG_KMC_USE_BT
    spin_lock_irqsave(&__kmc_linux_schedule_lock, flags);
#endif
#ifdef CONFIG_SMP
    int	cpu;

    cpu = smp_processor_id();
    index_next=__kmc_schedules_index_smp[cpu] & (KMC_MAX_SHEDULE_LIST-1);
    ++__kmc_schedules_index_smp[cpu];
    __kmc_schedules_list_pid_smp[cpu][index_next]=next->pid;
#else   // CONFIG_SMP
	index_next=__kmc_schedules_index & (KMC_MAX_SHEDULE_LIST-1);
	++__kmc_schedules_index;

	#ifdef CONFIG_KMC_USE_BT
		/* giga trace (P-Jet M40) mode */

		#ifdef CONFIG_KMC_USR_BT_DATA_TRACE
    			/* use Data Trace */
    			__kmc_schedule_trace_point=__kmc_schedules_list_pid[index_next]=next->pid;
		#else	// CONFIG_KMC_USR_BT_DATA_TRACE
    			/* PC Trace Only */
    			__kmc_code_data(__kmc_schedule_trace_point=__kmc_schedules_list_pid[index_next]=next->pid);
		#endif	// CONFIG_KMC_USR_BT_DATA_TRACE

	#else	// CONFIG_KMC_USE_BT
		/* normal mode */
		__kmc_schedules_list_pid[index_next]=next->pid;
	#endif	// !CONFIG_KMC_USE_BT
#endif	// CONFIG_SMP

#ifdef CONFIG_KMC_EVENTTRACKER_SUPPORT
    __kmc_linux_event_set_logI( KMC_LINUX_EVENTID_TASKSWITCH, prev->pid, next->pid );
#endif	// CONFIG_KMC_EVENTTRACKER_SUPPORT

#ifdef CONFIG_KMC_LINUX_PROFILE
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
    while (0 != (jtag_com_stat11() & 0x20000000) && i > 0) {
	i--;
    }
    if ((jtag_com_stat11() & 0x20000000)==0) {
	jtag_com_put11((next->pid << 1) | 1);
    } else {
	++pid_err_count;
    }
#endif  // CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
#endif	// CONFIG_KMC_LINUX_PROFILE
#ifdef CONFIG_KMC_USE_BT
    spin_unlock_irqrestore(&__kmc_linux_schedule_lock, flags);
#endif
}


void (*__kmc_schedule_call)(struct task_struct *,struct task_struct *)=__kmc_schedule;
#endif /* CONFIG_KMC_TRACE_EXT_OFF } */
/* ########## kernel/sched.c } ########## */

/* ########## arch/XXX/kernel/ptrace.c { ########## */
#ifndef CONFIG_KMC_PARTNER_AVAILABLE_OFF /* { */
int __kmc_available_ice = 0;
#endif /* } CONFIG_KMC_PARTNER_AVAILABLE_OFF */
/* ########## arch/XXX/kernel/ptrace.c } ########## */

#ifdef CONFIG_KMC_MODULE_DEBUG_NEW
char __kmc_debug_module_name[8][64];
struct module *__kmc_debug_module[8];
void *__kmc_tmp_mod_array[7];

__attribute__ ((noinline))
void __kmc_mod_init_brfore(struct module *mod, int index) {
	__KMC_BRK_CODE();
	asm("   .long   0x4c434d88");
}

__attribute__ ((noinline))
void __kmc_mod_init_after(struct module *mod) {
	__KMC_BRK_CODE();
	asm("   .long   0x4c434d89");
}

__attribute__ ((noinline))
void __kmc_mod_exit(struct module *mod) {
	__KMC_BRK_CODE();
	asm("   .long   0x4c434d8a");
}
#endif

#if defined(CONFIG_KMC_LINUX_PROFILE) || defined(CONFIG_KMC_EVENTTRACKER_SUPPORT)
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE

#define KMC_FORK_NAME_MAX	16
struct KMC_FORK_TBL{
int pid;
char name[KMC_FORK_NAME_MAX];
};
#define KMC_FORK_TBL_MAX	256
struct KMC_FORK_TBL __kmc_fork_tbl[KMC_FORK_TBL_MAX];
int __kmc_fork_tbl_index;

void *__kmc_prof_info[]={
__kmc_fork_tbl,
&__kmc_fork_tbl_index,
(void *)KMC_FORK_TBL_MAX,
0
};
#endif

void __kmc_make_fork_tbl(struct task_struct *task_p, int mode, unsigned long param)
// struct task_struct *task_p;
// int mode;		/* 0.. fork,clone , 1... execv */
// unsigned long param;		/* modo = 0, clone_flags, mode = 1, filename */
{
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
    struct KMC_FORK_TBL *fork_tp;
    char *p,*q;
    int i;
#endif

    if(mode==0){
/* fork,clone */
#ifdef CONFIG_KMC_EVENTTRACKER_SUPPORT
	if (task_p->mm == 0) {
	    // kernel thread
	    __kmc_linux_event_set_logI( KMC_LINUX_EVENTID_NEWTHREAD, task_p->pid, 0 );
	} else if ((param & CSIGNAL) != SIGCHLD) {
	    // user thread
#if LINUX_VERSION_CODE >= 0x020600
	    if (task_p == task_p->group_leader) {
		// linuxthreads
		__kmc_linux_event_set_logI( KMC_LINUX_EVENTID_NEWTHREAD, task_p->pid, task_p->parent->pid );
	    } else {
		// NPTL
		__kmc_linux_event_set_logI( KMC_LINUX_EVENTID_NEWTHREAD, task_p->pid, task_p->group_leader->pid );
	    }
#else
	    __kmc_linux_event_set_logI( KMC_LINUX_EVENTID_NEWTHREAD, task_p->pid, task_p->p_pptr->pid );
#endif
	} else {
	    // user process
	    __kmc_linux_event_set_logS( KMC_LINUX_EVENTID_FORK, task_p->pid, task_p->comm );
	}
#endif
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
	fork_tp=&__kmc_fork_tbl[__kmc_fork_tbl_index];
	if(++__kmc_fork_tbl_index>=KMC_FORK_TBL_MAX) __kmc_fork_tbl_index=0;
	fork_tp->pid=task_p->pid;
#endif
    }
    else{
/* execv */
#ifdef CONFIG_KMC_EVENTTRACKER_SUPPORT
	__kmc_linux_event_set_logS( KMC_LINUX_EVENTID_EXEC, current->pid, (char *)param);
#endif
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
	fork_tp=&__kmc_fork_tbl[i=__kmc_fork_tbl_index];
	do{
	    if(i==0){
		fork_tp=&__kmc_fork_tbl[KMC_FORK_TBL_MAX-1];
		i=KMC_FORK_TBL_MAX-1;
	    }
	    else{
		--i;
		--fork_tp;
	    }
	    if(fork_tp->pid==task_p->pid) goto found;
	}while(i!=__kmc_fork_tbl_index) ;
/* not found */
	return;
#endif
    }
#ifdef CONFIG_KMC_LINUX_ARMV6_EXT_PROFILE
found:
    q=task_p->comm;
    p=fork_tp->name;
    for(i=0;i<KMC_FORK_NAME_MAX;++i){
	*(p++)=*q;
	if(*(q++)=='\0') break;
    }
    fork_tp->name[KMC_FORK_NAME_MAX-1] = 0;
#endif
}

#endif	// CONFIG_KMC_LINUX_PROFILE , CONFIG_KMC_EVENTTRACKER_SUPPORT

#endif

