/**
 * @file ophelp.c
 * Print out PMC event information
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "op_version.h"
#include "op_events.h"
#include "op_popt.h"
#include "op_cpufreq.h"
#include "op_hw_config.h"
#include "op_string.h"
#include "op_alloc_counter.h"
#include "op_parse_event.h"
#include "op_libiberty.h"
#include "op_xml_events.h"

static char const ** chosen_events;
static int num_chosen_events;
struct parsed_event * parsed_events;
static op_cpu cpu_type = CPU_NO_GOOD;
static char * cpu_string;
static int callgraph_depth;
static int want_xml;

static poptContext optcon;


/// return the Hamming weight (number of set bits)
static size_t hweight(size_t mask)
{
	size_t count = 0;

	while (mask) {
		mask &= mask - 1;
		count++;
	}

	return count;
}

static void do_arch_specific_event_help(struct op_event * event)
{
	switch (cpu_type) {
	case CPU_PPC64_CELL:
		printf("Group %u :", event->val / 100);
		break;
	default:
		break;
	}
}

#define LINE_LEN 99

static void word_wrap(int indent, int *column, char *msg)
{
	while (*msg) {
		int wlen = strcspn(msg, " ");
		if (*column + wlen > LINE_LEN) {
			printf("\n%*s", indent, "");
			*column = indent;
		}
		printf("%.*s ", wlen, msg);
		*column += wlen + 1;
		msg += wlen;
		msg += strspn(msg, " ");
	}
}

/**
 * help_for_event - output event name and description
 * @param i  event number
 *
 * output an help string for the event @i
 */
static void help_for_event(struct op_event * event)
{
	int column;
	uint i, j;
	uint mask;
	size_t nr_counters;
	char buf[32];

	do_arch_specific_event_help(event);
	nr_counters = op_get_nr_counters(cpu_type);

	/* Sanity check */
	if (!event)
		return;

	printf("%s", event->name);

	if(event->counter_mask != 0) {
		printf(": (counter: ");

		mask = event->counter_mask;
		if (hweight(mask) == nr_counters) {
			printf("all");
		} else {
			for (i = 0; i < CHAR_BIT * sizeof(event->counter_mask); ++i) {
				if (mask & (1 << i)) {
					printf("%d", i);
					mask &= ~(1 << i);
					if (mask)
						printf(", ");
				}
			}
		}
	} else	if (event->ext != NULL) {
		/* Handling extended feature interface */
		printf(": (ext: %s", event->ext);
	} else {
		/* Handling arch_perfmon case */
		printf(": (counter: all");
	}   

	printf(")\n\t");
	column = 8;
	word_wrap(8, &column, event->desc);
	snprintf(buf, sizeof buf, "(min count: %d)", event->min_count);
	word_wrap(8, &column, buf);
	putchar('\n');

	if (strcmp(event->unit->name, "zero")) {

		printf("\tUnit masks (default 0x%x)\n",
		       event->unit->default_mask);
		printf("\t----------\n");

		for (j = 0; j < event->unit->num; j++) {
			printf("\t0x%.2x: ",
			       event->unit->um[j].value);
			column = 14;
			word_wrap(14, &column, event->unit->um[j].desc);
			putchar('\n');
		}
	}
}


static void check_event(struct parsed_event * pev,
			struct op_event const * event)
{
	int ret;
	int min_count;
	int const callgraph_min_count_scale = 15;

	if (!event) {
		event = find_event_by_name(pev->name, 0, 0);
		if (event)
			fprintf(stderr, "Invalid unit mask %x for event %s\n",
				pev->unit_mask, pev->name);
		else
			fprintf(stderr, "No event named %s is available.\n",
				pev->name);
		exit(EXIT_FAILURE);
	}

	ret = op_check_events(0, event->val, pev->unit_mask, cpu_type);

	if (ret & OP_INVALID_UM) {
		fprintf(stderr, "Invalid unit mask 0x%x for event %s\n",
		        pev->unit_mask, pev->name);
		exit(EXIT_FAILURE);
	}

	min_count = event->min_count;
	if (callgraph_depth)
		min_count *= callgraph_min_count_scale;
	if (pev->count < min_count) {
		fprintf(stderr, "Count %d for event %s is below the "
		        "minimum %d\n", pev->count, pev->name, min_count);
		exit(EXIT_FAILURE);
	}
}


