root/sys/dev/pci/if_vte.c
/*      $NetBSD: if_vte.c,v 1.39 2025/02/24 07:11:23 andvar Exp $       */

/*
 * Copyright (c) 2011 Manuel Bouyer.  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.
 *
 * 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.
 */

/*-
 * Copyright (c) 2010, Pyun YongHyeon <yongari@FreeBSD.org>
 * 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 unmodified, 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.
 */
/* FreeBSD: src/sys/dev/vte/if_vte.c,v 1.2 2010/12/31 01:23:04 yongari Exp */

/* Driver for DM&P Electronics, Inc, Vortex86 RDC R6040 FastEthernet. */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_vte.c,v 1.39 2025/02/24 07:11:23 andvar Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <net/bpf.h>

#include <sys/rndsource.h>

#include "opt_inet.h"
#include <net/if_ether.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_inarp.h>
#endif

#include <sys/bus.h>
#include <sys/intr.h>

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

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>

#include <dev/pci/if_vtereg.h>
#include <dev/pci/if_vtevar.h>

static int      vte_match(device_t, cfdata_t, void *);
static void     vte_attach(device_t, device_t, void *);
static int      vte_detach(device_t, int);
static int      vte_dma_alloc(struct vte_softc *);
static void     vte_dma_free(struct vte_softc *);
static struct vte_txdesc *
                vte_encap(struct vte_softc *, struct mbuf **);
static void     vte_get_macaddr(struct vte_softc *);
static int      vte_init(struct ifnet *);
static int      vte_init_rx_ring(struct vte_softc *);
static int      vte_init_tx_ring(struct vte_softc *);
static int      vte_intr(void *);
static int      vte_ifioctl(struct ifnet *, u_long, void *);
static void     vte_mac_config(struct vte_softc *);
static int      vte_miibus_readreg(device_t, int, int, uint16_t *);
static void     vte_miibus_statchg(struct ifnet *);
static int      vte_miibus_writereg(device_t, int, int, uint16_t);
static int      vte_mediachange(struct ifnet *);
static int      vte_newbuf(struct vte_softc *, struct vte_rxdesc *);
static void     vte_reset(struct vte_softc *);
static void     vte_rxeof(struct vte_softc *);
static void     vte_rxfilter(struct vte_softc *);
static bool     vte_shutdown(device_t, int);
static bool     vte_suspend(device_t, const pmf_qual_t *);
static bool     vte_resume(device_t, const pmf_qual_t *);
static void     vte_ifstart(struct ifnet *);
static void     vte_start_mac(struct vte_softc *);
static void     vte_stats_clear(struct vte_softc *);
static void     vte_stats_update(struct vte_softc *);
static void     vte_stop(struct ifnet *, int);
static void     vte_stop_mac(struct vte_softc *);
static void     vte_tick(void *);
static void     vte_txeof(struct vte_softc *);
static void     vte_ifwatchdog(struct ifnet *);

static int vte_sysctl_intrxct(SYSCTLFN_PROTO);
static int vte_sysctl_inttxct(SYSCTLFN_PROTO);
static int vte_root_num;

#define DPRINTF(a)

CFATTACH_DECL3_NEW(vte, sizeof(struct vte_softc),
    vte_match, vte_attach, vte_detach, NULL, NULL, NULL, DVF_DETACH_SHUTDOWN);


static int
vte_match(device_t parent, cfdata_t cf, void *aux)
{
        struct pci_attach_args *pa = (struct pci_attach_args *)aux;

        if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_RDC &&
            PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_RDC_R6040)
                return 1;

        return 0;
}

static void
vte_attach(device_t parent, device_t self, void *aux)
{
        struct vte_softc *sc = device_private(self);
        struct pci_attach_args * const pa = (struct pci_attach_args *)aux;
        struct ifnet * const ifp = &sc->vte_if;
        struct mii_data * const mii = &sc->vte_mii;
        int h_valid;
        pcireg_t reg, csr;
        pci_intr_handle_t intrhandle;
        const char *intrstr;
        int error;
        const struct sysctlnode *node;
        int vte_nodenum;
        char intrbuf[PCI_INTRSTR_LEN];

        sc->vte_dev = self;

        callout_init(&sc->vte_tick_ch, 0);
        callout_setfunc(&sc->vte_tick_ch, vte_tick, sc);

        /* Map the device. */
        h_valid = 0;
        reg = pci_conf_read(pa->pa_pc, pa->pa_tag, VTE_PCI_BMEM);
        if (PCI_MAPREG_TYPE(reg) == PCI_MAPREG_TYPE_MEM) {
                h_valid = (pci_mapreg_map(pa, VTE_PCI_BMEM,
                    PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_32BIT,
                    0, &sc->vte_bustag, &sc->vte_bushandle, NULL, NULL) == 0);
        }
        if (h_valid == 0) {
                reg = pci_conf_read(pa->pa_pc, pa->pa_tag, VTE_PCI_BIO);
                if (PCI_MAPREG_TYPE(reg) == PCI_MAPREG_TYPE_IO) {
                        h_valid = (pci_mapreg_map(pa, VTE_PCI_BIO,
                            PCI_MAPREG_TYPE_IO, 0, &sc->vte_bustag,
                            &sc->vte_bushandle, NULL, NULL) == 0);
                }
        }
        if (h_valid == 0) {
                aprint_error_dev(self, "unable to map device registers\n");
                return;
        }
        sc->vte_dmatag = pa->pa_dmat;
        /* Enable the device. */
        csr = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
        pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG,
            csr | PCI_COMMAND_MASTER_ENABLE);

        pci_aprint_devinfo(pa, NULL);

        /* Reset the ethernet controller. */
        vte_reset(sc);

        if ((error = vte_dma_alloc(sc)) != 0)
                return;

        /* Load station address. */
        vte_get_macaddr(sc);

        aprint_normal_dev(self, "Ethernet address %s\n",
            ether_sprintf(sc->vte_eaddr));

        /* Map and establish interrupts */
        if (pci_intr_map(pa, &intrhandle)) {
                aprint_error_dev(self, "couldn't map interrupt\n");
                return;
        }
        intrstr = pci_intr_string(pa->pa_pc, intrhandle, intrbuf,
            sizeof(intrbuf));
        sc->vte_ih = pci_intr_establish_xname(pa->pa_pc, intrhandle, IPL_NET,
            vte_intr, sc, device_xname(self));
        if (sc->vte_ih == NULL) {
                aprint_error_dev(self, "couldn't establish interrupt");
                if (intrstr != NULL)
                        aprint_error(" at %s", intrstr);
                aprint_error("\n");
                return;
        }
        aprint_normal_dev(self, "interrupting at %s\n", intrstr);

        sc->vte_if.if_softc = sc;
        mii->mii_ifp = ifp;
        mii->mii_readreg = vte_miibus_readreg;
        mii->mii_writereg = vte_miibus_writereg;
        mii->mii_statchg = vte_miibus_statchg;
        sc->vte_ec.ec_mii = mii;
        ifmedia_init(&mii->mii_media, IFM_IMASK, vte_mediachange,
            ether_mediastatus);
        mii_attach(self, mii, 0xffffffff, MII_PHY_ANY,
            MII_OFFSET_ANY, 0);
        if (LIST_FIRST(&mii->mii_phys) == NULL) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL);
                ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE);
        } else
                ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);

        /*
         * We can support 802.1Q VLAN-sized frames.
         */
        sc->vte_ec.ec_capabilities |= ETHERCAP_VLAN_MTU;

        strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ);
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_ioctl = vte_ifioctl;
        ifp->if_start = vte_ifstart;
        ifp->if_watchdog = vte_ifwatchdog;
        ifp->if_init = vte_init;
        ifp->if_stop = vte_stop;
        ifp->if_timer = 0;
        IFQ_SET_READY(&ifp->if_snd);
        if_attach(ifp);
        if_deferred_start_init(ifp, NULL);
        ether_ifattach(&(sc)->vte_if, (sc)->vte_eaddr);

        if (pmf_device_register1(self, vte_suspend, vte_resume, vte_shutdown))
                pmf_class_network_register(self, ifp);
        else
                aprint_error_dev(self, "couldn't establish power handler\n");

        rnd_attach_source(&sc->rnd_source, device_xname(self),
            RND_TYPE_NET, RND_FLAG_DEFAULT);

        if (sysctl_createv(&sc->vte_clog, 0, NULL, &node,
            0, CTLTYPE_NODE, device_xname(sc->vte_dev),
            SYSCTL_DESCR("vte per-controller controls"),
            NULL, 0, NULL, 0, CTL_HW, vte_root_num, CTL_CREATE,
            CTL_EOL) != 0) {
                aprint_normal_dev(sc->vte_dev, "couldn't create sysctl node\n");
                return;
        }
        vte_nodenum = node->sysctl_num;
        if (sysctl_createv(&sc->vte_clog, 0, NULL, &node,
            CTLFLAG_READWRITE,
            CTLTYPE_INT, "int_rxct",
            SYSCTL_DESCR("vte RX interrupt moderation packet counter"),
            vte_sysctl_intrxct, 0, (void *)sc,
            0, CTL_HW, vte_root_num, vte_nodenum, CTL_CREATE,
            CTL_EOL) != 0) {
                aprint_normal_dev(sc->vte_dev,
                    "couldn't create int_rxct sysctl node\n");
        }
        if (sysctl_createv(&sc->vte_clog, 0, NULL, &node,
            CTLFLAG_READWRITE,
            CTLTYPE_INT, "int_txct",
            SYSCTL_DESCR("vte TX interrupt moderation packet counter"),
            vte_sysctl_inttxct, 0, (void *)sc,
            0, CTL_HW, vte_root_num, vte_nodenum, CTL_CREATE,
            CTL_EOL) != 0) {
                aprint_normal_dev(sc->vte_dev,
                    "couldn't create int_txct sysctl node\n");
        }
}

