/*      $NetBSD: aic6915.c,v 1.47 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 Adaptec AIC-6915 (``Starfire'')
* 10/100 Ethernet controller.
*/

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


#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.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 <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>

#include <net/bpf.h>

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

#include <dev/mii/miivar.h>

#include <dev/ic/aic6915reg.h>
#include <dev/ic/aic6915var.h>

static void     sf_start(struct ifnet *);
static void     sf_watchdog(struct ifnet *);
static int      sf_ioctl(struct ifnet *, u_long, void *);
static int      sf_init(struct ifnet *);
static void     sf_stop(struct ifnet *, int);

static bool     sf_shutdown(device_t, int);

static void     sf_txintr(struct sf_softc *);
static void     sf_rxintr(struct sf_softc *);
static void     sf_stats_update(struct sf_softc *);

static void     sf_reset(struct sf_softc *);
static void     sf_macreset(struct sf_softc *);
static void     sf_rxdrain(struct sf_softc *);
static int      sf_add_rxbuf(struct sf_softc *, int);
static uint8_t  sf_read_eeprom(struct sf_softc *, int);
static void     sf_set_filter(struct sf_softc *);

static int      sf_mii_read(device_t, int, int, uint16_t *);
static int      sf_mii_write(device_t, int, int, uint16_t);
static void     sf_mii_statchg(struct ifnet *);

static void     sf_tick(void *);

#define sf_funcreg_read(sc, reg)                                        \
       bus_space_read_4((sc)->sc_st, (sc)->sc_sh_func, (reg))
#define sf_funcreg_write(sc, reg, val)                                  \
       bus_space_write_4((sc)->sc_st, (sc)->sc_sh_func, (reg), (val))

static inline uint32_t
sf_reg_read(struct sf_softc *sc, bus_addr_t reg)
{

       if (__predict_false(sc->sc_iomapped)) {
               bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoAccess,
                   reg);
               return (bus_space_read_4(sc->sc_st, sc->sc_sh,
                   SF_IndirectIoDataPort));
       }

       return (bus_space_read_4(sc->sc_st, sc->sc_sh, reg));
}

static inline void
sf_reg_write(struct sf_softc *sc, bus_addr_t reg, uint32_t val)
{

       if (__predict_false(sc->sc_iomapped)) {
               bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoAccess,
                   reg);
               bus_space_write_4(sc->sc_st, sc->sc_sh, SF_IndirectIoDataPort,
                   val);
               return;
       }

       bus_space_write_4(sc->sc_st, sc->sc_sh, reg, val);
}

#define sf_genreg_read(sc, reg)                                         \
       sf_reg_read((sc), (reg) + SF_GENREG_OFFSET)
#define sf_genreg_write(sc, reg, val)                                   \
       sf_reg_write((sc), (reg) + SF_GENREG_OFFSET, (val))

