root/sys/dev/aq/aq_main.c
/*
 * aQuantia Corporation Network Driver
 * Copyright (C) 2019 aQuantia Corporation. All rights reserved
 *
 * 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.
 *
 *   (3)The name of the author may not be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_rss.h"

#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/sbuf.h>
#include <sys/socket.h>
#include <sys/sysctl.h>

#include <machine/bus.h>
#include <machine/resource.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_var.h>
#include <net/iflib.h>
#include <net/rss_config.h>

#include "ifdi_if.h"

#include "aq_device.h"
#include "aq_fw.h"
#include "aq_hw.h"
#include "aq2_hw.h"
#include "aq_hw_llh.h"
#include "aq_ring.h"
#include "aq_dbg.h"

MALLOC_DEFINE(M_AQ, "aq", "Aquantia");

static const char aq_driver_version[] = AQ_VER;

#define AQUANTIA_VENDOR_ID 0x1D6A

#define AQ_DEVICE_ID_0001       0x0001
#define AQ_DEVICE_ID_D100       0xD100
#define AQ_DEVICE_ID_D107       0xD107
#define AQ_DEVICE_ID_D108       0xD108
#define AQ_DEVICE_ID_D109       0xD109

#define AQ_DEVICE_ID_AQC100     0x00B1
#define AQ_DEVICE_ID_AQC107     0x07B1
#define AQ_DEVICE_ID_AQC108     0x08B1
#define AQ_DEVICE_ID_AQC109     0x09B1
#define AQ_DEVICE_ID_AQC111     0x11B1
#define AQ_DEVICE_ID_AQC112     0x12B1

#define AQ_DEVICE_ID_AQC100S    0x80B1
#define AQ_DEVICE_ID_AQC107S    0x87B1
#define AQ_DEVICE_ID_AQC108S    0x88B1
#define AQ_DEVICE_ID_AQC109S    0x89B1
#define AQ_DEVICE_ID_AQC111S    0x91B1
#define AQ_DEVICE_ID_AQC112S    0x92B1

static pci_vendor_info_t aq_vendor_info_array[] = {
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_0001,
            "Aquantia AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_D107,
            "Aquantia AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_D108,
            "Aquantia AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_D109,
            "Aquantia AQtion 2.5Gbit Network Adapter"),

        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC107,
            "Aquantia AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC108,
            "Aquantia AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC109,
            "Aquantia AQtion 2.5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC100,
            "Aquantia AQtion 10Gbit Network Adapter"),

        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC107S,
            "Aquantia AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC108S,
            "Aquantia AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC109S,
            "Aquantia AQtion 2.5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC100S,
            "Aquantia AQtion 10Gbit Network Adapter"),

        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC111,
            "Aquantia AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC112,
            "Aquantia AQtion 2.5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC111S,
            "Aquantia AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC112S,
            "Aquantia AQtion 2.5Gbit Network Adapter"),

        /* Atlantic 2 (Marvell AQtion) */
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC113,
            "Marvell AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC113C,
            "Marvell AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC113CA,
            "Marvell AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC113CS,
            "Marvell AQtion 10Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC114CS,
            "Marvell AQtion 5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC115C,
            "Marvell AQtion 2.5Gbit Network Adapter"),
        PVID(AQUANTIA_VENDOR_ID, AQ_DEVICE_ID_AQC116C,
            "Marvell AQtion 1Gbit Network Adapter"),

        PVID_END
};


/* Device setup, teardown, etc */
static void *aq_register(device_t dev);
static int aq_if_attach_pre(if_ctx_t ctx);
static int aq_if_attach_post(if_ctx_t ctx);
static int aq_if_detach(if_ctx_t ctx);
static int aq_if_shutdown(if_ctx_t ctx);
static int aq_if_suspend(if_ctx_t ctx);
static int aq_if_resume(if_ctx_t ctx);

/* Soft queue setup and teardown */
static int aq_if_tx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs,
                    uint64_t *paddrs, int ntxqs, int ntxqsets);
static int aq_if_rx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs,
                    uint64_t *paddrs, int nrxqs, int nrxqsets);
static void aq_if_queues_free(if_ctx_t ctx);

/* Device configuration */
static void aq_if_init(if_ctx_t ctx);
static void aq_if_stop(if_ctx_t ctx);
static void aq_if_multi_set(if_ctx_t ctx);
static int aq_if_mtu_set(if_ctx_t ctx, uint32_t mtu);
static void aq_if_media_status(if_ctx_t ctx, struct ifmediareq *ifmr);
static int aq_if_media_change(if_ctx_t ctx);
static int aq_if_promisc_set(if_ctx_t ctx, int flags);
static uint64_t aq_if_get_counter(if_ctx_t ctx, ift_counter cnt);
static void aq_if_timer(if_ctx_t ctx, uint16_t qid);
static int aq_hw_capabilities(struct aq_dev *softc);
static void aq_add_stats_sysctls(struct aq_dev *softc);

/* Interrupt enable / disable */
static void     aq_if_enable_intr(if_ctx_t ctx);
static void     aq_if_disable_intr(if_ctx_t ctx);
static int      aq_if_rx_queue_intr_enable(if_ctx_t ctx, uint16_t rxqid);
static int      aq_if_tx_queue_intr_enable(if_ctx_t ctx, uint16_t txqid);
static int      aq_if_msix_intr_assign(if_ctx_t ctx, int msix);

/* VLAN support */
static bool aq_is_vlan_promisc_required(struct aq_dev *softc);
static void aq_update_vlan_filters(struct aq_dev *softc);
static void aq_if_vlan_register(if_ctx_t ctx, uint16_t vtag);
static void aq_if_vlan_unregister(if_ctx_t ctx, uint16_t vtag);

/* Informational/diagnostic */
static void     aq_if_led_func(if_ctx_t ctx, int onoff);

static device_method_t aq_methods[] = {
        DEVMETHOD(device_register, aq_register),
        DEVMETHOD(device_probe, iflib_device_probe),
        DEVMETHOD(device_attach, iflib_device_attach),
        DEVMETHOD(device_detach, iflib_device_detach),
        DEVMETHOD(device_shutdown, iflib_device_shutdown),
        DEVMETHOD(device_suspend, iflib_device_suspend),
        DEVMETHOD(device_resume, iflib_device_resume),

        DEVMETHOD_END
};

static driver_t aq_driver = {
        "aq", aq_methods, sizeof(struct aq_dev),
};

DRIVER_MODULE(atlantic, pci, aq_driver, 0, 0);