static int
vte_detach(device_t dev, int flags __unused)
{
        struct vte_softc *sc = device_private(dev);
        struct ifnet *ifp = &sc->vte_if;
        int s;

        s = splnet();
        /* Stop the interface. Callouts are stopped in it. */
        vte_stop(ifp, 1);
        splx(s);

        pmf_device_deregister(dev);

        mii_detach(&sc->vte_mii, MII_PHY_ANY, MII_OFFSET_ANY);

        ether_ifdetach(ifp);
        if_detach(ifp);
        ifmedia_fini(&sc->vte_mii.mii_media);

        vte_dma_free(sc);

        return (0);
}

static int
vte_miibus_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
        struct vte_softc *sc = device_private(dev);
        int i;

        CSR_WRITE_2(sc, VTE_MMDIO, MMDIO_READ |
            (phy << MMDIO_PHY_ADDR_SHIFT) | (reg << MMDIO_REG_ADDR_SHIFT));
        for (i = VTE_PHY_TIMEOUT; i > 0; i--) {
                DELAY(5);
                if ((CSR_READ_2(sc, VTE_MMDIO) & MMDIO_READ) == 0)
                        break;
        }

        if (i == 0) {
                aprint_error_dev(sc->vte_dev, "phy read timeout : %d\n", reg);
                return ETIMEDOUT;
        }

        *val = CSR_READ_2(sc, VTE_MMRD);
        return 0;
}

static int
vte_miibus_writereg(device_t dev, int phy, int reg, uint16_t val)
{
        struct vte_softc *sc = device_private(dev);
        int i;

        CSR_WRITE_2(sc, VTE_MMWD, val);
        CSR_WRITE_2(sc, VTE_MMDIO, MMDIO_WRITE |
            (phy << MMDIO_PHY_ADDR_SHIFT) | (reg << MMDIO_REG_ADDR_SHIFT));
        for (i = VTE_PHY_TIMEOUT; i > 0; i--) {
                DELAY(5);
                if ((CSR_READ_2(sc, VTE_MMDIO) & MMDIO_WRITE) == 0)
                        break;
        }

        if (i == 0) {
                aprint_error_dev(sc->vte_dev, "phy write timeout : %d\n", reg);
                return ETIMEDOUT;
        }

        return 0;
}

static void
vte_miibus_statchg(struct ifnet *ifp)
{
        struct vte_softc *sc = ifp->if_softc;
        uint16_t val;

        DPRINTF(("vte_miibus_statchg 0x%x 0x%x\n",
            sc->vte_mii.mii_media_status, sc->vte_mii.mii_media_active));

        sc->vte_flags &= ~VTE_FLAG_LINK;
        if ((sc->vte_mii.mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
            (IFM_ACTIVE | IFM_AVALID)) {
                switch (IFM_SUBTYPE(sc->vte_mii.mii_media_active)) {
                case IFM_10_T:
                case IFM_100_TX:
                        sc->vte_flags |= VTE_FLAG_LINK;
                        break;
                default:
                        break;
                }
        }

        /* Stop RX/TX MACs. */
        vte_stop_mac(sc);
        /* Program MACs with resolved duplex and flow control. */
        if ((sc->vte_flags & VTE_FLAG_LINK) != 0) {
                /*
                 * Timer waiting time : (63 + TIMER * 64) MII clock.
                 * MII clock : 25MHz(100Mbps) or 2.5MHz(10Mbps).
                 */
                if (IFM_SUBTYPE(sc->vte_mii.mii_media_active) == IFM_100_TX)
                        val = 18 << VTE_IM_TIMER_SHIFT;
                else
                        val = 1 << VTE_IM_TIMER_SHIFT;
                val |= sc->vte_int_rx_mod << VTE_IM_BUNDLE_SHIFT;
                /* 48.6us for 100Mbps, 50.8us for 10Mbps */
                CSR_WRITE_2(sc, VTE_MRICR, val);

                if (IFM_SUBTYPE(sc->vte_mii.mii_media_active) == IFM_100_TX)
                        val = 18 << VTE_IM_TIMER_SHIFT;
                else
                        val = 1 << VTE_IM_TIMER_SHIFT;
                val |= sc->vte_int_tx_mod << VTE_IM_BUNDLE_SHIFT;
                /* 48.6us for 100Mbps, 50.8us for 10Mbps */
                CSR_WRITE_2(sc, VTE_MTICR, val);

                vte_mac_config(sc);
                vte_start_mac(sc);
                DPRINTF(("vte_miibus_statchg: link\n"));
        }
}

static void
vte_get_macaddr(struct vte_softc *sc)
{
        uint16_t mid;

        /*
         * It seems there is no way to reload station address and
         * it is supposed to be set by BIOS.
         */
        mid = CSR_READ_2(sc, VTE_MID0L);
        sc->vte_eaddr[0] = (mid >> 0) & 0xFF;
        sc->vte_eaddr[1] = (mid >> 8) & 0xFF;
        mid = CSR_READ_2(sc, VTE_MID0M);
        sc->vte_eaddr[2] = (mid >> 0) & 0xFF;
        sc->vte_eaddr[3] = (mid >> 8) & 0xFF;
        mid = CSR_READ_2(sc, VTE_MID0H);
        sc->vte_eaddr[4] = (mid >> 0) & 0xFF;
        sc->vte_eaddr[5] = (mid >> 8) & 0xFF;
}


static int
vte_dma_alloc(struct vte_softc *sc)
{
        struct vte_txdesc *txd;
        struct vte_rxdesc *rxd;
        int error, i, rseg;

        /* create DMA map for TX ring */
        error = bus_dmamap_create(sc->vte_dmatag, VTE_TX_RING_SZ, 1,
            VTE_TX_RING_SZ, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
            &sc->vte_cdata.vte_tx_ring_map);
        if (error) {
                aprint_error_dev(sc->vte_dev,
                    "could not create dma map for TX ring (%d)\n",
                    error);
                goto fail;
        }
        /* Allocate and map DMA'able memory and load the DMA map for TX ring. */
        error = bus_dmamem_alloc(sc->vte_dmatag, VTE_TX_RING_SZ,
            VTE_TX_RING_ALIGN, 0,
            sc->vte_cdata.vte_tx_ring_seg, 1, &rseg,
            BUS_DMA_NOWAIT);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not allocate DMA'able memory for TX ring (%d).\n",
                    error);
                goto fail;
        }
        KASSERT(rseg == 1);
        error = bus_dmamem_map(sc->vte_dmatag,
            sc->vte_cdata.vte_tx_ring_seg, 1,
            VTE_TX_RING_SZ, (void **)(&sc->vte_cdata.vte_tx_ring),
            BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not map DMA'able memory for TX ring (%d).\n",
                    error);
                goto fail;
        }
        memset(sc->vte_cdata.vte_tx_ring, 0, VTE_TX_RING_SZ);
        error = bus_dmamap_load(sc->vte_dmatag,
            sc->vte_cdata.vte_tx_ring_map, sc->vte_cdata.vte_tx_ring,
            VTE_TX_RING_SZ, NULL,
            BUS_DMA_NOWAIT | BUS_DMA_READ | BUS_DMA_WRITE);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not load DMA'able memory for TX ring.\n");
                goto fail;
        }

        /* create DMA map for RX ring */
        error = bus_dmamap_create(sc->vte_dmatag, VTE_RX_RING_SZ, 1,
            VTE_RX_RING_SZ, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
            &sc->vte_cdata.vte_rx_ring_map);
        if (error) {
                aprint_error_dev(sc->vte_dev,
                    "could not create dma map for RX ring (%d)\n",
                    error);
                goto fail;
        }
        /* Allocate and map DMA'able memory and load the DMA map for RX ring. */
        error = bus_dmamem_alloc(sc->vte_dmatag, VTE_RX_RING_SZ,
            VTE_RX_RING_ALIGN, 0,
            sc->vte_cdata.vte_rx_ring_seg, 1, &rseg,
            BUS_DMA_NOWAIT);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not allocate DMA'able memory for RX ring (%d).\n",
                    error);
                goto fail;
        }
        KASSERT(rseg == 1);
        error = bus_dmamem_map(sc->vte_dmatag,
            sc->vte_cdata.vte_rx_ring_seg, 1,
            VTE_RX_RING_SZ, (void **)(&sc->vte_cdata.vte_rx_ring),
            BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not map DMA'able memory for RX ring (%d).\n",
                    error);
                goto fail;
        }
        memset(sc->vte_cdata.vte_rx_ring, 0, VTE_RX_RING_SZ);
        error = bus_dmamap_load(sc->vte_dmatag,
            sc->vte_cdata.vte_rx_ring_map, sc->vte_cdata.vte_rx_ring,
            VTE_RX_RING_SZ, NULL,
            BUS_DMA_NOWAIT | BUS_DMA_READ | BUS_DMA_WRITE);
        if (error != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not load DMA'able memory for RX ring (%d).\n",
                    error);
                goto fail;
        }

        /* Create DMA maps for TX buffers. */
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                txd = &sc->vte_cdata.vte_txdesc[i];
                txd->tx_m = NULL;
                txd->tx_dmamap = NULL;
                error = bus_dmamap_create(sc->vte_dmatag, MCLBYTES,
                    1, MCLBYTES, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
                    &txd->tx_dmamap);
                if (error != 0) {
                        aprint_error_dev(sc->vte_dev,
                            "could not create TX DMA map %d (%d).\n", i, error);
                        goto fail;
                }
        }
        /* Create DMA maps for RX buffers. */
        if ((error = bus_dmamap_create(sc->vte_dmatag, MCLBYTES,
            1, MCLBYTES, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
            &sc->vte_cdata.vte_rx_sparemap)) != 0) {
                aprint_error_dev(sc->vte_dev,
                    "could not create spare RX dmamap (%d).\n", error);
                goto fail;
        }
        for (i = 0; i < VTE_RX_RING_CNT; i++) {
                rxd = &sc->vte_cdata.vte_rxdesc[i];
                rxd->rx_m = NULL;
                rxd->rx_dmamap = NULL;
                error = bus_dmamap_create(sc->vte_dmatag, MCLBYTES,
                    1, MCLBYTES, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
                    &rxd->rx_dmamap);
                if (error != 0) {
                        aprint_error_dev(sc->vte_dev,
                            "could not create RX dmamap %d (%d).\n", i, error);
                        goto fail;
                }
        }
        return 0;

