root/usr/src/uts/common/io/sfe/sfe.c
/*
 *  sfe.c : DP83815/DP83816/SiS900 Fast Ethernet MAC driver for Solaris
 *
 * Copyright (c) 2002-2008 Masayuki Murayama.  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. Neither the name of the author nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT OWNER 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.
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2024 MNX Cloud, Inc.
 */

/*
 * System Header files.
 */
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/errno.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/byteorder.h>
#include <sys/ethernet.h>
#include <sys/pci.h>

#include "sfe_mii.h"
#include "sfe_util.h"
#include "sfereg.h"

char    ident[] = "sis900/dp83815 driver v" "2.6.1t30os";

/* Debugging support */
#ifdef DEBUG_LEVEL
static int sfe_debug = DEBUG_LEVEL;
#if DEBUG_LEVEL > 4
#define CONS    "^"
#else
#define CONS    "!"
#endif
#define DPRINTF(n, args)        if (sfe_debug > (n)) cmn_err args
#else
#define CONS    "!"
#define DPRINTF(n, args)
#endif

/*
 * Useful macros and typedefs
 */
#define ONESEC          (drv_usectohz(1*1000000))
#define ROUNDUP2(x, a)  (((x) + (a) - 1) & ~((a) - 1))

/*
 * Our configuration
 */
#define MAXTXFRAGS      1
#define MAXRXFRAGS      1

#ifndef TX_BUF_SIZE
#define TX_BUF_SIZE     64
#endif
#ifndef TX_RING_SIZE
#if MAXTXFRAGS == 1
#define TX_RING_SIZE    TX_BUF_SIZE
#else
#define TX_RING_SIZE    (TX_BUF_SIZE * 4)
#endif
#endif

#ifndef RX_BUF_SIZE
#define RX_BUF_SIZE     256
#endif
#ifndef RX_RING_SIZE
#define RX_RING_SIZE    RX_BUF_SIZE
#endif

#define OUR_INTR_BITS   \
        (ISR_DPERR | ISR_SSERR | ISR_RMABT | ISR_RTABT | ISR_RXSOVR |   \
        ISR_TXURN | ISR_TXDESC | ISR_TXERR |    \
        ISR_RXORN | ISR_RXIDLE | ISR_RXOK | ISR_RXERR)

#define USE_MULTICAST_HASHTBL

static int      sfe_tx_copy_thresh = 256;
static int      sfe_rx_copy_thresh = 256;

/* special PHY registers for SIS900 */
#define MII_CONFIG1     0x0010
#define MII_CONFIG2     0x0011
#define MII_MASK        0x0013
#define MII_RESV        0x0014

#define PHY_MASK                0xfffffff0
#define PHY_SIS900_INTERNAL     0x001d8000
#define PHY_ICS1893             0x0015f440


#define SFE_DESC_SIZE   16      /* including pads rounding up to power of 2 */

/*
 * Supported chips
 */
struct chip_info {
        uint16_t        venid;
        uint16_t        devid;
        char            *chip_name;
        int             chip_type;
#define CHIPTYPE_DP83815        0
#define CHIPTYPE_SIS900         1
};

/*
 * Chip dependent MAC state
 */
struct sfe_dev {
        /* misc HW information */
        struct chip_info        *chip;
        uint32_t                our_intr_bits;
        uint32_t                isr_pended;
        uint32_t                cr;
        uint_t                  tx_drain_threshold;
        uint_t                  tx_fill_threshold;
        uint_t                  rx_drain_threshold;
        uint_t                  rx_fill_threshold;
        uint8_t                 revid;  /* revision from PCI configuration */
        boolean_t               (*get_mac_addr)(struct gem_dev *);
        uint8_t                 mac_addr[ETHERADDRL];
        uint8_t                 bridge_revid;
};

/*
 * Hardware information
 */
struct chip_info sfe_chiptbl[] = {
        { 0x1039, 0x0900, "SiS900", CHIPTYPE_SIS900, },
        { 0x100b, 0x0020, "DP83815/83816", CHIPTYPE_DP83815, },
        { 0x1039, 0x7016, "SiS7016", CHIPTYPE_SIS900, },
};
#define CHIPTABLESIZE (sizeof (sfe_chiptbl)/sizeof (struct chip_info))

/* ======================================================== */

/* mii operations */
static void  sfe_mii_sync_dp83815(struct gem_dev *);
static void  sfe_mii_sync_sis900(struct gem_dev *);
static uint16_t  sfe_mii_read_dp83815(struct gem_dev *, uint_t);
static uint16_t  sfe_mii_read_sis900(struct gem_dev *, uint_t);
static void sfe_mii_write_dp83815(struct gem_dev *, uint_t, uint16_t);
static void sfe_mii_write_sis900(struct gem_dev *, uint_t, uint16_t);
static void sfe_set_eq_sis630(struct gem_dev *);
/* nic operations */
static int sfe_reset_chip_sis900(struct gem_dev *);
static int sfe_reset_chip_dp83815(struct gem_dev *);
static int sfe_init_chip(struct gem_dev *);
static int sfe_start_chip(struct gem_dev *);
static int sfe_stop_chip(struct gem_dev *);
static int sfe_set_media(struct gem_dev *);
static int sfe_set_rx_filter_dp83815(struct gem_dev *);
static int sfe_set_rx_filter_sis900(struct gem_dev *);
static int sfe_get_stats(struct gem_dev *);
static int sfe_attach_chip(struct gem_dev *);

/* descriptor operations */
static int sfe_tx_desc_write(struct gem_dev *dp, int slot,
                    ddi_dma_cookie_t *dmacookie, int frags, uint64_t flags);
static void sfe_tx_start(struct gem_dev *dp, int startslot, int nslot);
static void sfe_rx_desc_write(struct gem_dev *dp, int slot,
                    ddi_dma_cookie_t *dmacookie, int frags);
static uint_t sfe_tx_desc_stat(struct gem_dev *dp, int slot, int ndesc);
static uint64_t sfe_rx_desc_stat(struct gem_dev *dp, int slot, int ndesc);

static void sfe_tx_desc_init(struct gem_dev *dp, int slot);
static void sfe_rx_desc_init(struct gem_dev *dp, int slot);
static void sfe_tx_desc_clean(struct gem_dev *dp, int slot);
static void sfe_rx_desc_clean(struct gem_dev *dp, int slot);

/* interrupt handler */
static uint_t sfe_interrupt(struct gem_dev *dp);

/* ======================================================== */

/* mapping attributes */
/* Data access requirements. */
static struct ddi_device_acc_attr sfe_dev_attr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC,
        DDI_STRICTORDER_ACC
};

/* On sparc, Buffers should be native endian for speed */
static struct ddi_device_acc_attr sfe_buf_attr = {
        DDI_DEVICE_ATTR_V0,
        DDI_NEVERSWAP_ACC,      /* native endianness */
        DDI_STRICTORDER_ACC
};

static ddi_dma_attr_t sfe_dma_attr_buf = {
        DMA_ATTR_V0,            /* dma_attr_version */
        0,                      /* dma_attr_addr_lo */
        0xffffffffull,          /* dma_attr_addr_hi */
        0x00000fffull,          /* dma_attr_count_max */
        0, /* patched later */  /* dma_attr_align */
        0x000003fc,             /* dma_attr_burstsizes */
        1,                      /* dma_attr_minxfer */
        0x00000fffull,          /* dma_attr_maxxfer */
        0xffffffffull,          /* dma_attr_seg */
        0, /* patched later */  /* dma_attr_sgllen */
        1,                      /* dma_attr_granular */
        0                       /* dma_attr_flags */
};

static ddi_dma_attr_t sfe_dma_attr_desc = {
        DMA_ATTR_V0,            /* dma_attr_version */
        16,                     /* dma_attr_addr_lo */
        0xffffffffull,          /* dma_attr_addr_hi */
        0xffffffffull,          /* dma_attr_count_max */
        16,                     /* dma_attr_align */
        0x000003fc,             /* dma_attr_burstsizes */
        1,                      /* dma_attr_minxfer */
        0xffffffffull,          /* dma_attr_maxxfer */
        0xffffffffull,          /* dma_attr_seg */
        1,                      /* dma_attr_sgllen */
        1,                      /* dma_attr_granular */
        0                       /* dma_attr_flags */
};

uint32_t sfe_use_pcimemspace = 0;

/* ======================================================== */
/*
 * HW manipulation routines
 */
/* ======================================================== */

#define SFE_EEPROM_DELAY(dp)    \
        { (void) INL(dp, EROMAR); (void) INL(dp, EROMAR); }
#define EE_CMD_READ     6
#define EE_CMD_SHIFT    6

static uint16_t
sfe_read_eeprom(struct gem_dev *dp, uint_t offset)
{
        int             eedi;
        int             i;
        uint16_t        ret;

        /* ensure de-assert chip select */
        OUTL(dp, EROMAR, 0);
        SFE_EEPROM_DELAY(dp);
        OUTL(dp, EROMAR, EROMAR_EESK);
        SFE_EEPROM_DELAY(dp);

        /* assert chip select */
        offset |= EE_CMD_READ << EE_CMD_SHIFT;

        for (i = 8; i >= 0; i--) {
                /* make command */
                eedi = ((offset >> i) & 1) << EROMAR_EEDI_SHIFT;

                /* send 1 bit */
                OUTL(dp, EROMAR, EROMAR_EECS | eedi);
                SFE_EEPROM_DELAY(dp);
                OUTL(dp, EROMAR, EROMAR_EECS | eedi | EROMAR_EESK);
                SFE_EEPROM_DELAY(dp);
        }

        OUTL(dp, EROMAR, EROMAR_EECS);

        ret = 0;
        for (i = 0; i < 16; i++) {
                /* Get 1 bit */
                OUTL(dp, EROMAR, EROMAR_EECS);
                SFE_EEPROM_DELAY(dp);
                OUTL(dp, EROMAR, EROMAR_EECS | EROMAR_EESK);
                SFE_EEPROM_DELAY(dp);

                ret = (ret << 1) | ((INL(dp, EROMAR) >> EROMAR_EEDO_SHIFT) & 1);
        }

        OUTL(dp, EROMAR, 0);
        SFE_EEPROM_DELAY(dp);

        return (ret);
}
#undef SFE_EEPROM_DELAY

