/*
 * pfmlib_intel_nhm_unc.c : Intel Nehalem/Westmere uncore PMU
 *
 * Copyright (c) 2008 Google, Inc
 * Contributed by Stephane Eranian <eranian@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/* private headers */
#include "pfmlib_priv.h"
#include "pfmlib_intel_x86_priv.h"

#define NHM_UNC_ATTR_E	0
#define NHM_UNC_ATTR_I	1
#define NHM_UNC_ATTR_C	2
#define NHM_UNC_ATTR_O	3

#define _NHM_UNC_ATTR_I  (1 << NHM_UNC_ATTR_I)
#define _NHM_UNC_ATTR_E  (1 << NHM_UNC_ATTR_E)
#define _NHM_UNC_ATTR_C  (1 << NHM_UNC_ATTR_C)
#define _NHM_UNC_ATTR_O  (1 << NHM_UNC_ATTR_O)

#define NHM_UNC_ATTRS \
	(_NHM_UNC_ATTR_I|_NHM_UNC_ATTR_E|_NHM_UNC_ATTR_C|_NHM_UNC_ATTR_O)

#define NHM_UNC_MOD_OCC_BIT 	17
#define NHM_UNC_MOD_EDGE_BIT	18
#define NHM_UNC_MOD_INV_BIT	23
#define NHM_UNC_MOD_CMASK_BIT	24

#define NHM_UNC_MOD_OCC		(1 << NHM_UNC_MOD_OCC_BIT)
#define NHM_UNC_MOD_EDGE	(1 << NHM_UNC_MOD_EDGE_BIT)
#define NHM_UNC_MOD_INV		(1 << NHM_UNC_MOD_INV_BIT)

/* Intel Nehalem/Westmere uncore event table */
#include "events/intel_nhm_unc_events.h"
#include "events/intel_wsm_unc_events.h"

static const pfmlib_attr_desc_t nhm_unc_mods[]={
	PFM_ATTR_B("e", "edge level"),				/* edge */
	PFM_ATTR_B("i", "invert"),				/* invert */
	PFM_ATTR_I("c", "counter-mask in range [0-255]"),	/* counter-mask */
	PFM_ATTR_B("o", "queue occupancy"),			/* queue occupancy */
	PFM_ATTR_NULL
};

static int
pfm_nhm_unc_detect(void *this)
{
	int ret;

	ret = pfm_intel_x86_detect();
	if (ret != PFM_SUCCESS)

	if (pfm_intel_x86_cfg.family != 6)
		return PFM_ERR_NOTSUPP;

	switch(pfm_intel_x86_cfg.model) {
		case 26: /* Nehalem */
		case 30:
		case 31:
			  break;
		default:
			return PFM_ERR_NOTSUPP;
	}
	return PFM_SUCCESS;
}

static int
pfm_wsm_unc_detect(void *this)
{
	int ret;

	ret = pfm_intel_x86_detect();
	if (ret != PFM_SUCCESS)
		return ret;

	if (pfm_intel_x86_cfg.family != 6)
		return PFM_ERR_NOTSUPP;

	switch (pfm_intel_x86_cfg.model) {
		case 37: /* Westmere */
		case 44:
			  break;
		default:
			return PFM_ERR_NOTSUPP;
	}
	return PFM_SUCCESS;
}

