root/usr/src/uts/common/io/ib/adapters/hermon/hermon_stats.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.
 */

/*
 * hermon_stats.c
 *    Hermon IB Performance Statistics routines
 *
 *    Implements all the routines necessary for setting up, querying, and
 *    (later) tearing down all the kstats necessary for implementing to
 *    the interfaces necessary to provide busstat(8) access.
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>

#include <sys/ib/adapters/hermon/hermon.h>

static kstat_t *hermon_kstat_picN_create(hermon_state_t *state, int num_pic,
    int num_evt, hermon_ks_mask_t *ev_array);
static kstat_t *hermon_kstat_cntr_create(hermon_state_t *state, int num_pic,
    int (*update)(kstat_t *, int));
static int hermon_kstat_cntr_update(kstat_t *ksp, int rw);

void hermon_kstat_perfcntr64_create(hermon_state_t *state, uint_t port_num);
static int hermon_kstat_perfcntr64_read(hermon_state_t *state, uint_t port,
    int reset);
static void hermon_kstat_perfcntr64_thread_exit(hermon_ks_info_t *ksi);
static int hermon_kstat_perfcntr64_update(kstat_t *ksp, int rw);

/*
 * Hermon IB Performance Events structure
 *    This structure is read-only and is used to setup the individual kstats
 *    and to initialize the tki_ib_perfcnt[] array for each Hermon instance.
 */
hermon_ks_mask_t hermon_ib_perfcnt_list[HERMON_CNTR_NUMENTRIES] = {
        {"port_xmit_data", 0, 0},
        {"port_recv_data", 0, 0},
        {"port_xmit_pkts", 0, 0},
        {"port_recv_pkts", 0, 0},
        {"port_recv_err", 0, 0},
        {"port_xmit_discards", 0, 0},
        {"vl15_dropped", 0, 0},
        {"port_xmit_wait", 0, 0},
        {"port_recv_remote_phys_err", 0, 0},
        {"port_xmit_constraint_err", 0, 0},
        {"port_recv_constraint_err", 0, 0},
        {"symbol_err_counter", 0, 0},
        {"link_err_recovery_cnt", 0, 0},
        {"link_downed_cnt", 0, 0},
        {"excessive_buffer_overruns", 0, 0},
        {"local_link_integrity_err", 0, 0},
        {"clear_pic", 0, 0}
};

/*
 * Return the maximum of (x) and (y)
 */
#define MAX(x, y)       (((x) > (y)) ? (x) : (y))

/*
 * Set (x) to the maximum of (x) and (y)
 */
#define SET_TO_MAX(x, y)        \
{                               \
        if ((x) < (y))          \
                (x) = (y);      \
}

/*
 * hermon_kstat_init()
 *    Context: Only called from attach() path context
 */
int
hermon_kstat_init(hermon_state_t *state)
{
        hermon_ks_info_t                *ksi;
        uint_t                  numports;
        int                     i;

        /* Allocate a kstat info structure */
        ksi = (hermon_ks_info_t *)kmem_zalloc(sizeof (hermon_ks_info_t),
            KM_SLEEP);
        if (ksi == NULL) {
                return (DDI_FAILURE);
        }
        state->hs_ks_info = ksi;

        /*
         * Create as many "pic" and perfcntr64 kstats as we have IB ports.
         * Enable all of the events specified in the "hermon_ib_perfcnt_list"
         * structure.
         */
        numports = state->hs_cfg_profile->cp_num_ports;
        for (i = 0; i < numports; i++) {
                ksi->hki_picN_ksp[i] = hermon_kstat_picN_create(state, i,
                    HERMON_CNTR_NUMENTRIES, hermon_ib_perfcnt_list);
                if (ksi->hki_picN_ksp[i] == NULL) {
                        goto kstat_init_fail;
                }

                hermon_kstat_perfcntr64_create(state, i + 1);
                if (ksi->hki_perfcntr64[i].hki64_ksp == NULL) {
                        goto kstat_init_fail;
                }
        }

        /* Create the "counters" kstat too */
        ksi->hki_cntr_ksp = hermon_kstat_cntr_create(state, numports,
            hermon_kstat_cntr_update);
        if (ksi->hki_cntr_ksp == NULL) {
                goto kstat_init_fail;
        }

        /* Initialize the control register and initial counter values */
        ksi->hki_pcr  = 0;
        ksi->hki_pic0 = 0;
        ksi->hki_pic1 = 0;

        /*
         * Initialize the Hermon hki_ib_perfcnt[] array values using the
         * default values in hermon_ib_perfcnt_list[]
         */
        for (i = 0; i < HERMON_CNTR_NUMENTRIES; i++) {
                ksi->hki_ib_perfcnt[i] = hermon_ib_perfcnt_list[i];
        }

        mutex_init(&ksi->hki_perfcntr64_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&ksi->hki_perfcntr64_cv, NULL, CV_DRIVER, NULL);

        return (DDI_SUCCESS);


kstat_init_fail:

        /* Delete all the previously created kstats */
        if (ksi->hki_cntr_ksp != NULL) {
                kstat_delete(ksi->hki_cntr_ksp);
        }
        for (i = 0; i < numports; i++) {
                if (ksi->hki_picN_ksp[i] != NULL) {
                        kstat_delete(ksi->hki_picN_ksp[i]);
                }
                if (ksi->hki_perfcntr64[i].hki64_ksp != NULL) {
                        kstat_delete(ksi->hki_perfcntr64[i].hki64_ksp);
                }
        }

        /* Free the kstat info structure */
        kmem_free(ksi, sizeof (hermon_ks_info_t));

        return (DDI_FAILURE);
}