MODULE_VERSION(atlantic, 1);
MODULE_DEPEND(atlantic, pci, 1, 1, 1);
MODULE_DEPEND(atlantic, ether, 1, 1, 1);
MODULE_DEPEND(atlantic, iflib, 1, 1, 1);

IFLIB_PNP_INFO(pci, atlantic, aq_vendor_info_array);

static device_method_t aq_if_methods[] = {
        /* Device setup, teardown, etc */
        DEVMETHOD(ifdi_attach_pre, aq_if_attach_pre),
        DEVMETHOD(ifdi_attach_post, aq_if_attach_post),
        DEVMETHOD(ifdi_detach, aq_if_detach),

        DEVMETHOD(ifdi_shutdown, aq_if_shutdown),
        DEVMETHOD(ifdi_suspend, aq_if_suspend),
        DEVMETHOD(ifdi_resume, aq_if_resume),

        /* Soft queue setup and teardown */
        DEVMETHOD(ifdi_tx_queues_alloc, aq_if_tx_queues_alloc),
        DEVMETHOD(ifdi_rx_queues_alloc, aq_if_rx_queues_alloc),
        DEVMETHOD(ifdi_queues_free, aq_if_queues_free),

        /* Device configuration */
        DEVMETHOD(ifdi_init, aq_if_init),
        DEVMETHOD(ifdi_stop, aq_if_stop),
        DEVMETHOD(ifdi_multi_set, aq_if_multi_set),
        DEVMETHOD(ifdi_mtu_set, aq_if_mtu_set),
        DEVMETHOD(ifdi_media_status, aq_if_media_status),
        DEVMETHOD(ifdi_media_change, aq_if_media_change),
        DEVMETHOD(ifdi_promisc_set, aq_if_promisc_set),
        DEVMETHOD(ifdi_get_counter, aq_if_get_counter),
        DEVMETHOD(ifdi_update_admin_status, aq_if_update_admin_status),
        DEVMETHOD(ifdi_timer, aq_if_timer),

        /* Interrupt enable / disable */
        DEVMETHOD(ifdi_intr_enable, aq_if_enable_intr),
        DEVMETHOD(ifdi_intr_disable, aq_if_disable_intr),
        DEVMETHOD(ifdi_rx_queue_intr_enable, aq_if_rx_queue_intr_enable),
        DEVMETHOD(ifdi_tx_queue_intr_enable, aq_if_tx_queue_intr_enable),
        DEVMETHOD(ifdi_msix_intr_assign, aq_if_msix_intr_assign),

        /* VLAN support */
        DEVMETHOD(ifdi_vlan_register, aq_if_vlan_register),
        DEVMETHOD(ifdi_vlan_unregister, aq_if_vlan_unregister),

        /* Informational/diagnostic */
        DEVMETHOD(ifdi_led_func, aq_if_led_func),

        DEVMETHOD_END
};

static driver_t aq_if_driver = {
        "aq_if", aq_if_methods, sizeof(struct aq_dev)
};

static struct if_shared_ctx aq_sctx_init = {
        .isc_magic = IFLIB_MAGIC,
        .isc_q_align = PAGE_SIZE,
        .isc_tx_maxsize = HW_ATL_B0_TSO_SIZE,
        .isc_tx_maxsegsize = HW_ATL_B0_MTU_JUMBO,
        .isc_tso_maxsize = HW_ATL_B0_TSO_SIZE,
        .isc_tso_maxsegsize = HW_ATL_B0_MTU_JUMBO,
        .isc_rx_maxsize = HW_ATL_B0_MTU_JUMBO,
        .isc_rx_nsegments = 16,
        .isc_rx_maxsegsize = PAGE_SIZE,
        .isc_nfl = 1,
        .isc_nrxqs = 1,
        .isc_ntxqs = 1,
        .isc_admin_intrcnt = 1,
        .isc_vendor_info = aq_vendor_info_array,
        .isc_driver_version = aq_driver_version,
        .isc_driver = &aq_if_driver,
        .isc_flags = IFLIB_NEED_SCRATCH | IFLIB_TSO_INIT_IP |
            IFLIB_NEED_ZERO_CSUM,

        .isc_nrxd_min = {HW_ATL_B0_MIN_RXD},
        .isc_ntxd_min = {HW_ATL_B0_MIN_TXD},
        .isc_nrxd_max = {HW_ATL_B0_MAX_RXD},
        .isc_ntxd_max = {HW_ATL_B0_MAX_TXD},
        .isc_nrxd_default = {PAGE_SIZE / sizeof(volatile union aq_txc_desc) * 4},
        .isc_ntxd_default = {PAGE_SIZE / sizeof(volatile union aq_txc_desc) * 4},
};

/* RSS hash types; honor the kernel policy (UDP 4-tuple off by default). */
u_int
aq_rss_hashconfig(void)
{
#ifdef RSS
        return (rss_gethashconfig());
#else
        return (RSS_HASHTYPE_RSS_IPV4 | RSS_HASHTYPE_RSS_TCP_IPV4 |
            RSS_HASHTYPE_RSS_IPV6 | RSS_HASHTYPE_RSS_TCP_IPV6 |
            RSS_HASHTYPE_RSS_IPV6_EX | RSS_HASHTYPE_RSS_TCP_IPV6_EX);
#endif
}


/*
 * Device Methods
 */
static void *
aq_register(device_t dev)
{
        return (&aq_sctx_init);
}