fail:
        vte_dma_free(sc);
        return (error);
}

static void
vte_dma_free(struct vte_softc *sc)
{
        struct vte_txdesc *txd;
        struct vte_rxdesc *rxd;
        int i;

        /* TX buffers. */
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                txd = &sc->vte_cdata.vte_txdesc[i];
                if (txd->tx_dmamap != NULL) {
                        bus_dmamap_destroy(sc->vte_dmatag, txd->tx_dmamap);
                        txd->tx_dmamap = NULL;
                }
        }
        /* RX buffers */
        for (i = 0; i < VTE_RX_RING_CNT; i++) {
                rxd = &sc->vte_cdata.vte_rxdesc[i];
                if (rxd->rx_dmamap != NULL) {
                        bus_dmamap_destroy(sc->vte_dmatag, rxd->rx_dmamap);
                        rxd->rx_dmamap = NULL;
                }
        }
        if (sc->vte_cdata.vte_rx_sparemap != NULL) {
                bus_dmamap_destroy(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_sparemap);
                sc->vte_cdata.vte_rx_sparemap = NULL;
        }
        /* TX descriptor ring. */
        if (sc->vte_cdata.vte_tx_ring_map != NULL) {
                bus_dmamap_unload(sc->vte_dmatag,
                    sc->vte_cdata.vte_tx_ring_map);
                bus_dmamap_destroy(sc->vte_dmatag,
                    sc->vte_cdata.vte_tx_ring_map);
        }
        if (sc->vte_cdata.vte_tx_ring != NULL) {
                bus_dmamem_unmap(sc->vte_dmatag,
                    sc->vte_cdata.vte_tx_ring, VTE_TX_RING_SZ);
                bus_dmamem_free(sc->vte_dmatag,
                    sc->vte_cdata.vte_tx_ring_seg, 1);
        }
        sc->vte_cdata.vte_tx_ring = NULL;
        sc->vte_cdata.vte_tx_ring_map = NULL;
        /* RX ring. */
        if (sc->vte_cdata.vte_rx_ring_map != NULL) {
                bus_dmamap_unload(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_ring_map);
                bus_dmamap_destroy(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_ring_map);
        }
        if (sc->vte_cdata.vte_rx_ring != NULL) {
                bus_dmamem_unmap(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_ring, VTE_RX_RING_SZ);
                bus_dmamem_free(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_ring_seg, 1);
        }
        sc->vte_cdata.vte_rx_ring = NULL;
        sc->vte_cdata.vte_rx_ring_map = NULL;
}

static bool
vte_shutdown(device_t dev, int howto)
{

        return (vte_suspend(dev, NULL));
}

static bool
vte_suspend(device_t dev, const pmf_qual_t *qual)
{
        struct vte_softc *sc = device_private(dev);
        struct ifnet *ifp = &sc->vte_if;

        DPRINTF(("vte_suspend if_flags 0x%x\n", ifp->if_flags));
        if ((ifp->if_flags & IFF_RUNNING) != 0)
                vte_stop(ifp, 1);
        return (0);
}

static bool
vte_resume(device_t dev, const pmf_qual_t *qual)
{
        struct vte_softc *sc = device_private(dev);
        struct ifnet *ifp;

        ifp = &sc->vte_if;
        if ((ifp->if_flags & IFF_UP) != 0) {
                ifp->if_flags &= ~IFF_RUNNING;
                vte_init(ifp);
        }

        return (0);
}