static void resolve_events(void)
{
	size_t count, count_events;
	size_t i, j;
	size_t * counter_map;
	size_t nr_counters = op_get_nr_counters(cpu_type);
	struct op_event const * selected_events[num_chosen_events];

	count = parse_events(parsed_events, num_chosen_events, chosen_events);

	for (i = 0; i < count; ++i) {
		for (j = i + 1; j < count; ++j) {
			struct parsed_event * pev1 = &parsed_events[i];
			struct parsed_event * pev2 = &parsed_events[j];

			if (!strcmp(pev1->name, pev2->name) &&
			    pev1->count == pev2->count &&
			    pev1->unit_mask == pev2->unit_mask &&
			    pev1->kernel == pev2->kernel &&
			    pev1->user == pev2->user) {
				fprintf(stderr, "All events must be distinct.\n");
				exit(EXIT_FAILURE);
			}
		}
	}

	for (i = 0, count_events = 0; i < count; ++i) {
		struct parsed_event * pev = &parsed_events[i];

		/* For 0 unit mask always do wild card match */
		selected_events[i] = find_event_by_name(pev->name, pev->unit_mask,
					pev->unit_mask ? pev->unit_mask_valid : 0);
		check_event(pev, selected_events[i]);

		if (selected_events[i]->ext == NULL) {
			count_events++;
		}
	}
	if (count_events > nr_counters) {
		fprintf(stderr, "Not enough hardware counters. "
				"Need %lu counters but only has %lu.\n",
				(unsigned long) count_events,
				(unsigned long) nr_counters);
		exit(EXIT_FAILURE);
	}

	counter_map = map_event_to_counter(selected_events, count, cpu_type);

	if (!counter_map) {
		fprintf(stderr, "Couldn't allocate hardware counters for the selected events.\n");
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < count; ++i)
		if(counter_map[i] == (size_t)-1)
			if (selected_events[i]->ext != NULL)
				printf("%s ", (char*) selected_events[i]->ext);
			else
				printf("N/A ");
		else
			printf("%d ", (unsigned int) counter_map[i]);
	printf("\n");

	free(counter_map);
}


static void show_unit_mask(void)
{
	struct op_event * event;
	size_t count;

	count = parse_events(parsed_events, num_chosen_events, chosen_events);
	if (count > 1) {
		fprintf(stderr, "More than one event specified.\n");
		exit(EXIT_FAILURE);
	}

	event = find_event_by_name(parsed_events[0].name, 0, 0);

	if (!event) {
		fprintf(stderr, "No such event found.\n");
		exit(EXIT_FAILURE);
	}

	printf("%d\n", event->unit->default_mask);
}


static void show_default_event(void)
{
	struct op_default_event_descr descr;

	op_default_event(cpu_type, &descr);

	if (descr.name[0] == '\0')
		return;

	printf("%s:%lu:%lu:1:1\n", descr.name, descr.count, descr.um);
}


static int show_vers;
static int get_cpu_type;
static int check_events;
static int unit_mask;
static int get_default_event;

static struct poptOption options[] = {
	{ "cpu-type", 'c', POPT_ARG_STRING, &cpu_string, 0,
	  "use the given CPU type", "cpu type", },
	{ "check-events", 'e', POPT_ARG_NONE, &check_events, 0,
	  "check the given event descriptions for validity", NULL, },
	{ "unit-mask", 'u', POPT_ARG_NONE, &unit_mask, 0,
	  "default unit mask for the given event", NULL, },
	{ "get-cpu-type", 'r', POPT_ARG_NONE, &get_cpu_type, 0,
	  "show the auto-detected CPU type", NULL, },
	{ "get-default-event", 'd', POPT_ARG_NONE, &get_default_event, 0,
	  "get the default event", NULL, },
	{ "callgraph", '\0', POPT_ARG_INT, &callgraph_depth, 0,
	  "use this callgraph depth", "callgraph depth", },
	{ "version", 'v', POPT_ARG_NONE, &show_vers, 0,
	   "show version", NULL, },
	{ "xml", 'X', POPT_ARG_NONE, &want_xml, 0,
	   "list events as XML", NULL, },
	POPT_AUTOHELP
	{ NULL, 0, 0, NULL, 0, NULL, NULL, },
};

