root/sys/dev/axgbe/xgbe-sysctl.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2020 Advanced Micro Devices, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Contact Information :
 * Rajesh Kumar <rajesh1.kumar@amd.com>
 * Arpan Palit <Arpan.Palit@amd.com>
 */

#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/sbuf.h>

#include "xgbe.h"
#include "xgbe-common.h"

#define SYSCTL_BUF_LEN 64

typedef enum{
        /* Coalesce flag */
        rx_coalesce_usecs = 1,
        rx_max_coalesced_frames,
        rx_coalesce_usecs_irq,
        rx_max_coalesced_frames_irq,
        tx_coalesce_usecs,
        tx_max_coalesced_frames,
        tx_coalesce_usecs_irq,
        tx_max_coalesced_frames_irq,
        stats_block_coalesce_usecs,
        use_adaptive_rx_coalesce,
        use_adaptive_tx_coalesce,
        pkt_rate_low,
        rx_coalesce_usecs_low,
        rx_max_coalesced_frames_low,
        tx_coalesce_usecs_low,
        tx_max_coalesced_frames_low,
        pkt_rate_high,
        rx_coalesce_usecs_high,
        rx_max_coalesced_frames_high,
        tx_coalesce_usecs_high,
        tx_max_coalesced_frames_high,
        rate_sample_interval,

        /* Pasue flag */
        autoneg,
        tx_pause,
        rx_pause,

        /* link settings */
        speed,
        duplex,

        /* Ring settings */
        rx_pending,
        rx_mini_pending,
        rx_jumbo_pending,
        tx_pending,

        /* Channels settings */
        rx_count,
        tx_count,
        other_count,
        combined_count,
} sysctl_variable_t;

typedef enum {
        SYSL_NONE,
        SYSL_BOOL,
        SYSL_S32,
        SYSL_U8,
        SYSL_U16,
        SYSL_U32,
        SYSL_U64,
        SYSL_BE16,
        SYSL_IP4,
        SYSL_STR,
        SYSL_FLAG,
        SYSL_MAC,
} sysctl_type_t;

struct sysctl_info {
        uint8_t name[32];
        sysctl_type_t type;
        sysctl_variable_t flag;
        uint8_t support[16];
};

struct sysctl_op {
        /* Coalesce options */
        unsigned int rx_coalesce_usecs;
        unsigned int rx_max_coalesced_frames;
        unsigned int rx_coalesce_usecs_irq;
        unsigned int rx_max_coalesced_frames_irq;
        unsigned int tx_coalesce_usecs;
        unsigned int tx_max_coalesced_frames;
        unsigned int tx_coalesce_usecs_irq;
        unsigned int tx_max_coalesced_frames_irq;
        unsigned int stats_block_coalesce_usecs;
        unsigned int use_adaptive_rx_coalesce;
        unsigned int use_adaptive_tx_coalesce;
        unsigned int pkt_rate_low;
        unsigned int rx_coalesce_usecs_low;
        unsigned int rx_max_coalesced_frames_low;
        unsigned int tx_coalesce_usecs_low;
        unsigned int tx_max_coalesced_frames_low;
        unsigned int pkt_rate_high;
        unsigned int rx_coalesce_usecs_high;
        unsigned int rx_max_coalesced_frames_high;
        unsigned int tx_coalesce_usecs_high;
        unsigned int tx_max_coalesced_frames_high;
        unsigned int rate_sample_interval;

        /* Pasue options */
        unsigned int autoneg;
        unsigned int tx_pause;
        unsigned int rx_pause;

        /* Link settings options */
        unsigned int speed;
        unsigned int duplex;

        /* Ring param options */
        unsigned int rx_max_pending;
        unsigned int rx_mini_max_pending;
        unsigned int rx_jumbo_max_pending;
        unsigned int tx_max_pending;
        unsigned int rx_pending;
        unsigned int rx_mini_pending;
        unsigned int rx_jumbo_pending;
        unsigned int tx_pending;

        /* Channels options */
        unsigned int max_rx;
        unsigned int max_tx;
        unsigned int max_other;
        unsigned int max_combined;
        unsigned int rx_count;
        unsigned int tx_count;
        unsigned int other_count;
        unsigned int combined_count;
} sys_op;

#define GSTRING_LEN 32

struct xgbe_stats {
        char stat_string[GSTRING_LEN];
        int stat_size;
        int stat_offset;
};

#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))

#define XGMAC_MMC_STAT(_string, _var)                      \
        { _string,                                            \
          FIELD_SIZEOF(struct xgbe_mmc_stats, _var),        \
          offsetof(struct xgbe_prv_data, mmc_stats._var),       \
        }

#define XGMAC_EXT_STAT(_string, _var)                      \
        { _string,                                            \
          FIELD_SIZEOF(struct xgbe_ext_stats, _var),        \
          offsetof(struct xgbe_prv_data, ext_stats._var),       \
        }