/*
 * hermon_kstat_init()
 *    Context: Only called from attach() and/or detach() path contexts
 */
void
hermon_kstat_fini(hermon_state_t *state)
{
        hermon_ks_info_t        *ksi;
        uint_t                  numports;
        int                     i;

        /* Get pointer to kstat info */
        ksi = state->hs_ks_info;

        /*
         * Signal the perfcntr64_update_thread to exit and wait until the
         * thread exits.
         */
        mutex_enter(&ksi->hki_perfcntr64_lock);
        hermon_kstat_perfcntr64_thread_exit(ksi);
        mutex_exit(&ksi->hki_perfcntr64_lock);

        /* Delete all the "pic" and perfcntr64 kstats (one per port) */
        numports = state->hs_cfg_profile->cp_num_ports;
        for (i = 0; i < numports; i++) {
                if (ksi->hki_picN_ksp[i] != NULL) {
                        kstat_delete(ksi->hki_picN_ksp[i]);
                }

                if (ksi->hki_perfcntr64[i].hki64_ksp != NULL) {
                        kstat_delete(ksi->hki_perfcntr64[i].hki64_ksp);
                }
        }

        /* Delete the "counter" kstats (one per port) */
        kstat_delete(ksi->hki_cntr_ksp);

        cv_destroy(&ksi->hki_perfcntr64_cv);
        mutex_destroy(&ksi->hki_perfcntr64_lock);

        /* Free the kstat info structure */
        kmem_free(ksi, sizeof (hermon_ks_info_t));
}


/*
 * hermon_kstat_picN_create()
 *    Context: Only called from attach() path context
 */