/*
* sf_attach:
*
*      Attach a Starfire interface to the system.
*/
void
sf_attach(struct sf_softc *sc)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct mii_data * const mii = &sc->sc_mii;
       int i, rseg, error;
       bus_dma_segment_t seg;
       uint8_t enaddr[ETHER_ADDR_LEN];

       callout_init(&sc->sc_tick_callout, 0);
       callout_setfunc(&sc->sc_tick_callout, sf_tick, sc);

       /*
        * If we're I/O mapped, the functional register handle is
        * the same as the base handle.  If we're memory mapped,
        * carve off a chunk of the register space for the functional
        * registers, to save on arithmetic later.
        */
       if (sc->sc_iomapped)
               sc->sc_sh_func = sc->sc_sh;
       else {
               if ((error = bus_space_subregion(sc->sc_st, sc->sc_sh,
                   SF_GENREG_OFFSET, SF_FUNCREG_SIZE, &sc->sc_sh_func)) != 0) {
                       aprint_error_dev(sc->sc_dev, "unable to sub-region "
                           "functional registers, error = %d\n", error);
                       return;
               }
       }

       /*
        * Initialize the transmit threshold for this interface.  The
        * manual describes the default as 4 * 16 bytes.  We start out
        * at 10 * 16 bytes, to avoid a bunch of initial underruns on
        * several platforms.
        */
       sc->sc_txthresh = 10;

       /*
        * Allocate the control data structures, and create and load the
        * DMA map for it.
        */
       if ((error = bus_dmamem_alloc(sc->sc_dmat,
           sizeof(struct sf_control_data), PAGE_SIZE, 0, &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,
           sizeof(struct sf_control_data), (void **)&sc->sc_control_data,
           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;
       }

       if ((error = bus_dmamap_create(sc->sc_dmat,
           sizeof(struct sf_control_data), 1,
           sizeof(struct sf_control_data), 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_control_data, sizeof(struct sf_control_data), 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 < SF_NTXDESC; i++) {
               if ((error = bus_dmamap_create(sc->sc_dmat, MCLBYTES,
                   SF_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 < SF_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;
               }
       }

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

       /*
        * Read the Ethernet address from the EEPROM.
        */
       for (i = 0; i < ETHER_ADDR_LEN; i++)
               enaddr[i] = sf_read_eeprom(sc, (15 + (ETHER_ADDR_LEN - 1)) - i);

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

       if (sf_funcreg_read(sc, SF_PciDeviceConfig) & PDC_System64)
               printf("%s: 64-bit PCI slot detected\n",
                   device_xname(sc->sc_dev));

       /*
        * Initialize our media structures and probe the MII.
        */
       mii->mii_ifp = ifp;
       mii->mii_readreg = sf_mii_read;
       mii->mii_writereg = sf_mii_write;
       mii->mii_statchg = sf_mii_statchg;
       sc->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);
       if (LIST_FIRST(&mii->mii_phys) == NULL) {
               ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL);
               ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE);
       } else
               ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);

       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 = sf_ioctl;
       ifp->if_start = sf_start;
       ifp->if_watchdog = sf_watchdog;
       ifp->if_init = sf_init;
       ifp->if_stop = sf_stop;
       IFQ_SET_READY(&ifp->if_snd);

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

       /*
        * Make sure the interface is shutdown during reboot.
        */
       if (pmf_device_register1(sc->sc_dev, NULL, NULL, sf_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 an fall through.
        */
fail_5:
       for (i = 0; i < SF_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 < SF_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_control_data,
           sizeof(struct sf_control_data));
fail_1:
       bus_dmamem_free(sc->sc_dmat, &seg, rseg);
fail_0:
       return;
}

/*
* sf_shutdown:
*
*      Shutdown hook -- make sure the interface is stopped at reboot.
*/
static bool
sf_shutdown(device_t self, int howto)
{
       struct sf_softc *sc;

       sc = device_private(self);
       sf_stop(&sc->sc_ethercom.ec_if, 1);

       return true;
}

/*
* sf_start:            [ifnet interface function]
*
*      Start packet transmission on the interface.
*/
static void
sf_start(struct ifnet *ifp)
{
       struct sf_softc *sc = ifp->if_softc;
       struct mbuf *m0, *m;
       struct sf_txdesc0 *txd;
       struct sf_descsoft *ds;
       bus_dmamap_t dmamap;
       int error, producer, last = -1, opending, seg;

       /*
        * Remember the previous number of pending transmits.
        */
       opending = sc->sc_txpending;

       /*
        * Find out where we're sitting.
        */
       producer = SF_TXDINDEX_TO_HOST(
           TDQPI_HiPrTxProducerIndex_get(
           sf_funcreg_read(sc, SF_TxDescQueueProducerIndex)));

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

               /*
                * Get the transmit descriptor.
                */
               txd = &sc->sc_txdescs[producer];
               ds = &sc->sc_txsoft[producer];
               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 (bus_dmamap_load_mbuf(sc->sc_dmat, dmamap, m0,
                   BUS_DMA_WRITE | BUS_DMA_NOWAIT) != 0) {
                       MGETHDR(m, M_DONTWAIT, MT_DATA);
                       if (m == NULL) {
                               aprint_error_dev(sc->sc_dev,
                                   "unable to allocate Tx mbuf\n");
                               break;
                       }
                       MCLAIM(m, &sc->sc_ethercom.ec_tx_mowner);
                       if (m0->m_pkthdr.len > MHLEN) {
                               MCLGET(m, M_DONTWAIT);
                               if ((m->m_flags & M_EXT) == 0) {
                                       aprint_error_dev(sc->sc_dev,
                                           "unable to allocate Tx cluster\n");
                                       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) {
                               aprint_error_dev(sc->sc_dev,
                                   "unable to load Tx buffer, error = %d\n",
                                   error);
                               break;
                       }
               }

               /*
                * WE ARE NOW COMMITTED TO TRANSMITTING THE PACKET.
                */
               IFQ_DEQUEUE(&ifp->if_snd, m0);
               if (m != NULL) {
                       m_freem(m0);
                       m0 = m;
               }

               /* Initialize the descriptor. */
               txd->td_word0 =
                   htole32(TD_W0_ID | TD_W0_CRCEN | m0->m_pkthdr.len);
               if (producer == (SF_NTXDESC - 1))
                       txd->td_word0 |= TD_W0_END;
               txd->td_word1 = htole32(dmamap->dm_nsegs);
               for (seg = 0; seg < dmamap->dm_nsegs; seg++) {
                       txd->td_frags[seg].fr_addr =
                           htole32(dmamap->dm_segs[seg].ds_addr);
                       txd->td_frags[seg].fr_len =
                           htole32(dmamap->dm_segs[seg].ds_len);
               }

               /* Sync the descriptor and the DMA map. */
               SF_CDTXDSYNC(sc, producer, BUS_DMASYNC_PREWRITE);
               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;

               /* Advance the Tx pointer. */
               sc->sc_txpending++;
               last = producer;
               producer = SF_NEXTTX(producer);

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

       if (sc->sc_txpending != opending) {
               KASSERT(last != -1);
               /*
                * We enqueued packets.  Cause a transmit interrupt to
                * happen on the last packet we enqueued, and give the
                * new descriptors to the chip by writing the new
                * producer index.
                */
               sc->sc_txdescs[last].td_word0 |= TD_W0_INTR;
               SF_CDTXDSYNC(sc, last, BUS_DMASYNC_PREWRITE);

               sf_funcreg_write(sc, SF_TxDescQueueProducerIndex,
                   TDQPI_HiPrTxProducerIndex(SF_TXDINDEX_TO_CHIP(producer)));

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

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

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

       (void) sf_init(ifp);

       /* Try to get more packets going. */
       sf_start(ifp);
}

/*
* sf_ioctl:            [ifnet interface function]
*
*      Handle control requests from the operator.
*/
static int
sf_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
       struct sf_softc *sc = ifp->if_softc;
       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)
                       sf_set_filter(sc);
               error = 0;
       }

       /* Try to get more packets going. */
       sf_start(ifp);

       splx(s);
       return (error);
}

/*
* sf_intr:
*
*      Interrupt service routine.
*/
int
sf_intr(void *arg)
{
       struct sf_softc *sc = arg;
       uint32_t isr;
       int handled = 0, wantinit = 0;

       for (;;) {
               /* Reading clears all interrupts we're interested in. */
               isr = sf_funcreg_read(sc, SF_InterruptStatus);
               if ((isr & IS_PCIPadInt) == 0)
                       break;

               handled = 1;

               /* Handle receive interrupts. */
               if (isr & IS_RxQ1DoneInt)
                       sf_rxintr(sc);

               /* Handle transmit completion interrupts. */
               if (isr & (IS_TxDmaDoneInt | IS_TxQueueDoneInt))
                       sf_txintr(sc);

               /* Handle abnormal interrupts. */
               if (isr & IS_AbnormalInterrupt) {
                       /* Statistics. */
                       if (isr & IS_StatisticWrapInt)
                               sf_stats_update(sc);

                       /* DMA errors. */
                       if (isr & IS_DmaErrInt) {
                               wantinit = 1;
                               aprint_error_dev(sc->sc_dev,
                                   "WARNING: DMA error\n");
                       }

                       /* Transmit FIFO underruns. */
                       if (isr & IS_TxDataLowInt) {
                               if (sc->sc_txthresh < 0xff)
                                       sc->sc_txthresh++;
                               printf("%s: transmit FIFO underrun, new "
                                   "threshold: %d bytes\n",
                                   device_xname(sc->sc_dev),
                                   sc->sc_txthresh * 16);
                               sf_funcreg_write(sc, SF_TransmitFrameCSR,
                                   sc->sc_TransmitFrameCSR |
                                   TFCSR_TransmitThreshold(sc->sc_txthresh));
                               sf_funcreg_write(sc, SF_TxDescQueueCtrl,
                                   sc->sc_TxDescQueueCtrl |
                                   TDQC_TxHighPriorityFifoThreshold(
                                                       sc->sc_txthresh));
                       }
               }
       }

       if (handled) {
               /* Reset the interface, if necessary. */
               if (wantinit)
                       sf_init(&sc->sc_ethercom.ec_if);

               /* Try and get more packets going. */
               if_schedule_deferred_start(&sc->sc_ethercom.ec_if);
       }

       return (handled);
}

/*
* sf_txintr:
*
*      Helper -- handle transmit completion interrupts.
*/
static void
sf_txintr(struct sf_softc *sc)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct sf_descsoft *ds;
       uint32_t cqci, tcd;
       int consumer, producer, txidx;

try_again:
       cqci = sf_funcreg_read(sc, SF_CompletionQueueConsumerIndex);

       consumer = CQCI_TxCompletionConsumerIndex_get(cqci);
       producer = CQPI_TxCompletionProducerIndex_get(
           sf_funcreg_read(sc, SF_CompletionQueueProducerIndex));

       if (consumer == producer)
               return;

       while (consumer != producer) {
               SF_CDTXCSYNC(sc, consumer, BUS_DMASYNC_POSTREAD);
               tcd = le32toh(sc->sc_txcomp[consumer].tcd_word0);

               txidx = SF_TCD_INDEX_TO_HOST(TCD_INDEX(tcd));
#ifdef DIAGNOSTIC
               if ((tcd & TCD_PR) == 0)
                       aprint_error_dev(sc->sc_dev,
                           "Tx queue mismatch, index %d\n", txidx);
#endif
               /*
                * NOTE: stats are updated later.  We're just
                * releasing packets that have been DMA'd to
                * the chip.
                */
               ds = &sc->sc_txsoft[txidx];
               SF_CDTXDSYNC(sc, txidx, BUS_DMASYNC_POSTWRITE);
               bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap,
                   0, ds->ds_dmamap->dm_mapsize,
                   BUS_DMASYNC_POSTWRITE);
               m_freem(ds->ds_mbuf);
               ds->ds_mbuf = NULL;

               consumer = SF_NEXTTCD(consumer);
               sc->sc_txpending--;
       }

       /* XXXJRT -- should be KDASSERT() */
       KASSERT(sc->sc_txpending >= 0);

       /* If all packets are done, cancel the watchdog timer. */
       if (sc->sc_txpending == 0)
               ifp->if_timer = 0;

       /* Update the consumer index. */
       sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex,
           (cqci & ~CQCI_TxCompletionConsumerIndex(0x7ff)) |
            CQCI_TxCompletionConsumerIndex(consumer));

       /* Double check for new completions. */
       goto try_again;
}