static boolean_t
sfe_get_mac_addr_dp83815(struct gem_dev *dp)
{
        uint8_t         *mac;
        uint_t          val;
        int             i;

#define BITSET(p, ix, v)        (p)[(ix)/8] |= ((v) ? 1 : 0) << ((ix) & 0x7)

        DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__));

        mac = dp->dev_addr.ether_addr_octet;

        /* first of all, clear MAC address buffer */
        bzero(mac, ETHERADDRL);

        /* get bit 0 */
        val = sfe_read_eeprom(dp, 0x6);
        BITSET(mac, 0, val & 1);

        /* get bit 1 - 16 */
        val = sfe_read_eeprom(dp, 0x7);
        for (i = 0; i < 16; i++) {
                BITSET(mac, 1 + i, val & (1 << (15 - i)));
        }

        /* get bit 17 -  32 */
        val = sfe_read_eeprom(dp, 0x8);
        for (i = 0; i < 16; i++) {
                BITSET(mac, 17 + i, val & (1 << (15 - i)));
        }

        /* get bit 33 -  47 */
        val = sfe_read_eeprom(dp, 0x9);
        for (i = 0; i < 15; i++) {
                BITSET(mac, 33 + i, val & (1 << (15 - i)));
        }

        return (B_TRUE);
#undef BITSET
}

static boolean_t
sfe_get_mac_addr_sis900(struct gem_dev *dp)
{
        uint_t          val;
        int             i;
        uint8_t         *mac;

        mac = dp->dev_addr.ether_addr_octet;

        for (i = 0; i < ETHERADDRL/2; i++) {
                val = sfe_read_eeprom(dp, 0x8 + i);
                *mac++ = (uint8_t)val;
                *mac++ = (uint8_t)(val >> 8);
        }

        return (B_TRUE);
}

static dev_info_t *
sfe_search_pci_dev_subr(dev_info_t *cur_node, int vendor_id, int device_id)
{
        dev_info_t      *child_id;
        dev_info_t      *ret;
        int             vid, did;

        if (cur_node == NULL) {
                return (NULL);
        }

        /* check brothers */
        do {
                vid = ddi_prop_get_int(DDI_DEV_T_ANY, cur_node,
                    DDI_PROP_DONTPASS, "vendor-id", -1);
                did = ddi_prop_get_int(DDI_DEV_T_ANY, cur_node,
                    DDI_PROP_DONTPASS, "device-id", -1);

                if (vid == vendor_id && did == device_id) {
                        /* found */
                        return (cur_node);
                }

                /* check children */
                if ((child_id = ddi_get_child(cur_node)) != NULL) {
                        if ((ret = sfe_search_pci_dev_subr(child_id,
                            vendor_id, device_id)) != NULL) {
                                return (ret);
                        }
                }

        } while ((cur_node = ddi_get_next_sibling(cur_node)) != NULL);

        /* not found */
        return (NULL);
}

static dev_info_t *
sfe_search_pci_dev(int vendor_id, int device_id)
{
        return (sfe_search_pci_dev_subr(ddi_root_node(), vendor_id, device_id));
}

static boolean_t
sfe_get_mac_addr_sis962(struct gem_dev *dp)
{
        boolean_t       ret;
        int             i;

        ret = B_FALSE;

        /* rise request signal to access EEPROM */
        OUTL(dp, MEAR, EROMAR_EEREQ);
        for (i = 0; (INL(dp, MEAR) & EROMAR_EEGNT) == 0; i++) {
                if (i > 200) {
                        /* failed to acquire eeprom */
                        cmn_err(CE_NOTE,
                            CONS "%s: failed to access eeprom", dp->name);
                        goto x;
                }
                drv_usecwait(10);
        }
        ret = sfe_get_mac_addr_sis900(dp);
x:
        /* release EEPROM */
        OUTL(dp, MEAR, EROMAR_EEDONE);

        return (ret);
}

static int
sfe_reset_chip_sis900(struct gem_dev *dp)
{
        int             i;
        uint32_t        done;
        uint32_t        val;
        struct sfe_dev  *lp = dp->private;

        DPRINTF(4, (CE_CONT, CONS "%s: %s called", dp->name, __func__));

        /* invalidate mac addr cache */
        bzero(lp->mac_addr, sizeof (lp->mac_addr));

        lp->cr = 0;

        /* inhibit interrupt */
        OUTL(dp, IMR, 0);
        lp->isr_pended |= INL(dp, ISR) & lp->our_intr_bits;

        OUTLINL(dp, RFCR, 0);

        OUTL(dp, CR, CR_RST | CR_TXR | CR_RXR);
        drv_usecwait(10);

        done = 0;
        for (i = 0; done != (ISR_TXRCMP | ISR_RXRCMP); i++) {
                if (i > 1000) {
                        cmn_err(CE_WARN, "%s: chip reset timeout", dp->name);
                        return (GEM_FAILURE);
                }
                done |= INL(dp, ISR) & (ISR_TXRCMP | ISR_RXRCMP);
                drv_usecwait(10);
        }

        if (lp->revid == SIS630ET_900_REV) {
                lp->cr |= CR_ACCESSMODE;
                OUTL(dp, CR, lp->cr | INL(dp, CR));
        }

        /* Configuration register: enable PCI parity */
        DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b",
            dp->name, INL(dp, CFG), CFG_BITS_SIS900));
        val = 0;
        if (lp->revid >= SIS635A_900_REV ||
            lp->revid == SIS900B_900_REV) {
                /* what is this ? */
                val |= CFG_RND_CNT;
        }
        OUTL(dp, CFG, val);
        DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name,
            INL(dp, CFG), CFG_BITS_SIS900));

        return (GEM_SUCCESS);
}

static int
sfe_reset_chip_dp83815(struct gem_dev *dp)
{
        int             i;
        uint32_t        val;
        struct sfe_dev  *lp = dp->private;

        DPRINTF(4, (CE_CONT, CONS "%s: %s called", dp->name, __func__));

        /* invalidate mac addr cache */
        bzero(lp->mac_addr, sizeof (lp->mac_addr));

        lp->cr = 0;

        /* inhibit interrupts */
        OUTL(dp, IMR, 0);
        lp->isr_pended |= INL(dp, ISR) & lp->our_intr_bits;

        OUTL(dp, RFCR, 0);

        OUTL(dp, CR, CR_RST);
        drv_usecwait(10);

        for (i = 0; INL(dp, CR) & CR_RST; i++) {
                if (i > 100) {
                        cmn_err(CE_WARN, "!%s: chip reset timeout", dp->name);
                        return (GEM_FAILURE);
                }
                drv_usecwait(10);
        }
        DPRINTF(0, (CE_CONT, "!%s: chip reset in %duS", dp->name, i*10));

        OUTL(dp, CCSR, CCSR_PMESTS);
        OUTL(dp, CCSR, 0);

        /* Configuration register: enable PCI parity */
        DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b",
            dp->name, INL(dp, CFG), CFG_BITS_DP83815));
        val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG);
        OUTL(dp, CFG, val | CFG_PAUSE_ADV);
        DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name,
            INL(dp, CFG), CFG_BITS_DP83815));

        return (GEM_SUCCESS);
}

static int
sfe_init_chip(struct gem_dev *dp)
{
        /* Configuration register: have been set up in sfe_chip_reset */

        /* PCI test control register: do nothing */

        /* Interrupt status register : do nothing */

        /* Interrupt mask register: clear, but leave lp->our_intr_bits */
        OUTL(dp, IMR, 0);

        /* Enhanced PHY Access register (sis900): do nothing */

        /* Transmit Descriptor Pointer register: base addr of TX ring */
        OUTL(dp, TXDP, dp->tx_ring_dma);

        /* Receive descriptor pointer register: base addr of RX ring */
        OUTL(dp, RXDP, dp->rx_ring_dma);

        return (GEM_SUCCESS);
}

static uint_t
sfe_mcast_hash(struct gem_dev *dp, uint8_t *addr)
{
        return (gem_ether_crc_be(addr, ETHERADDRL));
}

#ifdef DEBUG_LEVEL
static void
sfe_rxfilter_dump(struct gem_dev *dp, int start, int end)
{
        int             i;
        int             j;
        uint16_t        ram[0x10];

        cmn_err(CE_CONT, "!%s: rx filter ram dump:", dp->name);
#define WORDS_PER_LINE  4
        for (i = start; i < end; i += WORDS_PER_LINE*2) {
                for (j = 0; j < WORDS_PER_LINE; j++) {
                        OUTL(dp, RFCR, RFADDR_MAC_DP83815 + i + j*2);
                        ram[j] = INL(dp, RFDR);
                }

                cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x",
                    i, ram[0], ram[1], ram[2], ram[3]);
                }

#undef  WORDS_PER_LINE
}
#endif

static uint_t   sfe_rf_perfect_base_dp83815[] = {
        RFADDR_PMATCH0_DP83815,
        RFADDR_PMATCH1_DP83815,
        RFADDR_PMATCH2_DP83815,
        RFADDR_PMATCH3_DP83815,
};

