root/usr/src/uts/sun4v/cpu/niagara_perfctr.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/async.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddi_impldefs.h>
#include <sys/machsystm.h>
#include <sys/hypervisor_api.h>
#include <sys/kstat.h>
#if defined(NIAGARA_IMPL)
#include <sys/niagararegs.h>
#elif defined(NIAGARA2_IMPL) || defined(VFALLS_IMPL) || defined(KT_IMPL)
#include <sys/niagara2regs.h>
#endif

extern char cpu_module_name[];

/*
 * Data structure used to build array of event-names and pcr-mask values
 */
typedef struct ni_kev_mask {
        char            *event_name;
        uint64_t        pcr_mask;
} ni_kev_mask_t;

/*
 * Kstat data structure for DRAM and JBUS performance counters
 *
 * Note that these performance counters are only 31 bits wide. Since
 * the "busstat" command assumes a 32-bit counter, we emulate a 32-bit
 * counter by detecting overflow on read of these performance counters
 * and using the least significant bit of the overflow count as the
 * most significant bit (i.e. bit# 31) of the DRAM and JBUS performance
 * counters.
 */
#define NUM_OF_PICS     2
#define NUM_OF_PIC_REGS 2

typedef struct ni_ksinfo {
        uint8_t         pic_no_evs;                     /* number of events */
        uint8_t         pic_sel_shift[NUM_OF_PICS];
        uint8_t         pic_shift[NUM_OF_PICS];
        uint64_t        pic_mask[NUM_OF_PICS];
        kstat_t         *pic_name_ksp[NUM_OF_PICS];
        kstat_t         *cntr_ksp;
        uint32_t        pic_reg[NUM_OF_PIC_REGS];
        uint32_t        pcr_reg;
        uint32_t        pic_overflow[NUM_OF_PICS];      /* overflow count */
        uint32_t        pic_last_val[NUM_OF_PICS];      /* last PIC value */
} ni_ksinfo_t;

static ni_ksinfo_t      *ni_dram_kstats[DRAM_BANKS];

#if defined(NIAGARA_IMPL)
static ni_ksinfo_t      *ni_jbus_kstat;
#endif

typedef struct ni_perf_regs {
        uint32_t        pcr_reg;
        uint32_t        pic_reg[NUM_OF_PIC_REGS];
} ni_perf_regs_t;

static ni_perf_regs_t dram_perf_regs[] = {
#if defined(NIAGARA_IMPL) || defined(NIAGARA2_IMPL)
        {HV_DRAM_CTL0, HV_DRAM_COUNT0},
        {HV_DRAM_CTL1, HV_DRAM_COUNT1},
        {HV_DRAM_CTL2, HV_DRAM_COUNT2},
        {HV_DRAM_CTL3, HV_DRAM_COUNT3},
#elif defined(VFALLS_IMPL) || defined(KT_IMPL)
        {HV_DRAM_CTL0, HV_DRAM_COUNT0},
        {HV_DRAM_CTL1, HV_DRAM_COUNT1},
        {HV_DRAM_CTL2, HV_DRAM_COUNT2},
        {HV_DRAM_CTL3, HV_DRAM_COUNT3},
        {HV_DRAM_CTL4, HV_DRAM_COUNT4},
        {HV_DRAM_CTL5, HV_DRAM_COUNT5},
        {HV_DRAM_CTL6, HV_DRAM_COUNT6},
        {HV_DRAM_CTL7, HV_DRAM_COUNT7}
#endif
};

#ifdef VFALLS_IMPL
/*
 * Kstat data structure for Zambezi performance counters
 * These performance counters are 64 bits wide.
 */
static ni_ksinfo_t      *zam_lpu_kstats[ZAMBEZI_LPU_COUNTERS];
static ni_ksinfo_t      *zam_gpd_kstats[ZAMBEZI_GPD_COUNTERS];
static ni_ksinfo_t      *zam_asu_kstats[ZAMBEZI_ASU_COUNTERS];

typedef struct zam_perf_regs {
        uint32_t        pcr_reg;
        uint32_t        pic_reg[NUM_OF_PICS];
} zam_perf_regs_t;