static const struct xgbe_stats xgbe_gstring_stats[] = {
        XGMAC_MMC_STAT("tx_bytes", txoctetcount_gb),
        XGMAC_MMC_STAT("tx_packets", txframecount_gb),
        XGMAC_MMC_STAT("tx_unicast_packets", txunicastframes_gb),
        XGMAC_MMC_STAT("tx_broadcast_packets", txbroadcastframes_gb),
        XGMAC_MMC_STAT("tx_multicast_packets", txmulticastframes_gb),
        XGMAC_MMC_STAT("tx_vlan_packets", txvlanframes_g),
        XGMAC_EXT_STAT("tx_vxlan_packets", tx_vxlan_packets),
        XGMAC_EXT_STAT("tx_tso_packets", tx_tso_packets),
        XGMAC_MMC_STAT("tx_64_byte_packets", tx64octets_gb),
        XGMAC_MMC_STAT("tx_65_to_127_byte_packets", tx65to127octets_gb),
        XGMAC_MMC_STAT("tx_128_to_255_byte_packets", tx128to255octets_gb),
        XGMAC_MMC_STAT("tx_256_to_511_byte_packets", tx256to511octets_gb),
        XGMAC_MMC_STAT("tx_512_to_1023_byte_packets", tx512to1023octets_gb),
        XGMAC_MMC_STAT("tx_1024_to_max_byte_packets", tx1024tomaxoctets_gb),
        XGMAC_MMC_STAT("tx_underflow_errors", txunderflowerror),
        XGMAC_MMC_STAT("tx_pause_frames", txpauseframes),

        XGMAC_MMC_STAT("rx_bytes", rxoctetcount_gb),
        XGMAC_MMC_STAT("rx_packets", rxframecount_gb),
        XGMAC_MMC_STAT("rx_unicast_packets", rxunicastframes_g),
        XGMAC_MMC_STAT("rx_broadcast_packets", rxbroadcastframes_g),
        XGMAC_MMC_STAT("rx_multicast_packets", rxmulticastframes_g),
        XGMAC_MMC_STAT("rx_vlan_packets", rxvlanframes_gb),
        XGMAC_EXT_STAT("rx_vxlan_packets", rx_vxlan_packets),
        XGMAC_MMC_STAT("rx_64_byte_packets", rx64octets_gb),
        XGMAC_MMC_STAT("rx_65_to_127_byte_packets", rx65to127octets_gb),
        XGMAC_MMC_STAT("rx_128_to_255_byte_packets", rx128to255octets_gb),
        XGMAC_MMC_STAT("rx_256_to_511_byte_packets", rx256to511octets_gb),
        XGMAC_MMC_STAT("rx_512_to_1023_byte_packets", rx512to1023octets_gb),
        XGMAC_MMC_STAT("rx_1024_to_max_byte_packets", rx1024tomaxoctets_gb),
        XGMAC_MMC_STAT("rx_undersize_packets", rxundersize_g),
        XGMAC_MMC_STAT("rx_oversize_packets", rxoversize_g),
        XGMAC_MMC_STAT("rx_crc_errors", rxcrcerror),
        XGMAC_MMC_STAT("rx_crc_errors_small_packets", rxrunterror),
        XGMAC_MMC_STAT("rx_crc_errors_giant_packets", rxjabbererror),
        XGMAC_MMC_STAT("rx_length_errors", rxlengtherror),
        XGMAC_MMC_STAT("rx_out_of_range_errors", rxoutofrangetype),
        XGMAC_MMC_STAT("rx_fifo_overflow_errors", rxfifooverflow),
        XGMAC_MMC_STAT("rx_watchdog_errors", rxwatchdogerror),
        XGMAC_EXT_STAT("rx_csum_errors", rx_csum_errors),
        XGMAC_EXT_STAT("rx_vxlan_csum_errors", rx_vxlan_csum_errors),
        XGMAC_MMC_STAT("rx_pause_frames", rxpauseframes),
        XGMAC_EXT_STAT("rx_split_header_packets", rx_split_header_packets),
        XGMAC_EXT_STAT("rx_buffer_unavailable", rx_buffer_unavailable),
};

#define XGBE_STATS_COUNT        ARRAY_SIZE(xgbe_gstring_stats)

char** alloc_sysctl_buffer(void);
void get_val(char *buf, char **op, char **val, int *n_op);
void fill_data(struct sysctl_op *sys_op, int flag, unsigned int value);

static int
exit_bad_op(void)
{

        printf("SYSCTL: bad command line option (s)\n");
        return(-EINVAL);
}

static inline int
get_ubuf(struct sysctl_req *req, char *ubuf)
{
        int rc;

        printf("%s: len:0x%li idx:0x%li\n", __func__, req->newlen,
            req->newidx);
        if (req->newlen >= SYSCTL_BUF_LEN)
                return (-EINVAL);

        rc = SYSCTL_IN(req, ubuf, req->newlen);
        if (rc)
                return (rc);
        ubuf[req->newlen] = '\0';

        return (0);
}

char**
alloc_sysctl_buffer(void)
{
        char **buffer;
        int i;

        buffer = malloc(sizeof(char *)*32, M_AXGBE, M_WAITOK | M_ZERO);
        for(i = 0; i < 32; i++)
                buffer[i] = malloc(sizeof(char)*32, M_AXGBE, M_WAITOK | M_ZERO);

        return (buffer);
}

void
get_val(char *buf, char **op, char **val, int *n_op)
{
        int blen = strlen(buf);
        int count = 0;
        int i, j;

        *n_op = 0;
        for (i = 0; i < blen; i++) {
                count++;
                /* Get sysctl command option */
                for (j = 0; buf[i] != ' '; j++) {
                        if (i >= blen)
                                break;
                        op[*n_op][j] = buf[i++];
                }
                op[*n_op][j+1] = '\0';
                if (i >= strlen(buf))
                        goto out;

                /* Get sysctl value*/
                i++;
                for (j = 0; buf[i] != ' '; j++) {
                        if (i >= blen)
                                break;
                        val[*n_op][j] = buf[i++]; 
                }
                val[*n_op][j+1] = '\0';
                if (i >= strlen(buf))
                        goto out;

                *n_op = count;
        }

out:
        *n_op = count;
}

