/*      $NetBSD: dp83932.c,v 1.50 2024/06/29 12:11:11 riastradh Exp $   */

/*-
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe.
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/

/*
* Device driver for the National Semiconductor DP83932
* Systems-Oriented Network Interface Controller (SONIC).
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dp83932.c,v 1.50 2024/06/29 12:11:11 riastradh Exp $");


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

#include <sys/rndsource.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>

#include <net/bpf.h>

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

#include <dev/ic/dp83932reg.h>
#include <dev/ic/dp83932var.h>

static void     sonic_start(struct ifnet *);
static void     sonic_watchdog(struct ifnet *);
static int      sonic_ioctl(struct ifnet *, u_long, void *);
static int      sonic_init(struct ifnet *);
static void     sonic_stop(struct ifnet *, int);

static bool     sonic_shutdown(device_t, int);

static void     sonic_reset(struct sonic_softc *);
static void     sonic_rxdrain(struct sonic_softc *);
static int      sonic_add_rxbuf(struct sonic_softc *, int);
static void     sonic_set_filter(struct sonic_softc *);

static uint16_t sonic_txintr(struct sonic_softc *);
static void     sonic_rxintr(struct sonic_softc *);

int     sonic_copy_small = 0;

#define ETHER_PAD_LEN (ETHER_MIN_LEN - ETHER_CRC_LEN)

/*
* sonic_attach:
*
*      Attach a SONIC interface to the system.
*/
void
sonic_attach(struct sonic_softc *sc, const uint8_t *enaddr)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       int i, rseg, error;
       bus_dma_segment_t seg;
       size_t cdatasize;
       uint8_t *nullbuf;

       /*
        * Allocate the control data structures, and create and load the
        * DMA map for it.
        */
       if (sc->sc_32bit)
               cdatasize = sizeof(struct sonic_control_data32);
       else
               cdatasize = sizeof(struct sonic_control_data16);

       if ((error = bus_dmamem_alloc(sc->sc_dmat, cdatasize + ETHER_PAD_LEN,
            PAGE_SIZE, (64 * 1024), &seg, 1, &rseg,
            BUS_DMA_NOWAIT)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to allocate control data, error = %d\n", error);
               goto fail_0;
       }

       if ((error = bus_dmamem_map(sc->sc_dmat, &seg, rseg,
           cdatasize + ETHER_PAD_LEN, (void **) &sc->sc_cdata16,
           BUS_DMA_NOWAIT | BUS_DMA_COHERENT)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to map control data, error = %d\n", error);
               goto fail_1;
       }
       nullbuf = (uint8_t *)sc->sc_cdata16 + cdatasize;
       memset(nullbuf, 0, ETHER_PAD_LEN);

       if ((error = bus_dmamap_create(sc->sc_dmat,
            cdatasize, 1, cdatasize, 0, BUS_DMA_NOWAIT,
            &sc->sc_cddmamap)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to create control data DMA map, error = %d\n",
                   error);
               goto fail_2;
       }

       if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_cddmamap,
            sc->sc_cdata16, cdatasize, NULL, BUS_DMA_NOWAIT)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to load control data DMA map, error = %d\n", error);
               goto fail_3;
       }

       /*
        * Create the transmit buffer DMA maps.
        */
       for (i = 0; i < SONIC_NTXDESC; i++) {
               if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES,
                    SONIC_NTXFRAGS, MCLBYTES, 0, BUS_DMA_NOWAIT,
                    &sc->sc_txsoft[i].ds_dmamap)) != 0) {
                       aprint_error_dev(sc->sc_dev,
                           "unable to create tx DMA map %d, error = %d\n",
                           i, error);
                       goto fail_4;
               }
       }

       /*
        * Create the receive buffer DMA maps.
        */
       for (i = 0; i < SONIC_NRXDESC; i++) {
               if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
                    MCLBYTES, 0, BUS_DMA_NOWAIT,
                    &sc->sc_rxsoft[i].ds_dmamap)) != 0) {
                       aprint_error_dev(sc->sc_dev,
                           "unable to create rx DMA map %d, error = %d\n",
                           i, error);
                       goto fail_5;
               }
               sc->sc_rxsoft[i].ds_mbuf = NULL;
       }

       /*
        * create and map the pad buffer
        */
       if ((error = bus_dmamap_create(sc->sc_dmat, ETHER_PAD_LEN, 1,
           ETHER_PAD_LEN, 0, BUS_DMA_NOWAIT, &sc->sc_nulldmamap)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to create pad buffer DMA map, error = %d\n", error);
               goto fail_5;
       }

       if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_nulldmamap,
           nullbuf, ETHER_PAD_LEN, NULL, BUS_DMA_NOWAIT)) != 0) {
               aprint_error_dev(sc->sc_dev,
                   "unable to load pad buffer DMA map, error = %d\n", error);
               goto fail_6;
       }
       bus_dmamap_sync(sc->sc_dmat, sc->sc_nulldmamap, 0, ETHER_PAD_LEN,
           BUS_DMASYNC_PREWRITE);

       /*
        * Reset the chip to a known state.
        */
       sonic_reset(sc);

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

       strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
       ifp->if_softc = sc;
       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
       ifp->if_ioctl = sonic_ioctl;
       ifp->if_start = sonic_start;
       ifp->if_watchdog = sonic_watchdog;
       ifp->if_init = sonic_init;
       ifp->if_stop = sonic_stop;
       IFQ_SET_READY(&ifp->if_snd);

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

       /*
        * Attach the interface.
        */
       if_attach(ifp);
       if_deferred_start_init(ifp, NULL);
       ether_ifattach(ifp, enaddr);

       rnd_attach_source(&sc->sc_rndsource, ifp->if_xname, RND_TYPE_NET,
           RND_FLAG_DEFAULT);

       /*
        * Make sure the interface is shutdown during reboot.
        */
       if (pmf_device_register1(sc->sc_dev, NULL, NULL, sonic_shutdown))
               pmf_class_network_register(sc->sc_dev, ifp);
       else
               aprint_error_dev(sc->sc_dev,
                   "couldn't establish power handler\n");

       return;

       /*
        * Free any resources we've allocated during the failed attach
        * attempt.  Do this in reverse order and fall through.
        */
