root/sys/dev/ic/rtl8169.c
/*      $NetBSD: rtl8169.c,v 1.183 2026/05/21 10:19:28 skrll Exp $      */

/*
 * Copyright (c) 1997, 1998-2003
 *      Bill Paul <wpaul@windriver.com>.  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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Bill Paul.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD
 * 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>
__KERNEL_RCSID(0, "$NetBSD: rtl8169.c,v 1.183 2026/05/21 10:19:28 skrll Exp $");
/* $FreeBSD: /repoman/r/ncvs/src/sys/dev/re/if_re.c,v 1.20 2004/04/11 20:34:08 ru Exp $ */

/*
 * RealTek 8139C+/8169/8169S/8168/8110S PCI NIC driver
 *
 * Written by Bill Paul <wpaul@windriver.com>
 * Senior Networking Software Engineer
 * Wind River Systems
 */

/*
 * This driver is designed to support RealTek's next generation of
 * 10/100 and 10/100/1000 PCI ethernet controllers. There are currently
 * six devices in this family: the RTL8139C+, the RTL8169, the RTL8169S,
 * RTL8110S, the RTL8168 and the RTL8111.
 *
 * The 8139C+ is a 10/100 ethernet chip. It is backwards compatible
 * with the older 8139 family, however it also supports a special
 * C+ mode of operation that provides several new performance enhancing
 * features. These include:
 *
 *      o Descriptor based DMA mechanism. Each descriptor represents
 *        a single packet fragment. Data buffers may be aligned on
 *        any byte boundary.
 *
 *      o 64-bit DMA
 *
 *      o TCP/IP checksum offload for both RX and TX
 *
 *      o High and normal priority transmit DMA rings
 *
 *      o VLAN tag insertion and extraction
 *
 *      o TCP large send (segmentation offload)
 *
 * Like the 8139, the 8139C+ also has a built-in 10/100 PHY. The C+
 * programming API is fairly straightforward. The RX filtering, EEPROM
 * access and PHY access is the same as it is on the older 8139 series
 * chips.
 *
 * The 8169 is a 64-bit 10/100/1000 gigabit ethernet MAC. It has almost the
 * same programming API and feature set as the 8139C+ with the following
 * differences and additions:
 *
 *      o 1000Mbps mode
 *
 *      o Jumbo frames
 *
 *      o GMII and TBI ports/registers for interfacing with copper
 *        or fiber PHYs
 *
 *      o RX and TX DMA rings can have up to 1024 descriptors
 *        (the 8139C+ allows a maximum of 64)
 *
 *      o Slight differences in register layout from the 8139C+
 *
 * The TX start and timer interrupt registers are at different locations
 * on the 8169 than they are on the 8139C+. Also, the status word in the
 * RX descriptor has a slightly different bit layout. The 8169 does not
 * have a built-in PHY. Most reference boards use a Marvell 88E1000 'Alaska'
 * copper gigE PHY.
 *
 * The 8169S/8110S 10/100/1000 devices have built-in copper gigE PHYs
 * (the 'S' stands for 'single-chip'). These devices have the same
 * programming API as the older 8169, but also have some vendor-specific
 * registers for the on-board PHY. The 8110S is a LAN-on-motherboard
 * part designed to be pin-compatible with the RealTek 8100 10/100 chip.
 *
 * This driver takes advantage of the RX and TX checksum offload and
 * VLAN tag insertion/extraction features. It also implements TX
 * interrupt moderation using the timer interrupt registers, which
 * significantly reduces TX interrupt load. There is also support
 * for jumbo frames, however the 8169/8169S/8110S can not transmit
 * jumbo frames larger than 7.5K, so the max MTU possible with this
 * driver is 7500 bytes.
 */


#include <sys/param.h>
#include <sys/cprng.h>
#include <sys/endian.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/device.h>

#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/if_vlanvar.h>

#include <netinet/in_systm.h>   /* XXX for IP_MAXPACKET */
#include <netinet/in.h>         /* XXX for IP_MAXPACKET */
#include <netinet/ip.h>         /* XXX for IP_MAXPACKET */

#include <net/bpf.h>
#include <sys/rndsource.h>

#include <sys/bus.h>

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

#include <dev/ic/rtl81x9reg.h>
#include <dev/ic/rtl81x9var.h>

#include <dev/ic/rtl8169var.h>

#define RE_LOCK(sc)             mutex_enter(&(sc)->sc_lock)
#define RE_UNLOCK(sc)           mutex_exit(&(sc)->sc_lock)
#define RE_ASSERT_LOCKED(sc)    KASSERT(mutex_owned(&(sc)->sc_lock))

#define ETHER_IS_ZERO(addr) \
        (!(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]))

static inline void re_set_bufaddr(struct re_desc *, bus_addr_t);

static int re_newbuf(struct rtk_softc *, int, struct mbuf *);
static int re_rx_list_init(struct rtk_softc *);
static int re_tx_list_init(struct rtk_softc *);
static void re_rxeof(struct rtk_softc *);
static void re_txeof(struct rtk_softc *);
static void re_tick(void *);
static void re_tick_locked(struct rtk_softc *);
static void re_start(struct ifnet *);
static void re_start_locked(struct rtk_softc *);
static int re_ioctl(struct ifnet *, u_long, void *);
static int re_ifflags_cb(struct ethercom *);
static int re_init(struct ifnet *);
static int re_init_locked(struct rtk_softc *);
static void re_stop(struct ifnet *, int);
static void re_stop_locked(struct rtk_softc *, int);
static void re_watchdog(struct ifnet *);

static int re_enable(struct rtk_softc *);
static void re_disable(struct rtk_softc *);

static int re_gmii_readreg(device_t, int, int, uint16_t *);
static int re_gmii_writereg(device_t, int, int, uint16_t);

static int re_miibus_readreg(device_t, int, int, uint16_t *);
static int re_miibus_writereg(device_t, int, int, uint16_t);
static void re_miibus_statchg(struct ifnet *);

static void re_reset(struct rtk_softc *);

static const struct re_revision {
        uint32_t                re_chipid;
        const char              *re_name;
} re_revisions[] = {
        { RTK_HWREV_8100,       "RTL8100" },
        { RTK_HWREV_8100E,      "RTL8100E" },
        { RTK_HWREV_8100E_SPIN2, "RTL8100E 2" },
        { RTK_HWREV_8101,       "RTL8101" },
        { RTK_HWREV_8101E,      "RTL8101E" },
        { RTK_HWREV_8102E,      "RTL8102E" },
        { RTK_HWREV_8106E,      "RTL8106E" },
        { RTK_HWREV_8401E,      "RTL8401E" },
        { RTK_HWREV_8402,       "RTL8402" },
        { RTK_HWREV_8411,       "RTL8411" },
        { RTK_HWREV_8411B,      "RTL8411B" },
        { RTK_HWREV_8102EL,     "RTL8102EL" },
        { RTK_HWREV_8102EL_SPIN1, "RTL8102EL 1" },
        { RTK_HWREV_8103E,       "RTL8103E" },
        { RTK_HWREV_8110S,      "RTL8110S" },
        { RTK_HWREV_8139CPLUS,  "RTL8139C+" },
        { RTK_HWREV_8168B_SPIN1, "RTL8168 1" },
        { RTK_HWREV_8168B_SPIN2, "RTL8168 2" },
        { RTK_HWREV_8168B_SPIN3, "RTL8168 3" },
        { RTK_HWREV_8168C,      "RTL8168C/8111C" },
        { RTK_HWREV_8168C_SPIN2, "RTL8168C/8111C" },
        { RTK_HWREV_8168CP,     "RTL8168CP/8111CP" },
        { RTK_HWREV_8168F,      "RTL8168F/8111F" },
        { RTK_HWREV_8168G,      "RTL8168G/8111G" },
        { RTK_HWREV_8168GU,     "RTL8168GU/8111GU" },
        { RTK_HWREV_8168H,      "RTL8168H/8111H" },
        { RTK_HWREV_8105E,      "RTL8105E" },
        { RTK_HWREV_8105E_SPIN1, "RTL8105E" },
        { RTK_HWREV_8168D,      "RTL8168D/8111D" },
        { RTK_HWREV_8168DP,     "RTL8168DP/8111DP" },
        { RTK_HWREV_8168E,      "RTL8168E/8111E" },
        { RTK_HWREV_8168E_VL,   "RTL8168E/8111E-VL" },
        { RTK_HWREV_8168EP,     "RTL8168EP/8111EP" },
        { RTK_HWREV_8168FP,     "RTL8168FP/8117" },
        { RTK_HWREV_8169,       "RTL8169" },
        { RTK_HWREV_8169_8110SB, "RTL8169/8110SB" },
        { RTK_HWREV_8169_8110SBL, "RTL8169SBL" },
        { RTK_HWREV_8169_8110SC, "RTL8169/8110SCd" },
        { RTK_HWREV_8169_8110SCE, "RTL8169/8110SCe" },
        { RTK_HWREV_8169S,      "RTL8169S" },

        { 0, NULL }
};

static inline void
re_set_bufaddr(struct re_desc *d, bus_addr_t addr)
{

        d->re_bufaddr_lo = htole32(RE_ADDR_LO(addr));
        d->re_bufaddr_hi = htole32(RE_ADDR_HI(addr));
}

static int
re_gmii_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
        struct rtk_softc *sc = device_private(dev);
        uint32_t data;
        int i;

        if (phy != 7)
                return -1;

        /* Let the rgephy driver read the GMEDIASTAT register */

        if (reg == RTK_GMEDIASTAT) {
                *val = CSR_READ_1(sc, RTK_GMEDIASTAT);
                return 0;
        }

        CSR_WRITE_4(sc, RTK_PHYAR, reg << 16);
        DELAY(1000);

        for (i = 0; i < RTK_TIMEOUT; i++) {
                data = CSR_READ_4(sc, RTK_PHYAR);
                if (data & RTK_PHYAR_BUSY)
                        break;
                DELAY(100);
        }

        if (i == RTK_TIMEOUT) {
                printf("%s: PHY read failed\n", device_xname(sc->sc_dev));
                return ETIMEDOUT;
        }

        *val = data & RTK_PHYAR_PHYDATA;
        return 0;
}

static int
re_gmii_writereg(device_t dev, int phy, int reg, uint16_t val)
{
        struct rtk_softc *sc = device_private(dev);
        uint32_t data;
        int i;

        CSR_WRITE_4(sc, RTK_PHYAR, (reg << 16) |
            (val & RTK_PHYAR_PHYDATA) | RTK_PHYAR_BUSY);
        DELAY(1000);

        for (i = 0; i < RTK_TIMEOUT; i++) {
                data = CSR_READ_4(sc, RTK_PHYAR);
                if (!(data & RTK_PHYAR_BUSY))
                        break;
                DELAY(100);
        }

        if (i == RTK_TIMEOUT) {
                printf("%s: PHY write reg %x <- %hx failed\n",
                    device_xname(sc->sc_dev), reg, val);
                return ETIMEDOUT;
        }

        return 0;
}

