root/usr/src/lib/libdladm/common/libdlstat.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.
 */

/*
 * Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <kstat.h>
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <sys/dld.h>
#include <sys/ddi.h>

#include <libdllink.h>
#include <libdlflow.h>
#include <libdlstat.h>
#include <libdlaggr.h>

struct flowlist {
        char            flowname[MAXFLOWNAMELEN];
        char            linkname[MAXLINKNAMELEN];
        datalink_id_t   linkid;
        int             fd;
        uint64_t        ifspeed;
        boolean_t       first;
        boolean_t       display;
        pktsum_t        prevstats;
        pktsum_t        diffstats;
};

pktsum_t                totalstats;
struct flowlist         *stattable = NULL;

#define STATGROWSIZE    16

/* Exported functions */

/*
 * dladm_kstat_lookup() is a modified version of kstat_lookup which
 * adds the class as a selector.
 */
kstat_t *
dladm_kstat_lookup(kstat_ctl_t *kcp, const char *module, int instance,
    const char *name, const char *class)
{
        kstat_t *ksp = NULL;

        for (ksp = kcp->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
                if ((module == NULL || strcmp(ksp->ks_module, module) == 0) &&
                    (instance == -1 || ksp->ks_instance == instance) &&
                    (name == NULL || strcmp(ksp->ks_name, name) == 0) &&
                    (class == NULL || strcmp(ksp->ks_class, class) == 0))
                        return (ksp);
        }

        errno = ENOENT;
        return (NULL);
}

/*
 * dladm_get_stats() populates the supplied pktsum_t structure with
 * the input and output  packet and byte kstats from the kstat_t
 * found with dladm_kstat_lookup.
 */
void
dladm_get_stats(kstat_ctl_t *kcp, kstat_t *ksp, pktsum_t *stats)
{

        if (kstat_read(kcp, ksp, NULL) == -1)
                return;

        stats->snaptime = gethrtime();

        if (dladm_kstat_value(ksp, "ipackets64", KSTAT_DATA_UINT64,
            &stats->ipackets) < 0) {
                if (dladm_kstat_value(ksp, "ipackets", KSTAT_DATA_UINT64,
                    &stats->ipackets) < 0)
                        return;
        }

        if (dladm_kstat_value(ksp, "opackets64", KSTAT_DATA_UINT64,
            &stats->opackets) < 0) {
                if (dladm_kstat_value(ksp, "opackets", KSTAT_DATA_UINT64,
                    &stats->opackets) < 0)
                        return;
        }

        if (dladm_kstat_value(ksp, "rbytes64", KSTAT_DATA_UINT64,
            &stats->rbytes) < 0) {
                if (dladm_kstat_value(ksp, "rbytes", KSTAT_DATA_UINT64,
                    &stats->rbytes) < 0)
                        return;
        }

        if (dladm_kstat_value(ksp, "obytes64", KSTAT_DATA_UINT64,
            &stats->obytes) < 0) {
                if (dladm_kstat_value(ksp, "obytes", KSTAT_DATA_UINT64,
                    &stats->obytes) < 0)
                        return;
        }

        if (dladm_kstat_value(ksp, "ierrors", KSTAT_DATA_UINT32,
            &stats->ierrors) < 0) {
                if (dladm_kstat_value(ksp, "ierrors", KSTAT_DATA_UINT64,
                    &stats->ierrors) < 0)
                return;
        }

        if (dladm_kstat_value(ksp, "oerrors", KSTAT_DATA_UINT32,
            &stats->oerrors) < 0) {
                if (dladm_kstat_value(ksp, "oerrors", KSTAT_DATA_UINT64,
                    &stats->oerrors) < 0)
                        return;
        }
}

int
dladm_kstat_value(kstat_t *ksp, const char *name, uint8_t type, void *buf)
{
        kstat_named_t   *knp;

        if ((knp = kstat_data_lookup(ksp, (char *)name)) == NULL)
                return (-1);

        if (knp->data_type != type)
                return (-1);

        switch (type) {
        case KSTAT_DATA_UINT64:
                *(uint64_t *)buf = knp->value.ui64;
                break;
        case KSTAT_DATA_UINT32:
                *(uint32_t *)buf = knp->value.ui32;
                break;
        default:
                return (-1);
        }

        return (0);
}

dladm_status_t
dladm_get_single_mac_stat(dladm_handle_t handle, datalink_id_t linkid,
    const char *name, uint8_t type, void *val)
{
        char            module[DLPI_LINKNAME_MAX];
        uint_t          instance;
        char            link[DLPI_LINKNAME_MAX];
        dladm_status_t  status;
        uint32_t        flags, media;
        kstat_t         *ksp;
        dladm_phys_attr_t dpap;

        if ((status = dladm_datalink_id2info(handle, linkid, &flags, NULL,
            &media, link, DLPI_LINKNAME_MAX)) != DLADM_STATUS_OK)
                return (status);

        if (media != DL_ETHER)
                return (DLADM_STATUS_LINKINVAL);

        status = dladm_phys_info(handle, linkid, &dpap, DLADM_OPT_PERSIST);

        if (status != DLADM_STATUS_OK)
                return (status);

        status = dladm_parselink(dpap.dp_dev, module, &instance);

        if (status != DLADM_STATUS_OK)
                return (status);

        /*
         * The kstat query could fail if the underlying MAC
         * driver was already detached.
         */
        if (dladm_dld_kcp(handle) == NULL) {
                warn("kstat_open operation failed");
                return (-1);
        }

        if ((ksp = kstat_lookup(dladm_dld_kcp(handle), module, instance,
            "mac")) == NULL &&
            (ksp = kstat_lookup(dladm_dld_kcp(handle), module, instance,
            NULL)) == NULL) {
                goto bail;
        }

        if (kstat_read(dladm_dld_kcp(handle), ksp, NULL) == -1)
                goto bail;

        if (dladm_kstat_value(ksp, name, type, val) < 0)
                goto bail;

        return (DLADM_STATUS_OK);

bail:
        return (dladm_errno2status(errno));
}

/* Compute sum of 2 pktsums (s1 = s2 + s3) */
void
dladm_stats_total(pktsum_t *s1, pktsum_t *s2, pktsum_t *s3)
{
        s1->rbytes    = s2->rbytes    + s3->rbytes;
        s1->ipackets  = s2->ipackets  + s3->ipackets;
        s1->ierrors   = s2->ierrors   + s3->ierrors;
        s1->obytes    = s2->obytes    + s3->obytes;
        s1->opackets  = s2->opackets  + s3->opackets;
        s1->oerrors   = s2->oerrors   + s3->oerrors;
        s1->snaptime  = s2->snaptime;
}

#define DIFF_STAT(s2, s3) ((s2) > (s3) ? ((s2) - (s3)) : 0)


/* Compute differences between 2 pktsums (s1 = s2 - s3) */
void
dladm_stats_diff(pktsum_t *s1, pktsum_t *s2, pktsum_t *s3)
{
        s1->rbytes    = DIFF_STAT(s2->rbytes,   s3->rbytes);
        s1->ipackets  = DIFF_STAT(s2->ipackets, s3->ipackets);
        s1->ierrors   = DIFF_STAT(s2->ierrors,  s3->ierrors);
        s1->obytes    = DIFF_STAT(s2->obytes,   s3->obytes);
        s1->opackets  = DIFF_STAT(s2->opackets, s3->opackets);
        s1->oerrors   = DIFF_STAT(s2->oerrors,  s3->oerrors);
        s1->snaptime  = DIFF_STAT(s2->snaptime, s3->snaptime);
}

#define DLSTAT_MAC_RX_SWLANE    "mac_rx_swlane"
#define DLSTAT_MAC_RX_HWLANE    "mac_rx_hwlane"
#define DLSTAT_MAC_TX_SWLANE    "mac_tx_swlane"
#define DLSTAT_MAC_TX_HWLANE    "mac_tx_hwlane"
#define DLSTAT_MAC_MISC_STAT    "mac_misc_stat"
#define DLSTAT_MAC_RX_RING      "mac_rx_ring"
#define DLSTAT_MAC_TX_RING      "mac_tx_ring"
#define DLSTAT_MAC_FANOUT       "mac_rx_swlane0_fanout"

typedef struct {
        const char      *si_name;
        uint_t          si_offset;
} stat_info_t;

#define A_CNT(arr)      (sizeof (arr) / sizeof (arr[0]))

/* Definitions for rx lane stats */
#define RL_OFF(f)       (offsetof(rx_lane_stat_t, f))

static  stat_info_t     rx_hwlane_stats_list[] = {
        {"ipackets",            RL_OFF(rl_ipackets)},
        {"rbytes",              RL_OFF(rl_rbytes)},
        {"intrs",               RL_OFF(rl_intrs)},
        {"intrbytes",           RL_OFF(rl_intrbytes)},
        {"polls",               RL_OFF(rl_polls)},
        {"pollbytes",           RL_OFF(rl_pollbytes)},
        {"rxsdrops",            RL_OFF(rl_sdrops)},
        {"chainunder10",        RL_OFF(rl_chl10)},
        {"chain10to50",         RL_OFF(rl_ch10_50)},
        {"chainover50",         RL_OFF(rl_chg50)}
};
#define RX_HWLANE_STAT_SIZE     A_CNT(rx_hwlane_stats_list)

static  stat_info_t     rx_swlane_stats_list[] = {
        {"ipackets",            RL_OFF(rl_ipackets)},
        {"rbytes",              RL_OFF(rl_rbytes)},
        {"local",               RL_OFF(rl_lclpackets)},
        {"localbytes",          RL_OFF(rl_lclbytes)},
        {"intrs",               RL_OFF(rl_intrs)},
        {"intrbytes",           RL_OFF(rl_intrbytes)},
        {"rxsdrops",            RL_OFF(rl_sdrops)}
};
#define RX_SWLANE_STAT_SIZE     A_CNT(rx_swlane_stats_list)

static  stat_info_t     rx_lane_stats_list[] = {
        {"ipackets",            RL_OFF(rl_ipackets)},
        {"rbytes",              RL_OFF(rl_rbytes)},
        {"local",               RL_OFF(rl_lclpackets)},
        {"localbytes",          RL_OFF(rl_lclbytes)},
        {"intrs",               RL_OFF(rl_intrs)},
        {"intrbytes",           RL_OFF(rl_intrbytes)},
        {"polls",               RL_OFF(rl_polls)},
        {"rxsdrops",            RL_OFF(rl_sdrops)},
        {"pollbytes",           RL_OFF(rl_pollbytes)},
        {"chainunder10",        RL_OFF(rl_chl10)},
        {"chain10to50",         RL_OFF(rl_ch10_50)},
        {"chainover50",         RL_OFF(rl_chg50)}
};
#define RX_LANE_STAT_SIZE       A_CNT(rx_lane_stats_list)

/* Definitions for tx lane stats */
#define TL_OFF(f)       (offsetof(tx_lane_stat_t, f))