static struct vte_txdesc *
vte_encap(struct vte_softc *sc, struct mbuf **m_head)
{
        struct vte_txdesc *txd;
        struct mbuf *m, *n;
        int copy, error, padlen;

        txd = &sc->vte_cdata.vte_txdesc[sc->vte_cdata.vte_tx_prod];
        m = *m_head;
        /*
         * Controller doesn't auto-pad, so we have to make sure pad
         * short frames out to the minimum frame length.
         */
        if (m->m_pkthdr.len < VTE_MIN_FRAMELEN)
                padlen = VTE_MIN_FRAMELEN - m->m_pkthdr.len;
        else
                padlen = 0;

        /*
         * Controller does not support multi-fragmented TX buffers.
         * Controller spends most of its TX processing time in
         * de-fragmenting TX buffers.  Either faster CPU or more
         * advanced controller DMA engine is required to speed up
         * TX path processing.
         * To mitigate the de-fragmenting issue, perform deep copy
         * from fragmented mbuf chains to a pre-allocated mbuf
         * cluster with extra cost of kernel memory.  For frames
         * that is composed of single TX buffer, the deep copy is
         * bypassed.
         */
        copy = 0;
        if (m->m_next != NULL)
                copy++;
        if (padlen > 0 && (M_READONLY(m) ||
            padlen > M_TRAILINGSPACE(m)))
                copy++;
        if (copy != 0) {
                n = sc->vte_cdata.vte_txmbufs[sc->vte_cdata.vte_tx_prod];
                m_copydata(m, 0, m->m_pkthdr.len, mtod(n, char *));
                n->m_pkthdr.len = m->m_pkthdr.len;
                n->m_len = m->m_pkthdr.len;
                m = n;
                txd->tx_flags |= VTE_TXMBUF;
        }

        if (padlen > 0) {
                /* Zero out the bytes in the pad area. */
                bzero(mtod(m, char *) + m->m_pkthdr.len, padlen);
                m->m_pkthdr.len += padlen;
                m->m_len = m->m_pkthdr.len;
        }

        error = bus_dmamap_load_mbuf(sc->vte_dmatag, txd->tx_dmamap, m,
            BUS_DMA_NOWAIT);
        if (error != 0) {
                txd->tx_flags &= ~VTE_TXMBUF;
                return (NULL);
        }
        KASSERT(txd->tx_dmamap->dm_nsegs == 1);
        bus_dmamap_sync(sc->vte_dmatag, txd->tx_dmamap, 0,
            txd->tx_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE);

        txd->tx_desc->dtlen =
            htole16(VTE_TX_LEN(txd->tx_dmamap->dm_segs[0].ds_len));
        txd->tx_desc->dtbp = htole32(txd->tx_dmamap->dm_segs[0].ds_addr);
        sc->vte_cdata.vte_tx_cnt++;
        /* Update producer index. */
        VTE_DESC_INC(sc->vte_cdata.vte_tx_prod, VTE_TX_RING_CNT);

        /* Finally hand over ownership to controller. */
        txd->tx_desc->dtst = htole16(VTE_DTST_TX_OWN);
        txd->tx_m = m;

        return (txd);
}