static int
re_miibus_readreg(device_t dev, int phy, int reg, uint16_t *val)
{
        struct rtk_softc *sc = device_private(dev);
        uint16_t re8139_reg = 0;
        int rv = 0;

        if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
                rv = re_gmii_readreg(dev, phy, reg, val);
                return rv;
        }

        /* Pretend the internal PHY is only at address 0 */
        if (phy) {
                return -1;
        }
        switch (reg) {
        case MII_BMCR:
                re8139_reg = RTK_BMCR;
                break;
        case MII_BMSR:
                re8139_reg = RTK_BMSR;
                break;
        case MII_ANAR:
                re8139_reg = RTK_ANAR;
                break;
        case MII_ANER:
                re8139_reg = RTK_ANER;
                break;
        case MII_ANLPAR:
                re8139_reg = RTK_LPAR;
                break;
        case MII_PHYIDR1:
        case MII_PHYIDR2:
                *val = 0;
                return 0;
        /*
         * Allow the rlphy driver to read the media status
         * register. If we have a link partner which does not
         * support NWAY, this is the register which will tell
         * us the results of parallel detection.
         */
        case RTK_MEDIASTAT:
                *val = CSR_READ_1(sc, RTK_MEDIASTAT);
                return 0;
        default:
                printf("%s: bad phy register\n", device_xname(sc->sc_dev));
                return -1;
        }
        *val = CSR_READ_2(sc, re8139_reg);
        if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0 && re8139_reg == RTK_BMCR) {
                /* 8139C+ has different bit layout. */
                *val &= ~(BMCR_LOOP | BMCR_ISO);
        }
        return 0;
}

static int
re_miibus_writereg(device_t dev, int phy, int reg, uint16_t val)
{
        struct rtk_softc *sc = device_private(dev);
        uint16_t re8139_reg = 0;
        int rv;

        if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
                rv = re_gmii_writereg(dev, phy, reg, val);
                return rv;
        }

        /* Pretend the internal PHY is only at address 0 */
        if (phy) {
                return -1;
        }
        switch (reg) {
        case MII_BMCR:
                re8139_reg = RTK_BMCR;
                if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0) {
                        /* 8139C+ has different bit layout. */
                        val &= ~(BMCR_LOOP | BMCR_ISO);
                }
                break;
        case MII_BMSR:
                re8139_reg = RTK_BMSR;
                break;
        case MII_ANAR:
                re8139_reg = RTK_ANAR;
                break;
        case MII_ANER:
                re8139_reg = RTK_ANER;
                break;
        case MII_ANLPAR:
                re8139_reg = RTK_LPAR;
                break;
        case MII_PHYIDR1:
        case MII_PHYIDR2:
                return 0;
                break;
        default:
                printf("%s: bad phy register\n", device_xname(sc->sc_dev));
                return -1;
        }
        CSR_WRITE_2(sc, re8139_reg, val);
        return 0;
}

static void
re_miibus_statchg(struct ifnet *ifp)
{

        return;
}

static void
re_reset(struct rtk_softc *sc)
{
        int i;

        CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_RESET);

        for (i = 0; i < RTK_TIMEOUT; i++) {
                DELAY(10);
                if ((CSR_READ_1(sc, RTK_COMMAND) & RTK_CMD_RESET) == 0)
                        break;
        }
        if (i == RTK_TIMEOUT)
                printf("%s: reset never completed!\n",
                    device_xname(sc->sc_dev));

        /*
         * NB: Realtek-supplied FreeBSD driver does this only for MACFG_3,
         *     but also says "Rtl8169s sigle chip detected".
         */
        if ((sc->sc_quirk & RTKQ_MACLDPS) != 0)
                CSR_WRITE_1(sc, RTK_LDPS, 1);

}

/*
 * The following routine is designed to test for a defect on some
 * 32-bit 8169 cards. Some of these NICs have the REQ64# and ACK64#
 * lines connected to the bus, however for a 32-bit only card, they
 * should be pulled high. The result of this defect is that the
 * NIC will not work right if you plug it into a 64-bit slot: DMA
 * operations will be done with 64-bit transfers, which will fail
 * because the 64-bit data lines aren't connected.
 *
 * There's no way to work around this (short of talking a soldering
 * iron to the board), however we can detect it. The method we use
 * here is to put the NIC into digital loopback mode, set the receiver
 * to promiscuous mode, and then try to send a frame. We then compare
 * the frame data we sent to what was received. If the data matches,
 * then the NIC is working correctly, otherwise we know the user has
 * a defective NIC which has been mistakenly plugged into a 64-bit PCI
 * slot. In the latter case, there's no way the NIC can work correctly,
 * so we print out a message on the console and abort the device attach.
 */

int
re_diag(struct rtk_softc *sc)
{
        struct ifnet *ifp = &sc->ethercom.ec_if;
        struct mbuf *m0;
        struct ether_header *eh;
        struct re_rxsoft *rxs;
        struct re_desc *cur_rx;
        bus_dmamap_t dmamap;
        uint16_t status;
        uint32_t rxstat;
        int total_len, i, error = 0;
        static const uint8_t dst[] = { 0x00, 'h', 'e', 'l', 'l', 'o' };
        static const uint8_t src[] = { 0x00, 'w', 'o', 'r', 'l', 'd' };

        RE_LOCK(sc);

        /* Allocate a single mbuf */

        MGETHDR(m0, M_DONTWAIT, MT_DATA);
        if (m0 == NULL) {
                RE_UNLOCK(sc);
                return ENOBUFS;
        }

        /*
         * Initialize the NIC in test mode. This sets the chip up
         * so that it can send and receive frames, but performs the
         * following special functions:
         * - Puts receiver in promiscuous mode
         * - Enables digital loopback mode
         * - Leaves interrupts turned off
         */

        ifp->if_flags |= IFF_PROMISC;
        sc->re_testmode = 1;
        re_init_locked(sc);
        re_stop_locked(sc, 0);
        DELAY(100000);
        re_init_locked(sc);

        /* Put some data in the mbuf */

        eh = mtod(m0, struct ether_header *);
        memcpy(eh->ether_dhost, &dst, ETHER_ADDR_LEN);
        memcpy(eh->ether_shost, &src, ETHER_ADDR_LEN);
        eh->ether_type = htons(ETHERTYPE_IP);
        m0->m_pkthdr.len = m0->m_len = ETHER_MIN_LEN - ETHER_CRC_LEN;

        /*
         * Queue the packet, start transmission.
         */

        CSR_WRITE_2(sc, RTK_ISR, 0xFFFF);
        IF_ENQUEUE(&ifp->if_snd, m0);
        re_start_locked(sc);
        m0 = NULL;

        /* Wait for it to propagate through the chip */

        DELAY(100000);
        for (i = 0; i < RTK_TIMEOUT; i++) {
                status = CSR_READ_2(sc, RTK_ISR);
                if ((status & (RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_RX_OK)) ==
                    (RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_RX_OK))
                        break;
                DELAY(10);
        }
        if (i == RTK_TIMEOUT) {
                aprint_error_dev(sc->sc_dev,
                    "diagnostic failed, failed to receive packet "
                    "in loopback mode\n");
                error = EIO;
                goto done;
        }

        /*
         * The packet should have been dumped into the first
         * entry in the RX DMA ring. Grab it from there.
         */

        rxs = &sc->re_ldata.re_rxsoft[0];
        dmamap = rxs->rxs_dmamap;
        bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
            BUS_DMASYNC_POSTREAD);
        bus_dmamap_unload(sc->sc_dmat, dmamap);

        m0 = rxs->rxs_mbuf;
        rxs->rxs_mbuf = NULL;
        eh = mtod(m0, struct ether_header *);

        RE_RXDESCSYNC(sc, 0, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
        cur_rx = &sc->re_ldata.re_rx_list[0];
        rxstat = le32toh(cur_rx->re_cmdstat);
        total_len = rxstat & sc->re_rxlenmask;

        if (total_len != ETHER_MIN_LEN) {
                aprint_error_dev(sc->sc_dev,
                    "diagnostic failed, received short packet\n");
                error = EIO;
                goto done;
        }

        /* Test that the received packet data matches what we sent. */

        if (memcmp(&eh->ether_dhost, &dst, ETHER_ADDR_LEN) ||
            memcmp(&eh->ether_shost, &src, ETHER_ADDR_LEN) ||
            ntohs(eh->ether_type) != ETHERTYPE_IP) {
                aprint_error_dev(sc->sc_dev, "WARNING, DMA FAILURE!\n"
                    "expected TX data: %s/%s/0x%x\n"
                    "received RX data: %s/%s/0x%x\n"
                    "You may have a defective 32-bit NIC plugged "
                    "into a 64-bit PCI slot.\n"
                    "Please re-install the NIC in a 32-bit slot "
                    "for proper operation.\n"
                    "Read the re(4) man page for more details.\n" ,
                    ether_sprintf(dst),  ether_sprintf(src), ETHERTYPE_IP,
                    ether_sprintf(eh->ether_dhost),
                    ether_sprintf(eh->ether_shost), ntohs(eh->ether_type));
                error = EIO;
        }

 done:
        /* Turn interface off, release resources */

        sc->re_testmode = 0;
        ifp->if_flags &= ~IFF_PROMISC;
        re_stop_locked(sc, 0);
        m_freem(m0);

        RE_UNLOCK(sc);

        return error;
}


/*
 * Attach the interface. Allocate softc structures, do ifmedia
 * setup and ethernet/BPF attach.
 */