fail_6:
       bus_dmamap_destroy(sc->sc_dmat, sc->sc_nulldmamap);
fail_5:
       for (i = 0; i < SONIC_NRXDESC; i++) {
               if (sc->sc_rxsoft[i].ds_dmamap != NULL)
                       bus_dmamap_destroy(sc->sc_dmat,
                           sc->sc_rxsoft[i].ds_dmamap);
       }
fail_4:
       for (i = 0; i < SONIC_NTXDESC; i++) {
               if (sc->sc_txsoft[i].ds_dmamap != NULL)
                       bus_dmamap_destroy(sc->sc_dmat,
                           sc->sc_txsoft[i].ds_dmamap);
       }
       bus_dmamap_unload(sc->sc_dmat, sc->sc_cddmamap);
fail_3:
       bus_dmamap_destroy(sc->sc_dmat, sc->sc_cddmamap);
fail_2:
       bus_dmamem_unmap(sc->sc_dmat, (void *)sc->sc_cdata16, cdatasize);
fail_1:
       bus_dmamem_free(sc->sc_dmat, &seg, rseg);
fail_0:
       return;
}

/*
* sonic_shutdown:
*
*      Make sure the interface is stopped at reboot.
*/
bool
sonic_shutdown(device_t self, int howto)
{
       struct sonic_softc *sc = device_private(self);

       sonic_stop(&sc->sc_ethercom.ec_if, 1);

       return true;
}