void
fill_data(struct sysctl_op *sys_op, int flag, unsigned int value)
{

        switch(flag) {
        case 1:
        sys_op->rx_coalesce_usecs = value;
        break;
        case 2:
        sys_op->rx_max_coalesced_frames = value;
        break;
        case 3:
        sys_op->rx_coalesce_usecs_irq = value;
        break;
        case 4:
        sys_op->rx_max_coalesced_frames_irq = value;
        break;
        case 5:
        sys_op->tx_coalesce_usecs = value;
        break;
        case 6:
        sys_op->tx_max_coalesced_frames = value;
        break;
        case 7:
        sys_op->tx_coalesce_usecs_irq = value;
        break;
        case 8:
        sys_op->tx_max_coalesced_frames_irq = value;
        break;
        case 9:
        sys_op->stats_block_coalesce_usecs = value;
        break;
        case 10:
        sys_op->use_adaptive_rx_coalesce = value;
        break;
        case 11:
        sys_op->use_adaptive_tx_coalesce = value;
        break;
        case 12:
        sys_op->pkt_rate_low = value;
        break;
        case 13:
        sys_op->rx_coalesce_usecs_low = value;
        break;
        case 14:
        sys_op->rx_max_coalesced_frames_low = value;
        break;
        case 15:
        sys_op->tx_coalesce_usecs_low = value;
        break;
        case 16:
        sys_op->tx_max_coalesced_frames_low = value;
        break;
        case 17:
        sys_op->pkt_rate_high = value;
        break;
        case 18:
        sys_op->rx_coalesce_usecs_high = value;
        break;
        case 19:
        sys_op->rx_max_coalesced_frames_high = value;
        break;
        case 20:
        sys_op->tx_coalesce_usecs_high = value;
        break;
        case 21:
        sys_op->tx_max_coalesced_frames_high = value;
        break;
        case 22:
        sys_op->rate_sample_interval = value;
        break;
        case 23:
        sys_op->autoneg = value;
        break;
        case 24:
        sys_op->rx_pause = value;
        break;
        case 25:
        sys_op->tx_pause = value;
        break;
        case 26:
        sys_op->speed = value;
        break;
        case 27:
        sys_op->duplex = value;
        break;
        case 28:
        sys_op->rx_pending = value;
        break;
        case 29:
        sys_op->rx_mini_pending = value;
        break;
        case 30:
        sys_op->rx_jumbo_pending = value;
        break;
        case 31:
        sys_op->tx_pending = value;
        break;
        default:
                printf("Option error\n");
        }
}

static int
parse_generic_sysctl(struct xgbe_prv_data *pdata, char *buf,
    struct sysctl_info *info, unsigned int n_info)
{
        struct sysctl_op *sys_op = pdata->sys_op;
        unsigned int value;
        char **op, **val;
        int n_op = 0;
        int rc = 0;
        int i, idx;

        op = alloc_sysctl_buffer();
        val = alloc_sysctl_buffer();
        get_val(buf, op, val, &n_op);

        for (i = 0; i < n_op; i++) {
                for (idx = 0; idx < n_info; idx++) {
                        if (strcmp(info[idx].name, op[i]) == 0) {
                                if (strcmp(info[idx].support,
                                    "not-supported") == 0){
                                        axgbe_printf(1, "ignoring not-supported "
                                            "option \"%s\"\n", info[idx].name);
                                        break;
                                }
                                switch(info[idx].type) {
                                case SYSL_BOOL: {
                                        if (!strcmp(val[i], "on"))
                                                fill_data(sys_op,
                                                    info[idx].flag, 1);
                                        else if (!strcmp(val[i], "off"))
                                                fill_data(sys_op,
                                                    info[idx].flag, 0);
                                        else
                                                rc = exit_bad_op();
                                        break;
                                }
                                case SYSL_S32:
                                        sscanf(val[i], "%u", &value);
                                        fill_data(sys_op, info[idx].flag, value);
                                        break;
                                case SYSL_U8:
                                        if (!strcmp(val[i], "half"))
                                                fill_data(sys_op,
                                                    info[idx].flag, DUPLEX_HALF);
                                        else if (!strcmp(val[i], "full"))
                                                fill_data(sys_op,
                                                    info[idx].flag, DUPLEX_FULL);
                                        else
                                                exit_bad_op();
                                default:
                                        rc = exit_bad_op();
                                }
                        }
                }
        }

        for(i = 0; i < 32; i++)
                free(op[i], M_AXGBE);
        free(op, M_AXGBE);

        for(i = 0; i < 32; i++)
                free(val[i], M_AXGBE);
        free(val, M_AXGBE);
        return (rc);
}


static int
sysctl_xgmac_reg_addr_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        unsigned int reg;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: sysctl_xgmac_reg: 0x%x\n",  __func__,
                    pdata->sysctl_xgmac_reg);
                sbuf_printf(sb, "\nXGMAC reg_addr:      0x%x\n",
                    pdata->sysctl_xgmac_reg);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &reg);
                axgbe_printf(2, "WRITE: %s: reg: 0x%x\n",  __func__, reg);
                pdata->sysctl_xgmac_reg = reg;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_get_drv_info_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct xgbe_hw_features *hw_feat = &pdata->hw_feat;
        ssize_t buf_size = 64;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                sbuf_printf(sb, "\ndriver:      %s", XGBE_DRV_NAME);
                sbuf_printf(sb, "\nversion: %s", XGBE_DRV_VERSION);
                sbuf_printf(sb, "\nfirmware-version: %d.%d.%d",
                    XGMAC_GET_BITS(hw_feat->version, MAC_VR, USERVER),
                    XGMAC_GET_BITS(hw_feat->version, MAC_VR, DEVID),
                    XGMAC_GET_BITS(hw_feat->version, MAC_VR, SNPSVER));
                sbuf_printf(sb, "\nbus-info: %04d:%02d:%02d",
                    pdata->pcie_bus, pdata->pcie_device, pdata->pcie_func);

                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        return (-EINVAL);
}

static int
sysctl_get_link_info_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                
                sbuf_printf(sb, "\nLink is %s", pdata->phy.link ? "Up" : "Down");
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        return (-EINVAL);
}