static kstat_t *
hermon_kstat_picN_create(hermon_state_t *state, int num_pic, int num_evt,
    hermon_ks_mask_t *ev_array)
{
        kstat_t                 *picN_ksp;
        struct kstat_named      *pic_named_data;
        int                     drv_instance, i;
        char                    *drv_name;
        char                    pic_name[16];

        /*
         * Create the "picN" kstat.  In the steps, below we will attach
         * all of our named event types to it.
         */
        drv_name = (char *)ddi_driver_name(state->hs_dip);
        drv_instance = ddi_get_instance(state->hs_dip);
        (void) sprintf(pic_name, "pic%d", num_pic);
        picN_ksp = kstat_create(drv_name, drv_instance, pic_name, "bus",
            KSTAT_TYPE_NAMED, num_evt, 0);
        if (picN_ksp == NULL) {
                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 separately below (as its pic
         * value must be inverted).
         */
        for (i = 0; i < num_evt - 1; i++) {
                pic_named_data[i].value.ui64 =
                    ((uint64_t)i << (num_pic * HERMON_CNTR_SIZE));
                kstat_named_init(&pic_named_data[i], ev_array[i].ks_evt_name,
                    KSTAT_DATA_UINT64);
        }

        /* Add the "clear_pic" entry */
        pic_named_data[i].value.ui64 =
            ~((uint64_t)HERMON_CNTR_MASK << (num_pic * HERMON_CNTR_SIZE));
        kstat_named_init(&pic_named_data[i], ev_array[i].ks_evt_name,
            KSTAT_DATA_UINT64);

        /* Install the kstat */
        kstat_install(picN_ksp);

        return (picN_ksp);
}


/*
 * hermon_kstat_cntr_create()
 *    Context: Only called from attach() path context
 */
static kstat_t *
hermon_kstat_cntr_create(hermon_state_t *state, int num_pic,
    int (*update)(kstat_t *, int))
{
        struct kstat            *cntr_ksp;
        struct kstat_named      *cntr_named_data;
        int                     drv_instance, i;
        char                    *drv_name;
        char                    pic_str[16];

        /*
         * Create the "counters" kstat.  In the steps, below we will attach
         * all of our "pic" to it.   Note:  The size of this kstat is
         * num_pic + 1 because it also contains the "%pcr".
         */
        drv_name = (char *)ddi_driver_name(state->hs_dip);
        drv_instance = ddi_get_instance(state->hs_dip);
        cntr_ksp = kstat_create(drv_name, drv_instance, "counters", "bus",
            KSTAT_TYPE_NAMED, num_pic + 1, KSTAT_FLAG_WRITABLE);
        if (cntr_ksp == NULL) {
                return (NULL);
        }
        cntr_named_data = (struct kstat_named *)(cntr_ksp->ks_data);

        /*
         * Initialize the named kstats (for the "pcr" and for the
         * individual "pic" kstats)
         */
        kstat_named_init(&cntr_named_data[0], "pcr", KSTAT_DATA_UINT64);
        for (i = 0; i < num_pic; i++) {
                (void) sprintf(pic_str, "pic%d", i);
                kstat_named_init(&cntr_named_data[i+1], pic_str,
                    KSTAT_DATA_UINT64);
        }

        /*
         * Store the Hermon softstate pointer in the kstat's private field so
         * that it is available to the update function.
         */
        cntr_ksp->ks_private = (void *)state;
        cntr_ksp->ks_update  = update;

        /* Install the kstat */
        kstat_install(cntr_ksp);

        return (cntr_ksp);
}


/*
 * hermon_kstat_cntr_update()
 *    Context: Called from the kstat context
 */
static int
hermon_kstat_cntr_update(kstat_t *ksp, int rw)
{
        hermon_state_t          *state;
        hermon_ks_mask_t                *ib_perf;
        hermon_ks_info_t                *ksi;
        struct kstat_named      *data;
        uint64_t                pcr;
        uint32_t                tmp;
        uint32_t                oldval;
        uint_t                  numports, indx;
        int                     status;
        hermon_hw_sm_perfcntr_t sm_perfcntr;

        /*
         * Extract the Hermon softstate pointer, kstat data, pointer to the
         * kstat info structure, and pointer to the hki_ib_perfcnt[] array
         * from the input parameters.  Note: For warlock purposes, these
         * parameters are all accessed only in this routine and are,
         * therefore, protected by the lock used by the kstat framework.
         */
        state   = ksp->ks_private;
        data    = (struct kstat_named *)(ksp->ks_data);
        ksi     = state->hs_ks_info;
        ib_perf = &ksi->hki_ib_perfcnt[0];
        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*ksi))
        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*ib_perf))

        /*
         * Depending on whether we are reading the "pic" counters or
         * writing the "pcr" control register, we need to handle and
         * fill in the kstat data appropriately.
         *
         * If this is a write to the "pcr", then extract the value from
         * the kstat data and store it in the kstat info structure.
         *
         * Otherwise, if this is a read of the "pic" counter(s), then
         * extract the register offset, size, and mask values from the
         * ib_perf[] array.  Then read the corresponding register and store
         * it into the kstat data.  Note:  We only read/fill in pic1 if more
         * than one port is configured.
         */
        numports = state->hs_cfg_profile->cp_num_ports;
        if (rw == KSTAT_WRITE) {
                /* Update the stored "pcr" value */
                ksi->hki_pcr = data[0].value.ui64;
                return (0);
        } else {
                /*
                 * Get the current "pcr" value and extract the lower
                 * portion (corresponding to the counters for "pic0")
                 */
                pcr  = ksi->hki_pcr;
                indx = pcr & HERMON_CNTR_MASK;
                data[0].value.ui64 = pcr;

                /*
                 * Fill in the "pic0" counter, corresponding to port 1.
                 * This involves reading in the current value in the register
                 * and calculating how many events have happened since this
                 * register was last polled.  Then we save away the current
                 * value for the counter and increment the "pic0" total by
                 * the number of new events.
                 */
                oldval = ib_perf[indx].ks_old_pic0;

                status = hermon_getperfcntr_cmd_post(state, 1,
                    HERMON_CMD_NOSLEEP_SPIN, &sm_perfcntr, 0);
                if (status != HERMON_CMD_SUCCESS) {
                        return (-1);
                }
                switch (indx) {
                case 0:         /* port_xmit_data */
                        tmp = sm_perfcntr.portxmdata;
                        break;
                case 1:         /* port_recv_data */
                        tmp = sm_perfcntr.portrcdata;
                        break;
                case 2:         /* port_xmit_pkts */
                        tmp = sm_perfcntr.portxmpkts;
                        break;
                case 3:         /* port_recv_pkts */
                        tmp = sm_perfcntr.portrcpkts;
                        break;
                case 4:         /* port_recv_err */
                        tmp = sm_perfcntr.portrcv;
                        break;
                case 5:         /* port_xmit_discards */
                        tmp = sm_perfcntr.portxmdiscard;
                        break;
                case 6:         /* vl15_dropped */
                        tmp = sm_perfcntr.vl15drop;
                        break;
                case 7:         /* port_xmit_wait */
                        tmp = sm_perfcntr.portxmwait;
                        break;
                case 8:         /* port_recv_remote_phys_err */
                        tmp = sm_perfcntr.portrcvrem;
                        break;
                case 9:         /* port_xmit_constraint_err */
                        tmp = sm_perfcntr.portxmconstr;
                        break;
                case 10:        /* port_recv_constraint_err */
                        tmp = sm_perfcntr.portrcconstr;
                        break;
                case 11:        /* symbol_err_counter */
                        tmp = sm_perfcntr.symerr;
                        break;
                case 12:        /* link_err_recovery_cnt */
                        tmp = sm_perfcntr.linkerrrec;
                        break;
                case 13:        /* link_downed_cnt */
                        tmp = sm_perfcntr.linkdown;
                        break;
                case 14:        /* excessive_buffer_overruns */
                        tmp = sm_perfcntr.xsbuffovrun;
                        break;
                case 15:        /* local_link_integrity_err */
                        tmp = sm_perfcntr.locallinkint;
                        break;
                case 16:        /* clear_pic */
                        tmp = 0;        /* XXX */
                        break;
                default:
                        cmn_err(CE_CONT, "perf counter out of range\n");
                }

                ib_perf[indx].ks_old_pic0 = tmp;

                tmp = tmp - oldval;
                ksi->hki_pic0 += tmp;
                data[1].value.ui64 = ksi->hki_pic0;

                /*
                 * If necessary, fill in the "pic1" counter for port 2.
                 * This works the same as above except that we extract the
                 * upper bits (corresponding to the counters for "pic1")
                 */
                if (numports == HERMON_MAX_PORTS) {
                        indx   = pcr >> HERMON_CNTR_SIZE;
                        oldval = ib_perf[indx].ks_old_pic1;

                        status = hermon_getperfcntr_cmd_post(state, 2,
                            HERMON_CMD_NOSLEEP_SPIN, &sm_perfcntr, 0);
                        if (status != HERMON_CMD_SUCCESS) {
                                return (-1);
                        }
                        switch (indx) {
                        case 0:         /* port_xmit_data */
                                tmp = sm_perfcntr.portxmdata;
                                break;
                        case 1:         /* port_recv_data */
                                tmp = sm_perfcntr.portrcdata;
                                break;
                        case 2:         /* port_xmit_pkts */
                                tmp = sm_perfcntr.portxmpkts;
                                break;
                        case 3:         /* port_recv_pkts */
                                tmp = sm_perfcntr.portrcpkts;
                                break;
                        case 4:         /* port_recv_err */
                                tmp = sm_perfcntr.portrcv;
                                break;
                        case 5:         /* port_xmit_discards */
                                tmp = sm_perfcntr.portxmdiscard;
                                break;
                        case 6:         /* vl15_dropped */
                                tmp = sm_perfcntr.vl15drop;
                                break;
                        case 7:         /* port_xmit_wait */
                                tmp = sm_perfcntr.portxmwait;
                                break;
                        case 8:         /* port_recv_remote_phys_err */
                                tmp = sm_perfcntr.portrcvrem;
                                break;
                        case 9:         /* port_xmit_constraint_err */
                                tmp = sm_perfcntr.portxmconstr;
                                break;
                        case 10:        /* port_recv_constraint_err */
                                tmp = sm_perfcntr.portrcconstr;
                                break;
                        case 11:        /* symbol_err_counter */
                                tmp = sm_perfcntr.symerr;
                                break;
                        case 12:        /* link_err_recovery_cnt */
                                tmp = sm_perfcntr.linkerrrec;
                                break;
                        case 13:        /* link_downed_cnt */
                                tmp = sm_perfcntr.linkdown;
                                break;
                        case 14:        /* excessive_buffer_overruns */
                                tmp = sm_perfcntr.xsbuffovrun;
                                break;
                        case 15:        /* local_link_integrity_err */
                                tmp = sm_perfcntr.locallinkint;
                                break;
                        case 16:        /* clear_pic */
                                tmp = 0;        /* XXX */
                                break;
                        default:
                                cmn_err(CE_CONT, "perf counter out of range\n");
                        }

                        ib_perf[indx].ks_old_pic1 = tmp;

                        tmp = tmp - oldval;
                        ksi->hki_pic1 += tmp;
                        data[2].value.ui64 = ksi->hki_pic1;
                }

                return (0);
        }
}