static int
aq_if_attach_pre(if_ctx_t ctx)
{
        struct aq_dev *softc;
        struct aq_hw *hw;
        if_softc_ctx_t scctx;
        int rc;

        AQ_DBG_ENTER();
        softc = iflib_get_softc(ctx);
        rc = 0;

        softc->ctx = ctx;
        softc->dev = iflib_get_dev(ctx);
        softc->media = iflib_get_media(ctx);
        softc->scctx = iflib_get_softc_ctx(ctx);
        softc->sctx = iflib_get_sctx(ctx);
        scctx = softc->scctx;

        softc->mmio_rid = PCIR_BAR(0);
        softc->mmio_res = bus_alloc_resource_any(softc->dev, SYS_RES_MEMORY,
            &softc->mmio_rid, RF_ACTIVE|RF_SHAREABLE);
        if (softc->mmio_res == NULL) {
                device_printf(softc->dev,
                    "failed to allocate MMIO resources\n");
                rc = ENXIO;
                goto fail;
        }

        softc->mmio_tag = rman_get_bustag(softc->mmio_res);
        softc->mmio_handle = rman_get_bushandle(softc->mmio_res);
        softc->mmio_size = rman_get_size(softc->mmio_res);
        softc->hw.hw_tag = softc->mmio_tag;
        softc->hw.hw_handle = softc->mmio_handle;
        softc->hw.dev = softc->dev;
        softc->hw.device_id = pci_get_device(softc->dev);
        if (aq_is_atlantic2(softc->hw.device_id))
                softc->hw.chip_features |= AQ_HW_CHIP_ATLANTIC2;
        hw = &softc->hw;
        hw->link_rate = aq_fw_speed_auto;
        hw->itr = -1;
        hw->fc.fc_rx = 1;
        hw->fc.fc_tx = 1;
        softc->linkup = 0U;

        /* Look up ops and caps. */
        rc = aq_hw_mpi_create(hw);
        if (rc != 0) {
                device_printf(softc->dev,
                    "%s: aq_hw_mpi_create failed, err=%d\n", __func__, rc);
                goto fail;
        }

        if (hw->fast_start_enabled)
                rc = hw->fw_ops->reset(hw);
        else
                rc = aq_hw_reset(&softc->hw, !IS_CHIP_FEATURE(hw, ATLANTIC2));
        if (rc != 0) {
                device_printf(softc->dev, "%s: reset failed, err=%d\n",
                    __func__, rc);
                goto fail;
        }
        aq_hw_capabilities(softc);

        rc = aq_hw_get_mac_permanent(hw, hw->mac_addr);
        if (rc != 0) {
                device_printf(softc->dev, "unable to get MAC address from HW\n");
                goto fail;
        }

        softc->admin_ticks = 0;

        iflib_set_mac(ctx, hw->mac_addr);
        scctx->isc_tx_csum_flags = CSUM_IP | CSUM_TCP | CSUM_UDP | CSUM_TSO |
            CSUM_IP6_TCP | CSUM_IP6_UDP | CSUM_IP6_TSO;
        scctx->isc_capabilities = IFCAP_RXCSUM | IFCAP_TXCSUM | IFCAP_HWCSUM |
            IFCAP_HWCSUM_IPV6 | IFCAP_TSO | IFCAP_LRO | IFCAP_JUMBO_MTU |
            IFCAP_VLAN_HWFILTER | IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING |
            IFCAP_VLAN_HWCSUM | IFCAP_VLAN_HWTSO;
        scctx->isc_capenable = scctx->isc_capabilities;
        scctx->isc_tx_nsegments = 31;
        scctx->isc_tx_tso_segments_max = 31;
        scctx->isc_tx_tso_size_max =
            HW_ATL_B0_TSO_SIZE - sizeof(struct ether_vlan_header);
        scctx->isc_tx_tso_segsize_max = HW_ATL_B0_MTU_JUMBO;
        scctx->isc_min_frame_size = 52;
        scctx->isc_max_frame_size = ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN +
            ETHER_VLAN_ENCAP_LEN;
        scctx->isc_txrx = &aq_txrx;

        scctx->isc_txqsizes[0] = sizeof(volatile struct aq_tx_desc) * scctx->isc_ntxd[0];
        scctx->isc_rxqsizes[0] = sizeof(volatile struct aq_rx_desc) * scctx->isc_nrxd[0];

        scctx->isc_ntxqsets_max = HW_ATL_B0_RINGS_MAX;
        scctx->isc_nrxqsets_max = HW_ATL_RSS_INDIRECTION_QUEUES_MAX;

        /* iflib will map and release this bar */
        scctx->isc_msix_bar = pci_msix_table_bar(softc->dev);

        softc->vlan_tags = bit_alloc(4096, M_AQ, M_NOWAIT);
        if (softc->vlan_tags == NULL) {
                rc = ENOMEM;
                goto fail;
        }

        AQ_DBG_EXIT(rc);
        return (rc);

fail:
        if (softc->mmio_res != NULL)
                bus_release_resource(softc->dev, SYS_RES_MEMORY,
                    softc->mmio_rid, softc->mmio_res);

        AQ_DBG_EXIT(rc);
        return (rc);
}


static int
aq_if_attach_post(if_ctx_t ctx)
{
        struct aq_dev *softc;
        int rc;

        AQ_DBG_ENTER();

        softc = iflib_get_softc(ctx);
        rc = 0;

        aq_update_hw_stats(softc);

        aq_initmedia(softc);


        switch (softc->scctx->isc_intr) {
        case IFLIB_INTR_LEGACY:
                rc = EOPNOTSUPP;
                goto exit;
        goto exit;
                break;
        case IFLIB_INTR_MSI:
                break;
        case IFLIB_INTR_MSIX:
                break;
        default:
                device_printf(softc->dev, "unknown interrupt mode\n");
                rc = EOPNOTSUPP;
                goto exit;
        }

        aq_add_stats_sysctls(softc);
        /* RSS */
        uint32_t rss_qs = MIN(softc->rx_rings_count, HW_ATL_RSS_INDIRECTION_QUEUES_MAX);
#ifdef RSS
        rss_getkey(softc->rss_key);
        for (int i = nitems(softc->rss_table); i--;)
                softc->rss_table[i] = rss_get_indirection_to_bucket(i) % rss_qs;
#else
        arc4rand(softc->rss_key, HW_ATL_RSS_HASHKEY_SIZE, 0);
        for (int i = nitems(softc->rss_table); i--;)
                softc->rss_table[i] = i % rss_qs;
#endif
exit:
        AQ_DBG_EXIT(rc);
        return (rc);
}


static int
aq_if_detach(if_ctx_t ctx)
{
        struct aq_dev *softc;
        int i;

        AQ_DBG_ENTER();
        softc = iflib_get_softc(ctx);

        aq_hw_deinit(&softc->hw);

        for (i = 0; i < softc->rx_rings_count; i++)
                iflib_irq_free(ctx, &softc->rx_rings[i]->irq);
        iflib_irq_free(ctx, &softc->irq);


        if (softc->mmio_res != NULL)
                bus_release_resource(softc->dev, SYS_RES_MEMORY,
                    softc->mmio_rid, softc->mmio_res);

        free(softc->vlan_tags, M_AQ);

        AQ_DBG_EXIT(0);
        return (0);
}

static int
aq_if_shutdown(if_ctx_t ctx)
{
        return (aq_if_suspend(ctx));
}

static int
aq_if_suspend(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);

        AQ_DBG_ENTER();

        aq_if_stop(ctx);
        aq_hw_deinit(&softc->hw);

        AQ_DBG_EXIT(0);
        return (0);
}