static int
sfe_set_rx_filter_dp83815(struct gem_dev *dp)
{
        int             i;
        int             j;
        uint32_t        mode;
        uint8_t         *mac = dp->cur_addr.ether_addr_octet;
        uint16_t        hash_tbl[32];
        struct sfe_dev  *lp = dp->private;

        DPRINTF(1, (CE_CONT, CONS "%s: %s: called, mc_count:%d, mode:0x%b",
            dp->name, __func__, dp->mc_count, dp->rxmode, RXMODE_BITS));

#if DEBUG_LEVEL > 0
        for (i = 0; i < dp->mc_count; i++) {
                cmn_err(CE_CONT,
                "!%s: adding mcast(%d) %02x:%02x:%02x:%02x:%02x:%02x",
                    dp->name, i,
                    dp->mc_list[i].addr.ether_addr_octet[0],
                    dp->mc_list[i].addr.ether_addr_octet[1],
                    dp->mc_list[i].addr.ether_addr_octet[2],
                    dp->mc_list[i].addr.ether_addr_octet[3],
                    dp->mc_list[i].addr.ether_addr_octet[4],
                    dp->mc_list[i].addr.ether_addr_octet[5]);
        }
#endif
        if ((dp->rxmode & RXMODE_ENABLE) == 0) {
                /* disable rx filter */
                OUTL(dp, RFCR, 0);
                return (GEM_SUCCESS);
        }

        /*
         * Set Receive filter control register
         */
        if (dp->rxmode & RXMODE_PROMISC) {
                /* all broadcast, all multicast, all physical */
                mode = RFCR_AAB | RFCR_AAM | RFCR_AAP;
        } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 16*32/2) {
                /* all broadcast, all multicast, physical for the chip */
                mode = RFCR_AAB | RFCR_AAM | RFCR_APM_DP83815;
        } else if (dp->mc_count > 4) {
                /*
                 * Use multicast hash table,
                 * accept all broadcast and physical for the chip.
                 */
                mode = RFCR_AAB | RFCR_MHEN_DP83815 | RFCR_APM_DP83815;

                bzero(hash_tbl, sizeof (hash_tbl));
                for (i = 0; i < dp->mc_count; i++) {
                        j = dp->mc_list[i].hash >> (32 - 9);
                        hash_tbl[j / 16] |= 1 << (j % 16);
                }
        } else {
                /*
                 * Use pattern mach filter for multicast address,
                 * accept all broadcast and physical for the chip
                 */
                /* need to enable corresponding pattern registers */
                mode = RFCR_AAB | RFCR_APM_DP83815 |
                    (((1 << dp->mc_count) - 1) << RFCR_APAT_SHIFT);
        }

#if DEBUG_LEVEL > 1
        cmn_err(CE_CONT,
            "!%s: mac %02x:%02x:%02x:%02x:%02x:%02x"
            "  cache %02x:%02x:%02x:%02x:%02x:%02x",
            dp->name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
            lp->mac_addr[0], lp->mac_addr[1],
            lp->mac_addr[2], lp->mac_addr[3],
            lp->mac_addr[4], lp->mac_addr[5]);
#endif
        if (bcmp(mac, lp->mac_addr, ETHERADDRL) != 0) {
                /*
                 * XXX - need to *disable* rx filter to load mac address for
                 * the chip. otherwise, we cannot setup rxfilter correctly.
                 */
                /* setup perfect match register for my station address */
                for (i = 0; i < ETHERADDRL; i += 2) {
                        OUTL(dp, RFCR, RFADDR_MAC_DP83815 + i);
                        OUTL(dp, RFDR, (mac[i+1] << 8) | mac[i]);
                }

                bcopy(mac, lp->mac_addr, ETHERADDRL);
        }

#if DEBUG_LEVEL > 3
        /* clear pattern ram */
        for (j = 0x200; j < 0x380; j += 2) {
                OUTL(dp, RFCR, j);
                OUTL(dp, RFDR, 0);
        }
#endif
        if (mode & RFCR_APAT_DP83815) {
                /* setup multicast address into pattern match registers */
                for (j = 0; j < dp->mc_count; j++) {
                        mac = &dp->mc_list[j].addr.ether_addr_octet[0];
                        for (i = 0; i < ETHERADDRL; i += 2) {
                                OUTL(dp, RFCR,
                                    sfe_rf_perfect_base_dp83815[j] + i*2);
                                OUTL(dp, RFDR, (mac[i+1] << 8) | mac[i]);
                        }
                }

                /* setup pattern count registers */
                OUTL(dp, RFCR, RFADDR_PCOUNT01_DP83815);
                OUTL(dp, RFDR, (ETHERADDRL << 8) | ETHERADDRL);
                OUTL(dp, RFCR, RFADDR_PCOUNT23_DP83815);
                OUTL(dp, RFDR, (ETHERADDRL << 8) | ETHERADDRL);
        }

        if (mode & RFCR_MHEN_DP83815) {
                /* Load Multicast hash table */
                for (i = 0; i < 32; i++) {
                        /* for DP83815, index is in byte */
                        OUTL(dp, RFCR, RFADDR_MULTICAST_DP83815 + i*2);
                        OUTL(dp, RFDR, hash_tbl[i]);
                }
        }
#if DEBUG_LEVEL > 2
        sfe_rxfilter_dump(dp, 0, 0x10);
        sfe_rxfilter_dump(dp, 0x200, 0x380);
#endif
        /* Set rx filter mode and enable rx filter */
        OUTL(dp, RFCR, RFCR_RFEN | mode);

        return (GEM_SUCCESS);
}

static int
sfe_set_rx_filter_sis900(struct gem_dev *dp)
{
        int             i;
        uint32_t        mode;
        uint16_t        hash_tbl[16];
        uint8_t         *mac = dp->cur_addr.ether_addr_octet;
        int             hash_size;
        int             hash_shift;
        struct sfe_dev  *lp = dp->private;

        DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__));

        if ((dp->rxmode & RXMODE_ENABLE) == 0) {
                /* disable rx filter */
                OUTLINL(dp, RFCR, 0);
                return (GEM_SUCCESS);
        }

        /*
         * determine hardware hash table size in word.
         */
        hash_shift = 25;
        if (lp->revid >= SIS635A_900_REV || lp->revid == SIS900B_900_REV) {
                hash_shift = 24;
        }
        hash_size = (1 << (32 - hash_shift)) / 16;
        bzero(hash_tbl, sizeof (hash_tbl));

        /* Set Receive filter control register */

        if (dp->rxmode & RXMODE_PROMISC) {
                /* all broadcast, all multicast, all physical */
                mode = RFCR_AAB | RFCR_AAM | RFCR_AAP;
        } else if ((dp->rxmode & RXMODE_ALLMULTI) ||
            dp->mc_count > hash_size*16/2) {
                /* all broadcast, all multicast, physical for the chip */
                mode = RFCR_AAB | RFCR_AAM;
        } else {
                /* all broadcast, physical for the chip */
                mode = RFCR_AAB;
        }

        /* make hash table */
        for (i = 0; i < dp->mc_count; i++) {
                uint_t  h;
                h = dp->mc_list[i].hash >> hash_shift;
                hash_tbl[h / 16] |= 1 << (h % 16);
        }

        if (bcmp(mac, lp->mac_addr, ETHERADDRL) != 0) {
                /* Disable Rx filter and load mac address */
                for (i = 0; i < ETHERADDRL/2; i++) {
                        /* For sis900, index is in word */
                        OUTLINL(dp, RFCR,
                            (RFADDR_MAC_SIS900+i) << RFCR_RFADDR_SHIFT_SIS900);
                        OUTLINL(dp, RFDR, (mac[i*2+1] << 8) | mac[i*2]);
                }

                bcopy(mac, lp->mac_addr, ETHERADDRL);
        }

        /* Load Multicast hash table */
        for (i = 0; i < hash_size; i++) {
                /* For sis900, index is in word */
                OUTLINL(dp, RFCR,
                    (RFADDR_MULTICAST_SIS900 + i) << RFCR_RFADDR_SHIFT_SIS900);
                OUTLINL(dp, RFDR, hash_tbl[i]);
        }

        /* Load rx filter mode and enable rx filter */
        OUTLINL(dp, RFCR, RFCR_RFEN | mode);

        return (GEM_SUCCESS);
}

static int
sfe_start_chip(struct gem_dev *dp)
{
        struct sfe_dev  *lp = dp->private;

        DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__));

        /*
         * setup interrupt mask, which shouldn't include ISR_TOK
         * to improve performance.
         */
        lp->our_intr_bits = OUR_INTR_BITS;

        /* enable interrupt */
        if ((dp->misc_flag & GEM_NOINTR) == 0) {
                OUTL(dp, IER, 1);
                OUTL(dp, IMR, lp->our_intr_bits);
        }

        /* Kick RX */
        OUTL(dp, CR, lp->cr | CR_RXE);

        return (GEM_SUCCESS);
}

/*
 * Stop nic core gracefully.
 */
static int
sfe_stop_chip(struct gem_dev *dp)
{
        struct sfe_dev  *lp = dp->private;
        uint32_t        done;
        int             i;
        uint32_t        val;

        DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__));

        /*
         * Although we inhibit interrupt here, we don't clear soft copy of
         * interrupt mask to avoid bogus interrupts.
         */
        OUTL(dp, IMR, 0);

        /* stop TX and RX immediately */
        OUTL(dp, CR, lp->cr | CR_TXR | CR_RXR);

        done = 0;
        for (i = 0; done != (ISR_RXRCMP | ISR_TXRCMP); i++) {
                if (i > 1000) {
                        /*
                         * As gem layer will call sfe_reset_chip(),
                         * we don't neet to reset futher
                         */
                        cmn_err(CE_NOTE, "!%s: %s: Tx/Rx reset timeout",
                            dp->name, __func__);

                        return (GEM_FAILURE);
                }
                val = INL(dp, ISR);
                done |= val & (ISR_RXRCMP | ISR_TXRCMP);
                lp->isr_pended |= val & lp->our_intr_bits;
                drv_usecwait(10);
        }

        return (GEM_SUCCESS);
}

#ifndef __sparc
/*
 * Stop nic core gracefully for quiesce
 */
static int
sfe_stop_chip_quiesce(struct gem_dev *dp)
{
        struct sfe_dev  *lp = dp->private;
        uint32_t        done;
        int             i;
        uint32_t        val;

        /*
         * Although we inhibit interrupt here, we don't clear soft copy of
         * interrupt mask to avoid bogus interrupts.
         */
        OUTL(dp, IMR, 0);

        /* stop TX and RX immediately */
        OUTL(dp, CR, CR_TXR | CR_RXR);

        done = 0;
        for (i = 0; done != (ISR_RXRCMP | ISR_TXRCMP); i++) {
                if (i > 1000) {
                        /*
                         * As gem layer will call sfe_reset_chip(),
                         * we don't neet to reset futher
                         */

                        return (DDI_FAILURE);
                }
                val = INL(dp, ISR);
                done |= val & (ISR_RXRCMP | ISR_TXRCMP);
                lp->isr_pended |= val & lp->our_intr_bits;
                drv_usecwait(10);
        }
        return (DDI_SUCCESS);
}
#endif