static  stat_info_t     tx_lane_stats_list[] = {
        {"opackets",    TL_OFF(tl_opackets)},
        {"obytes",      TL_OFF(tl_obytes)},
        {"blockcnt",    TL_OFF(tl_blockcnt)},
        {"unblockcnt",  TL_OFF(tl_unblockcnt)},
        {"txsdrops",    TL_OFF(tl_sdrops)}
};
#define TX_LANE_STAT_SIZE       A_CNT(tx_lane_stats_list)

/* Definitions for tx/rx misc stats */
#define M_OFF(f)        (offsetof(misc_stat_t, f))

static  stat_info_t     misc_stats_list[] = {
        {"multircv",            M_OFF(ms_multircv)},
        {"brdcstrcv",           M_OFF(ms_brdcstrcv)},
        {"multixmt",            M_OFF(ms_multixmt)},
        {"brdcstxmt",           M_OFF(ms_brdcstxmt)},
        {"multircvbytes",       M_OFF(ms_multircvbytes)},
        {"brdcstrcvbytes",      M_OFF(ms_brdcstrcvbytes)},
        {"multixmtbytes",       M_OFF(ms_multixmtbytes)},
        {"brdcstxmtbytes",      M_OFF(ms_brdcstxmtbytes)},
        {"txerrors",            M_OFF(ms_txerrors)},
        {"macspoofed",          M_OFF(ms_macspoofed)},
        {"ipspoofed",           M_OFF(ms_ipspoofed)},
        {"dhcpspoofed",         M_OFF(ms_dhcpspoofed)},
        {"restricted",          M_OFF(ms_restricted)},
        {"ipackets",            M_OFF(ms_ipackets)},
        {"rbytes",              M_OFF(ms_rbytes)},
        {"local",               M_OFF(ms_local)},
        {"localbytes",          M_OFF(ms_localbytes)},
        {"intrs",               M_OFF(ms_intrs)},
        {"intrbytes",           M_OFF(ms_intrbytes)},
        {"polls",               M_OFF(ms_polls)},
        {"pollbytes",           M_OFF(ms_pollbytes)},
        {"rxsdrops",            M_OFF(ms_rxsdrops)},
        {"chainunder10",        M_OFF(ms_chainunder10)},
        {"chain10to50",         M_OFF(ms_chain10to50)},
        {"chainover50",         M_OFF(ms_chainover50)},
        {"obytes",              M_OFF(ms_obytes)},
        {"opackets",            M_OFF(ms_opackets)},
        {"blockcnt",            M_OFF(ms_blockcnt)},
        {"unblockcnt",          M_OFF(ms_unblockcnt)},
        {"txsdrops",            M_OFF(ms_txsdrops)}
};
#define MISC_STAT_SIZE          A_CNT(misc_stats_list)

/* Definitions for rx ring stats */
#define R_OFF(f)        (offsetof(ring_stat_t, f))

static  stat_info_t     rx_ring_stats_list[] = {
        {"ipackets",    R_OFF(r_packets)},
        {"rbytes",      R_OFF(r_bytes)}
};
#define RX_RING_STAT_SIZE       A_CNT(rx_ring_stats_list)

/* Definitions for tx ring stats */
static  stat_info_t     tx_ring_stats_list[] = {
        {"opackets",    R_OFF(r_packets)},
        {"obytes",      R_OFF(r_bytes)}
};
#define TX_RING_STAT_SIZE       A_CNT(tx_ring_stats_list)

/* Definitions for fanout stats */
#define F_OFF(f)        (offsetof(fanout_stat_t, f))

static  stat_info_t     fanout_stats_list[] = {
        {"ipackets",    F_OFF(f_ipackets)},
        {"rbytes",      F_OFF(f_rbytes)},
};
#define FANOUT_STAT_SIZE        A_CNT(fanout_stats_list)

/* Definitions for total stats */
#define T_OFF(f)        (offsetof(total_stat_t, f))

static  stat_info_t     total_stats_list[] = {
        {"ipackets",    T_OFF(ts_ipackets)},
        {"rbytes",      T_OFF(ts_rbytes)},
        {"opackets",    T_OFF(ts_opackets)},
        {"obytes",      T_OFF(ts_obytes)}
};
#define TOTAL_STAT_SIZE         A_CNT(total_stats_list)

/* Definitions for aggr stats */
#define AP_OFF(f)       (offsetof(aggr_port_stat_t, f))

static  stat_info_t     aggr_port_stats_list[] = {
        {"ipackets64",  AP_OFF(ap_ipackets)},
        {"rbytes64",    AP_OFF(ap_rbytes)},
        {"opackets64",  AP_OFF(ap_opackets)},
        {"obytes64",    AP_OFF(ap_obytes)}
};
#define AGGR_PORT_STAT_SIZE     A_CNT(aggr_port_stats_list)

/* Definitions for flow stats */
#define FL_OFF(f)       (offsetof(flow_stat_t, f))

static  stat_info_t     flow_stats_list[] = {
        {"ipackets",    FL_OFF(fl_ipackets)},
        {"rbytes",      FL_OFF(fl_rbytes)},
        {"opackets",    FL_OFF(fl_opackets)},
        {"obytes",      FL_OFF(fl_obytes)}
};
#define FLOW_STAT_SIZE          A_CNT(flow_stats_list)

/* Rx lane specific functions */
void *                  dlstat_rx_lane_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_rx_lane_match(void *, void *);
static void *           i_dlstat_rx_lane_stat_entry_diff(void *, void *);

/* Tx lane specific functions */
void *                  dlstat_tx_lane_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_tx_lane_match(void *, void *);
static void *           i_dlstat_tx_lane_stat_entry_diff(void *, void *);

/* Rx lane total specific functions */
void *                  dlstat_rx_lane_total_stats(dladm_handle_t,
                            datalink_id_t);

/* Tx lane total specific functions */
void *                  dlstat_tx_lane_total_stats(dladm_handle_t,
                            datalink_id_t);

/* Fanout specific functions */
void *                  dlstat_fanout_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_fanout_match(void *, void *);
static void *           i_dlstat_fanout_stat_entry_diff(void *, void *);

/* Rx ring specific functions */
void *                  dlstat_rx_ring_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_rx_ring_match(void *, void *);
static void *           i_dlstat_rx_ring_stat_entry_diff(void *, void *);

/* Tx ring specific functions */
void *                  dlstat_tx_ring_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_tx_ring_match(void *, void *);
static void *           i_dlstat_tx_ring_stat_entry_diff(void *, void *);

/* Rx ring total specific functions */
void *                  dlstat_rx_ring_total_stats(dladm_handle_t,
                            datalink_id_t);

/* Tx ring total specific functions */
void *                  dlstat_tx_ring_total_stats(dladm_handle_t,
                            datalink_id_t);

/* Summary specific functions */
void *                  dlstat_total_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_total_match(void *, void *);
static void *           i_dlstat_total_stat_entry_diff(void *, void *);

/* Aggr port specific functions */
void *                  dlstat_aggr_port_stats(dladm_handle_t, datalink_id_t);
static boolean_t        i_dlstat_aggr_port_match(void *, void *);
static void *           i_dlstat_aggr_port_stat_entry_diff(void *, void *);

/* Misc stat specific functions */
void *                  dlstat_misc_stats(dladm_handle_t, datalink_id_t);

typedef void *          dladm_stat_query_t(dladm_handle_t, datalink_id_t);
typedef boolean_t       dladm_stat_match_t(void *, void *);
typedef void *          dladm_stat_diff_t(void *, void *);

typedef struct dladm_stat_desc_s {
        dladm_stat_type_t       ds_stattype;
        dladm_stat_query_t      *ds_querystat;
        dladm_stat_match_t      *ds_matchstat;
        dladm_stat_diff_t       *ds_diffstat;
        uint_t                  ds_offset;
        stat_info_t             *ds_statlist;
        uint_t                  ds_statsize;
} dladm_stat_desc_t;

/*
 * dladm_stat_table has one entry for each supported stat. ds_querystat returns
 * a chain of 'stat entries' for the queried stat.
 * Each stat entry has set of identifiers (ids) and an object containing actual
 * stat values. These stat entry objects are chained together in a linked list
 * of datatype dladm_stat_chain_t. Head of this list is returned to the caller
 * of dladm_link_stat_query.
 *
 * One node in the chain is shown below:
 *
 *      -------------------------
 *      | dc_statentry          |
 *      |    --------------     |
 *      |    |     ids     |    |
 *      |    --------------     |
 *      |    | stat fields |    |
 *      |    --------------     |
 *      -------------------------
 *      |      dc_next ---------|------> to next stat entry
 *      -------------------------
 *
 * In particular, for query DLADM_STAT_RX_LANE, dc_statentry carries pointer to
 * object of type rx_lane_stat_entry_t.
 *
 * dladm_link_stat_query_all returns similar chain. However, instead of storing
 * stat fields as raw numbers, it stores those as chain of <name, value> pairs.
 * The resulting structure is depicted below:
 *
 *      -------------------------
 *      | dc_statentry          |
 *      |    --------------     |   ---------------
 *      |    |  nv_header  |    |   |   name, val  |
 *      |    --------------     |   ---------------
 *      |    | nve_stats---|----|-->| nv_nextstat--|---> to next name, val pair
 *      |    --------------     |   ---------------
 *      -------------------------
 *      |      dc_next ---------|------> to next stat entry
 *      -------------------------
 */