#define COALESCE_SYSCTL_INFO(__coalop)                                                  \
{                                                                                       \
        { "adaptive-rx", SYSL_BOOL, use_adaptive_rx_coalesce, "not-supported" },        \
        { "adaptive-tx", SYSL_BOOL, use_adaptive_tx_coalesce, "not-supported" },        \
        { "sample-interval", SYSL_S32, rate_sample_interval, "not-supported" },         \
        { "stats-block-usecs", SYSL_S32, stats_block_coalesce_usecs, "not-supported" }, \
        { "pkt-rate-low", SYSL_S32, pkt_rate_low, "not-supported" },                    \
        { "pkt-rate-high", SYSL_S32, pkt_rate_high, "not-supported" },                  \
        { "rx-usecs", SYSL_S32, rx_coalesce_usecs, "supported" },                       \
        { "rx-frames", SYSL_S32, rx_max_coalesced_frames, "supported" },                \
        { "rx-usecs-irq", SYSL_S32, rx_coalesce_usecs_irq, "not-supported" },           \
        { "rx-frames-irq", SYSL_S32, rx_max_coalesced_frames_irq, "not-supported" },    \
        { "tx-usecs", SYSL_S32, tx_coalesce_usecs, "not-supported" },                   \
        { "tx-frames", SYSL_S32, tx_max_coalesced_frames, "supported" },                \
        { "tx-usecs-irq", SYSL_S32, tx_coalesce_usecs_irq, "not-supported" },           \
        { "tx-frames-irq", SYSL_S32, tx_max_coalesced_frames_irq, "not-supported" },    \
        { "rx-usecs-low", SYSL_S32, rx_coalesce_usecs_low, "not-supported" },           \
        { "rx-frames-low", SYSL_S32, rx_max_coalesced_frames_low, "not-supported"},     \
        { "tx-usecs-low", SYSL_S32, tx_coalesce_usecs_low, "not-supported" },           \
        { "tx-frames-low", SYSL_S32, tx_max_coalesced_frames_low, "not-supported" },    \
        { "rx-usecs-high", SYSL_S32, rx_coalesce_usecs_high, "not-supported" },         \
        { "rx-frames-high", SYSL_S32, rx_max_coalesced_frames_high, "not-supported" },  \
        { "tx-usecs-high", SYSL_S32, tx_coalesce_usecs_high, "not-supported" },         \
        { "tx-frames-high", SYSL_S32, tx_max_coalesced_frames_high, "not-supported" },  \
}

static int
sysctl_coalesce_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct xgbe_hw_if *hw_if = &pdata->hw_if;
        struct sysctl_op *sys_op = pdata->sys_op;
        struct sysctl_info sysctl_coalesce[] = COALESCE_SYSCTL_INFO(coalop);
        unsigned int rx_frames, rx_riwt, rx_usecs;
        unsigned int tx_frames;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                sys_op->rx_coalesce_usecs = pdata->rx_usecs;
                sys_op->rx_max_coalesced_frames = pdata->rx_frames;
                sys_op->tx_max_coalesced_frames = pdata->tx_frames;

                sbuf_printf(sb, "\nAdaptive RX: %s  TX: %s\n",
                    sys_op->use_adaptive_rx_coalesce ? "on" : "off",
                    sys_op->use_adaptive_tx_coalesce ? "on" : "off");

                sbuf_printf(sb, "stats-block-usecs: %u\n"
                    "sample-interval: %u\n"
                    "pkt-rate-low: %u\n"
                    "pkt-rate-high: %u\n"
                    "\n"
                    "rx-usecs: %u\n"
                    "rx-frames: %u\n"
                    "rx-usecs-irq: %u\n"
                    "rx-frames-irq: %u\n"
                    "\n"
                    "tx-usecs: %u\n"
                    "tx-frames: %u\n"
                    "tx-usecs-irq: %u\n"
                    "tx-frames-irq: %u\n"
                    "\n"
                    "rx-usecs-low: %u\n"
                    "rx-frames-low: %u\n"
                    "tx-usecs-low: %u\n"
                    "tx-frames-low: %u\n"
                    "\n"
                    "rx-usecs-high: %u\n"
                    "rx-frames-high: %u\n"
                    "tx-usecs-high: %u\n"
                    "tx-frames-high: %u\n",
                    sys_op->stats_block_coalesce_usecs,
                    sys_op->rate_sample_interval,
                    sys_op->pkt_rate_low,
                    sys_op->pkt_rate_high,

                    sys_op->rx_coalesce_usecs,
                    sys_op->rx_max_coalesced_frames,
                    sys_op->rx_coalesce_usecs_irq,
                    sys_op->rx_max_coalesced_frames_irq,

                    sys_op->tx_coalesce_usecs,
                    sys_op->tx_max_coalesced_frames,
                    sys_op->tx_coalesce_usecs_irq,
                    sys_op->tx_max_coalesced_frames_irq,

                    sys_op->rx_coalesce_usecs_low,
                    sys_op->rx_max_coalesced_frames_low,
                    sys_op->tx_coalesce_usecs_low,
                    sys_op->tx_max_coalesced_frames_low,

                    sys_op->rx_coalesce_usecs_high,
                    sys_op->rx_max_coalesced_frames_high,
                    sys_op->tx_coalesce_usecs_high,
                    sys_op->tx_max_coalesced_frames_high);

                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                parse_generic_sysctl(pdata, buf, sysctl_coalesce,
                    ARRAY_SIZE(sysctl_coalesce)); 

                rx_riwt = hw_if->usec_to_riwt(pdata, sys_op->rx_coalesce_usecs);
                rx_usecs = sys_op->rx_coalesce_usecs;
                rx_frames = sys_op->rx_max_coalesced_frames;

                /* Use smallest possible value if conversion resulted in zero */
                if (rx_usecs && !rx_riwt)
                        rx_riwt = 1;

                /* Check the bounds of values for Rx */
                if (rx_riwt > XGMAC_MAX_DMA_RIWT) {
                        axgbe_printf(2, "rx-usec is limited to %d usecs\n",
                            hw_if->riwt_to_usec(pdata, XGMAC_MAX_DMA_RIWT));
                        return (-EINVAL);
                }
                if (rx_frames > pdata->rx_desc_count) {
                        axgbe_printf(2, "rx-frames is limited to %d frames\n",
                            pdata->rx_desc_count);
                        return (-EINVAL);
                }

                tx_frames = sys_op->tx_max_coalesced_frames;

                /* Check the bounds of values for Tx */
                if (tx_frames > pdata->tx_desc_count) {
                        axgbe_printf(2, "tx-frames is limited to %d frames\n",
                            pdata->tx_desc_count);
                        return (-EINVAL);
                }

                pdata->rx_riwt = rx_riwt;
                pdata->rx_usecs = rx_usecs;
                pdata->rx_frames = rx_frames;
                hw_if->config_rx_coalesce(pdata);

                pdata->tx_frames = tx_frames;
                hw_if->config_tx_coalesce(pdata);
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);

        return (rc);
}