/*
 * Setup media mode
 */
static uint_t
sfe_mxdma_value[] = { 512, 4, 8, 16, 32, 64, 128, 256, };

static uint_t
sfe_encode_mxdma(uint_t burstsize)
{
        int     i;

        if (burstsize > 256) {
                /* choose 512 */
                return (0);
        }

        for (i = 1; i < 8; i++) {
                if (burstsize <= sfe_mxdma_value[i]) {
                        break;
                }
        }
        return (i);
}

static int
sfe_set_media(struct gem_dev *dp)
{
        uint32_t        txcfg;
        uint32_t        rxcfg;
        uint32_t        pcr;
        uint32_t        val;
        uint32_t        txmxdma;
        uint32_t        rxmxdma;
        struct sfe_dev  *lp = dp->private;
#ifdef DEBUG_LEVEL
        extern int      gem_speed_value[];
#endif
        DPRINTF(2, (CE_CONT, CONS "%s: %s: %s duplex, %d Mbps",
            dp->name, __func__,
            dp->full_duplex ? "full" : "half", gem_speed_value[dp->speed]));

        /* initialize txcfg and rxcfg */
        txcfg = TXCFG_ATP;
        if (dp->full_duplex) {
                txcfg |= (TXCFG_CSI | TXCFG_HBI);
        }
        rxcfg = RXCFG_AEP | RXCFG_ARP;
        if (dp->full_duplex) {
                rxcfg |= RXCFG_ATX;
        }

        /* select txmxdma and rxmxdma, maxmum burst length */
        if (lp->chip->chip_type == CHIPTYPE_SIS900) {
#ifdef DEBUG_SIS900_EDB
                val = CFG_EDB_MASTER;
#else
                val = INL(dp, CFG) & CFG_EDB_MASTER;
#endif
                if (val) {
                        /*
                         * sis900 built-in cores:
                         * max burst length must be fixed to 64
                         */
                        txmxdma = 64;
                        rxmxdma = 64;
                } else {
                        /*
                         * sis900 pci chipset:
                         * the vendor recommended to fix max burst length
                         * to 512
                         */
                        txmxdma = 512;
                        rxmxdma = 512;
                }
        } else {
                /*
                 * NS dp83815/816:
                 * use user defined or default for tx/rx max burst length
                 */
                txmxdma = max(dp->txmaxdma, 256);
                rxmxdma = max(dp->rxmaxdma, 256);
        }


        /* tx high water mark */
        lp->tx_drain_threshold = ROUNDUP2(dp->txthr, TXCFG_FIFO_UNIT);

        /* determine tx_fill_threshold accroding drain threshold */
        lp->tx_fill_threshold =
            TXFIFOSIZE - lp->tx_drain_threshold - TXCFG_FIFO_UNIT;

        /* tune txmxdma not to exceed tx_fill_threshold */
        for (; ; ) {
                /* normalize txmxdma requested */
                val = sfe_encode_mxdma(txmxdma);
                txmxdma = sfe_mxdma_value[val];

                if (txmxdma <= lp->tx_fill_threshold) {
                        break;
                }
                /* select new txmxdma */
                txmxdma = txmxdma / 2;
        }
        txcfg |= val << TXCFG_MXDMA_SHIFT;

        /* encode rxmxdma, maxmum burst length for rx */
        val = sfe_encode_mxdma(rxmxdma);
        rxcfg |= val << RXCFG_MXDMA_SHIFT;
        rxmxdma = sfe_mxdma_value[val];

        /* receive starting threshold - it have only 5bit-wide field */
        val = ROUNDUP2(max(dp->rxthr, ETHERMIN), RXCFG_FIFO_UNIT);
        lp->rx_drain_threshold =
            min(val, (RXCFG_DRTH >> RXCFG_DRTH_SHIFT) * RXCFG_FIFO_UNIT);

        DPRINTF(0, (CE_CONT,
            "%s: %s: tx: drain:%d(rest %d) fill:%d mxdma:%d,"
            " rx: drain:%d mxdma:%d",
            dp->name, __func__,
            lp->tx_drain_threshold, TXFIFOSIZE - lp->tx_drain_threshold,
            lp->tx_fill_threshold, txmxdma,
            lp->rx_drain_threshold, rxmxdma));

        ASSERT(lp->tx_drain_threshold < 64*TXCFG_FIFO_UNIT);
        ASSERT(lp->tx_fill_threshold < 64*TXCFG_FIFO_UNIT);
        ASSERT(lp->rx_drain_threshold < 32*RXCFG_FIFO_UNIT);

        txcfg |= ((lp->tx_fill_threshold/TXCFG_FIFO_UNIT) << TXCFG_FLTH_SHIFT)
            | (lp->tx_drain_threshold/TXCFG_FIFO_UNIT);
        OUTL(dp, TXCFG, txcfg);

        rxcfg |= ((lp->rx_drain_threshold/RXCFG_FIFO_UNIT) << RXCFG_DRTH_SHIFT);
        if (lp->chip->chip_type == CHIPTYPE_DP83815) {
                rxcfg |= RXCFG_ALP_DP83815;
        }
        OUTL(dp, RXCFG, rxcfg);

        DPRINTF(0, (CE_CONT, CONS "%s: %s: txcfg:%b rxcfg:%b",
            dp->name, __func__,
            txcfg, TXCFG_BITS, rxcfg, RXCFG_BITS));

        /* Flow control */
        if (lp->chip->chip_type == CHIPTYPE_DP83815) {
                pcr = INL(dp, PCR);
                switch (dp->flow_control) {
                case FLOW_CONTROL_SYMMETRIC:
                case FLOW_CONTROL_RX_PAUSE:
                        OUTL(dp, PCR, pcr | PCR_PSEN | PCR_PS_MCAST);
                        break;

                default:
                        OUTL(dp, PCR,
                            pcr & ~(PCR_PSEN | PCR_PS_MCAST | PCR_PS_DA));
                        break;
                }
                DPRINTF(2, (CE_CONT, CONS "%s: PCR: %b", dp->name,
                    INL(dp, PCR), PCR_BITS));

        } else if (lp->chip->chip_type == CHIPTYPE_SIS900) {
                switch (dp->flow_control) {
                case FLOW_CONTROL_SYMMETRIC:
                case FLOW_CONTROL_RX_PAUSE:
                        OUTL(dp, FLOWCTL, FLOWCTL_FLOWEN);
                        break;
                default:
                        OUTL(dp, FLOWCTL, 0);
                        break;
                }
                DPRINTF(2, (CE_CONT, CONS "%s: FLOWCTL: %b",
                    dp->name, INL(dp, FLOWCTL), FLOWCTL_BITS));
        }
        return (GEM_SUCCESS);
}

static int
sfe_get_stats(struct gem_dev *dp)
{
        /* do nothing */
        return (GEM_SUCCESS);
}

/*
 * descriptor manipulations
 */
static int
sfe_tx_desc_write(struct gem_dev *dp, int slot,
    ddi_dma_cookie_t *dmacookie, int frags, uint64_t flags)
{
        uint32_t                mark;
        struct sfe_desc         *tdp;
        ddi_dma_cookie_t        *dcp;
        uint32_t                tmp0;
#if DEBUG_LEVEL > 2
        int                     i;

        cmn_err(CE_CONT,
            CONS "%s: time:%d %s seqnum: %d, slot %d, frags: %d flags: %llx",
            dp->name, ddi_get_lbolt(), __func__,
            dp->tx_desc_tail, slot, frags, flags);

        for (i = 0; i < frags; i++) {
                cmn_err(CE_CONT, CONS "%d: addr: 0x%x, len: 0x%x",
                    i, dmacookie[i].dmac_address, dmacookie[i].dmac_size);
        }
#endif
        /*
         * write tx descriptor in reversed order.
         */
#if DEBUG_LEVEL > 3
        flags |= GEM_TXFLAG_INTR;
#endif
        mark = (flags & GEM_TXFLAG_INTR)
            ? (CMDSTS_OWN | CMDSTS_INTR) : CMDSTS_OWN;

        ASSERT(frags == 1);
        dcp = &dmacookie[0];
        if (flags & GEM_TXFLAG_HEAD) {
                mark &= ~CMDSTS_OWN;
        }

        tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot];
        tmp0 = (uint32_t)dcp->dmac_address;
        mark |= (uint32_t)dcp->dmac_size;
        tdp->d_bufptr = LE_32(tmp0);
        tdp->d_cmdsts = LE_32(mark);

        return (frags);
}

static void
sfe_tx_start(struct gem_dev *dp, int start_slot, int nslot)
{
        uint_t                  tx_ring_size = dp->gc.gc_tx_ring_size;
        struct sfe_desc         *tdp;
        struct sfe_dev          *lp = dp->private;

        if (nslot > 1) {
                gem_tx_desc_dma_sync(dp,
                    SLOT(start_slot + 1, tx_ring_size),
                    nslot - 1, DDI_DMA_SYNC_FORDEV);
        }

        tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * start_slot];
        tdp->d_cmdsts |= LE_32(CMDSTS_OWN);

        gem_tx_desc_dma_sync(dp, start_slot, 1, DDI_DMA_SYNC_FORDEV);

        /*
         * Let the Transmit Buffer Manager Fill state machine active.
         */
        if (dp->mac_active) {
                OUTL(dp, CR, lp->cr | CR_TXE);
        }
}

static void
sfe_rx_desc_write(struct gem_dev *dp, int slot,
    ddi_dma_cookie_t *dmacookie, int frags)
{
        struct sfe_desc         *rdp;
        uint32_t                tmp0;
        uint32_t                tmp1;
#if DEBUG_LEVEL > 2
        int                     i;

        ASSERT(frags == 1);

        cmn_err(CE_CONT, CONS
            "%s: %s seqnum: %d, slot %d, frags: %d",
            dp->name, __func__, dp->rx_active_tail, slot, frags);
        for (i = 0; i < frags; i++) {
                cmn_err(CE_CONT, CONS "  frag: %d addr: 0x%llx, len: 0x%lx",
                    i, dmacookie[i].dmac_address, dmacookie[i].dmac_size);
        }
#endif
        /* for the last slot of the packet */
        rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot];

        tmp0 = (uint32_t)dmacookie->dmac_address;
        tmp1 = CMDSTS_INTR | (uint32_t)dmacookie->dmac_size;
        rdp->d_bufptr = LE_32(tmp0);
        rdp->d_cmdsts = LE_32(tmp1);
}