/*
 * 64 bit kstats for performance counters:
 *
 * Export 64 bit performance counters in kstats.
 *
 * If the HCA hardware supports 64 bit extended port counters, we use the
 * hardware based counters. If the HCA hardware does not support extended port
 * counters, we maintain 64 bit performance counters in software using the
 * 32 bit hardware port counters.
 *
 * The software based counters are maintained as follows:
 *
 * We create a thread that, every one second, reads the values of 32 bit
 * hardware counters and adds them to the 64 bit software counters. Immediately
 * after reading, it resets the 32 bit hardware counters to zero (so that they
 * start counting from zero again). At any time the current value of a counter
 * is going to be the sum of the 64 bit software counter and the 32 bit
 * hardware counter.
 *
 * Since this work need not be done if there is no consumer, by default
 * we do not maintain 64 bit software counters. To enable this the consumer
 * needs to write a non-zero value to the "enable" component of the of
 * perf_counters kstat. Writing zero to this component will disable this work.
 * NOTE: The enabling or disabling applies to software based counters only.
 * Hardware based counters counters are always enabled.
 *
 * If performance monitor is enabled in subnet manager, the SM could
 * periodically reset the hardware counters by sending perf-MADs. So only
 * one of either our software 64 bit counters or the SM performance monitor
 * could be enabled at the same time. However, if both of them are enabled at
 * the same time we still do our best by keeping track of the values of the
 * last read 32 bit hardware counters. If the current read of a 32 bit hardware
 * counter is less than the last read of the counter, we ignore the current
 * value and go with the last read value.
 */