void
re_attach(struct rtk_softc *sc)
{
        uint8_t eaddr[ETHER_ADDR_LEN];
        struct ifnet *ifp;
        struct mii_data *mii = &sc->mii;
        int error = 0, i;
        const struct re_revision *rr;
        const char *re_name = NULL;
        uint32_t maclo, machi;

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NET);

        if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
                /* Revision of 8169/8169S/8110s in bits 30..26, 23 */
                sc->sc_hwrev = CSR_READ_4(sc, RTK_TXCFG) & RTK_TXCFG_HWREV;

                for (rr = re_revisions; rr->re_name != NULL; rr++) {
                        if (rr->re_chipid == sc->sc_hwrev)
                                re_name = rr->re_name;
                }

                if (re_name == NULL)
                        aprint_normal_dev(sc->sc_dev,
                            "unknown ASIC (0x%04x)\n", sc->sc_hwrev >> 16);
                else
                        aprint_normal_dev(sc->sc_dev,
                            "%s (0x%04x)\n", re_name, sc->sc_hwrev >> 16);

                switch (sc->sc_hwrev) {
                case RTK_HWREV_8169:
                        sc->sc_quirk |= RTKQ_8169NONS;
                        break;
                case RTK_HWREV_8169S:
                case RTK_HWREV_8110S:
                case RTK_HWREV_8169_8110SB:
                case RTK_HWREV_8169_8110SBL:
                case RTK_HWREV_8169_8110SC:
                        sc->sc_quirk |= RTKQ_MACLDPS;
                        break;
                case RTK_HWREV_8168B_SPIN1:
                case RTK_HWREV_8168B_SPIN2:
                case RTK_HWREV_8168B_SPIN3:
                        sc->sc_quirk |= RTKQ_MACSTAT;
                        break;
                case RTK_HWREV_8168C:
                case RTK_HWREV_8168C_SPIN2:
                case RTK_HWREV_8168CP:
                case RTK_HWREV_8168D:
                case RTK_HWREV_8168DP:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP;
                        /*
                         * From FreeBSD driver:
                         *
                         * These (8168/8111) controllers support jumbo frame
                         * but it seems that enabling it requires touching
                         * additional magic registers. Depending on MAC
                         * revisions some controllers need to disable
                         * checksum offload. So disable jumbo frame until
                         * I have better idea what it really requires to
                         * make it support.
                         * RTL8168C/CP : supports up to 6KB jumbo frame.
                         * RTL8111C/CP : supports up to 9KB jumbo frame.
                         */
                        sc->sc_quirk |= RTKQ_NOJUMBO;
                        break;
                case RTK_HWREV_8168E:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_PHYWAKE_PM |
                            RTKQ_NOJUMBO;
                        break;
                case RTK_HWREV_8168E_VL:
                case RTK_HWREV_8168F:
                        sc->sc_quirk |= RTKQ_EARLYOFF;
                        /*FALLTHROUGH*/
                case RTK_HWREV_8411:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO;
                        break;
                case RTK_HWREV_8168EP:
                case RTK_HWREV_8168FP:
                case RTK_HWREV_8168G:
                case RTK_HWREV_8168GU:
                case RTK_HWREV_8168H:
                case RTK_HWREV_8411B:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO | 
                            RTKQ_RXDV_GATED | RTKQ_TXRXEN_LATER;
                        break;
                case RTK_HWREV_8100E:
                case RTK_HWREV_8100E_SPIN2:
                case RTK_HWREV_8101E:
                        sc->sc_quirk |= RTKQ_NOJUMBO;
                        break;
                case RTK_HWREV_8102E:
                case RTK_HWREV_8102EL:
                case RTK_HWREV_8102EL_SPIN1:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP | RTKQ_NOJUMBO;
                        break;
                case RTK_HWREV_8103E:
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD |
                            RTKQ_MACSTAT | RTKQ_CMDSTOP;
                        break;
                case RTK_HWREV_8401E:
                case RTK_HWREV_8105E:
                case RTK_HWREV_8105E_SPIN1: /* XXX */
                case RTK_HWREV_8106E:
                        sc->sc_quirk |= RTKQ_PHYWAKE_PM |
                            RTKQ_DESCV2 | RTKQ_NOEECMD | RTKQ_MACSTAT |
                            RTKQ_CMDSTOP;
                        break;
                case RTK_HWREV_8402:
                        sc->sc_quirk |= RTKQ_PHYWAKE_PM |
                            RTKQ_DESCV2 | RTKQ_NOEECMD | RTKQ_MACSTAT |
                            RTKQ_CMDSTOP; /* CMDSTOP_WAIT_TXQ */
                        break;
                default:
                        /* assume the latest features */
                        sc->sc_quirk |= RTKQ_DESCV2 | RTKQ_NOEECMD;
                        sc->sc_quirk |= RTKQ_NOJUMBO;
                }

                /* Set RX length mask */
                sc->re_rxlenmask = RE_RDESC_STAT_GFRAGLEN;
                sc->re_ldata.re_tx_desc_cnt = RE_TX_DESC_CNT_8169;
                sc->re_ldata.re_rx_desc_cnt = RE_RX_DESC_CNT_8169;
        } else {
                sc->sc_quirk |= RTKQ_NOJUMBO;

                /* Set RX length mask */
                sc->re_rxlenmask = RE_RDESC_STAT_FRAGLEN;
                sc->re_ldata.re_tx_desc_cnt = RE_TX_DESC_CNT_8139;
                sc->re_ldata.re_rx_desc_cnt = RE_RX_DESC_CNT_8139;
        }

        /* Reset the adapter. */
        re_reset(sc);

        /*
         * RTL81x9 chips automatically read EEPROM to init MAC address,
         * and some NAS override its MAC address per own configuration,
         * so no need to explicitly read EEPROM and set ID registers.
         */
#ifdef RE_USE_EECMD
        if ((sc->sc_quirk & RTKQ_NOEECMD) != 0) {
                /*
                 * Get station address from ID registers.
                 */
                for (i = 0; i < ETHER_ADDR_LEN; i++)
                        eaddr[i] = CSR_READ_1(sc, RTK_IDR0 + i);
        } else {
                uint16_t val;
                int addr_len;

                /*
                 * Get EEPROM command address length to fetch station address.
                 */
                if (rtk_read_eeprom(sc, RTK_EE_ID, RTK_EEADDR_LEN1) == 0x8129)
                        addr_len = RTK_EEADDR_LEN1;
                else
                        addr_len = RTK_EEADDR_LEN0;

                /*
                 * Get station address from the EEPROM.
                 */
                for (i = 0; i < ETHER_ADDR_LEN / 2; i++) {
                        val = rtk_read_eeprom(sc, RTK_EE_EADDR0 + i, addr_len);
                        eaddr[(i * 2) + 0] = val & 0xff;
                        eaddr[(i * 2) + 1] = val >> 8;
                }
        }
#else
        /*
         * Get station address from ID registers.
         */
        for (i = 0; i < ETHER_ADDR_LEN; i++)
                eaddr[i] = CSR_READ_1(sc, RTK_IDR0 + i);
#endif
        /* 
         * Some chips don't provide a viable MAC address, e.g., what's present
         * on the NanoPi R4S (non-EE version).
         */
        if (ETHER_IS_ZERO(eaddr)) {
                maclo = 0x00f2 | (cprng_strong32() & 0xffff0000);
                machi = cprng_strong32() & 0xffff;
                eaddr[0] = maclo & 0xff;
                eaddr[1] = (maclo >> 8) & 0xff;
                eaddr[2] = (maclo >> 16) & 0xff;
                eaddr[3] = (maclo >> 24) & 0xff;
                eaddr[4] = machi & 0xff;
                eaddr[5] = (machi >> 8) & 0xff;
        }

        /* Take PHY out of power down mode. */
        if ((sc->sc_quirk & RTKQ_PHYWAKE_PM) != 0)
                CSR_WRITE_1(sc, RTK_PMCH, CSR_READ_1(sc, RTK_PMCH) | 0x80);

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

#if 0
        if (sc->re_ldata.re_tx_desc_cnt >
            PAGE_SIZE / sizeof(struct re_desc)) {
                sc->re_ldata.re_tx_desc_cnt =
                    PAGE_SIZE / sizeof(struct re_desc);
        }