static dladm_stat_desc_t  dladm_stat_table[] = {
{ DLADM_STAT_RX_LANE,           dlstat_rx_lane_stats,
    i_dlstat_rx_lane_match,     i_dlstat_rx_lane_stat_entry_diff,
    offsetof(rx_lane_stat_entry_t, rle_stats),
    rx_lane_stats_list,         RX_LANE_STAT_SIZE},

{ DLADM_STAT_TX_LANE,           dlstat_tx_lane_stats,
    i_dlstat_tx_lane_match,     i_dlstat_tx_lane_stat_entry_diff,
    offsetof(tx_lane_stat_entry_t, tle_stats),
    tx_lane_stats_list,         TX_LANE_STAT_SIZE},

{ DLADM_STAT_RX_LANE_TOTAL,     dlstat_rx_lane_total_stats,
    i_dlstat_rx_lane_match,     i_dlstat_rx_lane_stat_entry_diff,
    offsetof(rx_lane_stat_entry_t, rle_stats),
    rx_lane_stats_list,         RX_LANE_STAT_SIZE},

{ DLADM_STAT_TX_LANE_TOTAL,     dlstat_tx_lane_total_stats,
    i_dlstat_tx_lane_match,     i_dlstat_tx_lane_stat_entry_diff,
    offsetof(tx_lane_stat_entry_t, tle_stats),
    tx_lane_stats_list,         TX_LANE_STAT_SIZE},

{ DLADM_STAT_RX_LANE_FOUT,      dlstat_fanout_stats,
    i_dlstat_fanout_match,      i_dlstat_fanout_stat_entry_diff,
    offsetof(fanout_stat_entry_t, fe_stats),
    fanout_stats_list,          FANOUT_STAT_SIZE},

{ DLADM_STAT_RX_RING,           dlstat_rx_ring_stats,
    i_dlstat_rx_ring_match,     i_dlstat_rx_ring_stat_entry_diff,
    offsetof(ring_stat_entry_t, re_stats),
    rx_ring_stats_list,         RX_RING_STAT_SIZE},

{ DLADM_STAT_TX_RING,           dlstat_tx_ring_stats,
    i_dlstat_tx_ring_match,     i_dlstat_tx_ring_stat_entry_diff,
    offsetof(ring_stat_entry_t, re_stats),
    tx_ring_stats_list,         TX_RING_STAT_SIZE},

{ DLADM_STAT_RX_RING_TOTAL,     dlstat_rx_ring_total_stats,
    i_dlstat_rx_ring_match,     i_dlstat_rx_ring_stat_entry_diff,
    offsetof(ring_stat_entry_t, re_stats),
    rx_ring_stats_list,         RX_RING_STAT_SIZE},

{ DLADM_STAT_TX_RING_TOTAL,     dlstat_tx_ring_total_stats,
    i_dlstat_tx_ring_match,     i_dlstat_tx_ring_stat_entry_diff,
    offsetof(ring_stat_entry_t, re_stats),
    tx_ring_stats_list,         TX_RING_STAT_SIZE},

{ DLADM_STAT_TOTAL,             dlstat_total_stats,
    i_dlstat_total_match,       i_dlstat_total_stat_entry_diff,
    offsetof(total_stat_entry_t, tse_stats),
    total_stats_list,           TOTAL_STAT_SIZE},

{ DLADM_STAT_AGGR_PORT,         dlstat_aggr_port_stats,
    i_dlstat_aggr_port_match,   i_dlstat_aggr_port_stat_entry_diff,
    offsetof(aggr_port_stat_entry_t, ape_stats),
    aggr_port_stats_list,       AGGR_PORT_STAT_SIZE},
/*
 * We don't support -i <interval> query with misc stats. Several table fields
 * are left uninitialized thus.
 */
{ DLADM_STAT_MISC,              dlstat_misc_stats,
    NULL,                       NULL,
    0,
    misc_stats_list,            MISC_STAT_SIZE}
};

/* Internal functions */
static void *
dlstat_diff_stats(void *arg1, void *arg2, dladm_stat_type_t stattype)
{
        return (dladm_stat_table[stattype].ds_diffstat(arg1, arg2));
}

static boolean_t
dlstat_match_stats(void *arg1, void *arg2, dladm_stat_type_t stattype)
{
        return (dladm_stat_table[stattype].ds_matchstat(arg1, arg2));
}

/* Diff between two stats */
static void
i_dlstat_diff_stats(void *diff, void *op1, void *op2,
    stat_info_t stats_list[], uint_t size)
{
        uint_t  i;

        for (i = 0; i < size; i++) {
                uint64_t *op1_val  = (void *)
                    ((uchar_t *)op1 + stats_list[i].si_offset);
                uint64_t *op2_val = (void *)
                    ((uchar_t *)op2  + stats_list[i].si_offset);
                uint64_t *diff_val = (void *)
                    ((uchar_t *)diff + stats_list[i].si_offset);

                *diff_val = DIFF_STAT(*op1_val, *op2_val);
        }
}

/*
 * Perform diff = s1 - s2,  where diff, s1, s2 are structure objects of same
 * datatype. slist is list of offsets of the fields within the structure.
 */
#define DLSTAT_DIFF_STAT(s1, s2, diff, f, slist, sz) {                  \
        if (s2 == NULL) {                                               \
                bcopy(&s1->f, &diff->f, sizeof (s1->f));                \
        } else {                                                        \
                i_dlstat_diff_stats(&diff->f, &s1->f,                   \
                    &s2->f, slist, sz);                                 \
        }                                                               \
}

/* Sum two stats */
static void
i_dlstat_sum_stats(void *sum, void *op1, void *op2,
    stat_info_t stats_list[], uint_t size)
{
        uint_t  i;

        for (i = 0; i < size; i++) {
                uint64_t *op1_val = (void *)
                    ((uchar_t *)op1 + stats_list[i].si_offset);
                uint64_t *op2_val = (void *)
                    ((uchar_t *)op2 + stats_list[i].si_offset);
                uint64_t *sum_val = (void *)
                    ((uchar_t *)sum + stats_list[i].si_offset);

                *sum_val =  *op1_val + *op2_val;
        }
}

/* Look up kstat value */
static void
i_dlstat_get_stats(kstat_ctl_t *kcp, kstat_t *ksp, void *stats,
    stat_info_t stats_list[], uint_t size)
{
        uint_t  i;

        if (kstat_read(kcp, ksp, NULL) == -1)
                return;

        for (i = 0; i < size; i++) {
                uint64_t *val = (void *)
                    ((uchar_t *)stats + stats_list[i].si_offset);

                if (dladm_kstat_value(ksp, stats_list[i].si_name,
                    KSTAT_DATA_UINT64, val) < 0)
                        return;
        }
}

/* Append linked list list1 to linked list list2 and return resulting list */
static dladm_stat_chain_t *
i_dlstat_join_lists(dladm_stat_chain_t *list1, dladm_stat_chain_t *list2)
{
        dladm_stat_chain_t      *curr;

        if (list1 == NULL)
                return (list2);

        /* list1 has at least one element, find last element in list1 */
        curr = list1;
        while (curr->dc_next != NULL)
                curr = curr->dc_next;

        curr->dc_next = list2;
        return (list1);
}

uint_t default_idlist[] = {0};
uint_t default_idlist_size = 1;

typedef enum {
        DLSTAT_RX_RING_IDLIST,
        DLSTAT_TX_RING_IDLIST,
        DLSTAT_RX_HWLANE_IDLIST,
        DLSTAT_TX_HWLANE_IDLIST,
        DLSTAT_FANOUT_IDLIST
} dlstat_idlist_type_t;

void
dladm_sort_index_list(uint_t idlist[], uint_t size)
{
        uint_t  j;
        int i;

        for (j = 1; j < size; j++) {
                uint_t key = idlist[j];

                for (i = j - 1; (i >= 0) && (idlist[i] > key); i--)
                        idlist[i + 1] = idlist[i];
                idlist[i + 1] = key;
        }
}

/* Support for legacy drivers */
void
i_query_legacy_stats(dladm_handle_t dh, const char *linkname, pktsum_t *stats)
{
        kstat_t         *ksp;

        bzero(stats, sizeof (*stats));

        if (dladm_dld_kcp(dh) == NULL)
                return;

        ksp = dladm_kstat_lookup(dladm_dld_kcp(dh), "link", 0, linkname, NULL);

        if (ksp != NULL)
                dladm_get_stats(dladm_dld_kcp(dh), ksp, stats);
}

