/**
 * @file daemon/opd_trans.c
 * Processing the sample buffer
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 * Modified by Aravind Menon for Xen
 * These modifications are:
 * Copyright (C) 2005 Hewlett-Packard Co.
 *
 * Modified by Maynard Johnson <maynardj@us.ibm.com>
 * These modifications are:
 * (C) Copyright IBM Corporation 2007
 */

#include "opd_trans.h"
#include "opd_kernel.h"
#include "opd_sfile.h"
#include "opd_anon.h"
#include "opd_stats.h"
#include "opd_printf.h"
#include "opd_interface.h"
 
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>

extern size_t kernel_pointer_size;


void clear_trans_last(struct transient * trans)
{
	trans->last = NULL;
	trans->last_anon = NULL;
}


void clear_trans_current(struct transient * trans)
{
	trans->current = NULL;
	trans->anon = NULL;
}


uint64_t pop_buffer_value(struct transient * trans)
{
	uint64_t val;

	if (!trans->remaining) {
		fprintf(stderr, "BUG: popping empty buffer !\n");
		abort();
	}

	if (kernel_pointer_size == 4) {
		uint32_t const * lbuf = (void const *)trans->buffer;
		val = *lbuf;
	} else {
		uint64_t const * lbuf = (void const *)trans->buffer;
		val = *lbuf;
	}

	trans->remaining--;
	trans->buffer += kernel_pointer_size;
	return val;
}


int enough_remaining(struct transient * trans, size_t size)
{
	if (trans->remaining >= size)
		return 1;

	verbprintf(vmisc, "Dangling ESCAPE_CODE.\n");
	opd_stats[OPD_DANGLING_CODE]++;
	return 0;
}


static void opd_put_sample(struct transient * trans, unsigned long long pc)
{
	unsigned long long event;

	if (!enough_remaining(trans, 1)) {
		trans->remaining = 0;
		return;
	}

	event = pop_buffer_value(trans);

	if (trans->tracing != TRACING_ON)
		trans->event = event;

	trans->pc = pc;

	/* sfile can change at each sample for kernel */
	if (trans->in_kernel != 0)
		clear_trans_current(trans);

	if (!trans->in_kernel && trans->cookie == NO_COOKIE)
		trans->anon = find_anon_mapping(trans);

	/* get the current sfile if needed */
	if (!trans->current)
		trans->current = sfile_find(trans);

	/*
	 * can happen if kernel sample falls through the cracks, or if
	 * it's a sample from an anon region we couldn't find
	 */
	if (!trans->current)
		goto out;

	/* FIXME: this logic is perhaps too harsh? */
	if (trans->current->ignored || (trans->last && trans->last->ignored))
		goto out;

	/* log the sample or arc */
	sfile_log_sample(trans);

out:
	/* switch to trace mode */
	if (trans->tracing == TRACING_START)
		trans->tracing = TRACING_ON;

	update_trans_last(trans);
}


static void code_unknown(struct transient * trans __attribute__((unused)))
{
	fprintf(stderr, "Unknown code !\n");
	abort();
}


static void code_ctx_switch(struct transient * trans)
{
	clear_trans_current(trans);

	if (!enough_remaining(trans, 5)) {
		trans->remaining = 0;
		return;
	}

	trans->tid = pop_buffer_value(trans);
	trans->app_cookie = pop_buffer_value(trans);
	/* must be ESCAPE_CODE, CTX_TGID_CODE, tgid. Like this
	 * because tgid was added later in a compatible manner.
	 */
	pop_buffer_value(trans);
	pop_buffer_value(trans);
	trans->tgid = pop_buffer_value(trans);

	if (vmisc) {
		char const * app = find_cookie(trans->app_cookie);
		printf("CTX_SWITCH to tid %lu, tgid %lu, cookie %llx(%s)\n",
		       (unsigned long)trans->tid, (unsigned long)trans->tgid,
		       trans->app_cookie, app ? app : "none");
	}
}


static void code_cpu_switch(struct transient * trans)
{
	clear_trans_current(trans);

	if (!enough_remaining(trans, 1)) {
		trans->remaining = 0;
		return;
	}

	trans->cpu = pop_buffer_value(trans);
	verbprintf(vmisc, "CPU_SWITCH to %lu\n", trans->cpu);
}


static void code_cookie_switch(struct transient * trans)
{
	clear_trans_current(trans);

	if (!enough_remaining(trans, 1)) {
		trans->remaining = 0;
		return;
	}

	trans->cookie = pop_buffer_value(trans);

	if (vmisc) {
		char const * name = verbose_cookie(trans->cookie);
		verbprintf(vmisc, "COOKIE_SWITCH to cookie %s(%llx)\n",
			   name, trans->cookie);
	}
}