/*
* sonic_start:         [ifnet interface function]
*
*      Start packet transmission on the interface.
*/
void
sonic_start(struct ifnet *ifp)
{
       struct sonic_softc *sc = ifp->if_softc;
       struct mbuf *m0, *m;
       struct sonic_tda16 *tda16;
       struct sonic_tda32 *tda32;
       struct sonic_descsoft *ds;
       bus_dmamap_t dmamap;
       int error, olasttx, nexttx, opending, totlen, olseg;
       int seg = 0;    /* XXX: gcc */

       if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING)
               return;

       /*
        * Remember the previous txpending and the current "last txdesc
        * used" index.
        */
       opending = sc->sc_txpending;
       olasttx = sc->sc_txlast;

       /*
        * Loop through the send queue, setting up transmit descriptors
        * until we drain the queue, or use up all available transmit
        * descriptors.  Leave one at the end for sanity's sake.
        */
       while (sc->sc_txpending < (SONIC_NTXDESC - 1)) {
               /*
                * Grab a packet off the queue.
                */
               IFQ_POLL(&ifp->if_snd, m0);
               if (m0 == NULL)
                       break;
               m = NULL;

               /*
                * Get the next available transmit descriptor.
                */
               nexttx = SONIC_NEXTTX(sc->sc_txlast);
               ds = &sc->sc_txsoft[nexttx];
               dmamap = ds->ds_dmamap;

               /*
                * Load the DMA map.  If this fails, the packet either
                * didn't fit in the allotted number of frags, or we were
                * short on resources.  In this case, we'll copy and try
                * again.
                */
               if ((error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0,
                   BUS_DMA_WRITE | BUS_DMA_NOWAIT)) != 0 ||
                   (m0->m_pkthdr.len < ETHER_PAD_LEN &&
                   dmamap->dm_nsegs == SONIC_NTXFRAGS)) {
                       if (error == 0)
                               bus_dmamap_unload(sc->sc_dmat, dmamap);
                       MGETHDR(m, M_DONTWAIT, MT_DATA);
                       if (m == NULL) {
                               printf("%s: unable to allocate Tx mbuf\n",
                                   device_xname(sc->sc_dev));
                               break;
                       }
                       if (m0->m_pkthdr.len > MHLEN) {
                               MCLGET(m, M_DONTWAIT);
                               if ((m->m_flags & M_EXT) == 0) {
                                       printf("%s: unable to allocate Tx "
                                           "cluster\n",
                                           device_xname(sc->sc_dev));
                                       m_freem(m);
                                       break;
                               }
                       }
                       m_copydata(m0, 0, m0->m_pkthdr.len, mtod(m, void *));
                       m->m_pkthdr.len = m->m_len = m0->m_pkthdr.len;
                       error = bus_dmamap_load_mbuf(sc->sc_dmat, dmamap,
                           m, BUS_DMA_WRITE | BUS_DMA_NOWAIT);
                       if (error) {
                               printf("%s: unable to load Tx buffer, "
                                   "error = %d\n", device_xname(sc->sc_dev),
                                   error);
                               m_freem(m);
                               break;
                       }
               }
               IFQ_DEQUEUE(&ifp->if_snd, m0);
               if (m != NULL) {
                       m_freem(m0);
                       m0 = m;
               }

               /*
                * WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET.
                */

               /* Sync the DMA map. */
               bus_dmamap_sync(sc->sc_dmat, dmamap, 0, dmamap->dm_mapsize,
                   BUS_DMASYNC_PREWRITE);

               /*
                * Store a pointer to the packet so we can free it later.
                */
               ds->ds_mbuf = m0;

               /*
                * Initialize the transmit descriptor.
                */
               totlen = 0;
               if (sc->sc_32bit) {
                       tda32 = &sc->sc_tda32[nexttx];
                       for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
                               tda32->tda_frags[seg].frag_ptr1 =
                                   htosonic32(sc,
                                   (dmamap->dm_segs[seg].ds_addr >> 16) &
                                   0xffff);
                               tda32->tda_frags[seg].frag_ptr0 =
                                   htosonic32(sc,
                                   dmamap->dm_segs[seg].ds_addr & 0xffff);
                               tda32->tda_frags[seg].frag_size =
                                   htosonic32(sc, dmamap->dm_segs[seg].ds_len);
                               totlen += dmamap->dm_segs[seg].ds_len;
                       }
                       if (totlen < ETHER_PAD_LEN) {
                               tda32->tda_frags[seg].frag_ptr1 =
                                   htosonic32(sc,
                                   (sc->sc_nulldma >> 16) & 0xffff);
                               tda32->tda_frags[seg].frag_ptr0 =
                                   htosonic32(sc, sc->sc_nulldma & 0xffff);
                               tda32->tda_frags[seg].frag_size =
                                   htosonic32(sc, ETHER_PAD_LEN - totlen);
                               totlen = ETHER_PAD_LEN;
                               seg++;
                       }

                       tda32->tda_status = 0;
                       tda32->tda_pktconfig = 0;
                       tda32->tda_pktsize = htosonic32(sc, totlen);
                       tda32->tda_fragcnt = htosonic32(sc, seg);

                       /* Link it up. */
                       tda32->tda_frags[seg].frag_ptr0 =
                           htosonic32(sc, SONIC_CDTXADDR32(sc,
                           SONIC_NEXTTX(nexttx)) & 0xffff);

                       /* Sync the Tx descriptor. */
                       SONIC_CDTXSYNC32(sc, nexttx,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               } else {
                       tda16 = &sc->sc_tda16[nexttx];
                       for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
                               tda16->tda_frags[seg].frag_ptr1 =
                                   htosonic16(sc,
                                   (dmamap->dm_segs[seg].ds_addr >> 16) &
                                   0xffff);
                               tda16->tda_frags[seg].frag_ptr0 =
                                   htosonic16(sc,
                                   dmamap->dm_segs[seg].ds_addr & 0xffff);
                               tda16->tda_frags[seg].frag_size =
                                   htosonic16(sc, dmamap->dm_segs[seg].ds_len);
                               totlen += dmamap->dm_segs[seg].ds_len;
                       }
                       if (totlen < ETHER_PAD_LEN) {
                               tda16->tda_frags[seg].frag_ptr1 =
                                   htosonic16(sc,
                                   (sc->sc_nulldma >> 16) & 0xffff);
                               tda16->tda_frags[seg].frag_ptr0 =
                                   htosonic16(sc, sc->sc_nulldma & 0xffff);
                               tda16->tda_frags[seg].frag_size =
                                   htosonic16(sc, ETHER_PAD_LEN - totlen);
                               totlen = ETHER_PAD_LEN;
                               seg++;
                       }

                       tda16->tda_status = 0;
                       tda16->tda_pktconfig = 0;
                       tda16->tda_pktsize = htosonic16(sc, totlen);
                       tda16->tda_fragcnt = htosonic16(sc, seg);

                       /* Link it up. */
                       tda16->tda_frags[seg].frag_ptr0 =
                           htosonic16(sc, SONIC_CDTXADDR16(sc,
                           SONIC_NEXTTX(nexttx)) & 0xffff);

                       /* Sync the Tx descriptor. */
                       SONIC_CDTXSYNC16(sc, nexttx,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               }

               /* Advance the Tx pointer. */
               sc->sc_txpending++;
               sc->sc_txlast = nexttx;

               /*
                * Pass the packet to any BPF listeners.
                */
               bpf_mtap(ifp, m0, BPF_D_OUT);
       }

       if (sc->sc_txpending != opending) {
               /*
                * We enqueued packets.  If the transmitter was idle,
                * reset the txdirty pointer.
                */
               if (opending == 0)
                       sc->sc_txdirty = SONIC_NEXTTX(olasttx);

               /*
                * Stop the SONIC on the last packet we've set up,
                * and clear end-of-list on the descriptor previous
                * to our new chain.
                *
                * NOTE: our `seg' variable should still be valid!
                */
               if (sc->sc_32bit) {
                       olseg =
                           sonic32toh(sc, sc->sc_tda32[olasttx].tda_fragcnt);
                       sc->sc_tda32[sc->sc_txlast].tda_frags[seg].frag_ptr0 |=
                           htosonic32(sc, TDA_LINK_EOL);
                       SONIC_CDTXSYNC32(sc, sc->sc_txlast,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
                       sc->sc_tda32[olasttx].tda_frags[olseg].frag_ptr0 &=
                           htosonic32(sc, ~TDA_LINK_EOL);
                       SONIC_CDTXSYNC32(sc, olasttx,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               } else {
                       olseg =
                           sonic16toh(sc, sc->sc_tda16[olasttx].tda_fragcnt);
                       sc->sc_tda16[sc->sc_txlast].tda_frags[seg].frag_ptr0 |=
                           htosonic16(sc, TDA_LINK_EOL);
                       SONIC_CDTXSYNC16(sc, sc->sc_txlast,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
                       sc->sc_tda16[olasttx].tda_frags[olseg].frag_ptr0 &=
                           htosonic16(sc, ~TDA_LINK_EOL);
                       SONIC_CDTXSYNC16(sc, olasttx,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               }

               /* Start the transmitter. */
               CSR_WRITE(sc, SONIC_CR, CR_TXP);

               /* Set a watchdog timer in case the chip flakes out. */
               ifp->if_timer = 5;
       }
}

/*
* sonic_watchdog:      [ifnet interface function]
*
*      Watchdog timer handler.
*/
void
sonic_watchdog(struct ifnet *ifp)
{
       struct sonic_softc *sc = ifp->if_softc;

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

       (void)sonic_init(ifp);
}

/*
* sonic_ioctl:         [ifnet interface function]
*
*      Handle control requests from the operator.
*/
int
sonic_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
       int s, error;

       s = splnet();

       error = ether_ioctl(ifp, cmd, data);
       if (error == ENETRESET) {
               /*
                * Multicast list has changed; set the hardware
                * filter accordingly.
                */
               if (ifp->if_flags & IFF_RUNNING)
                       (void)sonic_init(ifp);
               error = 0;
       }

       splx(s);
       return error;
}

/*
* sonic_intr:
*
*      Interrupt service routine.
*/
int
sonic_intr(void *arg)
{
       struct sonic_softc *sc = arg;
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       uint16_t isr;
       int handled = 0, wantinit;

       for (wantinit = 0; wantinit == 0;) {
               isr = CSR_READ(sc, SONIC_ISR) & sc->sc_imr;
               if (isr == 0)
                       break;
               CSR_WRITE(sc, SONIC_ISR, isr);  /* ACK */

               handled = 1;

               if (isr & IMR_PRX)
                       sonic_rxintr(sc);

               if (isr & (IMR_PTX | IMR_TXER)) {
                       if (sonic_txintr(sc) & TCR_FU) {
                               printf("%s: transmit FIFO underrun\n",
                                   device_xname(sc->sc_dev));
                               wantinit = 1;
                       }
               }

               if (isr & (IMR_RFO | IMR_RBA | IMR_RBE | IMR_RDE)) {
#define PRINTERR(bit, str)                                              \
                       if (isr & (bit))                                \
                               printf("%s: %s\n",device_xname(sc->sc_dev), str)
                       PRINTERR(IMR_RFO, "receive FIFO overrun");
                       PRINTERR(IMR_RBA, "receive buffer exceeded");
                       PRINTERR(IMR_RBE, "receive buffers exhausted");
                       PRINTERR(IMR_RDE, "receive descriptors exhausted");
                       wantinit = 1;
               }
       }

       if (handled) {
               if (wantinit)
                       (void)sonic_init(ifp);
               if_schedule_deferred_start(ifp);
       }

       return handled;
}

/*
* sonic_txintr:
*
*      Helper; handle transmit complete interrupts.
*/
uint16_t
sonic_txintr(struct sonic_softc *sc)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct sonic_descsoft *ds;
       struct sonic_tda32 *tda32;
       struct sonic_tda16 *tda16;
       uint16_t status, totstat = 0;
       int i, count;

       count = 0;
       for (i = sc->sc_txdirty; sc->sc_txpending != 0;
            i = SONIC_NEXTTX(i), sc->sc_txpending--) {
               ds = &sc->sc_txsoft[i];

               if (sc->sc_32bit) {
                       SONIC_CDTXSYNC32(sc, i,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                       tda32 = &sc->sc_tda32[i];
                       status = sonic32toh(sc, tda32->tda_status);
                       SONIC_CDTXSYNC32(sc, i, BUS_DMASYNC_PREREAD);
               } else {
                       SONIC_CDTXSYNC16(sc, i,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                       tda16 = &sc->sc_tda16[i];
                       status = sonic16toh(sc, tda16->tda_status);
                       SONIC_CDTXSYNC16(sc, i, BUS_DMASYNC_PREREAD);
               }

               if ((status & ~(TCR_EXDIS |TCR_CRCI |TCR_POWC |TCR_PINT)) == 0)
                       break;

               totstat |= status;

               bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                   ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE);
               bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
               m_freem(ds->ds_mbuf);
               ds->ds_mbuf = NULL;

               /*
                * Check for errors and collisions.
                */
               net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
               if (status & TCR_PTX) {
                       if_statinc_ref(ifp, nsr, if_opackets);
                       count++;
               } else {
                       if_statinc_ref(ifp, nsr, if_oerrors);
               }
               if (TDA_STATUS_NCOL(status)) {
                       if_statadd_ref(ifp, nsr, if_collisions,
                           TDA_STATUS_NCOL(status));
               }
               IF_STAT_PUTREF(ifp);
       }

       /* Update the dirty transmit buffer pointer. */
       sc->sc_txdirty = i;

       /*
        * Cancel the watchdog timer if there are no pending
        * transmissions.
        */
       if (sc->sc_txpending == 0)
               ifp->if_timer = 0;

       if (totstat != 0)
               rnd_add_uint32(&sc->sc_rndsource, totstat);

       return totstat;
}

/*
* sonic_rxintr:
*
*      Helper; handle receive interrupts.
*/
void
sonic_rxintr(struct sonic_softc *sc)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct sonic_descsoft *ds;
       struct sonic_rda32 *rda32;
       struct sonic_rda16 *rda16;
       struct mbuf *m;
       int i, len, count;
       uint16_t status, bytecount /*, ptr0, ptr1, seqno */;

       count = 0;
       for (i = sc->sc_rxptr;; i = SONIC_NEXTRX(i)) {
               ds = &sc->sc_rxsoft[i];

               if (sc->sc_32bit) {
                       SONIC_CDRXSYNC32(sc, i,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                       rda32 = &sc->sc_rda32[i];
                       SONIC_CDRXSYNC32(sc, i, BUS_DMASYNC_PREREAD);
                       if (rda32->rda_inuse != 0)
                               break;
                       status = sonic32toh(sc, rda32->rda_status);
                       bytecount = sonic32toh(sc, rda32->rda_bytecount);
                       /* ptr0 = sonic32toh(sc, rda32->rda_pkt_ptr0); */
                       /* ptr1 = sonic32toh(sc, rda32->rda_pkt_ptr1); */
                       /* seqno = sonic32toh(sc, rda32->rda_seqno); */
               } else {
                       SONIC_CDRXSYNC16(sc, i,
                           BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
                       rda16 = &sc->sc_rda16[i];
                       SONIC_CDRXSYNC16(sc, i, BUS_DMASYNC_PREREAD);
                       if (rda16->rda_inuse != 0)
                               break;
                       status = sonic16toh(sc, rda16->rda_status);
                       bytecount = sonic16toh(sc, rda16->rda_bytecount);
                       /* ptr0 = sonic16toh(sc, rda16->rda_pkt_ptr0); */
                       /* ptr1 = sonic16toh(sc, rda16->rda_pkt_ptr1); */
                       /* seqno = sonic16toh(sc, rda16->rda_seqno); */
               }

               /*
                * Make absolutely sure this is the only packet
                * in this receive buffer.  Our entire Rx buffer
                * management scheme depends on this, and if the
                * SONIC didn't follow our rule, it means we've
                * misconfigured it.
                */
               KASSERT(status & RCR_LPKT);

               /*
                * Make sure the packet arrived OK.  If an error occurred,
                * update stats and reset the descriptor.  The buffer will
                * be reused the next time the descriptor comes up in the
                * ring.
                */
               if ((status & RCR_PRX) == 0) {
                       if (status & RCR_FAER)
                               printf("%s: Rx frame alignment error\n",
                                   device_xname(sc->sc_dev));
                       else if (status & RCR_CRCR)
                               printf("%s: Rx CRC error\n",
                                   device_xname(sc->sc_dev));
                       if_statinc(ifp, if_ierrors);
                       SONIC_INIT_RXDESC(sc, i);
                       continue;
               }

               bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                   ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD);

               /*
                * The SONIC includes the CRC with every packet.
                */
               len = bytecount - ETHER_CRC_LEN;

               /*
                * Ok, if the chip is in 32-bit mode, then receive
                * buffers must be aligned to 32-bit boundaries,
                * which means the payload is misaligned.  In this
                * case, we must allocate a new mbuf, and copy the
                * packet into it, scooted forward 2 bytes to ensure
                * proper alignment.
                *
                * Note, in 16-bit mode, we can configure the SONIC
                * to do what we want, and we have.
                */
#ifndef __NO_STRICT_ALIGNMENT
               if (sc->sc_32bit) {
                       MGETHDR(m, M_DONTWAIT, MT_DATA);
                       if (m == NULL)
                               goto dropit;
                       if (len > (MHLEN - 2)) {
                               MCLGET(m, M_DONTWAIT);
                               if ((m->m_flags & M_EXT) == 0) {
                                       m_freem(m);
                                       goto dropit;
                               }
                       }
                       m->m_data += 2;
                       /*
                        * Note that we use a cluster for incoming frames,
                        * so the buffer is virtually contiguous.
                        */
                       memcpy(mtod(m, void *), mtod(ds->ds_mbuf, void *),
                           len);
                       SONIC_INIT_RXDESC(sc, i);
                       bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                           ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
               } else
#endif /* ! __NO_STRICT_ALIGNMENT */
               /*
                * If the packet is small enough to fit in a single
                * header mbuf, allocate one and copy the data into
                * it.  This greatly reduces memory consumption when
                * we receive lots of small packets.
                */
               if (sonic_copy_small != 0 && len <= (MHLEN - 2)) {
                       MGETHDR(m, M_DONTWAIT, MT_DATA);
                       if (m == NULL)
                               goto dropit;
                       m->m_data += 2;
                       /*
                        * Note that we use a cluster for incoming frames,
                        * so the buffer is virtually contiguous.
                        */
                       memcpy(mtod(m, void *), mtod(ds->ds_mbuf, void *),
                           len);
                       SONIC_INIT_RXDESC(sc, i);
                       bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                           ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
               } else {
                       m = ds->ds_mbuf;
                       if (sonic_add_rxbuf(sc, i) != 0) {
dropit:
                               if_statinc(ifp, if_ierrors);
                               SONIC_INIT_RXDESC(sc, i);
                               bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                                   ds->ds_dmamap->dm_mapsize,
                                   BUS_DMASYNC_PREREAD);
                               continue;
                       }
               }

               m_set_rcvif(m, ifp);
               m->m_pkthdr.len = m->m_len = len;

               /* Pass it on. */
               if_percpuq_enqueue(ifp->if_percpuq, m);

               count++;
       }

       /* Update the receive pointer. */
       sc->sc_rxptr = i;
       CSR_WRITE(sc, SONIC_RWR, SONIC_CDRRADDR(sc, SONIC_PREVRX(i)));

       if (count != 0)
               rnd_add_uint32(&sc->sc_rndsource, count);
}

/*
* sonic_reset:
*
*      Perform a soft reset on the SONIC.
*/
void
sonic_reset(struct sonic_softc *sc)
{

       /* stop TX, RX and timer, and ensure RST is clear */
       CSR_WRITE(sc, SONIC_CR, CR_STP | CR_RXDIS | CR_HTX);
       delay(1000);

       CSR_WRITE(sc, SONIC_CR, CR_RST);
       delay(1000);

       /* clear all interrupts */
       CSR_WRITE(sc, SONIC_IMR, 0);
       CSR_WRITE(sc, SONIC_ISR, IMR_ALL);

       CSR_WRITE(sc, SONIC_CR, 0);
       delay(1000);
}

/*
* sonic_init:          [ifnet interface function]
*
*      Initialize the interface.  Must be called at splnet().
*/
int
sonic_init(struct ifnet *ifp)
{
       struct sonic_softc *sc = ifp->if_softc;
       struct sonic_descsoft *ds;
       int i, error = 0;
       uint16_t reg;

       /*
        * Cancel any pending I/O.
        */
       sonic_stop(ifp, 0);

       /*
        * Reset the SONIC to a known state.
        */
       sonic_reset(sc);

       /*
        * Bring the SONIC into reset state, and program the DCR.
        *
        * Note: We don't bother optimizing the transmit and receive
        * thresholds, here. TFT/RFT values should be set in MD attachments.
        */
       reg = sc->sc_dcr;
       if (sc->sc_32bit)
               reg |= DCR_DW;
       CSR_WRITE(sc, SONIC_CR, CR_RST);
       CSR_WRITE(sc, SONIC_DCR, reg);
       CSR_WRITE(sc, SONIC_DCR2, sc->sc_dcr2);
       CSR_WRITE(sc, SONIC_CR, 0);

       /*
        * Initialize the transmit descriptors.
        */
       if (sc->sc_32bit) {
               for (i = 0; i < SONIC_NTXDESC; i++) {
                       memset(&sc->sc_tda32[i], 0, sizeof(struct sonic_tda32));
                       SONIC_CDTXSYNC32(sc, i,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               }
       } else {
               for (i = 0; i < SONIC_NTXDESC; i++) {
                       memset(&sc->sc_tda16[i], 0, sizeof(struct sonic_tda16));
                       SONIC_CDTXSYNC16(sc, i,
                           BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
               }
       }
       sc->sc_txpending = 0;
       sc->sc_txdirty = 0;
       sc->sc_txlast = SONIC_NTXDESC - 1;

       /*
        * Initialize the receive descriptor ring.
        */
       for (i = 0; i < SONIC_NRXDESC; i++) {
               ds = &sc->sc_rxsoft[i];
               if (ds->ds_mbuf == NULL) {
                       if ((error = sonic_add_rxbuf(sc, i)) != 0) {
                               printf("%s: unable to allocate or map Rx "
                                   "buffer %d, error = %d\n",
                                   device_xname(sc->sc_dev), i, error);
                               /*
                                * XXX Should attempt to run with fewer receive
                                * XXX buffers instead of just failing.
                                */
                               sonic_rxdrain(sc);
                               goto out;
                       }
               } else
                       SONIC_INIT_RXDESC(sc, i);
       }
       sc->sc_rxptr = 0;

       /* Give the transmit ring to the SONIC. */
       CSR_WRITE(sc, SONIC_UTDAR, (SONIC_CDTXADDR(sc, 0) >> 16) & 0xffff);
       CSR_WRITE(sc, SONIC_CTDAR, SONIC_CDTXADDR(sc, 0) & 0xffff);

       /* Give the receive descriptor ring to the SONIC. */
       CSR_WRITE(sc, SONIC_URDAR, (SONIC_CDRXADDR(sc, 0) >> 16) & 0xffff);
       CSR_WRITE(sc, SONIC_CRDAR, SONIC_CDRXADDR(sc, 0) & 0xffff);

       /* Give the receive buffer ring to the SONIC. */
       CSR_WRITE(sc, SONIC_URRAR, (SONIC_CDRRADDR(sc, 0) >> 16) & 0xffff);
       CSR_WRITE(sc, SONIC_RSAR, SONIC_CDRRADDR(sc, 0) & 0xffff);
       if (sc->sc_32bit)
               CSR_WRITE(sc, SONIC_REAR,
                   (SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1) +
                   sizeof(struct sonic_rra32)) & 0xffff);
       else
               CSR_WRITE(sc, SONIC_REAR,
                   (SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1) +
                   sizeof(struct sonic_rra16)) & 0xffff);
       CSR_WRITE(sc, SONIC_RRR, SONIC_CDRRADDR(sc, 0) & 0xffff);
       CSR_WRITE(sc, SONIC_RWR, SONIC_CDRRADDR(sc, SONIC_NRXDESC - 1));

       /*
        * Set the End-Of-Buffer counter such that only one packet
        * will be placed into each buffer we provide.  Note we are
        * following the recommendation of section 3.4.4 of the manual
        * here, and have "lengthened" the receive buffers accordingly.
        */
       if (sc->sc_32bit)
               CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN + 2) / 2);
       else
               CSR_WRITE(sc, SONIC_EOBC, (ETHER_MAX_LEN / 2));

       /* Reset the receive sequence counter. */
       CSR_WRITE(sc, SONIC_RSC, 0);

       /* Clear the tally registers. */
       CSR_WRITE(sc, SONIC_CRCETC, 0xffff);
       CSR_WRITE(sc, SONIC_FAET, 0xffff);
       CSR_WRITE(sc, SONIC_MPT, 0xffff);

       /* Set the receive filter. */
       sonic_set_filter(sc);

       /*
        * Set the interrupt mask register.
        */
       sc->sc_imr = IMR_RFO | IMR_RBA | IMR_RBE | IMR_RDE |
           IMR_TXER | IMR_PTX | IMR_PRX;
       CSR_WRITE(sc, SONIC_IMR, sc->sc_imr);

       /*
        * Start the receive process in motion.  Note, we don't
        * start the transmit process until we actually try to
        * transmit packets.
        */
       CSR_WRITE(sc, SONIC_CR, CR_RXEN | CR_RRRA);

       /*
        * ...all done!
        */
       ifp->if_flags |= IFF_RUNNING;

