OpenSolaris_b135/uts/sun4v/pcbe/niagara2_pcbe.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * This file contains preset event names from the Performance Application
 * Programming Interface v3.5 which included the following notice:
 *
 *                             Copyright (c) 2005,6
 *                           Innovative Computing Labs
 *                         Computer Science Department,
 *                            University of Tennessee,
 *                                 Knoxville, TN.
 *                              All Rights Reserved.
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    * Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the name of the University of Tennessee nor the names of its
 *      contributors may be used to endorse or promote products derived from
 *      this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * This open source software license conforms to the BSD License template.
 */

/*
 * Niagara2 Performance Counter Backend
 */

#include <sys/cpuvar.h>
#include <sys/systm.h>
#include <sys/archsystm.h>
#include <sys/cmn_err.h>
#include <sys/cpc_impl.h>
#include <sys/cpc_pcbe.h>
#include <sys/modctl.h>
#include <sys/machsystm.h>
#include <sys/sdt.h>
#include <sys/niagara2regs.h>
#include <sys/hsvc.h>
#include <sys/hypervisor_api.h>
#include <sys/disp.h>

/*LINTLIBRARY*/
static int ni2_pcbe_init(void);
static uint_t ni2_pcbe_ncounters(void);
static const char *ni2_pcbe_impl_name(void);
static const char *ni2_pcbe_cpuref(void);
static char *ni2_pcbe_list_events(uint_t picnum);
static char *ni2_pcbe_list_attrs(void);
static uint64_t ni2_pcbe_event_coverage(char *event);
static uint64_t ni2_pcbe_overflow_bitmap(void);
static int ni2_pcbe_configure(uint_t picnum, char *event, uint64_t preset,
    uint32_t flags, uint_t nattrs, kcpc_attr_t *attrs, void **data,
    void *token);
static void ni2_pcbe_program(void *token);
static void ni2_pcbe_allstop(void);
static void ni2_pcbe_sample(void *token);
static void ni2_pcbe_free(void *config);

extern void ultra_setpcr(uint64_t);
extern uint64_t ultra_getpcr(void);
extern void ultra_setpic(uint64_t);
extern uint64_t ultra_getpic(void);
extern uint64_t ultra_gettick(void);
extern char cpu_module_name[];

pcbe_ops_t ni2_pcbe_ops = {
	PCBE_VER_1,
	CPC_CAP_OVERFLOW_INTERRUPT | CPC_CAP_OVERFLOW_PRECISE,
	ni2_pcbe_ncounters,
	ni2_pcbe_impl_name,
	ni2_pcbe_cpuref,
	ni2_pcbe_list_events,
	ni2_pcbe_list_attrs,
	ni2_pcbe_event_coverage,
	ni2_pcbe_overflow_bitmap,
	ni2_pcbe_configure,
	ni2_pcbe_program,
	ni2_pcbe_allstop,
	ni2_pcbe_sample,
	ni2_pcbe_free
};

typedef struct _ni2_pcbe_config {
	uint_t		pcbe_picno;	/* 0 for pic0 or 1 for pic1 */
	uint32_t	pcbe_evsel;	/* %pcr event code unshifted */
	uint32_t	pcbe_flags;	/* hpriv/user/system/priv */
	uint32_t	pcbe_pic;	/* unshifted raw %pic value */
} ni2_pcbe_config_t;

typedef struct _ni2_event {
	const char	*name;
	const uint32_t	emask;
	const uint32_t	emask_valid;	/* Mask of unreserved MASK bits */
} ni2_event_t;

typedef struct _ni2_generic_event {
	char *name;
	char *event;
} ni2_generic_event_t;

#define	ULTRA_PCR_PRIVPIC	(UINT64_C(1) << CPC_PCR_PRIV_SHIFT)
#define	EV_END {NULL, 0, 0}
#define	GEN_EV_END {NULL, NULL}

static const uint64_t	allstopped = (ULTRA_PCR_PRIVPIC |
	CPC_PCR_HOLDOV0 | CPC_PCR_HOLDOV1);

/*
 * We update this array in the program and allstop routine. The array
 * is checked in the sample routine to allow us to only perform the
 * PCR.ht bit check when counting is in progress.
 */
static boolean_t ni2_cpc_counting[NCPU];