#endif

        aprint_verbose_dev(sc->sc_dev, "using %d tx descriptors\n",
            sc->re_ldata.re_tx_desc_cnt);
        KASSERT(RE_NEXT_TX_DESC(sc, RE_TX_DESC_CNT(sc) - 1) == 0);

        /* Allocate DMA'able memory for the TX ring */
        if ((error = bus_dmamem_alloc(sc->sc_dmat, RE_TX_LIST_SZ(sc),
            RE_RING_ALIGN, 0, &sc->re_ldata.re_tx_listseg, 1,
            &sc->re_ldata.re_tx_listnseg, BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't allocate tx listseg, error = %d\n", error);
                goto fail_0;
        }

        /* Load the map for the TX ring. */
        if ((error = bus_dmamem_map(sc->sc_dmat, &sc->re_ldata.re_tx_listseg,
            sc->re_ldata.re_tx_listnseg, RE_TX_LIST_SZ(sc),
            (void **)&sc->re_ldata.re_tx_list,
            BUS_DMA_COHERENT | BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't map tx list, error = %d\n", error);
                goto fail_1;
        }
        memset(sc->re_ldata.re_tx_list, 0, RE_TX_LIST_SZ(sc));

        if ((error = bus_dmamap_create(sc->sc_dmat, RE_TX_LIST_SZ(sc), 1,
            RE_TX_LIST_SZ(sc), 0, 0,
            &sc->re_ldata.re_tx_list_map)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't create tx list map, error = %d\n", error);
                goto fail_2;
        }


        if ((error = bus_dmamap_load(sc->sc_dmat,
            sc->re_ldata.re_tx_list_map, sc->re_ldata.re_tx_list,
            RE_TX_LIST_SZ(sc), NULL, BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't load tx list, error = %d\n", error);
                goto fail_3;
        }

        /* Create DMA maps for TX buffers */
        for (i = 0; i < RE_TX_QLEN(sc); i++) {
                error = bus_dmamap_create(sc->sc_dmat,
                    round_page(IP_MAXPACKET),
                    RE_TX_DESC_CNT(sc), RE_TDESC_CMD_FRAGLEN,
                    0, 0, &sc->re_ldata.re_txq[i].txq_dmamap);
                if (error) {
                        aprint_error_dev(sc->sc_dev,
                            "can't create DMA map for TX\n");
                        goto fail_4;
                }
        }

        /* Allocate DMA'able memory for the RX ring */
        /* XXX see also a comment about RE_RX_DMAMEM_SZ in rtl81x9var.h */
        if ((error = bus_dmamem_alloc(sc->sc_dmat,
            RE_RX_DMAMEM_SZ, RE_RING_ALIGN, 0, &sc->re_ldata.re_rx_listseg, 1,
            &sc->re_ldata.re_rx_listnseg, BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't allocate rx listseg, error = %d\n", error);
                goto fail_4;
        }

        /* Load the map for the RX ring. */
        if ((error = bus_dmamem_map(sc->sc_dmat, &sc->re_ldata.re_rx_listseg,
            sc->re_ldata.re_rx_listnseg, RE_RX_DMAMEM_SZ,
            (void **)&sc->re_ldata.re_rx_list,
            BUS_DMA_COHERENT | BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't map rx list, error = %d\n", error);
                goto fail_5;
        }
        memset(sc->re_ldata.re_rx_list, 0, RE_RX_DMAMEM_SZ);

        if ((error = bus_dmamap_create(sc->sc_dmat,
            RE_RX_DMAMEM_SZ, 1, RE_RX_DMAMEM_SZ, 0, 0,
            &sc->re_ldata.re_rx_list_map)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't create rx list map, error = %d\n", error);
                goto fail_6;
        }

        if ((error = bus_dmamap_load(sc->sc_dmat,
            sc->re_ldata.re_rx_list_map, sc->re_ldata.re_rx_list,
            RE_RX_DMAMEM_SZ, NULL, BUS_DMA_NOWAIT)) != 0) {
                aprint_error_dev(sc->sc_dev,
                    "can't load rx list, error = %d\n", error);
                goto fail_7;
        }

        /* Create DMA maps for RX buffers */
        for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
                error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES,
                    0, 0, &sc->re_ldata.re_rxsoft[i].rxs_dmamap);
                if (error) {
                        aprint_error_dev(sc->sc_dev,
                            "can't create DMA map for RX\n");
                        goto fail_8;
                }
        }

        /*
         * Record interface as attached. From here, we should not fail.
         */
        sc->sc_flags |= RTK_ATTACHED;

        ifp = &sc->ethercom.ec_if;
        ifp->if_softc = sc;
        strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
        ifp->if_mtu = ETHERMTU;
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_extflags = IFEF_MPSAFE; 
        ifp->if_ioctl = re_ioctl;
        sc->ethercom.ec_capabilities |=
            ETHERCAP_VLAN_MTU | ETHERCAP_VLAN_HWTAGGING;
        ifp->if_start = re_start;
        ifp->if_stop = re_stop;

        sc->sc_if_flags = ifp->if_flags;

        /*
         * IFCAP_CSUM_IPv4_Tx on re(4) is broken for small packets,
         * so we have a workaround to handle the bug by padding
         * such packets manually.
         */
        ifp->if_capabilities |=
            IFCAP_CSUM_IPv4_Tx | IFCAP_CSUM_IPv4_Rx |
            IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv4_Rx |
            IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv4_Rx |
            IFCAP_TSOv4;

        ifp->if_watchdog = re_watchdog;
        ifp->if_init = re_init;
        ifp->if_capenable = ifp->if_capabilities;
        IFQ_SET_MAXLEN(&ifp->if_snd, RE_IFQ_MAXLEN);
        IFQ_SET_READY(&ifp->if_snd);

        callout_init(&sc->rtk_tick_ch, CALLOUT_MPSAFE);
        callout_setfunc(&sc->rtk_tick_ch, re_tick, sc);

        /* Do MII setup */
        mii->mii_ifp = ifp;
        mii->mii_readreg = re_miibus_readreg;
        mii->mii_writereg = re_miibus_writereg;
        mii->mii_statchg = re_miibus_statchg;
        sc->ethercom.ec_mii = mii;
        ifmedia_init(&mii->mii_media, IFM_IMASK, ether_mediachange,
            ether_mediastatus);
        mii_attach(sc->sc_dev, mii, 0xffffffff, MII_PHY_ANY,
            MII_OFFSET_ANY, 0);
        ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);

        /*
         * Call MI attach routine.
         */
        if_attach(ifp);
        if_deferred_start_init(ifp, NULL);
        ether_ifattach(ifp, eaddr);
        ether_set_ifflags_cb(&sc->ethercom, re_ifflags_cb);

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

        if (pmf_device_register(sc->sc_dev, NULL, NULL))
                pmf_class_network_register(sc->sc_dev, ifp);
        else
                aprint_error_dev(sc->sc_dev,
                    "couldn't establish power handler\n");

        return;

 fail_8:
        /* Destroy DMA maps for RX buffers. */
        for (i = 0; i < RE_RX_DESC_CNT(sc); i++)
                if (sc->re_ldata.re_rxsoft[i].rxs_dmamap != NULL)
                        bus_dmamap_destroy(sc->sc_dmat,
                            sc->re_ldata.re_rxsoft[i].rxs_dmamap);

        /* Free DMA'able memory for the RX ring. */
        bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
 fail_7:
        bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
 fail_6:
        bus_dmamem_unmap(sc->sc_dmat,
            (void *)sc->re_ldata.re_rx_list, RE_RX_DMAMEM_SZ);
 fail_5:
        bus_dmamem_free(sc->sc_dmat,
            &sc->re_ldata.re_rx_listseg, sc->re_ldata.re_rx_listnseg);

 fail_4:
        /* Destroy DMA maps for TX buffers. */
        for (i = 0; i < RE_TX_QLEN(sc); i++)
                if (sc->re_ldata.re_txq[i].txq_dmamap != NULL)
                        bus_dmamap_destroy(sc->sc_dmat,
                            sc->re_ldata.re_txq[i].txq_dmamap);

        /* Free DMA'able memory for the TX ring. */
        bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
 fail_3:
        bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
 fail_2:
        bus_dmamem_unmap(sc->sc_dmat,
            (void *)sc->re_ldata.re_tx_list, RE_TX_LIST_SZ(sc));
 fail_1:
        bus_dmamem_free(sc->sc_dmat,
            &sc->re_ldata.re_tx_listseg, sc->re_ldata.re_tx_listnseg);
 fail_0:
        mutex_destroy(&sc->sc_lock);

        return;
}


/*
 * re_activate:
 *     Handle device activation/deactivation requests.
 */
int
re_activate(device_t self, enum devact act)
{
        struct rtk_softc *sc = device_private(self);

        switch (act) {
        case DVACT_DEACTIVATE:
                if_deactivate(&sc->ethercom.ec_if);
                return 0;
        default:
                return EOPNOTSUPP;
        }
}

/*
 * re_detach:
 *     Detach a rtk interface.
 */
int
re_detach(struct rtk_softc *sc)
{
        struct ifnet *ifp = &sc->ethercom.ec_if;
        int i;

        /*
         * Succeed now if there isn't any work to do.
         */
        if ((sc->sc_flags & RTK_ATTACHED) == 0)
                return 0;

        re_stop(ifp, 1);

        /* Detach all PHYs. */
        mii_detach(&sc->mii, MII_PHY_ANY, MII_OFFSET_ANY);

        rnd_detach_source(&sc->rnd_source);
        ether_ifdetach(ifp);
        if_detach(ifp);

        /* Delete all remaining media. */
        ifmedia_fini(&sc->mii.mii_media);

        /* Destroy DMA maps for RX buffers. */
        for (i = 0; i < RE_RX_DESC_CNT(sc); i++)
                if (sc->re_ldata.re_rxsoft[i].rxs_dmamap != NULL)
                        bus_dmamap_destroy(sc->sc_dmat,
                            sc->re_ldata.re_rxsoft[i].rxs_dmamap);

        /* Free DMA'able memory for the RX ring. */
        bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
        bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_rx_list_map);
        bus_dmamem_unmap(sc->sc_dmat,
            (void *)sc->re_ldata.re_rx_list, RE_RX_DMAMEM_SZ);
        bus_dmamem_free(sc->sc_dmat,
            &sc->re_ldata.re_rx_listseg, sc->re_ldata.re_rx_listnseg);

        /* Destroy DMA maps for TX buffers. */
        for (i = 0; i < RE_TX_QLEN(sc); i++)
                if (sc->re_ldata.re_txq[i].txq_dmamap != NULL)
                        bus_dmamap_destroy(sc->sc_dmat,
                            sc->re_ldata.re_txq[i].txq_dmamap);

        /* Free DMA'able memory for the TX ring. */
        bus_dmamap_unload(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
        bus_dmamap_destroy(sc->sc_dmat, sc->re_ldata.re_tx_list_map);
        bus_dmamem_unmap(sc->sc_dmat,
            (void *)sc->re_ldata.re_tx_list, RE_TX_LIST_SZ(sc));
        bus_dmamem_free(sc->sc_dmat,
            &sc->re_ldata.re_tx_listseg, sc->re_ldata.re_tx_listnseg);

        pmf_device_deregister(sc->sc_dev);

        mutex_destroy(&sc->sc_lock);

        /* we don't want to run again */
        sc->sc_flags &= ~RTK_ATTACHED;

        return 0;
}

/*
 * re_enable:
 *     Enable the RTL81X9 chip.
 */
static int
re_enable(struct rtk_softc *sc)
{

        if (RTK_IS_ENABLED(sc) == 0 && sc->sc_enable != NULL) {
                if ((*sc->sc_enable)(sc) != 0) {
                        printf("%s: device enable failed\n",
                            device_xname(sc->sc_dev));
                        return EIO;
                }
                sc->sc_flags |= RTK_ENABLED;
        }
        return 0;
}

/*
 * re_disable:
 *     Disable the RTL81X9 chip.
 */
static void
re_disable(struct rtk_softc *sc)
{

        if (RTK_IS_ENABLED(sc) && sc->sc_disable != NULL) {
                (*sc->sc_disable)(sc);
                sc->sc_flags &= ~RTK_ENABLED;
        }
}

static int
re_newbuf(struct rtk_softc *sc, int idx, struct mbuf *m)
{
        struct mbuf *n = NULL;
        bus_dmamap_t map;
        struct re_desc *d;
        struct re_rxsoft *rxs;
        uint32_t cmdstat;
        int error;

        if (m == NULL) {
                MGETHDR(n, M_DONTWAIT, MT_DATA);
                if (n == NULL)
                        return ENOBUFS;

                MCLAIM(n, &sc->ethercom.ec_rx_mowner);
                MCLGET(n, M_DONTWAIT);
                if ((n->m_flags & M_EXT) == 0) {
                        m_freem(n);
                        return ENOBUFS;
                }
                m = n;
        } else
                m->m_data = m->m_ext.ext_buf;

        /*
         * Initialize mbuf length fields and fixup
         * alignment so that the frame payload is
         * longword aligned.
         */
        m->m_len = m->m_pkthdr.len = MCLBYTES - RE_ETHER_ALIGN;
        m->m_data += RE_ETHER_ALIGN;

        rxs = &sc->re_ldata.re_rxsoft[idx];
        map = rxs->rxs_dmamap;
        error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
            BUS_DMA_READ|BUS_DMA_NOWAIT);

        if (error)
                goto out;

        bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
            BUS_DMASYNC_PREREAD);

        d = &sc->re_ldata.re_rx_list[idx];
#ifdef DIAGNOSTIC
        RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
        cmdstat = le32toh(d->re_cmdstat);
        RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD);
        if (cmdstat & RE_RDESC_STAT_OWN) {
                panic("%s: tried to map busy RX descriptor",
                    device_xname(sc->sc_dev));
        }
#endif

        rxs->rxs_mbuf = m;

        d->re_vlanctl = 0;
        cmdstat = map->dm_segs[0].ds_len;
        if (idx == (RE_RX_DESC_CNT(sc) - 1))
                cmdstat |= RE_RDESC_CMD_EOR;
        re_set_bufaddr(d, map->dm_segs[0].ds_addr);
        d->re_cmdstat = htole32(cmdstat);
        RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
        cmdstat |= RE_RDESC_CMD_OWN;
        d->re_cmdstat = htole32(cmdstat);
        RE_RXDESCSYNC(sc, idx, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);

        return 0;
 out:
        m_freem(n);
        return ENOMEM;
}

static int
re_tx_list_init(struct rtk_softc *sc)
{
        int i;

        memset(sc->re_ldata.re_tx_list, 0, RE_TX_LIST_SZ(sc));
        for (i = 0; i < RE_TX_QLEN(sc); i++) {
                sc->re_ldata.re_txq[i].txq_mbuf = NULL;
        }

        bus_dmamap_sync(sc->sc_dmat,
            sc->re_ldata.re_tx_list_map, 0,
            sc->re_ldata.re_tx_list_map->dm_mapsize,
            BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
        sc->re_ldata.re_txq_prodidx = 0;
        sc->re_ldata.re_txq_considx = 0;
        sc->re_ldata.re_txq_free = RE_TX_QLEN(sc);
        sc->re_ldata.re_tx_free = RE_TX_DESC_CNT(sc);
        sc->re_ldata.re_tx_nextfree = 0;

        return 0;
}

static int
re_rx_list_init(struct rtk_softc *sc)
{
        int i;

        memset(sc->re_ldata.re_rx_list, 0, RE_RX_LIST_SZ);

        for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
                if (re_newbuf(sc, i, NULL) == ENOBUFS)
                        return ENOBUFS;
        }

        sc->re_ldata.re_rx_prodidx = 0;
        sc->re_head = sc->re_tail = NULL;

        return 0;
}

/*
 * RX handler for C+ and 8169. For the gigE chips, we support
 * the reception of jumbo frames that have been fragmented
 * across multiple 2K mbuf cluster buffers.
 */
static void
re_rxeof(struct rtk_softc *sc)
{
        struct mbuf *m;
        struct ifnet *ifp;
        int i, total_len;
        struct re_desc *cur_rx;
        struct re_rxsoft *rxs;
        uint32_t rxstat, rxvlan;

        RE_ASSERT_LOCKED(sc);

        ifp = &sc->ethercom.ec_if;

        for (i = sc->re_ldata.re_rx_prodidx;; i = RE_NEXT_RX_DESC(sc, i)) {
                cur_rx = &sc->re_ldata.re_rx_list[i];
                RE_RXDESCSYNC(sc, i,
                    BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
                rxstat = le32toh(cur_rx->re_cmdstat);
                rxvlan = le32toh(cur_rx->re_vlanctl);
                RE_RXDESCSYNC(sc, i, BUS_DMASYNC_PREREAD);
                if ((rxstat & RE_RDESC_STAT_OWN) != 0) {
                        break;
                }
                total_len = rxstat & sc->re_rxlenmask;
                rxs = &sc->re_ldata.re_rxsoft[i];
                m = rxs->rxs_mbuf;

                /* Invalidate the RX mbuf and unload its map */

                bus_dmamap_sync(sc->sc_dmat,
                    rxs->rxs_dmamap, 0, rxs->rxs_dmamap->dm_mapsize,
                    BUS_DMASYNC_POSTREAD);
                bus_dmamap_unload(sc->sc_dmat, rxs->rxs_dmamap);

                if ((rxstat & RE_RDESC_STAT_EOF) == 0) {
                        if (re_newbuf(sc, i, NULL) != 0) {
                                /*
                                 * If this is part of a multi-fragment packet,
                                 * discard all the pieces
                                 */
                                if_statinc(ifp, if_ierrors);
                                if (sc->re_head != NULL) {
                                        m_freem(sc->re_head);
                                        sc->re_head = sc->re_tail = NULL;
                                }
                                re_newbuf(sc, i, m);

                                /* XXX needs to drop remaining frags */
                                continue;
                        }
                        m->m_len = MCLBYTES - RE_ETHER_ALIGN;
                        if (sc->re_head == NULL)
                                sc->re_head = sc->re_tail = m;
                        else {
                                m_remove_pkthdr(m);
                                sc->re_tail->m_next = m;
                                sc->re_tail = m;
                        }
                        continue;
                }

                /*
                 * NOTE: for the 8139C+, the frame length field
                 * is always 12 bits in size, but for the gigE chips,
                 * it is 13 bits (since the max RX frame length is 16K).
                 * Unfortunately, all 32 bits in the status word
                 * were already used, so to make room for the extra
                 * length bit, RealTek took out the 'frame alignment
                 * error' bit and shifted the other status bits
                 * over one slot. The OWN, EOR, FS and LS bits are
                 * still in the same places. We have already extracted
                 * the frame length and checked the OWN bit, so rather
                 * than using an alternate bit mapping, we shift the
                 * status bits one space to the right so we can evaluate
                 * them using the 8169 status as though it was in the
                 * same format as that of the 8139C+.
                 */
                if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0)
                        rxstat >>= 1;

                if (__predict_false((rxstat & RE_RDESC_STAT_RXERRSUM) != 0)) {
#ifdef RE_DEBUG
                        printf("%s: RX error (rxstat = 0x%08x)",
                            device_xname(sc->sc_dev), rxstat);
                        if (rxstat & RE_RDESC_STAT_FRALIGN)
                                printf(", frame alignment error");
                        if (rxstat & RE_RDESC_STAT_BUFOFLOW)
                                printf(", out of buffer space");
                        if (rxstat & RE_RDESC_STAT_FIFOOFLOW)
                                printf(", FIFO overrun");
                        if (rxstat & RE_RDESC_STAT_GIANT)
                                printf(", giant packet");
                        if (rxstat & RE_RDESC_STAT_RUNT)
                                printf(", runt packet");
                        if (rxstat & RE_RDESC_STAT_CRCERR)
                                printf(", CRC error");
                        printf("\n");
#endif
                        if_statinc(ifp, if_ierrors);
                        /*
                         * If this is part of a multi-fragment packet,
                         * discard all the pieces.
                         */
                        if (sc->re_head != NULL) {
                                m_freem(sc->re_head);
                                sc->re_head = sc->re_tail = NULL;
                        }
                        re_newbuf(sc, i, m);
                        continue;
                }

                /*
                 * If allocating a replacement mbuf fails,
                 * reload the current one.
                 */

                if (__predict_false(re_newbuf(sc, i, NULL) != 0)) {
                        if_statinc(ifp, if_ierrors);
                        if (sc->re_head != NULL) {
                                m_freem(sc->re_head);
                                sc->re_head = sc->re_tail = NULL;
                        }
                        re_newbuf(sc, i, m);
                        continue;
                }

                if (sc->re_head != NULL) {
                        m->m_len = total_len % (MCLBYTES - RE_ETHER_ALIGN);
                        /*
                         * Special case: if there's 4 bytes or less
                         * in this buffer, the mbuf can be discarded:
                         * the last 4 bytes is the CRC, which we don't
                         * care about anyway.
                         */
                        if (m->m_len <= ETHER_CRC_LEN) {
                                sc->re_tail->m_len -=
                                    (ETHER_CRC_LEN - m->m_len);
                                m_freem(m);
                        } else {
                                m->m_len -= ETHER_CRC_LEN;
                                m_remove_pkthdr(m);
                                sc->re_tail->m_next = m;
                        }
                        m = sc->re_head;
                        sc->re_head = sc->re_tail = NULL;
                        m->m_pkthdr.len = total_len - ETHER_CRC_LEN;
                } else
                        m->m_pkthdr.len = m->m_len =
                            (total_len - ETHER_CRC_LEN);

                m_set_rcvif(m, ifp);

                /* Do RX checksumming */
                if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
                        /* Check IP header checksum */
                        if ((rxstat & RE_RDESC_STAT_PROTOID) != 0) {
                                m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
                                if (rxstat & RE_RDESC_STAT_IPSUMBAD)
                                        m->m_pkthdr.csum_flags |=
                                            M_CSUM_IPv4_BAD;

                                /* Check TCP/UDP checksum */
                                if (RE_TCPPKT(rxstat)) {
                                        m->m_pkthdr.csum_flags |= M_CSUM_TCPv4;
                                        if (rxstat & RE_RDESC_STAT_TCPSUMBAD)
                                                m->m_pkthdr.csum_flags |=
                                                    M_CSUM_TCP_UDP_BAD;
                                } else if (RE_UDPPKT(rxstat)) {
                                        m->m_pkthdr.csum_flags |= M_CSUM_UDPv4;
                                        if (rxstat & RE_RDESC_STAT_UDPSUMBAD) {
                                                /*
                                                 * XXX: 8139C+ thinks UDP csum
                                                 * 0xFFFF is bad, force software
                                                 * calculation.
                                                 */
                                                if (sc->sc_quirk & RTKQ_8139CPLUS)
                                                        m->m_pkthdr.csum_flags
                                                            &= ~M_CSUM_UDPv4;
                                                else
                                                        m->m_pkthdr.csum_flags
                                                            |= M_CSUM_TCP_UDP_BAD;
                                        }
                                }
                        }
                } else {
                        /* Check IPv4 header checksum */
                        if ((rxvlan & RE_RDESC_VLANCTL_IPV4) != 0) {
                                m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
                                if (rxstat & RE_RDESC_STAT_IPSUMBAD)
                                        m->m_pkthdr.csum_flags |=
                                            M_CSUM_IPv4_BAD;

                                /* Check TCPv4/UDPv4 checksum */
                                if (RE_TCPPKT(rxstat)) {
                                        m->m_pkthdr.csum_flags |= M_CSUM_TCPv4;
                                        if (rxstat & RE_RDESC_STAT_TCPSUMBAD)
                                                m->m_pkthdr.csum_flags |=
                                                    M_CSUM_TCP_UDP_BAD;
                                } else if (RE_UDPPKT(rxstat)) {
                                        m->m_pkthdr.csum_flags |= M_CSUM_UDPv4;
                                        if (rxstat & RE_RDESC_STAT_UDPSUMBAD)
                                                m->m_pkthdr.csum_flags |=
                                                    M_CSUM_TCP_UDP_BAD;
                                }
                        }
                        /* XXX Check TCPv6/UDPv6 checksum? */
                }

                if (rxvlan & RE_RDESC_VLANCTL_TAG) {
                        vlan_set_tag(m,
                             bswap16(rxvlan & RE_RDESC_VLANCTL_DATA));
                }
                if_percpuq_enqueue(ifp->if_percpuq, m);
        }

        sc->re_ldata.re_rx_prodidx = i;
}

static void
re_txeof(struct rtk_softc *sc)
{
        struct ifnet *ifp;
        struct re_txq *txq;
        uint32_t txstat;
        int idx, descidx;

        RE_ASSERT_LOCKED(sc);

        ifp = &sc->ethercom.ec_if;

        for (idx = sc->re_ldata.re_txq_considx;
            sc->re_ldata.re_txq_free < RE_TX_QLEN(sc);
            idx = RE_NEXT_TXQ(sc, idx), sc->re_ldata.re_txq_free++) {
                txq = &sc->re_ldata.re_txq[idx];
                KASSERT(txq->txq_mbuf != NULL);

                descidx = txq->txq_descidx;
                RE_TXDESCSYNC(sc, descidx,
                    BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
                txstat =
                    le32toh(sc->re_ldata.re_tx_list[descidx].re_cmdstat);
                RE_TXDESCSYNC(sc, descidx, BUS_DMASYNC_PREREAD);
                KASSERT((txstat & RE_TDESC_CMD_EOF) != 0);
                if (txstat & RE_TDESC_CMD_OWN) {
                        break;
                }
                
                sc->re_ldata.re_tx_free += txq->txq_nsegs;
                KASSERT(sc->re_ldata.re_tx_free <= RE_TX_DESC_CNT(sc));
                bus_dmamap_sync(sc->sc_dmat, txq->txq_dmamap,
                    0, txq->txq_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
                bus_dmamap_unload(sc->sc_dmat, txq->txq_dmamap);
                m_freem(txq->txq_mbuf);
                txq->txq_mbuf = NULL;
                txq->txq_nsegs = -42;

                net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
                if (txstat & (RE_TDESC_STAT_EXCESSCOL | RE_TDESC_STAT_COLCNT))
                        if_statinc_ref(ifp, nsr, if_collisions);
                if (txstat & RE_TDESC_STAT_TXERRSUM)
                        if_statinc_ref(ifp, nsr, if_oerrors);
                else
                        if_statinc_ref(ifp, nsr, if_opackets);
                IF_STAT_PUTREF(ifp);
        }

        sc->re_ldata.re_txq_considx = idx;

        if ((sc->sc_if_flags & IFF_OACTIVE) != 0 &&
            sc->re_ldata.re_txq_free > 0 &&
            sc->re_ldata.re_tx_free >= RE_NTXDESC_RSVD) {
                ifp->if_flags &= ~IFF_OACTIVE;
                sc->sc_if_flags = ifp->if_flags;
        }

        /*
         * If not all descriptors have been released reaped yet,
         * reload the timer so that we will eventually get another
         * interrupt that will cause us to re-enter this routine.
         * This is done in case the transmitter has gone idle.
         */
        if (sc->re_ldata.re_txq_free < RE_TX_QLEN(sc)) {
                if ((sc->sc_quirk & RTKQ_IM_HW) == 0)
                        CSR_WRITE_4(sc, RTK_TIMERCNT, 1);

        } else
                ifp->if_timer = 0;
}

static void
re_tick(void *arg)
{
        struct rtk_softc *sc = arg;

        RE_LOCK(sc);
        re_tick_locked(sc);
        RE_UNLOCK(sc);
}

static void
re_tick_locked(struct rtk_softc *sc)
{
        /* XXX: just return for 8169S/8110S with rev 2 or newer phy */
        mii_tick(&sc->mii);

        callout_schedule(&sc->rtk_tick_ch, hz);
}


int
re_intr(void *arg)
{
        struct rtk_softc *sc = arg;
        struct ifnet *ifp;
        uint16_t status, rndstatus = 0;
        int handled = 0;

        if (!device_has_power(sc->sc_dev))
                return 0;

        ifp = &sc->ethercom.ec_if;

        if ((ifp->if_flags & IFF_UP) == 0)
                return 0;

        const uint16_t status_mask = (sc->sc_quirk & RTKQ_IM_HW) ?
            RTK_INTRS_IM_HW : RTK_INTRS_CPLUS;

        RE_LOCK(sc);

        for (;;) {

                status = CSR_READ_2(sc, RTK_ISR);
                /* If the card has gone away the read returns 0xffff. */
                if (status == 0xffff)
                        break;
                if (status != 0) {
                        handled = 1;
                        CSR_WRITE_2(sc, RTK_ISR, status);
                        rndstatus = status;
                }

                if ((status & status_mask) == 0)
                        break;

                if (status & (RTK_ISR_RX_OK | RTK_ISR_RX_ERR))
                        re_rxeof(sc);

                /*      
                 * Some chips will ignore a second TX request issued
                 * while an existing transmission is in progress. If
                 * the transmitter goes idle but there are still
                 * packets waiting to be sent, we need to restart the
                 * channel here to flush them out. This only seems to
                 * be required with the PCIe devices.
                 */     
                if ((status & (RTK_ISR_TX_OK | RTK_ISR_TX_DESC_UNAVAIL)) &&
                    (sc->sc_quirk & RTKQ_PCIE) != 0) {
                        if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
                                CSR_WRITE_1(sc, RTK_TXSTART, RTK_TXSTART_START);
                        else
                                CSR_WRITE_1(sc, RTK_GTXSTART, RTK_TXSTART_START);
                }

                if (status & (RTK_ISR_TIMEOUT_EXPIRED | RTK_ISR_TX_ERR |
                    RTK_ISR_TX_DESC_UNAVAIL | RTK_ISR_TX_OK))
                        re_txeof(sc);

                if (status & RTK_ISR_SYSTEM_ERR)
                        re_init_locked(sc);

                if (status & RTK_ISR_LINKCHG) {
                        callout_stop(&sc->rtk_tick_ch);
                        re_tick_locked(sc);
                }
        }

        if (handled)
                if_schedule_deferred_start(ifp);

        rnd_add_uint32(&sc->rnd_source, rndstatus);

        RE_UNLOCK(sc);

        return handled;
}



/*
 * Main transmit routine for C+ and gigE NICs.
 */
static void
re_start(struct ifnet *ifp)
{
        struct rtk_softc *sc = ifp->if_softc;

        RE_LOCK(sc);
        re_start_locked(sc);
        RE_UNLOCK(sc);
}

static void
re_start_locked(struct rtk_softc *sc)
{
        struct ifnet *ifp = &sc->ethercom.ec_if;
        struct mbuf *m;
        bus_dmamap_t map;
        struct re_txq *txq;
        struct re_desc *d;
        uint32_t cmdstat, re_flags, vlanctl;
        int ofree, idx, error, nsegs, seg;
        int startdesc, curdesc, lastdesc;
        bool pad;

        RE_ASSERT_LOCKED(sc);

        ofree = sc->re_ldata.re_txq_free;

        for (idx = sc->re_ldata.re_txq_prodidx;; idx = RE_NEXT_TXQ(sc, idx)) {

                if (sc->re_ldata.re_txq_free == 0 ||
                    sc->re_ldata.re_tx_free == 0) {
                        /* no more free slots left */
                        ifp->if_flags |= IFF_OACTIVE;
                        sc->sc_if_flags = ifp->if_flags;
                        break;
                }

                IFQ_POLL(&ifp->if_snd, m);
                if (m == NULL)
                        break;

                /*
                 * Set up checksum offload. Note: checksum offload bits must
                 * appear in all descriptors of a multi-descriptor transmit
                 * attempt. (This is according to testing done with an 8169
                 * chip. I'm not sure if this is a requirement or a bug.)
                 */

                vlanctl = 0;
                if ((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) != 0) {
                        uint32_t segsz = m->m_pkthdr.segsz;

                        if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
                                re_flags = RE_TDESC_CMD_LGSEND |
                                    (segsz << RE_TDESC_CMD_MSSVAL_SHIFT);
                        } else {
                                re_flags = RE_TDESC_CMD_LGSEND_V4;
                                vlanctl |=
                                    (segsz << RE_TDESC_VLANCTL_MSSVAL_SHIFT);
                        }
                } else {
                        /*
                         * set RE_TDESC_CMD_IPCSUM if any checksum offloading
                         * is requested.  otherwise, RE_TDESC_CMD_TCPCSUM/
                         * RE_TDESC_CMD_UDPCSUM doesn't make effects.
                         */
                        re_flags = 0;
                        if ((m->m_pkthdr.csum_flags &
                            (M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4))
                            != 0) {
                                if ((sc->sc_quirk & RTKQ_DESCV2) == 0) {
                                        re_flags |= RE_TDESC_CMD_IPCSUM;
                                        if (m->m_pkthdr.csum_flags &
                                            M_CSUM_TCPv4) {
                                                re_flags |=
                                                    RE_TDESC_CMD_TCPCSUM;
                                        } else if (m->m_pkthdr.csum_flags &
                                            M_CSUM_UDPv4) {
                                                re_flags |=
                                                    RE_TDESC_CMD_UDPCSUM;
                                        }
                                } else {
                                        vlanctl |= RE_TDESC_VLANCTL_IPCSUM;
                                        if (m->m_pkthdr.csum_flags &
                                            M_CSUM_TCPv4) {
                                                vlanctl |=
                                                    RE_TDESC_VLANCTL_TCPCSUM;
                                        } else if (m->m_pkthdr.csum_flags &
                                            M_CSUM_UDPv4) {
                                                vlanctl |=
                                                    RE_TDESC_VLANCTL_UDPCSUM;
                                        }
                                }
                        }
                }

                txq = &sc->re_ldata.re_txq[idx];
                map = txq->txq_dmamap;
                error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
                    BUS_DMA_WRITE|BUS_DMA_NOWAIT);

                if (__predict_false(error)) {
                        /* XXX try to defrag if EFBIG? */
                        printf("%s: can't map mbuf (error %d)\n",
                            device_xname(sc->sc_dev), error);

                        IFQ_DEQUEUE(&ifp->if_snd, m);
                        m_freem(m);
                        if_statinc(ifp, if_oerrors);
                        continue;
                }

                nsegs = map->dm_nsegs;
                pad = false;
                if (__predict_false(m->m_pkthdr.len <= RE_IP4CSUMTX_PADLEN &&
                    (re_flags & RE_TDESC_CMD_IPCSUM) != 0 &&
                    (sc->sc_quirk & RTKQ_DESCV2) == 0)) {
                        pad = true;
                        nsegs++;
                }

                if (nsegs > sc->re_ldata.re_tx_free) {
                        /*
                         * Not enough free descriptors to transmit this packet.
                         */
                        ifp->if_flags |= IFF_OACTIVE;
                        sc->sc_if_flags = ifp->if_flags;
                        bus_dmamap_unload(sc->sc_dmat, map);
                        break;
                }

                IFQ_DEQUEUE(&ifp->if_snd, m);

                /*
                 * Make sure that the caches are synchronized before we
                 * ask the chip to start DMA for the packet data.
                 */
                bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
                    BUS_DMASYNC_PREWRITE);
                /*
                 * Set up hardware VLAN tagging. Note: vlan tag info must
                 * appear in all descriptors of a multi-descriptor
                 * transmission attempt.
                 */
                if (vlan_has_tag(m))
                        vlanctl |= bswap16(vlan_get_tag(m)) |
                            RE_TDESC_VLANCTL_TAG;

                /*
                 * Map the segment array into descriptors.
                 * Note that we set the start-of-frame and
                 * end-of-frame markers for either TX or RX,
                 * but they really only have meaning in the TX case.
                 * (In the RX case, it's the chip that tells us
                 *  where packets begin and end.)
                 * We also keep track of the end of the ring
                 * and set the end-of-ring bits as needed,
                 * and we set the ownership bits in all except
                 * the very first descriptor. (The caller will
                 * set this descriptor later when it start
                 * transmission or reception.)
                 */
                curdesc = startdesc = sc->re_ldata.re_tx_nextfree;
                lastdesc = -1;
                for (seg = 0; seg < map->dm_nsegs;
                    seg++, curdesc = RE_NEXT_TX_DESC(sc, curdesc)) {
                        d = &sc->re_ldata.re_tx_list[curdesc];
#ifdef DIAGNOSTIC
                        RE_TXDESCSYNC(sc, curdesc,
                            BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
                        cmdstat = le32toh(d->re_cmdstat);
                        RE_TXDESCSYNC(sc, curdesc, BUS_DMASYNC_PREREAD);
                        if (cmdstat & RE_TDESC_STAT_OWN) {
                                panic("%s: tried to map busy TX descriptor",
                                    device_xname(sc->sc_dev));
                        }
#endif

                        d->re_vlanctl = htole32(vlanctl);
                        re_set_bufaddr(d, map->dm_segs[seg].ds_addr);
                        cmdstat = re_flags | map->dm_segs[seg].ds_len;
                        if (seg == 0)
                                cmdstat |= RE_TDESC_CMD_SOF;
                        else
                                cmdstat |= RE_TDESC_CMD_OWN;
                        if (curdesc == (RE_TX_DESC_CNT(sc) - 1))
                                cmdstat |= RE_TDESC_CMD_EOR;
                        if (seg == nsegs - 1) {
                                cmdstat |= RE_TDESC_CMD_EOF;
                                lastdesc = curdesc;
                        }
                        d->re_cmdstat = htole32(cmdstat);
                        RE_TXDESCSYNC(sc, curdesc,
                            BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
                }
                if (__predict_false(pad)) {
                        d = &sc->re_ldata.re_tx_list[curdesc];
                        d->re_vlanctl = htole32(vlanctl);
                        re_set_bufaddr(d, RE_TXPADDADDR(sc));
                        cmdstat = re_flags |
                            RE_TDESC_CMD_OWN | RE_TDESC_CMD_EOF |
                            (RE_IP4CSUMTX_PADLEN + 1 - m->m_pkthdr.len);
                        if (curdesc == (RE_TX_DESC_CNT(sc) - 1))
                                cmdstat |= RE_TDESC_CMD_EOR;
                        d->re_cmdstat = htole32(cmdstat);
                        RE_TXDESCSYNC(sc, curdesc,
                            BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
                        lastdesc = curdesc;
                        curdesc = RE_NEXT_TX_DESC(sc, curdesc);
                }
                KASSERT(lastdesc != -1);

                /* Transfer ownership of packet to the chip. */

                sc->re_ldata.re_tx_list[startdesc].re_cmdstat |=
                    htole32(RE_TDESC_CMD_OWN);
                RE_TXDESCSYNC(sc, startdesc,
                    BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);

                /* update info of TX queue and descriptors */
                txq->txq_mbuf = m;
                txq->txq_descidx = lastdesc;
                txq->txq_nsegs = nsegs;

                sc->re_ldata.re_txq_free--;
                sc->re_ldata.re_tx_free -= nsegs;
                sc->re_ldata.re_tx_nextfree = curdesc;

                /*
                 * If there's a BPF listener, bounce a copy of this frame
                 * to him.
                 */
                bpf_mtap(ifp, m, BPF_D_OUT);
        }

        if (sc->re_ldata.re_txq_free < ofree) {
                /*
                 * TX packets are enqueued.
                 */
                sc->re_ldata.re_txq_prodidx = idx;

                /*
                 * Start the transmitter to poll.
                 *
                 * RealTek put the TX poll request register in a different
                 * location on the 8169 gigE chip. I don't know why.
                 */
                if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
                        CSR_WRITE_1(sc, RTK_TXSTART, RTK_TXSTART_START);
                else
                        CSR_WRITE_1(sc, RTK_GTXSTART, RTK_TXSTART_START);

                if ((sc->sc_quirk & RTKQ_IM_HW) == 0) {
                        /*
                         * Use the countdown timer for interrupt moderation.
                         * 'TX done' interrupts are disabled. Instead, we reset
                         * the countdown timer, which will begin counting until
                         * it hits the value in the TIMERINT register, and then
                         * trigger an interrupt. Each time we write to the
                         * TIMERCNT register, the timer count is reset to 0.
                         */
                        CSR_WRITE_4(sc, RTK_TIMERCNT, 1);
                }

                /*
                 * Set a timeout in case the chip goes out to lunch.
                 */
                ifp->if_timer = 5;
        }
}

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

        RE_LOCK(sc);
        error = re_init_locked(sc);
        RE_UNLOCK(sc);

        return error;
}

static int
re_init_locked(struct rtk_softc *sc)
{
        struct ifnet *ifp = &sc->ethercom.ec_if;
        uint32_t rxcfg = 0;
        uint16_t cfg;
        int error;
#ifdef RE_USE_EECMD
        const uint8_t *enaddr;
        uint32_t reg;
#endif

        sc->sc_if_flags = ifp->if_flags;

        if ((error = re_enable(sc)) != 0)
                goto out;

        /*
         * Cancel pending I/O and free all RX/TX buffers.
         */
        re_stop_locked(sc, 0);

        re_reset(sc);

        /*
         * Enable C+ RX and TX mode, as well as VLAN stripping and
         * RX checksum offload. We must configure the C+ register
         * before all others.
         */
        cfg = RE_CPLUSCMD_PCI_MRW;

        /*
         * XXX: For old 8169 set bit 14.
         *      For 8169S/8110S and above, do not set bit 14.
         */
        if ((sc->sc_quirk & RTKQ_8169NONS) != 0)
                cfg |= (0x1 << 14);

        if ((sc->ethercom.ec_capenable & ETHERCAP_VLAN_HWTAGGING) != 0)
                cfg |= RE_CPLUSCMD_VLANSTRIP;
        if ((ifp->if_capenable & (IFCAP_CSUM_IPv4_Rx |
             IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_UDPv4_Rx)) != 0)
                cfg |= RE_CPLUSCMD_RXCSUM_ENB;
        if ((sc->sc_quirk & RTKQ_MACSTAT) != 0) {
                cfg |= RE_CPLUSCMD_MACSTAT_DIS;
                cfg |= RE_CPLUSCMD_TXENB;
        } else
                cfg |= RE_CPLUSCMD_RXENB | RE_CPLUSCMD_TXENB;

        CSR_WRITE_2(sc, RTK_CPLUS_CMD, cfg);

        /* XXX: from Realtek-supplied Linux driver. Wholly undocumented. */
        if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
                if ((sc->sc_quirk & RTKQ_IM_HW) == 0) {
                        CSR_WRITE_2(sc, RTK_IM, 0x0000);
                } else {
                        CSR_WRITE_2(sc, RTK_IM, 0x5151);
                }
        }

        DELAY(10000);

#ifdef RE_USE_EECMD
        /*
         * Init our MAC address.  Even though the chipset
         * documentation doesn't mention it, we need to enter "Config
         * register write enable" mode to modify the ID registers.
         */
        CSR_WRITE_1(sc, RTK_EECMD, RTK_EEMODE_WRITECFG);
        enaddr = CLLADDR(ifp->if_sadl);
        reg = enaddr[0] | (enaddr[1] << 8) |
            (enaddr[2] << 16) | (enaddr[3] << 24);
        CSR_WRITE_4(sc, RTK_IDR0, reg);
        reg = enaddr[4] | (enaddr[5] << 8);
        CSR_WRITE_4(sc, RTK_IDR4, reg);
        CSR_WRITE_1(sc, RTK_EECMD, RTK_EEMODE_OFF);
#endif

        /*
         * For C+ mode, initialize the RX descriptors and mbufs.
         */
        re_rx_list_init(sc);
        re_tx_list_init(sc);

        /*
         * Load the addresses of the RX and TX lists into the chip.
         */
        CSR_WRITE_4(sc, RTK_RXLIST_ADDR_HI,
            RE_ADDR_HI(sc->re_ldata.re_rx_list_map->dm_segs[0].ds_addr));
        CSR_WRITE_4(sc, RTK_RXLIST_ADDR_LO,
            RE_ADDR_LO(sc->re_ldata.re_rx_list_map->dm_segs[0].ds_addr));

        CSR_WRITE_4(sc, RTK_TXLIST_ADDR_HI,
            RE_ADDR_HI(sc->re_ldata.re_tx_list_map->dm_segs[0].ds_addr));
        CSR_WRITE_4(sc, RTK_TXLIST_ADDR_LO,
            RE_ADDR_LO(sc->re_ldata.re_tx_list_map->dm_segs[0].ds_addr));

        if (sc->sc_quirk & RTKQ_RXDV_GATED) {
                CSR_WRITE_4(sc, RTK_MISC,
                    CSR_READ_4(sc, RTK_MISC) & ~RTK_MISC_RXDV_GATED_EN);
        }
                
        /*
         * Enable transmit and receive.
         */
        if ((sc->sc_quirk & RTKQ_TXRXEN_LATER) == 0)
                CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);

        /*
         * Set the initial TX and RX configuration.
         */
        if (sc->re_testmode && (sc->sc_quirk & RTKQ_8169NONS) != 0) {
                /* test mode is needed only for old 8169 */
                CSR_WRITE_4(sc, RTK_TXCFG,
                    RE_TXCFG_CONFIG | RTK_LOOPTEST_ON);
        } else
                CSR_WRITE_4(sc, RTK_TXCFG, RE_TXCFG_CONFIG);

        CSR_WRITE_1(sc, RTK_EARLY_TX_THRESH, 16);

        CSR_WRITE_4(sc, RTK_RXCFG, RE_RXCFG_CONFIG);

        /* Set the individual bit to receive frames for this host only. */
        rxcfg = CSR_READ_4(sc, RTK_RXCFG);
        rxcfg |= RTK_RXCFG_RX_INDIV;
        if (sc->sc_quirk & RTKQ_EARLYOFF)
                rxcfg |= RTK_RXCFG_EARLYOFF;
        else if (sc->sc_quirk & RTKQ_RXDV_GATED)
                rxcfg |= RTK_RXCFG_EARLYOFFV2;

        /* If we want promiscuous mode, set the allframes bit. */
        if (ifp->if_flags & IFF_PROMISC)
                rxcfg |= RTK_RXCFG_RX_ALLPHYS;
        else
                rxcfg &= ~RTK_RXCFG_RX_ALLPHYS;
        CSR_WRITE_4(sc, RTK_RXCFG, rxcfg);

        /*
         * Set capture broadcast bit to capture broadcast frames.
         */
        if (ifp->if_flags & IFF_BROADCAST)
                rxcfg |= RTK_RXCFG_RX_BROAD;
        else
                rxcfg &= ~RTK_RXCFG_RX_BROAD;
        CSR_WRITE_4(sc, RTK_RXCFG, rxcfg);

        /*
         * Program the multicast filter, if necessary.
         */
        rtk_setmulti(sc);

        /*
         * some chips require to enable TX/RX *AFTER* TX/RX configuration
         */
        if ((sc->sc_quirk & RTKQ_TXRXEN_LATER) != 0)
                CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);

        /*
         * Enable interrupts.
         */
        if (sc->re_testmode)
                CSR_WRITE_2(sc, RTK_IMR, 0);
        else if ((sc->sc_quirk & RTKQ_IM_HW) != 0)
                CSR_WRITE_2(sc, RTK_IMR, RTK_INTRS_IM_HW);
        else
                CSR_WRITE_2(sc, RTK_IMR, RTK_INTRS_CPLUS);

        /* Start RX/TX process. */
        CSR_WRITE_4(sc, RTK_MISSEDPKT, 0);
#ifdef notdef
        /* Enable receiver and transmitter. */
        CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_TX_ENB | RTK_CMD_RX_ENB);
#endif

        /*
         * Initialize the timer interrupt register so that
         * a timer interrupt will be generated once the timer
         * reaches a certain number of ticks. The timer is
         * reloaded on each transmit. This gives us TX interrupt
         * moderation, which dramatically improves TX frame rate.
         */

        unsigned defer;         /* timer interval / ns */
        unsigned period;        /* busclock period / ns */

        /*
         * Maximum frame rate
         * 1500 byte PDU -> 81274 Hz
         *   46 byte PDU -> 1488096 Hz
         *
         * Deferring interrupts by up to 128us needs descriptors for
         * 1500 byte PDU -> 10.4 frames
         *   46 byte PDU -> 190.4 frames
         *
         */
        defer = 128000;

        if ((sc->sc_quirk & RTKQ_IM_HW) != 0) {
                period = 1;
                defer = 0;
        } else if ((sc->sc_quirk & RTKQ_PCIE) != 0) {
                period = 8;
        } else {
                switch (CSR_READ_1(sc, RTK_CFG2_BUSFREQ) & 0x7) {
                case RTK_BUSFREQ_33MHZ:
                        period = 30;
                        break;
                case RTK_BUSFREQ_66MHZ:
                        period = 15;
                        break;
                default:
                        /* lowest possible clock */
                        period = 60;
                        break;
                }
        }

        /* Timer Interrupt register address varies */
        uint16_t re8139_reg;
        if ((sc->sc_quirk & RTKQ_8139CPLUS) != 0)
                re8139_reg = RTK_TIMERINT;
        else
                re8139_reg = RTK_TIMERINT_8169;
        CSR_WRITE_4(sc, re8139_reg, defer / period);

        if ((sc->sc_quirk & RTKQ_8139CPLUS) == 0) {
                /*
                 * For 8169 gigE NICs, set the max allowed RX packet
                 * size so we can receive jumbo frames.
                 */
                CSR_WRITE_2(sc, RTK_MAXRXPKTLEN, 16383);
        }

        if (sc->re_testmode)
                return 0;

        CSR_WRITE_1(sc, RTK_CFG1, RTK_CFG1_DRVLOAD);

        ifp->if_flags |= IFF_RUNNING;
        ifp->if_flags &= ~IFF_OACTIVE;
        sc->sc_if_flags = ifp->if_flags;

        callout_schedule(&sc->rtk_tick_ch, hz);

 out:
        if (error) {
                ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
                sc->sc_if_flags = ifp->if_flags;
                ifp->if_timer = 0;
                printf("%s: interface not running\n",
                    device_xname(sc->sc_dev));
        }

        return error;
}

static int
re_ioctl(struct ifnet *ifp, u_long command, void *data)
{
        struct rtk_softc *sc = ifp->if_softc;
        struct ifreq *ifr = data;
        int error = 0, s;

        switch (command) {
        case SIOCSIFMTU:
                /*
                 * Disable jumbo frames if it's not supported.
                 */
                if ((sc->sc_quirk & RTKQ_NOJUMBO) != 0 &&
                    ifr->ifr_mtu > ETHERMTU) {
                        error = EINVAL;
                        break;
                }

                if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > ETHERMTU_JUMBO)
                        error = EINVAL;
                else if ((error = ifioctl_common(ifp, command, data)) ==
                    ENETRESET)
                        error = 0;
                break;
        default:
                s = splnet();
                error = ether_ioctl(ifp, command, data);
                splx(s);

                if (error != ENETRESET)
                        break;

                error = 0;

                if (command == SIOCSIFCAP)
                        error = (*ifp->if_init)(ifp);
                else if (command == SIOCADDMULTI || command == SIOCADDMULTI) {
                        RE_LOCK(sc);
                        if (sc->sc_if_flags & IFF_RUNNING)
                                rtk_setmulti(sc);
                        RE_UNLOCK(sc);
                }
                break;
        }

        return error;
}