out:
       if (error)
               printf("%s: interface not running\n", device_xname(sc->sc_dev));
       return error;
}

/*
* sonic_rxdrain:
*
*      Drain the receive queue.
*/
void
sonic_rxdrain(struct sonic_softc *sc)
{
       struct sonic_descsoft *ds;
       int i;

       for (i = 0; i < SONIC_NRXDESC; i++) {
               ds = &sc->sc_rxsoft[i];
               if (ds->ds_mbuf != NULL) {
                       bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
                       m_freem(ds->ds_mbuf);
                       ds->ds_mbuf = NULL;
               }
       }
}

/*
* sonic_stop:          [ifnet interface function]
*
*      Stop transmission on the interface.
*/
void
sonic_stop(struct ifnet *ifp, int disable)
{
       struct sonic_softc *sc = ifp->if_softc;
       struct sonic_descsoft *ds;
       int i;

       /*
        * Disable interrupts.
        */
       CSR_WRITE(sc, SONIC_IMR, 0);

       /*
        * Stop the transmitter, receiver, and timer.
        */
       CSR_WRITE(sc, SONIC_CR, CR_HTX | CR_RXDIS | CR_STP);
       for (i = 0; i < 1000; i++) {
               if ((CSR_READ(sc, SONIC_CR) & (CR_TXP | CR_RXEN | CR_ST)) == 0)
                       break;
               delay(2);
       }
       if ((CSR_READ(sc, SONIC_CR) & (CR_TXP | CR_RXEN | CR_ST)) != 0)
               printf("%s: SONIC failed to stop\n", device_xname(sc->sc_dev));

       /*
        * Release any queued transmit buffers.
        */
       for (i = 0; i < SONIC_NTXDESC; i++) {
               ds = &sc->sc_txsoft[i];
               if (ds->ds_mbuf != NULL) {
                       bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);
                       m_freem(ds->ds_mbuf);
                       ds->ds_mbuf = NULL;
               }
       }

       /*
        * Mark the interface down and cancel the watchdog timer.
        */
       ifp->if_flags &= ~IFF_RUNNING;
       ifp->if_timer = 0;

       if (disable)
               sonic_rxdrain(sc);
}