static ni2_event_t ni2_events[] = {
	{ "Idle_strands",			0x000, 0x00 },
	{ "Br_completed",			0x201, 0xff },
	{ "Br_taken",				0x202, 0xff },
	{ "Instr_FGU_arithmetic",		0x204, 0xff },
	{ "Instr_ld",				0x208, 0xff },
	{ "Instr_st",				0x210, 0xff },
	{ "Instr_sw",				0x220, 0xff },
	{ "Instr_other",			0x240, 0xff },
	{ "Atomics",				0x280, 0xff },
	{ "Instr_cnt",				0x2fd, 0xff },
	{ "IC_miss",				0x301, 0x33 },
	{ "DC_miss",				0x302, 0x33 },
	{ "L2_imiss",				0x310, 0x33 },
	{ "L2_dmiss_ld",			0x320, 0x33 },
	{ "ITLB_HWTW_ref_L2",			0x404, 0x3c },
	{ "DTLB_HWTW_ref_L2",			0x408, 0x3c },
	{ "ITLB_HWTW_miss_L2",			0x410, 0x3c },
	{ "DTLB_HWTW_miss_L2",			0x420, 0x3c },
	{ "Stream_ld_to_PCX",			0x501, 0x3f },
	{ "Stream_st_to_PCX",			0x502, 0x3f },
	{ "CPU_ld_to_PCX",			0x504, 0x3f },
	{ "CPU_ifetch_to_PCX",			0x508, 0x3f },
	{ "CPU_st_to_PCX",			0x510, 0x3f },
	{ "MMU_ld_to_PCX",			0x520, 0x3f },
#ifdef KT_IMPL
	{ "DES_3DES_op",			0x601, 0xff },
	{ "AES_op",				0x602, 0xff },
	{ "Kasumi_op",				0x604, 0xff },
	{ "MD5_SHA-1_SHA-256_op",		0x608, 0xff },
	{ "MA_op",				0x610, 0xff },
	{ "CRC_TCPIP_cksum",			0x620, 0xff },
	{ "DES_3DES_busy_cycle",		0x701, 0xff },
	{ "AES_busy_cycle",			0x702, 0xff },
	{ "Kasumi_busy_cycle",			0x704, 0xff },
	{ "MD5_SHA-1_SHA-256_busy_cycle",	0x708, 0xff },
	{ "MA_busy_cycle",			0x710, 0xff },
	{ "CRC_MPA_cksum",			0x720, 0xff },
#else
	{ "DES_3DES_op",			0x601, 0x3f },
	{ "AES_op",				0x602, 0x3f },
	{ "RC4_op",				0x604, 0x3f },
	{ "MD5_SHA-1_SHA-256_op",		0x608, 0x3f },
	{ "MA_op",				0x610, 0x3f },
	{ "CRC_TCPIP_cksum",			0x620, 0x3f },
	{ "DES_3DES_busy_cycle",		0x701, 0x3f },
	{ "AES_busy_cycle",			0x702, 0x3f },
	{ "RC4_busy_cycle",			0x704, 0x3f },
	{ "MD5_SHA-1_SHA-256_busy_cycle",	0x708, 0x3f },
	{ "MA_busy_cycle",			0x710, 0x3f },
	{ "CRC_MPA_cksum",			0x720, 0x3f },
#endif
	{ "ITLB_miss",				0xb04, 0x0c },
	{ "DTLB_miss",				0xb08, 0x0c },
	{ "TLB_miss",				0xb0c, 0x0c },
	EV_END
};

static ni2_generic_event_t ni2_generic_events[] = {
	{ "PAPI_tot_ins",	"Instr_cnt" },
	{ "PAPI_l1_dcm",	"DC_miss" },
	{ "PAPI_l1_icm",	"IC_miss" },
	{ "PAPI_l2_icm",	"L2_imiss" },
	{ "PAPI_l2_ldm",	"L2_dmiss_ld" },
	{ "PAPI_tlb_dm",	"DTLB_miss" },
	{ "PAPI_tlb_im",	"ITLB_miss" },
	{ "PAPI_tlb_tm",	"TLB_miss" },
	{ "PAPI_br_tkn",	"Br_taken" },
	{ "PAPI_br_ins",	"Br_completed" },
	{ "PAPI_ld_ins",	"Instr_ld" },
	{ "PAPI_sr_ins",	"Instr_st" },
	{ "PAPI_fp_ops",	"Instr_FGU_arithmetic" },
	{ "PAPI_fp_ins",	"Instr_FGU_arithmetic" },
	GEN_EV_END
};