/*
* sf_rxintr:
*
*      Helper -- handle receive interrupts.
*/
static void
sf_rxintr(struct sf_softc *sc)
{
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       struct sf_descsoft *ds;
       struct sf_rcd_full *rcd;
       struct mbuf *m;
       uint32_t cqci, word0;
       int consumer, producer, bufproducer, rxidx, len;

try_again:
       cqci = sf_funcreg_read(sc, SF_CompletionQueueConsumerIndex);

       consumer = CQCI_RxCompletionQ1ConsumerIndex_get(cqci);
       producer = CQPI_RxCompletionQ1ProducerIndex_get(
           sf_funcreg_read(sc, SF_CompletionQueueProducerIndex));
       bufproducer = RXQ1P_RxDescQ1Producer_get(
           sf_funcreg_read(sc, SF_RxDescQueue1Ptrs));

       if (consumer == producer)
               return;

       while (consumer != producer) {
               rcd = &sc->sc_rxcomp[consumer];
               SF_CDRXCSYNC(sc, consumer,
                   BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
               SF_CDRXCSYNC(sc, consumer,
                   BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

               word0 = le32toh(rcd->rcd_word0);
               rxidx = RCD_W0_EndIndex(word0);

               ds = &sc->sc_rxsoft[rxidx];

               consumer = SF_NEXTRCD(consumer);
               bufproducer = SF_NEXTRX(bufproducer);

               if ((word0 & RCD_W0_OK) == 0) {
                       SF_INIT_RXDESC(sc, rxidx);
                       continue;
               }

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

               /*
                * No errors; receive the packet.  Note that we have
                * configured the Starfire to NOT transfer the CRC
                * with the packet.
                */
               len = RCD_W0_Length(word0);

#ifdef __NO_STRICT_ALIGNMENT
               /*
                * Allocate a new mbuf cluster.  If that fails, we are
                * out of memory, and must drop the packet and recycle
                * the buffer that's already attached to this descriptor.
                */
               m = ds->ds_mbuf;
               if (sf_add_rxbuf(sc, rxidx) != 0) {
                       if_statinc(ifp, if_ierrors);
                       SF_INIT_RXDESC(sc, rxidx);
                       bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                           ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
                       continue;
               }
#else
               /*
                * The Starfire's receive buffer must be 4-byte aligned.
                * But this means that the data after the Ethernet header
                * is misaligned.  We must allocate a new buffer and
                * copy the data, shifted forward 2 bytes.
                */
               MGETHDR(m, M_DONTWAIT, MT_DATA);
               if (m == NULL) {
dropit:
                       if_statinc(ifp, if_ierrors);
                       SF_INIT_RXDESC(sc, rxidx);
                       bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                           ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
                       continue;
               }
               MCLAIM(m, &sc->sc_ethercom.ec_rx_mowner);
               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 cluster for incoming frames, so the
                * buffer is virtually contiguous.
                */
               memcpy(mtod(m, void *), mtod(ds->ds_mbuf, void *), len);

               /* Allow the receive descriptor to continue using its mbuf. */
               SF_INIT_RXDESC(sc, rxidx);
               bus_dmamap_sync(sc->sc_dmat, ds->ds_dmamap, 0,
                   ds->ds_dmamap->dm_mapsize, BUS_DMASYNC_PREREAD);
#endif /* __NO_STRICT_ALIGNMENT */

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

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

       /* Update the chip's pointers. */
       sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex,
           (cqci & ~CQCI_RxCompletionQ1ConsumerIndex(0x7ff)) |
            CQCI_RxCompletionQ1ConsumerIndex(consumer));
       sf_funcreg_write(sc, SF_RxDescQueue1Ptrs,
           RXQ1P_RxDescQ1Producer(bufproducer));

       /* Double-check for any new completions. */
       goto try_again;
}

/*
* sf_tick:
*
*      One second timer, used to tick the MII and update stats.
*/
static void
sf_tick(void *arg)
{
       struct sf_softc *sc = arg;
       int s;

       s = splnet();
       mii_tick(&sc->sc_mii);
       sf_stats_update(sc);
       splx(s);

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

/*
* sf_stats_update:
*
*      Read the statistics counters.
*/
static void
sf_stats_update(struct sf_softc *sc)
{
       struct sf_stats stats;
       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
       uint32_t *p;
       u_int i;

       p = &stats.TransmitOKFrames;
       for (i = 0; i < (sizeof(stats) / sizeof(uint32_t)); i++) {
               *p++ = sf_genreg_read(sc,
                   SF_STATS_BASE + (i * sizeof(uint32_t)));
               sf_genreg_write(sc, SF_STATS_BASE + (i * sizeof(uint32_t)), 0);
       }

       net_stat_ref_t nsr = IF_STAT_GETREF(ifp);

       if_statadd_ref(ifp, nsr, if_opackets, stats.TransmitOKFrames);

       if_statadd_ref(ifp, nsr, if_collisions,
           stats.SingleCollisionFrames +
           stats.MultipleCollisionFrames);

       if_statadd_ref(ifp, nsr, if_oerrors,
           stats.TransmitAbortDueToExcessiveCollisions +
           stats.TransmitAbortDueToExcessingDeferral +
           stats.FramesLostDueToInternalTransmitErrors);

       if_statadd_ref(ifp, nsr, if_ierrors,
           stats.ReceiveCRCErrors + stats.AlignmentErrors +
           stats.ReceiveFramesTooLong + stats.ReceiveFramesTooShort +
           stats.ReceiveFramesJabbersError +
           stats.FramesLostDueToInternalReceiveErrors);

       IF_STAT_PUTREF(ifp);
}

/*
* sf_reset:
*
*      Perform a soft reset on the Starfire.
*/
static void
sf_reset(struct sf_softc *sc)
{
       int i;

       sf_funcreg_write(sc, SF_GeneralEthernetCtrl, 0);

       sf_macreset(sc);

       sf_funcreg_write(sc, SF_PciDeviceConfig, PDC_SoftReset);
       for (i = 0; i < 1000; i++) {
               delay(10);
               if ((sf_funcreg_read(sc, SF_PciDeviceConfig) &
                    PDC_SoftReset) == 0)
                       break;
       }

       if (i == 1000) {
               aprint_error_dev(sc->sc_dev, "reset failed to complete\n");
               sf_funcreg_write(sc, SF_PciDeviceConfig, 0);
       }

       delay(1000);
}

/*
* sf_macreset:
*
*      Reset the MAC portion of the Starfire.
*/
static void
sf_macreset(struct sf_softc *sc)
{

       sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1 | MC1_SoftRst);
       delay(1000);
       sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1);
}

/*
* sf_init:             [ifnet interface function]
*
*      Initialize the interface.  Must be called at splnet().
*/
static int
sf_init(struct ifnet *ifp)
{
       struct sf_softc *sc = ifp->if_softc;
       struct sf_descsoft *ds;
       int error = 0;
       u_int i;

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

       /*
        * Reset the Starfire to a known state.
        */
       sf_reset(sc);

       /* Clear the stat counters. */
       for (i = 0; i < sizeof(struct sf_stats); i += sizeof(uint32_t))
               sf_genreg_write(sc, SF_STATS_BASE + i, 0);

       /*
        * Initialize the transmit descriptor ring.
        */
       memset(sc->sc_txdescs, 0, sizeof(sc->sc_txdescs));
       sf_funcreg_write(sc, SF_TxDescQueueHighAddr, 0);
       sf_funcreg_write(sc, SF_HiPrTxDescQueueBaseAddr, SF_CDTXDADDR(sc, 0));
       sf_funcreg_write(sc, SF_LoPrTxDescQueueBaseAddr, 0);

       /*
        * Initialize the transmit completion ring.
        */
       for (i = 0; i < SF_NTCD; i++) {
               sc->sc_txcomp[i].tcd_word0 = TCD_DMA_ID;
               SF_CDTXCSYNC(sc, i, BUS_DMASYNC_PREREAD |BUS_DMASYNC_PREWRITE);
       }
       sf_funcreg_write(sc, SF_CompletionQueueHighAddr, 0);
       sf_funcreg_write(sc, SF_TxCompletionQueueCtrl, SF_CDTXCADDR(sc, 0));

       /*
        * Initialize the receive descriptor ring.
        */
       for (i = 0; i < SF_NRXDESC; i++) {
               ds = &sc->sc_rxsoft[i];
               if (ds->ds_mbuf == NULL) {
                       if ((error = sf_add_rxbuf(sc, i)) != 0) {
                               aprint_error_dev(sc->sc_dev,
                                   "unable to allocate or map rx buffer %d, "
                                   "error = %d\n", i, error);
                               /*
                                * XXX Should attempt to run with fewer receive
                                * XXX buffers instead of just failing.
                                */
                               sf_rxdrain(sc);
                               goto out;
                       }
               } else
                       SF_INIT_RXDESC(sc, i);
       }
       sf_funcreg_write(sc, SF_RxDescQueueHighAddress, 0);
       sf_funcreg_write(sc, SF_RxDescQueue1LowAddress, SF_CDRXDADDR(sc, 0));
       sf_funcreg_write(sc, SF_RxDescQueue2LowAddress, 0);

       /*
        * Initialize the receive completion ring.
        */
       for (i = 0; i < SF_NRCD; i++) {
               sc->sc_rxcomp[i].rcd_word0 = RCD_W0_ID;
               sc->sc_rxcomp[i].rcd_word1 = 0;
               sc->sc_rxcomp[i].rcd_word2 = 0;
               sc->sc_rxcomp[i].rcd_timestamp = 0;
               SF_CDRXCSYNC(sc, i, BUS_DMASYNC_PREREAD |BUS_DMASYNC_PREWRITE);
       }
       sf_funcreg_write(sc, SF_RxCompletionQueue1Ctrl, SF_CDRXCADDR(sc, 0) |
           RCQ1C_RxCompletionQ1Type(3));
       sf_funcreg_write(sc, SF_RxCompletionQueue2Ctrl, 0);

       /*
        * Initialize the Tx CSR.
        */
       sc->sc_TransmitFrameCSR = 0;
       sf_funcreg_write(sc, SF_TransmitFrameCSR,
           sc->sc_TransmitFrameCSR |
           TFCSR_TransmitThreshold(sc->sc_txthresh));

       /*
        * Initialize the Tx descriptor control register.
        */
       sc->sc_TxDescQueueCtrl = TDQC_SkipLength(0) |
           TDQC_TxDmaBurstSize(4) |    /* default */
           TDQC_MinFrameSpacing(3) |   /* 128 bytes */
           TDQC_TxDescType(0);
       sf_funcreg_write(sc, SF_TxDescQueueCtrl,
           sc->sc_TxDescQueueCtrl |
           TDQC_TxHighPriorityFifoThreshold(sc->sc_txthresh));

       /*
        * Initialize the Rx descriptor control registers.
        */
       sf_funcreg_write(sc, SF_RxDescQueue1Ctrl,
           RDQ1C_RxQ1BufferLength(MCLBYTES) |
           RDQ1C_RxDescSpacing(0));
       sf_funcreg_write(sc, SF_RxDescQueue2Ctrl, 0);

       /*
        * Initialize the Tx descriptor producer indices.
        */
       sf_funcreg_write(sc, SF_TxDescQueueProducerIndex,
           TDQPI_HiPrTxProducerIndex(0) |
           TDQPI_LoPrTxProducerIndex(0));

       /*
        * Initialize the Rx descriptor producer indices.
        */
       sf_funcreg_write(sc, SF_RxDescQueue1Ptrs,
           RXQ1P_RxDescQ1Producer(SF_NRXDESC - 1));
       sf_funcreg_write(sc, SF_RxDescQueue2Ptrs,
           RXQ2P_RxDescQ2Producer(0));

       /*
        * Initialize the Tx and Rx completion queue consumer indices.
        */
       sf_funcreg_write(sc, SF_CompletionQueueConsumerIndex,
           CQCI_TxCompletionConsumerIndex(0) |
           CQCI_RxCompletionQ1ConsumerIndex(0));
       sf_funcreg_write(sc, SF_RxHiPrCompletionPtrs, 0);

       /*
        * Initialize the Rx DMA control register.
        */
       sf_funcreg_write(sc, SF_RxDmaCtrl,
           RDC_RxHighPriorityThreshold(6) |    /* default */
           RDC_RxBurstSize(4));                /* default */

       /*
        * Set the receive filter.
        */
       sc->sc_RxAddressFilteringCtl = 0;
       sf_set_filter(sc);

       /*
        * Set MacConfig1.  When we set the media, MacConfig1 will
        * actually be written and the MAC part reset.
        */
       sc->sc_MacConfig1 = MC1_PadEn;

       /*
        * Set the media.
        */
       if ((error = ether_mediachange(ifp)) != 0)
               goto out;

       /*
        * Initialize the interrupt register.
        */
       sc->sc_InterruptEn = IS_PCIPadInt | IS_RxQ1DoneInt |
           IS_TxQueueDoneInt | IS_TxDmaDoneInt | IS_DmaErrInt |
           IS_StatisticWrapInt;
       sf_funcreg_write(sc, SF_InterruptEn, sc->sc_InterruptEn);

       sf_funcreg_write(sc, SF_PciDeviceConfig, PDC_IntEnable |
           PDC_PCIMstDmaEn | (1 << PDC_FifoThreshold_SHIFT));

       /*
        * Start the transmit and receive processes.
        */
       sf_funcreg_write(sc, SF_GeneralEthernetCtrl,
           GEC_TxDmaEn | GEC_RxDmaEn | GEC_TransmitEn | GEC_ReceiveEn);

       /* Start the on second clock. */
       callout_schedule(&sc->sc_tick_callout, hz);

       /*
        * Note that the interface is now running.
        */
       ifp->if_flags |= IFF_RUNNING;

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

/*
* sf_rxdrain:
*
*      Drain the receive queue.
*/
static void
sf_rxdrain(struct sf_softc *sc)
{
       struct sf_descsoft *ds;
       int i;

       for (i = 0; i < SF_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;
               }
       }
}

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

       /* Stop the one second clock. */
       callout_stop(&sc->sc_tick_callout);

       /* Down the MII. */
       mii_down(&sc->sc_mii);

       /* Disable interrupts. */
       sf_funcreg_write(sc, SF_InterruptEn, 0);

       /* Stop the transmit and receive processes. */
       sf_funcreg_write(sc, SF_GeneralEthernetCtrl, 0);

       /*
        * Release any queued transmit buffers.
        */
       for (i = 0; i < SF_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)
               sf_rxdrain(sc);
}