static void
vte_ifstart(struct ifnet *ifp)
{
        struct vte_softc *sc = ifp->if_softc;
        struct vte_txdesc *txd;
        struct mbuf *m_head, *m;
        int enq;

        ifp = &sc->vte_if;

        DPRINTF(("vte_ifstart 0x%x 0x%x\n", ifp->if_flags, sc->vte_flags));

        if ((ifp->if_flags & IFF_RUNNING) == 0) {
                return;
        }
        if ((sc->vte_flags & VTE_FLAG_LINK) == 0) {
                return;
        }

        /* Reserve one free TX descriptor. */
        for (enq = 0; sc->vte_cdata.vte_tx_cnt < VTE_TX_RING_CNT - 1; ) {
                IFQ_POLL(&ifp->if_snd, m_head);
                if (m_head == NULL)
                        break;
                /*
                 * Pack the data into the transmit ring.
                 */
                DPRINTF(("vte_encap:"));
                if ((txd = vte_encap(sc, &m_head)) == NULL) {
                        DPRINTF((" failed\n"));
                        break;
                }
                DPRINTF((" ok\n"));
                IFQ_DEQUEUE(&ifp->if_snd, m);
                KASSERT(m == m_head);

                enq++;
                /*
                 * If there's a BPF listener, bounce a copy of this frame
                 * to him.
                 */
                bpf_mtap(ifp, m_head, BPF_D_OUT);
                /* Free consumed TX frame. */
                if ((txd->tx_flags & VTE_TXMBUF) != 0)
                        m_freem(m_head);
        }

        if (enq > 0) {
                bus_dmamap_sync(sc->vte_dmatag,
                    sc->vte_cdata.vte_tx_ring_map, 0,
                    sc->vte_cdata.vte_tx_ring_map->dm_mapsize,
                    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
                CSR_WRITE_2(sc, VTE_TX_POLL, TX_POLL_START);
                sc->vte_watchdog_timer = VTE_TX_TIMEOUT;
        }
}

static void
vte_ifwatchdog(struct ifnet *ifp)
{
        struct vte_softc *sc = ifp->if_softc;

        if (sc->vte_watchdog_timer == 0 || --sc->vte_watchdog_timer)
                return;

        aprint_error_dev(sc->vte_dev, "watchdog timeout -- resetting\n");
        if_statinc(ifp, if_oerrors);
        vte_init(ifp);
        if (!IFQ_IS_EMPTY(&ifp->if_snd))
                vte_ifstart(ifp);
}

static int
vte_mediachange(struct ifnet *ifp)
{
        int error;
        struct vte_softc *sc = ifp->if_softc;

        if ((error = mii_mediachg(&sc->vte_mii)) == ENXIO)
                error = 0;
        else if (error != 0) {
                aprint_error_dev(sc->vte_dev, "could not set media\n");
                return error;
        }
        return 0;

}

static int
vte_ifioctl(struct ifnet *ifp, u_long cmd, void *data)
{
        struct vte_softc *sc = ifp->if_softc;
        int error, s;

        s = splnet();
        error = ether_ioctl(ifp, cmd, data);
        if (error == ENETRESET) {
                DPRINTF(("vte_ifioctl if_flags 0x%x\n", ifp->if_flags));
                if (ifp->if_flags & IFF_RUNNING)
                        vte_rxfilter(sc);
                error = 0;
        }
        splx(s);
        return error;
}

static void
vte_mac_config(struct vte_softc *sc)
{
        uint16_t mcr;

        mcr = CSR_READ_2(sc, VTE_MCR0);
        mcr &= ~(MCR0_FC_ENB | MCR0_FULL_DUPLEX);
        if ((IFM_OPTIONS(sc->vte_mii.mii_media_active) & IFM_FDX) != 0) {
                mcr |= MCR0_FULL_DUPLEX;
#ifdef notyet
                if ((IFM_OPTIONS(sc->vte_mii.mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0)
                        mcr |= MCR0_FC_ENB;
                /*
                 * The data sheet is not clear whether the controller
                 * honors received pause frames or not.  The is no
                 * separate control bit for RX pause frame so just
                 * enable MCR0_FC_ENB bit.
                 */
                if ((IFM_OPTIONS(sc->vte_mii.mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0)
                        mcr |= MCR0_FC_ENB;
#endif
        }
        CSR_WRITE_2(sc, VTE_MCR0, mcr);
}

static void
vte_stats_clear(struct vte_softc *sc)
{

        /* Reading counter registers clears its contents. */
        CSR_READ_2(sc, VTE_CNT_RX_DONE);
        CSR_READ_2(sc, VTE_CNT_MECNT0);
        CSR_READ_2(sc, VTE_CNT_MECNT1);
        CSR_READ_2(sc, VTE_CNT_MECNT2);
        CSR_READ_2(sc, VTE_CNT_MECNT3);
        CSR_READ_2(sc, VTE_CNT_TX_DONE);
        CSR_READ_2(sc, VTE_CNT_MECNT4);
        CSR_READ_2(sc, VTE_CNT_PAUSE);
}

static void
vte_stats_update(struct vte_softc *sc)
{
        struct vte_hw_stats *stat;
        struct ifnet *ifp = &sc->vte_if;
        uint16_t value;

        stat = &sc->vte_stats;

        net_stat_ref_t nsr = IF_STAT_GETREF(ifp);

        CSR_READ_2(sc, VTE_MECISR);

        /* RX stats. */
        stat->rx_frames += CSR_READ_2(sc, VTE_CNT_RX_DONE);

        value = CSR_READ_2(sc, VTE_CNT_MECNT0);
        stat->rx_bcast_frames += (value >> 8);
        stat->rx_mcast_frames += (value & 0xFF);

        value = CSR_READ_2(sc, VTE_CNT_MECNT1);
        if_statadd_ref(ifp, nsr, if_ierrors,
            (value >> 8) +                      /* rx_runts */
            (value & 0xFF));                    /* rx_crcerrs */

        value = CSR_READ_2(sc, VTE_CNT_MECNT2);
        if_statadd_ref(ifp, nsr, if_ierrors,
            (value & 0xFF));                    /* rx_long_frames */

        value = CSR_READ_2(sc, VTE_CNT_MECNT3);
        if_statadd_ref(ifp, nsr, if_ierrors,
            (value >> 8));                      /* rx_fifo_full */
        stat->rx_desc_unavail += (value & 0xFF);

        /* TX stats. */
        if_statadd_ref(ifp, nsr, if_opackets,
            CSR_READ_2(sc, VTE_CNT_TX_DONE));   /* tx_frames */

        value = CSR_READ_2(sc, VTE_CNT_MECNT4);
        if_statadd_ref(ifp, nsr, if_oerrors,
            (value >> 8) +                      /* tx_underruns */
            (value & 0xFF));                    /* tx_late_colls */

        /* Pause stats. */
        value = CSR_READ_2(sc, VTE_CNT_PAUSE);
        stat->tx_pause_frames += (value >> 8);
        stat->rx_pause_frames += (value & 0xFF);

        IF_STAT_PUTREF(ifp);
}

static int
vte_intr(void *arg)
{
        struct vte_softc *sc = (struct vte_softc *)arg;
        struct ifnet *ifp = &sc->vte_if;
        uint16_t status;
        int n;

        /* Reading VTE_MISR acknowledges interrupts. */
        status = CSR_READ_2(sc, VTE_MISR);
        DPRINTF(("vte_intr status 0x%x\n", status));
        if ((status & VTE_INTRS) == 0) {
                /* Not ours. */
                return 0;
        }

        /* Disable interrupts. */
        CSR_WRITE_2(sc, VTE_MIER, 0);
        for (n = 8; (status & VTE_INTRS) != 0;) {
                if ((ifp->if_flags & IFF_RUNNING) == 0)
                        break;
                if ((status & (MISR_RX_DONE | MISR_RX_DESC_UNAVAIL |
                    MISR_RX_FIFO_FULL)) != 0)
                        vte_rxeof(sc);
                if ((status & MISR_TX_DONE) != 0)
                        vte_txeof(sc);
                if ((status & MISR_EVENT_CNT_OFLOW) != 0)
                        vte_stats_update(sc);
                if_schedule_deferred_start(ifp);
                if (--n > 0)
                        status = CSR_READ_2(sc, VTE_MISR);
                else
                        break;
        }

        if ((ifp->if_flags & IFF_RUNNING) != 0) {
                /* Re-enable interrupts. */
                CSR_WRITE_2(sc, VTE_MIER, VTE_INTRS);
        }
        return 1;
}

static void
vte_txeof(struct vte_softc *sc)
{
        struct ifnet *ifp;
        struct vte_txdesc *txd;
        uint16_t status;
        int cons, prog;

        ifp = &sc->vte_if;

        if (sc->vte_cdata.vte_tx_cnt == 0)
                return;
        bus_dmamap_sync(sc->vte_dmatag,
            sc->vte_cdata.vte_tx_ring_map, 0,
            sc->vte_cdata.vte_tx_ring_map->dm_mapsize,
            BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
        cons = sc->vte_cdata.vte_tx_cons;
        /*
         * Go through our TX list and free mbufs for those
         * frames which have been transmitted.
         */
        for (prog = 0; sc->vte_cdata.vte_tx_cnt > 0; prog++) {
                txd = &sc->vte_cdata.vte_txdesc[cons];
                status = le16toh(txd->tx_desc->dtst);
                if ((status & VTE_DTST_TX_OWN) != 0)
                        break;
                if ((status & VTE_DTST_TX_OK) != 0)
                        if_statadd(ifp, if_collisions, (status & 0xf));
                sc->vte_cdata.vte_tx_cnt--;
                /* Reclaim transmitted mbufs. */
                bus_dmamap_sync(sc->vte_dmatag, txd->tx_dmamap, 0,
                    txd->tx_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
                bus_dmamap_unload(sc->vte_dmatag, txd->tx_dmamap);
                if ((txd->tx_flags & VTE_TXMBUF) == 0)
                        m_freem(txd->tx_m);
                txd->tx_flags &= ~VTE_TXMBUF;
                txd->tx_m = NULL;
                prog++;
                VTE_DESC_INC(cons, VTE_TX_RING_CNT);
        }

        if (prog > 0) {
                sc->vte_cdata.vte_tx_cons = cons;
                /*
                 * Unarm watchdog timer only when there is no pending
                 * frames in TX queue.
                 */
                if (sc->vte_cdata.vte_tx_cnt == 0)
                        sc->vte_watchdog_timer = 0;
        }
}

static int
vte_newbuf(struct vte_softc *sc, struct vte_rxdesc *rxd)
{
        struct mbuf *m;
        bus_dmamap_t map;

        m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
        if (m == NULL)
                return (ENOBUFS);
        m->m_len = m->m_pkthdr.len = MCLBYTES;
        m_adj(m, sizeof(uint32_t));

        if (bus_dmamap_load_mbuf(sc->vte_dmatag,
            sc->vte_cdata.vte_rx_sparemap, m, BUS_DMA_NOWAIT) != 0) {
                m_freem(m);
                return (ENOBUFS);
        }
        KASSERT(sc->vte_cdata.vte_rx_sparemap->dm_nsegs == 1);

        if (rxd->rx_m != NULL) {
                bus_dmamap_sync(sc->vte_dmatag, rxd->rx_dmamap,
                    0, rxd->rx_dmamap->dm_mapsize,
                    BUS_DMASYNC_POSTREAD);
                bus_dmamap_unload(sc->vte_dmatag, rxd->rx_dmamap);
        }
        map = rxd->rx_dmamap;
        rxd->rx_dmamap = sc->vte_cdata.vte_rx_sparemap;
        sc->vte_cdata.vte_rx_sparemap = map;
        bus_dmamap_sync(sc->vte_dmatag, rxd->rx_dmamap,
            0, rxd->rx_dmamap->dm_mapsize,
            BUS_DMASYNC_PREREAD);
        rxd->rx_m = m;
        rxd->rx_desc->drbp =
            htole32(rxd->rx_dmamap->dm_segs[0].ds_addr);
        rxd->rx_desc->drlen = htole16(
            VTE_RX_LEN(rxd->rx_dmamap->dm_segs[0].ds_len));
        DPRINTF(("rx data %p mbuf %p buf 0x%x/0x%x\n", rxd, m,
                (u_int)rxd->rx_dmamap->dm_segs[0].ds_addr,
                rxd->rx_dmamap->dm_segs[0].ds_len));
        rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN);

        return (0);
}

static void
vte_rxeof(struct vte_softc *sc)
{
        struct ifnet *ifp;
        struct vte_rxdesc *rxd;
        struct mbuf *m;
        uint16_t status, total_len;
        int cons, prog;

        bus_dmamap_sync(sc->vte_dmatag,
            sc->vte_cdata.vte_rx_ring_map, 0,
            sc->vte_cdata.vte_rx_ring_map->dm_mapsize,
            BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
        cons = sc->vte_cdata.vte_rx_cons;
        ifp = &sc->vte_if;
        DPRINTF(("vte_rxeof if_flags 0x%x\n", ifp->if_flags));
        for (prog = 0; (ifp->if_flags & IFF_RUNNING) != 0; prog++,
            VTE_DESC_INC(cons, VTE_RX_RING_CNT)) {
                rxd = &sc->vte_cdata.vte_rxdesc[cons];
                status = le16toh(rxd->rx_desc->drst);
                DPRINTF(("vte_rxeof rxd %d/%p mbuf %p status 0x%x len %d\n",
                        cons, rxd, rxd->rx_m, status,
                        VTE_RX_LEN(le16toh(rxd->rx_desc->drlen))));
                if ((status & VTE_DRST_RX_OWN) != 0)
                        break;
                total_len = VTE_RX_LEN(le16toh(rxd->rx_desc->drlen));
                m = rxd->rx_m;
                if ((status & VTE_DRST_RX_OK) == 0) {
                        /* Discard errored frame. */
                        rxd->rx_desc->drlen =
                            htole16(MCLBYTES - sizeof(uint32_t));
                        rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN);
                        continue;
                }
                if (vte_newbuf(sc, rxd) != 0) {
                        DPRINTF(("vte_rxeof newbuf failed\n"));
                        if_statinc(ifp, if_ierrors);
                        rxd->rx_desc->drlen =
                            htole16(MCLBYTES - sizeof(uint32_t));
                        rxd->rx_desc->drst = htole16(VTE_DRST_RX_OWN);
                        continue;
                }

                /*
                 * It seems there is no way to strip FCS bytes.
                 */
                m->m_pkthdr.len = m->m_len = total_len - ETHER_CRC_LEN;
                m_set_rcvif(m, ifp);
                if_percpuq_enqueue(ifp->if_percpuq, m);
        }

        if (prog > 0) {
                /* Update the consumer index. */
                sc->vte_cdata.vte_rx_cons = cons;
                /*
                 * Sync updated RX descriptors such that controller see
                 * modified RX buffer addresses.
                 */
                bus_dmamap_sync(sc->vte_dmatag,
                    sc->vte_cdata.vte_rx_ring_map, 0,
                    sc->vte_cdata.vte_rx_ring_map->dm_mapsize,
                    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
#ifdef notyet
                /*
                 * Update residue counter.  Controller does not
                 * keep track of number of available RX descriptors
                 * such that driver should have to update VTE_MRDCR
                 * to make controller know how many free RX
                 * descriptors were added to controller.  This is
                 * a similar mechanism used in VIA velocity
                 * controllers and it indicates controller just
                 * polls OWN bit of current RX descriptor pointer.
                 * A couple of severe issues were seen on sample
                 * board where the controller continuously emits TX
                 * pause frames once RX pause threshold crossed.
                 * Once triggered it never recovered from that
                 * state, I couldn't find a way to make it back to
                 * work at least.  This issue effectively
                 * disconnected the system from network.  Also, the
                 * controller used 00:00:00:00:00:00 as source
                 * station address of TX pause frame. Probably this
                 * is one of reason why vendor recommends not to
                 * enable flow control on R6040 controller.
                 */
                CSR_WRITE_2(sc, VTE_MRDCR, prog |
                    (((VTE_RX_RING_CNT * 2) / 10) <<
                    VTE_MRDCR_RX_PAUSE_THRESH_SHIFT));
#endif
        rnd_add_uint32(&sc->rnd_source, prog);
        }
}

static void
vte_tick(void *arg)
{
        struct vte_softc *sc;
        int s = splnet();

        sc = (struct vte_softc *)arg;

        mii_tick(&sc->vte_mii);
        vte_stats_update(sc);
        vte_txeof(sc);
        vte_ifwatchdog(&sc->vte_if);
        callout_schedule(&sc->vte_tick_ch, hz);
        splx(s);
}

static void
vte_reset(struct vte_softc *sc)
{
        uint16_t mcr, mdcsc;
        int i;

        mdcsc = CSR_READ_2(sc, VTE_MDCSC);
        mcr = CSR_READ_2(sc, VTE_MCR1);
        CSR_WRITE_2(sc, VTE_MCR1, mcr | MCR1_MAC_RESET);
        for (i = VTE_RESET_TIMEOUT; i > 0; i--) {
                DELAY(10);
                if ((CSR_READ_2(sc, VTE_MCR1) & MCR1_MAC_RESET) == 0)
                        break;
        }
        if (i == 0)
                aprint_error_dev(sc->vte_dev, "reset timeout(0x%04x)!\n", mcr);
        /*
         * Follow the guide of vendor recommended way to reset MAC.
         * Vendor confirms relying on MCR1_MAC_RESET of VTE_MCR1 is
         * not reliable so manually reset internal state machine.
         */
        CSR_WRITE_2(sc, VTE_MACSM, 0x0002);
        CSR_WRITE_2(sc, VTE_MACSM, 0);
        DELAY(5000);

        /*
         * On some SoCs (like Vortex86DX3) MDC speed control register value
         * needs to be restored to original value instead of default one,
         * otherwise some PHY registers may fail to be read.
         */
        if (mdcsc != MDCSC_DEFAULT)
                CSR_WRITE_2(sc, VTE_MDCSC, mdcsc);
}


static int
vte_init(struct ifnet *ifp)
{
        struct vte_softc *sc = ifp->if_softc;
        bus_addr_t paddr;
        uint8_t eaddr[ETHER_ADDR_LEN];
        int s, error;

        s = splnet();
        /*
         * Cancel any pending I/O.
         */
        vte_stop(ifp, 1);
        /*
         * Reset the chip to a known state.
         */
        vte_reset(sc);

        if ((sc->vte_if.if_flags & IFF_UP) == 0) {
                splx(s);
                return 0;
        }

        /* Initialize RX descriptors. */
        if (vte_init_rx_ring(sc) != 0) {
                aprint_error_dev(sc->vte_dev, "no memory for RX buffers.\n");
                vte_stop(ifp, 1);
                splx(s);
                return ENOMEM;
        }
        if (vte_init_tx_ring(sc) != 0) {
                aprint_error_dev(sc->vte_dev, "no memory for TX buffers.\n");
                vte_stop(ifp, 1);
                splx(s);
                return ENOMEM;
        }

        /*
         * Reprogram the station address.  Controller supports up
         * to 4 different station addresses so driver programs the
         * first station address as its own ethernet address and
         * configure the remaining three addresses as perfect
         * multicast addresses.
         */
        memcpy(eaddr, CLLADDR(ifp->if_sadl), ETHER_ADDR_LEN);
        CSR_WRITE_2(sc, VTE_MID0L, eaddr[1] << 8 | eaddr[0]);
        CSR_WRITE_2(sc, VTE_MID0M, eaddr[3] << 8 | eaddr[2]);
        CSR_WRITE_2(sc, VTE_MID0H, eaddr[5] << 8 | eaddr[4]);

        /* Set TX descriptor base addresses. */
        paddr = sc->vte_cdata.vte_tx_ring_map->dm_segs[0].ds_addr;
        DPRINTF(("tx paddr 0x%x\n", (u_int)paddr));
        CSR_WRITE_2(sc, VTE_MTDSA1, paddr >> 16);
        CSR_WRITE_2(sc, VTE_MTDSA0, paddr & 0xFFFF);

        /* Set RX descriptor base addresses. */
        paddr = sc->vte_cdata.vte_rx_ring_map->dm_segs[0].ds_addr;
        DPRINTF(("rx paddr 0x%x\n", (u_int)paddr));
        CSR_WRITE_2(sc, VTE_MRDSA1, paddr >> 16);
        CSR_WRITE_2(sc, VTE_MRDSA0, paddr & 0xFFFF);
        /*
         * Initialize RX descriptor residue counter and set RX
         * pause threshold to 20% of available RX descriptors.
         * See comments on vte_rxeof() for details on flow control
         * issues.
         */
        CSR_WRITE_2(sc, VTE_MRDCR, (VTE_RX_RING_CNT & VTE_MRDCR_RESIDUE_MASK) |
            (((VTE_RX_RING_CNT * 2) / 10) << VTE_MRDCR_RX_PAUSE_THRESH_SHIFT));

        /*
         * Always use maximum frame size that controller can
         * support.  Otherwise received frames that has longer
         * frame length than vte(4) MTU would be silently dropped
         * in controller.  This would break path-MTU discovery as
         * sender wouldn't get any responses from receiver. The
         * RX buffer size should be multiple of 4.
         * Note, jumbo frames are silently ignored by controller
         * and even MAC counters do not detect them.
         */
        CSR_WRITE_2(sc, VTE_MRBSR, VTE_RX_BUF_SIZE_MAX);

        /* Configure FIFO. */
        CSR_WRITE_2(sc, VTE_MBCR, MBCR_FIFO_XFER_LENGTH_16 |
            MBCR_TX_FIFO_THRESH_64 | MBCR_RX_FIFO_THRESH_16 |
            MBCR_SDRAM_BUS_REQ_TIMER_DEFAULT);

        /*
         * Configure TX/RX MACs.  Actual resolved duplex and flow
         * control configuration is done after detecting a valid
         * link.  Note, we don't generate early interrupt here
         * as well since FreeBSD does not have interrupt latency
         * problems like Windows.
         */
        CSR_WRITE_2(sc, VTE_MCR0, MCR0_ACCPT_LONG_PKT);
        /*
         * We manually keep track of PHY status changes to
         * configure resolved duplex and flow control since only
         * duplex configuration can be automatically reflected to
         * MCR0.
         */
        CSR_WRITE_2(sc, VTE_MCR1, MCR1_PKT_LENGTH_1537 |
            MCR1_EXCESS_COL_RETRY_16);

        /* Initialize RX filter. */
        vte_rxfilter(sc);

        /* Disable TX/RX interrupt moderation control. */
        CSR_WRITE_2(sc, VTE_MRICR, 0);
        CSR_WRITE_2(sc, VTE_MTICR, 0);

        /* Enable MAC event counter interrupts. */
        CSR_WRITE_2(sc, VTE_MECIER, VTE_MECIER_INTRS);
        /* Clear MAC statistics. */
        vte_stats_clear(sc);

        /* Acknowledge all pending interrupts and clear it. */
        CSR_WRITE_2(sc, VTE_MIER, VTE_INTRS);
        CSR_WRITE_2(sc, VTE_MISR, 0);
        DPRINTF(("before ipend 0x%x 0x%x\n", CSR_READ_2(sc, VTE_MIER),
                CSR_READ_2(sc, VTE_MISR)));

        sc->vte_flags &= ~VTE_FLAG_LINK;
        ifp->if_flags |= IFF_RUNNING;

        /* calling mii_mediachg will call back vte_start_mac() */
        if ((error = mii_mediachg(&sc->vte_mii)) == ENXIO)
                error = 0;
        else if (error != 0) {
                aprint_error_dev(sc->vte_dev, "could not set media\n");
                splx(s);
                return error;
        }

        callout_schedule(&sc->vte_tick_ch, hz);

        DPRINTF(("ipend 0x%x 0x%x\n", CSR_READ_2(sc, VTE_MIER),
                CSR_READ_2(sc, VTE_MISR)));
        splx(s);
        return 0;
}

static void
vte_stop(struct ifnet *ifp, int disable)
{
        struct vte_softc *sc = ifp->if_softc;
        struct vte_txdesc *txd;
        struct vte_rxdesc *rxd;
        int i;

        DPRINTF(("vte_stop if_flags 0x%x\n", ifp->if_flags));
        if ((ifp->if_flags & IFF_RUNNING) == 0)
                return;
        /*
         * Mark the interface down and cancel the watchdog timer.
         */
        ifp->if_flags &= ~IFF_RUNNING;
        sc->vte_flags &= ~VTE_FLAG_LINK;
        callout_stop(&sc->vte_tick_ch);
        sc->vte_watchdog_timer = 0;
        vte_stats_update(sc);
        /* Disable interrupts. */
        CSR_WRITE_2(sc, VTE_MIER, 0);
        CSR_WRITE_2(sc, VTE_MECIER, 0);
        /* Stop RX/TX MACs. */
        vte_stop_mac(sc);
        /* Clear interrupts. */
        CSR_READ_2(sc, VTE_MISR);
        /*
         * Free TX/RX mbufs still in the queues.
         */
        for (i = 0; i < VTE_RX_RING_CNT; i++) {
                rxd = &sc->vte_cdata.vte_rxdesc[i];
                if (rxd->rx_m != NULL) {
                        bus_dmamap_sync(sc->vte_dmatag,
                            rxd->rx_dmamap, 0, rxd->rx_dmamap->dm_mapsize,
                            BUS_DMASYNC_POSTREAD);
                        bus_dmamap_unload(sc->vte_dmatag,
                            rxd->rx_dmamap);
                        m_freem(rxd->rx_m);
                        rxd->rx_m = NULL;
                }
        }
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                txd = &sc->vte_cdata.vte_txdesc[i];
                if (txd->tx_m != NULL) {
                        bus_dmamap_sync(sc->vte_dmatag,
                            txd->tx_dmamap, 0, txd->tx_dmamap->dm_mapsize,
                            BUS_DMASYNC_POSTWRITE);
                        bus_dmamap_unload(sc->vte_dmatag,
                            txd->tx_dmamap);
                        if ((txd->tx_flags & VTE_TXMBUF) == 0)
                                m_freem(txd->tx_m);
                        txd->tx_m = NULL;
                        txd->tx_flags &= ~VTE_TXMBUF;
                }
        }
        /* Free TX mbuf pools used for deep copy. */
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                m_freem(sc->vte_cdata.vte_txmbufs[i]);
                sc->vte_cdata.vte_txmbufs[i] = NULL;
        }
}

static void
vte_start_mac(struct vte_softc *sc)
{
        struct ifnet *ifp = &sc->vte_if;
        uint16_t mcr;
        int i;

        /* Enable RX/TX MACs. */
        mcr = CSR_READ_2(sc, VTE_MCR0);
        if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) !=
            (MCR0_RX_ENB | MCR0_TX_ENB) &&
            (ifp->if_flags & IFF_RUNNING) != 0) {
                mcr |= MCR0_RX_ENB | MCR0_TX_ENB;
                CSR_WRITE_2(sc, VTE_MCR0, mcr);
                for (i = VTE_TIMEOUT; i > 0; i--) {
                        mcr = CSR_READ_2(sc, VTE_MCR0);
                        if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) ==
                            (MCR0_RX_ENB | MCR0_TX_ENB))
                                break;
                        DELAY(10);
                }
                if (i == 0)
                        aprint_error_dev(sc->vte_dev,
                            "could not enable RX/TX MAC(0x%04x)!\n", mcr);
        }
        vte_rxfilter(sc);
}

static void
vte_stop_mac(struct vte_softc *sc)
{
        uint16_t mcr;
        int i;

        /* Disable RX/TX MACs. */
        mcr = CSR_READ_2(sc, VTE_MCR0);
        if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) != 0) {
                mcr &= ~(MCR0_RX_ENB | MCR0_TX_ENB);
                CSR_WRITE_2(sc, VTE_MCR0, mcr);
                for (i = VTE_TIMEOUT; i > 0; i--) {
                        mcr = CSR_READ_2(sc, VTE_MCR0);
                        if ((mcr & (MCR0_RX_ENB | MCR0_TX_ENB)) == 0)
                                break;
                        DELAY(10);
                }
                if (i == 0)
                        aprint_error_dev(sc->vte_dev,
                            "could not disable RX/TX MAC(0x%04x)!\n", mcr);
        }
}