/*
* sonic_add_rxbuf:
*
*      Add a receive buffer to the indicated descriptor.
*/
int
sonic_add_rxbuf(struct sonic_softc *sc, int idx)
{
       struct sonic_descsoft *ds = &sc->sc_rxsoft[idx];
       struct mbuf *m;
       int error;

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

       MCLGET(m, M_DONTWAIT);
       if ((m->m_flags & M_EXT) == 0) {
               m_freem(m);
               return ENOBUFS;
       }

       if (ds->ds_mbuf != NULL)
               bus_dmamap_unload(sc->sc_dmat, ds->ds_dmamap);

       ds->ds_mbuf = m;

       error = bus_dmamap_load(sc->sc_dmat, ds->ds_dmamap,
           m->m_ext.ext_buf, m->m_ext.ext_size, NULL,
           BUS_DMA_READ | BUS_DMA_NOWAIT);
       if (error) {
               printf("%s: can't load rx DMA map %d, error = %d\n",
                   device_xname(sc->sc_dev), idx, error);
               panic("sonic_add_rxbuf");       /* XXX */
       }

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

       SONIC_INIT_RXDESC(sc, idx);

       return 0;
}

static void
sonic_set_camentry(struct sonic_softc *sc, int entry, const uint8_t *enaddr)
{

       if (sc->sc_32bit) {
               struct sonic_cda32 *cda = &sc->sc_cda32[entry];

               cda->cda_entry = htosonic32(sc, entry);
               cda->cda_addr0 = htosonic32(sc, enaddr[0] | (enaddr[1] << 8));
               cda->cda_addr1 = htosonic32(sc, enaddr[2] | (enaddr[3] << 8));
               cda->cda_addr2 = htosonic32(sc, enaddr[4] | (enaddr[5] << 8));
       } else {
               struct sonic_cda16 *cda = &sc->sc_cda16[entry];

               cda->cda_entry = htosonic16(sc, entry);
               cda->cda_addr0 = htosonic16(sc, enaddr[0] | (enaddr[1] << 8));
               cda->cda_addr1 = htosonic16(sc, enaddr[2] | (enaddr[3] << 8));
               cda->cda_addr2 = htosonic16(sc, enaddr[4] | (enaddr[5] << 8));
       }
}