static zam_perf_regs_t lpu_perf_regs[] = {
        {HV_ZAM0_LPU_A_PCR, HV_ZAM0_LPU_A_PIC0, HV_ZAM0_LPU_A_PIC1},
        {HV_ZAM0_LPU_B_PCR, HV_ZAM0_LPU_B_PIC0, HV_ZAM0_LPU_B_PIC1},
        {HV_ZAM0_LPU_C_PCR, HV_ZAM0_LPU_C_PIC0, HV_ZAM0_LPU_C_PIC1},
        {HV_ZAM0_LPU_D_PCR, HV_ZAM0_LPU_D_PIC0, HV_ZAM0_LPU_D_PIC1},

        {HV_ZAM1_LPU_A_PCR, HV_ZAM1_LPU_A_PIC0, HV_ZAM1_LPU_A_PIC1},
        {HV_ZAM1_LPU_B_PCR, HV_ZAM1_LPU_B_PIC0, HV_ZAM1_LPU_B_PIC1},
        {HV_ZAM1_LPU_C_PCR, HV_ZAM1_LPU_C_PIC0, HV_ZAM1_LPU_C_PIC1},
        {HV_ZAM1_LPU_D_PCR, HV_ZAM1_LPU_D_PIC0, HV_ZAM1_LPU_D_PIC1},

        {HV_ZAM2_LPU_A_PCR, HV_ZAM2_LPU_A_PIC0, HV_ZAM2_LPU_A_PIC1},
        {HV_ZAM2_LPU_B_PCR, HV_ZAM2_LPU_B_PIC0, HV_ZAM2_LPU_B_PIC1},
        {HV_ZAM2_LPU_C_PCR, HV_ZAM2_LPU_C_PIC0, HV_ZAM2_LPU_C_PIC1},
        {HV_ZAM2_LPU_D_PCR, HV_ZAM2_LPU_D_PIC0, HV_ZAM2_LPU_D_PIC1},

        {HV_ZAM3_LPU_A_PCR, HV_ZAM3_LPU_A_PIC0, HV_ZAM3_LPU_A_PIC1},
        {HV_ZAM3_LPU_B_PCR, HV_ZAM3_LPU_B_PIC0, HV_ZAM3_LPU_B_PIC1},
        {HV_ZAM3_LPU_C_PCR, HV_ZAM3_LPU_C_PIC0, HV_ZAM3_LPU_C_PIC1},
        {HV_ZAM3_LPU_D_PCR, HV_ZAM3_LPU_D_PIC0, HV_ZAM3_LPU_D_PIC1}
};

static zam_perf_regs_t gpd_perf_regs[] = {
        {HV_ZAM0_GPD_PCR, HV_ZAM0_GPD_PIC0, HV_ZAM0_GPD_PIC1},
        {HV_ZAM1_GPD_PCR, HV_ZAM1_GPD_PIC0, HV_ZAM1_GPD_PIC1},
        {HV_ZAM2_GPD_PCR, HV_ZAM2_GPD_PIC0, HV_ZAM2_GPD_PIC1},
        {HV_ZAM3_GPD_PCR, HV_ZAM3_GPD_PIC0, HV_ZAM3_GPD_PIC1}
};

static zam_perf_regs_t asu_perf_regs[] = {
        {HV_ZAM0_ASU_PCR, HV_ZAM0_ASU_PIC0, HV_ZAM0_ASU_PIC1},
        {HV_ZAM1_ASU_PCR, HV_ZAM1_ASU_PIC0, HV_ZAM1_ASU_PIC1},
        {HV_ZAM2_ASU_PCR, HV_ZAM2_ASU_PIC0, HV_ZAM2_ASU_PIC1},
        {HV_ZAM3_ASU_PCR, HV_ZAM3_ASU_PIC0, HV_ZAM3_ASU_PIC1}
};

static int zam_cntr_kstat_update(kstat_t *, int);
#endif

static void ni_create_name_kstat(char *, ni_ksinfo_t *, ni_kev_mask_t *);
static void ni_delete_name_kstat(ni_ksinfo_t *);

static kstat_t *ni_create_cntr_kstat(char *, int,
        int (*update)(kstat_t *, int), void *);

static int ni_cntr_kstat_update(kstat_t *, int);

static kstat_t *ni_create_picN_kstat(char *, int, int, int,
        ni_kev_mask_t *);

#ifdef DEBUG
static int      ni_perf_debug;
#endif

/*
 * Niagara, Niagara2 and VFalls DRAM Performance Events
 */