void *
i_dlstat_legacy_rx_lane_stats(dladm_handle_t dh, const char *linkname)
{
        dladm_stat_chain_t      *head = NULL;
        pktsum_t                stats;
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        bzero(&stats, sizeof (pktsum_t));

        /* Query for dls stats */
        i_query_legacy_stats(dh, linkname, &stats);

        /* Convert to desired data type */
        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry->rle_index = DLSTAT_INVALID_ENTRY;
        rx_lane_stat_entry->rle_id = L_SWLANE;

        rx_lane_stat_entry->rle_stats.rl_ipackets = stats.ipackets;
        rx_lane_stat_entry->rle_stats.rl_intrs = stats.ipackets;
        rx_lane_stat_entry->rle_stats.rl_rbytes = stats.rbytes;

        /* Allocate memory for wrapper */
        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(rx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = rx_lane_stat_entry;
        head->dc_next = NULL;
done:
        return (head);
}

void *
i_dlstat_legacy_tx_lane_stats(dladm_handle_t dh, const char *linkname)
{
        dladm_stat_chain_t      *head = NULL;
        pktsum_t                stats;
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        bzero(&stats, sizeof (pktsum_t));

        /* Query for dls stats */
        i_query_legacy_stats(dh, linkname, &stats);

        /* Convert to desired data type */
        tx_lane_stat_entry = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (tx_lane_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry->tle_index = DLSTAT_INVALID_ENTRY;
        tx_lane_stat_entry->tle_id = L_SWLANE;

        tx_lane_stat_entry->tle_stats.tl_opackets = stats.opackets;
        tx_lane_stat_entry->tle_stats.tl_obytes = stats.obytes;

        /* Allocate memory for wrapper */
        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(tx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = tx_lane_stat_entry;
        head->dc_next = NULL;
done:
        return (head);
}

/*
 * Ideally, we would want an ioctl to return list of ring-ids (or lane-ids)
 * for a given data-link (or mac client). We could then query for specific
 * kstats based on these ring-ids (lane-ids).
 * Ring-ids (or lane-ids) could be returned like any other link properties
 * queried by dladm show-linkprop. However, non-global zones do not have
 * access to this information today.
 * We thus opt for an implementation that relies heavily on kstat internals:
 * i_dlstat_*search routines and i_dlstat_get_idlist.
 */
/* rx hwlane specific */
static boolean_t
i_dlstat_rx_hwlane_search(kstat_t *ksp)
{
        return (ksp->ks_instance == 0 &&
            strstr(ksp->ks_name, "mac_rx") != 0 &&
            strstr(ksp->ks_name, "hwlane") != 0 &&
            strstr(ksp->ks_name, "fanout") == 0 &&
            strcmp(ksp->ks_class, "net") == 0);
}

/* tx hwlane specific */
static boolean_t
i_dlstat_tx_hwlane_search(kstat_t *ksp)
{
        return (ksp->ks_instance == 0 &&
            strstr(ksp->ks_name, "mac_tx") != 0 &&
            strstr(ksp->ks_name, "hwlane") != 0 &&
            strcmp(ksp->ks_class, "net") == 0);
}

/* rx fanout specific */
static boolean_t
i_dlstat_fanout_search(kstat_t *ksp)
{
        return (ksp->ks_instance == 0 &&
            strstr(ksp->ks_name, "mac_rx") != 0 &&
            strstr(ksp->ks_name, "swlane") != 0 &&
            strstr(ksp->ks_name, "fanout") != 0 &&
            strcmp(ksp->ks_class, "net") == 0);
}

/* rx ring specific */
static boolean_t
i_dlstat_rx_ring_search(kstat_t *ksp)
{
        return (ksp->ks_instance == 0 &&
            strstr(ksp->ks_name, "mac_rx") != 0 &&
            strstr(ksp->ks_name, "ring") != 0 &&
            strcmp(ksp->ks_class, "net") == 0);
}

/* tx ring specific */
static boolean_t
i_dlstat_tx_ring_search(kstat_t *ksp)
{
        return (ksp->ks_instance == 0) &&
            strstr(ksp->ks_name, "mac_tx") != 0 &&
            strstr(ksp->ks_name, "ring") != 0 &&
            strcmp(ksp->ks_class, "net") == 0;
}

typedef boolean_t       dladm_search_kstat_t(kstat_t *);
typedef struct dladm_extract_idlist_s {
        dlstat_idlist_type_t    di_type;
        char                    *di_prefix;
        dladm_search_kstat_t    *di_searchkstat;
} dladm_extract_idlist_t;

static dladm_extract_idlist_t dladm_extract_idlist[] = {
{ DLSTAT_RX_RING_IDLIST,        DLSTAT_MAC_RX_RING,
    i_dlstat_rx_ring_search},
{ DLSTAT_TX_RING_IDLIST,        DLSTAT_MAC_TX_RING,
    i_dlstat_tx_ring_search},
{ DLSTAT_RX_HWLANE_IDLIST,      DLSTAT_MAC_RX_HWLANE,
    i_dlstat_rx_hwlane_search},
{ DLSTAT_TX_HWLANE_IDLIST,      DLSTAT_MAC_TX_HWLANE,
    i_dlstat_tx_hwlane_search},
{ DLSTAT_FANOUT_IDLIST,         DLSTAT_MAC_FANOUT,
    i_dlstat_fanout_search}
};

static void
i_dlstat_get_idlist(dladm_handle_t handle, const char *modname,
    dlstat_idlist_type_t idlist_type,
    uint_t idlist[], uint_t *size)
{
        kstat_ctl_t     *kcp = dladm_dld_kcp(handle);
        kstat_t         *ksp;
        char            *prefix;
        int             prefixlen;
        boolean_t       (*fptr_searchkstat)(kstat_t *);

        *size = 0;

        if (kcp == NULL) {
                warn("kstat_open operation failed");
                return;
        }

        prefix = dladm_extract_idlist[idlist_type].di_prefix;
        fptr_searchkstat = dladm_extract_idlist[idlist_type].di_searchkstat;
        prefixlen = strlen(prefix);
        for (ksp = kcp->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
                if ((strcmp(ksp->ks_module, modname) == 0) &&
                    fptr_searchkstat(ksp)) {
                        idlist[(*size)++] = atoi(&ksp->ks_name[prefixlen]);
                }
        }
        dladm_sort_index_list(idlist, *size);
}

static dladm_stat_chain_t *
i_dlstat_query_stats(dladm_handle_t handle, const char *modname,
    const char *prefix, uint_t idlist[], uint_t idlist_size,
    void * (*fn)(kstat_ctl_t *, kstat_t *, int))
{
        kstat_t                 *ksp;
        char                    statname[MAXLINKNAMELEN];
        uint_t                  i;
        dladm_stat_chain_t      *head = NULL, *prev = NULL;
        dladm_stat_chain_t      *curr;

        if (dladm_dld_kcp(handle) == NULL) {
                warn("kstat_open operation failed");
                return (NULL);
        }

        for (i = 0; i < idlist_size; i++) {
                uint_t  index = idlist[i];

                (void) snprintf(statname, sizeof (statname), "%s%d", prefix,
                    index);

                ksp = dladm_kstat_lookup(dladm_dld_kcp(handle), modname, 0,
                    statname, NULL);
                if (ksp == NULL)
                        continue;

                curr = malloc(sizeof (dladm_stat_chain_t));
                if (curr == NULL)
                        break;

                curr->dc_statentry = fn(dladm_dld_kcp(handle), ksp, index);
                if (curr->dc_statentry == NULL) {
                        free(curr);
                        break;
                }

                (void) strlcpy(curr->dc_statheader, statname,
                    sizeof (curr->dc_statheader));
                curr->dc_next = NULL;

                if (head == NULL)       /* First node */
                        head = curr;
                else
                        prev->dc_next = curr;

                prev = curr;
        }
        return (head);
}

static misc_stat_entry_t *
i_dlstat_misc_stats(dladm_handle_t handle, const char *linkname)
{
        kstat_t                 *ksp;
        misc_stat_entry_t       *misc_stat_entry = NULL;

        if (dladm_dld_kcp(handle) == NULL)
                return (NULL);

        ksp = dladm_kstat_lookup(dladm_dld_kcp(handle), linkname, 0,
            DLSTAT_MAC_MISC_STAT, NULL);
        if (ksp == NULL)
                goto done;

        misc_stat_entry = calloc(1, sizeof (misc_stat_entry_t));
        if (misc_stat_entry == NULL)
                goto done;

        i_dlstat_get_stats(dladm_dld_kcp(handle), ksp,
            &misc_stat_entry->mse_stats,
            misc_stats_list, MISC_STAT_SIZE);
done:
        return (misc_stat_entry);
}

/* Rx lane statistic specific functions */
static boolean_t
i_dlstat_rx_lane_match(void *arg1, void *arg2)
{
        rx_lane_stat_entry_t *s1 = arg1;
        rx_lane_stat_entry_t *s2 = arg2;

        return (s1->rle_index == s2->rle_index &&
            s1->rle_id == s2->rle_id);
}

static void *
i_dlstat_rx_lane_stat_entry_diff(void *arg1, void *arg2)
{
        rx_lane_stat_entry_t *s1 = arg1;
        rx_lane_stat_entry_t *s2 = arg2;
        rx_lane_stat_entry_t *diff_entry;

        diff_entry = malloc(sizeof (rx_lane_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->rle_index = s1->rle_index;
        diff_entry->rle_id = s1->rle_id;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, rle_stats, rx_lane_stats_list,
            RX_LANE_STAT_SIZE);

done:
        return (diff_entry);
}

static void *
i_dlstat_rx_hwlane_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i)
{
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry->rle_index = i;
        rx_lane_stat_entry->rle_id = L_HWLANE;

        i_dlstat_get_stats(kcp, ksp, &rx_lane_stat_entry->rle_stats,
            rx_hwlane_stats_list, RX_HWLANE_STAT_SIZE);

done:
        return (rx_lane_stat_entry);
}

static void *
i_dlstat_rx_swlane_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i __unused)
{
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry->rle_index = DLSTAT_INVALID_ENTRY;
        rx_lane_stat_entry->rle_id = L_SWLANE;

        i_dlstat_get_stats(kcp, ksp, &rx_lane_stat_entry->rle_stats,
            rx_swlane_stats_list, RX_SWLANE_STAT_SIZE);

        rx_lane_stat_entry->rle_stats.rl_ipackets =
            rx_lane_stat_entry->rle_stats.rl_intrs;
        rx_lane_stat_entry->rle_stats.rl_rbytes =
            rx_lane_stat_entry->rle_stats.rl_intrbytes;
done:
        return (rx_lane_stat_entry);
}

static void *
i_dlstat_rx_local_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i __unused)
{
        rx_lane_stat_entry_t    *local_stat_entry = NULL;
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        local_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (local_stat_entry == NULL)
                goto done;

        local_stat_entry->rle_index = DLSTAT_INVALID_ENTRY;
        local_stat_entry->rle_id = L_LOCAL;

        i_dlstat_get_stats(kcp, ksp, &rx_lane_stat_entry->rle_stats,
            rx_swlane_stats_list, RX_SWLANE_STAT_SIZE);

        local_stat_entry->rle_stats.rl_ipackets =
            rx_lane_stat_entry->rle_stats.rl_lclpackets;
        local_stat_entry->rle_stats.rl_rbytes =
            rx_lane_stat_entry->rle_stats.rl_lclbytes;

done:
        free(rx_lane_stat_entry);
        return (local_stat_entry);
}

static dladm_stat_chain_t *
i_dlstat_rx_local_stats(dladm_handle_t handle, const char *linkname)
{
        dladm_stat_chain_t      *local_stats = NULL;

        local_stats = i_dlstat_query_stats(handle, linkname,
            DLSTAT_MAC_RX_SWLANE,
            default_idlist, default_idlist_size,
            i_dlstat_rx_local_retrieve_stat);

        if (local_stats != NULL) {
                (void) strlcpy(local_stats->dc_statheader, "mac_rx_local",
                    sizeof (local_stats->dc_statheader));
        }
        return (local_stats);
}

static dladm_stat_chain_t *
i_dlstat_rx_bcast_stats(dladm_handle_t handle, const char *linkname)
{
        misc_stat_entry_t       *misc_stat_entry;
        dladm_stat_chain_t      *head = NULL;
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        misc_stat_entry = i_dlstat_misc_stats(handle, linkname);
        if (misc_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry->rle_index = DLSTAT_INVALID_ENTRY;
        rx_lane_stat_entry->rle_id = L_BCAST;

        rx_lane_stat_entry->rle_stats.rl_ipackets =
            misc_stat_entry->mse_stats.ms_brdcstrcv +
            misc_stat_entry->mse_stats.ms_multircv;
        rx_lane_stat_entry->rle_stats.rl_intrs =
            misc_stat_entry->mse_stats.ms_brdcstrcv +
            misc_stat_entry->mse_stats.ms_multircv;
        rx_lane_stat_entry->rle_stats.rl_rbytes =
            misc_stat_entry->mse_stats.ms_brdcstrcvbytes +
            misc_stat_entry->mse_stats.ms_multircvbytes;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(rx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = rx_lane_stat_entry;
        head->dc_next = NULL;

done:
        free(misc_stat_entry);
        return (head);
}

static dladm_stat_chain_t *
i_dlstat_rx_defunctlane_stats(dladm_handle_t handle, const char *linkname)
{
        misc_stat_entry_t       *misc_stat_entry;
        dladm_stat_chain_t      *head = NULL;
        rx_lane_stat_entry_t    *rx_lane_stat_entry;

        misc_stat_entry = i_dlstat_misc_stats(handle, linkname);
        if (misc_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (rx_lane_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry->rle_index = DLSTAT_INVALID_ENTRY;
        rx_lane_stat_entry->rle_id = L_DFNCT;

        rx_lane_stat_entry->rle_stats.rl_ipackets =
            misc_stat_entry->mse_stats.ms_ipackets;
        rx_lane_stat_entry->rle_stats.rl_rbytes =
            misc_stat_entry->mse_stats.ms_rbytes;
        rx_lane_stat_entry->rle_stats.rl_intrs =
            misc_stat_entry->mse_stats.ms_intrs;
        rx_lane_stat_entry->rle_stats.rl_polls =
            misc_stat_entry->mse_stats.ms_polls;
        rx_lane_stat_entry->rle_stats.rl_sdrops =
            misc_stat_entry->mse_stats.ms_rxsdrops;
        rx_lane_stat_entry->rle_stats.rl_chl10 =
            misc_stat_entry->mse_stats.ms_chainunder10;
        rx_lane_stat_entry->rle_stats.rl_ch10_50 =
            misc_stat_entry->mse_stats.ms_chain10to50;
        rx_lane_stat_entry->rle_stats.rl_chg50 =
            misc_stat_entry->mse_stats.ms_chainover50;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(rx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = rx_lane_stat_entry;
        head->dc_next = NULL;

done:
        free(misc_stat_entry);
        return (head);
}

static dladm_stat_chain_t *
i_dlstat_rx_hwlane_stats(dladm_handle_t handle, const char *linkname)
{
        uint_t  rx_hwlane_idlist[MAX_RINGS_PER_GROUP];
        uint_t  rx_hwlane_idlist_size;

        i_dlstat_get_idlist(handle, linkname, DLSTAT_RX_HWLANE_IDLIST,
            rx_hwlane_idlist, &rx_hwlane_idlist_size);

        return (i_dlstat_query_stats(handle, linkname, DLSTAT_MAC_RX_HWLANE,
            rx_hwlane_idlist, rx_hwlane_idlist_size,
            i_dlstat_rx_hwlane_retrieve_stat));
}

static dladm_stat_chain_t *
i_dlstat_rx_swlane_stats(dladm_handle_t dh, datalink_id_t linkid __unused,
    const char *linkname)
{
        return (i_dlstat_query_stats(dh, linkname, DLSTAT_MAC_RX_SWLANE,
            default_idlist, default_idlist_size,
            i_dlstat_rx_swlane_retrieve_stat));
}

void *
dlstat_rx_lane_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *head = NULL;
        dladm_stat_chain_t      *local_stats = NULL;
        dladm_stat_chain_t      *bcast_stats = NULL;
        dladm_stat_chain_t      *defunctlane_stats = NULL;
        dladm_stat_chain_t      *lane_stats = NULL;
        char                    linkname[MAXLINKNAMELEN];
        boolean_t               is_legacy_driver;

        if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                goto done;
        }

        /* Check if it is legacy driver */
        if (dladm_linkprop_is_set(dh, linkid, DLADM_PROP_VAL_CURRENT,
            "_softmac", &is_legacy_driver) != DLADM_STATUS_OK) {
                goto done;
        }

        if (is_legacy_driver) {
                head = i_dlstat_legacy_rx_lane_stats(dh, linkname);
                goto done;
        }

        local_stats = i_dlstat_rx_local_stats(dh, linkname);
        bcast_stats = i_dlstat_rx_bcast_stats(dh, linkname);
        defunctlane_stats = i_dlstat_rx_defunctlane_stats(dh, linkname);
        lane_stats = i_dlstat_rx_hwlane_stats(dh, linkname);
        if (lane_stats == NULL)
                lane_stats = i_dlstat_rx_swlane_stats(dh, linkid, linkname);

        head = i_dlstat_join_lists(local_stats, bcast_stats);
        head = i_dlstat_join_lists(head, defunctlane_stats);
        head = i_dlstat_join_lists(head, lane_stats);
done:
        return (head);
}

/* Tx lane statistic specific functions */
static boolean_t
i_dlstat_tx_lane_match(void *arg1, void *arg2)
{
        tx_lane_stat_entry_t *s1 = arg1;
        tx_lane_stat_entry_t *s2 = arg2;

        return (s1->tle_index == s2->tle_index &&
            s1->tle_id == s2->tle_id);
}

static void *
i_dlstat_tx_lane_stat_entry_diff(void *arg1, void *arg2)
{
        tx_lane_stat_entry_t *s1 = arg1;
        tx_lane_stat_entry_t *s2 = arg2;
        tx_lane_stat_entry_t *diff_entry;

        diff_entry = malloc(sizeof (tx_lane_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->tle_index = s1->tle_index;
        diff_entry->tle_id = s1->tle_id;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, tle_stats, tx_lane_stats_list,
            TX_LANE_STAT_SIZE);

done:
        return (diff_entry);
}

static void *
i_dlstat_tx_hwlane_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i)
{
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        tx_lane_stat_entry = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (tx_lane_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry->tle_index   = i;
        tx_lane_stat_entry->tle_id      = L_HWLANE;

        i_dlstat_get_stats(kcp, ksp, &tx_lane_stat_entry->tle_stats,
            tx_lane_stats_list, TX_LANE_STAT_SIZE);

done:
        return (tx_lane_stat_entry);
}

static void *
i_dlstat_tx_swlane_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i __unused)
{
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        tx_lane_stat_entry = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (tx_lane_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry->tle_index = DLSTAT_INVALID_ENTRY;
        tx_lane_stat_entry->tle_id = L_SWLANE;

        i_dlstat_get_stats(kcp, ksp, &tx_lane_stat_entry->tle_stats,
            tx_lane_stats_list, TX_LANE_STAT_SIZE);

done:
        return (tx_lane_stat_entry);
}

static dladm_stat_chain_t *
i_dlstat_tx_bcast_stats(dladm_handle_t handle, const char *linkname)
{
        misc_stat_entry_t       *misc_stat_entry;
        dladm_stat_chain_t      *head = NULL;
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        misc_stat_entry = i_dlstat_misc_stats(handle, linkname);
        if (misc_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (tx_lane_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry->tle_index = DLSTAT_INVALID_ENTRY;
        tx_lane_stat_entry->tle_id = L_BCAST;

        tx_lane_stat_entry->tle_stats.tl_opackets =
            misc_stat_entry->mse_stats.ms_brdcstxmt +
            misc_stat_entry->mse_stats.ms_multixmt;

        tx_lane_stat_entry->tle_stats.tl_obytes =
            misc_stat_entry->mse_stats.ms_brdcstxmtbytes +
            misc_stat_entry->mse_stats.ms_multixmtbytes;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(tx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = tx_lane_stat_entry;
        head->dc_next = NULL;

done:
        free(misc_stat_entry);
        return (head);
}

static dladm_stat_chain_t *
i_dlstat_tx_defunctlane_stats(dladm_handle_t handle, const char *linkname)
{
        misc_stat_entry_t       *misc_stat_entry;
        dladm_stat_chain_t      *head = NULL;
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        misc_stat_entry = i_dlstat_misc_stats(handle, linkname);
        if (misc_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (tx_lane_stat_entry == NULL)
                goto done;

        tx_lane_stat_entry->tle_index = DLSTAT_INVALID_ENTRY;
        tx_lane_stat_entry->tle_id = L_DFNCT;

        tx_lane_stat_entry->tle_stats.tl_opackets =
            misc_stat_entry->mse_stats.ms_opackets;
        tx_lane_stat_entry->tle_stats.tl_obytes =
            misc_stat_entry->mse_stats.ms_obytes;
        tx_lane_stat_entry->tle_stats.tl_sdrops =
            misc_stat_entry->mse_stats.ms_txsdrops;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(tx_lane_stat_entry);
                goto done;
        }

        head->dc_statentry = tx_lane_stat_entry;
        head->dc_next = NULL;

done:
        free(misc_stat_entry);
        return (head);
}

static dladm_stat_chain_t *
i_dlstat_tx_hwlane_stats(dladm_handle_t handle, const char *linkname)
{
        uint_t  tx_hwlane_idlist[MAX_RINGS_PER_GROUP];
        uint_t  tx_hwlane_idlist_size;

        i_dlstat_get_idlist(handle, linkname, DLSTAT_TX_HWLANE_IDLIST,
            tx_hwlane_idlist, &tx_hwlane_idlist_size);

        return (i_dlstat_query_stats(handle, linkname, DLSTAT_MAC_TX_HWLANE,
            tx_hwlane_idlist, tx_hwlane_idlist_size,
            i_dlstat_tx_hwlane_retrieve_stat));
}

static dladm_stat_chain_t *
i_dlstat_tx_swlane_stats(dladm_handle_t dh, datalink_id_t linkid __unused,
    const char *linkname)
{
        return (i_dlstat_query_stats(dh, linkname, DLSTAT_MAC_TX_SWLANE,
            default_idlist, default_idlist_size,
            i_dlstat_tx_swlane_retrieve_stat));
}

void *
dlstat_tx_lane_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *head = NULL;
        dladm_stat_chain_t      *bcast_stats = NULL;
        dladm_stat_chain_t      *defunctlane_stats = NULL;
        dladm_stat_chain_t      *lane_stats;
        char                    linkname[MAXLINKNAMELEN];
        boolean_t               is_legacy_driver;

        if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                goto done;
        }

        /* Check if it is legacy driver */
        if (dladm_linkprop_is_set(dh, linkid, DLADM_PROP_VAL_CURRENT,
            "_softmac", &is_legacy_driver) != DLADM_STATUS_OK) {
                goto done;
        }

        if (is_legacy_driver) {
                head = i_dlstat_legacy_tx_lane_stats(dh, linkname);
                goto done;
        }

        bcast_stats = i_dlstat_tx_bcast_stats(dh, linkname);
        defunctlane_stats = i_dlstat_tx_defunctlane_stats(dh, linkname);
        lane_stats = i_dlstat_tx_hwlane_stats(dh, linkname);
        if (lane_stats == NULL)
                lane_stats = i_dlstat_tx_swlane_stats(dh, linkid, linkname);

        head = i_dlstat_join_lists(bcast_stats, defunctlane_stats);
        head = i_dlstat_join_lists(head, lane_stats);

done:
        return (head);
}

/* Rx lane total statistic specific functions */
void *
dlstat_rx_lane_total_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *total_head = NULL;
        dladm_stat_chain_t      *rx_lane_head, *curr;
        rx_lane_stat_entry_t    *total_stats;

        /* Get per rx lane stats */
        rx_lane_head = dlstat_rx_lane_stats(dh, linkid);
        if (rx_lane_head == NULL)
                goto done;

        total_stats = calloc(1, sizeof (rx_lane_stat_entry_t));
        if (total_stats == NULL)
                goto done;

        total_stats->rle_index = DLSTAT_INVALID_ENTRY;
        total_stats->rle_id = DLSTAT_INVALID_ENTRY;

        for (curr = rx_lane_head; curr != NULL; curr = curr->dc_next) {
                rx_lane_stat_entry_t    *curr_lane_stats = curr->dc_statentry;

                i_dlstat_sum_stats(&total_stats->rle_stats,
                    &curr_lane_stats->rle_stats, &total_stats->rle_stats,
                    rx_lane_stats_list, RX_LANE_STAT_SIZE);
        }

        total_head = malloc(sizeof (dladm_stat_chain_t));
        if (total_head == NULL) {
                free(total_stats);
                goto done;
        }

        total_head->dc_statentry = total_stats;
        (void) strlcpy(total_head->dc_statheader, "mac_rx_lane_total",
            sizeof (total_head->dc_statheader));
        total_head->dc_next = NULL;

done:
        dladm_link_stat_free(rx_lane_head);
        return (total_head);
}

/* Tx lane total statistic specific functions */
void *
dlstat_tx_lane_total_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *total_head = NULL;
        dladm_stat_chain_t      *tx_lane_head, *curr;
        tx_lane_stat_entry_t    *total_stats;

        /* Get per tx lane stats */
        tx_lane_head = dlstat_tx_lane_stats(dh, linkid);
        if (tx_lane_head == NULL)
                goto done;

        total_stats = calloc(1, sizeof (tx_lane_stat_entry_t));
        if (total_stats == NULL)
                goto done;

        total_stats->tle_index = DLSTAT_INVALID_ENTRY;
        total_stats->tle_id = DLSTAT_INVALID_ENTRY;

        for (curr = tx_lane_head; curr != NULL; curr = curr->dc_next) {
                tx_lane_stat_entry_t    *curr_lane_stats = curr->dc_statentry;

                i_dlstat_sum_stats(&total_stats->tle_stats,
                    &curr_lane_stats->tle_stats, &total_stats->tle_stats,
                    tx_lane_stats_list, TX_LANE_STAT_SIZE);
        }

        total_head = malloc(sizeof (dladm_stat_chain_t));
        if (total_head == NULL) {
                free(total_stats);
                goto done;
        }

        total_head->dc_statentry = total_stats;
        (void) strlcpy(total_head->dc_statheader, "mac_tx_lane_total",
            sizeof (total_head->dc_statheader));
        total_head->dc_next = NULL;

done:
        dladm_link_stat_free(tx_lane_head);
        return (total_head);
}

/* Fanout specific functions */
static boolean_t
i_dlstat_fanout_match(void *arg1, void *arg2)
{
        fanout_stat_entry_t     *s1 = arg1;
        fanout_stat_entry_t     *s2 = arg2;

        return (s1->fe_index == s2->fe_index &&
            s1->fe_id == s2->fe_id &&
            s1->fe_foutindex == s2->fe_foutindex);
}

static void *
i_dlstat_fanout_stat_entry_diff(void *arg1, void *arg2)
{
        fanout_stat_entry_t     *s1 = arg1;
        fanout_stat_entry_t     *s2 = arg2;
        fanout_stat_entry_t     *diff_entry;

        diff_entry = malloc(sizeof (fanout_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->fe_index = s1->fe_index;
        diff_entry->fe_id = s1->fe_id;
        diff_entry->fe_foutindex = s1->fe_foutindex;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, fe_stats, fanout_stats_list,
            FANOUT_STAT_SIZE);

done:
        return (diff_entry);
}

static void *
i_dlstat_fanout_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i)
{
        fanout_stat_entry_t     *fanout_stat_entry;

        fanout_stat_entry = calloc(1, sizeof (fanout_stat_entry_t));
        if (fanout_stat_entry == NULL)
                goto done;

                                        /* Set by the caller later */
        fanout_stat_entry->fe_index = DLSTAT_INVALID_ENTRY;
        fanout_stat_entry->fe_id = DLSTAT_INVALID_ENTRY;

        fanout_stat_entry->fe_foutindex = i;

        i_dlstat_get_stats(kcp, ksp, &fanout_stat_entry->fe_stats,
            fanout_stats_list, FANOUT_STAT_SIZE);

done:
        return (fanout_stat_entry);
}

static void *
i_dlstat_query_fanout_stats(dladm_handle_t dh, datalink_id_t linkid,
    uint_t idlist[], uint_t idlist_size,
    const char *modname, const char *prefix)
{
        uint_t                  i;
        char                    statprefix[MAXLINKNAMELEN];
        char                    linkname[MAXLINKNAMELEN];
        dladm_stat_chain_t      *curr, *curr_head;
        dladm_stat_chain_t      *head = NULL, *prev = NULL;
        uint_t                  fanout_idlist[MAX_RINGS_PER_GROUP];
        uint_t                  fanout_idlist_size;

        if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                return (NULL);
        }

        i_dlstat_get_idlist(dh, linkname, DLSTAT_FANOUT_IDLIST,
            fanout_idlist, &fanout_idlist_size);

        for (i = 0; i < idlist_size; i++) {
                uint_t  index = idlist[i];

                (void) snprintf(statprefix, sizeof (statprefix), "%s%d_fanout",
                    prefix, index);

                curr_head = i_dlstat_query_stats(dh, modname, statprefix,
                    fanout_idlist, fanout_idlist_size,
                    i_dlstat_fanout_retrieve_stat);

                if (curr_head == NULL)  /* Last lane */
                        break;

                if (head == NULL)       /* First lane */
                        head = curr_head;
                else    /* Link new lane list to end of previous lane list */
                        prev->dc_next = curr_head;

                /* Walk new lane list and set ids */
                for (curr = curr_head; curr != NULL; curr = curr->dc_next) {
                        fanout_stat_entry_t *curr_stats = curr->dc_statentry;

                        curr_stats->fe_index = index;
                        curr_stats->fe_id = L_HWLANE;
                        /*
                         * Save last pointer of previous linked list.
                         * This pointer is used to chain linked lists
                         * generated in each iteration.
                         */
                        prev = curr;
                }
        }

        return (head);
}

void *
dlstat_fanout_swlane_and_local_stats(dladm_handle_t dh, datalink_id_t linkid,
    const char *linkname)
{
        return (i_dlstat_query_fanout_stats(dh, linkid,
            default_idlist, default_idlist_size, linkname,
            DLSTAT_MAC_RX_SWLANE));
}

void *
dlstat_fanout_hwlane_stats(dladm_handle_t dh, datalink_id_t linkid,
    const char *linkname)
{
        uint_t  rx_hwlane_idlist[MAX_RINGS_PER_GROUP];
        uint_t  rx_hwlane_idlist_size;

        i_dlstat_get_idlist(dh, linkname, DLSTAT_RX_HWLANE_IDLIST,
            rx_hwlane_idlist, &rx_hwlane_idlist_size);

        return (i_dlstat_query_fanout_stats(dh, linkid, rx_hwlane_idlist,
            rx_hwlane_idlist_size, linkname, DLSTAT_MAC_RX_HWLANE));
}

void *
dlstat_fanout_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *head = NULL;
        dladm_stat_chain_t      *fout_hwlane_stats;
        dladm_stat_chain_t      *fout_swlane_and_local_stats;
        fanout_stat_entry_t     *fout_stats;
        char                    linkname[MAXLINKNAMELEN];

        if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                goto done;
        }

        fout_swlane_and_local_stats =
            dlstat_fanout_swlane_and_local_stats(dh, linkid, linkname);
        fout_hwlane_stats = dlstat_fanout_hwlane_stats(dh, linkid, linkname);

        if (fout_swlane_and_local_stats == NULL) {
                head = fout_hwlane_stats;
                goto done;
        }

        fout_stats = fout_swlane_and_local_stats->dc_statentry;

        if (fout_hwlane_stats != NULL) { /* hwlane(s), only local traffic */
                fout_stats->fe_id = L_LOCAL;
                fout_stats->fe_index = DLSTAT_INVALID_ENTRY;
        } else { /* no hwlane, mix of local+sw classified */
                fout_stats->fe_id = L_LCLSWLANE;
                fout_stats->fe_index = DLSTAT_INVALID_ENTRY;
        }

        fout_swlane_and_local_stats->dc_next = fout_hwlane_stats;
        head = fout_swlane_and_local_stats;

done:
        return (head);
}

/* Rx ring statistic specific functions */
static boolean_t
i_dlstat_rx_ring_match(void *arg1, void *arg2)
{
        rx_lane_stat_entry_t    *s1 = arg1;
        rx_lane_stat_entry_t    *s2 = arg2;

        return (s1->rle_index == s2->rle_index);
}

static void *
i_dlstat_rx_ring_stat_entry_diff(void *arg1, void *arg2)
{
        ring_stat_entry_t       *s1 = arg1;
        ring_stat_entry_t       *s2 = arg2;
        ring_stat_entry_t       *diff_entry;

        diff_entry = malloc(sizeof (ring_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->re_index    = s1->re_index;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, re_stats, rx_ring_stats_list,
            RX_RING_STAT_SIZE);

done:
        return (diff_entry);
}

static void *
i_dlstat_rx_ring_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i)
{
        ring_stat_entry_t       *rx_ring_stat_entry;

        rx_ring_stat_entry = calloc(1, sizeof (ring_stat_entry_t));
        if (rx_ring_stat_entry == NULL)
                goto done;

        rx_ring_stat_entry->re_index    = i;

        i_dlstat_get_stats(kcp, ksp, &rx_ring_stat_entry->re_stats,
            rx_ring_stats_list, RX_RING_STAT_SIZE);

done:
        return (rx_ring_stat_entry);
}

void *
dlstat_rx_ring_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        uint_t                  rx_ring_idlist[MAX_RINGS_PER_GROUP];
        uint_t                  rx_ring_idlist_size;
        dladm_phys_attr_t       dpa;
        char                    linkname[MAXLINKNAMELEN];
        char                    *modname;
        datalink_class_t        class;

        /*
         * kstats corresponding to physical device rings continue to use
         * device names even if the link is renamed using dladm rename-link.
         * Thus, given a linkid, we lookup the physical device name.
         * However, if an aggr is renamed, kstats corresponding to its
         * pseudo rings are renamed as well.
         */
        if (dladm_datalink_id2info(dh, linkid, NULL, &class, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                return (NULL);
        }

        if (class != DATALINK_CLASS_AGGR) {
                if (dladm_phys_info(dh, linkid, &dpa, DLADM_OPT_ACTIVE) !=
                    DLADM_STATUS_OK) {
                        return (NULL);
                }
                modname = dpa.dp_dev;
        } else
                modname = linkname;

        i_dlstat_get_idlist(dh, modname, DLSTAT_RX_RING_IDLIST,
            rx_ring_idlist, &rx_ring_idlist_size);

        return (i_dlstat_query_stats(dh, modname, DLSTAT_MAC_RX_RING,
            rx_ring_idlist, rx_ring_idlist_size,
            i_dlstat_rx_ring_retrieve_stat));
}

/* Tx ring statistic specific functions */
static boolean_t
i_dlstat_tx_ring_match(void *arg1, void *arg2)
{
        tx_lane_stat_entry_t    *s1 = arg1;
        tx_lane_stat_entry_t    *s2 = arg2;

        return (s1->tle_index == s2->tle_index);
}

static void *
i_dlstat_tx_ring_stat_entry_diff(void *arg1, void *arg2)
{
        ring_stat_entry_t       *s1 = arg1;
        ring_stat_entry_t       *s2 = arg2;
        ring_stat_entry_t       *diff_entry;

        diff_entry = malloc(sizeof (ring_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->re_index    = s1->re_index;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, re_stats, tx_ring_stats_list,
            TX_RING_STAT_SIZE);

done:
        return (diff_entry);
}

static void *
i_dlstat_tx_ring_retrieve_stat(kstat_ctl_t *kcp, kstat_t *ksp, int i)
{
        ring_stat_entry_t       *tx_ring_stat_entry;

        tx_ring_stat_entry = calloc(1, sizeof (ring_stat_entry_t));
        if (tx_ring_stat_entry == NULL)
                goto done;

        tx_ring_stat_entry->re_index    = i;

        i_dlstat_get_stats(kcp, ksp, &tx_ring_stat_entry->re_stats,
            tx_ring_stats_list, TX_RING_STAT_SIZE);

done:
        return (tx_ring_stat_entry);
}

void *
dlstat_tx_ring_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        uint_t                  tx_ring_idlist[MAX_RINGS_PER_GROUP];
        uint_t                  tx_ring_idlist_size;
        dladm_phys_attr_t       dpa;
        char                    linkname[MAXLINKNAMELEN];
        char                    *modname;
        datalink_class_t        class;

        /*
         * kstats corresponding to physical device rings continue to use
         * device names even if the link is renamed using dladm rename-link.
         * Thus, given a linkid, we lookup the physical device name.
         * However, if an aggr is renamed, kstats corresponding to its
         * pseudo rings are renamed as well.
         */
        if (dladm_datalink_id2info(dh, linkid, NULL, &class, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                return (NULL);
        }

        if (class != DATALINK_CLASS_AGGR) {
                if (dladm_phys_info(dh, linkid, &dpa, DLADM_OPT_ACTIVE) !=
                    DLADM_STATUS_OK) {
                        return (NULL);
                }
                modname = dpa.dp_dev;
        } else
                modname = linkname;

        i_dlstat_get_idlist(dh, modname, DLSTAT_TX_RING_IDLIST,
            tx_ring_idlist, &tx_ring_idlist_size);

        return (i_dlstat_query_stats(dh, modname, DLSTAT_MAC_TX_RING,
            tx_ring_idlist, tx_ring_idlist_size,
            i_dlstat_tx_ring_retrieve_stat));
}

/* Rx ring total statistic specific functions */
void *
dlstat_rx_ring_total_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *total_head = NULL;
        dladm_stat_chain_t      *rx_ring_head, *curr;
        ring_stat_entry_t       *total_stats;

        /* Get per rx ring stats */
        rx_ring_head = dlstat_rx_ring_stats(dh, linkid);
        if (rx_ring_head == NULL)
                goto done;

        total_stats = calloc(1, sizeof (ring_stat_entry_t));
        if (total_stats == NULL)
                goto done;

        total_stats->re_index = DLSTAT_INVALID_ENTRY;

        for (curr = rx_ring_head; curr != NULL; curr = curr->dc_next) {
                ring_stat_entry_t       *curr_ring_stats = curr->dc_statentry;

                i_dlstat_sum_stats(&total_stats->re_stats,
                    &curr_ring_stats->re_stats, &total_stats->re_stats,
                    rx_ring_stats_list, RX_RING_STAT_SIZE);
        }

        total_head = malloc(sizeof (dladm_stat_chain_t));
        if (total_head == NULL) {
                free(total_stats);
                goto done;
        }

        total_head->dc_statentry = total_stats;
        (void) strlcpy(total_head->dc_statheader, "mac_rx_ring_total",
            sizeof (total_head->dc_statheader));
        total_head->dc_next = NULL;

done:
        dladm_link_stat_free(rx_ring_head);
        return (total_head);
}

/* Tx ring total statistic specific functions */
void *
dlstat_tx_ring_total_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *total_head = NULL;
        dladm_stat_chain_t      *tx_ring_head, *curr;
        ring_stat_entry_t       *total_stats;

        /* Get per tx ring stats */
        tx_ring_head = dlstat_tx_ring_stats(dh, linkid);
        if (tx_ring_head == NULL)
                goto done;

        total_stats = calloc(1, sizeof (ring_stat_entry_t));
        if (total_stats == NULL)
                goto done;

        total_stats->re_index = DLSTAT_INVALID_ENTRY;

        for (curr = tx_ring_head; curr != NULL; curr = curr->dc_next) {
                ring_stat_entry_t       *curr_ring_stats = curr->dc_statentry;

                i_dlstat_sum_stats(&total_stats->re_stats,
                    &curr_ring_stats->re_stats, &total_stats->re_stats,
                    tx_ring_stats_list, TX_RING_STAT_SIZE);
        }

        total_head = malloc(sizeof (dladm_stat_chain_t));
        if (total_head == NULL) {
                free(total_stats);
                goto done;
        }

        total_head->dc_statentry = total_stats;
        (void) strlcpy(total_head->dc_statheader, "mac_tx_ring_total",
            sizeof (total_head->dc_statheader));
        total_head->dc_next = NULL;

done:
        dladm_link_stat_free(tx_ring_head);
        return (total_head);
}