/*
* sf_read_eeprom:
*
*      Read from the Starfire EEPROM.
*/
static uint8_t
sf_read_eeprom(struct sf_softc *sc, int offset)
{
       uint32_t reg;

       reg = sf_genreg_read(sc, SF_EEPROM_BASE + (offset & ~3));

       return ((reg >> (8 * (offset & 3))) & 0xff);
}

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

       MGETHDR(m, M_DONTWAIT, MT_DATA);
       if (m == NULL)
               return (ENOBUFS);
       MCLAIM(m, &sc->sc_ethercom.ec_rx_mowner);

       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) {
               aprint_error_dev(sc->sc_dev,
                   "can't load rx DMA map %d, error = %d\n", idx, error);
               panic("sf_add_rxbuf"); /* XXX */
       }

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

       SF_INIT_RXDESC(sc, idx);

       return (0);
}

static void
sf_set_filter_perfect(struct sf_softc *sc, int slot, const uint8_t *enaddr)
{
       uint32_t reg0, reg1, reg2;

       reg0 = enaddr[5] | (enaddr[4] << 8);
       reg1 = enaddr[3] | (enaddr[2] << 8);
       reg2 = enaddr[1] | (enaddr[0] << 8);

       sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 0, reg0);
       sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 4, reg1);
       sf_genreg_write(sc, SF_PERFECT_BASE + (slot * 0x10) + 8, reg2);
}