static ni_kev_mask_t
niagara_dram_events[] = {
#if defined(NIAGARA_IMPL) || defined(NIAGARA2_IMPL) || defined(VFALLS_IMPL)
        {"mem_reads",           0x0},
        {"mem_writes",          0x1},
        {"mem_read_write",      0x2},
#elif defined(KT_IMPL)
        {"remote_reads",        0x0},
        {"remote_writes",       0x1},
        {"remote_read_write",   0x2},
#endif
#if defined(NIAGARA_IMPL) || defined(KT_IMPL)
        {"bank_busy_stalls",    0x3},
#endif
        {"rd_queue_latency",    0x4},
        {"wr_queue_latency",    0x5},
        {"rw_queue_latency",    0x6},
#if defined(NIAGARA_IMPL) || defined(NIAGARA2_IMPL) || defined(VFALLS_IMPL)
        {"wb_buf_hits",         0x7},
#elif defined(KT_IMPL)
        {"write_queue_drain",   0x7},
        {"read_all_channels",   0x8},
        {"write_starved",       0x9},
        {"write_all_channels",  0xa},
        {"read_write_channel0", 0xb},
        {"read_write_channel1", 0xc},
#endif
        {"clear_pic",           0xf}
};

#if defined(VFALLS_IMPL)
/*
 * Zambezi Performance Events
 */
static ni_kev_mask_t
zam_lpu_perf_events[] = {
        {"none",                0x0},
        {"clock_cycles",        0x1},
        {"cycles_c2c_portX",    0x2},
        {"cycles_mem_portX",    0x3},
        {"cycles_WB_portX",     0x4},
        {"cycles_NC_portX",     0x5},
        {"cycles_c2c_portY",    0x6},
        {"cycles_mem_portY",    0x7},
        {"cycles_WB_portY",     0x8},
        {"cycles_NC_portY",     0x9},
        {"cycles_c2c_portZ",    0xa},
        {"cycles_mem_portZ",    0xb},
        {"cycles_WB_portZ",     0xc},
        {"cycles_NC_portZ",     0xd},
        {"cycles_TID_WB",       0xe},
        {"cycles_TID_INV",      0xf},
        {"cycles_TID_RTD",      0x10},
        {"cycles_TID_RTO",      0x11},
        {"cycles_TID_RTS",      0x12},
        {"cycles_IO_WRM",       0x13},
        {"cycles_IO_RD",        0x14},
        {"cycles_WB_egress",    0x15},
        {"cycles_INV_egress",   0x16},
        {"cycles_RTO_egress",   0x17},
        {"cycles_RTD_egress",   0x18},
        {"cycles_RTS_egress",   0x19},
        {"cycles_no_WB",        0x1a},
        {"cycles_no_read/inv",  0x1b},
        {"cycles_HIT_M",        0x1c},
        {"cycles_HIT_O",        0x1d},
        {"cycles_HIT_S",        0x1e},
        {"cycles_WB_HIT",       0x1f},
        {"cycles_MISS",         0x20},
        {"cycles_READ_or_INV",  0x21},
        {"cycles_WB",           0x22},
        {"cycles_NDR",          0x23},
        {"cycles_cache_miss",   0x24},
        {"cycles_cache_hit",    0x25},
        {"cycles_CRC_errors",   0x26},
        {"cycles_replys_sent",  0x27},
        {"cycles_replys_recev", 0x28},
        {"cycles_link_retrain", 0x29},
        {"clear_pic",           0xff}
};

static ni_kev_mask_t
zam_gpd_perf_events[] = {
        {"none",                0x0},
        {"clock_cycles",        0x1},
        {"clear_pic",           0xf}
};

static ni_kev_mask_t
zam_asu_perf_events[] = {
        {"none",                0x0},
        {"clock_cycles",        0x1},
        {"asu_in_pck",          0x2},
        {"asu_out_pck",         0x3},
        {"asu_CAM_hit",         0x4},
        {"asu_wakeup",          0x5},
        {"clear_pic",           0xf}
};
#endif

#if defined(NIAGARA_IMPL)
/*
 * Niagara JBUS Performance Events
 */
static ni_kev_mask_t
niagara_jbus_events[] = {
        {"jbus_cycles",         0x1},
        {"dma_reads",           0x2},
        {"dma_read_latency",    0x3},
        {"dma_writes",          0x4},
        {"dma_write8",          0x5},
        {"ordering_waits",      0x6},
        {"pio_reads",           0x8},
        {"pio_read_latency",    0x9},
        {"aok_dok_off_cycles",  0xc},
        {"aok_off_cycles",      0xd},
        {"dok_off_cycles",      0xe},
        {"clear_pic",           0xf}
};
#endif