static int
vte_init_tx_ring(struct vte_softc *sc)
{
        struct vte_tx_desc *desc;
        struct vte_txdesc *txd;
        bus_addr_t addr;
        int i;

        sc->vte_cdata.vte_tx_prod = 0;
        sc->vte_cdata.vte_tx_cons = 0;
        sc->vte_cdata.vte_tx_cnt = 0;

        /* Pre-allocate TX mbufs for deep copy. */
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                sc->vte_cdata.vte_txmbufs[i] = m_getcl(M_DONTWAIT,
                    MT_DATA, M_PKTHDR);
                if (sc->vte_cdata.vte_txmbufs[i] == NULL)
                        return (ENOBUFS);
                sc->vte_cdata.vte_txmbufs[i]->m_pkthdr.len = MCLBYTES;
                sc->vte_cdata.vte_txmbufs[i]->m_len = MCLBYTES;
        }
        desc = sc->vte_cdata.vte_tx_ring;
        bzero(desc, VTE_TX_RING_SZ);
        for (i = 0; i < VTE_TX_RING_CNT; i++) {
                txd = &sc->vte_cdata.vte_txdesc[i];
                txd->tx_m = NULL;
                if (i != VTE_TX_RING_CNT - 1)
                        addr = sc->vte_cdata.vte_tx_ring_map->dm_segs[0].ds_addr +
                            sizeof(struct vte_tx_desc) * (i + 1);
                else
                        addr = sc->vte_cdata.vte_tx_ring_map->dm_segs[0].ds_addr +
                            sizeof(struct vte_tx_desc) * 0;
                desc = &sc->vte_cdata.vte_tx_ring[i];
                desc->dtnp = htole32(addr);
                DPRINTF(("tx ring desc %d addr 0x%x\n", i, (u_int)addr));
                txd->tx_desc = desc;
        }

        bus_dmamap_sync(sc->vte_dmatag,
            sc->vte_cdata.vte_tx_ring_map, 0,
            sc->vte_cdata.vte_tx_ring_map->dm_mapsize,
            BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
        return (0);
}