static char		*evlist;
static size_t		evlist_sz;
static uint16_t 	pcr_pic0_mask;
static uint16_t 	pcr_pic1_mask;

#define	CPU_REF_URL " Documentation for Sun processors can be found at: " \
			"http://www.sun.com/processors/manuals"

#if defined(NIAGARA2_IMPL)
static const char	*cpu_impl_name = "UltraSPARC T2";
static const char *cpu_pcbe_ref = "See the \"UltraSPARC T2 User's Manual\" "
			"for descriptions of these events." CPU_REF_URL;
#elif defined(VFALLS_IMPL)
static const char	*cpu_impl_name = "UltraSPARC T2+";
static const char *cpu_pcbe_ref = "See the \"UltraSPARC T2+ User's Manual\" "
			"for descriptions of these events." CPU_REF_URL;
#elif defined(KT_IMPL)
static const char	*cpu_impl_name = "UltraSPARC T3";
static const char *cpu_pcbe_ref = "See the \"UltraSPARC T3 User's Manual\" "
			"for descriptions of these events." CPU_REF_URL;
#endif

static boolean_t cpu_hsvc_available = B_TRUE;

static int
ni2_pcbe_init(void)
{
	ni2_event_t		*evp;
	ni2_generic_event_t	*gevp;
	int			status;
	uint64_t		cpu_hsvc_major;
	uint64_t		cpu_hsvc_minor;
#if defined(NIAGARA2_IMPL)
	uint64_t		hsvc_cpu_group = HSVC_GROUP_NIAGARA2_CPU;
	uint64_t		hsvc_cpu_major = NIAGARA2_HSVC_MAJOR;
#elif defined(VFALLS_IMPL)
	uint64_t		hsvc_cpu_group = HSVC_GROUP_VFALLS_CPU;
	uint64_t		hsvc_cpu_major = VFALLS_HSVC_MAJOR;
#elif defined(KT_IMPL)
	uint64_t	hsvc_cpu_group = HSVC_GROUP_KT_CPU;
	uint64_t	hsvc_cpu_major = KT_HSVC_MAJOR;
#endif

	pcr_pic0_mask = CPC_PCR_PIC0_MASK;
	pcr_pic1_mask = CPC_PCR_PIC1_MASK;

	/*
	 * Validate API version for Niagara2 specific hypervisor services
	 */
	status = hsvc_version(hsvc_cpu_group, &cpu_hsvc_major,
	    &cpu_hsvc_minor);
	if ((status != 0) || (cpu_hsvc_major != hsvc_cpu_major)) {
		cmn_err(CE_WARN, "hypervisor services not negotiated "
		    "or unsupported major number: group: 0x%lx major: 0x%lx "
		    "minor: 0x%lx errno: %d", hsvc_cpu_group,
		    cpu_hsvc_major, cpu_hsvc_minor, status);
		cpu_hsvc_available = B_FALSE;
	}

	/*
	 * Construct event list.
	 *
	 * First pass:  Calculate size needed. We'll need an additional byte
	 *		for the NULL pointer during the last strcat.
	 *
	 * Second pass: Copy strings.
	 */
	for (evp = ni2_events; evp->name != NULL; evp++)
		evlist_sz += strlen(evp->name) + 1;

	for (gevp = ni2_generic_events; gevp->name != NULL; gevp++)
		evlist_sz += strlen(gevp->name) + 1;

	evlist = kmem_alloc(evlist_sz + 1, KM_SLEEP);
	evlist[0] = '\0';

	for (evp = ni2_events; evp->name != NULL; evp++) {
		(void) strcat(evlist, evp->name);
		(void) strcat(evlist, ",");
	}

	for (gevp = ni2_generic_events; gevp->name != NULL; gevp++) {
		(void) strcat(evlist, gevp->name);
		(void) strcat(evlist, ",");
	}

	/*
	 * Remove trailing comma.
	 */
	evlist[evlist_sz - 1] = '\0';

	return (0);
}

static uint_t
ni2_pcbe_ncounters(void)
{
	return (2);
}

static const char *
ni2_pcbe_impl_name(void)
{
	return (cpu_impl_name);
}