static int
re_ifflags_cb(struct ethercom *ec)
{
        struct ifnet * const ifp = &ec->ec_if;
        struct rtk_softc * const sc = ifp->if_softc;
        int ret = 0;
        u_short change;

        KASSERT(IFNET_LOCKED(ifp));
        RE_LOCK(sc);

        change = ifp->if_flags ^ sc->sc_if_flags;
        sc->sc_if_flags = ifp->if_flags;

        if ((change & ~(IFF_CANTCHANGE | IFF_DEBUG)) != 0) {
                ret = ENETRESET;
        } else if ((change & IFF_PROMISC) != 0) {
                if ((sc->sc_if_flags & IFF_RUNNING) != 0)
                        rtk_setmulti(sc);
        }

        RE_UNLOCK(sc);
        return ret;
}

static void
re_watchdog(struct ifnet *ifp)
{
        struct rtk_softc *sc = ifp->if_softc;

        RE_LOCK(sc);

        printf("%s: watchdog timeout\n", device_xname(sc->sc_dev));
        if_statinc(ifp, if_oerrors);

        re_txeof(sc);
        re_rxeof(sc);

        re_init_locked(sc);

        RE_UNLOCK(sc);
}

/*
 * Stop the adapter and free any mbufs allocated to the
 * RX and TX lists.
 */