/*
 * hermon_kstat_perfcntr64_create()
 *    Context: Only called from attach() path context
 *
 * Create "port#/perf_counters" kstat for the specified port number.
 */
void
hermon_kstat_perfcntr64_create(hermon_state_t *state, uint_t port_num)
{
        hermon_ks_info_t        *ksi = state->hs_ks_info;
        struct kstat            *cntr_ksp;
        struct kstat_named      *cntr_named_data;
        int                     drv_instance;
        char                    *drv_name;
        char                    kname[32];
        int                     status, ext_width_supported;

        ASSERT(port_num != 0);

        status = hermon_is_ext_port_counters_supported(state, port_num,
            HERMON_CMD_NOSLEEP_SPIN, &ext_width_supported);
        if (status == HERMON_CMD_SUCCESS) {
                ksi->hki_perfcntr64[port_num - 1].
                    hki64_ext_port_counters_supported = ext_width_supported;
        }

        drv_name = (char *)ddi_driver_name(state->hs_dip);
        drv_instance = ddi_get_instance(state->hs_dip);
        (void) snprintf(kname, sizeof (kname), "port%u/perf_counters",
            port_num);
        cntr_ksp = kstat_create(drv_name, drv_instance, kname, "ib",
            KSTAT_TYPE_NAMED, HERMON_PERFCNTR64_NUM_COUNTERS,
            KSTAT_FLAG_WRITABLE);
        if (cntr_ksp == NULL) {
                return;
        }
        cntr_named_data = (struct kstat_named *)(cntr_ksp->ks_data);

        kstat_named_init(&cntr_named_data[HERMON_PERFCNTR64_ENABLE_IDX],
            "enable", KSTAT_DATA_UINT32);
        kstat_named_init(&cntr_named_data[HERMON_PERFCNTR64_XMIT_DATA_IDX],
            "xmit_data", KSTAT_DATA_UINT64);
        kstat_named_init(&cntr_named_data[HERMON_PERFCNTR64_RECV_DATA_IDX],
            "recv_data", KSTAT_DATA_UINT64);
        kstat_named_init(&cntr_named_data[HERMON_PERFCNTR64_XMIT_PKTS_IDX],
            "xmit_pkts", KSTAT_DATA_UINT64);
        kstat_named_init(&cntr_named_data[HERMON_PERFCNTR64_RECV_PKTS_IDX],
            "recv_pkts", KSTAT_DATA_UINT64);

        ksi->hki_perfcntr64[port_num - 1].hki64_ksp = cntr_ksp;
        ksi->hki_perfcntr64[port_num - 1].hki64_port_num = port_num;
        ksi->hki_perfcntr64[port_num - 1].hki64_state = state;

        cntr_ksp->ks_private = &ksi->hki_perfcntr64[port_num - 1];
        cntr_ksp->ks_update  = hermon_kstat_perfcntr64_update;

        /* Install the kstat */
        kstat_install(cntr_ksp);
}