static const char *
ni2_pcbe_cpuref(void)
{
	return (cpu_pcbe_ref);
}

static char *
ni2_pcbe_list_events(uint_t picnum)
{
	ASSERT(picnum < cpc_ncounters);

	return (evlist);
}

static char *
ni2_pcbe_list_attrs(void)
{
	if (cpu_hsvc_available == B_TRUE)
#if defined(NIAGARA2_IMPL)
		return ("hpriv,emask");
#elif defined(VFALLS_IMPL)
		return ("hpriv,l2ctl,emask");
#elif defined(KT_IMPL)
		return ("hpriv,l2ctl,emask,sample");
#endif
	else
#if defined(KT_IMPL)
		return ("emask,sample");
#else
		return ("emask");
#endif
}

static ni2_generic_event_t *
find_generic_event(char *name)
{
	ni2_generic_event_t	*gevp;

	for (gevp = ni2_generic_events; gevp->name != NULL; gevp++) {
		if (strcmp(name, gevp->name) == 0)
			return (gevp);
	}

	return (NULL);
}

static ni2_event_t *
find_event(char *name)
{
	ni2_event_t		*evp;

	for (evp = ni2_events; evp->name != NULL; evp++)
		if (strcmp(name, evp->name) == 0)
			return (evp);

	return (NULL);
}

/*ARGSUSED*/
static uint64_t
ni2_pcbe_event_coverage(char *event)
{
	/*
	 * Check whether counter event is supported
	 */
	if (find_event(event) == NULL && find_generic_event(event) == NULL)
		return (0);

	/*
	 * Fortunately, both pic0 and pic1 can count all events.
	 */
	return (0x3);
}

static uint64_t
ni2_pcbe_overflow_bitmap(void)
{
	uint64_t	pcr, overflow;
	uint64_t	pic;
	uint32_t	pic0, pic1;
	boolean_t	update_pic = B_FALSE;
	boolean_t	pic_inrange = B_FALSE;

	ASSERT(getpil() >= DISP_LEVEL);
	pcr = ultra_getpcr();
	DTRACE_PROBE1(niagara2__getpcr, uint64_t, pcr);
	overflow =  (pcr & CPC_PCR_OV0_MASK) >>
	    CPC_PCR_OV0_SHIFT;
	overflow |=  (pcr & CPC_PCR_OV1_MASK) >>
	    CPC_PCR_OV1_SHIFT;

	pic = ultra_getpic();
	pic0 = (uint32_t)(pic & PIC0_MASK);
	pic1 = (uint32_t)((pic >> PIC1_SHIFT) & PIC0_MASK);

	pcr |= (CPC_PCR_HOLDOV0 | CPC_PCR_HOLDOV1);

	if (overflow & 0x1) {
		pcr &= ~(CPC_PCR_OV0_MASK |
		    CPC_PCR_HOLDOV0);
		pic_inrange = PIC_IN_OV_RANGE(pic0);
#if defined(KT_IMPL)
		if (pcr & CPC_PCR_SAMPLE_MODE_MASK)
			pic_inrange = SAMPLE_PIC_IN_OV_RANGE(pic0);
#endif
		if (pic_inrange) {
			pic0 = 0;
			update_pic = B_TRUE;
		}
	}

	if (overflow & 0x2) {
		pcr &= ~(CPC_PCR_OV1_MASK |
		    CPC_PCR_HOLDOV1);
		pic_inrange = PIC_IN_OV_RANGE(pic1);
#if defined(KT_IMPL)
		if (pcr & CPC_PCR_SAMPLE_MODE_MASK)
			pic_inrange = SAMPLE_PIC_IN_OV_RANGE(pic1);
#endif
		if (pic_inrange) {
			pic1 = 0;
			update_pic = B_TRUE;
		}
	}

	if (update_pic)
		ultra_setpic(((uint64_t)pic1 << PIC1_SHIFT) | pic0);

	/*
	 * The HV interface does not need to be used here because we are
	 * only resetting the OV bits and do not need to set the HT bit.
	 */
	DTRACE_PROBE1(niagara2__setpcr, uint64_t, pcr);
	ultra_setpcr(pcr);

	return (overflow);
}