static int
sysctl_pauseparam_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct sysctl_op *sys_op = pdata->sys_op;
        struct sysctl_info sysctl_pauseparam[] = {
                { "autoneg", SYSL_BOOL, autoneg, "supported" },
                { "rx", SYSL_BOOL, rx_pause, "supported" },
                { "tx", SYSL_BOOL, tx_pause, "supported" },
        };
        ssize_t buf_size = 512;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                sys_op->autoneg = pdata->phy.pause_autoneg;
                sys_op->tx_pause = pdata->phy.tx_pause;
                sys_op->rx_pause = pdata->phy.rx_pause;

                sbuf_printf(sb,
                    "\nAutonegotiate:   %s\n"
                    "RX:                %s\n"
                    "TX:                %s\n",
                    sys_op->autoneg ? "on" : "off",
                    sys_op->rx_pause ? "on" : "off",
                    sys_op->tx_pause ? "on" : "off");

                if (pdata->phy.lp_advertising) {
                        int an_rx = 0, an_tx = 0;

                        if (pdata->phy.advertising & pdata->phy.lp_advertising &
                            ADVERTISED_Pause) {
                                an_tx = 1;
                                an_rx = 1;
                        } else if (pdata->phy.advertising &
                            pdata->phy.lp_advertising & ADVERTISED_Asym_Pause) {
                                if (pdata->phy.advertising & ADVERTISED_Pause)
                                        an_rx = 1;
                                else if (pdata->phy.lp_advertising &
                                    ADVERTISED_Pause)
                                an_tx = 1;
                        }
                        sbuf_printf(sb,
                            "\n->\nRX negotiated:       %s\n"
                            "TX negotiated:     %s\n",
                            an_rx ? "on" : "off",
                            an_tx ? "on" : "off");
                }
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                parse_generic_sysctl(pdata, buf, sysctl_pauseparam,
                    ARRAY_SIZE(sysctl_pauseparam));

                if (sys_op->autoneg && (pdata->phy.autoneg != AUTONEG_ENABLE)) {
                        axgbe_error("autoneg disabled, pause autoneg not available\n");
                        return (-EINVAL);
                }

                pdata->phy.pause_autoneg = sys_op->autoneg;
                pdata->phy.tx_pause = sys_op->tx_pause;
                pdata->phy.rx_pause = sys_op->rx_pause;

                XGBE_CLR_ADV(&pdata->phy, Pause);
                XGBE_CLR_ADV(&pdata->phy, Asym_Pause);

                if (sys_op->rx_pause) {
                        XGBE_SET_ADV(&pdata->phy, Pause);
                        XGBE_SET_ADV(&pdata->phy, Asym_Pause);
                }

                if (sys_op->tx_pause) {
                        /* Equivalent to XOR of Asym_Pause */
                        if (XGBE_ADV(&pdata->phy, Asym_Pause))
                                XGBE_CLR_ADV(&pdata->phy, Asym_Pause);
                        else
                                XGBE_SET_ADV(&pdata->phy, Asym_Pause);
                }

                if (test_bit(XGBE_LINK_INIT, &pdata->dev_state))
                        rc = pdata->phy_if.phy_config_aneg(pdata);

        }

        return (rc);
}

static int
sysctl_link_ksettings_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct sysctl_op *sys_op = pdata->sys_op;
        struct sysctl_info sysctl_linksettings[] = {
                { "autoneg", SYSL_BOOL, autoneg, "supported" },
                { "speed", SYSL_U32, speed, "supported" },
                { "duplex", SYSL_U8, duplex, "supported" },
        };
        ssize_t buf_size = 512;
        char buf[buf_size], link_modes[16], speed_modes[16];
        struct sbuf *sb;
        uint32_t speed;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                sys_op->autoneg = pdata->phy.autoneg;
                sys_op->speed = pdata->phy.speed;
                sys_op->duplex = pdata->phy.duplex;

                XGBE_LM_COPY(&pdata->phy, supported, &pdata->phy, supported);
                XGBE_LM_COPY(&pdata->phy, advertising, &pdata->phy, advertising);
                XGBE_LM_COPY(&pdata->phy, lp_advertising, &pdata->phy, lp_advertising);

                switch (sys_op->speed) {
                case 1:
                        strcpy(link_modes, "Unknown");
                        strcpy(speed_modes, "Unknown");
                        break;
                case 2:
                        strcpy(link_modes, "10Gbps/Full");
                        strcpy(speed_modes, "10000");
                        break;
                case 3: 
                        strcpy(link_modes, "2.5Gbps/Full");
                        strcpy(speed_modes, "2500");
                        break;
                case 4: 
                        strcpy(link_modes, "1Gbps/Full");
                        strcpy(speed_modes, "1000");
                        break;
                case 5: 
                        strcpy(link_modes, "100Mbps/Full");
                        strcpy(speed_modes, "100");
                        break;
                case 6:
                        strcpy(link_modes, "10Mbps/Full");
                        strcpy(speed_modes, "10");
                        break;
                }
                        
                sbuf_printf(sb,
                    "\nlink_modes: %s\n"
                    "autonegotiation: %s\n"
                    "speed: %sMbps\n",
                    link_modes,
                    (sys_op->autoneg == AUTONEG_DISABLE) ? "off" : "on",
                    speed_modes);

                switch (sys_op->duplex) {
                        case DUPLEX_HALF:
                                sbuf_printf(sb, "Duplex: Half\n");
                                break;
                        case DUPLEX_FULL:
                                sbuf_printf(sb, "Duplex: Full\n");
                                break;
                        default:
                                sbuf_printf(sb, "Duplex: Unknown\n");
                                break;
                }
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                parse_generic_sysctl(pdata, buf, sysctl_linksettings,
                    ARRAY_SIZE(sysctl_linksettings));

                speed = sys_op->speed;

                if ((sys_op->autoneg != AUTONEG_ENABLE) &&
                    (sys_op->autoneg != AUTONEG_DISABLE)) {
                        axgbe_error("unsupported autoneg %hhu\n",
                            (unsigned char)sys_op->autoneg);
                        return (-EINVAL);
                }

                if (sys_op->autoneg == AUTONEG_DISABLE) {
                        if (!pdata->phy_if.phy_valid_speed(pdata, speed)) {
                                axgbe_error("unsupported speed %u\n", speed);
                                return (-EINVAL);
                        }

                        if (sys_op->duplex != DUPLEX_FULL) {
                                axgbe_error("unsupported duplex %hhu\n",
                                    (unsigned char)sys_op->duplex);
                                return (-EINVAL);
                        }
                }

                pdata->phy.autoneg = sys_op->autoneg;
                pdata->phy.speed = speed;
                pdata->phy.duplex = sys_op->duplex;

                if (sys_op->autoneg == AUTONEG_ENABLE)
                        XGBE_SET_ADV(&pdata->phy, Autoneg);
                else
                        XGBE_CLR_ADV(&pdata->phy, Autoneg);

                if (test_bit(XGBE_LINK_INIT, &pdata->dev_state))
                        rc = pdata->phy_if.phy_config_aneg(pdata);
        }

        return (rc);
}