static void
sf_set_filter_hash(struct sf_softc *sc, uint8_t *enaddr)
{
       uint32_t hash, slot, reg;

       hash = ether_crc32_be(enaddr, ETHER_ADDR_LEN) >> 23;
       slot = hash >> 4;

       reg = sf_genreg_read(sc, SF_HASH_BASE + (slot * 0x10));
       reg |= 1 << (hash & 0xf);
       sf_genreg_write(sc, SF_HASH_BASE + (slot * 0x10), reg);
}

/*
* sf_set_filter:
*
*      Set the Starfire receive filter.
*/
static void
sf_set_filter(struct sf_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;

       /* Start by clearing the perfect and hash tables. */
       for (i = 0; i < SF_PERFECT_SIZE; i += sizeof(uint32_t))
               sf_genreg_write(sc, SF_PERFECT_BASE + i, 0);

       for (i = 0; i < SF_HASH_SIZE; i += sizeof(uint32_t))
               sf_genreg_write(sc, SF_HASH_BASE + i, 0);

       /*
        * Clear the perfect and hash mode bits.
        */
       sc->sc_RxAddressFilteringCtl &=
           ~(RAFC_PerfectFilteringMode(3) | RAFC_HashFilteringMode(3));

       if (ifp->if_flags & IFF_BROADCAST)
               sc->sc_RxAddressFilteringCtl |= RAFC_PassBroadcast;
       else
               sc->sc_RxAddressFilteringCtl &= ~RAFC_PassBroadcast;

       if (ifp->if_flags & IFF_PROMISC) {
               sc->sc_RxAddressFilteringCtl |= RAFC_PromiscuousMode;
               goto allmulti;
       } else
               sc->sc_RxAddressFilteringCtl &= ~RAFC_PromiscuousMode;

       /*
        * Set normal perfect filtering mode.
        */
       sc->sc_RxAddressFilteringCtl |= RAFC_PerfectFilteringMode(1);

       /*
        * First, write the station address to the perfect filter
        * table.
        */
       sf_set_filter_perfect(sc, 0, CLLADDR(ifp->if_sadl));

       /*
        * Now set the hash bits for each multicast address in our
        * list.
        */
       ETHER_LOCK(ec);
       ETHER_FIRST_MULTI(step, ec, enm);
       if (enm == NULL) {
               ETHER_UNLOCK(ec);
               goto done;
       }
       while (enm != NULL) {
               if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
                       /*
                        * We must listen to a range of multicast addresses.
                        * For now, just accept all multicasts, rather than
                        * trying to set only those filter bits needed to match
                        * the range.  (At this time, the only use of address
                        * ranges is for IP multicast routing, for which the
                        * range is big enough to require all bits set.)
                        */
                       ETHER_UNLOCK(ec);
                       goto allmulti;
               }
               sf_set_filter_hash(sc, enm->enm_addrlo);
               ETHER_NEXT_MULTI(step, enm);
       }
       ETHER_UNLOCK(ec);

       /*
        * Set "hash only multicast dest, match regardless of VLAN ID".
        */
       sc->sc_RxAddressFilteringCtl |= RAFC_HashFilteringMode(2);
       goto done;