/*
 * Create the picN kstats for DRAM, JBUS and Zambezi events
 */
void
niagara_kstat_init()
{
        int i;
        ni_ksinfo_t *ksinfop;
#if defined(VFALLS_IMPL) || defined(KT_IMPL)
        uint64_t stat, pcr;
#endif

#ifdef DEBUG
        if (ni_perf_debug)
                printf("ni_kstat_init called\n");
#endif

        /*
         * Create DRAM perf events kstat
         */
        for (i = 0; i < DRAM_BANKS; i++) {
#if defined(VFALLS_IMPL) || defined(KT_IMPL)
                /* check if this dram instance is enabled in the HW */
                stat = hv_niagara_getperf(dram_perf_regs[i].pcr_reg, &pcr);
                if ((stat != H_EINVAL) && (stat != H_ENOTSUPPORTED)) {
#endif
                        ksinfop = (ni_ksinfo_t *)kmem_zalloc(
                            sizeof (ni_ksinfo_t), KM_NOSLEEP);

                        if (ksinfop == NULL) {
                                cmn_err(CE_WARN,
                                    "%s: no space for dram kstat\n",
                                    cpu_module_name);
                                break;
                        }
                        ksinfop->pic_no_evs =
                            sizeof (niagara_dram_events) /
                            sizeof (ni_kev_mask_t);
                        ksinfop->pic_sel_shift[0] = DRAM_PIC0_SEL_SHIFT;
                        ksinfop->pic_shift[0] = DRAM_PIC0_SHIFT;
                        ksinfop->pic_mask[0] = DRAM_PIC0_MASK;
                        ksinfop->pic_sel_shift[1] = DRAM_PIC1_SEL_SHIFT;
                        ksinfop->pic_shift[1] = DRAM_PIC1_SHIFT;
                        ksinfop->pic_mask[1] = DRAM_PIC1_MASK;
                        ksinfop->pic_reg[0] = dram_perf_regs[i].pic_reg[0];
                        ksinfop->pcr_reg = dram_perf_regs[i].pcr_reg;
                        ni_dram_kstats[i] = ksinfop;

                        /* create basic pic event/mask pair (only once) */
                        if (i == 0)
                                ni_create_name_kstat("dram", ksinfop,
                                    niagara_dram_events);

                        /* create counter kstats */
                        ni_dram_kstats[i]->cntr_ksp = ni_create_cntr_kstat(
                            "dram", i, ni_cntr_kstat_update, ksinfop);
#if defined(VFALLS_IMPL) || defined(KT_IMPL)
                }
#endif
        }

#ifdef VFALLS_IMPL
        /*
         * Create Zambezi LPU perf events kstat
         */
        for (i = 0; i < ZAMBEZI_LPU_COUNTERS; i++) {
                /* check if this Zambezi LPU instance is enabled in the HW */
                stat = hv_niagara_getperf(lpu_perf_regs[i].pcr_reg, &pcr);
                if ((stat != H_EINVAL) && (stat != H_ENOTSUPPORTED)) {
                        ksinfop = (ni_ksinfo_t *)kmem_zalloc(
                            sizeof (ni_ksinfo_t), KM_NOSLEEP);

                        if (ksinfop == NULL) {
                                cmn_err(CE_WARN,
                                    "%s: no space for zambezi lpu kstat\n",
                                    cpu_module_name);
                                break;
                        }
                        ksinfop->pic_no_evs =
                            sizeof (zam_lpu_perf_events) /
                            sizeof (ni_kev_mask_t);
                        ksinfop->pic_sel_shift[0] = ZAMBEZI_PIC0_SEL_SHIFT;
                        ksinfop->pic_reg[0] = lpu_perf_regs[i].pic_reg[0];
                        ksinfop->pic_sel_shift[1] = ZAMBEZI_PIC1_SEL_SHIFT;
                        ksinfop->pic_reg[1] = lpu_perf_regs[i].pic_reg[1];
                        ksinfop->pcr_reg = lpu_perf_regs[i].pcr_reg;
                        zam_lpu_kstats[i] = ksinfop;

                        /* create basic pic event/mask pair (only once) */
                        if (i == 0)
                                ni_create_name_kstat("lpu", ksinfop,
                                    zam_lpu_perf_events);

                        /* create counter kstats */
                        zam_lpu_kstats[i]->cntr_ksp = ni_create_cntr_kstat(
                            "lpu", i, zam_cntr_kstat_update, ksinfop);
                }
        }
        /*
         * Create Zambezi GPD perf events kstat
         */
        for (i = 0; i < ZAMBEZI_GPD_COUNTERS; i++) {
                /* check if this Zambezi GPD instance is enabled in the HW */
                stat = hv_niagara_getperf(gpd_perf_regs[i].pcr_reg, &pcr);
                if ((stat != H_EINVAL) && (stat != H_ENOTSUPPORTED)) {
                        ksinfop = (ni_ksinfo_t *)kmem_zalloc(
                            sizeof (ni_ksinfo_t), KM_NOSLEEP);

                        if (ksinfop == NULL) {
                                cmn_err(CE_WARN,
                                    "%s: no space for zambezi gpd kstat\n",
                                    cpu_module_name);
                                break;
                        }
                        ksinfop->pic_no_evs =
                            sizeof (zam_gpd_perf_events) /
                            sizeof (ni_kev_mask_t);
                        ksinfop->pic_sel_shift[0] = ZAMBEZI_PIC0_SEL_SHIFT;
                        ksinfop->pic_reg[0] = gpd_perf_regs[i].pic_reg[0];
                        ksinfop->pic_sel_shift[1] = ZAMBEZI_PIC1_SEL_SHIFT;
                        ksinfop->pic_reg[1] = gpd_perf_regs[i].pic_reg[1];
                        ksinfop->pcr_reg = gpd_perf_regs[i].pcr_reg;
                        zam_gpd_kstats[i] = ksinfop;

                        /* create basic pic event/mask pair (only once) */
                        if (i == 0)
                                ni_create_name_kstat("gpd", ksinfop,
                                    zam_gpd_perf_events);

                        /* create counter kstats */
                        zam_lpu_kstats[i]->cntr_ksp = ni_create_cntr_kstat(
                            "gpd", i, zam_cntr_kstat_update, ksinfop);
                }
        }
        /*
         * Create Zambezi ASU perf events kstat
         */
        for (i = 0; i < ZAMBEZI_ASU_COUNTERS; i++) {
                /* check if this Zambezi ASU instance is enabled in the HW */
                stat = hv_niagara_getperf(asu_perf_regs[i].pcr_reg, &pcr);
                if ((stat != H_EINVAL) && (stat != H_ENOTSUPPORTED)) {
                        ksinfop = (ni_ksinfo_t *)kmem_zalloc(
                            sizeof (ni_ksinfo_t), KM_NOSLEEP);

                        if (ksinfop == NULL) {
                                cmn_err(CE_WARN,
                                    "%s: no space for zambezi asu kstat\n",
                                    cpu_module_name);
                                break;
                        }
                        ksinfop->pic_no_evs =
                            sizeof (zam_asu_perf_events) /
                            sizeof (ni_kev_mask_t);
                        ksinfop->pic_sel_shift[0] = ZAMBEZI_PIC0_SEL_SHIFT;
                        ksinfop->pic_reg[0] = asu_perf_regs[i].pic_reg[0];
                        ksinfop->pic_sel_shift[1] = ZAMBEZI_PIC1_SEL_SHIFT;
                        ksinfop->pic_reg[1] = asu_perf_regs[i].pic_reg[1];
                        ksinfop->pcr_reg = asu_perf_regs[i].pcr_reg;
                        zam_asu_kstats[i] = ksinfop;

                        /* create basic pic event/mask pair (only once) */
                        if (i == 0)
                                ni_create_name_kstat("asu", ksinfop,
                                    zam_asu_perf_events);

                        /* create counter kstats */
                        zam_lpu_kstats[i]->cntr_ksp = ni_create_cntr_kstat(
                            "asu", i, zam_cntr_kstat_update, ksinfop);
                }
        }
#endif

#if defined(NIAGARA_IMPL)
        /*
         * Create JBUS perf events kstat
         */
        ni_jbus_kstat = (ni_ksinfo_t *)kmem_alloc(sizeof (ni_ksinfo_t),
            KM_NOSLEEP);

        if (ni_jbus_kstat == NULL) {
                cmn_err(CE_WARN, "%s: no space for niagara jbus kstat\n",
                    cpu_module_name);
        } else {
                ni_jbus_kstat->pic_no_evs =
                    sizeof (niagara_jbus_events) / sizeof (ni_kev_mask_t);
                ni_jbus_kstat->pic_sel_shift[0] = NIAGARA_JBUS_PIC0_SEL_SHIFT;
                ni_jbus_kstat->pic_shift[0] = NIAGARA_JBUS_PIC0_SHIFT;
                ni_jbus_kstat->pic_mask[0] = NIAGARA_JBUS_PIC0_MASK;
                ni_jbus_kstat->pic_sel_shift[1] = NIAGARA_JBUS_PIC1_SEL_SHIFT;
                ni_jbus_kstat->pic_shift[1] = NIAGARA_JBUS_PIC1_SHIFT;
                ni_jbus_kstat->pic_mask[1] = NIAGARA_JBUS_PIC1_MASK;
                ni_jbus_kstat->pic_reg[0] = HV_NIAGARA_JBUS_COUNT;
                ni_jbus_kstat->pcr_reg = HV_NIAGARA_JBUS_CTL;
                ni_create_name_kstat("jbus", ni_jbus_kstat,
                    niagara_jbus_events);
                ni_jbus_kstat->cntr_ksp = ni_create_cntr_kstat("jbus", 0,
                    ni_cntr_kstat_update, ni_jbus_kstat);
        }
#endif
}