/* Summary statistic specific functions */
static boolean_t
i_dlstat_total_match(void *arg1 __unused, void *arg2 __unused)
{
        /* Always single entry for total */
        return (B_TRUE);
}

static void *
i_dlstat_total_stat_entry_diff(void *arg1, void *arg2)
{
        total_stat_entry_t      *s1 = arg1;
        total_stat_entry_t      *s2 = arg2;
        total_stat_entry_t      *diff_entry;

        diff_entry = malloc(sizeof (total_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, tse_stats, total_stats_list,
            TOTAL_STAT_SIZE);

done:
        return (diff_entry);
}

void *
dlstat_total_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_stat_chain_t      *head = NULL;
        dladm_stat_chain_t      *rx_total = NULL;
        dladm_stat_chain_t      *tx_total = NULL;
        total_stat_entry_t      *total_stat_entry;
        rx_lane_stat_entry_t    *rx_lane_stat_entry;
        tx_lane_stat_entry_t    *tx_lane_stat_entry;

        /* Get total rx lane stats */
        rx_total = dlstat_rx_lane_total_stats(dh, linkid);
        if (rx_total == NULL)
                goto done;

        /* Get total tx lane stats */
        tx_total = dlstat_tx_lane_total_stats(dh, linkid);
        if (tx_total == NULL)
                goto done;

        /* Build total stat */
        total_stat_entry = calloc(1, sizeof (total_stat_entry_t));
        if (total_stat_entry == NULL)
                goto done;

        rx_lane_stat_entry = rx_total->dc_statentry;
        tx_lane_stat_entry = tx_total->dc_statentry;

        /* Extract total rx ipackets, rbytes */
        total_stat_entry->tse_stats.ts_ipackets =
            rx_lane_stat_entry->rle_stats.rl_ipackets;
        total_stat_entry->tse_stats.ts_rbytes =
            rx_lane_stat_entry->rle_stats.rl_rbytes;

        /* Extract total tx opackets, obytes */
        total_stat_entry->tse_stats.ts_opackets =
            tx_lane_stat_entry->tle_stats.tl_opackets;
        total_stat_entry->tse_stats.ts_obytes =
            tx_lane_stat_entry->tle_stats.tl_obytes;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(total_stat_entry);
                goto done;
        }

        head->dc_statentry = total_stat_entry;
        (void) strlcpy(head->dc_statheader, "mac_lane_total",
            sizeof (head->dc_statheader));
        head->dc_next = NULL;