static int
aq_if_resume(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        int err;

        AQ_DBG_ENTER();
        err = aq_hw_mpi_create(&softc->hw);
        AQ_DBG_EXIT(err);
        return (err);
}

_Static_assert(sizeof(struct aq_ring_stats) % sizeof(counter_u64_t) == 0,
    "aq_ring_stats must contain only counter_u64_t fields");

static int
aq_ring_stats_alloc(struct aq_ring *ring)
{
        counter_u64_t *c = (counter_u64_t *)&ring->stats;
        int i, n = sizeof(ring->stats) / sizeof(counter_u64_t);

        for (i = 0; i < n; i++) {
                c[i] = counter_u64_alloc(M_NOWAIT);
                if (c[i] == NULL) {
                        while (i-- > 0) {
                                counter_u64_free(c[i]);
                                c[i] = NULL;
                        }
                        return (ENOMEM);
                }
        }
        return (0);
}

static void
aq_ring_stats_free(struct aq_ring *ring)
{
        counter_u64_t *c = (counter_u64_t *)&ring->stats;
        int i, n = sizeof(ring->stats) / sizeof(counter_u64_t);

        for (i = 0; i < n; i++) {
                if (c[i] != NULL) {
                        counter_u64_free(c[i]);
                        c[i] = NULL;
                }
        }
}

/* Soft queue setup and teardown */
static int
aq_if_tx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs, uint64_t *paddrs,
    int ntxqs, int ntxqsets)
{
        struct aq_dev *softc;
        struct aq_ring *ring;
        int rc = 0, i;

        AQ_DBG_ENTERA("ntxqs=%d, ntxqsets=%d", ntxqs, ntxqsets);
        softc = iflib_get_softc(ctx);
        AQ_DBG_PRINT("tx descriptors  number %d", softc->scctx->isc_ntxd[0]);

        for (i = 0; i < ntxqsets; i++) {
                ring = softc->tx_rings[i] = malloc(sizeof(struct aq_ring),
                                                   M_AQ, M_NOWAIT | M_ZERO);
                if (!ring){
                        rc = ENOMEM;
                        device_printf(softc->dev, "atlantic: tx_ring malloc fail\n");
                        goto fail;
                }
                ring->tx_descs = (volatile struct aq_tx_desc*)vaddrs[i];
                ring->tx_size = softc->scctx->isc_ntxd[0];
                ring->tx_descs_phys = paddrs[i];
                ring->tx_head = ring->tx_tail = 0;
                ring->index = i;
                ring->dev = softc;

                softc->tx_rings_count++;

                rc = aq_ring_stats_alloc(ring);
                if (rc != 0) {
                        device_printf(softc->dev,
                            "atlantic: tx_ring stats alloc fail\n");
                        goto fail;
                }
        }

        AQ_DBG_EXIT(rc);
        return (rc);

fail:
        aq_if_queues_free(ctx);
        AQ_DBG_EXIT(rc);
        return (rc);
}

static int
aq_if_rx_queues_alloc(if_ctx_t ctx, caddr_t *vaddrs, uint64_t *paddrs,
    int nrxqs, int nrxqsets)
{
        struct aq_dev *softc;
        struct aq_ring *ring;
        int rc = 0, i;

        AQ_DBG_ENTERA("nrxqs=%d, nrxqsets=%d", nrxqs, nrxqsets);
        softc = iflib_get_softc(ctx);

        for (i = 0; i < nrxqsets; i++) {
                ring = softc->rx_rings[i] = malloc(sizeof(struct aq_ring),
                                                   M_AQ, M_NOWAIT | M_ZERO);
                if (!ring){
                        rc = ENOMEM;
                        device_printf(softc->dev,
                            "atlantic: rx_ring malloc fail\n");
                        goto fail;
                }

                ring->rx_descs = (volatile struct aq_rx_desc*)vaddrs[i];
                ring->rx_descs_phys = paddrs[i];
                ring->rx_size = softc->scctx->isc_nrxd[0];
                ring->index = i;
                ring->dev = softc;

                softc->rx_rings_count++;

                rc = aq_ring_stats_alloc(ring);
                if (rc != 0) {
                        device_printf(softc->dev,
                            "atlantic: rx_ring stats alloc fail\n");
                        goto fail;
                }
        }

        AQ_DBG_EXIT(rc);
        return (rc);

fail:
        aq_if_queues_free(ctx);
        AQ_DBG_EXIT(rc);
        return (rc);
}

static void
aq_if_queues_free(if_ctx_t ctx)
{
        struct aq_dev *softc;
        int i;

        AQ_DBG_ENTER();
        softc = iflib_get_softc(ctx);

        for (i = 0; i < softc->tx_rings_count; i++) {
                if (softc->tx_rings[i]) {
                        aq_ring_stats_free(softc->tx_rings[i]);
                        free(softc->tx_rings[i], M_AQ);
                        softc->tx_rings[i] = NULL;
                }
        }
        softc->tx_rings_count = 0;
        for (i = 0; i < softc->rx_rings_count; i++) {
                if (softc->rx_rings[i]){
                        aq_ring_stats_free(softc->rx_rings[i]);
                        free(softc->rx_rings[i], M_AQ);
                        softc->rx_rings[i] = NULL;
                }
        }
        softc->rx_rings_count = 0;

        AQ_DBG_EXIT(0);
        return;
}