allmulti:
       /*
        * XXX RAFC_PassMulticast is sub-optimal if using VLAN mode.
        */
       sc->sc_RxAddressFilteringCtl |= RAFC_PassMulticast;
       ifp->if_flags |= IFF_ALLMULTI;

done:
       sf_funcreg_write(sc, SF_RxAddressFilteringCtl,
           sc->sc_RxAddressFilteringCtl);
}

/*
* sf_mii_read:         [mii interface function]
*
*      Read from the MII.
*/
static int
sf_mii_read(device_t self, int phy, int reg, uint16_t *data)
{
       struct sf_softc *sc = device_private(self);
       uint32_t v;
       int i;

       for (i = 0; i < 1000; i++) {
               v = sf_genreg_read(sc, SF_MII_PHY_REG(phy, reg));
               if (v & MiiDataValid)
                       break;
               delay(1);
       }

       if ((v & MiiDataValid) == 0)
               return -1;

       if (MiiRegDataPort(v) == 0xffff)
               return -1;

       *data = MiiRegDataPort(v);
       return 0;
}

/*
* sf_mii_write:        [mii interface function]
*
*      Write to the MII.
*/
static int
sf_mii_write(device_t self, int phy, int reg, uint16_t val)
{
       struct sf_softc *sc = device_private(self);
       int i;

       sf_genreg_write(sc, SF_MII_PHY_REG(phy, reg), val);

       for (i = 0; i < 1000; i++) {
               if ((sf_genreg_read(sc, SF_MII_PHY_REG(phy, reg)) &
                    MiiBusy) == 0)
                       return 0;
               delay(1);
       }

       printf("%s: MII write timed out\n", device_xname(sc->sc_dev));
       return ETIMEDOUT;
}

/*
* sf_mii_statchg:      [mii interface function]
*
*      Callback from the PHY when the media changes.
*/
static void
sf_mii_statchg(struct ifnet *ifp)
{
       struct sf_softc *sc = ifp->if_softc;
       uint32_t ipg;

       if (sc->sc_mii.mii_media_active & IFM_FDX) {
               sc->sc_MacConfig1 |= MC1_FullDuplex;
               ipg = 0x15;
       } else {
               sc->sc_MacConfig1 &= ~MC1_FullDuplex;
               ipg = 0x11;
       }

       sf_genreg_write(sc, SF_MacConfig1, sc->sc_MacConfig1);
       sf_macreset(sc);

       sf_genreg_write(sc, SF_BkToBkIPG, ipg);
}