void
niagara_kstat_fini()
{
        int i;

#ifdef DEBUG
        if (ni_perf_debug)
                printf("ni_kstat_fini called\n");
#endif

        for (i = 0; i < DRAM_BANKS; i++) {
                if (ni_dram_kstats[i] != NULL) {
                        ni_delete_name_kstat(ni_dram_kstats[i]);
                        if (ni_dram_kstats[i]->cntr_ksp != NULL)
                                kstat_delete(ni_dram_kstats[i]->cntr_ksp);
                        kmem_free(ni_dram_kstats[i], sizeof (ni_ksinfo_t));
                        ni_dram_kstats[i] = NULL;
                }
        }

#if defined(VFALLS_IMPL)
        for (i = 0; i < ZAMBEZI_LPU_COUNTERS; i++) {
                if (zam_lpu_kstats[i] != NULL) {
                        ni_delete_name_kstat(zam_lpu_kstats[i]);
                        if (zam_lpu_kstats[i]->cntr_ksp != NULL)
                                kstat_delete(zam_lpu_kstats[i]->cntr_ksp);
                        kmem_free(zam_lpu_kstats[i], sizeof (ni_ksinfo_t));
                        zam_lpu_kstats[i] = NULL;
                }
        }

        for (i = 0; i < ZAMBEZI_GPD_COUNTERS; i++) {
                if (zam_gpd_kstats[i] != NULL) {
                        ni_delete_name_kstat(zam_gpd_kstats[i]);
                        if (zam_gpd_kstats[i]->cntr_ksp != NULL)
                                kstat_delete(zam_gpd_kstats[i]->cntr_ksp);
                        kmem_free(zam_gpd_kstats[i], sizeof (ni_ksinfo_t));
                        zam_gpd_kstats[i] = NULL;
                }
        }

        for (i = 0; i < ZAMBEZI_ASU_COUNTERS; i++) {
                if (zam_asu_kstats[i] != NULL) {
                        ni_delete_name_kstat(zam_asu_kstats[i]);
                        if (zam_asu_kstats[i]->cntr_ksp != NULL)
                                kstat_delete(zam_asu_kstats[i]->cntr_ksp);
                        kmem_free(zam_asu_kstats[i], sizeof (ni_ksinfo_t));
                        zam_asu_kstats[i] = NULL;
                }
        }
#endif

#if defined(NIAGARA_IMPL)
        if (ni_jbus_kstat != NULL) {
                ni_delete_name_kstat(ni_jbus_kstat);
                if (ni_jbus_kstat->cntr_ksp != NULL)
                        kstat_delete(ni_jbus_kstat->cntr_ksp);
                kmem_free(ni_jbus_kstat, sizeof (ni_ksinfo_t));
                ni_jbus_kstat = NULL;
        }
#endif
}