static void
re_stop(struct ifnet *ifp, int disable)
{
        struct rtk_softc *sc = ifp->if_softc;

        RE_LOCK(sc);

        re_stop_locked(sc, disable);

        RE_UNLOCK(sc);
}

static void
re_stop_locked(struct rtk_softc *sc, int disable)
{
        struct ifnet *ifp = &sc->ethercom.ec_if;
        int i;

        callout_stop(&sc->rtk_tick_ch);

        mii_down(&sc->mii);

        /*
         * Disable accepting frames to put RX MAC into idle state.
         * Otherwise it's possible to get frames while stop command
         * execution is in progress and controller can DMA the frame
         * to already freed RX buffer during that period.
         */
        CSR_WRITE_4(sc, RTK_RXCFG, CSR_READ_4(sc, RTK_RXCFG) &
            ~(RTK_RXCFG_RX_ALLPHYS | RTK_RXCFG_RX_INDIV | RTK_RXCFG_RX_MULTI |
            RTK_RXCFG_RX_BROAD));

        if (sc->sc_quirk & RTKQ_RXDV_GATED) {
                CSR_WRITE_4(sc, RTK_MISC,
                    CSR_READ_4(sc, RTK_MISC) | RTK_MISC_RXDV_GATED_EN);
        }

        if ((sc->sc_quirk & RTKQ_CMDSTOP) != 0)
                CSR_WRITE_1(sc, RTK_COMMAND, RTK_CMD_STOPREQ | RTK_CMD_TX_ENB |
                    RTK_CMD_RX_ENB);
        else
                CSR_WRITE_1(sc, RTK_COMMAND, 0x00);
        DELAY(1000);
        CSR_WRITE_2(sc, RTK_IMR, 0x0000);
        CSR_WRITE_2(sc, RTK_ISR, 0xFFFF);

        if (sc->re_head != NULL) {
                m_freem(sc->re_head);
                sc->re_head = sc->re_tail = NULL;
        }

        /* Free the TX list buffers. */
        for (i = 0; i < RE_TX_QLEN(sc); i++) {
                if (sc->re_ldata.re_txq[i].txq_mbuf != NULL) {
                        bus_dmamap_unload(sc->sc_dmat,
                            sc->re_ldata.re_txq[i].txq_dmamap);
                        m_freem(sc->re_ldata.re_txq[i].txq_mbuf);
                        sc->re_ldata.re_txq[i].txq_mbuf = NULL;
                        sc->re_ldata.re_txq[i].txq_nsegs = 0;
                }
        }

        /* Free the RX list buffers. */
        for (i = 0; i < RE_RX_DESC_CNT(sc); i++) {
                if (sc->re_ldata.re_rxsoft[i].rxs_mbuf != NULL) {
                        bus_dmamap_unload(sc->sc_dmat,
                            sc->re_ldata.re_rxsoft[i].rxs_dmamap);
                        m_freem(sc->re_ldata.re_rxsoft[i].rxs_mbuf);
                        sc->re_ldata.re_rxsoft[i].rxs_mbuf = NULL;
                }
        }

        if (disable)
                re_disable(sc);

        ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
        ifp->if_timer = 0;
        sc->sc_if_flags = ifp->if_flags;
}