static uint_t
sfe_tx_desc_stat(struct gem_dev *dp, int slot, int ndesc)
{
        uint_t                  tx_ring_size = dp->gc.gc_tx_ring_size;
        struct sfe_desc         *tdp;
        uint32_t                status;
        int                     cols;
        struct sfe_dev          *lp = dp->private;
#ifdef DEBUG_LEVEL
        int                     i;
        clock_t                 delay;
#endif
        /* check status of the last descriptor */
        tdp = (void *)
            &dp->tx_ring[SFE_DESC_SIZE * SLOT(slot + ndesc - 1, tx_ring_size)];

        /*
         * Don't use LE_32() directly to refer tdp->d_cmdsts.
         * It is not atomic for big endian cpus.
         */
        status = tdp->d_cmdsts;
        status = LE_32(status);

        DPRINTF(2, (CE_CONT, CONS "%s: time:%ld %s: slot:%d, status:0x%b",
            dp->name, ddi_get_lbolt(), __func__,
            slot, status, TXSTAT_BITS));

        if (status & CMDSTS_OWN) {
                /*
                 * not yet transmitted
                 */
                /* workaround for tx hang */
                if (lp->chip->chip_type == CHIPTYPE_DP83815 &&
                    dp->mac_active) {
                        OUTL(dp, CR, lp->cr | CR_TXE);
                }
                return (0);
        }

        if (status & CMDSTS_MORE) {
                /* XXX - the hardware problem but don't panic the system */
                /* avoid lint bug for %b format string including 32nd bit */
                cmn_err(CE_NOTE, CONS
                    "%s: tx status bits incorrect:  slot:%d, status:0x%x",
                    dp->name, slot, status);
        }

#if DEBUG_LEVEL > 3
        delay = (ddi_get_lbolt() - dp->tx_buf_head->txb_stime) * 10;
        if (delay >= 50) {
                DPRINTF(0, (CE_NOTE, "%s: tx deferred %d mS: slot %d",
                    dp->name, delay, slot));
        }
#endif

#if DEBUG_LEVEL > 3
        for (i = 0; i < nfrag-1; i++) {
                uint32_t        s;
                int             n;

                n = SLOT(slot + i, tx_ring_size);
                s = LE_32(
                    ((struct sfe_desc *)((void *)
                    &dp->tx_ring[SFE_DESC_SIZE * n]))->d_cmdsts);

                ASSERT(s & CMDSTS_MORE);
                ASSERT((s & CMDSTS_OWN) == 0);
        }
#endif

        /*
         *  collect statistics
         */
        if ((status & CMDSTS_OK) == 0) {

                /* failed to transmit the packet */

                DPRINTF(0, (CE_CONT, CONS "%s: Transmit error, Tx status %b",
                    dp->name, status, TXSTAT_BITS));

                dp->stats.errxmt++;

                if (status & CMDSTS_TFU) {
                        dp->stats.underflow++;
                } else if (status & CMDSTS_CRS) {
                        dp->stats.nocarrier++;
                } else if (status & CMDSTS_OWC) {
                        dp->stats.xmtlatecoll++;
                } else if ((!dp->full_duplex) && (status & CMDSTS_EC)) {
                        dp->stats.excoll++;
                        dp->stats.collisions += 16;
                } else {
                        dp->stats.xmit_internal_err++;
                }
        } else if (!dp->full_duplex) {
                cols = (status >> CMDSTS_CCNT_SHIFT) & CCNT_MASK;

                if (cols > 0) {
                        if (cols == 1) {
                                dp->stats.first_coll++;
                        } else /* (cols > 1) */ {
                                dp->stats.multi_coll++;
                        }
                        dp->stats.collisions += cols;
                } else if (status & CMDSTS_TD) {
                        dp->stats.defer++;
                }
        }
        return (GEM_TX_DONE);
}

static uint64_t
sfe_rx_desc_stat(struct gem_dev *dp, int slot, int ndesc)
{
        struct sfe_desc         *rdp;
        uint_t                  len;
        uint_t                  flag;
        uint32_t                status;

        flag = GEM_RX_DONE;

        /* Dont read ISR because we cannot ack only to rx interrupt. */

        rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot];

        /*
         * Don't use LE_32() directly to refer rdp->d_cmdsts.
         * It is not atomic for big endian cpus.
         */
        status = rdp->d_cmdsts;
        status = LE_32(status);

        DPRINTF(2, (CE_CONT, CONS "%s: time:%ld %s: slot:%d, status:0x%b",
            dp->name, ddi_get_lbolt(), __func__,
            slot, status, RXSTAT_BITS));

        if ((status & CMDSTS_OWN) == 0) {
                /*
                 * No more received packets because
                 * this buffer is owned by NIC.
                 */
                return (0);
        }

#define RX_ERR_BITS \
        (CMDSTS_RXA | CMDSTS_RXO | CMDSTS_LONG | CMDSTS_RUNT | \
                CMDSTS_ISE | CMDSTS_CRCE | CMDSTS_FAE | CMDSTS_MORE)

        if (status & RX_ERR_BITS) {
                /*
                 * Packet with error received
                 */
                DPRINTF(0, (CE_CONT, CONS "%s: Corrupted packet "
                    "received, buffer status: %b",
                    dp->name, status, RXSTAT_BITS));

                /* collect statistics information */
                dp->stats.errrcv++;

                if (status & CMDSTS_RXO) {
                        dp->stats.overflow++;
                } else if (status & (CMDSTS_LONG | CMDSTS_MORE)) {
                        dp->stats.frame_too_long++;
                } else if (status & CMDSTS_RUNT) {
                        dp->stats.runt++;
                } else if (status & (CMDSTS_ISE | CMDSTS_FAE)) {
                        dp->stats.frame++;
                } else if (status & CMDSTS_CRCE) {
                        dp->stats.crc++;
                } else {
                        dp->stats.rcv_internal_err++;
                }

                return (flag | GEM_RX_ERR);
        }

        /*
         * this packet was received without errors
         */
        if ((len = (status & CMDSTS_SIZE)) >= ETHERFCSL) {
                len -= ETHERFCSL;
        }

#if DEBUG_LEVEL > 10
{
        int     i;
        uint8_t *bp = dp->rx_buf_head->rxb_buf;

        cmn_err(CE_CONT, CONS "%s: len:%d", dp->name, len);

        for (i = 0; i < 60; i += 10) {
                cmn_err(CE_CONT, CONS
                    "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
                    bp[0], bp[1], bp[2], bp[3], bp[4],
                    bp[5], bp[6], bp[7], bp[8], bp[9]);
        }
        bp += 10;
}
#endif
        return (flag | (len & GEM_RX_LEN));
}

static void
sfe_tx_desc_init(struct gem_dev *dp, int slot)
{
        uint_t                  tx_ring_size = dp->gc.gc_tx_ring_size;
        struct sfe_desc         *tdp;
        uint32_t                here;

        tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot];

        /* don't clear d_link field, which have a valid pointer */
        tdp->d_cmdsts = 0;

        /* make a link to this from the previous descriptor */
        here = ((uint32_t)dp->tx_ring_dma) + SFE_DESC_SIZE*slot;

        tdp = (void *)
            &dp->tx_ring[SFE_DESC_SIZE * SLOT(slot - 1, tx_ring_size)];
        tdp->d_link = LE_32(here);
}

static void
sfe_rx_desc_init(struct gem_dev *dp, int slot)
{
        uint_t                  rx_ring_size = dp->gc.gc_rx_ring_size;
        struct sfe_desc         *rdp;
        uint32_t                here;

        rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot];

        /* don't clear d_link field, which have a valid pointer */
        rdp->d_cmdsts = LE_32(CMDSTS_OWN);

        /* make a link to this from the previous descriptor */
        here = ((uint32_t)dp->rx_ring_dma) + SFE_DESC_SIZE*slot;

        rdp = (void *)
            &dp->rx_ring[SFE_DESC_SIZE * SLOT(slot - 1, rx_ring_size)];
        rdp->d_link = LE_32(here);
}

static void
sfe_tx_desc_clean(struct gem_dev *dp, int slot)
{
        struct sfe_desc         *tdp;

        tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot];
        tdp->d_cmdsts = 0;
}

static void
sfe_rx_desc_clean(struct gem_dev *dp, int slot)
{
        struct sfe_desc         *rdp;

        rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot];
        rdp->d_cmdsts = LE_32(CMDSTS_OWN);
}

/*
 * Device depend interrupt handler
 */