/*
* sonic_set_filter:
*
*      Set the SONIC receive filter.
*/
void
sonic_set_filter(struct sonic_softc *sc)
{
       struct ethercom *ec = &sc->sc_ethercom;
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct ether_multi *enm;
       struct ether_multistep step;
       int i, entry = 0;
       uint16_t camvalid = 0;
       uint16_t rcr = 0;

       if (ifp->if_flags & IFF_BROADCAST)
               rcr |= RCR_BRD;

       if (ifp->if_flags & IFF_PROMISC) {
               rcr |= RCR_PRO;
               goto allmulti;
       }

       /* Put our station address in the first CAM slot. */
       sonic_set_camentry(sc, entry, CLLADDR(ifp->if_sadl));
       camvalid |= (1U << entry);
       entry++;

       /* Add the multicast addresses to the CAM. */
       ETHER_LOCK(ec);
       ETHER_FIRST_MULTI(step, ec, enm);
       while (enm != NULL) {
               if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
                       /*
                        * We must listen to a range of multicast addresses.
                        * The only way to do this on the SONIC is to enable
                        * reception of all multicast packets.
                        */
                       ETHER_UNLOCK(ec);
                       goto allmulti;
               }

               if (entry == SONIC_NCAMENT) {
                       /*
                        * Out of CAM slots.  Have to enable reception
                        * of all multicast addresses.
                        */
                       ETHER_UNLOCK(ec);
                       goto allmulti;
               }

               sonic_set_camentry(sc, entry, enm->enm_addrlo);
               camvalid |= (1U << entry);
               entry++;

               ETHER_NEXT_MULTI(step, enm);
       }
       ETHER_UNLOCK(ec);

       ifp->if_flags &= ~IFF_ALLMULTI;
       goto setit;