/**
 * get_options - process command line
 * @param argc  program arg count
 * @param argv  program arg array
 *
 * Process the arguments, fatally complaining on error.
 */
static void get_options(int argc, char const * argv[])
{
	optcon = op_poptGetContext(NULL, argc, argv, options, 0);

	if (show_vers)
		show_version(argv[0]);

	/* non-option, must be a valid event name or event specs */
	chosen_events = poptGetArgs(optcon);

	if(chosen_events) {
		num_chosen_events = 0;
		while (chosen_events[num_chosen_events] != NULL)
			num_chosen_events++;
	}

	/* don't free the context now, we need chosen_events */
}


/** make valgrind happy */
static void cleanup(void)
{
	int i;
	if (parsed_events) {
		for (i = 0; i < num_chosen_events; ++i) {
			if (parsed_events[i].name)
				free(parsed_events[i].name);
		}
	}
	op_free_events();
	if (optcon)
		poptFreeContext(optcon);
	if (parsed_events)
		free(parsed_events);
}


#define MAX_LINE 256
int main(int argc, char const * argv[])
{
	struct list_head * events;
	struct list_head * pos;
	char const * pretty;
	char title[10 * MAX_LINE];
	char const * event_doc = "";

	atexit(cleanup);

	get_options(argc, argv);

	/* usefull for testing purpose to allow to force the cpu type
	 * with --cpu-type */
	if (cpu_string) {
		cpu_type = op_get_cpu_number(cpu_string);
	} else {
		cpu_type = op_get_cpu_type();
	}

	if (cpu_type == CPU_NO_GOOD) {
		fprintf(stderr, "cpu_type '%s' is not valid\n",
		        cpu_string ? cpu_string : "unset");
		fprintf(stderr, "you should upgrade oprofile or force the "
			"use of timer mode\n");
		exit(EXIT_FAILURE);
	}

	parsed_events = (struct parsed_event *)xcalloc(num_chosen_events,
		sizeof(struct parsed_event));

	pretty = op_get_cpu_type_str(cpu_type);

	if (get_cpu_type) {
		printf("%s\n", pretty);
		exit(EXIT_SUCCESS);
	}

	if (get_default_event) {
		show_default_event();
		exit(EXIT_SUCCESS);
	}

	if (cpu_type == CPU_TIMER_INT) {
		if (!check_events)
			printf("Using timer interrupt.\n");
		exit(EXIT_SUCCESS);
	}

	events = op_events(cpu_type);

	if (!chosen_events && (unit_mask || check_events)) {
		fprintf(stderr, "No events given.\n");
		exit(EXIT_FAILURE);
	}

	if (unit_mask) {
		show_unit_mask();
		exit(EXIT_SUCCESS);
	}

	if (check_events) {
		resolve_events();
		exit(EXIT_SUCCESS);
	}

	/* without --check-events, the only argument must be an event name */
	if (chosen_events && chosen_events[0]) {
		if (chosen_events[1]) {
			fprintf(stderr, "Too many arguments.\n");
			exit(EXIT_FAILURE);
		}

		list_for_each(pos, events) {
			struct op_event * event = list_entry(pos, struct op_event, event_next);

			if (strcmp(event->name, chosen_events[0]) == 0) {
				char const * map = find_mapping_for_event(event->val, cpu_type);
				if (map) {
					printf("%d %s\n", event->val, map);
				} else {
					printf("%d\n", event->val);
				}
				exit(EXIT_SUCCESS);
			}
		}
		fprintf(stderr, "No such event \"%s\"\n", chosen_events[0]);
		exit(EXIT_FAILURE);
	}

	/* default: list all events */

	switch (cpu_type) {
	case CPU_HAMMER:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Athlon and AMD Opteron Processors\n"
			"(26094.pdf), Section 10.2\n\n";
		break;
	case CPU_FAMILY10:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Family 10h Processors\n"
			"(31116.pdf), Section 3.14\n\n";
		break;
	case CPU_FAMILY11H:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Family 11h Processors\n"
			"(41256.pdf), Section 3.14\n\n";
		break;
	case CPU_FAMILY12H:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Family 12h Processors\n";
		break;
	case CPU_FAMILY14H:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Family 14h Processors\n";
		break;
	case CPU_FAMILY15H:
		event_doc =
			"See BIOS and Kernel Developer's Guide for AMD Family 15h Processors\n";
		break;
	case CPU_ATHLON:
		event_doc =
			"See AMD Athlon Processor x86 Code Optimization Guide\n"
			"(22007.pdf), Appendix D\n\n";
		break;
	case CPU_PPRO:
	case CPU_PII:
	case CPU_PIII:
	case CPU_P6_MOBILE:
	case CPU_P4:
	case CPU_P4_HT2:
	case CPU_CORE:
	case CPU_CORE_2:
	case CPU_CORE_I7:
	case CPU_NEHALEM:
	case CPU_WESTMERE:
	case CPU_ATOM:
		event_doc =
			"See Intel Architecture Developer's Manual Volume 3B, Appendix A and\n"
			"Intel Architecture Optimization Reference Manual (730795-001)\n\n";
		break;

	case CPU_ARCH_PERFMON:
		event_doc =
			"See Intel 64 and IA-32 Architectures Software Developer's Manual\n"
			"Volume 3B (Document 253669) Chapter 18 for architectural perfmon events\n"
			"This is a limited set of fallback events because oprofile doesn't know your CPU\n";
		break;
	
	case CPU_IA64:
	case CPU_IA64_1:
	case CPU_IA64_2:
		event_doc =
			"See Intel Itanium Processor Reference Manual\n"
			"for Software Development (Document 245320-003),\n"
			"Intel Itanium Processor Reference Manual\n"
			"for Software Optimization (Document 245473-003),\n"
			"Intel Itanium 2 Processor Reference Manual\n"
			"for Software Development and Optimization (Document 251110-001)\n\n";
		break;
	case CPU_AXP_EV4:
	case CPU_AXP_EV5:
	case CPU_AXP_PCA56:
	case CPU_AXP_EV6:
	case CPU_AXP_EV67:
		event_doc =
			"See Alpha Architecture Reference Manual\n"
			"http://download.majix.org/dec/alpha_arch_ref.pdf\n";
		break;
	case CPU_ARM_XSCALE1:
	case CPU_ARM_XSCALE2:
		event_doc =
			"See Intel XScale Core Developer's Manual\n"
			"Chapter 8 Performance Monitoring\n";
		break;
	case CPU_ARM_MPCORE:
		event_doc =
			"See ARM11 MPCore Processor Technical Reference Manual r1p0\n"
			"Page 3-70, performance counters\n";
		break;

	case CPU_ARM_V6:
		event_doc = "See ARM11 Technical Reference Manual\n";
  		break;

	case CPU_ARM_V7:
		event_doc =
			"See Cortex-A8 Technical Reference Manual\n"
			"Cortex A8 DDI (ARM DDI 0344B, revision r1p1)\n";
		break;

	case CPU_ARM_V7_CA9:
		event_doc =
			"See Cortex-A9 Technical Reference Manual\n"
			"Cortex A9 DDI (ARM DDI 0388E, revision r2p0)\n";
		break;

	case CPU_PPC64_PA6T:
		event_doc =
			"See PA6T Power Implementation Features Book IV\n"
			"Chapter 7 Performance Counters\n";
		break;

	case CPU_PPC64_POWER4:
	case CPU_PPC64_POWER5:
	case CPU_PPC64_POWER6:
	case CPU_PPC64_POWER5p:
	case CPU_PPC64_POWER5pp:
	case CPU_PPC64_970:
	case CPU_PPC64_970MP:
	case CPU_PPC64_POWER7:
	case CPU_PPC64_IBM_COMPAT_V1:
		event_doc =
			"Obtain PowerPC64 processor documentation at:\n"
			"http://www-306.ibm.com/chips/techlib/techlib.nsf/productfamilies/PowerPC\n";
		break;

	case CPU_PPC64_CELL:
		event_doc =
			"Obtain Cell Broadband Engine documentation at:\n"
			"http://www-306.ibm.com/chips/techlib/techlib.nsf/products/Cell_Broadband_Engine\n";
		break;

	case CPU_MIPS_20K:
		event_doc =
			"See Programming the MIPS64 20Kc Processor Core User's "
		"manual available from www.mips.com\n";
		break;
	case CPU_MIPS_24K:
		event_doc =
			"See Programming the MIPS32 24K Core "
			"available from www.mips.com\n";
		break;
	case CPU_MIPS_25K:
		event_doc =
			"See Programming the MIPS64 25Kf Processor Core User's "
			"manual available from www.mips.com\n";
		break;
	case CPU_MIPS_34K:
		event_doc =
			"See Programming the MIPS32 34K Core Family "
			"available from www.mips.com\n";
		break;
	case CPU_MIPS_74K:
		event_doc =
			"See Programming the MIPS32 74K Core Family "
			"available from www.mips.com\n";
		break;
	case CPU_MIPS_1004K:
		event_doc =
			"See Programming the MIPS32 1004K Core Family "
			"available from www.mips.com\n";
		break;
	case CPU_MIPS_5K:
		event_doc =
			"See Programming the MIPS64 5K Processor Core Family "
			"Software User's manual available from www.mips.com\n";
		break;
	case CPU_MIPS_R10000:
	case CPU_MIPS_R12000:
		event_doc =
			"See NEC R10000 / R12000 User's Manual\n"
			"http://www.necelam.com/docs/files/U10278EJ3V0UM00.pdf\n";
		break;
	case CPU_MIPS_RM7000:
		event_doc =
			"See RM7000 Family User Manual "
			"available from www.pmc-sierra.com\n";
		break;
	case CPU_MIPS_RM9000:
		event_doc =
			"See RM9000x2 Family User Manual "
			"available from www.pmc-sierra.com\n";
		break;
	case CPU_MIPS_SB1:
	case CPU_MIPS_VR5432:
		event_doc =
			"See NEC VR5443 User's Manual, Volume 1\n"
			"http://www.necelam.com/docs/files/1375_V1.pdf\n";
		break;
	case CPU_MIPS_VR5500:
		event_doc =
			"See NEC R10000 / R12000 User's Manual\n"
			"http://www.necel.com/nesdis/image/U16677EJ3V0UM00.pdf\n";
		break;

	case CPU_MIPS_LOONGSON2:
		event_doc = 
			"See loongson2 RISC Microprocessor Family Reference Manual\n";
		break;

	case CPU_PPC_E500:
	case CPU_PPC_E500_2:
		event_doc =
			"See PowerPC e500 Core Complex Reference Manual\n"
			"Chapter 7: Performance Monitor\n"
			"Downloadable from http://www.freescale.com\n";
		break;

	case CPU_PPC_E300:
		event_doc =
			"See PowerPC e300 Core Reference Manual\n"
			"Downloadable from http://www.freescale.com\n";
		break;

	case CPU_PPC_7450:
		event_doc =
			"See MPC7450 RISC Microprocessor Family Reference "
			"Manual\n"
			"Chapter 11: Performance Monitor\n"
			"Downloadable from http://www.freescale.com\n";
		break;

	case CPU_AVR32:
		event_doc =
			"See AVR32 Architecture Manual\n"
			"Chapter 6: Performance Counters\n"
			"http://www.atmel.com/dyn/resources/prod_documents/doc32000.pdf\n";

		case CPU_RTC:
			break;

		// don't use default, if someone add a cpu he wants a compiler warning
		// if he forgets to handle it here.
		case CPU_TIMER_INT:
		case CPU_NO_GOOD:
		case MAX_CPU_TYPE:
			printf("%d is not a valid processor type.\n", cpu_type);
			exit(EXIT_FAILURE);
	}

	sprintf(title, "oprofile: available events for CPU type \"%s\"\n\n", pretty);
	if (want_xml)
		open_xml_events(title, event_doc, cpu_type);
	else
		printf("%s%s", title, event_doc);

	list_for_each(pos, events) {
		struct op_event * event = list_entry(pos, struct op_event, event_next);
		if (want_xml) 
			xml_help_for_event(event);
		else
			help_for_event(event);
	}

	if (want_xml)
		close_xml_events();

	return EXIT_SUCCESS;
}