static int
sysctl_ringparam_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct sysctl_op *sys_op = pdata->sys_op;
        struct sysctl_info sysctl_ringparam[] = {
                { "rx", SYSL_S32, rx_pending, "supported" },
                { "rx-mini", SYSL_S32, rx_mini_pending, "supported" },
                { "rx-jumbo", SYSL_S32, rx_jumbo_pending, "supported" },
                { "tx", SYSL_S32, tx_pending, "supported" },
        };
        ssize_t buf_size = 512;
        unsigned int rx, tx;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                sys_op->rx_max_pending = XGBE_RX_DESC_CNT_MAX;
                sys_op->tx_max_pending = XGBE_TX_DESC_CNT_MAX;
                sys_op->rx_pending = pdata->rx_desc_count;
                sys_op->tx_pending = pdata->tx_desc_count;

                sbuf_printf(sb,
                    "\nPre-set maximums:\n"
                    "RX:                %u\n"
                    "RX Mini:   %u\n"
                    "RX Jumbo:  %u\n"
                    "TX:                %u\n",
                    sys_op->rx_max_pending,
                    sys_op->rx_mini_max_pending,
                    sys_op->rx_jumbo_max_pending,
                    sys_op->tx_max_pending);

                sbuf_printf(sb,
                    "\nCurrent hardware settings:\n"
                    "RX:                %u\n"
                    "RX Mini:   %u\n"
                    "RX Jumbo:  %u\n"
                    "TX:                %u\n",
                    sys_op->rx_pending,
                    sys_op->rx_mini_pending,
                    sys_op->rx_jumbo_pending,
                    sys_op->tx_pending);

                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                parse_generic_sysctl(pdata, buf, sysctl_ringparam,
                    ARRAY_SIZE(sysctl_ringparam));

                if (sys_op->rx_mini_pending || sys_op->rx_jumbo_pending) {
                        axgbe_error("unsupported ring parameter\n");
                        return (-EINVAL);
                }

                if ((sys_op->rx_pending < XGBE_RX_DESC_CNT_MIN) ||
                                (sys_op->rx_pending > XGBE_RX_DESC_CNT_MAX)) {
                        axgbe_error("rx ring param must be between %u and %u\n",
                            XGBE_RX_DESC_CNT_MIN, XGBE_RX_DESC_CNT_MAX);
                        return (-EINVAL);
                }

                if ((sys_op->tx_pending < XGBE_TX_DESC_CNT_MIN) ||
                                (sys_op->tx_pending > XGBE_TX_DESC_CNT_MAX)) {
                        axgbe_error("tx ring param must be between %u and %u\n",
                            XGBE_TX_DESC_CNT_MIN, XGBE_TX_DESC_CNT_MAX);
                        return (-EINVAL);
                }

                rx = rounddown_pow_of_two(sys_op->rx_pending);
                if (rx != sys_op->rx_pending)
                        axgbe_printf(1, "rx ring param rounded to power of 2: %u\n",
                            rx);

                tx = rounddown_pow_of_two(sys_op->tx_pending);
                if (tx != sys_op->tx_pending)
                        axgbe_printf(1, "tx ring param rounded to power of 2: %u\n",
                            tx);

                if ((rx == pdata->rx_desc_count) &&
                    (tx == pdata->tx_desc_count))
                        goto out;

                pdata->rx_desc_count = rx;
                pdata->tx_desc_count = tx;

                /* TODO - restart dev */
        }

out:
        return (0);
}

static int
sysctl_channels_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        struct sysctl_op *sys_op = pdata->sys_op;
        struct sysctl_info sysctl_channels[] = {
                { "rx", SYSL_S32, rx_count, "supported" },
                { "tx", SYSL_S32, tx_count, "supported" },
                { "other", SYSL_S32, other_count, "supported" },
                { "combined", SYSL_S32, combined_count, "supported" },
        };
        unsigned int rx, tx, combined;
        ssize_t buf_size = 512;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }
                rx = min(pdata->hw_feat.rx_ch_cnt, pdata->rx_max_channel_count);
                rx = min(rx, pdata->channel_irq_count);
                tx = min(pdata->hw_feat.tx_ch_cnt, pdata->tx_max_channel_count);
                tx = min(tx, pdata->channel_irq_count);
                tx = min(tx, pdata->tx_max_q_count);

                combined = min(rx, tx);

                sys_op->max_combined = combined;
                sys_op->max_rx = rx ? rx - 1 : 0;
                sys_op->max_tx = tx ? tx - 1 : 0;

                /* Get current settings based on device state */
                rx = pdata->rx_ring_count;
                tx = pdata->tx_ring_count;

                combined = min(rx, tx);
                rx -= combined;
                tx -= combined;

                sys_op->combined_count = combined;
                sys_op->rx_count = rx;
                sys_op->tx_count = tx;

                sbuf_printf(sb,
                    "\nPre-set maximums:\n"
                    "RX:                %u\n"
                    "TX:                %u\n"
                    "Other:             %u\n"
                    "Combined:  %u\n",
                    sys_op->max_rx, sys_op->max_tx,
                    sys_op->max_other,
                    sys_op->max_combined);

                sbuf_printf(sb,
                    "\nCurrent hardware settings:\n"
                    "RX:                %u\n"
                    "TX:                %u\n"
                    "Other:             %u\n"
                    "Combined:  %u\n",
                    sys_op->rx_count, sys_op->tx_count,
                    sys_op->other_count,
                    sys_op->combined_count);

                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (0);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                parse_generic_sysctl(pdata, buf, sysctl_channels,
                    ARRAY_SIZE(sysctl_channels));

                axgbe_error( "channel inputs: combined=%u, rx-only=%u,"
                    " tx-only=%u\n", sys_op->combined_count,
                    sys_op->rx_count, sys_op->tx_count);
        }

        return (rc);
}