/*
 * hermon_kstat_perfcntr64_read()
 *
 * Read the values of 32 bit hardware counters.
 *
 * If reset is true, reset the 32 bit hardware counters. Add the values of the
 * 32 bit hardware counters to the 64 bit software counters.
 *
 * If reset is false, just save the values read from the 32 bit hardware
 * counters in hki64_last_read[].
 *
 * See the general comment on the 64 bit performance counters
 * regarding the use of last read 32 bit hardware counter values.
 */
static int
hermon_kstat_perfcntr64_read(hermon_state_t *state, uint_t port, int reset)
{
        hermon_ks_info_t        *ksi = state->hs_ks_info;
        hermon_perfcntr64_ks_info_t *ksi64 = &ksi->hki_perfcntr64[port - 1];
        int                     status, i;
        uint32_t                tmp;
        hermon_hw_sm_perfcntr_t sm_perfcntr;

        ASSERT(MUTEX_HELD(&ksi->hki_perfcntr64_lock));
        ASSERT(port != 0);

        /* read the 32 bit hardware counters */
        status = hermon_getperfcntr_cmd_post(state, port,
            HERMON_CMD_NOSLEEP_SPIN, &sm_perfcntr, 0);
        if (status != HERMON_CMD_SUCCESS) {
                return (status);
        }

        if (reset) {
                /* reset the hardware counters */
                status = hermon_getperfcntr_cmd_post(state, port,
                    HERMON_CMD_NOSLEEP_SPIN, NULL, 1);
                if (status != HERMON_CMD_SUCCESS) {
                        return (status);
                }

                /*
                 * Update 64 bit software counters
                 */
                tmp = MAX(sm_perfcntr.portxmdata,
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_DATA_IDX]);
                ksi64->hki64_counters[HERMON_PERFCNTR64_XMIT_DATA_IDX] += tmp;

                tmp = MAX(sm_perfcntr.portrcdata,
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_DATA_IDX]);
                ksi64->hki64_counters[HERMON_PERFCNTR64_RECV_DATA_IDX] += tmp;

                tmp = MAX(sm_perfcntr.portxmpkts,
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_PKTS_IDX]);
                ksi64->hki64_counters[HERMON_PERFCNTR64_XMIT_PKTS_IDX] += tmp;

                tmp = MAX(sm_perfcntr.portrcpkts,
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_PKTS_IDX]);
                ksi64->hki64_counters[HERMON_PERFCNTR64_RECV_PKTS_IDX] += tmp;

                for (i = 0; i < HERMON_PERFCNTR64_NUM_COUNTERS; i++)
                        ksi64->hki64_last_read[i] = 0;

        } else {
                /*
                 * Update ksi64->hki64_last_read[]
                 */
                SET_TO_MAX(
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_DATA_IDX],
                    sm_perfcntr.portxmdata);

                SET_TO_MAX(
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_DATA_IDX],
                    sm_perfcntr.portrcdata);

                SET_TO_MAX(
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_PKTS_IDX],
                    sm_perfcntr.portxmpkts);

                SET_TO_MAX(
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_PKTS_IDX],
                    sm_perfcntr.portrcpkts);
        }

        return (HERMON_CMD_SUCCESS);
}

/*
 * hermon_kstat_perfcntr64_update_thread()
 *    Context: Entry point for a kernel thread
 *
 * Maintain 64 bit performance counters in software using the 32 bit
 * hardware counters.
 */