static uint_t
sfe_interrupt(struct gem_dev *dp)
{
        uint_t          rx_ring_size = dp->gc.gc_rx_ring_size;
        uint32_t        isr;
        uint32_t        isr_bogus;
        uint_t          flags = 0;
        struct sfe_dev  *lp = dp->private;

        /* read reason and clear interrupt */
        isr = INL(dp, ISR);

        isr_bogus = lp->isr_pended;
        lp->isr_pended = 0;

        if (((isr | isr_bogus) & lp->our_intr_bits) == 0) {
                /* we are not the interrupt source */
                return (DDI_INTR_UNCLAIMED);
        }

        DPRINTF(3, (CE_CONT,
            CONS "%s: time:%ld %s:called: isr:0x%b rx_active_head: %d",
            dp->name, ddi_get_lbolt(), __func__,
            isr, INTR_BITS, dp->rx_active_head));

        if (!dp->mac_active) {
                /* the device is going to stop */
                lp->our_intr_bits = 0;
                return (DDI_INTR_CLAIMED);
        }

        isr &= lp->our_intr_bits;

        if (isr & (ISR_RXSOVR | ISR_RXORN | ISR_RXIDLE | ISR_RXERR |
            ISR_RXDESC | ISR_RXOK)) {
                (void) gem_receive(dp);

                if (isr & (ISR_RXSOVR | ISR_RXORN)) {
                        DPRINTF(0, (CE_CONT,
                            CONS "%s: rx fifo overrun: isr %b",
                            dp->name, isr, INTR_BITS));
                        /* no need restart rx */
                        dp->stats.overflow++;
                }

                if (isr & ISR_RXIDLE) {
                        DPRINTF(0, (CE_CONT,
                            CONS "%s: rx buffer ran out: isr %b",
                            dp->name, isr, INTR_BITS));

                        dp->stats.norcvbuf++;

                        /*
                         * Make RXDP points the head of receive
                         * buffer list.
                         */
                        OUTL(dp, RXDP, dp->rx_ring_dma +
                            SFE_DESC_SIZE *
                            SLOT(dp->rx_active_head, rx_ring_size));

                        /* Restart the receive engine */
                        OUTL(dp, CR, lp->cr | CR_RXE);
                }
        }

        if (isr & (ISR_TXURN | ISR_TXERR | ISR_TXDESC |
            ISR_TXIDLE | ISR_TXOK)) {
                /* need to reclaim tx buffers */
                if (gem_tx_done(dp)) {
                        flags |= INTR_RESTART_TX;
                }
                /*
                 * XXX - tx error statistics will be counted in
                 * sfe_tx_desc_stat() and no need to restart tx on errors.
                 */
        }

        if (isr & (ISR_DPERR | ISR_SSERR | ISR_RMABT | ISR_RTABT)) {
                cmn_err(CE_WARN, "%s: ERROR interrupt: isr %b.",
                    dp->name, isr, INTR_BITS);

                (void) gem_restart_nic(dp, GEM_RESTART_KEEP_BUF);
                flags |= INTR_RESTART_TX;
        }

        DPRINTF(5, (CE_CONT, CONS "%s: %s: return: isr: %b",
            dp->name, __func__, isr, INTR_BITS));

        return (DDI_INTR_CLAIMED | flags);
}

/* ======================================================== */
/*
 * HW depend MII routine
 */
/* ======================================================== */

/*
 * MII routines for NS DP83815
 */
static void
sfe_mii_sync_dp83815(struct gem_dev *dp)
{
        /* do nothing */
}

static uint16_t
sfe_mii_read_dp83815(struct gem_dev *dp, uint_t offset)
{
        DPRINTF(4, (CE_CONT, CONS"%s: %s: offset 0x%x",
            dp->name, __func__, offset));
        return ((uint16_t)INL(dp, MII_REGS_BASE + offset*4));
}

static void
sfe_mii_write_dp83815(struct gem_dev *dp, uint_t offset, uint16_t val)
{
        DPRINTF(4, (CE_CONT, CONS"%s: %s: offset 0x%x 0x%x",
            dp->name, __func__, offset, val));
        OUTL(dp, MII_REGS_BASE + offset*4, val);
}

static int
sfe_mii_config_dp83815(struct gem_dev *dp)
{
        uint32_t        srr;

        srr = INL(dp, SRR) & SRR_REV;

        DPRINTF(0, (CE_CONT, CONS "%s: srr:0x%04x %04x %04x %04x %04x %04x",
            dp->name, srr,
            INW(dp, 0x00cc),    /* PGSEL */
            INW(dp, 0x00e4),    /* PMDCSR */
            INW(dp, 0x00fc),    /* TSTDAT */
            INW(dp, 0x00f4),    /* DSPCFG */
            INW(dp, 0x00f8)));  /* SDCFG */

        if (srr == SRR_REV_DP83815CVNG) {
                /*
                 * NS datasheet says that DP83815CVNG needs following
                 * registers to be patched for optimizing its performance.
                 * A report said that CRC errors on RX disappeared
                 * with the patch.
                 */
                OUTW(dp, 0x00cc, 0x0001);       /* PGSEL */
                OUTW(dp, 0x00e4, 0x189c);       /* PMDCSR */
                OUTW(dp, 0x00fc, 0x0000);       /* TSTDAT */
                OUTW(dp, 0x00f4, 0x5040);       /* DSPCFG */
                OUTW(dp, 0x00f8, 0x008c);       /* SDCFG */
                OUTW(dp, 0x00cc, 0x0000);       /* PGSEL */

                DPRINTF(0, (CE_CONT,
                    CONS "%s: PHY patched %04x %04x %04x %04x %04x",
                    dp->name,
                    INW(dp, 0x00cc),    /* PGSEL */
                    INW(dp, 0x00e4),    /* PMDCSR */
                    INW(dp, 0x00fc),    /* TSTDAT */
                    INW(dp, 0x00f4),    /* DSPCFG */
                    INW(dp, 0x00f8)));  /* SDCFG */
        } else if (((srr ^ SRR_REV_DP83815DVNG) & 0xff00) == 0 ||
            ((srr ^ SRR_REV_DP83816AVNG) & 0xff00) == 0) {
                /*
                 * Additional packets for later chipset
                 */
                OUTW(dp, 0x00cc, 0x0001);       /* PGSEL */
                OUTW(dp, 0x00e4, 0x189c);       /* PMDCSR */
                OUTW(dp, 0x00cc, 0x0000);       /* PGSEL */

                DPRINTF(0, (CE_CONT,
                    CONS "%s: PHY patched %04x %04x",
                    dp->name,
                    INW(dp, 0x00cc),    /* PGSEL */
                    INW(dp, 0x00e4)));  /* PMDCSR */
        }

        return (gem_mii_config_default(dp));
}

static int
sfe_mii_probe_dp83815(struct gem_dev *dp)
{
        uint32_t        val;

        /* try external phy first */
        DPRINTF(0, (CE_CONT, CONS "%s: %s: trying external phy",
            dp->name, __func__));
        dp->mii_phy_addr = 0;
        dp->gc.gc_mii_sync = &sfe_mii_sync_sis900;
        dp->gc.gc_mii_read = &sfe_mii_read_sis900;
        dp->gc.gc_mii_write = &sfe_mii_write_sis900;

        val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG);
        OUTL(dp, CFG, val | CFG_EXT_PHY | CFG_PHY_DIS);

        if (gem_mii_probe_default(dp) == GEM_SUCCESS) {
                return (GEM_SUCCESS);
        }

        /* switch to internal phy */
        DPRINTF(0, (CE_CONT, CONS "%s: %s: switching to internal phy",
            dp->name, __func__));
        dp->mii_phy_addr = -1;
        dp->gc.gc_mii_sync = &sfe_mii_sync_dp83815;
        dp->gc.gc_mii_read = &sfe_mii_read_dp83815;
        dp->gc.gc_mii_write = &sfe_mii_write_dp83815;

        val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG);
        OUTL(dp, CFG, val | CFG_PAUSE_ADV | CFG_PHY_RST);
        drv_usecwait(100);      /* keep to assert RST bit for a while */
        OUTL(dp, CFG, val | CFG_PAUSE_ADV);

        /* wait for PHY reset */
        delay(drv_usectohz(10000));

        return (gem_mii_probe_default(dp));
}

static int
sfe_mii_init_dp83815(struct gem_dev *dp)
{
        uint32_t        val;

        val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG);

        if (dp->mii_phy_addr == -1) {
                /* select internal phy */
                OUTL(dp, CFG, val | CFG_PAUSE_ADV);
        } else {
                /* select external phy */
                OUTL(dp, CFG, val | CFG_EXT_PHY | CFG_PHY_DIS);
        }

        return (GEM_SUCCESS);
}

/*
 * MII routines for SiS900
 */
#define MDIO_DELAY(dp)  {(void) INL(dp, MEAR); (void) INL(dp, MEAR); }
static void
sfe_mii_sync_sis900(struct gem_dev *dp)
{
        int     i;

        /* send 32 ONE's to make MII line idle */
        for (i = 0; i < 32; i++) {
                OUTL(dp, MEAR, MEAR_MDDIR | MEAR_MDIO);
                MDIO_DELAY(dp);
                OUTL(dp, MEAR, MEAR_MDDIR | MEAR_MDIO | MEAR_MDC);
                MDIO_DELAY(dp);
        }
}

static int
sfe_mii_config_sis900(struct gem_dev *dp)
{
        struct sfe_dev  *lp = dp->private;

        /* Do chip depend setup */
        if ((dp->mii_phy_id & PHY_MASK) == PHY_ICS1893) {
                /* workaround for ICS1893 PHY */
                gem_mii_write(dp, 0x0018, 0xD200);
        }

        if (lp->revid == SIS630E_900_REV) {
                /*
                 * SiS 630E has bugs on default values
                 * of PHY registers
                 */
                gem_mii_write(dp, MII_AN_ADVERT, 0x05e1);
                gem_mii_write(dp, MII_CONFIG1, 0x0022);
                gem_mii_write(dp, MII_CONFIG2, 0xff00);
                gem_mii_write(dp, MII_MASK,    0xffc0);
        }
        sfe_set_eq_sis630(dp);

        return (gem_mii_config_default(dp));
}