static int
pfm_nhm_unc_get_encoding(void *this, pfmlib_event_desc_t *e)
{
	pfm_intel_x86_reg_t reg;
	pfm_event_attr_info_t *a;
	const intel_x86_entry_t *pe = this_pe(this);
	unsigned int grpmsk, ugrpmsk = 0;
	int umodmsk = 0, modmsk_r = 0;
	uint64_t val;
	uint64_t umask;
	unsigned int modhw = 0;
	int k, ret, grpid, last_grpid = -1;
	int grpcounts[INTEL_X86_NUM_GRP];
	int ncombo[INTEL_X86_NUM_GRP];
	char umask_str[PFMLIB_EVT_MAX_NAME_LEN];

	memset(grpcounts, 0, sizeof(grpcounts));
	memset(ncombo, 0, sizeof(ncombo));

	pe = this_pe(this);
	umask_str[0] = e->fstr[0] = '\0';

	reg.val = 0;

	val = pe[e->event].code;

	grpmsk = (1 << pe[e->event].ngrp)-1;
	reg.val |= val; /* preset some filters from code */

	/* take into account hardcoded umask */
	umask = (val >> 8) & 0xff;

	modmsk_r = pe[e->event].modmsk_req;

	for(k=0; k < e->nattrs; k++) {
		a = attr(e, k);

		if (a->ctrl != PFM_ATTR_CTRL_PMU)
			continue;

		if (a->type == PFM_ATTR_UMASK) {
			grpid = pe[e->event].umasks[a->idx].grpid;

			/*
			 * cfor certain events groups are meant to be
			 * exclusive, i.e., only unit masks of one group
			 * can be used
			 */
			if (last_grpid != -1 && grpid != last_grpid
			    && intel_x86_eflag(this, e->event, INTEL_X86_GRP_EXCL)) {
				DPRINT("exclusive unit mask group error\n");
				return PFM_ERR_FEATCOMB;
			}
			/*
			 * upper layer has removed duplicates
			 * so if we come here more than once, it is for two
			 * disinct umasks
			 *
			 * NCOMBO=no combination of unit masks within the same
			 * umask group
			 */
			++grpcounts[grpid];

			if (intel_x86_uflag(this, e->event, a->idx, INTEL_X86_NCOMBO))
				ncombo[grpid] = 1;

			if (grpcounts[grpid] > 1 && ncombo[grpid])  {
				DPRINT("event does not support unit mask combination within a group\n");
				return PFM_ERR_FEATCOMB;
			}

			evt_strcat(umask_str, ":%s", pe[e->event].umasks[a->idx].uname);

			last_grpid = grpid;
			modhw    |= pe[e->event].umasks[a->idx].modhw;
			umask    |= pe[e->event].umasks[a->idx].ucode >> 8;
			ugrpmsk  |= 1 << pe[e->event].umasks[a->idx].grpid;

			reg.val |= umask << 8;

			modmsk_r |= pe[e->event].umasks[a->idx].umodmsk_req;

		} else if (a->type == PFM_ATTR_RAW_UMASK) {

			/* there can only be one RAW_UMASK per event */

			/* sanity check */
			if (a->idx & ~0xff) {
				DPRINT("raw umask is 8-bit wide\n");
				return PFM_ERR_ATTR;
			}
			/* override umask */
			umask = a->idx & 0xff;
			ugrpmsk = grpmsk;
		} else {
			uint64_t ival = e->attrs[k].ival;
			switch(a->idx) {
				case NHM_UNC_ATTR_I: /* invert */
					reg.nhm_unc.usel_inv = !!ival;
					umodmsk |= _NHM_UNC_ATTR_I;
					break;
				case NHM_UNC_ATTR_E: /* edge */
					reg.nhm_unc.usel_edge = !!ival;
					umodmsk |= _NHM_UNC_ATTR_E;
					break;
				case NHM_UNC_ATTR_C: /* counter-mask */
					/* already forced, cannot overwrite */
					if (ival > 255)
						return PFM_ERR_INVAL;
					reg.nhm_unc.usel_cnt_mask = ival;
					umodmsk |= _NHM_UNC_ATTR_C;
					break;
				case NHM_UNC_ATTR_O: /* occupancy */
					reg.nhm_unc.usel_occ = !!ival;
					umodmsk |= _NHM_UNC_ATTR_O;
					break;
			}
		}
	}

	if ((modhw & _NHM_UNC_ATTR_I) && reg.nhm_unc.usel_inv)
		return PFM_ERR_ATTR_SET;
	if ((modhw & _NHM_UNC_ATTR_E) && reg.nhm_unc.usel_edge)
		return PFM_ERR_ATTR_SET;
	if ((modhw & _NHM_UNC_ATTR_C) && reg.nhm_unc.usel_cnt_mask)
		return PFM_ERR_ATTR_SET;
	if ((modhw & _NHM_UNC_ATTR_O) && reg.nhm_unc.usel_occ)
		return PFM_ERR_ATTR_SET;

	/*
	 * check that there is at least of unit mask in each unit
	 * mask group
	 */
	if ((ugrpmsk != grpmsk && !intel_x86_eflag(this, e->event, INTEL_X86_GRP_EXCL)) || ugrpmsk == 0) {
		ugrpmsk ^= grpmsk;
		ret = pfm_intel_x86_add_defaults(this, e, ugrpmsk, &umask, -1);
		if (ret != PFM_SUCCESS)
			return ret;
	}

	if (modmsk_r && (umodmsk ^ modmsk_r)) {
		DPRINT("required modifiers missing: 0x%x\n", modmsk_r);
		return PFM_ERR_ATTR;
	}

	evt_strcat(e->fstr, "%s", pe[e->event].name);
	pfmlib_sort_attr(e);
	for(k=0; k < e->nattrs; k++) {
		a = attr(e, k);
		if (a->ctrl != PFM_ATTR_CTRL_PMU)
			continue;
		if (a->type == PFM_ATTR_UMASK)
			evt_strcat(e->fstr, ":%s", pe[e->event].umasks[a->idx].uname);
		else if (a->type == PFM_ATTR_RAW_UMASK)
			evt_strcat(e->fstr, ":0x%x", a->idx);
	}

	reg.val |= umask << 8;

	reg.nhm_unc.usel_en    = 1; /* force enable bit to 1 */
	reg.nhm_unc.usel_int   = 1; /* force APIC int to 1 */

	e->codes[0] = reg.val;
	e->count = 1;

	for (k = 0; k < e->npattrs; k++) {
		int idx;

		if (e->pattrs[k].ctrl != PFM_ATTR_CTRL_PMU)
			continue;

		if (e->pattrs[k].type == PFM_ATTR_UMASK)
			continue;

		idx = e->pattrs[k].idx;
		switch(idx) {
		case NHM_UNC_ATTR_E:
			evt_strcat(e->fstr, ":%s=%lu", nhm_unc_mods[idx].name, reg.nhm_unc.usel_edge);
			break;
		case NHM_UNC_ATTR_I:
			evt_strcat(e->fstr, ":%s=%lu", nhm_unc_mods[idx].name, reg.nhm_unc.usel_inv);
			break;
		case NHM_UNC_ATTR_C:
			evt_strcat(e->fstr, ":%s=%lu", nhm_unc_mods[idx].name, reg.nhm_unc.usel_cnt_mask);
			break;
		case NHM_UNC_ATTR_O:
			evt_strcat(e->fstr, ":%s=%lu", nhm_unc_mods[idx].name, reg.nhm_unc.usel_occ);
			break;
		}
	}
	__pfm_vbprintf("[UNC_PERFEVTSEL=0x%"PRIx64" event=0x%x umask=0x%x en=%d int=%d inv=%d edge=%d occ=%d cnt_msk=%d] %s\n",
		reg.val,
		reg.nhm_unc.usel_event,
		reg.nhm_unc.usel_umask,
		reg.nhm_unc.usel_en,
		reg.nhm_unc.usel_int,
		reg.nhm_unc.usel_inv,
		reg.nhm_unc.usel_edge,
		reg.nhm_unc.usel_occ,
		reg.nhm_unc.usel_cnt_mask,
		pe[e->event].name);

	return PFM_SUCCESS;
}