/* Device configuration */
static void
aq_if_init(if_ctx_t ctx)
{
        struct aq_dev *softc;
        struct aq_hw *hw;
        struct ifmediareq ifmr;
        int i, err;

        AQ_DBG_ENTER();
        softc = iflib_get_softc(ctx);
        hw = &softc->hw;

        atomic_store_rel_long(&hw->flags, 0);

        hw->tx_rings_count = softc->tx_rings_count;

        err = aq_hw_init(&softc->hw, softc->hw.mac_addr, softc->msix,
            softc->scctx->isc_intr == IFLIB_INTR_MSIX);
        if (err != 0) {
                device_printf(softc->dev, "atlantic: aq_hw_init: %d\n", err);
                AQ_DBG_EXIT(err);
                return;
        }

        aq_if_media_status(ctx, &ifmr);

        aq_update_vlan_filters(softc);

        for (i = 0; i < softc->tx_rings_count; i++) {
                struct aq_ring *ring = softc->tx_rings[i];
                err = aq_ring_tx_init(&softc->hw, ring);
                if (err) {
                        device_printf(softc->dev,
                            "atlantic: aq_ring_tx_init: %d\n", err);
                }
                err = aq_ring_tx_start(hw, ring);
                if (err != 0) {
                        device_printf(softc->dev,
                            "atlantic: aq_ring_tx_start: %d\n", err);
                }
        }
        for (i = 0; i < softc->rx_rings_count; i++) {
                struct aq_ring *ring = softc->rx_rings[i];
                ring->rx_buf_size = iflib_get_rx_mbuf_sz(ctx);
                err = aq_ring_rx_init(&softc->hw, ring);
                if (err) {
                        device_printf(softc->dev,
                            "atlantic: aq_ring_rx_init: %d\n", err);
                }
                err = aq_ring_rx_start(hw, ring);
                if (err != 0) {
                        device_printf(softc->dev,
                            "atlantic: aq_ring_rx_start: %d\n", err);
                }
                aq_if_rx_queue_intr_enable(ctx, i);
        }

        aq_hw_start(hw);
        aq_if_enable_intr(ctx);
        aq_hw_rss_hash_set(&softc->hw, softc->rss_key);
        aq_hw_rss_set(&softc->hw, softc->rss_table);
        /* A2 selects UDP hashing per-protocol in REDIR2; A1 uses the filter. */
        if (!IS_CHIP_FEATURE(hw, ATLANTIC2))
                aq_hw_udp_rss_enable(hw, (aq_rss_hashconfig() &
                    (RSS_HASHTYPE_RSS_UDP_IPV4 | RSS_HASHTYPE_RSS_UDP_IPV6 |
                    RSS_HASHTYPE_RSS_UDP_IPV6_EX)) != 0);
        aq_hw_set_link_speed(hw, hw->link_rate);

        /* iflib does not replay filter state after init; aq_hw_init() clears it. */
        aq_if_multi_set(ctx);
        if (aq_if_promisc_set(ctx, if_getflags(iflib_get_ifp(ctx))) != 0)
                device_printf(softc->dev, "could not restore promiscuous mode\n");

        AQ_DBG_EXIT(0);
}


static void
aq_if_stop(if_ctx_t ctx)
{
        struct aq_dev *softc;
        struct aq_hw *hw;
        int i;

        AQ_DBG_ENTER();

        softc = iflib_get_softc(ctx);
        hw = &softc->hw;

        /* disable interrupt */
        aq_if_disable_intr(ctx);

        for (i = 0; i < softc->tx_rings_count; i++) {
                aq_ring_tx_stop(hw, softc->tx_rings[i]);
                softc->tx_rings[i]->tx_head = 0;
                softc->tx_rings[i]->tx_tail = 0;
        }
        for (i = 0; i < softc->rx_rings_count; i++) {
                aq_ring_rx_stop(hw, softc->rx_rings[i]);
        }

        aq_hw_reset(&softc->hw, true);
        memset(&softc->last_stats, 0, sizeof(softc->last_stats));
        softc->linkup = false;
        aq_if_update_admin_status(ctx);
        AQ_DBG_EXIT(0);
}

static uint64_t
aq_if_get_counter(if_ctx_t ctx, ift_counter cnt)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        if_t ifp = iflib_get_ifp(ctx);

        switch (cnt) {
        case IFCOUNTER_IERRORS:
                return (softc->curr_stats.erpr);
        case IFCOUNTER_IQDROPS:
                return (softc->curr_stats.dpc);
        case IFCOUNTER_OERRORS:
                return (softc->curr_stats.erpt);
        default:
                return (if_get_counter_default(ifp, cnt));
        }
}

static u_int
aq_mc_filter_apply(void *arg, struct sockaddr_dl *dl, u_int count)
{
        struct aq_dev *softc = arg;
        struct aq_hw *hw = &softc->hw;
        uint8_t *mac_addr = NULL;

        if (count == AQ_HW_MAC_MAX)
                return (0);

        mac_addr = LLADDR(dl);
        aq_hw_mac_addr_set(hw, mac_addr, count + 1);

        aq_log_detail("set %d mc address %6D", count + 1, mac_addr, ":");
        return (1);
}

static bool
aq_is_mc_promisc_required(struct aq_dev *softc)
{
        return (softc->mcnt >= AQ_HW_MAC_MAX);
}

static void
aq_if_multi_set(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        if_t ifp = iflib_get_ifp(ctx);
        struct aq_hw  *hw = &softc->hw;
        AQ_DBG_ENTER();
        softc->mcnt = if_llmaddr_count(ifp);

        /* Reconcile HW to the current list: clear stale slots, reprogram. */
        if (softc->mcnt < AQ_HW_MAC_MAX) {
                for (int i = 1; i < AQ_HW_MAC_MAX; i++)
                        rpfl2_uc_flr_en_set(hw, 0U, i);
                if_foreach_llmaddr(ifp, &aq_mc_filter_apply, softc);
        }

        if (aq_hw_set_promisc(hw, !!(if_getflags(ifp) & IFF_PROMISC),
            aq_is_vlan_promisc_required(softc),
            !!(if_getflags(ifp) & IFF_ALLMULTI) ||
            aq_is_mc_promisc_required(softc)) != 0)
                device_printf(softc->dev,
                    "multicast filter update failed\n");
        AQ_DBG_EXIT(0);
}

static int
aq_if_mtu_set(if_ctx_t ctx, uint32_t mtu)
{
        if_softc_ctx_t scctx = iflib_get_softc_ctx(ctx);
        uint32_t max_frame;

        AQ_DBG_ENTERA("mtu %u", mtu);

        max_frame = mtu + ETHER_HDR_LEN + ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN;
        if (max_frame > HW_ATL_B0_MTU_JUMBO) {
                AQ_DBG_EXIT(EINVAL);
                return (EINVAL);
        }

        scctx->isc_max_frame_size = max_frame;

        AQ_DBG_EXIT(0);
        return (0);
}

static void
aq_if_media_status(if_ctx_t ctx, struct ifmediareq *ifmr)
{
        if_t ifp;

        AQ_DBG_ENTER();

        ifp = iflib_get_ifp(ctx);

        aq_mediastatus(ifp, ifmr);

        AQ_DBG_EXIT(0);
}

static int
aq_if_media_change(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        if_t ifp = iflib_get_ifp(ctx);
        int rc = 0;

        AQ_DBG_ENTER();

        /* Not allowd in UP state, since causes unsync of rings */
        if ((if_getflags(ifp) & IFF_UP)){
                rc = EPERM;
                goto exit;
        }

        ifp = iflib_get_ifp(softc->ctx);

        rc = aq_mediachange(ifp);

exit:
        AQ_DBG_EXIT(rc);
        return (rc);
}