/*ARGSUSED*/
static int
ni2_pcbe_configure(uint_t picnum, char *event, uint64_t preset, uint32_t flags,
    uint_t nattrs, kcpc_attr_t *attrs, void **data, void *token)
{
	ni2_pcbe_config_t	*cfg;
	ni2_pcbe_config_t	*other_config;
	ni2_event_t		*evp;
	ni2_generic_event_t	*gevp;
	int			i;
	uint32_t		evsel;
#if defined(VFALLS_IMPL) || defined(KT_IMPL)
	uint64_t		l2ctl = 0;
#endif

	/*
	 * If we've been handed an existing configuration, we need only preset
	 * the counter value.
	 */
	if (*data != NULL) {
		cfg = *data;
		cfg->pcbe_pic = (uint32_t)preset;
		return (0);
	}

	if (picnum > 1)
		return (CPC_INVALID_PICNUM);


	if ((evp = find_event(event)) == NULL) {
		if ((gevp = find_generic_event(event)) != NULL) {
			evp = find_event(gevp->event);
			ASSERT(evp != NULL);

			if (nattrs > 0)
				return (CPC_ATTRIBUTE_OUT_OF_RANGE);
		} else {
			return (CPC_INVALID_EVENT);
		}
	}

	evsel = evp->emask;

	for (i = 0; i < nattrs; i++) {
		if (strcmp(attrs[i].ka_name, "hpriv") == 0) {
			if (attrs[i].ka_val != 0)
				flags |= CPC_COUNT_HV;
		} else if (strcmp(attrs[i].ka_name, "emask") == 0) {
			if ((attrs[i].ka_val | evp->emask_valid) !=
			    evp->emask_valid)
				return (CPC_ATTRIBUTE_OUT_OF_RANGE);
			evsel |= attrs[i].ka_val;
#if defined(VFALLS_IMPL) || defined(KT_IMPL)
		} else if (strcmp(attrs[i].ka_name, "l2ctl") == 0) {
			if ((attrs[i].ka_val | L2_CTL_MASK) !=
			    L2_CTL_MASK)
				return (CPC_ATTRIBUTE_OUT_OF_RANGE);
			else
				l2ctl = attrs[i].ka_val;
#endif
#if defined(KT_IMPL)
		} else if (strcmp(attrs[i].ka_name, "sample") == 0) {
			if (attrs[i].ka_val != 0)
				flags |= CPC_COUNT_SAMPLE_MODE;
#endif
		} else
			return (CPC_INVALID_ATTRIBUTE);
	}

#if defined(VFALLS_IMPL) || defined(KT_IMPL)
	/*
	 * Set PERF_CONTROL bits in L2_CONTROL_REG only when events have
	 * SL bits equal to 3.
	 */
	if ((evsel & SL3_MASK) == SL3_MASK) {
		if ((hv_niagara_setperf(HV_L2_CTL, l2ctl)) != 0)
			return (CPC_HV_NO_ACCESS);
	}
#endif

	/*
	 * Find other requests that will be programmed with this one, and ensure
	 * the flags don't conflict.
	 */
	if (((other_config = kcpc_next_config(token, NULL, NULL)) != NULL) &&
	    (other_config->pcbe_flags != flags))
		return (CPC_CONFLICTING_REQS);

	/*
	 * If the hpriv attribute is present, make sure we have
	 * access to hyperprivileged events before continuing with
	 * this configuration. If we can set the ht bit in the PCR
	 * successfully, we must have access to hyperprivileged
	 * events.
	 *
	 * If this is a static per-CPU configuration, the CPC
	 * driver ensures there can not be more than one for this
	 * CPU. If this is a per-LWP configuration, the driver
	 * ensures no static per-CPU counting is ongoing and that
	 * the target LWP is not already being monitored.
	 */
	if (flags & CPC_COUNT_HV) {
		kpreempt_disable();

		DTRACE_PROBE1(niagara2__setpcr, uint64_t,
		    allstopped | CPC_PCR_HT);
		if (hv_niagara_setperf(HV_SPARC_CTL,
		    allstopped | CPC_PCR_HT) != H_EOK) {
			kpreempt_enable();
			return (CPC_HV_NO_ACCESS);
		}

		DTRACE_PROBE1(niagara2__setpcr, uint64_t, allstopped);
		(void) hv_niagara_setperf(HV_SPARC_CTL, allstopped);

		kpreempt_enable();
	}

	cfg = kmem_alloc(sizeof (*cfg), KM_SLEEP);

	cfg->pcbe_picno = picnum;
	cfg->pcbe_evsel = evsel;
	cfg->pcbe_flags = flags;
	cfg->pcbe_pic = (uint32_t)preset;

	*data = cfg;
	return (0);
}

