/**
 * @file daemon/opd_events.c
 * Event details for each counter
 *
 * @remark Copyright 2002, 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "config.h"
 
#include "opd_events.h"
#include "opd_printf.h"
#include "opd_extended.h"
#include "oprofiled.h"

#include "op_string.h"
#include "op_config.h"
#include "op_cpufreq.h"
#include "op_cpu_type.h"
#include "op_libiberty.h"
#include "op_hw_config.h"
#include "op_sample_file.h"

#include <stdlib.h>
#include <stdio.h>

extern op_cpu cpu_type;

struct opd_event opd_events[OP_MAX_COUNTERS];

static double cpu_speed;

static void malformed_events(void)
{
	fprintf(stderr, "oprofiled: malformed events passed "
		"on the command line\n");
	exit(EXIT_FAILURE);
}


static char * copy_token(char ** c, char delim)
{
	char * tmp = *c;
	char * tmp2 = *c;
	char * str;

	if (!**c)
		return NULL;

	while (*tmp2 && *tmp2 != delim)
		++tmp2;

	if (tmp2 == tmp)
		return NULL;

	str = op_xstrndup(tmp, tmp2 - tmp);
	*c = tmp2;
	if (**c)
		++*c;
	return str;
}


static unsigned long copy_ulong(char ** c, char delim)
{
	unsigned long val = 0;
	char * str = copy_token(c, delim);
	if (!str)
		malformed_events();
	val = strtoul(str, NULL, 0);
	free(str);
	return val;
}


void opd_parse_events(char const * events)
{
	char * ev = xstrdup(events);
	char * c;
	size_t cur = 0;

	if (cpu_type == CPU_TIMER_INT) {
		struct opd_event * event = &opd_events[0];
		event->name = xstrdup("TIMER");
		event->value = event->counter
			= event->count = event->um = 0;
		event->kernel = 1;
		event->user = 1;
		return;
	}

	if (!ev || !strlen(ev)) {
		fprintf(stderr, "oprofiled: no events passed.\n");
		exit(EXIT_FAILURE);
	}

	verbprintf(vmisc, "Events: %s\n", ev);

	c = ev;

	while (*c && cur < op_nr_counters) {
		struct opd_event * event = &opd_events[cur];

		if (!(event->name = copy_token(&c, ':')))
			malformed_events();
		event->value = copy_ulong(&c, ':');
		event->counter = copy_ulong(&c, ':');
		event->count = copy_ulong(&c, ':');
		event->um = copy_ulong(&c, ':');
		event->kernel = copy_ulong(&c, ':');
		event->user = copy_ulong(&c, ',');
		++cur;
	}

	if (*c) {
		fprintf(stderr, "oprofiled: too many events passed.\n");
		exit(EXIT_FAILURE);
	}

	free(ev);

	cpu_speed = op_cpu_frequency();
}


struct opd_event * find_counter_event(unsigned long counter)
{
	size_t i;
	struct opd_event * ret = NULL;

	if (counter >= OP_MAX_COUNTERS) {
		if((ret = opd_ext_find_counter_event(counter)) != NULL)
			return ret;
	}

	for (i = 0; i < op_nr_counters && opd_events[i].name; ++i) {
		if (counter == opd_events[i].counter)
			return &opd_events[i];
	}

	fprintf(stderr, "Unknown event for counter %lu\n", counter);
	abort();
	return NULL;
}


void fill_header(struct opd_header * header, unsigned long counter,
		 vma_t anon_start, vma_t cg_to_anon_start,
		 int is_kernel, int cg_to_is_kernel,
		 int spu_samples, uint64_t embed_offset, time_t mtime)
{
	struct opd_event * event = find_counter_event(counter);

	memset(header, '\0', sizeof(struct opd_header));
	header->version = OPD_VERSION;
	memcpy(header->magic, OPD_MAGIC, sizeof(header->magic));
	header->cpu_type = cpu_type;
	header->ctr_event = event->value;
	header->ctr_count = event->count;
	header->ctr_um = event->um;
	header->is_kernel = is_kernel;
	header->cg_to_is_kernel = cg_to_is_kernel;
	header->cpu_speed = cpu_speed;
	header->mtime = mtime;
	header->anon_start = anon_start;
	header->spu_profile = spu_samples;
	header->embedded_offset = embed_offset;
	header->cg_to_anon_start = cg_to_anon_start;
}