static int
aq_if_promisc_set(if_ctx_t ctx, int flags)
{
        struct aq_dev *softc;
        int err;

        AQ_DBG_ENTER();

        softc = iflib_get_softc(ctx);

        err = aq_hw_set_promisc(&softc->hw, !!(flags & IFF_PROMISC),
            aq_is_vlan_promisc_required(softc),
            !!(flags & IFF_ALLMULTI) || aq_is_mc_promisc_required(softc));

        AQ_DBG_EXIT(err);
        return (err);
}

static void
aq_if_timer(if_ctx_t ctx, uint16_t qid)
{
        struct aq_dev *softc;
        uint64_t ticks_now;


        softc = iflib_get_softc(ctx);
        ticks_now = ticks;

        /* Schedule aqc_if_update_admin_status() once per sec */
        if (ticks_now - softc->admin_ticks >= hz) {
                softc->admin_ticks = ticks_now;
                iflib_admin_intr_deferred(ctx);
        }

        return;

}

/* Interrupt enable / disable */
static void
aq_if_enable_intr(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        struct aq_hw  *hw = &softc->hw;

        AQ_DBG_ENTER();

        /* Enable interrupts */
        itr_irq_msk_setlsw_set(hw, BIT(softc->msix + 1) - 1);

        AQ_DBG_EXIT(0);
}

static void
aq_if_disable_intr(if_ctx_t ctx)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        struct aq_hw  *hw = &softc->hw;

        AQ_DBG_ENTER();

        /* Disable interrupts */
        itr_irq_msk_clearlsw_set(hw, BIT(softc->msix + 1) - 1);

        AQ_DBG_EXIT(0);
}

static int
aq_if_rx_queue_intr_enable(if_ctx_t ctx, uint16_t rxqid)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        struct aq_hw  *hw = &softc->hw;

        AQ_DBG_ENTER();

        itr_irq_msk_setlsw_set(hw, BIT(softc->rx_rings[rxqid]->msix));

        AQ_DBG_EXIT(0);
        return (0);
}

static int
aq_if_tx_queue_intr_enable(if_ctx_t ctx, uint16_t txqid)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        struct aq_hw  *hw = &softc->hw;

        AQ_DBG_ENTER();

        itr_irq_msk_setlsw_set(hw, BIT(softc->tx_rings[txqid]->msix));

        AQ_DBG_EXIT(0);
        return (0);
}

static int
aq_if_msix_intr_assign(if_ctx_t ctx, int msix)
{
        struct aq_dev *softc;
        int i, vector = 0, rc;
        char irq_name[16];
        int rx_vectors;

        AQ_DBG_ENTER();
        softc = iflib_get_softc(ctx);

        for (i = 0; i < softc->rx_rings_count; i++, vector++) {
                snprintf(irq_name, sizeof(irq_name), "rxq%d", i);
                rc = iflib_irq_alloc_generic(ctx, &softc->rx_rings[i]->irq,
                    vector + 1, IFLIB_INTR_RXTX, aq_isr_rx, softc->rx_rings[i],
                        softc->rx_rings[i]->index, irq_name);
                device_printf(softc->dev, "Assign IRQ %u to rx ring %u\n",
                                          vector, softc->rx_rings[i]->index);

                if (rc) {
                        device_printf(softc->dev, "failed to set up RX handler\n");
                        goto fail;
                }

                softc->rx_rings[i]->msix = vector;
        }

        rx_vectors = vector;

        for (i = 0; i < softc->tx_rings_count; i++) {
                snprintf(irq_name, sizeof(irq_name), "txq%d", i);
                softc->tx_rings[i]->msix = (i % softc->rx_rings_count);
                iflib_softirq_alloc_generic(ctx,
                    &softc->rx_rings[softc->tx_rings[i]->msix]->irq,
                    IFLIB_INTR_TX, softc->tx_rings[i],
                    softc->tx_rings[i]->index, irq_name);
                device_printf(softc->dev, "Assign IRQ %u to tx ring %u\n",
                    softc->tx_rings[i]->msix, softc->tx_rings[i]->index);
        }

        rc = iflib_irq_alloc_generic(ctx, &softc->irq, rx_vectors + 1,
            IFLIB_INTR_ADMIN, aq_linkstat_isr, softc, 0, "aq");
        softc->msix = rx_vectors;
        device_printf(softc->dev, "Assign IRQ %u to admin proc \n",
            rx_vectors);
        if (rc) {
                device_printf(iflib_get_dev(ctx),
                    "Failed to register admin handler\n");
                goto fail;
        }
        AQ_DBG_EXIT(0);
        return (0);

fail:
        AQ_DBG_EXIT(rc);
        return (rc);
}

static bool
aq_is_vlan_promisc_required(struct aq_dev *softc)
{
        int vlan_tag_count;

        bit_count(softc->vlan_tags, 0, 4096, &vlan_tag_count);

        /* Filter only with 1..16 VLANs; 0 or >16 pass all tags. */
        return (vlan_tag_count == 0 ||
            vlan_tag_count > AQ_HW_VLAN_MAX_FILTERS);
}

static void
aq_update_vlan_filters(struct aq_dev *softc)
{
        struct aq_rx_filter_vlan aq_vlans[AQ_HW_VLAN_MAX_FILTERS];
        struct aq_hw  *hw = &softc->hw;
        int bit_pos = 0;
        int vlan_tag = -1;
        int i;

        hw_atl_b0_hw_vlan_promisc_set(hw, true);
        for (i = 0; i < AQ_HW_VLAN_MAX_FILTERS; i++) {
                bit_ffs_at(softc->vlan_tags, bit_pos, 4096, &vlan_tag);
                if (vlan_tag != -1) {
                        aq_vlans[i].enable = true;
                        aq_vlans[i].location = i;
                        aq_vlans[i].queue = 0xFF;
                        aq_vlans[i].vlan_id = vlan_tag;
                        bit_pos = vlan_tag + 1;
                } else {
                        aq_vlans[i].enable = false;
                }
        }

        hw_atl_b0_hw_vlan_set(hw, aq_vlans);
        if (hw_atl_b0_hw_vlan_promisc_set(hw,
            aq_is_vlan_promisc_required(softc) ||
            (if_getflags(iflib_get_ifp(softc->ctx)) & IFF_PROMISC) != 0) != 0)
                device_printf(softc->dev, "VLAN filter update failed\n");
}