static void
ni2_pcbe_program(void *token)
{
	ni2_pcbe_config_t	*pic0;
	ni2_pcbe_config_t	*pic1;
	ni2_pcbe_config_t	*tmp;
	ni2_pcbe_config_t	nullcfg = { 1, 0, 0, 0 };
	uint64_t		pcr;
	uint64_t		curpic;
	uint64_t		toe;

	/* enable trap-on-event for pic0 and pic1 */
	toe = (CPC_PCR_TOE0 | CPC_PCR_TOE1);

	if ((pic0 = (ni2_pcbe_config_t *)kcpc_next_config(token, NULL, NULL)) ==
	    NULL)
		panic("ni2_pcbe: token %p has no configs", token);

	if ((pic1 = kcpc_next_config(token, pic0, NULL)) == NULL) {
		pic1 = &nullcfg;
		nullcfg.pcbe_flags = pic0->pcbe_flags;
		toe = CPC_PCR_TOE0; /* enable trap-on-event for pic0 */
	}

	if (pic0->pcbe_picno != 0) {
		/*
		 * pic0 is counter 1, so if we need the null config it should
		 * be counter 0.
		 */
		nullcfg.pcbe_picno = 0;
		tmp = pic0;
		pic0 = pic1;
		pic1 = tmp;
		toe = CPC_PCR_TOE1; /* enable trap-on-event for pic1 */
	}

	if (pic0->pcbe_picno != 0 || pic1->pcbe_picno != 1)
		panic("%s: bad config on token %p\n", cpu_impl_name, token);

	/*
	 * UltraSPARC does not allow pic0 to be configured differently
	 * from pic1. If the flags on these two configurations are
	 * different, they are incompatible. This condition should be
	 * caught at configure time.
	 */
	ASSERT(pic0->pcbe_flags == pic1->pcbe_flags);

	ni2_pcbe_allstop();

	ultra_setpic(((uint64_t)pic1->pcbe_pic << PIC1_SHIFT) |
	    (uint64_t)pic0->pcbe_pic);

	pcr = (pic0->pcbe_evsel & pcr_pic0_mask) << CPC_PCR_PIC0_SHIFT;
	pcr |= (pic1->pcbe_evsel & pcr_pic1_mask) <<
	    CPC_PCR_PIC1_SHIFT;

	if (pic0->pcbe_flags & CPC_COUNT_USER)
		pcr |= (1ull << CPC_PCR_UT_SHIFT);
	if (pic0->pcbe_flags & CPC_COUNT_SYSTEM)
		pcr |= (1ull << CPC_PCR_ST_SHIFT);
	if (pic0->pcbe_flags & CPC_COUNT_HV)
		pcr |= (1ull << CPC_PCR_HT_SHIFT);
#if defined(KT_IMPL)
	if (pic0->pcbe_flags & CPC_COUNT_SAMPLE_MODE)
		pcr |= (1ull << CPC_PCR_SAMPLE_MODE_SHIFT);
#endif
	pcr |= toe;

	DTRACE_PROBE1(niagara2__setpcr, uint64_t, pcr);

	if (pic0->pcbe_flags & CPC_COUNT_HV) {
		/*
		 * The ht bit in the PCR is only writable in
		 * hyperprivileged mode. So if we are counting
		 * hpriv events, we must use the HV interface
		 * hv_niagara_setperf to set the PCR. If this
		 * fails, assume we no longer have access to
		 * hpriv events.
		 */
		if (hv_niagara_setperf(HV_SPARC_CTL, pcr) != H_EOK) {
			kcpc_invalidate_config(token);
			return;
		}
	} else
		/* Set the PCR with no hpriv event counting enabled. */
		ultra_setpcr(pcr);

	ni2_cpc_counting[CPU->cpu_id] = B_TRUE;

	/*
	 * On UltraSPARC, only read-to-read counts are accurate. We cannot
	 * expect the value we wrote into the PIC, above, to be there after
	 * starting the counter. We must sample the counter value now and use
	 * that as the baseline for future samples.
	 */
	curpic = ultra_getpic();
	pic0->pcbe_pic = (uint32_t)(curpic & PIC0_MASK);
	pic1->pcbe_pic = (uint32_t)(curpic >> PIC1_SHIFT);

	DTRACE_PROBE1(niagara2__newpic, uint64_t, curpic);
}