static int
sysctl_mac_stats_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        struct sbuf *sb;
        int rc = 0;
        int i;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                pdata->hw_if.read_mmc_stats(pdata);
                for (i = 0; i < XGBE_STATS_COUNT; i++) {
                sbuf_printf(sb, "\n %s: %lu",
                    xgbe_gstring_stats[i].stat_string,
                    *(uint64_t *)((uint8_t *)pdata + xgbe_gstring_stats[i].stat_offset));
                }
                for (i = 0; i < pdata->tx_ring_count; i++) {
                        sbuf_printf(sb,
                            "\n txq_packets[%d]: %lu"
                            "\n txq_bytes[%d]: %lu",
                            i, pdata->ext_stats.txq_packets[i],
                            i, pdata->ext_stats.txq_bytes[i]);
                }
                for (i = 0; i < pdata->rx_ring_count; i++) {
                        sbuf_printf(sb,
                            "\n rxq_packets[%d]: %lu"
                            "\n rxq_bytes[%d]: %lu",
                            i, pdata->ext_stats.rxq_packets[i],
                            i, pdata->ext_stats.rxq_bytes[i]);
                }

                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        return (-EINVAL);
}

static int
sysctl_xgmac_reg_value_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        unsigned int value;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                value = XGMAC_IOREAD(pdata, pdata->sysctl_xgmac_reg);
                axgbe_printf(2, "READ: %s: value: 0x%x\n",  __func__, value);
                sbuf_printf(sb, "\nXGMAC reg_value:     0x%x\n", value);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &value);
                axgbe_printf(2, "WRITE: %s: value: 0x%x\n",  __func__, value);
                XGMAC_IOWRITE(pdata, pdata->sysctl_xgmac_reg, value);
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xpcs_mmd_reg_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        unsigned int reg;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: xpcs_mmd: 0x%x\n",  __func__,
                    pdata->sysctl_xpcs_mmd);
                sbuf_printf(sb, "\nXPCS mmd_reg:        0x%x\n",
                    pdata->sysctl_xpcs_mmd);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &reg);
                axgbe_printf(2, "WRITE: %s: mmd_reg: 0x%x\n",  __func__, reg);
                pdata->sysctl_xpcs_mmd = reg;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xpcs_reg_addr_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        unsigned int reg;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: sysctl_xpcs_reg: 0x%x\n",  __func__,
                    pdata->sysctl_xpcs_reg);
                sbuf_printf(sb, "\nXPCS reg_addr:       0x%x\n",
                    pdata->sysctl_xpcs_reg);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &reg);
                axgbe_printf(2, "WRITE: %s: reg: 0x%x\n",  __func__, reg);
                pdata->sysctl_xpcs_reg = reg;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xpcs_reg_value_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        unsigned int value;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                value = XMDIO_READ(pdata, pdata->sysctl_xpcs_mmd,
                    pdata->sysctl_xpcs_reg);
                axgbe_printf(2, "READ: %s: value: 0x%x\n",  __func__, value);
                sbuf_printf(sb, "\nXPCS reg_value:      0x%x\n", value);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &value);
                axgbe_printf(2, "WRITE: %s: value: 0x%x\n",  __func__, value);
                XMDIO_WRITE(pdata, pdata->sysctl_xpcs_mmd,
                    pdata->sysctl_xpcs_reg, value);
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xprop_reg_addr_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        unsigned int reg;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: sysctl_xprop_reg: 0x%x\n",  __func__,
                    pdata->sysctl_xprop_reg);
                sbuf_printf(sb, "\nXPROP reg_addr:      0x%x\n",
                    pdata->sysctl_xprop_reg);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &reg);
                axgbe_printf(2, "WRITE: %s: reg: 0x%x\n",  __func__, reg);
                pdata->sysctl_xprop_reg = reg;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xprop_reg_value_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        unsigned int value;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                value = XP_IOREAD(pdata, pdata->sysctl_xprop_reg);
                axgbe_printf(2, "READ: %s: value: 0x%x\n",  __func__, value);
                sbuf_printf(sb, "\nXPROP reg_value:     0x%x\n", value);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &value);
                axgbe_printf(2, "WRITE: %s: value: 0x%x\n",  __func__, value);
                XP_IOWRITE(pdata, pdata->sysctl_xprop_reg, value);
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xi2c_reg_addr_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        unsigned int reg;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: sysctl_xi2c_reg: 0x%x\n",  __func__,
                    pdata->sysctl_xi2c_reg);
                sbuf_printf(sb, "\nXI2C reg_addr:       0x%x\n",
                    pdata->sysctl_xi2c_reg);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &reg);
                axgbe_printf(2, "WRITE: %s: reg: 0x%x\n",  __func__, reg);
                pdata->sysctl_xi2c_reg = reg;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_xi2c_reg_value_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        ssize_t buf_size = 64;
        char buf[buf_size];
        unsigned int value;
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                value = XI2C_IOREAD(pdata, pdata->sysctl_xi2c_reg);
                axgbe_printf(2, "READ: %s: value: 0x%x\n",  __func__, value);
                sbuf_printf(sb, "\nXI2C reg_value:      0x%x\n", value);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%x", &value);
                axgbe_printf(2, "WRITE: %s: value: 0x%x\n",  __func__, value);
                XI2C_IOWRITE(pdata, pdata->sysctl_xi2c_reg, value);
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_an_cdr_wr_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        unsigned int an_cdr_wr = 0;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: an_cdr_wr: %d\n",  __func__,
                    pdata->sysctl_an_cdr_workaround);
                sbuf_printf(sb, "%d\n", pdata->sysctl_an_cdr_workaround);
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%u", &an_cdr_wr);
                axgbe_printf(2, "WRITE: %s: an_cdr_wr: 0x%d\n",  __func__,
                    an_cdr_wr);

                if (an_cdr_wr)
                        pdata->sysctl_an_cdr_workaround = 1;
                else
                        pdata->sysctl_an_cdr_workaround = 0;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