/* VLAN support */
static void
aq_if_vlan_register(if_ctx_t ctx, uint16_t vtag)
{
        struct aq_dev *softc = iflib_get_softc(ctx);

        AQ_DBG_ENTERA("%d", vtag);

        bit_set(softc->vlan_tags, vtag);

        aq_update_vlan_filters(softc);

        AQ_DBG_EXIT(0);
}

static void
aq_if_vlan_unregister(if_ctx_t ctx, uint16_t vtag)
{
        struct aq_dev *softc = iflib_get_softc(ctx);

        AQ_DBG_ENTERA("%d", vtag);

        bit_clear(softc->vlan_tags, vtag);

        aq_update_vlan_filters(softc);

        AQ_DBG_EXIT(0);
}

static void
aq_if_led_func(if_ctx_t ctx, int onoff)
{
        struct aq_dev *softc = iflib_get_softc(ctx);
        struct aq_hw  *hw = &softc->hw;

        AQ_DBG_ENTERA("%d", onoff);
        if (hw->fw_ops->led_control)
                hw->fw_ops->led_control(hw, onoff);

        AQ_DBG_EXIT(0);
}

static int
aq_hw_capabilities(struct aq_dev *softc)
{

        if (pci_get_vendor(softc->dev) != AQUANTIA_VENDOR_ID)
                return (ENXIO);

        switch (pci_get_device(softc->dev)) {
        case AQ_DEVICE_ID_D100:
        case AQ_DEVICE_ID_AQC100:
        case AQ_DEVICE_ID_AQC100S:
                softc->media_type = AQ_MEDIA_TYPE_FIBRE;
                softc->link_speeds = AQ_LINK_ALL_ATLANTIC1;
                break;

        case AQ_DEVICE_ID_0001:
        case AQ_DEVICE_ID_D107:
        case AQ_DEVICE_ID_AQC107:
        case AQ_DEVICE_ID_AQC107S:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL_ATLANTIC1;
                break;

        case AQ_DEVICE_ID_D108:
        case AQ_DEVICE_ID_AQC108:
        case AQ_DEVICE_ID_AQC108S:
        case AQ_DEVICE_ID_AQC111:
        case AQ_DEVICE_ID_AQC111S:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL_ATLANTIC1 & ~AQ_LINK_10G;
                break;

        case AQ_DEVICE_ID_D109:
        case AQ_DEVICE_ID_AQC109:
        case AQ_DEVICE_ID_AQC109S:
        case AQ_DEVICE_ID_AQC112:
        case AQ_DEVICE_ID_AQC112S:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL_ATLANTIC1 &
                    ~(AQ_LINK_10G | AQ_LINK_5G);
                break;

        case AQ_DEVICE_ID_AQC113:
        case AQ_DEVICE_ID_AQC113C:
        case AQ_DEVICE_ID_AQC113CA:
        case AQ_DEVICE_ID_AQC113CS:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL;
                break;

        case AQ_DEVICE_ID_AQC114CS:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL & ~AQ_LINK_10G;
                break;

        case AQ_DEVICE_ID_AQC115C:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds = AQ_LINK_ALL & ~(AQ_LINK_10G | AQ_LINK_5G);
                break;

        case AQ_DEVICE_ID_AQC116C:
                softc->media_type = AQ_MEDIA_TYPE_TP;
                softc->link_speeds =
                    AQ_LINK_ALL & ~(AQ_LINK_10G | AQ_LINK_5G | AQ_LINK_2G5);
                break;

        default:
                return (ENXIO);
        }

        return (0);
}

static int
aq_sysctl_print_rss_config(SYSCTL_HANDLER_ARGS)
{
        struct aq_dev  *softc = (struct aq_dev *)arg1;
        device_t        dev = softc->dev;
        struct sbuf     *buf;
        int             error = 0;

        buf = sbuf_new_for_sysctl(NULL, NULL, 256, req);
        if (!buf) {
                device_printf(dev, "Could not allocate sbuf for output.\n");
                return (ENOMEM);
        }

        /* Print out the redirection table */
        sbuf_cat(buf, "\nRSS Indirection table:\n");
        for (int i = 0; i < HW_ATL_RSS_INDIRECTION_TABLE_MAX; i++) {
                sbuf_printf(buf, "%d ", softc->rss_table[i]);
                if ((i+1) % 10 == 0)
                        sbuf_printf(buf, "\n");
        }

        sbuf_cat(buf, "\nRSS Key:\n");
        for (int i = 0; i < HW_ATL_RSS_HASHKEY_SIZE; i++) {
                sbuf_printf(buf, "0x%02x ", softc->rss_key[i]);
        }
        sbuf_printf(buf, "\n");

        error = sbuf_finish(buf);
        if (error)
                device_printf(dev, "Error finishing sbuf: %d\n", error);

        sbuf_delete(buf);

        return (0);
}

static int
aq_sysctl_print_tx_head(SYSCTL_HANDLER_ARGS)
{
        struct aq_ring  *ring = arg1;
        int             error = 0;
        unsigned int   val;

        if (!ring)
                return (0);

        val = tdm_tx_desc_head_ptr_get(&ring->dev->hw, ring->index);

        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error || !req->newptr)
                return (error);

        return (0);
}

static int
aq_sysctl_print_tx_tail(SYSCTL_HANDLER_ARGS)
{
        struct aq_ring  *ring = arg1;
        int             error = 0;
        unsigned int   val;

        if (!ring)
                return (0);

        val = reg_tx_dma_desc_tail_ptr_get(&ring->dev->hw, ring->index);

        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error || !req->newptr)
                return (error);

        return (0);
}

static int
aq_sysctl_print_rx_head(SYSCTL_HANDLER_ARGS)
{
        struct aq_ring  *ring = arg1;
        int             error = 0;
        unsigned int   val;

        if (!ring)
                return (0);

        val = rdm_rx_desc_head_ptr_get(&ring->dev->hw, ring->index);

        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error || !req->newptr)
                return (error);

        return (0);
}

static int
aq_sysctl_print_rx_tail(SYSCTL_HANDLER_ARGS)
{
        struct aq_ring  *ring = arg1;
        int             error = 0;
        unsigned int   val;

        if (!ring)
                return (0);

        val = reg_rx_dma_desc_tail_ptr_get(&ring->dev->hw, ring->index);

        error = sysctl_handle_int(oidp, &val, 0, req);
        if (error || !req->newptr)
                return (error);

        return (0);
}