static uint16_t
sfe_mii_read_sis900(struct gem_dev *dp, uint_t reg)
{
        uint32_t        cmd;
        uint16_t        ret;
        int             i;
        uint32_t        data;

        cmd = MII_READ_CMD(dp->mii_phy_addr, reg);

        for (i = 31; i >= 18; i--) {
                data = ((cmd >> i) & 1) <<  MEAR_MDIO_SHIFT;
                OUTL(dp, MEAR, data | MEAR_MDDIR);
                MDIO_DELAY(dp);
                OUTL(dp, MEAR, data | MEAR_MDDIR | MEAR_MDC);
                MDIO_DELAY(dp);
        }

        /* turn around cycle */
        OUTL(dp, MEAR, 0);
        MDIO_DELAY(dp);

        /* get response from PHY */
        OUTL(dp, MEAR, MEAR_MDC);
        MDIO_DELAY(dp);

        OUTL(dp, MEAR, 0);
#if DEBUG_LEBEL > 0
        (void) INL(dp, MEAR);   /* delay */
        if (INL(dp, MEAR) & MEAR_MDIO) {
                cmn_err(CE_WARN, "%s: PHY@%d not responded",
                    dp->name, dp->mii_phy_addr);
        }
#else
        MDIO_DELAY(dp);
#endif
        /* terminate response cycle */
        OUTL(dp, MEAR, MEAR_MDC);
        MDIO_DELAY(dp);

        ret = 0;        /* to avoid lint errors */
        for (i = 16; i > 0; i--) {
                OUTL(dp, MEAR, 0);
                (void) INL(dp, MEAR);   /* delay */
                ret = (ret << 1) | ((INL(dp, MEAR) >> MEAR_MDIO_SHIFT) & 1);
                OUTL(dp, MEAR, MEAR_MDC);
                MDIO_DELAY(dp);
        }

        /* send two idle(Z) bits to terminate the read cycle */
        for (i = 0; i < 2; i++) {
                OUTL(dp, MEAR, 0);
                MDIO_DELAY(dp);
                OUTL(dp, MEAR, MEAR_MDC);
                MDIO_DELAY(dp);
        }

        return (ret);
}

static void
sfe_mii_write_sis900(struct gem_dev *dp, uint_t reg, uint16_t val)
{
        uint32_t        cmd;
        int             i;
        uint32_t        data;

        cmd = MII_WRITE_CMD(dp->mii_phy_addr, reg, val);

        for (i = 31; i >= 0; i--) {
                data = ((cmd >> i) & 1) << MEAR_MDIO_SHIFT;
                OUTL(dp, MEAR, data | MEAR_MDDIR);
                MDIO_DELAY(dp);
                OUTL(dp, MEAR, data | MEAR_MDDIR | MEAR_MDC);
                MDIO_DELAY(dp);
        }

        /* send two idle(Z) bits to terminate the write cycle. */
        for (i = 0; i < 2; i++) {
                OUTL(dp, MEAR, 0);
                MDIO_DELAY(dp);
                OUTL(dp, MEAR, MEAR_MDC);
                MDIO_DELAY(dp);
        }
}
#undef MDIO_DELAY

static void
sfe_set_eq_sis630(struct gem_dev *dp)
{
        uint16_t        reg14h;
        uint16_t        eq_value;
        uint16_t        max_value;
        uint16_t        min_value;
        int             i;
        uint8_t         rev;
        struct sfe_dev  *lp = dp->private;

        rev = lp->revid;

        if (!(rev == SIS630E_900_REV || rev == SIS630EA1_900_REV ||
            rev == SIS630A_900_REV || rev == SIS630ET_900_REV)) {
                /* it doesn't have a internal PHY */
                return;
        }

        if (dp->mii_state == MII_STATE_LINKUP) {
                reg14h = gem_mii_read(dp, MII_RESV);
                gem_mii_write(dp, MII_RESV, (0x2200 | reg14h) & 0xBFFF);

                eq_value = (0x00f8 & gem_mii_read(dp, MII_RESV)) >> 3;
                max_value = min_value = eq_value;
                for (i = 1; i < 10; i++) {
                        eq_value = (0x00f8 & gem_mii_read(dp, MII_RESV)) >> 3;
                        max_value = max(eq_value, max_value);
                        min_value = min(eq_value, min_value);
                }

                /* for 630E, rule to determine the equalizer value */
                if (rev == SIS630E_900_REV || rev == SIS630EA1_900_REV ||
                    rev == SIS630ET_900_REV) {
                        if (max_value < 5) {
                                eq_value = max_value;
                        } else if (5 <= max_value && max_value < 15) {
                                eq_value =
                                    max(max_value + 1,
                                    min_value + 2);
                        } else if (15 <= max_value) {
                                eq_value =
                                    max(max_value + 5,
                                    min_value + 6);
                        }
                }
                /* for 630B0&B1, rule to determine the equalizer value */
                else
                if (rev == SIS630A_900_REV &&
                    (lp->bridge_revid == SIS630B0 ||
                    lp->bridge_revid == SIS630B1)) {

                        if (max_value == 0) {
                                eq_value = 3;
                        } else {
                                eq_value = (max_value + min_value + 1)/2;
                        }
                }
                /* write equalizer value and setting */
                reg14h = gem_mii_read(dp, MII_RESV) & ~0x02f8;
                reg14h |= 0x6000 | (eq_value << 3);
                gem_mii_write(dp, MII_RESV, reg14h);
        } else {
                reg14h = (gem_mii_read(dp, MII_RESV) & ~0x4000) | 0x2000;
                if (rev == SIS630A_900_REV &&
                    (lp->bridge_revid == SIS630B0 ||
                    lp->bridge_revid == SIS630B1)) {

                        reg14h |= 0x0200;
                }
                gem_mii_write(dp, MII_RESV, reg14h);
        }
}

/* ======================================================== */
/*
 * OS depend (device driver) routine
 */
/* ======================================================== */
static void
sfe_chipinfo_init_sis900(struct gem_dev *dp)
{
        int             rev;
        struct sfe_dev  *lp = (struct sfe_dev *)dp->private;

        rev = lp->revid;

        if (rev == SIS962_900_REV /* 0x91 */) {
                /* sis962 or later */
                lp->get_mac_addr = &sfe_get_mac_addr_sis962;
        } else {
                /* sis900 */
                lp->get_mac_addr = &sfe_get_mac_addr_sis900;
        }

        lp->bridge_revid = 0;

        if (rev == SIS630E_900_REV || rev == SIS630EA1_900_REV ||
            rev == SIS630A_900_REV || rev ==  SIS630ET_900_REV) {
                /*
                 * read host bridge revision
                 */
                dev_info_t      *bridge;
                ddi_acc_handle_t bridge_handle;

                if ((bridge = sfe_search_pci_dev(0x1039, 0x630)) == NULL) {
                        cmn_err(CE_WARN,
                            "%s: cannot find host bridge (pci1039,630)",
                            dp->name);
                        return;
                }

                if (pci_config_setup(bridge, &bridge_handle) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "%s: pci_config_setup failed",
                            dp->name);
                        return;
                }

                lp->bridge_revid =
                    pci_config_get8(bridge_handle, PCI_CONF_REVID);
                pci_config_teardown(&bridge_handle);
        }
}

static int
sfe_attach_chip(struct gem_dev *dp)
{
        struct sfe_dev          *lp = (struct sfe_dev *)dp->private;

        DPRINTF(4, (CE_CONT, CONS "!%s: %s called", dp->name, __func__));

        /* setup chip-depend get_mac_address function */
        if (lp->chip->chip_type == CHIPTYPE_SIS900) {
                sfe_chipinfo_init_sis900(dp);
        } else {
                lp->get_mac_addr = &sfe_get_mac_addr_dp83815;
        }

        /* read MAC address */
        if (!(lp->get_mac_addr)(dp)) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to get factory mac address"
                    " please specify a mac address in sfe.conf",
                    dp->name, __func__);
                return (GEM_FAILURE);
        }

        if (lp->chip->chip_type == CHIPTYPE_DP83815) {
                dp->mii_phy_addr = -1;  /* no need to scan PHY */
                dp->misc_flag |= GEM_VLAN_SOFT;
                dp->txthr += 4; /* VTAG_SIZE */
        }
        dp->txthr = min(dp->txthr, TXFIFOSIZE - 2);

        return (GEM_SUCCESS);
}

static int
sfeattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        int                     unit;
        const char              *drv_name;
        int                     i;
        ddi_acc_handle_t        conf_handle;
        uint16_t                vid;
        uint16_t                did;
        uint8_t                 rev;
#ifdef DEBUG_LEVEL
        uint32_t                iline;
        uint8_t                 latim;
#endif
        struct chip_info        *p;
        struct gem_dev          *dp;
        struct sfe_dev          *lp;
        caddr_t                 base;
        ddi_acc_handle_t        regs_ha;
        struct gem_conf         *gcp;

        unit = ddi_get_instance(dip);
        drv_name = ddi_driver_name(dip);

        DPRINTF(3, (CE_CONT, CONS "%s%d: sfeattach: called", drv_name, unit));

        /*
         * Common codes after power-up
         */
        if (pci_config_setup(dip, &conf_handle) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s%d: ddi_regs_map_setup failed",
                    drv_name, unit);
                goto err;
        }

        vid  = pci_config_get16(conf_handle, PCI_CONF_VENID);
        did  = pci_config_get16(conf_handle, PCI_CONF_DEVID);
        rev  = pci_config_get16(conf_handle, PCI_CONF_REVID);
#ifdef DEBUG_LEVEL
        iline = pci_config_get32(conf_handle, PCI_CONF_ILINE);
        latim = pci_config_get8(conf_handle, PCI_CONF_LATENCY_TIMER);
#endif
#ifdef DEBUG_BUILT_IN_SIS900
        rev  = SIS630E_900_REV;
#endif
        for (i = 0, p = sfe_chiptbl; i < CHIPTABLESIZE; i++, p++) {
                if (p->venid == vid && p->devid == did) {
                        /* found */
                        goto chip_found;
                }
        }

        /* Not found */
        cmn_err(CE_WARN,
            "%s%d: sfe_attach: wrong PCI venid/devid (0x%x, 0x%x)",
            drv_name, unit, vid, did);
        pci_config_teardown(&conf_handle);
        goto err;