static int
vte_init_rx_ring(struct vte_softc *sc)
{
        struct vte_rx_desc *desc;
        struct vte_rxdesc *rxd;
        bus_addr_t addr;
        int i;

        sc->vte_cdata.vte_rx_cons = 0;
        desc = sc->vte_cdata.vte_rx_ring;
        bzero(desc, VTE_RX_RING_SZ);
        for (i = 0; i < VTE_RX_RING_CNT; i++) {
                rxd = &sc->vte_cdata.vte_rxdesc[i];
                rxd->rx_m = NULL;
                if (i != VTE_RX_RING_CNT - 1)
                        addr = sc->vte_cdata.vte_rx_ring_map->dm_segs[0].ds_addr
                            + sizeof(struct vte_rx_desc) * (i + 1);
                else
                        addr = sc->vte_cdata.vte_rx_ring_map->dm_segs[0].ds_addr
                            + sizeof(struct vte_rx_desc) * 0;
                desc = &sc->vte_cdata.vte_rx_ring[i];
                desc->drnp = htole32(addr);
                DPRINTF(("rx ring desc %d addr 0x%x\n", i, (u_int)addr));
                rxd->rx_desc = desc;
                if (vte_newbuf(sc, rxd) != 0)
                        return (ENOBUFS);
        }

        bus_dmamap_sync(sc->vte_dmatag,
            sc->vte_cdata.vte_rx_ring_map, 0,
            sc->vte_cdata.vte_rx_ring_map->dm_mapsize,
            BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

        return (0);
}

static void
vte_rxfilter(struct vte_softc *sc)
{
        struct ethercom *ec = &sc->vte_ec;
        struct ether_multistep step;
        struct ether_multi *enm;
        struct ifnet *ifp;
        uint8_t *eaddr;
        uint32_t crc;
        uint16_t rxfilt_perf[VTE_RXFILT_PERFECT_CNT][3];
        uint16_t mchash[4], mcr;
        int i, nperf;

        ifp = &sc->vte_if;

        DPRINTF(("vte_rxfilter\n"));
        memset(mchash, 0, sizeof(mchash));
        for (i = 0; i < VTE_RXFILT_PERFECT_CNT; i++) {
                rxfilt_perf[i][0] = 0xFFFF;
                rxfilt_perf[i][1] = 0xFFFF;
                rxfilt_perf[i][2] = 0xFFFF;
        }

        mcr = CSR_READ_2(sc, VTE_MCR0);
        DPRINTF(("vte_rxfilter mcr 0x%x\n", mcr));
        mcr &= ~(MCR0_PROMISC | MCR0_BROADCAST_DIS | MCR0_MULTICAST);
        if ((ifp->if_flags & IFF_BROADCAST) == 0)
                mcr |= MCR0_BROADCAST_DIS;
        if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) {
                if ((ifp->if_flags & IFF_PROMISC) != 0)
                        mcr |= MCR0_PROMISC;
                if ((ifp->if_flags & IFF_ALLMULTI) != 0)
                        mcr |= MCR0_MULTICAST;
                mchash[0] = 0xFFFF;
                mchash[1] = 0xFFFF;
                mchash[2] = 0xFFFF;
                mchash[3] = 0xFFFF;
                goto chipit;
        }

        ETHER_LOCK(ec);
        ETHER_FIRST_MULTI(step, ec, enm);
        nperf = 0;
        while (enm != NULL) {
                if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)
                    != 0) {
                        sc->vte_if.if_flags |= IFF_ALLMULTI;
                        mcr |= MCR0_MULTICAST;
                        mchash[0] = 0xFFFF;
                        mchash[1] = 0xFFFF;
                        mchash[2] = 0xFFFF;
                        mchash[3] = 0xFFFF;
                        ETHER_UNLOCK(ec);
                        goto chipit;
                }
                /*
                 * Program the first 3 multicast groups into
                 * the perfect filter.  For all others, use the
                 * hash table.
                 */
                if (nperf < VTE_RXFILT_PERFECT_CNT) {
                        eaddr = enm->enm_addrlo;
                        rxfilt_perf[nperf][0] = eaddr[1] << 8 | eaddr[0];
                        rxfilt_perf[nperf][1] = eaddr[3] << 8 | eaddr[2];
                        rxfilt_perf[nperf][2] = eaddr[5] << 8 | eaddr[4];
                        nperf++;
                } else {
                        crc = ether_crc32_be(enm->enm_addrlo, ETHER_ADDR_LEN);
                        mchash[crc >> 30] |= 1 << ((crc >> 26) & 0x0F);
                }
                ETHER_NEXT_MULTI(step, enm);
        }
        ETHER_UNLOCK(ec);
        if (mchash[0] != 0 || mchash[1] != 0 || mchash[2] != 0 ||
            mchash[3] != 0)
                mcr |= MCR0_MULTICAST;