static void
ni_create_name_kstat(char *name, ni_ksinfo_t *pp, ni_kev_mask_t *ev)
{
        int     i;

#ifdef DEBUG
        if (ni_perf_debug > 1)
                printf("ni_create_name_kstat: name: %s\n", name);
#endif
        for (i = 0; i < NUM_OF_PICS; i++) {
                pp->pic_name_ksp[i] = ni_create_picN_kstat(name,
                    i, pp->pic_sel_shift[i], pp->pic_no_evs, ev);

                if (pp->pic_name_ksp[i] == NULL) {
                        cmn_err(CE_WARN, "%s: unable to create name kstat",
                            cpu_module_name);
                }
        }
}

static void
ni_delete_name_kstat(ni_ksinfo_t *pp)
{
        int     i;

        if (pp != NULL) {
                for (i = 0; i < NUM_OF_PICS; i++) {
                        if (pp->pic_name_ksp[i] != NULL)
                                kstat_delete(pp->pic_name_ksp[i]);
                }
        }
}

/*
 * Create the picN kstat. Returns a pointer to the
 * kstat which the driver must store to allow it
 * to be deleted when necessary.
 */
static kstat_t *
ni_create_picN_kstat(char *mod_name, int pic, int pic_sel_shift,
    int num_ev, ni_kev_mask_t *ev_array)
{
        struct kstat_named *pic_named_data;
        int     inst = 0;
        int     event;
        char    pic_name[30];
        kstat_t *picN_ksp = NULL;

        (void) sprintf(pic_name, "pic%d", pic);
        if ((picN_ksp = kstat_create(mod_name, inst, pic_name,
            "bus", KSTAT_TYPE_NAMED, num_ev, 0)) == NULL) {
                cmn_err(CE_WARN, "%s %s : kstat create failed",
                    mod_name, pic_name);

                /*
                 * It is up to the calling function to delete any kstats
                 * that may have been created already. We just
                 * return NULL to indicate an error has occured.
                 */
                return (NULL);
        }

        pic_named_data = (struct kstat_named *)
            picN_ksp->ks_data;

        /*
         * Write event names and their associated pcr masks. The
         * last entry in the array (clear_pic) is added seperately
         * below as the pic value must be inverted.
         */
        for (event = 0; event < num_ev - 1; event++) {
                pic_named_data[event].value.ui64 =
                    (ev_array[event].pcr_mask << pic_sel_shift);

                kstat_named_init(&pic_named_data[event],
                    ev_array[event].event_name,
                    KSTAT_DATA_UINT64);
        }

        /*
         * add the clear_pic entry.
         */
        pic_named_data[event].value.ui64 =
            (uint64_t)~(ev_array[event].pcr_mask << pic_sel_shift);

        kstat_named_init(&pic_named_data[event], ev_array[event].event_name,
            KSTAT_DATA_UINT64);

        kstat_install(picN_ksp);

        return (picN_ksp);
}