done:
        dladm_link_stat_free(rx_total);
        dladm_link_stat_free(tx_total);
        return (head);
}

/* Aggr total statistic(summed across all component ports) specific functions */
void *
dlstat_aggr_total_stats(dladm_stat_chain_t *head)
{
        dladm_stat_chain_t      *curr;
        dladm_stat_chain_t      *total_head = NULL;
        aggr_port_stat_entry_t  *total_stats;

        total_stats = calloc(1, sizeof (aggr_port_stat_entry_t));
        if (total_stats == NULL)
                goto done;

        total_stats->ape_portlinkid = DATALINK_INVALID_LINKID;

        for (curr = head; curr != NULL; curr = curr->dc_next) {
                aggr_port_stat_entry_t  *curr_aggr_port_stats;

                curr_aggr_port_stats = curr->dc_statentry;

                i_dlstat_sum_stats(&total_stats->ape_stats,
                    &curr_aggr_port_stats->ape_stats, &total_stats->ape_stats,
                    aggr_port_stats_list, AGGR_PORT_STAT_SIZE);
        }

        total_head = malloc(sizeof (dladm_stat_chain_t));
        if (total_head == NULL) {
                free(total_stats);
                goto done;
        }

        total_head->dc_statentry = total_stats;
        total_head->dc_next = NULL;

done:
        return (total_head);
}