chipit:
        /* Program multicast hash table. */
        DPRINTF(("chipit write multicast\n"));
        CSR_WRITE_2(sc, VTE_MAR0, mchash[0]);
        CSR_WRITE_2(sc, VTE_MAR1, mchash[1]);
        CSR_WRITE_2(sc, VTE_MAR2, mchash[2]);
        CSR_WRITE_2(sc, VTE_MAR3, mchash[3]);
        /* Program perfect filter table. */
        DPRINTF(("chipit write perfect filter\n"));
        for (i = 0; i < VTE_RXFILT_PERFECT_CNT; i++) {
                CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 0,
                    rxfilt_perf[i][0]);
                CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 2,
                    rxfilt_perf[i][1]);
                CSR_WRITE_2(sc, VTE_RXFILTER_PEEFECT_BASE + 8 * i + 4,
                    rxfilt_perf[i][2]);
        }
        DPRINTF(("chipit mcr0 0x%x\n", mcr));
        CSR_WRITE_2(sc, VTE_MCR0, mcr);
        DPRINTF(("chipit read mcro\n"));
        CSR_READ_2(sc, VTE_MCR0);
        DPRINTF(("chipit done\n"));
}

/*
 * Set up sysctl(3) MIB, hw.vte.* - Individual controllers will be
 * set up in vte_pci_attach()
 */
SYSCTL_SETUP(sysctl_vte, "sysctl vte subtree setup")
{
        int rc;
        const struct sysctlnode *node;

        if ((rc = sysctl_createv(clog, 0, NULL, &node,
            0, CTLTYPE_NODE, "vte",
            SYSCTL_DESCR("vte interface controls"),
            NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) {
                goto err;
        }

        vte_root_num = node->sysctl_num;
        return;

err:
        aprint_error("%s: syctl_createv failed (rc = %d)\n", __func__, rc);
}

static int
vte_sysctl_intrxct(SYSCTLFN_ARGS)
{
        int error, t;
        struct sysctlnode node;
        struct vte_softc *sc;

        node = *rnode;
        sc = node.sysctl_data;
        t = sc->vte_int_rx_mod;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;
        if (t < VTE_IM_BUNDLE_MIN || t > VTE_IM_BUNDLE_MAX)
                return EINVAL;

        sc->vte_int_rx_mod = t;
        vte_miibus_statchg(&sc->vte_if);
        return 0;
}

static int
vte_sysctl_inttxct(SYSCTLFN_ARGS)
{
        int error, t;
        struct sysctlnode node;
        struct vte_softc *sc;

        node = *rnode;
        sc = node.sysctl_data;
        t = sc->vte_int_tx_mod;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;

        if (t < VTE_IM_BUNDLE_MIN || t > VTE_IM_BUNDLE_MAX)
                return EINVAL;
        sc->vte_int_tx_mod = t;
        vte_miibus_statchg(&sc->vte_if);
        return 0;
}