static void code_kernel_enter(struct transient * trans)
{
	verbprintf(vmisc, "KERNEL_ENTER_SWITCH to kernel\n");
	trans->in_kernel = 1;
	clear_trans_current(trans);
	/* subtlety: we must keep trans->cookie cached,
	 * even though it's meaningless for the kernel -
	 * we won't necessarily get a cookie switch on
	 * kernel exit. See comments in opd_sfile.c
	 */
}


static void code_user_enter(struct transient * trans)
{
	verbprintf(vmisc, "USER_ENTER_SWITCH to user-space\n");
	trans->in_kernel = 0;
	clear_trans_current(trans);
	clear_trans_last(trans);
}


static void code_module_loaded(struct transient * trans __attribute__((unused)))
{
	verbprintf(vmodule, "MODULE_LOADED_CODE\n");
	opd_reread_module_info();
	clear_trans_current(trans);
	clear_trans_last(trans);
}


/*
 * This also implicitly signals the end of the previous
 * trace, so we never explicitly set TRACING_OFF when
 * processing a buffer.
 */
static void code_trace_begin(struct transient * trans)
{
	verbprintf(varcs, "TRACE_BEGIN\n");
	trans->tracing = TRACING_START;
}

static void code_xen_enter(struct transient * trans)
{
	verbprintf(vmisc, "XEN_ENTER_SWITCH to xen\n");
	trans->in_kernel = 1;
	trans->current = NULL;
	/* subtlety: we must keep trans->cookie cached, even though it's
	 * meaningless for Xen - we won't necessarily get a cookie switch
	 * on Xen exit. See comments in opd_sfile.c. It seems that we can
	 * get away with in_kernel = 1 as long as we supply the correct
	 * Xen image, and its address range in startup find_kernel_image
	 * is modified to look in the Xen image also
	 */
}

extern void code_spu_profiling(struct transient * trans);
extern void code_spu_ctx_switch(struct transient * trans);

extern void code_ibs_fetch_sample(struct transient * trans);
extern void code_ibs_op_sample(struct transient * trans);

handler_t handlers[LAST_CODE + 1] = {
	&code_unknown,
	&code_ctx_switch,
	&code_cpu_switch,
	&code_cookie_switch,
	&code_kernel_enter,
	&code_user_enter,
	&code_module_loaded,
	/* tgid handled differently */
	&code_unknown,
	&code_trace_begin,
	&code_unknown,
	&code_xen_enter,
#if defined(__powerpc__)
	&code_spu_profiling,
	&code_spu_ctx_switch,
#else
	&code_unknown,
	&code_unknown,
#endif
	&code_ibs_fetch_sample,
	&code_ibs_op_sample,
};

extern void (*special_processor)(struct transient *);

void opd_process_samples(char const * buffer, size_t count)
{
	struct transient trans = {
		.buffer = buffer,
		.remaining = count,
		.tracing = TRACING_OFF,
		.current = NULL,
		.last = NULL,
		.cookie = INVALID_COOKIE,
		.app_cookie = INVALID_COOKIE,
		.anon = NULL,
		.last_anon = NULL,
		.pc = 0,
		.last_pc = 0,
		.event = 0,
		.in_kernel = -1,
		.cpu = -1,
		.tid = -1,
		.embedded_offset = UNUSED_EMBEDDED_OFFSET,
		.tgid = -1,
		.ext = NULL
	};

	/* FIXME: was uint64_t but it can't compile on alpha where uint64_t
	 * is an unsigned long and below the printf("..." %llu\n", code)
	 * generate a warning, this look like a stopper to use c98 types :/
	 */
	unsigned long long code;

	if (special_processor) {
		special_processor(&trans);
		return;
	}

	while (trans.remaining) {
		code = pop_buffer_value(&trans);

		if (!is_escape_code(code)) {
			opd_put_sample(&trans, code);
			continue;
		}

		if (!trans.remaining) {
			verbprintf(vmisc, "Dangling ESCAPE_CODE.\n");
			opd_stats[OPD_DANGLING_CODE]++;
			break;
		}

		// started with ESCAPE_CODE, next is type
		code = pop_buffer_value(&trans);
	
		if (code >= LAST_CODE) {
			fprintf(stderr, "Unknown code %llu\n", code);
			abort();
		}

		handlers[code](&trans);
	}
}