/* Aggr port statistic specific functions */
static boolean_t
i_dlstat_aggr_port_match(void *arg1, void *arg2)
{
        aggr_port_stat_entry_t *s1 = arg1;
        aggr_port_stat_entry_t *s2 = arg2;

        return (s1->ape_portlinkid == s2->ape_portlinkid);
}

static void *
i_dlstat_aggr_port_stat_entry_diff(void *arg1, void *arg2)
{
        aggr_port_stat_entry_t  *s1 = arg1;
        aggr_port_stat_entry_t  *s2 = arg2;
        aggr_port_stat_entry_t  *diff_entry;

        diff_entry = malloc(sizeof (aggr_port_stat_entry_t));
        if (diff_entry == NULL)
                goto done;

        diff_entry->ape_portlinkid = s1->ape_portlinkid;

        DLSTAT_DIFF_STAT(s1, s2, diff_entry, ape_stats, aggr_port_stats_list,
            AGGR_PORT_STAT_SIZE);

done:
        return (diff_entry);
}

/*
 * Query dls stats for the aggr port. This results in query for stats into
 * the corresponding device driver.
 */
static aggr_port_stat_entry_t *
i_dlstat_single_port_stats(dladm_handle_t handle, const char *portname,
    datalink_id_t linkid)
{
        kstat_t                 *ksp;
        char                    module[DLPI_LINKNAME_MAX];
        uint_t                  instance;
        aggr_port_stat_entry_t  *aggr_port_stat_entry = NULL;

        if (dladm_parselink(portname, module, &instance) != DLADM_STATUS_OK)
                goto done;

        if (dladm_dld_kcp(handle) == NULL) {
                warn("kstat open operation failed");
                return (NULL);
        }

        ksp = dladm_kstat_lookup(dladm_dld_kcp(handle), module, instance,
            "mac", NULL);
        if (ksp == NULL)
                goto done;

        aggr_port_stat_entry = calloc(1, sizeof (aggr_port_stat_entry_t));
        if (aggr_port_stat_entry == NULL)
                goto done;

        /* Save port's linkid */
        aggr_port_stat_entry->ape_portlinkid = linkid;

        i_dlstat_get_stats(dladm_dld_kcp(handle), ksp,
            &aggr_port_stat_entry->ape_stats,
            aggr_port_stats_list, AGGR_PORT_STAT_SIZE);
done:
        return (aggr_port_stat_entry);
}

void *
dlstat_aggr_port_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        dladm_aggr_grp_attr_t   ginfo;
        uint_t                  i;
        dladm_aggr_port_attr_t   *portp;
        dladm_phys_attr_t       dpa;
        aggr_port_stat_entry_t  *aggr_port_stat_entry;
        dladm_stat_chain_t      *head = NULL, *prev = NULL, *curr;
        dladm_stat_chain_t      *total_stats;

        /* Get aggr info */
        bzero(&ginfo, sizeof (dladm_aggr_grp_attr_t));
        if (dladm_aggr_info(dh, linkid, &ginfo, DLADM_OPT_ACTIVE)
            != DLADM_STATUS_OK)
                goto done;
        /* For every port that is member of this aggr do */
        for (i = 0; i < ginfo.lg_nports; i++) {
                portp = &(ginfo.lg_ports[i]);
                if (dladm_phys_info(dh, portp->lp_linkid, &dpa,
                    DLADM_OPT_ACTIVE) != DLADM_STATUS_OK) {
                        goto done;
                }

                aggr_port_stat_entry = i_dlstat_single_port_stats(dh,
                    dpa.dp_dev, portp->lp_linkid);

                /* Create dladm_stat_chain_t object for this stat */
                curr = malloc(sizeof (dladm_stat_chain_t));
                if (curr == NULL) {
                        free(aggr_port_stat_entry);
                        goto done;
                }
                (void) strlcpy(curr->dc_statheader, dpa.dp_dev,
                    sizeof (curr->dc_statheader));
                curr->dc_statentry = aggr_port_stat_entry;
                curr->dc_next = NULL;

                /* Chain this aggr port stat entry */
                /* head of the stat list */
                if (prev == NULL)
                        head = curr;
                else
                        prev->dc_next = curr;
                prev = curr;
        }

        /*
         * Prepend the stat list with cumulative aggr stats i.e. summed over all
         * component ports
         */
        total_stats = dlstat_aggr_total_stats(head);
        if (total_stats != NULL) {
                total_stats->dc_next = head;
                head = total_stats;
        }