static void
ni2_pcbe_allstop(void)
{
	/*
	 * We use the HV interface here because if we were counting
	 * hyperprivileged events, we must reset the PCR.ht bit to stop
	 * the counting. In the event that this HV call fails, we fall
	 * back on ultra_setpcr which does not have write access to the
	 * ht bit.
	 */
	if (hv_niagara_setperf(HV_SPARC_CTL, allstopped) != H_EOK)
		ultra_setpcr(allstopped);

	ni2_cpc_counting[CPU->cpu_id] = B_FALSE;
}

static void
ni2_pcbe_sample(void *token)
{
	uint64_t		curpic;
	int64_t			diff;
	uint64_t		*pic0_data;
	uint64_t		*pic1_data;
	uint64_t		*dtmp;
	uint64_t		tmp;
	uint64_t		pcr;
	ni2_pcbe_config_t	*pic0;
	ni2_pcbe_config_t	*pic1;
	ni2_pcbe_config_t	nullcfg = { 1, 0, 0, 0 };
	ni2_pcbe_config_t	*ctmp;

	curpic = ultra_getpic();
	DTRACE_PROBE1(niagara2__getpic, uint64_t, curpic);

	if ((pic0 = kcpc_next_config(token, NULL, &pic0_data)) == NULL)
		panic("%s: token %p has no configs", cpu_impl_name, token);

	if ((pic1 = kcpc_next_config(token, pic0, &pic1_data)) == NULL) {
		pic1 = &nullcfg;
		pic1_data = &tmp;
	}

	if (pic0->pcbe_picno != 0) {
		nullcfg.pcbe_picno = 0;
		ctmp = pic0;
		pic0 = pic1;
		pic1 = ctmp;
		dtmp = pic0_data;
		pic0_data = pic1_data;
		pic1_data = dtmp;
	}

	if (pic0->pcbe_picno != 0 || pic1->pcbe_picno != 1)
		panic("%s: bad config on token %p\n", cpu_impl_name, token);


	if (pic0->pcbe_flags & CPC_COUNT_HV) {
		/*
		 * If the hpriv attribute is present, but the HT bit
		 * is not set in the PCR, access to hyperprivileged
		 * events must have been revoked. Only perform this
		 * check if counting is not stopped.
		 */
		pcr = ultra_getpcr();
		DTRACE_PROBE1(niagara2__getpcr, uint64_t, pcr);
		if (ni2_cpc_counting[CPU->cpu_id] &&
		    !(pcr & CPC_PCR_HT)) {
			kcpc_invalidate_config(token);
			return;
		}
	}

	diff = (curpic & PIC0_MASK) - (uint64_t)pic0->pcbe_pic;
	if (diff < 0)
		diff += (1ll << 32);
	*pic0_data += diff;

	diff = (curpic >> 32) - (uint64_t)pic1->pcbe_pic;
	if (diff < 0)
		diff += (1ll << 32);
	*pic1_data += diff;

	pic0->pcbe_pic = (uint32_t)(curpic & PIC0_MASK);
	pic1->pcbe_pic = (uint32_t)(curpic >> PIC1_SHIFT);
}

static void
ni2_pcbe_free(void *config)
{
	kmem_free(config, sizeof (ni2_pcbe_config_t));
}


static struct modlpcbe modlpcbe = {
	&mod_pcbeops,
#if defined(NIAGARA2_IMPL)
	"UltraSPARC T2 Performance Counters",
#elif defined(VFALLS_IMPL)
	"UltraSPARC T2+ Performance Counters",
#elif defined(KT_IMPL)
	"UltraSPARC T3 Performance Counters",
#endif
	&ni2_pcbe_ops
};

static struct modlinkage modl = {
	MODREV_1,
	&modlpcbe,
};

int
_init(void)
{
	if (ni2_pcbe_init() != 0)
		return (ENOTSUP);
	return (mod_install(&modl));
}

int
_fini(void)
{
	return (mod_remove(&modl));
}

int
_info(struct modinfo *mi)
{
	return (mod_info(&modl, mi));
}