static void
hermon_kstat_perfcntr64_update_thread(void *arg)
{
        hermon_state_t          *state = (hermon_state_t *)arg;
        hermon_ks_info_t        *ksi = state->hs_ks_info;
        uint_t                  i;
        clock_t                 delta = drv_usectohz(1000000);

        mutex_enter(&ksi->hki_perfcntr64_lock);
        /*
         * Every one second update the values 64 bit software counters
         * for all ports. Exit if HERMON_PERFCNTR64_THREAD_EXIT flag is set.
         */
        while (!(ksi->hki_perfcntr64_flags & HERMON_PERFCNTR64_THREAD_EXIT)) {
                for (i = 0; i < state->hs_cfg_profile->cp_num_ports; i++) {
                        if (ksi->hki_perfcntr64[i].hki64_enabled) {
                                (void) hermon_kstat_perfcntr64_read(state,
                                    i + 1, 1);
                        }
                }
                /* sleep for a second */
                (void) cv_reltimedwait(&ksi->hki_perfcntr64_cv,
                    &ksi->hki_perfcntr64_lock, delta, TR_CLOCK_TICK);
        }
        ksi->hki_perfcntr64_flags = 0;
        mutex_exit(&ksi->hki_perfcntr64_lock);
}

/*
 * hermon_kstat_perfcntr64_thread_create()
 *    Context: Called from the kstat context
 *
 * Create a thread that maintains 64 bit performance counters in software.
 */
static void
hermon_kstat_perfcntr64_thread_create(hermon_state_t *state)
{
        hermon_ks_info_t        *ksi = state->hs_ks_info;
        kthread_t               *thr;

        ASSERT(MUTEX_HELD(&ksi->hki_perfcntr64_lock));

        /*
         * One thread per hermon instance. Don't create a thread if already
         * created.
         */
        if (!(ksi->hki_perfcntr64_flags & HERMON_PERFCNTR64_THREAD_CREATED)) {
                thr = thread_create(NULL, 0,
                    hermon_kstat_perfcntr64_update_thread,
                    state, 0, &p0, TS_RUN, minclsyspri);
                ksi->hki_perfcntr64_thread_id = thr->t_did;
                ksi->hki_perfcntr64_flags |= HERMON_PERFCNTR64_THREAD_CREATED;
        }
}

/*
 * hermon_kstat_perfcntr64_thread_exit()
 *    Context: Called from attach, detach or kstat context
 */
static void
hermon_kstat_perfcntr64_thread_exit(hermon_ks_info_t *ksi)
{
        kt_did_t        tid;

        ASSERT(MUTEX_HELD(&ksi->hki_perfcntr64_lock));

        if (ksi->hki_perfcntr64_flags & HERMON_PERFCNTR64_THREAD_CREATED) {
                /*
                 * Signal the thread to exit and wait until the thread exits.
                 */
                ksi->hki_perfcntr64_flags |= HERMON_PERFCNTR64_THREAD_EXIT;
                tid = ksi->hki_perfcntr64_thread_id;
                cv_signal(&ksi->hki_perfcntr64_cv);

                mutex_exit(&ksi->hki_perfcntr64_lock);
                thread_join(tid);
                mutex_enter(&ksi->hki_perfcntr64_lock);
        }
}

/*
 * hermon_kstat_perfcntr64_update_ext()
 *    Context: Called from the kstat context
 *
 * Update perf_counters kstats with the values of the extended port counters
 * from the hardware.
 */
static int
hermon_kstat_perfcntr64_update_ext(hermon_perfcntr64_ks_info_t *ksi64, int rw,
    struct kstat_named *data)
{
        hermon_hw_sm_extperfcntr_t      sm_extperfcntr;

        /*
         * The "enable" component of the kstat is the only writable kstat.
         * It is a no-op when the hardware supports extended port counters.
         */
        if (rw == KSTAT_WRITE)
                return (0);

        /*
         * Read the counters and update kstats.
         */
        if (hermon_getextperfcntr_cmd_post(ksi64->hki64_state,
            ksi64->hki64_port_num, HERMON_CMD_NOSLEEP_SPIN, &sm_extperfcntr) !=
            HERMON_CMD_SUCCESS) {
                return (EIO);
        }

        data[HERMON_PERFCNTR64_ENABLE_IDX].value.ui32 = 1;

        data[HERMON_PERFCNTR64_XMIT_DATA_IDX].value.ui64 =
            sm_extperfcntr.portxmdata;

        data[HERMON_PERFCNTR64_RECV_DATA_IDX].value.ui64 =
            sm_extperfcntr.portrcdata;

        data[HERMON_PERFCNTR64_XMIT_PKTS_IDX].value.ui64 =
            sm_extperfcntr.portxmpkts;

        data[HERMON_PERFCNTR64_RECV_PKTS_IDX].value.ui64 =
            sm_extperfcntr.portrcpkts;

        return (0);
}

/*
 * hermon_kstat_perfcntr64_update()
 *    Context: Called from the kstat context
 *
 * See the general comment on 64 bit kstats for performance counters:
 */