static int
sysctl_an_cdr_track_early_handler(SYSCTL_HANDLER_ARGS)
{
        struct xgbe_prv_data *pdata = (struct xgbe_prv_data *)arg1;
        unsigned int an_cdr_track_early = 0;
        ssize_t buf_size = 64;
        char buf[buf_size];
        struct sbuf *sb;
        int rc = 0;

        if (req->newptr == NULL) {
                sb = sbuf_new_for_sysctl(NULL, NULL, buf_size, req);
                if (sb == NULL) {
                        rc = ENOMEM;
                        return (rc);
                }

                axgbe_printf(2, "READ: %s: an_cdr_track_early %d\n",  __func__,
                    pdata->sysctl_an_cdr_track_early);
                sbuf_printf(sb, "%d\n", pdata->sysctl_an_cdr_track_early);                      
                rc = sbuf_finish(sb);
                sbuf_delete(sb);
                return (rc);
        }

        rc = get_ubuf(req, buf);
        if (rc == 0) {
                sscanf(buf, "%u", &an_cdr_track_early);
                axgbe_printf(2, "WRITE: %s: an_cdr_track_early: %d\n",  __func__,
                    an_cdr_track_early);

                if (an_cdr_track_early)
                        pdata->sysctl_an_cdr_track_early = 1;
                else
                        pdata->sysctl_an_cdr_track_early = 0;
        }

        axgbe_printf(2, "%s: rc= %d\n",  __func__, rc);
        return (rc);
}

void
axgbe_sysctl_exit(struct xgbe_prv_data *pdata)
{

        if (pdata->sys_op)
                free(pdata->sys_op, M_AXGBE);
}

void 
axgbe_sysctl_init(struct xgbe_prv_data *pdata)
{
        struct sysctl_ctx_list *clist;
        struct sysctl_oid_list *top;
        struct sysctl_oid *parent; 
        struct sysctl_op *sys_op;

        sys_op = malloc(sizeof(*sys_op), M_AXGBE, M_WAITOK | M_ZERO);
        pdata->sys_op = sys_op;

        clist = device_get_sysctl_ctx(pdata->dev); 
        parent = device_get_sysctl_tree(pdata->dev);
        top = SYSCTL_CHILDREN(parent);

        /* Set defaults */
        pdata->sysctl_xgmac_reg = 0;
        pdata->sysctl_xpcs_mmd = 1;
        pdata->sysctl_xpcs_reg = 0;
        pdata->link_workaround = 1;
        pdata->tx_pause = 1;
        pdata->rx_pause = 1;
        pdata->enable_rss = 1;

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "axgbe_debug_level", CTLFLAG_RWTUN,
            &pdata->debug_level, 0, "axgbe log level -- higher is verbose");

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "sph_enable",
            CTLFLAG_RDTUN, &pdata->sph_enable, 1,
            "shows the split header feature state (1 - enable, 0 - disable");

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "link_workaround",
            CTLFLAG_RWTUN, &pdata->link_workaround, 0,
            "enable the workaround for link issue in coming up");

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "rss_enabled",
                CTLFLAG_RDTUN, &pdata->enable_rss, 1,
                "shows the RSS feature state (1 - enable, 0 - disable)");

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "tx_pause",
                CTLFLAG_RDTUN, &pdata->tx_pause, 1,
                "shows the Flow Control TX pause feature state (1 - enable, 0 - disable)");

        SYSCTL_ADD_UINT(clist, top, OID_AUTO, "rx_pause",
                CTLFLAG_RDTUN, &pdata->rx_pause, 1,
                "shows the Flow Control RX pause feature state (1 - enable, 0 - disable)");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xgmac_register",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_xgmac_reg_addr_handler, "IU",
            "xgmac register addr");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xgmac_register_value",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_xgmac_reg_value_handler, "IU",
            "xgmac register value");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xpcs_mmd",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_xpcs_mmd_reg_handler, "IU", "xpcs mmd register");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xpcs_register",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_xpcs_reg_addr_handler, "IU", "xpcs register");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xpcs_register_value",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_xpcs_reg_value_handler, "IU",
            "xpcs register value");

        if (pdata->xpcs_res) {
                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xprop_register",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_xprop_reg_addr_handler,
                    "IU", "xprop register");

                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xprop_register_value",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_xprop_reg_value_handler,
                    "IU", "xprop register value");
        }

        if (pdata->xpcs_res) {
                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xi2c_register",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_xi2c_reg_addr_handler,
                    "IU", "xi2c register");

                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "xi2c_register_value",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_xi2c_reg_value_handler,
                    "IU", "xi2c register value");
        }

        if (pdata->vdata->an_cdr_workaround) {
                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "an_cdr_workaround",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_an_cdr_wr_handler, "IU",
                    "an cdr workaround");

                SYSCTL_ADD_PROC(clist, top, OID_AUTO, "an_cdr_track_early",
                    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
                    pdata, 0, sysctl_an_cdr_track_early_handler, "IU",
                    "an cdr track early");
        }

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "drv_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_get_drv_info_handler, "IU",
            "xgbe drv info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "link_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_get_link_info_handler, "IU",
            "xgbe link info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "coalesce_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_coalesce_handler, "IU",
            "xgbe coalesce info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "pauseparam_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_pauseparam_handler, "IU",
            "xgbe pauseparam info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "link_ksettings_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_link_ksettings_handler, "IU",
            "xgbe link_ksettings info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "ringparam_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_ringparam_handler, "IU",
            "xgbe ringparam info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "channels_info",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_channels_handler, "IU",
            "xgbe channels info");

        SYSCTL_ADD_PROC(clist, top, OID_AUTO, "mac_stats",
            CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
            pdata, 0, sysctl_mac_stats_handler, "IU",
            "xgbe mac stats");
}