static void
aq_add_stats_sysctls(struct aq_dev *softc)
{
        device_t                dev = softc->dev;
        struct sysctl_ctx_list  *ctx = device_get_sysctl_ctx(dev);
        struct sysctl_oid       *tree = device_get_sysctl_tree(dev);
        struct sysctl_oid_list  *child = SYSCTL_CHILDREN(tree);
        struct aq_stats *stats = &softc->curr_stats;
        struct sysctl_oid       *stat_node, *queue_node;
        struct sysctl_oid_list  *stat_list, *queue_list;

#define QUEUE_NAME_LEN 32
        char                    namebuf[QUEUE_NAME_LEN];
        /* RSS configuration */
        SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "print_rss_config",
            CTLTYPE_STRING | CTLFLAG_RD, softc, 0,
            aq_sysctl_print_rss_config, "A", "Prints RSS Configuration");

        /* Runtime trace controls (global) */
        SYSCTL_ADD_INT(ctx, child, OID_AUTO, "debug",
            CTLFLAG_RW, &aq_dbg_level, 0,
            "Trace verbosity: 0=off, 3=err, 4=+warn, 5=+trace, 6=+detail");
        SYSCTL_ADD_U32(ctx, child, OID_AUTO, "debug_categories",
            CTLFLAG_RW, &aq_dbg_categories, 0,
            "Trace category mask: init=1 config=2 tx=4 rx=8 intr=16 fw=32");

        /* Driver Statistics */
        for (int i = 0; i < softc->tx_rings_count; i++) {
                struct aq_ring *ring = softc->tx_rings[i];
                snprintf(namebuf, QUEUE_NAME_LEN, "tx_queue%d", i);
                queue_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, namebuf,
                    CTLFLAG_RD, NULL, "Queue Name");
                queue_list = SYSCTL_CHILDREN(queue_node);

                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "tx_pkts",
                    CTLFLAG_RD, &(ring->stats.tx_pkts), "TX Packets");
                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "tx_bytes",
                    CTLFLAG_RD, &(ring->stats.tx_bytes), "TX Octets");
                SYSCTL_ADD_PROC(ctx, queue_list, OID_AUTO, "tx_head",
                    CTLTYPE_UINT | CTLFLAG_RD, ring, 0,
                    aq_sysctl_print_tx_head, "IU", "ring head pointer");
                SYSCTL_ADD_PROC(ctx, queue_list, OID_AUTO, "tx_tail",
                    CTLTYPE_UINT | CTLFLAG_RD, ring, 0,
                aq_sysctl_print_tx_tail, "IU", "ring tail pointer");
        }

        for (int i = 0; i < softc->rx_rings_count; i++) {
                struct aq_ring *ring = softc->rx_rings[i];
                snprintf(namebuf, QUEUE_NAME_LEN, "rx_queue%d", i);
                queue_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, namebuf,
                    CTLFLAG_RD, NULL, "Queue Name");
                queue_list = SYSCTL_CHILDREN(queue_node);

                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "rx_pkts",
                    CTLFLAG_RD, &(ring->stats.rx_pkts), "RX Packets");
                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "rx_bytes",
                    CTLFLAG_RD, &(ring->stats.rx_bytes), "RX Octets");
                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "rx_err",
                    CTLFLAG_RD, &(ring->stats.rx_err), "RX Errors");
                SYSCTL_ADD_COUNTER_U64(ctx, queue_list, OID_AUTO, "irq",
                    CTLFLAG_RD, &(ring->stats.irq), "RX interrupts");
                SYSCTL_ADD_PROC(ctx, queue_list, OID_AUTO, "rx_head",
                    CTLTYPE_UINT | CTLFLAG_RD, ring, 0,
                aq_sysctl_print_rx_head, "IU", "ring head pointer");
                SYSCTL_ADD_PROC(ctx, queue_list, OID_AUTO, "rx_tail",
                    CTLTYPE_UINT | CTLFLAG_RD, ring, 0,
                aq_sysctl_print_rx_tail, "IU", " ring tail pointer");
        }

        stat_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "mac",
            CTLFLAG_RD, NULL, "Statistics (read from HW registers)");
        stat_list = SYSCTL_CHILDREN(stat_node);

        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "good_pkts_rcvd",
            CTLFLAG_RD, &stats->prc, "Good Packets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "ucast_pkts_rcvd",
            CTLFLAG_RD, &stats->uprc, "Unicast Packets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "mcast_pkts_rcvd",
            CTLFLAG_RD, &stats->mprc, "Multicast Packets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "bcast_pkts_rcvd",
            CTLFLAG_RD, &stats->bprc, "Broadcast Packets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "rsc_pkts_rcvd",
            CTLFLAG_RD, &stats->cprc, "Coalesced Packets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "err_pkts_rcvd",
            CTLFLAG_RD, &stats->erpr, "Errors of Packet Receive");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "drop_pkts_dma",
            CTLFLAG_RD, &stats->dpc, "Dropped Packets in DMA");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "good_octets_rcvd",
            CTLFLAG_RD, &stats->brc, "Good Octets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "ucast_octets_rcvd",
            CTLFLAG_RD, &stats->ubrc, "Unicast Octets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "mcast_octets_rcvd",
            CTLFLAG_RD, &stats->mbrc, "Multicast Octets Received");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "bcast_octets_rcvd",
            CTLFLAG_RD, &stats->bbrc, "Broadcast Octets Received");

        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "good_pkts_txd",
            CTLFLAG_RD, &stats->ptc, "Good Packets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "ucast_pkts_txd",
            CTLFLAG_RD, &stats->uptc, "Unicast Packets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "mcast_pkts_txd",
            CTLFLAG_RD, &stats->mptc, "Multicast Packets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "bcast_pkts_txd",
            CTLFLAG_RD, &stats->bptc, "Broadcast Packets Transmitted");

        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "err_pkts_txd",
            CTLFLAG_RD, &stats->erpt, "Errors of Packet Transmit");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "good_octets_txd",
            CTLFLAG_RD, &stats->btc, "Good Octets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "ucast_octets_txd",
            CTLFLAG_RD, &stats->ubtc, "Unicast Octets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "mcast_octets_txd",
            CTLFLAG_RD, &stats->mbtc, "Multicast Octets Transmitted");
        SYSCTL_ADD_UQUAD(ctx, stat_list, OID_AUTO, "bcast_octets_txd",
            CTLFLAG_RD, &stats->bbtc, "Broadcast Octets Transmitted");
}