/*
 * Create the "counters" kstat.
 */
static kstat_t *
ni_create_cntr_kstat(char *name, int instance, int (*update)(kstat_t *, int),
    void *ksinfop)
{
        struct kstat    *counters_ksp;
        struct kstat_named      *counters_named_data;
        char            pic_str[10];
        int             i;
        int             num_pics = NUM_OF_PICS;

#ifdef DEBUG
        if (ni_perf_debug > 1)
                printf("ni_create_cntr_kstat: name: %s instance: %d\n",
                    name, instance);
#endif

        /*
         * Size of kstat is num_pics + 1 as it
         * also contains the %pcr
         */
        if ((counters_ksp = kstat_create(name, instance, "counters", "bus",
            KSTAT_TYPE_NAMED, num_pics + 1, KSTAT_FLAG_WRITABLE)) == NULL) {
                cmn_err(CE_WARN,
                    "%s: kstat_create for %s%d failed", cpu_module_name,
                    name, instance);
                return (NULL);
        }

        counters_named_data = (struct kstat_named *)(counters_ksp->ks_data);

        /*
         * Iinitialize the named kstats
         */
        kstat_named_init(&counters_named_data[0], "pcr", KSTAT_DATA_UINT64);

        for (i = 0; i < num_pics; i++) {
                (void) sprintf(pic_str, "pic%d", i);

                kstat_named_init(&counters_named_data[i+1], pic_str,
                    KSTAT_DATA_UINT64);
        }

        /*
         * Store the register offset's in the kstat's
         * private field so that they are available
         * to the update function.
         */
        counters_ksp->ks_private = (void *)ksinfop;
        counters_ksp->ks_update = update;

        kstat_install(counters_ksp);

        return (counters_ksp);
}

#if defined(VFALLS_IMPL)
/*
 * zambezi kstat update function. Handles reads/writes
 * from/to kstat.
 */