static int
hermon_kstat_perfcntr64_update(kstat_t *ksp, int rw)
{
        hermon_state_t                  *state;
        struct kstat_named              *data;
        hermon_ks_info_t                *ksi;
        hermon_perfcntr64_ks_info_t     *ksi64;
        int                             i, thr_exit;
        int                             rv;

        ksi64   = ksp->ks_private;
        state   = ksi64->hki64_state;
        ksi     = state->hs_ks_info;
        data    = (struct kstat_named *)(ksp->ks_data);

        mutex_enter(&ksi->hki_perfcntr64_lock);

        if (ksi64->hki64_ext_port_counters_supported) {
                rv = hermon_kstat_perfcntr64_update_ext(ksi64, rw, data);
                mutex_exit(&ksi->hki_perfcntr64_lock);
                return (rv);
        }

        /*
         * 64 bit performance counters maintained by the software is not
         * enabled by default. Enable them upon a writing a non-zero value
         * to "enable" kstat. Disable them upon a writing zero to the
         * "enable" kstat.
         */
        if (rw == KSTAT_WRITE) {
                if (data[HERMON_PERFCNTR64_ENABLE_IDX].value.ui32) {
                        if (ksi64->hki64_enabled == 0) {
                                /*
                                 * Reset the hardware counters to ensure that
                                 * the hardware counter doesn't max out
                                 * (and hence stop counting) before we get
                                 * a chance to reset the counter in
                                 * hermon_kstat_perfcntr64_update_thread.
                                 */
                                if (hermon_getperfcntr_cmd_post(state,
                                    ksi64->hki64_port_num,
                                    HERMON_CMD_NOSLEEP_SPIN, NULL, 1) !=
                                    HERMON_CMD_SUCCESS) {
                                        mutex_exit(&ksi->hki_perfcntr64_lock);
                                        return (EIO);
                                }

                                /* Enable 64 bit software counters */
                                ksi64->hki64_enabled = 1;
                                for (i = 0;
                                    i < HERMON_PERFCNTR64_NUM_COUNTERS; i++) {
                                        ksi64->hki64_counters[i] = 0;
                                        ksi64->hki64_last_read[i] = 0;
                                }
                                hermon_kstat_perfcntr64_thread_create(state);
                        }

                } else if (ksi64->hki64_enabled) {
                        /* Disable 64 bit software counters */
                        ksi64->hki64_enabled = 0;
                        thr_exit = 1;
                        for (i = 0; i < state->hs_cfg_profile->cp_num_ports;
                            i++) {
                                if (ksi->hki_perfcntr64[i].hki64_enabled) {
                                        thr_exit = 0;
                                        break;
                                }
                        }
                        if (thr_exit)
                                hermon_kstat_perfcntr64_thread_exit(ksi);
                }
        } else if (ksi64->hki64_enabled) {
                /*
                 * Read the counters and update kstats.
                 */
                if (hermon_kstat_perfcntr64_read(state, ksi64->hki64_port_num,
                    0) != HERMON_CMD_SUCCESS) {
                        mutex_exit(&ksi->hki_perfcntr64_lock);
                        return (EIO);
                }

                data[HERMON_PERFCNTR64_ENABLE_IDX].value.ui32 = 1;

                data[HERMON_PERFCNTR64_XMIT_DATA_IDX].value.ui64 =
                    ksi64->hki64_counters[HERMON_PERFCNTR64_XMIT_DATA_IDX] +
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_DATA_IDX];

                data[HERMON_PERFCNTR64_RECV_DATA_IDX].value.ui64 =
                    ksi64->hki64_counters[HERMON_PERFCNTR64_RECV_DATA_IDX] +
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_DATA_IDX];

                data[HERMON_PERFCNTR64_XMIT_PKTS_IDX].value.ui64 =
                    ksi64->hki64_counters[HERMON_PERFCNTR64_XMIT_PKTS_IDX] +
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_XMIT_PKTS_IDX];

                data[HERMON_PERFCNTR64_RECV_PKTS_IDX].value.ui64 =
                    ksi64->hki64_counters[HERMON_PERFCNTR64_RECV_PKTS_IDX] +
                    ksi64->hki64_last_read[HERMON_PERFCNTR64_RECV_PKTS_IDX];

        } else {
                /* return 0 in kstats if not enabled */
                data[HERMON_PERFCNTR64_ENABLE_IDX].value.ui32 = 0;
                for (i = 1; i < HERMON_PERFCNTR64_NUM_COUNTERS; i++)
                        data[i].value.ui64 = 0;
        }

        mutex_exit(&ksi->hki_perfcntr64_lock);
        return (0);
}