/*
 * Header for timepeg instrumentation.  See:
 *
 * Documentation/timepeg.txt
 * http://www.zip.com.au/~akpm/linux/
 *
 * Andrew Morton <akpm@zip.com.au>
 *
 * Some code and ideas taken from the net profiling code
 * which was contributed by <insert name here>
 */

#ifndef _TIMEPEG_H_
#define _TIMEPEG_H_ 1

#include <linux/config.h> 		/* for CONFIG_TIMEPEG */

#ifdef CONFIG_TIMEPEG

#include <linux/types.h>
#include <linux/time.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <linux/spinlock.h>
#include <linux/threads.h>
#include <linux/module.h>		/* for EXPORT_SYMBOL */
#include <linux/cache.h>		/* for __cacheline_aligned */
#include <asm/current.h>		/* smp_processor_id() needs this */
#include <linux/sched.h>		/* for struct task_struct */
#include <linux/smp.h>			/* for smp_proessor_id() */
#include <asm/atomic.h>
#ifdef CONFIG_X86_TSC
#include <asm/msr.h>			/* for rdtsc() */
#endif

#include <asm/timepeg_slot.h>

#ifdef CONFIG_X86_TSC

extern __inline__ void
timepeg_stamp(timepeg_t *pstamp)
{
	rdtsc(pstamp->lo, pstamp->hi);
}

/* acc = sub - acc */
extern __inline__ void
timepeg_sub(timepeg_t *sub, timepeg_t *acc)
{
	__asm__ __volatile__ ("subl %2,%0\n\t" 
			      "sbbl %3,%1\n\t" 
			      : "=r" (acc->lo), "=r" (acc->hi)
			      : "g" (sub->lo), "g" (sub->hi),
			      "0" (acc->lo), "1" (acc->hi));
}

/* acc = add + acc */
extern __inline__ void
timepeg_add(timepeg_t *add, timepeg_t *acc)
{
	__asm__ __volatile__ ("addl %2,%0\n\t" 
			      "adcl %3,%1\n\t" 
			      : "=r" (acc->lo), "=r" (acc->hi)
			      : "g" (add->lo), "g" (add->hi),
			      "0" (acc->lo), "1" (acc->hi));
}

/*
 * Compare 'left' and 'right'.
 *
 * Return < 0 if left < right
 * Return > 0 if left > right
 * Return 0 if left == right
 */

extern __inline__ int
timepeg_cmp(timepeg_t *left, timepeg_t *right)
{
	/* No asm from this guy... */
	if (left->hi > right->hi)
		return 1;
	else if (left->hi < right->hi)
		return -1;
	else
	{
		if (left->lo > right->lo)
			return 1;
		else if (left->lo < right->lo)
			return -1;
		return 0;
	}
}

#elif defined (__alpha__)

extern __u32 alpha_lo;
extern long alpha_hi;

/* On alpha cycle counter has only 32 bits :-( :-( */

extern __inline__ void
timepeg_stamp(struct timepeg_t *pstamp)
{
	__u32 result;
	__asm__ __volatile__ ("rpcc %0" : "r="(result));
	if (result <= alpha_lo)
		alpha_hi++;
	alpha_lo = result;
	pstamp->hi = alpha_hi;
	pstamp->lo = alpha_lo;
}

extern __inline__ void
timepeg_sub(timepeg *sub, timepeg_t *acc)
{
	unsigned long lo = acc->lo - sub->lo;
	unsigned long hi = acc->hi - sub->hi;

	if (lo < 0) {
		lo += 0x100000000L;
		hi--;
	}
	acc->hi = hi;
	acc->lo = lo;
}

extern __inline__ void
timepeg_add(timepeg_t *add, timepeg_t *acc)
{
	unsigned long lo = acc->lo + add->lo;
	unsigned long hi = acc->hi + add->hi;

	if (lo >= 0x100000000L) {
		lo -= 0x100000000L;
		hi++;
	}
	acc->hi = hi;
	acc->lo = lo;
}

#else

/*
 * The profile stuff uses gettimeofday() here.
 * Skip it for now.
 */

#error Timepegs not supported on this architecture.  Did you configure for a Pentium?

#endif	/* architecture */

#endif	/* CONFIG_TIMEPEG */


#ifdef CONFIG_TIMEPEG

/*
 * This does the real work for timepeg_hit.  It returns a pointer to
 * the most-recently-departed timepeg_slot_percpu's departure_tp
 * so that we can update it within the inline code, thus providing
 * minimal drift.
 *
 * Performs once-off on-demand initialisation of the timepeg.
 */

extern struct timepeg_t *
timepeg_do_hit(	struct timepeg_slot * const _ntps,
		struct timepeg_t * const _now_tp,
		const int _mode);
/*
 * The main worker function:  execution enters a new timepeg.
 *
 * 'mode' is a combination of:
 *
 * TPH_MODE_START: this is a 'start' timepeg - mark it in g_prev_slots
 * TPH_MODE_STOP:  this is a 'stop' timepeh - accumulate predecessor arc.
 */

#define TPH_MODE_START	1
#define TPH_MODE_STOP	2
#define TPH_MODE_BOTH	(TPH_MODE_START | TPH_MODE_STOP)

extern int timepeg_no_recur[NR_CPUS] __cacheline_aligned;

extern __inline__ void
timepeg_hit(struct timepeg_slot * const _ntps, const int _mode)
{
	const int cpu = smp_processor_id();

	if (timepeg_no_recur[cpu] == 0)
	{
		unsigned long flags;
		struct timepeg_t entered_tp;
		struct timepeg_t *prev_tp;

		timepeg_no_recur[cpu] = 1;

		local_irq_save(flags);

		timepeg_stamp(&entered_tp);

		{
			/*
			 * Within this block timepeg accounting is 'suspended'.  We
			 * can spend as long as we like in here and it doesn't
			 * affect the instrumentation
			 */
			prev_tp = timepeg_do_hit(_ntps, &entered_tp, _mode);
		}

		if (prev_tp)
			timepeg_stamp(prev_tp);

		local_irq_restore(flags);
		timepeg_no_recur[cpu] = 0;
	}
}

/*
 * timepeg Interface macros.  NB: None of macros have
 * semicolons.
 */

/*
 * Define and hit a timepeg
 */
#define TIMEPEG_MODE(_M_name, _M_mode)				\
	do {							\
		static struct timepeg_slot _x_Timepeg =		\
		{						\
			name: _M_name,				\
			lock: SPIN_LOCK_UNLOCKED,		\
		};						\
		(void)timepeg_hit(&_x_Timepeg, _M_mode);	\
	} while (0)

#define TIMEPEG(_M_name)					\
	TIMEPEG_MODE(_M_name, TPH_MODE_BOTH)

/*
 * Define and hit a directed timepeg
 */
#define DTIMEPEG(_M_name, _M_directed_pred)			\
	do {							\
		static struct timepeg_slot _x_Timepeg =		\
		{						\
			name: _M_name,				\
			lock: SPIN_LOCK_UNLOCKED,		\
			directee_name: _M_directed_pred,	\
		};						\
		(void)timepeg_hit(&_x_Timepeg, TPH_MODE_BOTH);	\
	} while (0)

#else	/* CONFIG_TIMEPEG */

#define TIMEPEG(_M_name)					\
	do { } while (0)

#define DTIMEPEG(_M_name, _M_directed_pred)			\
	do { } while (0)

#endif	/* CONFIG_TIMEPEG */

#endif	/* _TIMEPEG_H_ */