pfmlib_pmu_t intel_nhm_unc_support={
	.desc			= "Intel Nehalem uncore",
	.name			= "nhm_unc",
	.perf_name		= "uncore",

	.pmu			= PFM_PMU_INTEL_NHM_UNC,
	.pme_count		= LIBPFM_ARRAY_SIZE(intel_nhm_unc_pe),
	.type			= PFM_PMU_TYPE_UNCORE,
	.num_cntrs		= 8,
	.num_fixed_cntrs	= 1,
	.max_encoding		= 1,
	.pe			= intel_nhm_unc_pe,
	.atdesc			= nhm_unc_mods,
	.flags			= PFMLIB_PMU_FL_RAW_UMASK,

	.pmu_detect		= pfm_nhm_unc_detect,

	.get_event_encoding[PFM_OS_NONE] = pfm_nhm_unc_get_encoding,
	 PFMLIB_ENCODE_PERF(pfm_intel_nhm_unc_get_perf_encoding),

	.get_event_first	= pfm_intel_x86_get_event_first,
	.get_event_next		= pfm_intel_x86_get_event_next,
	.event_is_valid		= pfm_intel_x86_event_is_valid,
	.validate_table		= pfm_intel_x86_validate_table,
	.get_event_info		= pfm_intel_x86_get_event_info,
	.get_event_attr_info	= pfm_intel_x86_get_event_attr_info,
	PFMLIB_VALID_PERF_PATTRS(pfm_intel_x86_perf_validate_pattrs),
	.get_event_nattrs	= pfm_intel_x86_get_event_nattrs,
};

pfmlib_pmu_t intel_wsm_unc_support={
	.desc			= "Intel Westmere uncore",
	.name			= "wsm_unc",
	.perf_name		= "uncore",

	.pmu			= PFM_PMU_INTEL_WSM_UNC,
	.pme_count		= LIBPFM_ARRAY_SIZE(intel_wsm_unc_pe),
	.type			= PFM_PMU_TYPE_UNCORE,
	.num_cntrs		= 8,
	.num_fixed_cntrs	= 1,
	.max_encoding		= 1,
	.pe			= intel_wsm_unc_pe,
	.atdesc			= nhm_unc_mods,
	.flags			= PFMLIB_PMU_FL_RAW_UMASK,

	.pmu_detect		= pfm_wsm_unc_detect,

	.get_event_encoding[PFM_OS_NONE] = pfm_nhm_unc_get_encoding,
	 PFMLIB_ENCODE_PERF(pfm_intel_nhm_unc_get_perf_encoding),

	.get_event_first	= pfm_intel_x86_get_event_first,
	.get_event_next		= pfm_intel_x86_get_event_next,
	.event_is_valid		= pfm_intel_x86_event_is_valid,
	.validate_table		= pfm_intel_x86_validate_table,
	.get_event_info		= pfm_intel_x86_get_event_info,
	.get_event_attr_info	= pfm_intel_x86_get_event_attr_info,
	PFMLIB_VALID_PERF_PATTRS(pfm_intel_x86_perf_validate_pattrs),
	.get_event_nattrs	= pfm_intel_x86_get_event_nattrs,
};