allmulti:
       /* Use only the first CAM slot (station address). */
       camvalid = 0x0001;
       entry = 1;
       rcr |= RCR_AMC;

setit:
       /* set mask for the CAM Enable register */
       if (sc->sc_32bit) {
               if (entry == SONIC_NCAMENT)
                       sc->sc_cdaenable32 = htosonic32(sc, camvalid);
               else
                       sc->sc_cda32[entry].cda_entry =
                           htosonic32(sc, camvalid);
       } else {
               if (entry == SONIC_NCAMENT)
                       sc->sc_cdaenable16 = htosonic16(sc, camvalid);
               else
                       sc->sc_cda16[entry].cda_entry =
                           htosonic16(sc, camvalid);
       }

       /* Load the CAM. */
       SONIC_CDCAMSYNC(sc, BUS_DMASYNC_PREWRITE);
       CSR_WRITE(sc, SONIC_CDP, SONIC_CDCAMADDR(sc) & 0xffff);
       CSR_WRITE(sc, SONIC_CDC, entry);
       CSR_WRITE(sc, SONIC_CR, CR_LCAM);
       for (i = 0; i < 10000; i++) {
               if ((CSR_READ(sc, SONIC_CR) & CR_LCAM) == 0)
                       break;
               delay(2);
       }
       if (CSR_READ(sc, SONIC_CR) & CR_LCAM)
               printf("%s: CAM load failed\n", device_xname(sc->sc_dev));
       SONIC_CDCAMSYNC(sc, BUS_DMASYNC_POSTWRITE);

       /* Set the receive control register. */
       CSR_WRITE(sc, SONIC_RCR, rcr);
}