static int
zam_cntr_kstat_update(kstat_t *ksp, int rw)
{
        struct kstat_named      *data_p;
        ni_ksinfo_t     *ksinfop = ksp->ks_private;
        uint64_t        pic0, pic1, pcr;
        int             stat = 0;
        uint64_t        pic0_stat = 0, pic1_stat = 0, pcr_stat = 0;

        data_p = (struct kstat_named *)ksp->ks_data;

        if (rw == KSTAT_WRITE) {
#ifdef DEBUG
                if (ni_perf_debug)
                        printf("zam_cntr_kstat_update: wr pcr-%d: %lx\n",
                            ksinfop->pcr_reg, data_p[0].value.ui64);
#endif
                if (hv_niagara_setperf(ksinfop->pcr_reg, data_p[0].value.ui64))
                        stat = EACCES;
        } else {
                do {
                        pic0_stat = hv_niagara_getperf(ksinfop->pic_reg[0],
                            &pic0);
                } while (pic0_stat == H_EWOULDBLOCK);
                do {
                        pic1_stat = hv_niagara_getperf(ksinfop->pic_reg[1],
                            &pic1);
                } while (pic1_stat == H_EWOULDBLOCK);
                do {
                        pcr_stat = hv_niagara_getperf(ksinfop->pcr_reg,
                            &pcr);
                } while (pcr_stat == H_EWOULDBLOCK);
                if (pic0_stat != 0 || pic1_stat != 0 || pcr_stat != 0)
                        stat = EACCES;
                else {
                        data_p[0].value.ui64 = pcr;
                        data_p[1].value.ui64 = pic0;
                        data_p[2].value.ui64 = pic1;
                }
#ifdef DEBUG
                if (ni_perf_debug)
                        printf("zam_cntr_kstat_update: rd pcr%d: %lx  "
                            "pic0: %16lx pic1: %16lx\n",
                            ksinfop->pcr_reg, pcr,
                            data_p[1].value.ui64, data_p[2].value.ui64);
#endif
        }

        return (stat);
}
#endif

/*
 * kstat update function. Handles reads/writes
 * from/to kstat.
 */
static int
ni_cntr_kstat_update(kstat_t *ksp, int rw)
{
        struct kstat_named      *data_p;
        ni_ksinfo_t     *ksinfop = ksp->ks_private;
        uint64_t        pic, pcr;
        int             stat = 0;
        uint32_t        pic0, pic1;

        data_p = (struct kstat_named *)ksp->ks_data;

        if (rw == KSTAT_WRITE) {
#ifdef DEBUG
                if (ni_perf_debug)
                        printf("ni_cntr_kstat_update: wr pcr-%d: %lx\n",
                            ksinfop->pcr_reg, data_p[0].value.ui64);
#endif
                if (hv_niagara_setperf(ksinfop->pcr_reg, data_p[0].value.ui64))
                        stat = EACCES;
        } else {
                if (hv_niagara_getperf(ksinfop->pic_reg[0], &pic) != 0 ||
                    hv_niagara_getperf(ksinfop->pcr_reg, &pcr) != 0)
                        stat = EACCES;
                else {
                        data_p[0].value.ui64 = pcr;

                        /*
                         * Generate a 32-bit PIC0 value by detecting overflow
                         */
                        pic0 = (uint32_t)((pic >> ksinfop->pic_shift[0]) &
                            ksinfop->pic_mask[0]);
                        if (pic0 < ksinfop->pic_last_val[0])
                                ksinfop->pic_overflow[0]++;
                        ksinfop->pic_last_val[0] = pic0;
                        pic0 += (ksinfop->pic_overflow[0] & 1) << 31;
                        data_p[1].value.ui64 = (uint64_t)pic0;

                        /*
                         * Generate a 32-bit PIC1 value by detecting overflow
                         */
                        pic1 = (uint32_t)((pic >> ksinfop->pic_shift[1]) &
                            ksinfop->pic_mask[1]);
                        if (pic1 < ksinfop->pic_last_val[1])
                                ksinfop->pic_overflow[1]++;
                        ksinfop->pic_last_val[1] = pic1;
                        pic1 += (ksinfop->pic_overflow[1] & 1) << 31;
                        data_p[2].value.ui64 = (uint64_t)pic1;
                }
#ifdef DEBUG
                if (ni_perf_debug)
                        printf("ni_cntr_kstat_update: rd pcr%d: %lx  "
                            "pic%d: %16lx pic0: %8lx pic1: %8lx\n",
                            ksinfop->pcr_reg, pcr, ksinfop->pic_reg[0], pic,
                            data_p[1].value.ui64, data_p[2].value.ui64);
#endif
        }
        return (stat);
}