done:
        free(ginfo.lg_ports);
        return (head);
}

/* Misc stat specific functions */
void *
dlstat_misc_stats(dladm_handle_t dh, datalink_id_t linkid)
{
        misc_stat_entry_t       *misc_stat_entry;
        dladm_stat_chain_t      *head = NULL;
        char                    linkname[MAXLINKNAMELEN];

        if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, linkname,
            DLPI_LINKNAME_MAX) != DLADM_STATUS_OK) {
                goto done;
        }

        misc_stat_entry = i_dlstat_misc_stats(dh, linkname);
        if (misc_stat_entry == NULL)
                goto done;

        head = malloc(sizeof (dladm_stat_chain_t));
        if (head == NULL) {
                free(misc_stat_entry);
                goto done;
        }

        head->dc_statentry = misc_stat_entry;
        (void) strlcpy(head->dc_statheader, "mac_misc_stat",
            sizeof (head->dc_statheader));
        head->dc_next = NULL;

done:
        return (head);
}

/* Exported functions */
dladm_stat_chain_t *
dladm_link_stat_query(dladm_handle_t dh, datalink_id_t linkid,
    dladm_stat_type_t stattype)
{
        return (dladm_stat_table[stattype].ds_querystat(dh, linkid));
}

dladm_stat_chain_t *
dladm_link_stat_diffchain(dladm_stat_chain_t *op1, dladm_stat_chain_t *op2,
    dladm_stat_type_t stattype)
{
        dladm_stat_chain_t      *op1_curr, *op2_curr;
        dladm_stat_chain_t      *diff_curr;
        dladm_stat_chain_t      *diff_prev = NULL, *diff_head = NULL;

                                /* Perform op1 - op2, store result in diff */
        for (op1_curr = op1; op1_curr != NULL; op1_curr = op1_curr->dc_next) {
                for (op2_curr = op2; op2_curr != NULL;
                    op2_curr = op2_curr->dc_next) {
                        if (dlstat_match_stats(op1_curr->dc_statentry,
                            op2_curr->dc_statentry, stattype)) {
                                break;
                        }
                }
                diff_curr = malloc(sizeof (dladm_stat_chain_t));
                if (diff_curr == NULL)
                        goto done;

                diff_curr->dc_next = NULL;

                if (op2_curr == NULL) {
                        /* prev iteration did not have this stat entry */
                        diff_curr->dc_statentry =
                            dlstat_diff_stats(op1_curr->dc_statentry,
                            NULL, stattype);
                } else {
                        diff_curr->dc_statentry =
                            dlstat_diff_stats(op1_curr->dc_statentry,
                            op2_curr->dc_statentry, stattype);
                }

                if (diff_curr->dc_statentry == NULL) {
                        free(diff_curr);
                        goto done;
                }

                if (diff_prev == NULL) /* head of the diff stat list */
                        diff_head = diff_curr;
                else
                        diff_prev->dc_next = diff_curr;
                diff_prev = diff_curr;
        }
done:
        return (diff_head);
}

void
dladm_link_stat_free(dladm_stat_chain_t *curr)
{
        while (curr != NULL) {
                dladm_stat_chain_t      *tofree = curr;

                curr = curr->dc_next;
                free(tofree->dc_statentry);
                free(tofree);
        }
}

/* Query all link stats */
static name_value_stat_t *
i_dlstat_convert_stats(void *stats, stat_info_t stats_list[], uint_t size)
{
        uint_t                  i;
        name_value_stat_t       *head_stat = NULL, *prev_stat = NULL;
        name_value_stat_t       *curr_stat;

        for (i = 0; i < size; i++) {
                uint64_t *val = (void *)
                    ((uchar_t *)stats + stats_list[i].si_offset);

                curr_stat = calloc(1, sizeof (name_value_stat_t));
                if (curr_stat == NULL)
                        break;

                (void) strlcpy(curr_stat->nv_statname, stats_list[i].si_name,
                    sizeof (curr_stat->nv_statname));
                curr_stat->nv_statval = *val;
                curr_stat->nv_nextstat = NULL;

                if (head_stat == NULL)  /* First node */
                        head_stat = curr_stat;
                else
                        prev_stat->nv_nextstat = curr_stat;

                prev_stat = curr_stat;
        }
        return (head_stat);
}

void *
build_nvs_entry(char *statheader, void *statentry, dladm_stat_type_t stattype)
{
        name_value_stat_entry_t *name_value_stat_entry;
        dladm_stat_desc_t       *stattbl_ptr;
        void                    *statfields;

        stattbl_ptr = &dladm_stat_table[stattype];

        /* Allocate memory for query all stat entry */
        name_value_stat_entry = calloc(1, sizeof (name_value_stat_entry_t));
        if (name_value_stat_entry == NULL)
                goto done;

        /* Header for these stat fields */
        (void) strlcpy(name_value_stat_entry->nve_header, statheader,
            sizeof (name_value_stat_entry->nve_header));

        /* Extract stat fields from the statentry */
        statfields = (uchar_t *)statentry +
            dladm_stat_table[stattype].ds_offset;

        /* Convert curr_stat to <statname, statval> pair */
        name_value_stat_entry->nve_stats =
            i_dlstat_convert_stats(statfields,
            stattbl_ptr->ds_statlist, stattbl_ptr->ds_statsize);
done:
        return (name_value_stat_entry);
}

void *
i_walk_dlstat_chain(dladm_stat_chain_t *stat_head, dladm_stat_type_t stattype)
{
        dladm_stat_chain_t      *curr;
        dladm_stat_chain_t      *nvstat_head = NULL, *nvstat_prev = NULL;
        dladm_stat_chain_t      *nvstat_curr;

        /*
         * For every stat in the chain, build header and convert all
         * its stat fields
         */
        for (curr = stat_head; curr != NULL; curr = curr->dc_next) {
                nvstat_curr = malloc(sizeof (dladm_stat_chain_t));
                if (nvstat_curr == NULL)
                        break;

                nvstat_curr->dc_statentry = build_nvs_entry(curr->dc_statheader,
                    curr->dc_statentry, stattype);

                if (nvstat_curr->dc_statentry == NULL) {
                        free(nvstat_curr);
                        break;
                }

                nvstat_curr->dc_next = NULL;

                if (nvstat_head == NULL)        /* First node */
                        nvstat_head = nvstat_curr;
                else
                        nvstat_prev->dc_next = nvstat_curr;

                nvstat_prev = nvstat_curr;
        }
        return (nvstat_head);
}

dladm_stat_chain_t *
dladm_link_stat_query_all(dladm_handle_t dh, datalink_id_t linkid,
    dladm_stat_type_t stattype)
{
        dladm_stat_chain_t      *stat_head;
        dladm_stat_chain_t      *nvstat_head = NULL;

        /* Query the requested stat */
        stat_head = dladm_link_stat_query(dh, linkid, stattype);
        if (stat_head == NULL)
                goto done;

        /*
         * Convert every statfield in every stat-entry of stat chain to
         * <statname, statval> pair
         */
        nvstat_head = i_walk_dlstat_chain(stat_head, stattype);

        /* Free stat_head */
        dladm_link_stat_free(stat_head);

done:
        return (nvstat_head);
}

void
dladm_link_stat_query_all_free(dladm_stat_chain_t *curr)
{
        while (curr != NULL) {
                dladm_stat_chain_t      *tofree = curr;
                name_value_stat_entry_t *nv_entry = curr->dc_statentry;
                name_value_stat_t       *nv_curr = nv_entry->nve_stats;

                while (nv_curr != NULL) {
                        name_value_stat_t       *nv_tofree = nv_curr;

                        nv_curr = nv_curr->nv_nextstat;
                        free(nv_tofree);
                }

                curr = curr->dc_next;
                free(nv_entry);
                free(tofree);
        }
}

/* flow stats specific routines */
flow_stat_t *
dladm_flow_stat_query(dladm_handle_t handle, const char *flowname)
{
        kstat_t         *ksp;
        flow_stat_t     *flow_stat = NULL;

        if (dladm_dld_kcp(handle) == NULL)
                return (NULL);

        flow_stat = calloc(1, sizeof (flow_stat_t));
        if (flow_stat == NULL)
                goto done;

        ksp = dladm_kstat_lookup(dladm_dld_kcp(handle), NULL, -1, flowname,
            "flow");

        if (ksp != NULL) {
                i_dlstat_get_stats(dladm_dld_kcp(handle), ksp, flow_stat,
                    flow_stats_list, FLOW_STAT_SIZE);
        }

done:
        return (flow_stat);
}

flow_stat_t *
dladm_flow_stat_diff(flow_stat_t *op1, flow_stat_t *op2)
{
        flow_stat_t     *diff_stat;

        diff_stat = calloc(1, sizeof (flow_stat_t));
        if (diff_stat == NULL)
                goto done;

        if (op2 == NULL) {
                bcopy(op1, diff_stat, sizeof (flow_stat_t));
        } else {
                i_dlstat_diff_stats(diff_stat, op1, op2, flow_stats_list,
                    FLOW_STAT_SIZE);
        }
done:
        return (diff_stat);
}

void
dladm_flow_stat_free(flow_stat_t *curr)
{
        free(curr);
}

/* Query all flow stats */
name_value_stat_entry_t *
dladm_flow_stat_query_all(dladm_handle_t handle, const char *flowname)
{
        flow_stat_t             *flow_stat;
        name_value_stat_entry_t *name_value_stat_entry = NULL;

        /* Query flow stats */
        flow_stat = dladm_flow_stat_query(handle, flowname);
        if (flow_stat == NULL)
                goto done;

        /* Allocate memory for query all stat entry */
        name_value_stat_entry = calloc(1, sizeof (name_value_stat_entry_t));
        if (name_value_stat_entry == NULL) {
                dladm_flow_stat_free(flow_stat);
                goto done;
        }

        /* Header for these stat fields */
        (void) strncpy(name_value_stat_entry->nve_header, flowname,
            MAXFLOWNAMELEN);

        /* Convert every statfield in flow_stat to <statname, statval> pair */
        name_value_stat_entry->nve_stats =
            i_dlstat_convert_stats(flow_stat, flow_stats_list, FLOW_STAT_SIZE);

        /* Free flow_stat */
        dladm_flow_stat_free(flow_stat);

done:
        return (name_value_stat_entry);
}

void
dladm_flow_stat_query_all_free(name_value_stat_entry_t *curr)
{
        name_value_stat_t       *nv_curr = curr->nve_stats;

        while (nv_curr != NULL) {
                name_value_stat_t       *nv_tofree = nv_curr;

                nv_curr = nv_curr->nv_nextstat;
                free(nv_tofree);
        }
}