chip_found:
        pci_config_put16(conf_handle, PCI_CONF_COMM,
            PCI_COMM_IO | PCI_COMM_MAE | PCI_COMM_ME |
            pci_config_get16(conf_handle, PCI_CONF_COMM));

        /* ensure D0 mode */
        (void) gem_pci_set_power_state(dip, conf_handle, PCI_PMCSR_D0);

        pci_config_teardown(&conf_handle);

        switch (cmd) {
        case DDI_RESUME:
                return (gem_resume(dip));

        case DDI_ATTACH:

                DPRINTF(0, (CE_CONT,
                    CONS "%s%d: ilr 0x%08x, latency_timer:0x%02x",
                    drv_name, unit, iline, latim));

                /*
                 * Map in the device registers.
                 */
                if (gem_pci_regs_map_setup(dip,
                    (sfe_use_pcimemspace && p->chip_type == CHIPTYPE_DP83815)
                    ? PCI_ADDR_MEM32 : PCI_ADDR_IO, PCI_ADDR_MASK,
                    &sfe_dev_attr, &base, &regs_ha) != DDI_SUCCESS) {
                        cmn_err(CE_WARN,
                            "%s%d: ddi_regs_map_setup failed",
                            drv_name, unit);
                        goto err;
                }

                /*
                 * construct gem configuration
                 */
                gcp = kmem_zalloc(sizeof (*gcp), KM_SLEEP);

                /* name */
                (void) sprintf(gcp->gc_name, "%s%d", drv_name, unit);

                /* consistency on tx and rx */
                gcp->gc_tx_buf_align = sizeof (uint8_t) - 1;
                gcp->gc_tx_max_frags = MAXTXFRAGS;
                gcp->gc_tx_max_descs_per_pkt = gcp->gc_tx_max_frags;
                gcp->gc_tx_desc_unit_shift = 4; /* 16 byte */
                gcp->gc_tx_buf_size  = TX_BUF_SIZE;
                gcp->gc_tx_buf_limit = gcp->gc_tx_buf_size;
                gcp->gc_tx_ring_size = TX_RING_SIZE;
                gcp->gc_tx_ring_limit = gcp->gc_tx_ring_size;
                gcp->gc_tx_auto_pad  = B_TRUE;
                gcp->gc_tx_copy_thresh = sfe_tx_copy_thresh;
                gcp->gc_tx_desc_write_oo = B_TRUE;

                gcp->gc_rx_buf_align = sizeof (uint8_t) - 1;
                gcp->gc_rx_max_frags = MAXRXFRAGS;
                gcp->gc_rx_desc_unit_shift = 4;
                gcp->gc_rx_ring_size = RX_RING_SIZE;
                gcp->gc_rx_buf_max   = RX_BUF_SIZE;
                gcp->gc_rx_copy_thresh = sfe_rx_copy_thresh;

                /* map attributes */
                gcp->gc_dev_attr = sfe_dev_attr;
                gcp->gc_buf_attr = sfe_buf_attr;
                gcp->gc_desc_attr = sfe_buf_attr;

                /* dma attributes */
                gcp->gc_dma_attr_desc = sfe_dma_attr_desc;

                gcp->gc_dma_attr_txbuf = sfe_dma_attr_buf;
                gcp->gc_dma_attr_txbuf.dma_attr_align = gcp->gc_tx_buf_align+1;
                gcp->gc_dma_attr_txbuf.dma_attr_sgllen = gcp->gc_tx_max_frags;

                gcp->gc_dma_attr_rxbuf = sfe_dma_attr_buf;
                gcp->gc_dma_attr_rxbuf.dma_attr_align = gcp->gc_rx_buf_align+1;
                gcp->gc_dma_attr_rxbuf.dma_attr_sgllen = gcp->gc_rx_max_frags;

                /* time out parameters */
                gcp->gc_tx_timeout = 3*ONESEC;
                gcp->gc_tx_timeout_interval = ONESEC;
                if (p->chip_type == CHIPTYPE_DP83815) {
                        /* workaround for tx hang */
                        gcp->gc_tx_timeout_interval = ONESEC/20; /* 50mS */
                }

                /* MII timeout parameters */
                gcp->gc_mii_link_watch_interval = ONESEC;
                gcp->gc_mii_an_watch_interval   = ONESEC/5;
                gcp->gc_mii_reset_timeout = MII_RESET_TIMEOUT;  /* 1 sec */
                gcp->gc_mii_an_timeout = MII_AN_TIMEOUT;        /* 5 sec */
                gcp->gc_mii_an_wait = 0;
                gcp->gc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT;

                /* setting for general PHY */
                gcp->gc_mii_an_delay = 0;
                gcp->gc_mii_linkdown_action = MII_ACTION_RSA;
                gcp->gc_mii_linkdown_timeout_action = MII_ACTION_RESET;
                gcp->gc_mii_dont_reset = B_FALSE;


                /* I/O methods */

                /* mac operation */
                gcp->gc_attach_chip = &sfe_attach_chip;
                if (p->chip_type == CHIPTYPE_DP83815) {
                        gcp->gc_reset_chip = &sfe_reset_chip_dp83815;
                } else {
                        gcp->gc_reset_chip = &sfe_reset_chip_sis900;
                }
                gcp->gc_init_chip  = &sfe_init_chip;
                gcp->gc_start_chip = &sfe_start_chip;
                gcp->gc_stop_chip  = &sfe_stop_chip;
#ifdef USE_MULTICAST_HASHTBL
                gcp->gc_multicast_hash = &sfe_mcast_hash;
#endif
                if (p->chip_type == CHIPTYPE_DP83815) {
                        gcp->gc_set_rx_filter = &sfe_set_rx_filter_dp83815;
                } else {
                        gcp->gc_set_rx_filter = &sfe_set_rx_filter_sis900;
                }
                gcp->gc_set_media = &sfe_set_media;
                gcp->gc_get_stats = &sfe_get_stats;
                gcp->gc_interrupt = &sfe_interrupt;

                /* descriptor operation */
                gcp->gc_tx_desc_write = &sfe_tx_desc_write;
                gcp->gc_tx_start = &sfe_tx_start;
                gcp->gc_rx_desc_write = &sfe_rx_desc_write;
                gcp->gc_rx_start = NULL;

                gcp->gc_tx_desc_stat = &sfe_tx_desc_stat;
                gcp->gc_rx_desc_stat = &sfe_rx_desc_stat;
                gcp->gc_tx_desc_init = &sfe_tx_desc_init;
                gcp->gc_rx_desc_init = &sfe_rx_desc_init;
                gcp->gc_tx_desc_clean = &sfe_tx_desc_clean;
                gcp->gc_rx_desc_clean = &sfe_rx_desc_clean;

                /* mii operations */
                if (p->chip_type == CHIPTYPE_DP83815) {
                        gcp->gc_mii_probe = &sfe_mii_probe_dp83815;
                        gcp->gc_mii_init = &sfe_mii_init_dp83815;
                        gcp->gc_mii_config = &sfe_mii_config_dp83815;
                        gcp->gc_mii_sync = &sfe_mii_sync_dp83815;
                        gcp->gc_mii_read = &sfe_mii_read_dp83815;
                        gcp->gc_mii_write = &sfe_mii_write_dp83815;
                        gcp->gc_mii_tune_phy = NULL;
                        gcp->gc_flow_control = FLOW_CONTROL_NONE;
                } else {
                        gcp->gc_mii_probe = &gem_mii_probe_default;
                        gcp->gc_mii_init = NULL;
                        gcp->gc_mii_config = &sfe_mii_config_sis900;
                        gcp->gc_mii_sync = &sfe_mii_sync_sis900;
                        gcp->gc_mii_read = &sfe_mii_read_sis900;
                        gcp->gc_mii_write = &sfe_mii_write_sis900;
                        gcp->gc_mii_tune_phy = &sfe_set_eq_sis630;
                        gcp->gc_flow_control = FLOW_CONTROL_RX_PAUSE;
                }

                lp = kmem_zalloc(sizeof (*lp), KM_SLEEP);
                lp->chip = p;
                lp->revid = rev;
                lp->our_intr_bits = 0;
                lp->isr_pended = 0;

                cmn_err(CE_CONT, CONS "%s%d: chip:%s rev:0x%02x",
                    drv_name, unit, p->chip_name, rev);

                dp = gem_do_attach(dip, 0, gcp, base, &regs_ha,
                    lp, sizeof (*lp));
                kmem_free(gcp, sizeof (*gcp));

                if (dp == NULL) {
                        goto err_freelp;
                }

                return (DDI_SUCCESS);

err_freelp:
                kmem_free(lp, sizeof (struct sfe_dev));
err:
                return (DDI_FAILURE);
        }
        return (DDI_FAILURE);
}

static int
sfedetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_SUSPEND:
                return (gem_suspend(dip));

        case DDI_DETACH:
                return (gem_do_detach(dip));
        }
        return (DDI_FAILURE);
}

/*
 * quiesce(9E) entry point.
 *
 * This function is called when the system is single-threaded at high
 * PIL with preemption disabled. Therefore, this function must not be
 * blocked.
 *
 * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
 * DDI_FAILURE indicates an error condition and should almost never happen.
 */
#ifdef  __sparc
#define sfe_quiesce     ddi_quiesce_not_supported
#else
static int
sfe_quiesce(dev_info_t *dip)
{
        struct gem_dev  *dp;
        int     ret = 0;

        dp = GEM_GET_DEV(dip);

        if (dp == NULL)
                return (DDI_FAILURE);

        ret = sfe_stop_chip_quiesce(dp);

        return (ret);
}
#endif

/* ======================================================== */
/*
 * OS depend (loadable streams driver) routine
 */
/* ======================================================== */
DDI_DEFINE_STREAM_OPS(sfe_ops, nulldev, nulldev, sfeattach, sfedetach,
    nodev, NULL, D_MP, NULL, sfe_quiesce);

static struct modldrv modldrv = {
        &mod_driverops, /* Type of module.  This one is a driver */
        ident,
        &sfe_ops,       /* driver ops */
};

static struct modlinkage modlinkage = {
        MODREV_1, &modldrv, NULL
};

/* ======================================================== */
/*
 * Loadable module support
 */
/* ======================================================== */
int
_init(void)
{
        int     status;

        DPRINTF(2, (CE_CONT, CONS "sfe: _init: called"));
        gem_mod_init(&sfe_ops, "sfe");
        status = mod_install(&modlinkage);
        if (status != DDI_SUCCESS) {
                gem_mod_fini(&sfe_ops);
        }
        return (status);
}

/*
 * _fini : done
 */
int
_fini(void)
{
        int     status;

        DPRINTF(2, (CE_CONT, CONS "sfe: _fini: called"));
        status = mod_remove(&modlinkage);
        if (status == DDI_SUCCESS) {
                gem_mod_fini(&sfe_ops);
        }
        return (status);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}