root/usr/src/uts/common/io/sfe/sfe_util.c
/*
 * sfe_util.c: general ethernet mac driver framework version 2.6
 *
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * System Header files.
 */
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/vtrace.h>
#include <sys/ethernet.h>
#include <sys/modctl.h>
#include <sys/errno.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stream.h>         /* required for MBLK* */
#include <sys/strsun.h>         /* required for mionack() */
#include <sys/byteorder.h>
#include <sys/sysmacros.h>
#include <sys/pci.h>
#include <inet/common.h>
#include <inet/led.h>
#include <inet/mi.h>
#include <inet/nd.h>
#include <sys/crc32.h>

#include <sys/note.h>

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



extern char ident[];

/* Debugging support */
#ifdef GEM_DEBUG_LEVEL
static int gem_debug = GEM_DEBUG_LEVEL;
#define DPRINTF(n, args)        if (gem_debug > (n)) cmn_err args
#else
#define DPRINTF(n, args)
#undef ASSERT
#define ASSERT(x)
#endif

#define IOC_LINESIZE    0x40    /* Is it right for amd64? */

/*
 * Useful macros and typedefs
 */
#define ROUNDUP(x, a)   (((x) + (a) - 1) & ~((a) - 1))

#define GET_NET16(p)    ((((uint8_t *)(p))[0] << 8)| ((uint8_t *)(p))[1])
#define GET_ETHERTYPE(p)        GET_NET16(((uint8_t *)(p)) + ETHERADDRL*2)

#define GET_IPTYPEv4(p) (((uint8_t *)(p))[sizeof (struct ether_header) + 9])
#define GET_IPTYPEv6(p) (((uint8_t *)(p))[sizeof (struct ether_header) + 6])


#ifndef INT32_MAX
#define INT32_MAX       0x7fffffff
#endif

#define VTAG_OFF        (ETHERADDRL*2)
#ifndef VTAG_SIZE
#define VTAG_SIZE       4
#endif
#ifndef VTAG_TPID
#define VTAG_TPID       0x8100U
#endif

#define GET_TXBUF(dp, sn)       \
        &(dp)->tx_buf[SLOT((dp)->tx_slots_base + (sn), (dp)->gc.gc_tx_buf_size)]

#define TXFLAG_VTAG(flag)       \
        (((flag) & GEM_TXFLAG_VTAG) >> GEM_TXFLAG_VTAG_SHIFT)

#define MAXPKTBUF(dp)   \
        ((dp)->mtu + sizeof (struct ether_header) + VTAG_SIZE + ETHERFCSL)

#define WATCH_INTERVAL_FAST     drv_usectohz(100*1000)  /* 100mS */
#define BOOLEAN(x)      ((x) != 0)

/*
 * Macros to distinct chip generation.
 */

/*
 * Private functions
 */
static void gem_mii_start(struct gem_dev *);
static void gem_mii_stop(struct gem_dev *);

/* local buffer management */
static void gem_nd_setup(struct gem_dev *dp);
static void gem_nd_cleanup(struct gem_dev *dp);
static int gem_alloc_memory(struct gem_dev *);
static void gem_free_memory(struct gem_dev *);
static void gem_init_rx_ring(struct gem_dev *);
static void gem_init_tx_ring(struct gem_dev *);
__INLINE__ static void gem_append_rxbuf(struct gem_dev *, struct rxbuf *);

static void gem_tx_timeout(struct gem_dev *);
static void gem_mii_link_watcher(struct gem_dev *dp);
static int gem_mac_init(struct gem_dev *dp);
static int gem_mac_start(struct gem_dev *dp);
static int gem_mac_stop(struct gem_dev *dp, uint_t flags);
static void gem_mac_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp);

static  struct ether_addr       gem_etherbroadcastaddr = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

int gem_speed_value[] = {10, 100, 1000};

/* ============================================================== */
/*
 * Misc runtime routines
 */
/* ============================================================== */
/*
 * Ether CRC calculation according to 21143 data sheet
 */
uint32_t
gem_ether_crc_le(const uint8_t *addr, int len)
{
        uint32_t        crc;

        CRC32(crc, addr, ETHERADDRL, 0xffffffffU, crc32_table);
        return (crc);
}

uint32_t
gem_ether_crc_be(const uint8_t *addr, int len)
{
        int             idx;
        int             bit;
        uint_t          data;
        uint32_t        crc;
#define CRC32_POLY_BE   0x04c11db7

        crc = 0xffffffff;
        for (idx = 0; idx < len; idx++) {
                for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) {
                        crc = (crc << 1)
                            ^ ((((crc >> 31) ^ data) & 1) ? CRC32_POLY_BE : 0);
                }
        }
        return (crc);
#undef  CRC32_POLY_BE
}

int
gem_prop_get_int(struct gem_dev *dp, char *prop_template, int def_val)
{
        char    propname[32];

        (void) sprintf(propname, prop_template, dp->name);

        return (ddi_prop_get_int(DDI_DEV_T_ANY, dp->dip,
            DDI_PROP_DONTPASS, propname, def_val));
}

static int
gem_population(uint32_t x)
{
        int     i;
        int     cnt;

        cnt = 0;
        for (i = 0; i < 32; i++) {
                if (x & (1 << i)) {
                        cnt++;
                }
        }
        return (cnt);
}

#ifdef GEM_DEBUG_LEVEL
#ifdef GEM_DEBUG_VLAN
static void
gem_dump_packet(struct gem_dev *dp, char *title, mblk_t *mp,
    boolean_t check_cksum)
{
        char    msg[180];
        uint8_t buf[18+20+20];
        uint8_t *p;
        size_t  offset;
        uint_t  ethertype;
        uint_t  proto;
        uint_t  ipproto = 0;
        uint_t  iplen;
        uint_t  iphlen;
        uint_t  tcplen;
        uint_t  udplen;
        uint_t  cksum;
        int     rest;
        int     len;
        char    *bp;
        mblk_t  *tp;
        extern uint_t   ip_cksum(mblk_t *, int, uint32_t);

        msg[0] = 0;
        bp = msg;

        rest = sizeof (buf);
        offset = 0;
        for (tp = mp; tp; tp = tp->b_cont) {
                len = tp->b_wptr - tp->b_rptr;
                len = min(rest, len);
                bcopy(tp->b_rptr, &buf[offset], len);
                rest -= len;
                offset += len;
                if (rest == 0) {
                        break;
                }
        }

        offset = 0;
        p = &buf[offset];

        /* ethernet address */
        sprintf(bp,
            "ether: %02x:%02x:%02x:%02x:%02x:%02x"
            " -> %02x:%02x:%02x:%02x:%02x:%02x",
            p[6], p[7], p[8], p[9], p[10], p[11],
            p[0], p[1], p[2], p[3], p[4], p[5]);
        bp = &msg[strlen(msg)];

        /* vlag tag and etherrtype */
        ethertype = GET_ETHERTYPE(p);
        if (ethertype == VTAG_TPID) {
                sprintf(bp, " vtag:0x%04x", GET_NET16(&p[14]));
                bp = &msg[strlen(msg)];

                offset += VTAG_SIZE;
                p = &buf[offset];
                ethertype = GET_ETHERTYPE(p);
        }
        sprintf(bp, " type:%04x", ethertype);
        bp = &msg[strlen(msg)];

        /* ethernet packet length */
        sprintf(bp, " mblklen:%d", msgdsize(mp));
        bp = &msg[strlen(msg)];
        if (mp->b_cont) {
                sprintf(bp, "(");
                bp = &msg[strlen(msg)];
                for (tp = mp; tp; tp = tp->b_cont) {
                        if (tp == mp) {
                                sprintf(bp, "%d", tp->b_wptr - tp->b_rptr);
                        } else {
                                sprintf(bp, "+%d", tp->b_wptr - tp->b_rptr);
                        }
                        bp = &msg[strlen(msg)];
                }
                sprintf(bp, ")");
                bp = &msg[strlen(msg)];
        }

        if (ethertype != ETHERTYPE_IP) {
                goto x;
        }

        /* ip address */
        offset += sizeof (struct ether_header);
        p = &buf[offset];
        ipproto = p[9];
        iplen = GET_NET16(&p[2]);
        sprintf(bp, ", ip: %d.%d.%d.%d -> %d.%d.%d.%d proto:%d iplen:%d",
            p[12], p[13], p[14], p[15],
            p[16], p[17], p[18], p[19],
            ipproto, iplen);
        bp = (void *)&msg[strlen(msg)];

        iphlen = (p[0] & 0xf) * 4;

        /* cksum for psuedo header */
        cksum = *(uint16_t *)&p[12];
        cksum += *(uint16_t *)&p[14];
        cksum += *(uint16_t *)&p[16];
        cksum += *(uint16_t *)&p[18];
        cksum += BE_16(ipproto);

        /* tcp or udp protocol header */
        offset += iphlen;
        p = &buf[offset];
        if (ipproto == IPPROTO_TCP) {
                tcplen = iplen - iphlen;
                sprintf(bp, ", tcp: len:%d cksum:%x",
                    tcplen, GET_NET16(&p[16]));
                bp = (void *)&msg[strlen(msg)];

                if (check_cksum) {
                        cksum += BE_16(tcplen);
                        cksum = (uint16_t)ip_cksum(mp, offset, cksum);
                        sprintf(bp, " (%s)",
                            (cksum == 0 || cksum == 0xffff) ? "ok" : "ng");
                        bp = (void *)&msg[strlen(msg)];
                }
        } else if (ipproto == IPPROTO_UDP) {
                udplen = GET_NET16(&p[4]);
                sprintf(bp, ", udp: len:%d cksum:%x",
                    udplen, GET_NET16(&p[6]));
                bp = (void *)&msg[strlen(msg)];

                if (GET_NET16(&p[6]) && check_cksum) {
                        cksum += *(uint16_t *)&p[4];
                        cksum = (uint16_t)ip_cksum(mp, offset, cksum);
                        sprintf(bp, " (%s)",
                            (cksum == 0 || cksum == 0xffff) ? "ok" : "ng");
                        bp = (void *)&msg[strlen(msg)];
                }
        }
x:
        cmn_err(CE_CONT, "!%s: %s: %s", dp->name, title, msg);
}
#endif /* GEM_DEBUG_VLAN */
#endif /* GEM_DEBUG_LEVEL */

/* ============================================================== */
/*
 * IO cache flush
 */
/* ============================================================== */
__INLINE__ void
gem_rx_desc_dma_sync(struct gem_dev *dp, int head, int nslot, int how)
{
        int     n;
        int     m;
        int     rx_desc_unit_shift = dp->gc.gc_rx_desc_unit_shift;

        /* sync active descriptors */
        if (rx_desc_unit_shift < 0 || nslot == 0) {
                /* no rx descriptor ring */
                return;
        }

        n = dp->gc.gc_rx_ring_size - head;
        if ((m = nslot - n) > 0) {
                (void) ddi_dma_sync(dp->desc_dma_handle,
                    (off_t)0,
                    (size_t)(m << rx_desc_unit_shift),
                    how);
                nslot = n;
        }

        (void) ddi_dma_sync(dp->desc_dma_handle,
            (off_t)(head << rx_desc_unit_shift),
            (size_t)(nslot << rx_desc_unit_shift),
            how);
}

__INLINE__ void
gem_tx_desc_dma_sync(struct gem_dev *dp, int head, int nslot, int how)
{
        int     n;
        int     m;
        int     tx_desc_unit_shift = dp->gc.gc_tx_desc_unit_shift;

        /* sync active descriptors */
        if (tx_desc_unit_shift < 0 || nslot == 0) {
                /* no tx descriptor ring */
                return;
        }

        n = dp->gc.gc_tx_ring_size - head;
        if ((m = nslot - n) > 0) {
                (void) ddi_dma_sync(dp->desc_dma_handle,
                    (off_t)(dp->tx_ring_dma - dp->rx_ring_dma),
                    (size_t)(m << tx_desc_unit_shift),
                    how);
                nslot = n;
        }

        (void) ddi_dma_sync(dp->desc_dma_handle,
            (off_t)((head << tx_desc_unit_shift)
            + (dp->tx_ring_dma - dp->rx_ring_dma)),
            (size_t)(nslot << tx_desc_unit_shift),
            how);
}

static void
gem_rx_start_default(struct gem_dev *dp, int head, int nslot)
{
        gem_rx_desc_dma_sync(dp,
            SLOT(head, dp->gc.gc_rx_ring_size), nslot,
            DDI_DMA_SYNC_FORDEV);
}

/* ============================================================== */
/*
 * Buffer management
 */
/* ============================================================== */
static void
gem_dump_txbuf(struct gem_dev *dp, int level, const char *title)
{
        cmn_err(level,
            "!%s: %s: tx_active: %d[%d] %d[%d] (+%d), "
            "tx_softq: %d[%d] %d[%d] (+%d), "
            "tx_free: %d[%d] %d[%d] (+%d), "
            "tx_desc: %d[%d] %d[%d] (+%d), "
            "intr: %d[%d] (+%d), ",
            dp->name, title,
            dp->tx_active_head,
            SLOT(dp->tx_active_head, dp->gc.gc_tx_buf_size),
            dp->tx_active_tail,
            SLOT(dp->tx_active_tail, dp->gc.gc_tx_buf_size),
            dp->tx_active_tail - dp->tx_active_head,
            dp->tx_softq_head,
            SLOT(dp->tx_softq_head, dp->gc.gc_tx_buf_size),
            dp->tx_softq_tail,
            SLOT(dp->tx_softq_tail, dp->gc.gc_tx_buf_size),
            dp->tx_softq_tail - dp->tx_softq_head,
            dp->tx_free_head,
            SLOT(dp->tx_free_head, dp->gc.gc_tx_buf_size),
            dp->tx_free_tail,
            SLOT(dp->tx_free_tail, dp->gc.gc_tx_buf_size),
            dp->tx_free_tail - dp->tx_free_head,
            dp->tx_desc_head,
            SLOT(dp->tx_desc_head, dp->gc.gc_tx_ring_size),
            dp->tx_desc_tail,
            SLOT(dp->tx_desc_tail, dp->gc.gc_tx_ring_size),
            dp->tx_desc_tail - dp->tx_desc_head,
            dp->tx_desc_intr,
            SLOT(dp->tx_desc_intr, dp->gc.gc_tx_ring_size),
            dp->tx_desc_intr - dp->tx_desc_head);
}

static void
gem_free_rxbuf(struct rxbuf *rbp)
{
        struct gem_dev  *dp;

        dp = rbp->rxb_devp;
        ASSERT(mutex_owned(&dp->intrlock));
        rbp->rxb_next = dp->rx_buf_freelist;
        dp->rx_buf_freelist = rbp;
        dp->rx_buf_freecnt++;
}

/*
 * gem_get_rxbuf: supply a receive buffer which have been mapped into
 * DMA space.
 */
struct rxbuf *
gem_get_rxbuf(struct gem_dev *dp, int cansleep)
{
        struct rxbuf            *rbp;
        uint_t                  count = 0;
        int                     i;
        int                     err;

        ASSERT(mutex_owned(&dp->intrlock));

        DPRINTF(3, (CE_CONT, "!gem_get_rxbuf: called freecnt:%d",
            dp->rx_buf_freecnt));
        /*
         * Get rx buffer management structure
         */
        rbp = dp->rx_buf_freelist;
        if (rbp) {
                /* get one from the recycle list */
                ASSERT(dp->rx_buf_freecnt > 0);

                dp->rx_buf_freelist = rbp->rxb_next;
                dp->rx_buf_freecnt--;
                rbp->rxb_next = NULL;
                return (rbp);
        }

        /*
         * Allocate a rx buffer management structure
         */
        rbp = kmem_zalloc(sizeof (*rbp), cansleep ? KM_SLEEP : KM_NOSLEEP);
        if (rbp == NULL) {
                /* no memory */
                return (NULL);
        }

        /*
         * Prepare a back pointer to the device structure which will be
         * refered on freeing the buffer later.
         */
        rbp->rxb_devp = dp;

        /* allocate a dma handle for rx data buffer */
        if ((err = ddi_dma_alloc_handle(dp->dip,
            &dp->gc.gc_dma_attr_rxbuf,
            (cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT),
            NULL, &rbp->rxb_dh)) != DDI_SUCCESS) {

                cmn_err(CE_WARN,
                    "!%s: %s: ddi_dma_alloc_handle:1 failed, err=%d",
                    dp->name, __func__, err);

                kmem_free(rbp, sizeof (struct rxbuf));
                return (NULL);
        }

        /* allocate a bounce buffer for rx */
        if ((err = ddi_dma_mem_alloc(rbp->rxb_dh,
            ROUNDUP(dp->rx_buf_len, IOC_LINESIZE),
            &dp->gc.gc_buf_attr,
                /*
                 * if the nic requires a header at the top of receive buffers,
                 * it may access the rx buffer randomly.
                 */
            (dp->gc.gc_rx_header_len > 0)
            ? DDI_DMA_CONSISTENT : DDI_DMA_STREAMING,
            cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT,
            NULL,
            &rbp->rxb_buf, &rbp->rxb_buf_len,
            &rbp->rxb_bah)) != DDI_SUCCESS) {

                cmn_err(CE_WARN,
                    "!%s: %s: ddi_dma_mem_alloc: failed, err=%d",
                    dp->name, __func__, err);

                ddi_dma_free_handle(&rbp->rxb_dh);
                kmem_free(rbp, sizeof (struct rxbuf));
                return (NULL);
        }

        /* Mapin the bounce buffer into the DMA space */
        if ((err = ddi_dma_addr_bind_handle(rbp->rxb_dh,
            NULL, rbp->rxb_buf, dp->rx_buf_len,
            ((dp->gc.gc_rx_header_len > 0)
            ?(DDI_DMA_RDWR | DDI_DMA_CONSISTENT)
            :(DDI_DMA_READ | DDI_DMA_STREAMING)),
            cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT,
            NULL,
            rbp->rxb_dmacookie,
            &count)) != DDI_DMA_MAPPED) {

                ASSERT(err != DDI_DMA_INUSE);
                DPRINTF(0, (CE_WARN,
                    "!%s: ddi_dma_addr_bind_handle: failed, err=%d",
                    dp->name, __func__, err));

                /*
                 * we failed to allocate a dma resource
                 * for the rx bounce buffer.
                 */
                ddi_dma_mem_free(&rbp->rxb_bah);
                ddi_dma_free_handle(&rbp->rxb_dh);
                kmem_free(rbp, sizeof (struct rxbuf));
                return (NULL);
        }

        /* correct the rest of the DMA mapping */
        for (i = 1; i < count; i++) {
                ddi_dma_nextcookie(rbp->rxb_dh, &rbp->rxb_dmacookie[i]);
        }
        rbp->rxb_nfrags = count;

        /* Now we successfully prepared an rx buffer */
        dp->rx_buf_allocated++;

        return (rbp);
}

/* ============================================================== */
/*
 * memory resource management
 */
/* ============================================================== */
static int
gem_alloc_memory(struct gem_dev *dp)
{
        caddr_t                 ring;
        caddr_t                 buf;
        size_t                  req_size;
        size_t                  ring_len;
        size_t                  buf_len;
        ddi_dma_cookie_t        ring_cookie;
        ddi_dma_cookie_t        buf_cookie;
        uint_t                  count;
        int                     i;
        int                     err;
        struct txbuf            *tbp;
        int                     tx_buf_len;
        ddi_dma_attr_t          dma_attr_txbounce;

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

        dp->desc_dma_handle = NULL;
        req_size = dp->rx_desc_size + dp->tx_desc_size + dp->gc.gc_io_area_size;

        if (req_size > 0) {
                /*
                 * Alloc RX/TX descriptors and a io area.
                 */
                if ((err = ddi_dma_alloc_handle(dp->dip,
                    &dp->gc.gc_dma_attr_desc,
                    DDI_DMA_SLEEP, NULL,
                    &dp->desc_dma_handle)) != DDI_SUCCESS) {
                        cmn_err(CE_WARN,
                            "!%s: %s: ddi_dma_alloc_handle failed: %d",
                            dp->name, __func__, err);
                        return (ENOMEM);
                }

                if ((err = ddi_dma_mem_alloc(dp->desc_dma_handle,
                    req_size, &dp->gc.gc_desc_attr,
                    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
                    &ring, &ring_len,
                    &dp->desc_acc_handle)) != DDI_SUCCESS) {
                        cmn_err(CE_WARN,
                            "!%s: %s: ddi_dma_mem_alloc failed: "
                            "ret %d, request size: %d",
                            dp->name, __func__, err, (int)req_size);
                        ddi_dma_free_handle(&dp->desc_dma_handle);
                        return (ENOMEM);
                }

                if ((err = ddi_dma_addr_bind_handle(dp->desc_dma_handle,
                    NULL, ring, ring_len,
                    DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
                    DDI_DMA_SLEEP, NULL,
                    &ring_cookie, &count)) != DDI_SUCCESS) {
                        ASSERT(err != DDI_DMA_INUSE);
                        cmn_err(CE_WARN,
                            "!%s: %s: ddi_dma_addr_bind_handle failed: %d",
                            dp->name, __func__, err);
                        ddi_dma_mem_free(&dp->desc_acc_handle);
                        ddi_dma_free_handle(&dp->desc_dma_handle);
                        return (ENOMEM);
                }
                ASSERT(count == 1);

                /* set base of rx descriptor ring */
                dp->rx_ring = ring;
                dp->rx_ring_dma = ring_cookie.dmac_laddress;

                /* set base of tx descriptor ring */
                dp->tx_ring = dp->rx_ring + dp->rx_desc_size;
                dp->tx_ring_dma = dp->rx_ring_dma + dp->rx_desc_size;

                /* set base of io area */
                dp->io_area = dp->tx_ring + dp->tx_desc_size;
                dp->io_area_dma = dp->tx_ring_dma + dp->tx_desc_size;
        }

        /*
         * Prepare DMA resources for tx packets
         */
        ASSERT(dp->gc.gc_tx_buf_size > 0);

        /* Special dma attribute for tx bounce buffers */
        dma_attr_txbounce = dp->gc.gc_dma_attr_txbuf;
        dma_attr_txbounce.dma_attr_sgllen = 1;
        dma_attr_txbounce.dma_attr_align =
            max(dma_attr_txbounce.dma_attr_align, IOC_LINESIZE);

        /* Size for tx bounce buffers must be max tx packet size. */
        tx_buf_len = MAXPKTBUF(dp);
        tx_buf_len = ROUNDUP(tx_buf_len, IOC_LINESIZE);

        ASSERT(tx_buf_len >= ETHERMAX+ETHERFCSL);

        for (i = 0, tbp = dp->tx_buf;
            i < dp->gc.gc_tx_buf_size; i++, tbp++) {

                /* setup bounce buffers for tx packets */
                if ((err = ddi_dma_alloc_handle(dp->dip,
                    &dma_attr_txbounce,
                    DDI_DMA_SLEEP, NULL,
                    &tbp->txb_bdh)) != DDI_SUCCESS) {

                        cmn_err(CE_WARN,
                    "!%s: %s ddi_dma_alloc_handle for bounce buffer failed:"
                            " err=%d, i=%d",
                            dp->name, __func__, err, i);
                        goto err_alloc_dh;
                }

                if ((err = ddi_dma_mem_alloc(tbp->txb_bdh,
                    tx_buf_len,
                    &dp->gc.gc_buf_attr,
                    DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL,
                    &buf, &buf_len,
                    &tbp->txb_bah)) != DDI_SUCCESS) {
                        cmn_err(CE_WARN,
                    "!%s: %s: ddi_dma_mem_alloc for bounce buffer failed"
                            "ret %d, request size %d",
                            dp->name, __func__, err, tx_buf_len);
                        ddi_dma_free_handle(&tbp->txb_bdh);
                        goto err_alloc_dh;
                }

                if ((err = ddi_dma_addr_bind_handle(tbp->txb_bdh,
                    NULL, buf, buf_len,
                    DDI_DMA_WRITE | DDI_DMA_STREAMING,
                    DDI_DMA_SLEEP, NULL,
                    &buf_cookie, &count)) != DDI_SUCCESS) {
                                ASSERT(err != DDI_DMA_INUSE);
                                cmn_err(CE_WARN,
        "!%s: %s: ddi_dma_addr_bind_handle for bounce buffer failed: %d",
                                    dp->name, __func__, err);
                                ddi_dma_mem_free(&tbp->txb_bah);
                                ddi_dma_free_handle(&tbp->txb_bdh);
                                goto err_alloc_dh;
                }
                ASSERT(count == 1);
                tbp->txb_buf = buf;
                tbp->txb_buf_dma = buf_cookie.dmac_laddress;
        }

        return (0);

err_alloc_dh:
        if (dp->gc.gc_tx_buf_size > 0) {
                while (i-- > 0) {
                        (void) ddi_dma_unbind_handle(dp->tx_buf[i].txb_bdh);
                        ddi_dma_mem_free(&dp->tx_buf[i].txb_bah);
                        ddi_dma_free_handle(&dp->tx_buf[i].txb_bdh);
                }
        }

        if (dp->desc_dma_handle) {
                (void) ddi_dma_unbind_handle(dp->desc_dma_handle);
                ddi_dma_mem_free(&dp->desc_acc_handle);
                ddi_dma_free_handle(&dp->desc_dma_handle);
                dp->desc_dma_handle = NULL;
        }

        return (ENOMEM);
}

static void
gem_free_memory(struct gem_dev *dp)
{
        int             i;
        struct rxbuf    *rbp;
        struct txbuf    *tbp;

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

        /* Free TX/RX descriptors and tx padding buffer */
        if (dp->desc_dma_handle) {
                (void) ddi_dma_unbind_handle(dp->desc_dma_handle);
                ddi_dma_mem_free(&dp->desc_acc_handle);
                ddi_dma_free_handle(&dp->desc_dma_handle);
                dp->desc_dma_handle = NULL;
        }

        /* Free dma handles for Tx */
        for (i = dp->gc.gc_tx_buf_size, tbp = dp->tx_buf; i--; tbp++) {
                /* Free bounce buffer associated to each txbuf */
                (void) ddi_dma_unbind_handle(tbp->txb_bdh);
                ddi_dma_mem_free(&tbp->txb_bah);
                ddi_dma_free_handle(&tbp->txb_bdh);
        }

        /* Free rx buffer */
        while ((rbp = dp->rx_buf_freelist) != NULL) {

                ASSERT(dp->rx_buf_freecnt > 0);

                dp->rx_buf_freelist = rbp->rxb_next;
                dp->rx_buf_freecnt--;

                /* release DMA mapping */
                ASSERT(rbp->rxb_dh != NULL);

                /* free dma handles for rx bbuf */
                /* it has dma mapping always */
                ASSERT(rbp->rxb_nfrags > 0);
                (void) ddi_dma_unbind_handle(rbp->rxb_dh);

                /* free the associated bounce buffer and dma handle */
                ASSERT(rbp->rxb_bah != NULL);
                ddi_dma_mem_free(&rbp->rxb_bah);
                /* free the associated dma handle */
                ddi_dma_free_handle(&rbp->rxb_dh);

                /* free the base memory of rx buffer management */
                kmem_free(rbp, sizeof (struct rxbuf));
        }
}

/* ============================================================== */
/*
 * Rx/Tx descriptor slot management
 */
/* ============================================================== */
/*
 * Initialize an empty rx ring.
 */
static void
gem_init_rx_ring(struct gem_dev *dp)
{
        int             i;
        int             rx_ring_size = dp->gc.gc_rx_ring_size;

        DPRINTF(1, (CE_CONT, "!%s: %s ring_size:%d, buf_max:%d",
            dp->name, __func__,
            rx_ring_size, dp->gc.gc_rx_buf_max));

        /* make a physical chain of rx descriptors */
        for (i = 0; i < rx_ring_size; i++) {
                (*dp->gc.gc_rx_desc_init)(dp, i);
        }
        gem_rx_desc_dma_sync(dp, 0, rx_ring_size, DDI_DMA_SYNC_FORDEV);

        dp->rx_active_head = (seqnum_t)0;
        dp->rx_active_tail = (seqnum_t)0;

        ASSERT(dp->rx_buf_head == (struct rxbuf *)NULL);
        ASSERT(dp->rx_buf_tail == (struct rxbuf *)NULL);
}

/*
 * Prepare rx buffers and put them into the rx buffer/descriptor ring.
 */
static void
gem_prepare_rx_buf(struct gem_dev *dp)
{
        int             i;
        int             nrbuf;
        struct rxbuf    *rbp;

        ASSERT(mutex_owned(&dp->intrlock));

        /* Now we have no active buffers in rx ring */

        nrbuf = min(dp->gc.gc_rx_ring_size, dp->gc.gc_rx_buf_max);
        for (i = 0; i < nrbuf; i++) {
                if ((rbp = gem_get_rxbuf(dp, B_TRUE)) == NULL) {
                        break;
                }
                gem_append_rxbuf(dp, rbp);
        }

        gem_rx_desc_dma_sync(dp,
            0, dp->gc.gc_rx_ring_size, DDI_DMA_SYNC_FORDEV);
}

/*
 * Reclaim active rx buffers in rx buffer ring.
 */
static void
gem_clean_rx_buf(struct gem_dev *dp)
{
        int             i;
        struct rxbuf    *rbp;
        int             rx_ring_size = dp->gc.gc_rx_ring_size;
#ifdef GEM_DEBUG_LEVEL
        int             total;
#endif
        ASSERT(mutex_owned(&dp->intrlock));

        DPRINTF(2, (CE_CONT, "!%s: %s: %d buffers are free",
            dp->name, __func__, dp->rx_buf_freecnt));
        /*
         * clean up HW descriptors
         */
        for (i = 0; i < rx_ring_size; i++) {
                (*dp->gc.gc_rx_desc_clean)(dp, i);
        }
        gem_rx_desc_dma_sync(dp, 0, rx_ring_size, DDI_DMA_SYNC_FORDEV);

#ifdef GEM_DEBUG_LEVEL
        total = 0;
#endif
        /*
         * Reclaim allocated rx buffers
         */
        while ((rbp = dp->rx_buf_head) != NULL) {
#ifdef GEM_DEBUG_LEVEL
                total++;
#endif
                /* remove the first one from rx buffer list */
                dp->rx_buf_head = rbp->rxb_next;

                /* recycle the rxbuf */
                gem_free_rxbuf(rbp);
        }
        dp->rx_buf_tail = (struct rxbuf *)NULL;

        DPRINTF(2, (CE_CONT,
            "!%s: %s: %d buffers freeed, total: %d free",
            dp->name, __func__, total, dp->rx_buf_freecnt));
}

/*
 * Initialize an empty transmit buffer/descriptor ring
 */
static void
gem_init_tx_ring(struct gem_dev *dp)
{
        int             i;
        int             tx_buf_size = dp->gc.gc_tx_buf_size;
        int             tx_ring_size = dp->gc.gc_tx_ring_size;

        DPRINTF(2, (CE_CONT, "!%s: %s: ring_size:%d, buf_size:%d",
            dp->name, __func__,
            dp->gc.gc_tx_ring_size, dp->gc.gc_tx_buf_size));

        ASSERT(!dp->mac_active);

        /* initialize active list and free list */
        dp->tx_slots_base =
            SLOT(dp->tx_slots_base + dp->tx_softq_head, tx_buf_size);
        dp->tx_softq_tail -= dp->tx_softq_head;
        dp->tx_softq_head = (seqnum_t)0;

        dp->tx_active_head = dp->tx_softq_head;
        dp->tx_active_tail = dp->tx_softq_head;

        dp->tx_free_head   = dp->tx_softq_tail;
        dp->tx_free_tail   = dp->gc.gc_tx_buf_limit;

        dp->tx_desc_head = (seqnum_t)0;
        dp->tx_desc_tail = (seqnum_t)0;
        dp->tx_desc_intr = (seqnum_t)0;

        for (i = 0; i < tx_ring_size; i++) {
                (*dp->gc.gc_tx_desc_init)(dp, i);
        }
        gem_tx_desc_dma_sync(dp, 0, tx_ring_size, DDI_DMA_SYNC_FORDEV);
}

__INLINE__
static void
gem_txbuf_free_dma_resources(struct txbuf *tbp)
{
        if (tbp->txb_mp) {
                freemsg(tbp->txb_mp);
                tbp->txb_mp = NULL;
        }
        tbp->txb_nfrags = 0;
        tbp->txb_flag = 0;
}

/*
 * reclaim active tx buffers and reset positions in tx rings.
 */
static void
gem_clean_tx_buf(struct gem_dev *dp)
{
        int             i;
        seqnum_t        head;
        seqnum_t        tail;
        seqnum_t        sn;
        struct txbuf    *tbp;
        int             tx_ring_size = dp->gc.gc_tx_ring_size;
#ifdef GEM_DEBUG_LEVEL
        int             err;
#endif

        ASSERT(!dp->mac_active);
        ASSERT(dp->tx_busy == 0);
        ASSERT(dp->tx_softq_tail == dp->tx_free_head);

        /*
         * clean up all HW descriptors
         */
        for (i = 0; i < tx_ring_size; i++) {
                (*dp->gc.gc_tx_desc_clean)(dp, i);
        }
        gem_tx_desc_dma_sync(dp, 0, tx_ring_size, DDI_DMA_SYNC_FORDEV);

        /* dequeue all active and loaded buffers */
        head = dp->tx_active_head;
        tail = dp->tx_softq_tail;

        ASSERT(dp->tx_free_head - head >= 0);
        tbp = GET_TXBUF(dp, head);
        for (sn = head; sn != tail; sn++) {
                gem_txbuf_free_dma_resources(tbp);
                ASSERT(tbp->txb_mp == NULL);
                dp->stats.errxmt++;
                tbp = tbp->txb_next;
        }

#ifdef GEM_DEBUG_LEVEL
        /* ensure no dma resources for tx are not in use now */
        err = 0;
        while (sn != head + dp->gc.gc_tx_buf_size) {
                if (tbp->txb_mp || tbp->txb_nfrags) {
                        DPRINTF(0, (CE_CONT,
                            "%s: %s: sn:%d[%d] mp:%p nfrags:%d",
                            dp->name, __func__,
                            sn, SLOT(sn, dp->gc.gc_tx_buf_size),
                            tbp->txb_mp, tbp->txb_nfrags));
                        err = 1;
                }
                sn++;
                tbp = tbp->txb_next;
        }

        if (err) {
                gem_dump_txbuf(dp, CE_WARN,
                    "gem_clean_tx_buf: tbp->txb_mp != NULL");
        }
#endif
        /* recycle buffers, now no active tx buffers in the ring */
        dp->tx_free_tail += tail - head;
        ASSERT(dp->tx_free_tail == dp->tx_free_head + dp->gc.gc_tx_buf_limit);

        /* fix positions in tx buffer rings */
        dp->tx_active_head = dp->tx_free_head;
        dp->tx_active_tail = dp->tx_free_head;
        dp->tx_softq_head  = dp->tx_free_head;
        dp->tx_softq_tail  = dp->tx_free_head;
}

/*
 * Reclaim transmitted buffers from tx buffer/descriptor ring.
 */
__INLINE__ int
gem_reclaim_txbuf(struct gem_dev *dp)
{
        struct txbuf    *tbp;
        uint_t          txstat;
        int             err = GEM_SUCCESS;
        seqnum_t        head;
        seqnum_t        tail;
        seqnum_t        sn;
        seqnum_t        desc_head;
        int             tx_ring_size = dp->gc.gc_tx_ring_size;
        uint_t (*tx_desc_stat)(struct gem_dev *dp,
            int slot, int ndesc) = dp->gc.gc_tx_desc_stat;
        clock_t         now;

        now = ddi_get_lbolt();
        if (now == (clock_t)0) {
                /* make non-zero timestamp */
                now--;
        }

        mutex_enter(&dp->xmitlock);

        head = dp->tx_active_head;
        tail = dp->tx_active_tail;

#if GEM_DEBUG_LEVEL > 2
        if (head != tail) {
                cmn_err(CE_CONT, "!%s: %s: "
                    "testing active_head:%d[%d], active_tail:%d[%d]",
                    dp->name, __func__,
                    head, SLOT(head, dp->gc.gc_tx_buf_size),
                    tail, SLOT(tail, dp->gc.gc_tx_buf_size));
        }
#endif
#ifdef DEBUG
        if (dp->tx_reclaim_busy == 0) {
                /* check tx buffer management consistency */
                ASSERT(dp->tx_free_tail - dp->tx_active_head
                    == dp->gc.gc_tx_buf_limit);
                /* EMPTY */
        }
#endif
        dp->tx_reclaim_busy++;

        /* sync all active HW descriptors */
        gem_tx_desc_dma_sync(dp,
            SLOT(dp->tx_desc_head, tx_ring_size),
            dp->tx_desc_tail - dp->tx_desc_head,
            DDI_DMA_SYNC_FORKERNEL);

        tbp = GET_TXBUF(dp, head);
        desc_head = dp->tx_desc_head;
        for (sn = head; sn != tail;
            dp->tx_active_head = (++sn), tbp = tbp->txb_next) {
                int     ndescs;

                ASSERT(tbp->txb_desc == desc_head);

                ndescs = tbp->txb_ndescs;
                if (ndescs == 0) {
                        /* skip errored descriptors */
                        continue;
                }
                txstat = (*tx_desc_stat)(dp,
                    SLOT(tbp->txb_desc, tx_ring_size), ndescs);

                if (txstat == 0) {
                        /* not transmitted yet */
                        break;
                }

                if (!dp->tx_blocked && (tbp->txb_flag & GEM_TXFLAG_INTR)) {
                        dp->tx_blocked = now;
                }

                ASSERT(txstat & (GEM_TX_DONE | GEM_TX_ERR));

                if (txstat & GEM_TX_ERR) {
                        err = GEM_FAILURE;
                        cmn_err(CE_WARN, "!%s: tx error at desc %d[%d]",
                            dp->name, sn, SLOT(sn, tx_ring_size));
                }
#if GEM_DEBUG_LEVEL > 4
                if (now - tbp->txb_stime >= 50) {
                        cmn_err(CE_WARN, "!%s: tx delay while %d mS",
                            dp->name, (now - tbp->txb_stime)*10);
                }
#endif
                /* free transmitted descriptors */
                desc_head += ndescs;
        }

        if (dp->tx_desc_head != desc_head) {
                /* we have reclaimed one or more tx buffers */
                dp->tx_desc_head = desc_head;

                /* If we passed the next interrupt position, update it */
                if (desc_head - dp->tx_desc_intr > 0) {
                        dp->tx_desc_intr = desc_head;
                }
        }
        mutex_exit(&dp->xmitlock);

        /* free dma mapping resources associated with transmitted tx buffers */
        tbp = GET_TXBUF(dp, head);
        tail = sn;
#if GEM_DEBUG_LEVEL > 2
        if (head != tail) {
                cmn_err(CE_CONT, "%s: freeing head:%d[%d], tail:%d[%d]",
                    __func__,
                    head, SLOT(head, dp->gc.gc_tx_buf_size),
                    tail, SLOT(tail, dp->gc.gc_tx_buf_size));
        }
#endif
        for (sn = head; sn != tail; sn++, tbp = tbp->txb_next) {
                gem_txbuf_free_dma_resources(tbp);
        }

        /* recycle the tx buffers */
        mutex_enter(&dp->xmitlock);
        if (--dp->tx_reclaim_busy == 0) {
                /* we are the last thread who can update free tail */
#if GEM_DEBUG_LEVEL > 4
                /* check all resouces have been deallocated */
                sn = dp->tx_free_tail;
                tbp = GET_TXBUF(dp, new_tail);
                while (sn != dp->tx_active_head + dp->gc.gc_tx_buf_limit) {
                        if (tbp->txb_nfrags) {
                                /* in use */
                                break;
                        }
                        ASSERT(tbp->txb_mp == NULL);
                        tbp = tbp->txb_next;
                        sn++;
                }
                ASSERT(dp->tx_active_head + dp->gc.gc_tx_buf_limit == sn);
#endif
                dp->tx_free_tail =
                    dp->tx_active_head + dp->gc.gc_tx_buf_limit;
        }
        if (!dp->mac_active) {
                /* someone may be waiting for me. */
                cv_broadcast(&dp->tx_drain_cv);
        }
#if GEM_DEBUG_LEVEL > 2
        cmn_err(CE_CONT, "!%s: %s: called, "
            "free_head:%d free_tail:%d(+%d) added:%d",
            dp->name, __func__,
            dp->tx_free_head, dp->tx_free_tail,
            dp->tx_free_tail - dp->tx_free_head, tail - head);
#endif
        mutex_exit(&dp->xmitlock);

        return (err);
}

/*
 * Make tx descriptors in out-of-order manner
 */
static void
gem_tx_load_descs_oo(struct gem_dev *dp,
    seqnum_t start_slot, seqnum_t end_slot, uint64_t flags)
{
        seqnum_t        sn;
        struct txbuf    *tbp;
        int     tx_ring_size = dp->gc.gc_tx_ring_size;
        int     (*tx_desc_write)
            (struct gem_dev *dp, int slot,
            ddi_dma_cookie_t *dmacookie,
            int frags, uint64_t flag) = dp->gc.gc_tx_desc_write;
        clock_t now = ddi_get_lbolt();

        sn = start_slot;
        tbp = GET_TXBUF(dp, sn);
        do {
#if GEM_DEBUG_LEVEL > 1
                if (dp->tx_cnt < 100) {
                        dp->tx_cnt++;
                        flags |= GEM_TXFLAG_INTR;
                }
#endif
                /* write a tx descriptor */
                tbp->txb_desc = sn;
                tbp->txb_ndescs = (*tx_desc_write)(dp,
                    SLOT(sn, tx_ring_size),
                    tbp->txb_dmacookie,
                    tbp->txb_nfrags, flags | tbp->txb_flag);
                tbp->txb_stime = now;
                ASSERT(tbp->txb_ndescs == 1);

                flags = 0;
                sn++;
                tbp = tbp->txb_next;
        } while (sn != end_slot);
}

__INLINE__
static size_t
gem_setup_txbuf_copy(struct gem_dev *dp, mblk_t *mp, struct txbuf *tbp)
{
        size_t                  min_pkt;
        caddr_t                 bp;
        size_t                  off;
        mblk_t                  *tp;
        size_t                  len;
        uint64_t                flag;

        ASSERT(tbp->txb_mp == NULL);

        /* we use bounce buffer for the packet */
        min_pkt = ETHERMIN;
        bp = tbp->txb_buf;
        off = 0;
        tp = mp;

        flag = tbp->txb_flag;
        if (flag & GEM_TXFLAG_SWVTAG) {
                /* need to increase min packet size */
                min_pkt += VTAG_SIZE;
                ASSERT((flag & GEM_TXFLAG_VTAG) == 0);
        }

        /* copy the rest */
        for (; tp; tp = tp->b_cont) {
                if ((len = (long)tp->b_wptr - (long)tp->b_rptr) > 0) {
                        bcopy(tp->b_rptr, &bp[off], len);
                        off += len;
                }
        }

        if (off < min_pkt &&
            (min_pkt > ETHERMIN || !dp->gc.gc_tx_auto_pad)) {
                /*
                 * Extend the packet to minimum packet size explicitly.
                 * For software vlan packets, we shouldn't use tx autopad
                 * function because nics may not be aware of vlan.
                 * we must keep 46 octet of payload even if we use vlan.
                 */
                bzero(&bp[off], min_pkt - off);
                off = min_pkt;
        }

        (void) ddi_dma_sync(tbp->txb_bdh, (off_t)0, off, DDI_DMA_SYNC_FORDEV);

        tbp->txb_dmacookie[0].dmac_laddress = tbp->txb_buf_dma;
        tbp->txb_dmacookie[0].dmac_size = off;

        DPRINTF(2, (CE_CONT,
            "!%s: %s: copy: addr:0x%llx len:0x%x, vtag:0x%04x, min_pkt:%d",
            dp->name, __func__,
            tbp->txb_dmacookie[0].dmac_laddress,
            tbp->txb_dmacookie[0].dmac_size,
            (flag & GEM_TXFLAG_VTAG) >> GEM_TXFLAG_VTAG_SHIFT,
            min_pkt));

        /* save misc info */
        tbp->txb_mp = mp;
        tbp->txb_nfrags = 1;
#ifdef DEBUG_MULTIFRAGS
        if (dp->gc.gc_tx_max_frags >= 3 &&
            tbp->txb_dmacookie[0].dmac_size > 16*3) {
                tbp->txb_dmacookie[1].dmac_laddress =
                    tbp->txb_dmacookie[0].dmac_laddress + 16;
                tbp->txb_dmacookie[2].dmac_laddress =
                    tbp->txb_dmacookie[1].dmac_laddress + 16;

                tbp->txb_dmacookie[2].dmac_size =
                    tbp->txb_dmacookie[0].dmac_size - 16*2;
                tbp->txb_dmacookie[1].dmac_size = 16;
                tbp->txb_dmacookie[0].dmac_size = 16;
                tbp->txb_nfrags  = 3;
        }
#endif
        return (off);
}

__INLINE__
static void
gem_tx_start_unit(struct gem_dev *dp)
{
        seqnum_t        head;
        seqnum_t        tail;
        struct txbuf    *tbp_head;
        struct txbuf    *tbp_tail;

        /* update HW descriptors from soft queue */
        ASSERT(mutex_owned(&dp->xmitlock));
        ASSERT(dp->tx_softq_head == dp->tx_active_tail);

        head = dp->tx_softq_head;
        tail = dp->tx_softq_tail;

        DPRINTF(1, (CE_CONT,
            "%s: %s: called, softq %d %d[+%d], desc %d %d[+%d]",
            dp->name, __func__, head, tail, tail - head,
            dp->tx_desc_head, dp->tx_desc_tail,
            dp->tx_desc_tail - dp->tx_desc_head));

        ASSERT(tail - head > 0);

        dp->tx_desc_tail = tail;

        tbp_head = GET_TXBUF(dp, head);
        tbp_tail = GET_TXBUF(dp, tail - 1);

        ASSERT(tbp_tail->txb_desc + tbp_tail->txb_ndescs == dp->tx_desc_tail);

        dp->gc.gc_tx_start(dp,
            SLOT(tbp_head->txb_desc, dp->gc.gc_tx_ring_size),
            tbp_tail->txb_desc + tbp_tail->txb_ndescs - tbp_head->txb_desc);

        /* advance softq head and active tail */
        dp->tx_softq_head = dp->tx_active_tail = tail;
}

#ifdef GEM_DEBUG_LEVEL
static int gem_send_cnt[10];
#endif
#define PKT_MIN_SIZE    (sizeof (struct ether_header) + 10 + VTAG_SIZE)
#define EHLEN   (sizeof (struct ether_header))
/*
 * check ether packet type and ip protocol
 */
static uint64_t
gem_txbuf_options(struct gem_dev *dp, mblk_t *mp, uint8_t *bp)
{
        mblk_t          *tp;
        ssize_t         len;
        uint_t          vtag;
        int             off;
        uint64_t        flag;

        flag = 0ULL;

        /*
         * prepare continuous header of the packet for protocol analysis
         */
        if ((long)mp->b_wptr - (long)mp->b_rptr < PKT_MIN_SIZE) {
                /* we use work buffer to copy mblk */
                for (tp = mp, off = 0;
                    tp && (off < PKT_MIN_SIZE);
                    tp = tp->b_cont, off += len) {
                        len = (long)tp->b_wptr - (long)tp->b_rptr;
                        len = min(len, PKT_MIN_SIZE - off);
                        bcopy(tp->b_rptr, &bp[off], len);
                }
        } else {
                /* we can use mblk without copy */
                bp = mp->b_rptr;
        }

        /* process vlan tag for GLD v3 */
        if (GET_NET16(&bp[VTAG_OFF]) == VTAG_TPID) {
                if (dp->misc_flag & GEM_VLAN_HARD) {
                        vtag = GET_NET16(&bp[VTAG_OFF + 2]);
                        ASSERT(vtag);
                        flag |= vtag << GEM_TXFLAG_VTAG_SHIFT;
                } else {
                        flag |= GEM_TXFLAG_SWVTAG;
                }
        }
        return (flag);
}
#undef EHLEN
#undef PKT_MIN_SIZE
/*
 * gem_send_common is an exported function because hw depend routines may
 * use it for sending control frames like setup frames for 2114x chipset.
 */
mblk_t *
gem_send_common(struct gem_dev *dp, mblk_t *mp_head, uint32_t flags)
{
        int                     nmblk;
        int                     avail;
        mblk_t                  *tp;
        mblk_t                  *mp;
        int                     i;
        struct txbuf            *tbp;
        seqnum_t                head;
        uint64_t                load_flags;
        uint64_t                len_total = 0;
        uint32_t                bcast = 0;
        uint32_t                mcast = 0;

        ASSERT(mp_head != NULL);

        mp = mp_head;
        nmblk = 1;
        while ((mp = mp->b_next) != NULL) {
                nmblk++;
        }
#ifdef GEM_DEBUG_LEVEL
        gem_send_cnt[0]++;
        gem_send_cnt[min(nmblk, 9)]++;
#endif
        /*
         * Aquire resources
         */
        mutex_enter(&dp->xmitlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->xmitlock);
                mp = mp_head;
                while (mp) {
                        tp = mp->b_next;
                        freemsg(mp);
                        mp = tp;
                }
                return (NULL);
        }

        if (!dp->mac_active && (flags & GEM_SEND_CTRL) == 0) {
                /* don't send data packets while mac isn't active */
                /* XXX - should we discard packets? */
                mutex_exit(&dp->xmitlock);
                return (mp_head);
        }

        /* allocate free slots */
        head = dp->tx_free_head;
        avail = dp->tx_free_tail - head;

        DPRINTF(2, (CE_CONT,
            "!%s: %s: called, free_head:%d free_tail:%d(+%d) req:%d",
            dp->name, __func__,
            dp->tx_free_head, dp->tx_free_tail, avail, nmblk));

        avail = min(avail, dp->tx_max_packets);

        if (nmblk > avail) {
                if (avail == 0) {
                        /* no resources; short cut */
                        DPRINTF(2, (CE_CONT, "!%s: no resources", __func__));
                        dp->tx_max_packets = max(dp->tx_max_packets - 1, 1);
                        goto done;
                }
                nmblk = avail;
        }

        dp->tx_free_head = head + nmblk;
        load_flags = ((dp->tx_busy++) == 0) ? GEM_TXFLAG_HEAD : 0;

        /* update last interrupt position if tx buffers exhaust.  */
        if (nmblk == avail) {
                tbp = GET_TXBUF(dp, head + avail - 1);
                tbp->txb_flag = GEM_TXFLAG_INTR;
                dp->tx_desc_intr = head + avail;
        }
        mutex_exit(&dp->xmitlock);

        tbp = GET_TXBUF(dp, head);

        for (i = nmblk; i > 0; i--, tbp = tbp->txb_next) {
                uint8_t         *bp;
                uint64_t        txflag;

                /* remove one from the mblk list */
                ASSERT(mp_head != NULL);
                mp = mp_head;
                mp_head = mp_head->b_next;
                mp->b_next = NULL;

                /* statistics for non-unicast packets */
                bp = mp->b_rptr;
                if ((bp[0] & 1) && (flags & GEM_SEND_CTRL) == 0) {
                        if (bcmp(bp, gem_etherbroadcastaddr.ether_addr_octet,
                            ETHERADDRL) == 0) {
                                bcast++;
                        } else {
                                mcast++;
                        }
                }

                /* save misc info */
                txflag = tbp->txb_flag;
                txflag |= (flags & GEM_SEND_CTRL) << GEM_TXFLAG_PRIVATE_SHIFT;
                txflag |= gem_txbuf_options(dp, mp, (uint8_t *)tbp->txb_buf);
                tbp->txb_flag = txflag;

                len_total += gem_setup_txbuf_copy(dp, mp, tbp);
        }

        (void) gem_tx_load_descs_oo(dp, head, head + nmblk, load_flags);

        /* Append the tbp at the tail of the active tx buffer list */
        mutex_enter(&dp->xmitlock);

        if ((--dp->tx_busy) == 0) {
                /* extend the tail of softq, as new packets have been ready. */
                dp->tx_softq_tail = dp->tx_free_head;

                if (!dp->mac_active && (flags & GEM_SEND_CTRL) == 0) {
                        /*
                         * The device status has changed while we are
                         * preparing tx buf.
                         * As we are the last one that make tx non-busy.
                         * wake up someone who may wait for us.
                         */
                        cv_broadcast(&dp->tx_drain_cv);
                } else {
                        ASSERT(dp->tx_softq_tail - dp->tx_softq_head > 0);
                        gem_tx_start_unit(dp);
                }
        }
        dp->stats.obytes += len_total;
        dp->stats.opackets += nmblk;
        dp->stats.obcast += bcast;
        dp->stats.omcast += mcast;
done:
        mutex_exit(&dp->xmitlock);

        return (mp_head);
}

/* ========================================================== */
/*
 * error detection and restart routines
 */
/* ========================================================== */
int
gem_restart_nic(struct gem_dev *dp, uint_t flags)
{
        ASSERT(mutex_owned(&dp->intrlock));

        DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__));
#ifdef GEM_DEBUG_LEVEL
#if GEM_DEBUG_LEVEL > 1
        gem_dump_txbuf(dp, CE_CONT, "gem_restart_nic");
#endif
#endif

        if (dp->mac_suspended) {
                /* should we return GEM_FAILURE ? */
                return (GEM_FAILURE);
        }

        /*
         * We should avoid calling any routines except xxx_chip_reset
         * when we are resuming the system.
         */
        if (dp->mac_active) {
                if (flags & GEM_RESTART_KEEP_BUF) {
                        /* stop rx gracefully */
                        dp->rxmode &= ~RXMODE_ENABLE;
                        (void) (*dp->gc.gc_set_rx_filter)(dp);
                }
                (void) gem_mac_stop(dp, flags);
        }

        /* reset the chip. */
        if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) {
                cmn_err(CE_WARN, "%s: %s: failed to reset chip",
                    dp->name, __func__);
                goto err;
        }

        if (gem_mac_init(dp) != GEM_SUCCESS) {
                goto err;
        }

        /* setup media mode if the link have been up */
        if (dp->mii_state == MII_STATE_LINKUP) {
                if ((dp->gc.gc_set_media)(dp) != GEM_SUCCESS) {
                        goto err;
                }
        }

        /* setup mac address and enable rx filter */
        dp->rxmode |= RXMODE_ENABLE;
        if ((*dp->gc.gc_set_rx_filter)(dp) != GEM_SUCCESS) {
                goto err;
        }

        /*
         * XXX - a panic happened because of linkdown.
         * We must check mii_state here, because the link can be down just
         * before the restart event happen. If the link is down now,
         * gem_mac_start() will be called from gem_mii_link_check() when
         * the link become up later.
         */
        if (dp->mii_state == MII_STATE_LINKUP) {
                /* restart the nic */
                ASSERT(!dp->mac_active);
                (void) gem_mac_start(dp);
        }
        return (GEM_SUCCESS);
err:
        return (GEM_FAILURE);
}


static void
gem_tx_timeout(struct gem_dev *dp)
{
        clock_t         now;
        boolean_t       tx_sched;
        struct txbuf    *tbp;

        mutex_enter(&dp->intrlock);

        tx_sched = B_FALSE;
        now = ddi_get_lbolt();

        mutex_enter(&dp->xmitlock);
        if (!dp->mac_active || dp->mii_state != MII_STATE_LINKUP) {
                mutex_exit(&dp->xmitlock);
                goto schedule_next;
        }
        mutex_exit(&dp->xmitlock);

        /* reclaim transmitted buffers to check the trasmitter hangs or not. */
        if (gem_reclaim_txbuf(dp) != GEM_SUCCESS) {
                /* tx error happened, reset transmitter in the chip */
                (void) gem_restart_nic(dp, 0);
                tx_sched = B_TRUE;
                dp->tx_blocked = (clock_t)0;

                goto schedule_next;
        }

        mutex_enter(&dp->xmitlock);
        /* check if the transmitter thread is stuck */
        if (dp->tx_active_head == dp->tx_active_tail) {
                /* no tx buffer is loaded to the nic */
                if (dp->tx_blocked &&
                    now - dp->tx_blocked > dp->gc.gc_tx_timeout_interval) {
                        gem_dump_txbuf(dp, CE_WARN,
                            "gem_tx_timeout: tx blocked");
                        tx_sched = B_TRUE;
                        dp->tx_blocked = (clock_t)0;
                }
                mutex_exit(&dp->xmitlock);
                goto schedule_next;
        }

        tbp = GET_TXBUF(dp, dp->tx_active_head);
        if (now - tbp->txb_stime < dp->gc.gc_tx_timeout) {
                mutex_exit(&dp->xmitlock);
                goto schedule_next;
        }
        mutex_exit(&dp->xmitlock);

        gem_dump_txbuf(dp, CE_WARN, "gem_tx_timeout: tx timeout");

        /* discard untransmitted packet and restart tx.  */
        (void) gem_restart_nic(dp, GEM_RESTART_NOWAIT);
        tx_sched = B_TRUE;
        dp->tx_blocked = (clock_t)0;

schedule_next:
        mutex_exit(&dp->intrlock);

        /* restart the downstream if needed */
        if (tx_sched) {
                mac_tx_update(dp->mh);
        }

        DPRINTF(4, (CE_CONT,
            "!%s: blocked:%d active_head:%d active_tail:%d desc_intr:%d",
            dp->name, BOOLEAN(dp->tx_blocked),
            dp->tx_active_head, dp->tx_active_tail, dp->tx_desc_intr));
        dp->timeout_id =
            timeout((void (*)(void *))gem_tx_timeout,
            (void *)dp, dp->gc.gc_tx_timeout_interval);
}

/* ================================================================== */
/*
 * Interrupt handler
 */
/* ================================================================== */
__INLINE__
static void
gem_append_rxbuf(struct gem_dev *dp, struct rxbuf *rbp_head)
{
        struct rxbuf    *rbp;
        seqnum_t        tail;
        int             rx_ring_size = dp->gc.gc_rx_ring_size;

        ASSERT(rbp_head != NULL);
        ASSERT(mutex_owned(&dp->intrlock));

        DPRINTF(3, (CE_CONT, "!%s: %s: slot_head:%d, slot_tail:%d",
            dp->name, __func__, dp->rx_active_head, dp->rx_active_tail));

        /*
         * Add new buffers into active rx buffer list
         */
        if (dp->rx_buf_head == NULL) {
                dp->rx_buf_head = rbp_head;
                ASSERT(dp->rx_buf_tail == NULL);
        } else {
                dp->rx_buf_tail->rxb_next = rbp_head;
        }

        tail = dp->rx_active_tail;
        for (rbp = rbp_head; rbp; rbp = rbp->rxb_next) {
                /* need to notify the tail for the lower layer */
                dp->rx_buf_tail = rbp;

                dp->gc.gc_rx_desc_write(dp,
                    SLOT(tail, rx_ring_size),
                    rbp->rxb_dmacookie,
                    rbp->rxb_nfrags);

                dp->rx_active_tail = tail = tail + 1;
        }
}

mblk_t *
gem_get_packet_default(struct gem_dev *dp, struct rxbuf *rbp, size_t len)
{
        int             rx_header_len = dp->gc.gc_rx_header_len;
        uint8_t         *bp;
        mblk_t          *mp;

        /* allocate a new mblk */
        if (mp = allocb(len + VTAG_SIZE, BPRI_MED)) {
                ASSERT(mp->b_next == NULL);
                ASSERT(mp->b_cont == NULL);

                mp->b_rptr += VTAG_SIZE;
                bp = mp->b_rptr;
                mp->b_wptr = bp + len;

                /*
                 * flush the range of the entire buffer to invalidate
                 * all of corresponding dirty entries in iocache.
                 */
                (void) ddi_dma_sync(rbp->rxb_dh, rx_header_len,
                    0, DDI_DMA_SYNC_FORKERNEL);

                bcopy(rbp->rxb_buf + rx_header_len, bp, len);
        }
        return (mp);
}

#ifdef GEM_DEBUG_LEVEL
uint_t  gem_rx_pkts[17];
#endif


int
gem_receive(struct gem_dev *dp)
{
        uint64_t        len_total = 0;
        struct rxbuf    *rbp;
        mblk_t          *mp;
        int             cnt = 0;
        uint64_t        rxstat;
        struct rxbuf    *newbufs;
        struct rxbuf    **newbufs_tailp;
        mblk_t          *rx_head;
        mblk_t          **rx_tailp;
        int             rx_ring_size = dp->gc.gc_rx_ring_size;
        seqnum_t        active_head;
        uint64_t        (*rx_desc_stat)(struct gem_dev *dp,
            int slot, int ndesc);
        int             ethermin = ETHERMIN;
        int             ethermax = dp->mtu + sizeof (struct ether_header);
        int             rx_header_len = dp->gc.gc_rx_header_len;

        ASSERT(mutex_owned(&dp->intrlock));

        DPRINTF(3, (CE_CONT, "!%s: gem_receive: rx_buf_head:%p",
            dp->name, dp->rx_buf_head));

        rx_desc_stat  = dp->gc.gc_rx_desc_stat;
        newbufs_tailp = &newbufs;
        rx_tailp = &rx_head;
        for (active_head = dp->rx_active_head;
            (rbp = dp->rx_buf_head) != NULL; active_head++) {
                int             len;
                if (cnt == 0) {
                        cnt = max(dp->poll_pkt_delay*2, 10);
                        cnt = min(cnt,
                            dp->rx_active_tail - active_head);
                        gem_rx_desc_dma_sync(dp,
                            SLOT(active_head, rx_ring_size),
                            cnt,
                            DDI_DMA_SYNC_FORKERNEL);
                }

                if (rx_header_len > 0) {
                        (void) ddi_dma_sync(rbp->rxb_dh, 0,
                            rx_header_len, DDI_DMA_SYNC_FORKERNEL);
                }

                if (((rxstat = (*rx_desc_stat)(dp,
                    SLOT(active_head, rx_ring_size),
                    rbp->rxb_nfrags))
                    & (GEM_RX_DONE | GEM_RX_ERR)) == 0) {
                        /* not received yet */
                        break;
                }

                /* Remove the head of the rx buffer list */
                dp->rx_buf_head = rbp->rxb_next;
                cnt--;


                if (rxstat & GEM_RX_ERR) {
                        goto next;
                }

                len = rxstat & GEM_RX_LEN;
                DPRINTF(3, (CE_CONT, "!%s: %s: rxstat:0x%llx, len:0x%x",
                    dp->name, __func__, rxstat, len));

                /*
                 * Copy the packet
                 */
                if ((mp = dp->gc.gc_get_packet(dp, rbp, len)) == NULL) {
                        /* no memory, discard the packet */
                        dp->stats.norcvbuf++;
                        goto next;
                }

                /*
                 * Process VLAN tag
                 */
                ethermin = ETHERMIN;
                ethermax = dp->mtu + sizeof (struct ether_header);
                if (GET_NET16(mp->b_rptr + VTAG_OFF) == VTAG_TPID) {
                        ethermax += VTAG_SIZE;
                }

                /* check packet size */
                if (len < ethermin) {
                        dp->stats.errrcv++;
                        dp->stats.runt++;
                        freemsg(mp);
                        goto next;
                }

                if (len > ethermax) {
                        dp->stats.errrcv++;
                        dp->stats.frame_too_long++;
                        freemsg(mp);
                        goto next;
                }

                len_total += len;

#ifdef GEM_DEBUG_VLAN
                if (GET_ETHERTYPE(mp->b_rptr) == VTAG_TPID) {
                        gem_dump_packet(dp, (char *)__func__, mp, B_TRUE);
                }
#endif
                /* append received packet to temporaly rx buffer list */
                *rx_tailp = mp;
                rx_tailp  = &mp->b_next;

                if (mp->b_rptr[0] & 1) {
                        if (bcmp(mp->b_rptr,
                            gem_etherbroadcastaddr.ether_addr_octet,
                            ETHERADDRL) == 0) {
                                dp->stats.rbcast++;
                        } else {
                                dp->stats.rmcast++;
                        }
                }
next:
                ASSERT(rbp != NULL);

                /* append new one to temporal new buffer list */
                *newbufs_tailp = rbp;
                newbufs_tailp  = &rbp->rxb_next;
        }

        /* advance rx_active_head */
        if ((cnt = active_head - dp->rx_active_head) > 0) {
                dp->stats.rbytes += len_total;
                dp->stats.rpackets += cnt;
        }
        dp->rx_active_head = active_head;

        /* terminate the working list */
        *newbufs_tailp = NULL;
        *rx_tailp = NULL;

        if (dp->rx_buf_head == NULL) {
                dp->rx_buf_tail = NULL;
        }

        DPRINTF(4, (CE_CONT, "%s: %s: cnt:%d, rx_head:%p",
            dp->name, __func__, cnt, rx_head));

        if (newbufs) {
                /*
                 * fillfull rx list with new buffers
                 */
                seqnum_t        head;

                /* save current tail */
                head = dp->rx_active_tail;
                gem_append_rxbuf(dp, newbufs);

                /* call hw depend start routine if we have. */
                dp->gc.gc_rx_start(dp,
                    SLOT(head, rx_ring_size), dp->rx_active_tail - head);
        }

        if (rx_head) {
                /*
                 * send up received packets
                 */
                mutex_exit(&dp->intrlock);
                mac_rx(dp->mh, NULL, rx_head);
                mutex_enter(&dp->intrlock);
        }

#ifdef GEM_DEBUG_LEVEL
        gem_rx_pkts[min(cnt, sizeof (gem_rx_pkts)/sizeof (uint_t)-1)]++;
#endif
        return (cnt);
}

boolean_t
gem_tx_done(struct gem_dev *dp)
{
        boolean_t       tx_sched = B_FALSE;

        if (gem_reclaim_txbuf(dp) != GEM_SUCCESS) {
                (void) gem_restart_nic(dp, GEM_RESTART_KEEP_BUF);
                DPRINTF(2, (CE_CONT, "!%s: gem_tx_done: tx_desc: %d %d",
                    dp->name, dp->tx_active_head, dp->tx_active_tail));
                tx_sched = B_TRUE;
                goto x;
        }

        mutex_enter(&dp->xmitlock);

        /* XXX - we must not have any packets in soft queue */
        ASSERT(dp->tx_softq_head == dp->tx_softq_tail);
        /*
         * If we won't have chance to get more free tx buffers, and blocked,
         * it is worth to reschedule the downstream i.e. tx side.
         */
        ASSERT(dp->tx_desc_intr - dp->tx_desc_head >= 0);
        if (dp->tx_blocked && dp->tx_desc_intr == dp->tx_desc_head) {
                /*
                 * As no further tx-done interrupts are scheduled, this
                 * is the last chance to kick tx side, which may be
                 * blocked now, otherwise the tx side never works again.
                 */
                tx_sched = B_TRUE;
                dp->tx_blocked = (clock_t)0;
                dp->tx_max_packets =
                    min(dp->tx_max_packets + 2, dp->gc.gc_tx_buf_limit);
        }

        mutex_exit(&dp->xmitlock);

        DPRINTF(3, (CE_CONT, "!%s: %s: ret: blocked:%d",
            dp->name, __func__, BOOLEAN(dp->tx_blocked)));
x:
        return (tx_sched);
}

static uint_t
gem_intr(struct gem_dev *dp)
{
        uint_t          ret;

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return (DDI_INTR_UNCLAIMED);
        }
        dp->intr_busy = B_TRUE;

        ret = (*dp->gc.gc_interrupt)(dp);

        if (ret == DDI_INTR_UNCLAIMED) {
                dp->intr_busy = B_FALSE;
                mutex_exit(&dp->intrlock);
                return (ret);
        }

        if (!dp->mac_active) {
                cv_broadcast(&dp->tx_drain_cv);
        }


        dp->stats.intr++;
        dp->intr_busy = B_FALSE;

        mutex_exit(&dp->intrlock);

        if (ret & INTR_RESTART_TX) {
                DPRINTF(4, (CE_CONT, "!%s: calling mac_tx_update", dp->name));
                mac_tx_update(dp->mh);
                ret &= ~INTR_RESTART_TX;
        }
        return (ret);
}

static void
gem_intr_watcher(struct gem_dev *dp)
{
        (void) gem_intr(dp);

        /* schedule next call of tu_intr_watcher */
        dp->intr_watcher_id =
            timeout((void (*)(void *))gem_intr_watcher, (void *)dp, 1);
}

/* ======================================================================== */
/*
 * MII support routines
 */
/* ======================================================================== */
static void
gem_choose_forcedmode(struct gem_dev *dp)
{
        /* choose media mode */
        if (dp->anadv_1000fdx || dp->anadv_1000hdx) {
                dp->speed = GEM_SPD_1000;
                dp->full_duplex = dp->anadv_1000fdx;
        } else if (dp->anadv_100fdx || dp->anadv_100t4) {
                dp->speed = GEM_SPD_100;
                dp->full_duplex = B_TRUE;
        } else if (dp->anadv_100hdx) {
                dp->speed = GEM_SPD_100;
                dp->full_duplex = B_FALSE;
        } else {
                dp->speed = GEM_SPD_10;
                dp->full_duplex = dp->anadv_10fdx;
        }
}

uint16_t
gem_mii_read(struct gem_dev *dp, uint_t reg)
{
        if ((dp->mii_status & MII_STATUS_MFPRMBLSUPR) == 0) {
                (*dp->gc.gc_mii_sync)(dp);
        }
        return ((*dp->gc.gc_mii_read)(dp, reg));
}

void
gem_mii_write(struct gem_dev *dp, uint_t reg, uint16_t val)
{
        if ((dp->mii_status & MII_STATUS_MFPRMBLSUPR) == 0) {
                (*dp->gc.gc_mii_sync)(dp);
        }
        (*dp->gc.gc_mii_write)(dp, reg, val);
}

#define fc_cap_decode(x)        \
        ((((x) & MII_ABILITY_PAUSE) ? 1 : 0) |  \
        (((x) & MII_ABILITY_ASMPAUSE) ? 2 : 0))

int
gem_mii_config_default(struct gem_dev *dp)
{
        uint16_t        mii_stat;
        uint16_t        val;
        static uint16_t fc_cap_encode[4] = {
                0, /* none */
                MII_ABILITY_PAUSE, /* symmetric */
                MII_ABILITY_ASMPAUSE, /* tx */
                MII_ABILITY_PAUSE | MII_ABILITY_ASMPAUSE, /* rx-symmetric */
        };

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

        /*
         * Configure bits in advertisement register
         */
        mii_stat = dp->mii_status;

        DPRINTF(1, (CE_CONT, "!%s: %s: MII_STATUS reg:%b",
            dp->name, __func__, mii_stat, MII_STATUS_BITS));

        if ((mii_stat & MII_STATUS_ABILITY_TECH) == 0) {
                /* it's funny */
                cmn_err(CE_WARN, "!%s: wrong ability bits: mii_status:%b",
                    dp->name, mii_stat, MII_STATUS_BITS);
                return (GEM_FAILURE);
        }

        /* Do not change the rest of the ability bits in the advert reg */
        val = gem_mii_read(dp, MII_AN_ADVERT) & ~MII_ABILITY_ALL;

        DPRINTF(0, (CE_CONT,
            "!%s: %s: 100T4:%d 100F:%d 100H:%d 10F:%d 10H:%d",
            dp->name, __func__,
            dp->anadv_100t4, dp->anadv_100fdx, dp->anadv_100hdx,
            dp->anadv_10fdx, dp->anadv_10hdx));

        if (dp->anadv_100t4) {
                val |= MII_ABILITY_100BASE_T4;
        }
        if (dp->anadv_100fdx) {
                val |= MII_ABILITY_100BASE_TX_FD;
        }
        if (dp->anadv_100hdx) {
                val |= MII_ABILITY_100BASE_TX;
        }
        if (dp->anadv_10fdx) {
                val |= MII_ABILITY_10BASE_T_FD;
        }
        if (dp->anadv_10hdx) {
                val |= MII_ABILITY_10BASE_T;
        }

        /* set flow control capability */
        val |= fc_cap_encode[dp->anadv_flow_control];

        DPRINTF(0, (CE_CONT,
            "!%s: %s: setting MII_AN_ADVERT reg:%b, mii_mode:%d, fc:%d",
            dp->name, __func__, val, MII_ABILITY_BITS, dp->gc.gc_mii_mode,
            dp->anadv_flow_control));

        gem_mii_write(dp, MII_AN_ADVERT, val);

        if (mii_stat & MII_STATUS_XSTATUS) {
                /*
                 * 1000Base-T GMII support
                 */
                if (!dp->anadv_autoneg) {
                        /* enable manual configuration */
                        val = MII_1000TC_CFG_EN;
                } else {
                        val = 0;
                        if (dp->anadv_1000fdx) {
                                val |= MII_1000TC_ADV_FULL;
                        }
                        if (dp->anadv_1000hdx) {
                                val |= MII_1000TC_ADV_HALF;
                        }
                }
                DPRINTF(0, (CE_CONT,
                    "!%s: %s: setting MII_1000TC reg:%b",
                    dp->name, __func__, val, MII_1000TC_BITS));

                gem_mii_write(dp, MII_1000TC, val);
        }

        return (GEM_SUCCESS);
}

#define GEM_LINKUP(dp)          mac_link_update((dp)->mh, LINK_STATE_UP)
#define GEM_LINKDOWN(dp)        mac_link_update((dp)->mh, LINK_STATE_DOWN)

static uint8_t gem_fc_result[4 /* my cap */ ][4 /* lp cap */] = {
/*       none   symm    tx      rx/symm */
/* none */
        {FLOW_CONTROL_NONE,
                FLOW_CONTROL_NONE,
                        FLOW_CONTROL_NONE,
                                FLOW_CONTROL_NONE},
/* sym */
        {FLOW_CONTROL_NONE,
                FLOW_CONTROL_SYMMETRIC,
                        FLOW_CONTROL_NONE,
                                FLOW_CONTROL_SYMMETRIC},
/* tx */
        {FLOW_CONTROL_NONE,
                FLOW_CONTROL_NONE,
                        FLOW_CONTROL_NONE,
                                FLOW_CONTROL_TX_PAUSE},
/* rx/symm */
        {FLOW_CONTROL_NONE,
                FLOW_CONTROL_SYMMETRIC,
                        FLOW_CONTROL_RX_PAUSE,
                                FLOW_CONTROL_SYMMETRIC},
};

static char *gem_fc_type[] = {
        "without",
        "with symmetric",
        "with tx",
        "with rx",
};

boolean_t
gem_mii_link_check(struct gem_dev *dp)
{
        uint16_t        old_mii_state;
        boolean_t       tx_sched = B_FALSE;
        uint16_t        status;
        uint16_t        advert;
        uint16_t        lpable;
        uint16_t        exp;
        uint16_t        ctl1000;
        uint16_t        stat1000;
        uint16_t        val;
        clock_t         now;
        clock_t         diff;
        int             linkdown_action;
        boolean_t       fix_phy = B_FALSE;

        now = ddi_get_lbolt();
        old_mii_state = dp->mii_state;

        DPRINTF(3, (CE_CONT, "!%s: %s: time:%d state:%d",
            dp->name, __func__, now, dp->mii_state));

        diff = now - dp->mii_last_check;
        dp->mii_last_check = now;

        /*
         * For NWAM, don't show linkdown state right
         * after the system boots
         */
        if (dp->linkup_delay > 0) {
                if (dp->linkup_delay > diff) {
                        dp->linkup_delay -= diff;
                } else {
                        /* link up timeout */
                        dp->linkup_delay = -1;
                }
        }

next_nowait:
        switch (dp->mii_state) {
        case MII_STATE_UNKNOWN:
                /* power-up, DP83840 requires 32 sync bits */
                (*dp->gc.gc_mii_sync)(dp);
                goto reset_phy;

        case MII_STATE_RESETTING:
                dp->mii_timer -= diff;
                if (dp->mii_timer > 0) {
                        /* don't read phy registers in resetting */
                        dp->mii_interval = WATCH_INTERVAL_FAST;
                        goto next;
                }

                /* Timer expired, ensure reset bit is not set */

                if (dp->mii_status & MII_STATUS_MFPRMBLSUPR) {
                        /* some phys need sync bits after reset */
                        (*dp->gc.gc_mii_sync)(dp);
                }
                val = gem_mii_read(dp, MII_CONTROL);
                if (val & MII_CONTROL_RESET) {
                        cmn_err(CE_NOTE,
                            "!%s: time:%ld resetting phy not complete."
                            " mii_control:0x%b",
                            dp->name, ddi_get_lbolt(),
                            val, MII_CONTROL_BITS);
                }

                /* ensure neither isolated nor pwrdown nor auto-nego mode */
                /* XXX -- this operation is required for NS DP83840A. */
                gem_mii_write(dp, MII_CONTROL, 0);

                /* As resetting PHY has completed, configure PHY registers */
                if ((*dp->gc.gc_mii_config)(dp) != GEM_SUCCESS) {
                        /* we failed to configure PHY. */
                        goto reset_phy;
                }

                /* mii_config may disable autonegatiation */
                gem_choose_forcedmode(dp);

                dp->mii_lpable = 0;
                dp->mii_advert = 0;
                dp->mii_exp = 0;
                dp->mii_ctl1000 = 0;
                dp->mii_stat1000 = 0;
                dp->flow_control = FLOW_CONTROL_NONE;

                if (!dp->anadv_autoneg) {
                        /* skip auto-negotiation phase */
                        dp->mii_state = MII_STATE_MEDIA_SETUP;
                        dp->mii_timer = 0;
                        dp->mii_interval = 0;
                        goto next_nowait;
                }

                /* Issue auto-negotiation command */
                goto autonego;

        case MII_STATE_AUTONEGOTIATING:
                /*
                 * Autonegotiation is in progress
                 */
                dp->mii_timer -= diff;
                if (dp->mii_timer -
                    (dp->gc.gc_mii_an_timeout
                    - dp->gc.gc_mii_an_wait) > 0) {
                        /*
                         * wait for a while, typically autonegotiation
                         * completes in 2.3 - 2.5 sec.
                         */
                        dp->mii_interval = WATCH_INTERVAL_FAST;
                        goto next;
                }

                /* read PHY status */
                status = gem_mii_read(dp, MII_STATUS);
                DPRINTF(4, (CE_CONT,
                    "!%s: %s: called: mii_state:%d MII_STATUS reg:%b",
                    dp->name, __func__, dp->mii_state,
                    status, MII_STATUS_BITS));

                if (status & MII_STATUS_REMFAULT) {
                        /*
                         * The link parnert told me something wrong happend.
                         * What do we do ?
                         */
                        cmn_err(CE_CONT,
                            "!%s: auto-negotiation failed: remote fault",
                            dp->name);
                        goto autonego;
                }

                if ((status & MII_STATUS_ANDONE) == 0) {
                        if (dp->mii_timer <= 0) {
                                /*
                                 * Auto-negotiation was timed out,
                                 * try again w/o resetting phy.
                                 */
                                if (!dp->mii_supress_msg) {
                                        cmn_err(CE_WARN,
                                    "!%s: auto-negotiation failed: timeout",
                                            dp->name);
                                        dp->mii_supress_msg = B_TRUE;
                                }
                                goto autonego;
                        }
                        /*
                         * Auto-negotiation is in progress. Wait.
                         */
                        dp->mii_interval = dp->gc.gc_mii_an_watch_interval;
                        goto next;
                }

                /*
                 * Auto-negotiation have completed.
                 * Assume linkdown and fall through.
                 */
                dp->mii_supress_msg = B_FALSE;
                dp->mii_state = MII_STATE_AN_DONE;
                DPRINTF(0, (CE_CONT,
                    "!%s: auto-negotiation completed, MII_STATUS:%b",
                    dp->name, status, MII_STATUS_BITS));

                if (dp->gc.gc_mii_an_delay > 0) {
                        dp->mii_timer = dp->gc.gc_mii_an_delay;
                        dp->mii_interval = drv_usectohz(20*1000);
                        goto next;
                }

                dp->mii_timer = 0;
                diff = 0;
                goto next_nowait;

        case MII_STATE_AN_DONE:
                /*
                 * Auto-negotiation have done. Now we can set up media.
                 */
                dp->mii_timer -= diff;
                if (dp->mii_timer > 0) {
                        /* wait for a while */
                        dp->mii_interval = WATCH_INTERVAL_FAST;
                        goto next;
                }

                /*
                 * set up the result of auto negotiation
                 */

                /*
                 * Read registers required to determin current
                 * duplex mode and media speed.
                 */
                if (dp->gc.gc_mii_an_delay > 0) {
                        /*
                         * As the link watcher context has been suspended,
                         * 'status' is invalid. We must status register here
                         */
                        status = gem_mii_read(dp, MII_STATUS);
                }
                advert = gem_mii_read(dp, MII_AN_ADVERT);
                lpable = gem_mii_read(dp, MII_AN_LPABLE);
                exp = gem_mii_read(dp, MII_AN_EXPANSION);
                if (exp == 0xffff) {
                        /* some phys don't have exp register */
                        exp = 0;
                }
                ctl1000  = 0;
                stat1000 = 0;
                if (dp->mii_status & MII_STATUS_XSTATUS) {
                        ctl1000  = gem_mii_read(dp, MII_1000TC);
                        stat1000 = gem_mii_read(dp, MII_1000TS);
                }
                dp->mii_lpable = lpable;
                dp->mii_advert = advert;
                dp->mii_exp = exp;
                dp->mii_ctl1000  = ctl1000;
                dp->mii_stat1000 = stat1000;

                cmn_err(CE_CONT,
                "!%s: auto-negotiation done, advert:%b, lpable:%b, exp:%b",
                    dp->name,
                    advert, MII_ABILITY_BITS,
                    lpable, MII_ABILITY_BITS,
                    exp, MII_AN_EXP_BITS);

                if (dp->mii_status & MII_STATUS_XSTATUS) {
                        cmn_err(CE_CONT,
                            "! MII_1000TC:%b, MII_1000TS:%b",
                            ctl1000, MII_1000TC_BITS,
                            stat1000, MII_1000TS_BITS);
                }

                if (gem_population(lpable) <= 1 &&
                    (exp & MII_AN_EXP_LPCANAN) == 0) {
                        if ((advert & MII_ABILITY_TECH) != lpable) {
                                cmn_err(CE_WARN,
                                    "!%s: but the link partnar doesn't seem"
                                    " to have auto-negotiation capability."
                                    " please check the link configuration.",
                                    dp->name);
                        }
                        /*
                         * it should be result of parallel detection, which
                         * cannot detect duplex mode.
                         */
                        if (lpable & MII_ABILITY_100BASE_TX) {
                                /*
                                 * we prefer full duplex mode for 100Mbps
                                 * connection, if we can.
                                 */
                                lpable |= advert & MII_ABILITY_100BASE_TX_FD;
                        }

                        if ((advert & lpable) == 0 &&
                            lpable & MII_ABILITY_10BASE_T) {
                                lpable |= advert & MII_ABILITY_10BASE_T_FD;
                        }
                        /*
                         * as the link partnar isn't auto-negotiatable, use
                         * fixed mode temporally.
                         */
                        fix_phy = B_TRUE;
                } else if (lpable == 0) {
                        cmn_err(CE_WARN, "!%s: wrong lpable.", dp->name);
                        goto reset_phy;
                }
                /*
                 * configure current link mode according to AN priority.
                 */
                val = advert & lpable;
                if ((ctl1000 & MII_1000TC_ADV_FULL) &&
                    (stat1000 & MII_1000TS_LP_FULL)) {
                        /* 1000BaseT & full duplex */
                        dp->speed        = GEM_SPD_1000;
                        dp->full_duplex  = B_TRUE;
                } else if ((ctl1000 & MII_1000TC_ADV_HALF) &&
                    (stat1000 & MII_1000TS_LP_HALF)) {
                        /* 1000BaseT & half duplex */
                        dp->speed = GEM_SPD_1000;
                        dp->full_duplex = B_FALSE;
                } else if (val & MII_ABILITY_100BASE_TX_FD) {
                        /* 100BaseTx & full duplex */
                        dp->speed = GEM_SPD_100;
                        dp->full_duplex = B_TRUE;
                } else if (val & MII_ABILITY_100BASE_T4) {
                        /* 100BaseT4 & full duplex */
                        dp->speed = GEM_SPD_100;
                        dp->full_duplex = B_TRUE;
                } else if (val & MII_ABILITY_100BASE_TX) {
                        /* 100BaseTx & half duplex */
                        dp->speed        = GEM_SPD_100;
                        dp->full_duplex  = B_FALSE;
                } else if (val & MII_ABILITY_10BASE_T_FD) {
                        /* 10BaseT & full duplex */
                        dp->speed        = GEM_SPD_10;
                        dp->full_duplex  = B_TRUE;
                } else if (val & MII_ABILITY_10BASE_T) {
                        /* 10BaseT & half duplex */
                        dp->speed        = GEM_SPD_10;
                        dp->full_duplex  = B_FALSE;
                } else {
                        /*
                         * It seems that the link partnar doesn't have
                         * auto-negotiation capability and our PHY
                         * could not report the correct current mode.
                         * We guess current mode by mii_control register.
                         */
                        val = gem_mii_read(dp, MII_CONTROL);

                        /* select 100m full or 10m half */
                        dp->speed = (val & MII_CONTROL_100MB) ?
                            GEM_SPD_100 : GEM_SPD_10;
                        dp->full_duplex = dp->speed != GEM_SPD_10;
                        fix_phy = B_TRUE;

                        cmn_err(CE_NOTE,
                            "!%s: auto-negotiation done but "
                            "common ability not found.\n"
                            "PHY state: control:%b advert:%b lpable:%b\n"
                            "guessing %d Mbps %s duplex mode",
                            dp->name,
                            val, MII_CONTROL_BITS,
                            advert, MII_ABILITY_BITS,
                            lpable, MII_ABILITY_BITS,
                            gem_speed_value[dp->speed],
                            dp->full_duplex ? "full" : "half");
                }

                if (dp->full_duplex) {
                        dp->flow_control =
                            gem_fc_result[fc_cap_decode(advert)]
                            [fc_cap_decode(lpable)];
                } else {
                        dp->flow_control = FLOW_CONTROL_NONE;
                }
                dp->mii_state = MII_STATE_MEDIA_SETUP;
                /* FALLTHROUGH */

        case MII_STATE_MEDIA_SETUP:
                dp->mii_state = MII_STATE_LINKDOWN;
                dp->mii_timer = dp->gc.gc_mii_linkdown_timeout;
                DPRINTF(2, (CE_CONT, "!%s: setup midia mode done", dp->name));
                dp->mii_supress_msg = B_FALSE;

                /* use short interval */
                dp->mii_interval = WATCH_INTERVAL_FAST;

                if ((!dp->anadv_autoneg) ||
                    dp->gc.gc_mii_an_oneshot || fix_phy) {

                        /*
                         * write specified mode to phy.
                         */
                        val = gem_mii_read(dp, MII_CONTROL);
                        val &= ~(MII_CONTROL_SPEED | MII_CONTROL_FDUPLEX |
                            MII_CONTROL_ANE | MII_CONTROL_RSAN);

                        if (dp->full_duplex) {
                                val |= MII_CONTROL_FDUPLEX;
                        }

                        switch (dp->speed) {
                        case GEM_SPD_1000:
                                val |= MII_CONTROL_1000MB;
                                break;

                        case GEM_SPD_100:
                                val |= MII_CONTROL_100MB;
                                break;

                        default:
                                cmn_err(CE_WARN, "%s: unknown speed:%d",
                                    dp->name, dp->speed);
                                /* FALLTHROUGH */
                        case GEM_SPD_10:
                                /* for GEM_SPD_10, do nothing */
                                break;
                        }

                        if (dp->mii_status & MII_STATUS_XSTATUS) {
                                gem_mii_write(dp,
                                    MII_1000TC, MII_1000TC_CFG_EN);
                        }
                        gem_mii_write(dp, MII_CONTROL, val);
                }

                if (dp->nic_state >= NIC_STATE_INITIALIZED) {
                        /* notify the result of auto-negotiation to mac */
                        (*dp->gc.gc_set_media)(dp);
                }

                if ((void *)dp->gc.gc_mii_tune_phy) {
                        /* for built-in sis900 */
                        /* XXX - this code should be removed.  */
                        (*dp->gc.gc_mii_tune_phy)(dp);
                }

                goto next_nowait;

        case MII_STATE_LINKDOWN:
                status = gem_mii_read(dp, MII_STATUS);
                if (status & MII_STATUS_LINKUP) {
                        /*
                         * Link going up
                         */
                        dp->mii_state = MII_STATE_LINKUP;
                        dp->mii_supress_msg = B_FALSE;

                        DPRINTF(0, (CE_CONT,
                            "!%s: link up detected: mii_stat:%b",
                            dp->name, status, MII_STATUS_BITS));

                        /*
                         * MII_CONTROL_100MB and  MII_CONTROL_FDUPLEX are
                         * ignored when MII_CONTROL_ANE is set.
                         */
                        cmn_err(CE_CONT,
                            "!%s: Link up: %d Mbps %s duplex %s flow control",
                            dp->name,
                            gem_speed_value[dp->speed],
                            dp->full_duplex ? "full" : "half",
                            gem_fc_type[dp->flow_control]);

                        dp->mii_interval = dp->gc.gc_mii_link_watch_interval;

                        /* XXX - we need other timer to watch statictics */
                        if (dp->gc.gc_mii_hw_link_detection &&
                            dp->nic_state == NIC_STATE_ONLINE) {
                                dp->mii_interval = 0;
                        }

                        if (dp->nic_state == NIC_STATE_ONLINE) {
                                if (!dp->mac_active) {
                                        (void) gem_mac_start(dp);
                                }
                                tx_sched = B_TRUE;
                        }
                        goto next;
                }

                dp->mii_supress_msg = B_TRUE;
                if (dp->anadv_autoneg) {
                        dp->mii_timer -= diff;
                        if (dp->mii_timer <= 0) {
                                /*
                                 * link down timer expired.
                                 * need to restart auto-negotiation.
                                 */
                                linkdown_action =
                                    dp->gc.gc_mii_linkdown_timeout_action;
                                goto restart_autonego;
                        }
                }
                /* don't change mii_state */
                break;

        case MII_STATE_LINKUP:
                status = gem_mii_read(dp, MII_STATUS);
                if ((status & MII_STATUS_LINKUP) == 0) {
                        /*
                         * Link going down
                         */
                        cmn_err(CE_NOTE,
                            "!%s: link down detected: mii_stat:%b",
                            dp->name, status, MII_STATUS_BITS);

                        if (dp->nic_state == NIC_STATE_ONLINE &&
                            dp->mac_active &&
                            dp->gc.gc_mii_stop_mac_on_linkdown) {
                                (void) gem_mac_stop(dp, 0);

                                if (dp->tx_blocked) {
                                        /* drain tx */
                                        tx_sched = B_TRUE;
                                }
                        }

                        if (dp->anadv_autoneg) {
                                /* need to restart auto-negotiation */
                                linkdown_action = dp->gc.gc_mii_linkdown_action;
                                goto restart_autonego;
                        }

                        dp->mii_state = MII_STATE_LINKDOWN;
                        dp->mii_timer = dp->gc.gc_mii_linkdown_timeout;

                        if ((void *)dp->gc.gc_mii_tune_phy) {
                                /* for built-in sis900 */
                                (*dp->gc.gc_mii_tune_phy)(dp);
                        }
                        dp->mii_interval = dp->gc.gc_mii_link_watch_interval;
                        goto next;
                }

                /* don't change mii_state */
                if (dp->gc.gc_mii_hw_link_detection &&
                    dp->nic_state == NIC_STATE_ONLINE) {
                        dp->mii_interval = 0;
                        goto next;
                }
                break;
        }
        dp->mii_interval = dp->gc.gc_mii_link_watch_interval;
        goto next;

        /* Actions on the end of state routine */

restart_autonego:
        switch (linkdown_action) {
        case MII_ACTION_RESET:
                if (!dp->mii_supress_msg) {
                        cmn_err(CE_CONT, "!%s: resetting PHY", dp->name);
                }
                dp->mii_supress_msg = B_TRUE;
                goto reset_phy;

        case MII_ACTION_NONE:
                dp->mii_supress_msg = B_TRUE;
                if (dp->gc.gc_mii_an_oneshot) {
                        goto autonego;
                }
                /* PHY will restart autonego automatically */
                dp->mii_state = MII_STATE_AUTONEGOTIATING;
                dp->mii_timer = dp->gc.gc_mii_an_timeout;
                dp->mii_interval = dp->gc.gc_mii_an_watch_interval;
                goto next;

        case MII_ACTION_RSA:
                if (!dp->mii_supress_msg) {
                        cmn_err(CE_CONT, "!%s: restarting auto-negotiation",
                            dp->name);
                }
                dp->mii_supress_msg = B_TRUE;
                goto autonego;

        default:
                cmn_err(CE_WARN, "!%s: unknowm linkdown action: %d",
                    dp->name, dp->gc.gc_mii_linkdown_action);
                dp->mii_supress_msg = B_TRUE;
        }
        /* NOTREACHED */

reset_phy:
        if (!dp->mii_supress_msg) {
                cmn_err(CE_CONT, "!%s: resetting PHY", dp->name);
        }
        dp->mii_state = MII_STATE_RESETTING;
        dp->mii_timer = dp->gc.gc_mii_reset_timeout;
        if (!dp->gc.gc_mii_dont_reset) {
                gem_mii_write(dp, MII_CONTROL, MII_CONTROL_RESET);
        }
        dp->mii_interval = WATCH_INTERVAL_FAST;
        goto next;

autonego:
        if (!dp->mii_supress_msg) {
                cmn_err(CE_CONT, "!%s: auto-negotiation started", dp->name);
        }
        dp->mii_state = MII_STATE_AUTONEGOTIATING;
        dp->mii_timer = dp->gc.gc_mii_an_timeout;

        /* start/restart auto nego */
        val = gem_mii_read(dp, MII_CONTROL) &
            ~(MII_CONTROL_ISOLATE | MII_CONTROL_PWRDN | MII_CONTROL_RESET);

        gem_mii_write(dp, MII_CONTROL,
            val | MII_CONTROL_RSAN | MII_CONTROL_ANE);

        dp->mii_interval = dp->gc.gc_mii_an_watch_interval;

next:
        if (dp->link_watcher_id == 0 && dp->mii_interval) {
                /* we must schedule next mii_watcher */
                dp->link_watcher_id =
                    timeout((void (*)(void *))&gem_mii_link_watcher,
                    (void *)dp, dp->mii_interval);
        }

        if (old_mii_state != dp->mii_state) {
                /* notify new mii link state */
                if (dp->mii_state == MII_STATE_LINKUP) {
                        dp->linkup_delay = 0;
                        GEM_LINKUP(dp);
                } else if (dp->linkup_delay <= 0) {
                        GEM_LINKDOWN(dp);
                }
        } else if (dp->linkup_delay < 0) {
                /* first linkup timeout */
                dp->linkup_delay = 0;
                GEM_LINKDOWN(dp);
        }

        return (tx_sched);
}

static void
gem_mii_link_watcher(struct gem_dev *dp)
{
        boolean_t       tx_sched;

        mutex_enter(&dp->intrlock);

        dp->link_watcher_id = 0;
        tx_sched = gem_mii_link_check(dp);
#if GEM_DEBUG_LEVEL > 2
        if (dp->link_watcher_id == 0) {
                cmn_err(CE_CONT, "%s: link watcher stopped", dp->name);
        }
#endif
        mutex_exit(&dp->intrlock);

        if (tx_sched) {
                /* kick potentially stopped downstream */
                mac_tx_update(dp->mh);
        }
}

int
gem_mii_probe_default(struct gem_dev *dp)
{
        int8_t          phy;
        uint16_t        status;
        uint16_t        adv;
        uint16_t        adv_org;

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

        /*
         * Scan PHY
         */
        /* ensure to send sync bits */
        dp->mii_status = 0;

        /* Try default phy first */
        if (dp->mii_phy_addr) {
                status = gem_mii_read(dp, MII_STATUS);
                if (status != 0xffff && status != 0) {
                        gem_mii_write(dp, MII_CONTROL, 0);
                        goto PHY_found;
                }

                if (dp->mii_phy_addr < 0) {
                        cmn_err(CE_NOTE,
            "!%s: failed to probe default internal and/or non-MII PHY",
                            dp->name);
                        return (GEM_FAILURE);
                }

                cmn_err(CE_NOTE,
                    "!%s: failed to probe default MII PHY at %d",
                    dp->name, dp->mii_phy_addr);
        }

        /* Try all possible address */
        for (phy = dp->gc.gc_mii_addr_min; phy < 32; phy++) {
                dp->mii_phy_addr = phy;
                status = gem_mii_read(dp, MII_STATUS);

                if (status != 0xffff && status != 0) {
                        gem_mii_write(dp, MII_CONTROL, 0);
                        goto PHY_found;
                }
        }

        for (phy = dp->gc.gc_mii_addr_min; phy < 32; phy++) {
                dp->mii_phy_addr = phy;
                gem_mii_write(dp, MII_CONTROL, 0);
                status = gem_mii_read(dp, MII_STATUS);

                if (status != 0xffff && status != 0) {
                        goto PHY_found;
                }
        }

        cmn_err(CE_NOTE, "!%s: no MII PHY found", dp->name);
        dp->mii_phy_addr = -1;

        return (GEM_FAILURE);

PHY_found:
        dp->mii_status = status;
        dp->mii_phy_id  = (gem_mii_read(dp, MII_PHYIDH) << 16) |
            gem_mii_read(dp, MII_PHYIDL);

        if (dp->mii_phy_addr < 0) {
                cmn_err(CE_CONT, "!%s: using internal/non-MII PHY(0x%08x)",
                    dp->name, dp->mii_phy_id);
        } else {
                cmn_err(CE_CONT, "!%s: MII PHY (0x%08x) found at %d",
                    dp->name, dp->mii_phy_id, dp->mii_phy_addr);
        }

        cmn_err(CE_CONT, "!%s: PHY control:%b, status:%b, advert:%b, lpar:%b",
            dp->name,
            gem_mii_read(dp, MII_CONTROL), MII_CONTROL_BITS,
            status, MII_STATUS_BITS,
            gem_mii_read(dp, MII_AN_ADVERT), MII_ABILITY_BITS,
            gem_mii_read(dp, MII_AN_LPABLE), MII_ABILITY_BITS);

        dp->mii_xstatus = 0;
        if (status & MII_STATUS_XSTATUS) {
                dp->mii_xstatus = gem_mii_read(dp, MII_XSTATUS);

                cmn_err(CE_CONT, "!%s: xstatus:%b",
                    dp->name, dp->mii_xstatus, MII_XSTATUS_BITS);
        }

        /* check if the phy can advertize pause abilities */
        adv_org = gem_mii_read(dp, MII_AN_ADVERT);

        gem_mii_write(dp, MII_AN_ADVERT,
            MII_ABILITY_PAUSE | MII_ABILITY_ASMPAUSE);

        adv = gem_mii_read(dp, MII_AN_ADVERT);

        if ((adv & MII_ABILITY_PAUSE) == 0) {
                dp->gc.gc_flow_control &= ~1;
        }

        if ((adv & MII_ABILITY_ASMPAUSE) == 0) {
                dp->gc.gc_flow_control &= ~2;
        }

        gem_mii_write(dp, MII_AN_ADVERT, adv_org);

        return (GEM_SUCCESS);
}

static void
gem_mii_start(struct gem_dev *dp)
{
        DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        /* make a first call of check link */
        dp->mii_state = MII_STATE_UNKNOWN;
        dp->mii_last_check = ddi_get_lbolt();
        dp->linkup_delay = dp->gc.gc_mii_linkdown_timeout;
        (void) gem_mii_link_watcher(dp);
}

static void
gem_mii_stop(struct gem_dev *dp)
{
        DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        /* Ensure timer routine stopped */
        mutex_enter(&dp->intrlock);
        if (dp->link_watcher_id) {
                while (untimeout(dp->link_watcher_id) == -1)
                        ;
                dp->link_watcher_id = 0;
        }
        mutex_exit(&dp->intrlock);
}

boolean_t
gem_get_mac_addr_conf(struct gem_dev *dp)
{
        char            propname[32];
        char            *valstr;
        uint8_t         mac[ETHERADDRL];
        char            *cp;
        int             c;
        int             i;
        int             j;
        uint8_t         v;
        uint8_t         d;
        uint8_t         ored;

        DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__));
        /*
         * Get ethernet address from .conf file
         */
        (void) sprintf(propname, "mac-addr");
        if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, dp->dip,
            DDI_PROP_DONTPASS, propname, &valstr)) !=
            DDI_PROP_SUCCESS) {
                return (B_FALSE);
        }

        if (strlen(valstr) != ETHERADDRL*3-1) {
                goto syntax_err;
        }

        cp = valstr;
        j  = 0;
        ored = 0;
        for (;;) {
                v = 0;
                for (i = 0; i < 2; i++) {
                        c = *cp++;

                        if (c >= 'a' && c <= 'f') {
                                d = c - 'a' + 10;
                        } else if (c >= 'A' && c <= 'F') {
                                d = c - 'A' + 10;
                        } else if (c >= '0' && c <= '9') {
                                d = c - '0';
                        } else {
                                goto syntax_err;
                        }
                        v = (v << 4) | d;
                }

                mac[j++] = v;
                ored |= v;
                if (j == ETHERADDRL) {
                        /* done */
                        break;
                }

                c = *cp++;
                if (c != ':') {
                        goto syntax_err;
                }
        }

        if (ored == 0) {
                goto err;
        }
        for (i = 0; i < ETHERADDRL; i++) {
                dp->dev_addr.ether_addr_octet[i] = mac[i];
        }
        ddi_prop_free(valstr);
        return (B_TRUE);

syntax_err:
        cmn_err(CE_CONT,
            "!%s: read mac addr: trying .conf: syntax err %s",
            dp->name, valstr);
err:
        ddi_prop_free(valstr);

        return (B_FALSE);
}


/* ============================================================== */
/*
 * internal start/stop interface
 */
/* ============================================================== */
static int
gem_mac_set_rx_filter(struct gem_dev *dp)
{
        return ((*dp->gc.gc_set_rx_filter)(dp));
}

/*
 * gem_mac_init: cold start
 */
static int
gem_mac_init(struct gem_dev *dp)
{
        DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        if (dp->mac_suspended) {
                return (GEM_FAILURE);
        }

        dp->mac_active = B_FALSE;

        gem_init_rx_ring(dp);
        gem_init_tx_ring(dp);

        /* reset transmitter state */
        dp->tx_blocked = (clock_t)0;
        dp->tx_busy = 0;
        dp->tx_reclaim_busy = 0;
        dp->tx_max_packets = dp->gc.gc_tx_buf_limit;

        if ((*dp->gc.gc_init_chip)(dp) != GEM_SUCCESS) {
                return (GEM_FAILURE);
        }

        gem_prepare_rx_buf(dp);

        return (GEM_SUCCESS);
}
/*
 * gem_mac_start: warm start
 */
static int
gem_mac_start(struct gem_dev *dp)
{
        DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        ASSERT(mutex_owned(&dp->intrlock));
        ASSERT(dp->nic_state == NIC_STATE_ONLINE);
        ASSERT(dp->mii_state ==  MII_STATE_LINKUP);

        /* enable tx and rx */
        mutex_enter(&dp->xmitlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->xmitlock);
                return (GEM_FAILURE);
        }
        dp->mac_active = B_TRUE;
        mutex_exit(&dp->xmitlock);

        /* setup rx buffers */
        (*dp->gc.gc_rx_start)(dp,
            SLOT(dp->rx_active_head, dp->gc.gc_rx_ring_size),
            dp->rx_active_tail - dp->rx_active_head);

        if ((*dp->gc.gc_start_chip)(dp) != GEM_SUCCESS) {
                cmn_err(CE_WARN, "%s: %s: start_chip: failed",
                    dp->name, __func__);
                return (GEM_FAILURE);
        }

        mutex_enter(&dp->xmitlock);

        /* load untranmitted packets to the nic */
        ASSERT(dp->tx_softq_tail - dp->tx_softq_head >= 0);
        if (dp->tx_softq_tail - dp->tx_softq_head > 0) {
                gem_tx_load_descs_oo(dp,
                    dp->tx_softq_head, dp->tx_softq_tail,
                    GEM_TXFLAG_HEAD);
                /* issue preloaded tx buffers */
                gem_tx_start_unit(dp);
        }

        mutex_exit(&dp->xmitlock);

        return (GEM_SUCCESS);
}

static int
gem_mac_stop(struct gem_dev *dp, uint_t flags)
{
        int             i;
        int             wait_time; /* in uS */
#ifdef GEM_DEBUG_LEVEL
        clock_t         now;
#endif
        int             ret = GEM_SUCCESS;

        DPRINTF(1, (CE_CONT, "!%s: %s: called, rx_buf_free:%d",
            dp->name, __func__, dp->rx_buf_freecnt));

        ASSERT(mutex_owned(&dp->intrlock));
        ASSERT(!mutex_owned(&dp->xmitlock));

        /*
         * Block transmits
         */
        mutex_enter(&dp->xmitlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->xmitlock);
                return (GEM_SUCCESS);
        }
        dp->mac_active = B_FALSE;

        while (dp->tx_busy > 0) {
                cv_wait(&dp->tx_drain_cv, &dp->xmitlock);
        }
        mutex_exit(&dp->xmitlock);

        if ((flags & GEM_RESTART_NOWAIT) == 0) {
                /*
                 * Wait for all tx buffers sent.
                 */
                wait_time =
                    2 * (8 * MAXPKTBUF(dp) / gem_speed_value[dp->speed]) *
                    (dp->tx_active_tail - dp->tx_active_head);

                DPRINTF(0, (CE_CONT, "%s: %s: max drain time: %d uS",
                    dp->name, __func__, wait_time));
                i = 0;
#ifdef GEM_DEBUG_LEVEL
                now = ddi_get_lbolt();
#endif
                while (dp->tx_active_tail != dp->tx_active_head) {
                        if (i > wait_time) {
                                /* timeout */
                                cmn_err(CE_NOTE, "%s: %s timeout: tx drain",
                                    dp->name, __func__);
                                break;
                        }
                        (void) gem_reclaim_txbuf(dp);
                        drv_usecwait(100);
                        i += 100;
                }
                DPRINTF(0, (CE_NOTE,
                    "!%s: %s: the nic have drained in %d uS, real %d mS",
                    dp->name, __func__, i,
                    10*((int)(ddi_get_lbolt() - now))));
        }

        /*
         * Now we can stop the nic safely.
         */
        if ((*dp->gc.gc_stop_chip)(dp) != GEM_SUCCESS) {
                cmn_err(CE_NOTE, "%s: %s: resetting the chip to stop it",
                    dp->name, __func__);
                if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) {
                        cmn_err(CE_WARN, "%s: %s: failed to reset chip",
                            dp->name, __func__);
                }
        }

        /*
         * Clear all rx buffers
         */
        if (flags & GEM_RESTART_KEEP_BUF) {
                (void) gem_receive(dp);
        }
        gem_clean_rx_buf(dp);

        /*
         * Update final statistics
         */
        (*dp->gc.gc_get_stats)(dp);

        /*
         * Clear all pended tx packets
         */
        ASSERT(dp->tx_active_tail == dp->tx_softq_head);
        ASSERT(dp->tx_softq_tail == dp->tx_free_head);
        if (flags & GEM_RESTART_KEEP_BUF) {
                /* restore active tx buffers */
                dp->tx_active_tail = dp->tx_active_head;
                dp->tx_softq_head  = dp->tx_active_head;
        } else {
                gem_clean_tx_buf(dp);
        }

        return (ret);
}

static int
gem_add_multicast(struct gem_dev *dp, const uint8_t *ep)
{
        int             cnt;
        int             err;

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

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return (GEM_FAILURE);
        }

        if (dp->mc_count_req++ < GEM_MAXMC) {
                /* append the new address at the end of the mclist */
                cnt = dp->mc_count;
                bcopy(ep, dp->mc_list[cnt].addr.ether_addr_octet,
                    ETHERADDRL);
                if (dp->gc.gc_multicast_hash) {
                        dp->mc_list[cnt].hash =
                            (*dp->gc.gc_multicast_hash)(dp, (uint8_t *)ep);
                }
                dp->mc_count = cnt + 1;
        }

        if (dp->mc_count_req != dp->mc_count) {
                /* multicast address list overflow */
                dp->rxmode |= RXMODE_MULTI_OVF;
        } else {
                dp->rxmode &= ~RXMODE_MULTI_OVF;
        }

        /* tell new multicast list to the hardware */
        err = gem_mac_set_rx_filter(dp);

        mutex_exit(&dp->intrlock);

        return (err);
}

static int
gem_remove_multicast(struct gem_dev *dp, const uint8_t *ep)
{
        size_t          len;
        int             i;
        int             cnt;
        int             err;

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

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return (GEM_FAILURE);
        }

        dp->mc_count_req--;
        cnt = dp->mc_count;
        for (i = 0; i < cnt; i++) {
                if (bcmp(ep, &dp->mc_list[i].addr, ETHERADDRL)) {
                        continue;
                }
                /* shrink the mclist by copying forward */
                len = (cnt - (i + 1)) * sizeof (*dp->mc_list);
                if (len > 0) {
                        bcopy(&dp->mc_list[i+1], &dp->mc_list[i], len);
                }
                dp->mc_count--;
                break;
        }

        if (dp->mc_count_req != dp->mc_count) {
                /* multicast address list overflow */
                dp->rxmode |= RXMODE_MULTI_OVF;
        } else {
                dp->rxmode &= ~RXMODE_MULTI_OVF;
        }
        /* In gem v2, don't hold xmitlock on calling set_rx_filter */
        err = gem_mac_set_rx_filter(dp);

        mutex_exit(&dp->intrlock);

        return (err);
}

/* ============================================================== */
/*
 * ND interface
 */
/* ============================================================== */
enum {
        PARAM_AUTONEG_CAP,
        PARAM_PAUSE_CAP,
        PARAM_ASYM_PAUSE_CAP,
        PARAM_1000FDX_CAP,
        PARAM_1000HDX_CAP,
        PARAM_100T4_CAP,
        PARAM_100FDX_CAP,
        PARAM_100HDX_CAP,
        PARAM_10FDX_CAP,
        PARAM_10HDX_CAP,

        PARAM_ADV_AUTONEG_CAP,
        PARAM_ADV_PAUSE_CAP,
        PARAM_ADV_ASYM_PAUSE_CAP,
        PARAM_ADV_1000FDX_CAP,
        PARAM_ADV_1000HDX_CAP,
        PARAM_ADV_100T4_CAP,
        PARAM_ADV_100FDX_CAP,
        PARAM_ADV_100HDX_CAP,
        PARAM_ADV_10FDX_CAP,
        PARAM_ADV_10HDX_CAP,

        PARAM_LP_AUTONEG_CAP,
        PARAM_LP_PAUSE_CAP,
        PARAM_LP_ASYM_PAUSE_CAP,
        PARAM_LP_1000FDX_CAP,
        PARAM_LP_1000HDX_CAP,
        PARAM_LP_100T4_CAP,
        PARAM_LP_100FDX_CAP,
        PARAM_LP_100HDX_CAP,
        PARAM_LP_10FDX_CAP,
        PARAM_LP_10HDX_CAP,

        PARAM_LINK_STATUS,
        PARAM_LINK_SPEED,
        PARAM_LINK_DUPLEX,

        PARAM_LINK_AUTONEG,
        PARAM_LINK_RX_PAUSE,
        PARAM_LINK_TX_PAUSE,

        PARAM_LOOP_MODE,
        PARAM_MSI_CNT,

#ifdef DEBUG_RESUME
        PARAM_RESUME_TEST,
#endif
        PARAM_COUNT
};

enum ioc_reply {
        IOC_INVAL = -1,                         /* bad, NAK with EINVAL */
        IOC_DONE,                               /* OK, reply sent       */
        IOC_ACK,                                /* OK, just send ACK    */
        IOC_REPLY,                              /* OK, just send reply  */
        IOC_RESTART_ACK,                        /* OK, restart & ACK    */
        IOC_RESTART_REPLY                       /* OK, restart & reply  */
};

struct gem_nd_arg {
        struct gem_dev  *dp;
        int             item;
};

static int
gem_param_get(queue_t *q, mblk_t *mp, caddr_t arg, cred_t *credp)
{
        struct gem_dev  *dp = ((struct gem_nd_arg *)(void *)arg)->dp;
        int             item = ((struct gem_nd_arg *)(void *)arg)->item;
        long            val;

        DPRINTF(0, (CE_CONT, "!%s: %s: called, item:%d",
            dp->name, __func__, item));

        switch (item) {
        case PARAM_AUTONEG_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG);
                DPRINTF(0, (CE_CONT, "autoneg_cap:%d", val));
                break;

        case PARAM_PAUSE_CAP:
                val = BOOLEAN(dp->gc.gc_flow_control & 1);
                break;

        case PARAM_ASYM_PAUSE_CAP:
                val = BOOLEAN(dp->gc.gc_flow_control & 2);
                break;

        case PARAM_1000FDX_CAP:
                val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD);
                break;

        case PARAM_1000HDX_CAP:
                val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX);
                break;

        case PARAM_100T4_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4);
                break;

        case PARAM_100FDX_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD);
                break;

        case PARAM_100HDX_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX);
                break;

        case PARAM_10FDX_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD);
                break;

        case PARAM_10HDX_CAP:
                val = BOOLEAN(dp->mii_status & MII_STATUS_10);
                break;

        case PARAM_ADV_AUTONEG_CAP:
                val = dp->anadv_autoneg;
                break;

        case PARAM_ADV_PAUSE_CAP:
                val = BOOLEAN(dp->anadv_flow_control & 1);
                break;

        case PARAM_ADV_ASYM_PAUSE_CAP:
                val = BOOLEAN(dp->anadv_flow_control & 2);
                break;

        case PARAM_ADV_1000FDX_CAP:
                val = dp->anadv_1000fdx;
                break;

        case PARAM_ADV_1000HDX_CAP:
                val = dp->anadv_1000hdx;
                break;

        case PARAM_ADV_100T4_CAP:
                val = dp->anadv_100t4;
                break;

        case PARAM_ADV_100FDX_CAP:
                val = dp->anadv_100fdx;
                break;

        case PARAM_ADV_100HDX_CAP:
                val = dp->anadv_100hdx;
                break;

        case PARAM_ADV_10FDX_CAP:
                val = dp->anadv_10fdx;
                break;

        case PARAM_ADV_10HDX_CAP:
                val = dp->anadv_10hdx;
                break;

        case PARAM_LP_AUTONEG_CAP:
                val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN);
                break;

        case PARAM_LP_PAUSE_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE);
                break;

        case PARAM_LP_ASYM_PAUSE_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASMPAUSE);
                break;

        case PARAM_LP_1000FDX_CAP:
                val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL);
                break;

        case PARAM_LP_1000HDX_CAP:
                val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF);
                break;

        case PARAM_LP_100T4_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4);
                break;

        case PARAM_LP_100FDX_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD);
                break;

        case PARAM_LP_100HDX_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX);
                break;

        case PARAM_LP_10FDX_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD);
                break;

        case PARAM_LP_10HDX_CAP:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T);
                break;

        case PARAM_LINK_STATUS:
                val = (dp->mii_state == MII_STATE_LINKUP);
                break;

        case PARAM_LINK_SPEED:
                val = gem_speed_value[dp->speed];
                break;

        case PARAM_LINK_DUPLEX:
                val = 0;
                if (dp->mii_state == MII_STATE_LINKUP) {
                        val = dp->full_duplex ? 2 : 1;
                }
                break;

        case PARAM_LINK_AUTONEG:
                val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN);
                break;

        case PARAM_LINK_RX_PAUSE:
                val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) ||
                    (dp->flow_control == FLOW_CONTROL_RX_PAUSE);
                break;

        case PARAM_LINK_TX_PAUSE:
                val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) ||
                    (dp->flow_control == FLOW_CONTROL_TX_PAUSE);
                break;

#ifdef DEBUG_RESUME
        case PARAM_RESUME_TEST:
                val = 0;
                break;
#endif
        default:
                cmn_err(CE_WARN, "%s: unimplemented ndd control (%d)",
                    dp->name, item);
                break;
        }

        (void) mi_mpprintf(mp, "%ld", val);

        return (0);
}

static int
gem_param_set(queue_t *q, mblk_t *mp, char *value, caddr_t arg, cred_t *credp)
{
        struct gem_dev  *dp = ((struct gem_nd_arg *)(void *)arg)->dp;
        int             item = ((struct gem_nd_arg *)(void *)arg)->item;
        long            val;
        char            *end;

        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));
        if (ddi_strtol(value, &end, 10, &val)) {
                return (EINVAL);
        }
        if (end == value) {
                return (EINVAL);
        }

        switch (item) {
        case PARAM_ADV_AUTONEG_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_CANAUTONEG) == 0) {
                        goto err;
                }
                dp->anadv_autoneg = (int)val;
                break;

        case PARAM_ADV_PAUSE_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val) {
                        dp->anadv_flow_control |= 1;
                } else {
                        dp->anadv_flow_control &= ~1;
                }
                break;

        case PARAM_ADV_ASYM_PAUSE_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val) {
                        dp->anadv_flow_control |= 2;
                } else {
                        dp->anadv_flow_control &= ~2;
                }
                break;

        case PARAM_ADV_1000FDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_xstatus &
                    (MII_XSTATUS_1000BASET_FD |
                    MII_XSTATUS_1000BASEX_FD)) == 0) {
                        goto err;
                }
                dp->anadv_1000fdx = (int)val;
                break;

        case PARAM_ADV_1000HDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_xstatus &
                    (MII_XSTATUS_1000BASET | MII_XSTATUS_1000BASEX)) == 0) {
                        goto err;
                }
                dp->anadv_1000hdx = (int)val;
                break;

        case PARAM_ADV_100T4_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_100_BASE_T4) == 0) {
                        goto err;
                }
                dp->anadv_100t4 = (int)val;
                break;

        case PARAM_ADV_100FDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_100_BASEX_FD) == 0) {
                        goto err;
                }
                dp->anadv_100fdx = (int)val;
                break;

        case PARAM_ADV_100HDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_100_BASEX) == 0) {
                        goto err;
                }
                dp->anadv_100hdx = (int)val;
                break;

        case PARAM_ADV_10FDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_10_FD) == 0) {
                        goto err;
                }
                dp->anadv_10fdx = (int)val;
                break;

        case PARAM_ADV_10HDX_CAP:
                if (val != 0 && val != 1) {
                        goto err;
                }
                if (val && (dp->mii_status & MII_STATUS_10) == 0) {
                        goto err;
                }
                dp->anadv_10hdx = (int)val;
                break;
        }

        /* sync with PHY */
        gem_choose_forcedmode(dp);

        dp->mii_state = MII_STATE_UNKNOWN;
        if (dp->gc.gc_mii_hw_link_detection && dp->link_watcher_id == 0) {
                /* XXX - Can we ignore the return code ? */
                (void) gem_mii_link_check(dp);
        }

        return (0);
err:
        return (EINVAL);
}

static void
gem_nd_load(struct gem_dev *dp, char *name, ndgetf_t gf, ndsetf_t sf, int item)
{
        struct gem_nd_arg       *arg;

        ASSERT(item >= 0);
        ASSERT(item < PARAM_COUNT);

        arg = &((struct gem_nd_arg *)(void *)dp->nd_arg_p)[item];
        arg->dp = dp;
        arg->item = item;

        DPRINTF(2, (CE_CONT, "!%s: %s: name:%s, item:%d",
            dp->name, __func__, name, item));
        (void) nd_load(&dp->nd_data_p, name, gf, sf, (caddr_t)arg);
}

static void
gem_nd_setup(struct gem_dev *dp)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called, mii_status:0x%b",
            dp->name, __func__, dp->mii_status, MII_STATUS_BITS));

        ASSERT(dp->nd_arg_p == NULL);

        dp->nd_arg_p =
            kmem_zalloc(sizeof (struct gem_nd_arg) * PARAM_COUNT, KM_SLEEP);

#define SETFUNC(x)      ((x) ? gem_param_set : NULL)

        gem_nd_load(dp, "autoneg_cap",
            gem_param_get, NULL, PARAM_AUTONEG_CAP);
        gem_nd_load(dp, "pause_cap",
            gem_param_get, NULL, PARAM_PAUSE_CAP);
        gem_nd_load(dp, "asym_pause_cap",
            gem_param_get, NULL, PARAM_ASYM_PAUSE_CAP);
        gem_nd_load(dp, "1000fdx_cap",
            gem_param_get, NULL, PARAM_1000FDX_CAP);
        gem_nd_load(dp, "1000hdx_cap",
            gem_param_get, NULL, PARAM_1000HDX_CAP);
        gem_nd_load(dp, "100T4_cap",
            gem_param_get, NULL, PARAM_100T4_CAP);
        gem_nd_load(dp, "100fdx_cap",
            gem_param_get, NULL, PARAM_100FDX_CAP);
        gem_nd_load(dp, "100hdx_cap",
            gem_param_get, NULL, PARAM_100HDX_CAP);
        gem_nd_load(dp, "10fdx_cap",
            gem_param_get, NULL, PARAM_10FDX_CAP);
        gem_nd_load(dp, "10hdx_cap",
            gem_param_get, NULL, PARAM_10HDX_CAP);

        /* Our advertised capabilities */
        gem_nd_load(dp, "adv_autoneg_cap", gem_param_get,
            SETFUNC(dp->mii_status & MII_STATUS_CANAUTONEG),
            PARAM_ADV_AUTONEG_CAP);
        gem_nd_load(dp, "adv_pause_cap", gem_param_get,
            SETFUNC(dp->gc.gc_flow_control & 1),
            PARAM_ADV_PAUSE_CAP);
        gem_nd_load(dp, "adv_asym_pause_cap", gem_param_get,
            SETFUNC(dp->gc.gc_flow_control & 2),
            PARAM_ADV_ASYM_PAUSE_CAP);
        gem_nd_load(dp, "adv_1000fdx_cap", gem_param_get,
            SETFUNC(dp->mii_xstatus &
            (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD)),
            PARAM_ADV_1000FDX_CAP);
        gem_nd_load(dp, "adv_1000hdx_cap", gem_param_get,
            SETFUNC(dp->mii_xstatus &
            (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET)),
            PARAM_ADV_1000HDX_CAP);
        gem_nd_load(dp, "adv_100T4_cap", gem_param_get,
            SETFUNC((dp->mii_status & MII_STATUS_100_BASE_T4) &&
            !dp->mii_advert_ro),
            PARAM_ADV_100T4_CAP);
        gem_nd_load(dp, "adv_100fdx_cap", gem_param_get,
            SETFUNC((dp->mii_status & MII_STATUS_100_BASEX_FD) &&
            !dp->mii_advert_ro),
            PARAM_ADV_100FDX_CAP);
        gem_nd_load(dp, "adv_100hdx_cap", gem_param_get,
            SETFUNC((dp->mii_status & MII_STATUS_100_BASEX) &&
            !dp->mii_advert_ro),
            PARAM_ADV_100HDX_CAP);
        gem_nd_load(dp, "adv_10fdx_cap", gem_param_get,
            SETFUNC((dp->mii_status & MII_STATUS_10_FD) &&
            !dp->mii_advert_ro),
            PARAM_ADV_10FDX_CAP);
        gem_nd_load(dp, "adv_10hdx_cap", gem_param_get,
            SETFUNC((dp->mii_status & MII_STATUS_10) &&
            !dp->mii_advert_ro),
            PARAM_ADV_10HDX_CAP);

        /* Partner's advertised capabilities */
        gem_nd_load(dp, "lp_autoneg_cap",
            gem_param_get, NULL, PARAM_LP_AUTONEG_CAP);
        gem_nd_load(dp, "lp_pause_cap",
            gem_param_get, NULL, PARAM_LP_PAUSE_CAP);
        gem_nd_load(dp, "lp_asym_pause_cap",
            gem_param_get, NULL, PARAM_LP_ASYM_PAUSE_CAP);
        gem_nd_load(dp, "lp_1000fdx_cap",
            gem_param_get, NULL, PARAM_LP_1000FDX_CAP);
        gem_nd_load(dp, "lp_1000hdx_cap",
            gem_param_get, NULL, PARAM_LP_1000HDX_CAP);
        gem_nd_load(dp, "lp_100T4_cap",
            gem_param_get, NULL, PARAM_LP_100T4_CAP);
        gem_nd_load(dp, "lp_100fdx_cap",
            gem_param_get, NULL, PARAM_LP_100FDX_CAP);
        gem_nd_load(dp, "lp_100hdx_cap",
            gem_param_get, NULL, PARAM_LP_100HDX_CAP);
        gem_nd_load(dp, "lp_10fdx_cap",
            gem_param_get, NULL, PARAM_LP_10FDX_CAP);
        gem_nd_load(dp, "lp_10hdx_cap",
            gem_param_get, NULL, PARAM_LP_10HDX_CAP);

        /* Current operating modes */
        gem_nd_load(dp, "link_status",
            gem_param_get, NULL, PARAM_LINK_STATUS);
        gem_nd_load(dp, "link_speed",
            gem_param_get, NULL, PARAM_LINK_SPEED);
        gem_nd_load(dp, "link_duplex",
            gem_param_get, NULL, PARAM_LINK_DUPLEX);
        gem_nd_load(dp, "link_autoneg",
            gem_param_get, NULL, PARAM_LINK_AUTONEG);
        gem_nd_load(dp, "link_rx_pause",
            gem_param_get, NULL, PARAM_LINK_RX_PAUSE);
        gem_nd_load(dp, "link_tx_pause",
            gem_param_get, NULL, PARAM_LINK_TX_PAUSE);
#ifdef DEBUG_RESUME
        gem_nd_load(dp, "resume_test",
            gem_param_get, NULL, PARAM_RESUME_TEST);
#endif
#undef  SETFUNC
}

static
enum ioc_reply
gem_nd_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp, struct iocblk *iocp)
{
        boolean_t       ok;

        ASSERT(mutex_owned(&dp->intrlock));

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

        switch (iocp->ioc_cmd) {
        case ND_GET:
                ok = nd_getset(wq, dp->nd_data_p, mp);
                DPRINTF(0, (CE_CONT,
                    "%s: get %s", dp->name, ok ? "OK" : "FAIL"));
                return (ok ? IOC_REPLY : IOC_INVAL);

        case ND_SET:
                ok = nd_getset(wq, dp->nd_data_p, mp);

                DPRINTF(0, (CE_CONT, "%s: set %s err %d",
                    dp->name, ok ? "OK" : "FAIL", iocp->ioc_error));

                if (!ok) {
                        return (IOC_INVAL);
                }

                if (iocp->ioc_error) {
                        return (IOC_REPLY);
                }

                return (IOC_RESTART_REPLY);
        }

        cmn_err(CE_WARN, "%s: invalid cmd 0x%x", dp->name, iocp->ioc_cmd);

        return (IOC_INVAL);
}

static void
gem_nd_cleanup(struct gem_dev *dp)
{
        ASSERT(dp->nd_data_p != NULL);
        ASSERT(dp->nd_arg_p != NULL);

        nd_free(&dp->nd_data_p);

        kmem_free(dp->nd_arg_p, sizeof (struct gem_nd_arg) * PARAM_COUNT);
        dp->nd_arg_p = NULL;
}

static void
gem_mac_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp)
{
        struct iocblk   *iocp;
        enum ioc_reply  status;
        int             cmd;

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

        /*
         * Validate the command before bothering with the mutex ...
         */
        iocp = (void *)mp->b_rptr;
        iocp->ioc_error = 0;
        cmd = iocp->ioc_cmd;

        DPRINTF(0, (CE_CONT, "%s: %s cmd:0x%x", dp->name, __func__, cmd));

        mutex_enter(&dp->intrlock);
        mutex_enter(&dp->xmitlock);

        switch (cmd) {
        default:
                _NOTE(NOTREACHED)
                status = IOC_INVAL;
                break;

        case ND_GET:
        case ND_SET:
                status = gem_nd_ioctl(dp, wq, mp, iocp);
                break;
        }

        mutex_exit(&dp->xmitlock);
        mutex_exit(&dp->intrlock);

#ifdef DEBUG_RESUME
        if (cmd == ND_GET)  {
                gem_suspend(dp->dip);
                gem_resume(dp->dip);
        }
#endif
        /*
         * Finally, decide how to reply
         */
        switch (status) {
        default:
        case IOC_INVAL:
                /*
                 * Error, reply with a NAK and EINVAL or the specified error
                 */
                miocnak(wq, mp, 0, iocp->ioc_error == 0 ?
                    EINVAL : iocp->ioc_error);
                break;

        case IOC_DONE:
                /*
                 * OK, reply already sent
                 */
                break;

        case IOC_RESTART_ACK:
        case IOC_ACK:
                /*
                 * OK, reply with an ACK
                 */
                miocack(wq, mp, 0, 0);
                break;

        case IOC_RESTART_REPLY:
        case IOC_REPLY:
                /*
                 * OK, send prepared reply as ACK or NAK
                 */
                mp->b_datap->db_type =
                    iocp->ioc_error == 0 ? M_IOCACK : M_IOCNAK;
                qreply(wq, mp);
                break;
        }
}

#ifndef SYS_MAC_H
#define XCVR_UNDEFINED  0
#define XCVR_NONE       1
#define XCVR_10         2
#define XCVR_100T4      3
#define XCVR_100X       4
#define XCVR_100T2      5
#define XCVR_1000X      6
#define XCVR_1000T      7
#endif
static int
gem_mac_xcvr_inuse(struct gem_dev *dp)
{
        int     val = XCVR_UNDEFINED;

        if ((dp->mii_status & MII_STATUS_XSTATUS) == 0) {
                if (dp->mii_status & MII_STATUS_100_BASE_T4) {
                        val = XCVR_100T4;
                } else if (dp->mii_status &
                    (MII_STATUS_100_BASEX_FD |
                    MII_STATUS_100_BASEX)) {
                        val = XCVR_100X;
                } else if (dp->mii_status &
                    (MII_STATUS_100_BASE_T2_FD |
                    MII_STATUS_100_BASE_T2)) {
                        val = XCVR_100T2;
                } else if (dp->mii_status &
                    (MII_STATUS_10_FD | MII_STATUS_10)) {
                        val = XCVR_10;
                }
        } else if (dp->mii_xstatus &
            (MII_XSTATUS_1000BASET_FD | MII_XSTATUS_1000BASET)) {
                val = XCVR_1000T;
        } else if (dp->mii_xstatus &
            (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASEX)) {
                val = XCVR_1000X;
        }

        return (val);
}

/* ============================================================== */
/*
 * GLDv3 interface
 */
/* ============================================================== */
static int              gem_m_getstat(void *, uint_t, uint64_t *);
static int              gem_m_start(void *);
static void             gem_m_stop(void *);
static int              gem_m_setpromisc(void *, boolean_t);
static int              gem_m_multicst(void *, boolean_t, const uint8_t *);
static int              gem_m_unicst(void *, const uint8_t *);
static mblk_t           *gem_m_tx(void *, mblk_t *);
static void             gem_m_ioctl(void *, queue_t *, mblk_t *);
static boolean_t        gem_m_getcapab(void *, mac_capab_t, void *);

#define GEM_M_CALLBACK_FLAGS    (MC_IOCTL | MC_GETCAPAB)

static mac_callbacks_t gem_m_callbacks = {
        GEM_M_CALLBACK_FLAGS,
        gem_m_getstat,
        gem_m_start,
        gem_m_stop,
        gem_m_setpromisc,
        gem_m_multicst,
        gem_m_unicst,
        gem_m_tx,
        NULL,
        gem_m_ioctl,
        gem_m_getcapab,
};

static int
gem_m_start(void *arg)
{
        int             err = 0;
        struct gem_dev *dp = arg;

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

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                err = EIO;
                goto x;
        }
        if (gem_mac_init(dp) != GEM_SUCCESS) {
                err = EIO;
                goto x;
        }
        dp->nic_state = NIC_STATE_INITIALIZED;

        /* reset rx filter state */
        dp->mc_count = 0;
        dp->mc_count_req = 0;

        /* setup media mode if the link have been up */
        if (dp->mii_state == MII_STATE_LINKUP) {
                (dp->gc.gc_set_media)(dp);
        }

        /* setup initial rx filter */
        bcopy(dp->dev_addr.ether_addr_octet,
            dp->cur_addr.ether_addr_octet, ETHERADDRL);
        dp->rxmode |= RXMODE_ENABLE;

        if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) {
                err = EIO;
                goto x;
        }

        dp->nic_state = NIC_STATE_ONLINE;
        if (dp->mii_state == MII_STATE_LINKUP) {
                if (gem_mac_start(dp) != GEM_SUCCESS) {
                        err = EIO;
                        goto x;
                }
        }

        dp->timeout_id = timeout((void (*)(void *))gem_tx_timeout,
            (void *)dp, dp->gc.gc_tx_timeout_interval);
        mutex_exit(&dp->intrlock);

        return (0);
x:
        dp->nic_state = NIC_STATE_STOPPED;
        mutex_exit(&dp->intrlock);
        return (err);
}

static void
gem_m_stop(void *arg)
{
        struct gem_dev  *dp = arg;

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

        /* stop rx */
        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return;
        }
        dp->rxmode &= ~RXMODE_ENABLE;
        (void) gem_mac_set_rx_filter(dp);
        mutex_exit(&dp->intrlock);

        /* stop tx timeout watcher */
        if (dp->timeout_id) {
                while (untimeout(dp->timeout_id) == -1)
                        ;
                dp->timeout_id = 0;
        }

        /* make the nic state inactive */
        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return;
        }
        dp->nic_state = NIC_STATE_STOPPED;

        /* we need deassert mac_active due to block interrupt handler */
        mutex_enter(&dp->xmitlock);
        dp->mac_active = B_FALSE;
        mutex_exit(&dp->xmitlock);

        /* block interrupts */
        while (dp->intr_busy) {
                cv_wait(&dp->tx_drain_cv, &dp->intrlock);
        }
        (void) gem_mac_stop(dp, 0);
        mutex_exit(&dp->intrlock);
}

static int
gem_m_multicst(void *arg, boolean_t add, const uint8_t *ep)
{
        int             err;
        int             ret;
        struct gem_dev  *dp = arg;

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

        if (add) {
                ret = gem_add_multicast(dp, ep);
        } else {
                ret = gem_remove_multicast(dp, ep);
        }

        err = 0;
        if (ret != GEM_SUCCESS) {
                err = EIO;
        }

        return (err);
}

static int
gem_m_setpromisc(void *arg, boolean_t on)
{
        int             err = 0;        /* no error */
        struct gem_dev  *dp = arg;

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

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return (EIO);
        }
        if (on) {
                dp->rxmode |= RXMODE_PROMISC;
        } else {
                dp->rxmode &= ~RXMODE_PROMISC;
        }

        if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) {
                err = EIO;
        }
        mutex_exit(&dp->intrlock);

        return (err);
}

int
gem_m_getstat(void *arg, uint_t stat, uint64_t *valp)
{
        struct gem_dev          *dp = arg;
        struct gem_stats        *gstp = &dp->stats;
        uint64_t                val = 0;

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

        if (mutex_owned(&dp->intrlock)) {
                if (dp->mac_suspended) {
                        return (EIO);
                }
        } else {
                mutex_enter(&dp->intrlock);
                if (dp->mac_suspended) {
                        mutex_exit(&dp->intrlock);
                        return (EIO);
                }
                mutex_exit(&dp->intrlock);
        }

        if ((*dp->gc.gc_get_stats)(dp) != GEM_SUCCESS) {
                return (EIO);
        }

        switch (stat) {
        case MAC_STAT_IFSPEED:
                val = gem_speed_value[dp->speed] *1000000ull;
                break;

        case MAC_STAT_MULTIRCV:
                val = gstp->rmcast;
                break;

        case MAC_STAT_BRDCSTRCV:
                val = gstp->rbcast;
                break;

        case MAC_STAT_MULTIXMT:
                val = gstp->omcast;
                break;

        case MAC_STAT_BRDCSTXMT:
                val = gstp->obcast;
                break;

        case MAC_STAT_NORCVBUF:
                val = gstp->norcvbuf + gstp->missed;
                break;

        case MAC_STAT_IERRORS:
                val = gstp->errrcv;
                break;

        case MAC_STAT_NOXMTBUF:
                val = gstp->noxmtbuf;
                break;

        case MAC_STAT_OERRORS:
                val = gstp->errxmt;
                break;

        case MAC_STAT_COLLISIONS:
                val = gstp->collisions;
                break;

        case MAC_STAT_RBYTES:
                val = gstp->rbytes;
                break;

        case MAC_STAT_IPACKETS:
                val = gstp->rpackets;
                break;

        case MAC_STAT_OBYTES:
                val = gstp->obytes;
                break;

        case MAC_STAT_OPACKETS:
                val = gstp->opackets;
                break;

        case MAC_STAT_UNDERFLOWS:
                val = gstp->underflow;
                break;

        case MAC_STAT_OVERFLOWS:
                val = gstp->overflow;
                break;

        case ETHER_STAT_ALIGN_ERRORS:
                val = gstp->frame;
                break;

        case ETHER_STAT_FCS_ERRORS:
                val = gstp->crc;
                break;

        case ETHER_STAT_FIRST_COLLISIONS:
                val = gstp->first_coll;
                break;

        case ETHER_STAT_MULTI_COLLISIONS:
                val = gstp->multi_coll;
                break;

        case ETHER_STAT_SQE_ERRORS:
                val = gstp->sqe;
                break;

        case ETHER_STAT_DEFER_XMTS:
                val = gstp->defer;
                break;

        case ETHER_STAT_TX_LATE_COLLISIONS:
                val = gstp->xmtlatecoll;
                break;

        case ETHER_STAT_EX_COLLISIONS:
                val = gstp->excoll;
                break;

        case ETHER_STAT_MACXMT_ERRORS:
                val = gstp->xmit_internal_err;
                break;

        case ETHER_STAT_CARRIER_ERRORS:
                val = gstp->nocarrier;
                break;

        case ETHER_STAT_TOOLONG_ERRORS:
                val = gstp->frame_too_long;
                break;

        case ETHER_STAT_MACRCV_ERRORS:
                val = gstp->rcv_internal_err;
                break;

        case ETHER_STAT_XCVR_ADDR:
                val = dp->mii_phy_addr;
                break;

        case ETHER_STAT_XCVR_ID:
                val = dp->mii_phy_id;
                break;

        case ETHER_STAT_XCVR_INUSE:
                val = gem_mac_xcvr_inuse(dp);
                break;

        case ETHER_STAT_CAP_1000FDX:
                val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD);
                break;

        case ETHER_STAT_CAP_1000HDX:
                val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX);
                break;

        case ETHER_STAT_CAP_100FDX:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD);
                break;

        case ETHER_STAT_CAP_100HDX:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX);
                break;

        case ETHER_STAT_CAP_10FDX:
                val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD);
                break;

        case ETHER_STAT_CAP_10HDX:
                val = BOOLEAN(dp->mii_status & MII_STATUS_10);
                break;

        case ETHER_STAT_CAP_ASMPAUSE:
                val = BOOLEAN(dp->gc.gc_flow_control & 2);
                break;

        case ETHER_STAT_CAP_PAUSE:
                val = BOOLEAN(dp->gc.gc_flow_control & 1);
                break;

        case ETHER_STAT_CAP_AUTONEG:
                val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG);
                break;

        case ETHER_STAT_ADV_CAP_1000FDX:
                val = dp->anadv_1000fdx;
                break;

        case ETHER_STAT_ADV_CAP_1000HDX:
                val = dp->anadv_1000hdx;
                break;

        case ETHER_STAT_ADV_CAP_100FDX:
                val = dp->anadv_100fdx;
                break;

        case ETHER_STAT_ADV_CAP_100HDX:
                val = dp->anadv_100hdx;
                break;

        case ETHER_STAT_ADV_CAP_10FDX:
                val = dp->anadv_10fdx;
                break;

        case ETHER_STAT_ADV_CAP_10HDX:
                val = dp->anadv_10hdx;
                break;

        case ETHER_STAT_ADV_CAP_ASMPAUSE:
                val = BOOLEAN(dp->anadv_flow_control & 2);
                break;

        case ETHER_STAT_ADV_CAP_PAUSE:
                val = BOOLEAN(dp->anadv_flow_control & 1);
                break;

        case ETHER_STAT_ADV_CAP_AUTONEG:
                val = dp->anadv_autoneg;
                break;

        case ETHER_STAT_LP_CAP_1000FDX:
                val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL);
                break;

        case ETHER_STAT_LP_CAP_1000HDX:
                val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF);
                break;

        case ETHER_STAT_LP_CAP_100FDX:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD);
                break;

        case ETHER_STAT_LP_CAP_100HDX:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX);
                break;

        case ETHER_STAT_LP_CAP_10FDX:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD);
                break;

        case ETHER_STAT_LP_CAP_10HDX:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T);
                break;

        case ETHER_STAT_LP_CAP_ASMPAUSE:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASMPAUSE);
                break;

        case ETHER_STAT_LP_CAP_PAUSE:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE);
                break;

        case ETHER_STAT_LP_CAP_AUTONEG:
                val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN);
                break;

        case ETHER_STAT_LINK_ASMPAUSE:
                val = BOOLEAN(dp->flow_control & 2);
                break;

        case ETHER_STAT_LINK_PAUSE:
                val = BOOLEAN(dp->flow_control & 1);
                break;

        case ETHER_STAT_LINK_AUTONEG:
                val = dp->anadv_autoneg &&
                    BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN);
                break;

        case ETHER_STAT_LINK_DUPLEX:
                val = (dp->mii_state == MII_STATE_LINKUP) ?
                    (dp->full_duplex ? 2 : 1) : 0;
                break;

        case ETHER_STAT_TOOSHORT_ERRORS:
                val = gstp->runt;
                break;
        case ETHER_STAT_LP_REMFAULT:
                val = BOOLEAN(dp->mii_lpable & MII_AN_ADVERT_REMFAULT);
                break;

        case ETHER_STAT_JABBER_ERRORS:
                val = gstp->jabber;
                break;

        case ETHER_STAT_CAP_100T4:
                val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4);
                break;

        case ETHER_STAT_ADV_CAP_100T4:
                val = dp->anadv_100t4;
                break;

        case ETHER_STAT_LP_CAP_100T4:
                val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4);
                break;

        default:
#if GEM_DEBUG_LEVEL > 2
                cmn_err(CE_WARN,
                    "%s: unrecognized parameter value = %d",
                    __func__, stat);
#endif
                return (ENOTSUP);
        }

        *valp = val;

        return (0);
}

static int
gem_m_unicst(void *arg, const uint8_t *mac)
{
        int             err = 0;
        struct gem_dev  *dp = arg;

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

        mutex_enter(&dp->intrlock);
        if (dp->mac_suspended) {
                mutex_exit(&dp->intrlock);
                return (EIO);
        }
        bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL);
        dp->rxmode |= RXMODE_ENABLE;

        if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) {
                err = EIO;
        }
        mutex_exit(&dp->intrlock);

        return (err);
}

/*
 * gem_m_tx is used only for sending data packets into ethernet wire.
 */
static mblk_t *
gem_m_tx(void *arg, mblk_t *mp)
{
        uint32_t        flags = 0;
        struct gem_dev  *dp = arg;
        mblk_t          *tp;

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

        ASSERT(dp->nic_state == NIC_STATE_ONLINE);
        if (dp->mii_state != MII_STATE_LINKUP) {
                /* Some nics hate to send packets when the link is down. */
                while (mp) {
                        tp = mp->b_next;
                        mp->b_next = NULL;
                        freemsg(mp);
                        mp = tp;
                }
                return (NULL);
        }

        return (gem_send_common(dp, mp, flags));
}

static void
gem_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called",
            ((struct gem_dev *)arg)->name, __func__));

        gem_mac_ioctl((struct gem_dev *)arg, wq, mp);
}

/* ARGSUSED */
static boolean_t
gem_m_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
        return (B_FALSE);
}

static void
gem_gld3_init(struct gem_dev *dp, mac_register_t *macp)
{
        macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        macp->m_driver = dp;
        macp->m_dip = dp->dip;
        macp->m_src_addr = dp->dev_addr.ether_addr_octet;
        macp->m_callbacks = &gem_m_callbacks;
        macp->m_min_sdu = 0;
        macp->m_max_sdu = dp->mtu;

        if (dp->misc_flag & GEM_VLAN) {
                macp->m_margin = VTAG_SIZE;
        }
}

/* ======================================================================== */
/*
 * attach/detatch support
 */
/* ======================================================================== */
static void
gem_read_conf(struct gem_dev *dp)
{
        int     val;

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

        /*
         * Get media mode infomation from .conf file
         */
        dp->anadv_autoneg = gem_prop_get_int(dp, "adv_autoneg_cap", 1) != 0;
        dp->anadv_1000fdx = gem_prop_get_int(dp, "adv_1000fdx_cap", 1) != 0;
        dp->anadv_1000hdx = gem_prop_get_int(dp, "adv_1000hdx_cap", 1) != 0;
        dp->anadv_100t4   = gem_prop_get_int(dp, "adv_100T4_cap", 1) != 0;
        dp->anadv_100fdx  = gem_prop_get_int(dp, "adv_100fdx_cap", 1) != 0;
        dp->anadv_100hdx  = gem_prop_get_int(dp, "adv_100hdx_cap", 1) != 0;
        dp->anadv_10fdx   = gem_prop_get_int(dp, "adv_10fdx_cap", 1) != 0;
        dp->anadv_10hdx   = gem_prop_get_int(dp, "adv_10hdx_cap", 1) != 0;

        if ((ddi_prop_exists(DDI_DEV_T_ANY, dp->dip,
            DDI_PROP_DONTPASS, "full-duplex"))) {
                dp->full_duplex = gem_prop_get_int(dp, "full-duplex", 1) != 0;
                dp->anadv_autoneg = B_FALSE;
                if (dp->full_duplex) {
                        dp->anadv_1000hdx = B_FALSE;
                        dp->anadv_100hdx = B_FALSE;
                        dp->anadv_10hdx = B_FALSE;
                } else {
                        dp->anadv_1000fdx = B_FALSE;
                        dp->anadv_100fdx = B_FALSE;
                        dp->anadv_10fdx = B_FALSE;
                }
        }

        if ((val = gem_prop_get_int(dp, "speed", 0)) > 0) {
                dp->anadv_autoneg = B_FALSE;
                switch (val) {
                case 1000:
                        dp->speed = GEM_SPD_1000;
                        dp->anadv_100t4   = B_FALSE;
                        dp->anadv_100fdx  = B_FALSE;
                        dp->anadv_100hdx  = B_FALSE;
                        dp->anadv_10fdx   = B_FALSE;
                        dp->anadv_10hdx   = B_FALSE;
                        break;
                case 100:
                        dp->speed = GEM_SPD_100;
                        dp->anadv_1000fdx = B_FALSE;
                        dp->anadv_1000hdx = B_FALSE;
                        dp->anadv_10fdx   = B_FALSE;
                        dp->anadv_10hdx   = B_FALSE;
                        break;
                case 10:
                        dp->speed = GEM_SPD_10;
                        dp->anadv_1000fdx = B_FALSE;
                        dp->anadv_1000hdx = B_FALSE;
                        dp->anadv_100t4   = B_FALSE;
                        dp->anadv_100fdx  = B_FALSE;
                        dp->anadv_100hdx  = B_FALSE;
                        break;
                default:
                        cmn_err(CE_WARN,
                            "!%s: property %s: illegal value:%d",
                            dp->name, "speed", val);
                        dp->anadv_autoneg = B_TRUE;
                        break;
                }
        }

        val = gem_prop_get_int(dp, "flow-control", dp->gc.gc_flow_control);
        if (val > FLOW_CONTROL_RX_PAUSE || val < FLOW_CONTROL_NONE) {
                cmn_err(CE_WARN,
                    "!%s: property %s: illegal value:%d",
                    dp->name, "flow-control", val);
        } else {
                val = min(val, dp->gc.gc_flow_control);
        }
        dp->anadv_flow_control = val;

        if (gem_prop_get_int(dp, "nointr", 0)) {
                dp->misc_flag |= GEM_NOINTR;
                cmn_err(CE_NOTE, "!%s: polling mode enabled", dp->name);
        }

        dp->mtu = gem_prop_get_int(dp, "mtu", dp->mtu);
        dp->txthr = gem_prop_get_int(dp, "txthr", dp->txthr);
        dp->rxthr = gem_prop_get_int(dp, "rxthr", dp->rxthr);
        dp->txmaxdma = gem_prop_get_int(dp, "txmaxdma", dp->txmaxdma);
        dp->rxmaxdma = gem_prop_get_int(dp, "rxmaxdma", dp->rxmaxdma);
}


/*
 * Gem kstat support
 */

#define GEM_LOCAL_DATA_SIZE(gc) \
        (sizeof (struct gem_dev) + \
        sizeof (struct mcast_addr) * GEM_MAXMC + \
        sizeof (struct txbuf) * ((gc)->gc_tx_buf_size) + \
        sizeof (void *) * ((gc)->gc_tx_buf_size))

struct gem_dev *
gem_do_attach(dev_info_t *dip, int port,
    struct gem_conf *gc, void *base, ddi_acc_handle_t *regs_handlep,
    void *lp, int lmsize)
{
        struct gem_dev          *dp;
        int                     i;
        ddi_iblock_cookie_t     c;
        mac_register_t          *macp = NULL;
        int                     ret;
        int                     unit;
        int                     nports;

        unit = ddi_get_instance(dip);
        if ((nports = gc->gc_nports) == 0) {
                nports = 1;
        }
        if (nports == 1) {
                ddi_set_driver_private(dip, NULL);
        }

        DPRINTF(2, (CE_CONT, "!gem%d: gem_do_attach: called cmd:ATTACH",
            unit));

        /*
         * Allocate soft data structure
         */
        dp = kmem_zalloc(GEM_LOCAL_DATA_SIZE(gc), KM_SLEEP);

        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                cmn_err(CE_WARN, "!gem%d: %s: mac_alloc failed",
                    unit, __func__);
                kmem_free(dp, GEM_LOCAL_DATA_SIZE(gc));
                return (NULL);
        }
        /* ddi_set_driver_private(dip, dp); */

        /* link to private area */
        dp->private = lp;
        dp->priv_size = lmsize;
        dp->mc_list = (struct mcast_addr *)&dp[1];

        dp->dip = dip;
        (void) sprintf(dp->name, gc->gc_name, nports * unit + port);

        /*
         * Get iblock cookie
         */
        if (ddi_get_iblock_cookie(dip, 0, &c) != DDI_SUCCESS) {
                cmn_err(CE_CONT,
                    "!%s: gem_do_attach: ddi_get_iblock_cookie: failed",
                    dp->name);
                goto err_free_private;
        }
        dp->iblock_cookie = c;

        /*
         * Initialize mutex's for this device.
         */
        mutex_init(&dp->intrlock, NULL, MUTEX_DRIVER, (void *)c);
        mutex_init(&dp->xmitlock, NULL, MUTEX_DRIVER, (void *)c);
        cv_init(&dp->tx_drain_cv, NULL, CV_DRIVER, NULL);

        /*
         * configure gem parameter
         */
        dp->base_addr = base;
        dp->regs_handle = *regs_handlep;
        dp->gc = *gc;
        gc = &dp->gc;
        /* patch for simplify dma resource management */
        gc->gc_tx_max_frags = 1;
        gc->gc_tx_max_descs_per_pkt = 1;
        gc->gc_tx_ring_size = gc->gc_tx_buf_size;
        gc->gc_tx_ring_limit = gc->gc_tx_buf_limit;
        gc->gc_tx_desc_write_oo = B_TRUE;

        gc->gc_nports = nports; /* fix nports */

        /* fix copy threadsholds */
        gc->gc_tx_copy_thresh = max(ETHERMIN, gc->gc_tx_copy_thresh);
        gc->gc_rx_copy_thresh = max(ETHERMIN, gc->gc_rx_copy_thresh);

        /* fix rx buffer boundary for iocache line size */
        ASSERT(gc->gc_dma_attr_txbuf.dma_attr_align-1 == gc->gc_tx_buf_align);
        ASSERT(gc->gc_dma_attr_rxbuf.dma_attr_align-1 == gc->gc_rx_buf_align);
        gc->gc_rx_buf_align = max(gc->gc_rx_buf_align, IOC_LINESIZE - 1);
        gc->gc_dma_attr_rxbuf.dma_attr_align = gc->gc_rx_buf_align + 1;

        /* fix descriptor boundary for cache line size */
        gc->gc_dma_attr_desc.dma_attr_align =
            max(gc->gc_dma_attr_desc.dma_attr_align, IOC_LINESIZE);

        /* patch get_packet method */
        if (gc->gc_get_packet == NULL) {
                gc->gc_get_packet = &gem_get_packet_default;
        }

        /* patch get_rx_start method */
        if (gc->gc_rx_start == NULL) {
                gc->gc_rx_start = &gem_rx_start_default;
        }

        /* calculate descriptor area */
        if (gc->gc_rx_desc_unit_shift >= 0) {
                dp->rx_desc_size =
                    ROUNDUP(gc->gc_rx_ring_size << gc->gc_rx_desc_unit_shift,
                    gc->gc_dma_attr_desc.dma_attr_align);
        }
        if (gc->gc_tx_desc_unit_shift >= 0) {
                dp->tx_desc_size =
                    ROUNDUP(gc->gc_tx_ring_size << gc->gc_tx_desc_unit_shift,
                    gc->gc_dma_attr_desc.dma_attr_align);
        }

        dp->mtu = ETHERMTU;
        dp->tx_buf = (void *)&dp->mc_list[GEM_MAXMC];
        /* link tx buffers */
        for (i = 0; i < dp->gc.gc_tx_buf_size; i++) {
                dp->tx_buf[i].txb_next =
                    &dp->tx_buf[SLOT(i + 1, dp->gc.gc_tx_buf_size)];
        }

        dp->rxmode         = 0;
        dp->speed          = GEM_SPD_10;        /* default is 10Mbps */
        dp->full_duplex    = B_FALSE;           /* default is half */
        dp->flow_control   = FLOW_CONTROL_NONE;
        dp->poll_pkt_delay = 8;         /* typical coalease for rx packets */

        /* performance tuning parameters */
        dp->txthr    = ETHERMAX;        /* tx fifo threshold */
        dp->txmaxdma = 16*4;            /* tx max dma burst size */
        dp->rxthr    = 128;             /* rx fifo threshold */
        dp->rxmaxdma = 16*4;            /* rx max dma burst size */

        /*
         * Get media mode information from .conf file
         */
        gem_read_conf(dp);

        /* rx_buf_len is required buffer length without padding for alignment */
        dp->rx_buf_len = MAXPKTBUF(dp) + dp->gc.gc_rx_header_len;

        /*
         * Reset the chip
         */
        mutex_enter(&dp->intrlock);
        dp->nic_state = NIC_STATE_STOPPED;
        ret = (*dp->gc.gc_reset_chip)(dp);
        mutex_exit(&dp->intrlock);
        if (ret != GEM_SUCCESS) {
                goto err_free_regs;
        }

        /*
         * HW dependant paremeter initialization
         */
        mutex_enter(&dp->intrlock);
        ret = (*dp->gc.gc_attach_chip)(dp);
        mutex_exit(&dp->intrlock);
        if (ret != GEM_SUCCESS) {
                goto err_free_regs;
        }

#ifdef DEBUG_MULTIFRAGS
        dp->gc.gc_tx_copy_thresh = dp->mtu;
#endif
        /* allocate tx and rx resources */
        if (gem_alloc_memory(dp)) {
                goto err_free_regs;
        }

        DPRINTF(0, (CE_CONT,
            "!%s: at 0x%x, %02x:%02x:%02x:%02x:%02x:%02x",
            dp->name, (long)dp->base_addr,
            dp->dev_addr.ether_addr_octet[0],
            dp->dev_addr.ether_addr_octet[1],
            dp->dev_addr.ether_addr_octet[2],
            dp->dev_addr.ether_addr_octet[3],
            dp->dev_addr.ether_addr_octet[4],
            dp->dev_addr.ether_addr_octet[5]));

        /* copy mac address */
        dp->cur_addr = dp->dev_addr;

        gem_gld3_init(dp, macp);

        /* Probe MII phy (scan phy) */
        dp->mii_lpable = 0;
        dp->mii_advert = 0;
        dp->mii_exp = 0;
        dp->mii_ctl1000 = 0;
        dp->mii_stat1000 = 0;
        if ((*dp->gc.gc_mii_probe)(dp) != GEM_SUCCESS) {
                goto err_free_ring;
        }

        /* mask unsupported abilities */
        dp->anadv_autoneg &= BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG);
        dp->anadv_1000fdx &=
            BOOLEAN(dp->mii_xstatus &
            (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD));
        dp->anadv_1000hdx &=
            BOOLEAN(dp->mii_xstatus &
            (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET));
        dp->anadv_100t4  &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4);
        dp->anadv_100fdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD);
        dp->anadv_100hdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX);
        dp->anadv_10fdx  &= BOOLEAN(dp->mii_status & MII_STATUS_10_FD);
        dp->anadv_10hdx  &= BOOLEAN(dp->mii_status & MII_STATUS_10);

        gem_choose_forcedmode(dp);

        /* initialize MII phy if required */
        if (dp->gc.gc_mii_init) {
                if ((*dp->gc.gc_mii_init)(dp) != GEM_SUCCESS) {
                        goto err_free_ring;
                }
        }

        /*
         * initialize kstats including mii statistics
         */
        gem_nd_setup(dp);

        /*
         * Add interrupt to system.
         */
        if (ret = mac_register(macp, &dp->mh)) {
                cmn_err(CE_WARN, "!%s: mac_register failed, error:%d",
                    dp->name, ret);
                goto err_release_stats;
        }
        mac_free(macp);
        macp = NULL;

        if (dp->misc_flag & GEM_SOFTINTR) {
                if (ddi_add_softintr(dip,
                    DDI_SOFTINT_LOW, &dp->soft_id,
                    NULL, NULL,
                    (uint_t (*)(caddr_t))gem_intr,
                    (caddr_t)dp) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "!%s: ddi_add_softintr failed",
                            dp->name);
                        goto err_unregister;
                }
        } else if ((dp->misc_flag & GEM_NOINTR) == 0) {
                if (ddi_add_intr(dip, 0, NULL, NULL,
                    (uint_t (*)(caddr_t))gem_intr,
                    (caddr_t)dp) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "!%s: ddi_add_intr failed", dp->name);
                        goto err_unregister;
                }
        } else {
                /*
                 * Dont use interrupt.
                 * schedule first call of gem_intr_watcher
                 */
                dp->intr_watcher_id =
                    timeout((void (*)(void *))gem_intr_watcher,
                    (void *)dp, drv_usectohz(3*1000000));
        }

        /* link this device to dev_info */
        dp->next = (struct gem_dev *)ddi_get_driver_private(dip);
        dp->port = port;
        ddi_set_driver_private(dip, (caddr_t)dp);

        /* reset mii phy and start mii link watcher */
        gem_mii_start(dp);

        DPRINTF(2, (CE_CONT, "!gem_do_attach: return: success"));
        return (dp);

err_unregister:
        (void) mac_unregister(dp->mh);
err_release_stats:
        /* release NDD resources */
        gem_nd_cleanup(dp);

err_free_ring:
        gem_free_memory(dp);
err_free_regs:
        ddi_regs_map_free(&dp->regs_handle);
        mutex_destroy(&dp->xmitlock);
        mutex_destroy(&dp->intrlock);
        cv_destroy(&dp->tx_drain_cv);
err_free_private:
        if (macp) {
                mac_free(macp);
        }
        kmem_free((caddr_t)dp, GEM_LOCAL_DATA_SIZE(gc));

        return (NULL);
}

int
gem_do_detach(dev_info_t *dip)
{
        struct gem_dev  *dp;
        struct gem_dev  *tmp;
        caddr_t         private;
        int             priv_size;
        ddi_acc_handle_t        rh;

        dp = GEM_GET_DEV(dip);
        if (dp == NULL) {
                return (DDI_SUCCESS);
        }

        rh = dp->regs_handle;
        private = dp->private;
        priv_size = dp->priv_size;

        while (dp) {
                /* unregister with gld v3 */
                if (mac_unregister(dp->mh) != 0) {
                        return (DDI_FAILURE);
                }

                /* ensure any rx buffers are not used */
                if (dp->rx_buf_allocated != dp->rx_buf_freecnt) {
                        /* resource is busy */
                        cmn_err(CE_PANIC,
                            "!%s: %s: rxbuf is busy: allocated:%d, freecnt:%d",
                            dp->name, __func__,
                            dp->rx_buf_allocated, dp->rx_buf_freecnt);
                        /* NOT REACHED */
                }

                /* stop mii link watcher */
                gem_mii_stop(dp);

                /* unregister interrupt handler */
                if (dp->misc_flag & GEM_SOFTINTR) {
                        ddi_remove_softintr(dp->soft_id);
                } else if ((dp->misc_flag & GEM_NOINTR) == 0) {
                        ddi_remove_intr(dip, 0, dp->iblock_cookie);
                } else {
                        /* stop interrupt watcher */
                        if (dp->intr_watcher_id) {
                                while (untimeout(dp->intr_watcher_id) == -1)
                                        ;
                                dp->intr_watcher_id = 0;
                        }
                }

                /* release NDD resources */
                gem_nd_cleanup(dp);
                /* release buffers, descriptors and dma resources */
                gem_free_memory(dp);

                /* release locks and condition variables */
                mutex_destroy(&dp->xmitlock);
                mutex_destroy(&dp->intrlock);
                cv_destroy(&dp->tx_drain_cv);

                /* release basic memory resources */
                tmp = dp->next;
                kmem_free((caddr_t)dp, GEM_LOCAL_DATA_SIZE(&dp->gc));
                dp = tmp;
        }

        /* release common private memory for the nic */
        kmem_free(private, priv_size);

        /* release register mapping resources */
        ddi_regs_map_free(&rh);

        DPRINTF(2, (CE_CONT, "!%s%d: gem_do_detach: return: success",
            ddi_driver_name(dip), ddi_get_instance(dip)));

        return (DDI_SUCCESS);
}

int
gem_suspend(dev_info_t *dip)
{
        struct gem_dev  *dp;

        /*
         * stop the device
         */
        dp = GEM_GET_DEV(dip);
        ASSERT(dp);

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

        for (; dp; dp = dp->next) {

                /* stop mii link watcher */
                gem_mii_stop(dp);

                /* stop interrupt watcher for no-intr mode */
                if (dp->misc_flag & GEM_NOINTR) {
                        if (dp->intr_watcher_id) {
                                while (untimeout(dp->intr_watcher_id) == -1)
                                        ;
                        }
                        dp->intr_watcher_id = 0;
                }

                /* stop tx timeout watcher */
                if (dp->timeout_id) {
                        while (untimeout(dp->timeout_id) == -1)
                                ;
                        dp->timeout_id = 0;
                }

                /* make the nic state inactive */
                mutex_enter(&dp->intrlock);
                (void) gem_mac_stop(dp, 0);
                ASSERT(!dp->mac_active);

                /* no further register access */
                dp->mac_suspended = B_TRUE;
                mutex_exit(&dp->intrlock);
        }

        /* XXX - power down the nic */

        return (DDI_SUCCESS);
}

int
gem_resume(dev_info_t *dip)
{
        struct gem_dev  *dp;

        /*
         * restart the device
         */
        dp = GEM_GET_DEV(dip);
        ASSERT(dp);

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

        for (; dp; dp = dp->next) {

                /*
                 * Bring up the nic after power up
                 */

                /* gem_xxx.c layer to setup power management state. */
                ASSERT(!dp->mac_active);

                /* reset the chip, because we are just after power up. */
                mutex_enter(&dp->intrlock);

                dp->mac_suspended = B_FALSE;
                dp->nic_state = NIC_STATE_STOPPED;

                if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) {
                        cmn_err(CE_WARN, "%s: %s: failed to reset chip",
                            dp->name, __func__);
                        mutex_exit(&dp->intrlock);
                        goto err;
                }
                mutex_exit(&dp->intrlock);

                /* initialize mii phy because we are just after power up */
                if (dp->gc.gc_mii_init) {
                        (void) (*dp->gc.gc_mii_init)(dp);
                }

                if (dp->misc_flag & GEM_NOINTR) {
                        /*
                         * schedule first call of gem_intr_watcher
                         * instead of interrupts.
                         */
                        dp->intr_watcher_id =
                            timeout((void (*)(void *))gem_intr_watcher,
                            (void *)dp, drv_usectohz(3*1000000));
                }

                /* restart mii link watcher */
                gem_mii_start(dp);

                /* restart mac */
                mutex_enter(&dp->intrlock);

                if (gem_mac_init(dp) != GEM_SUCCESS) {
                        mutex_exit(&dp->intrlock);
                        goto err_reset;
                }
                dp->nic_state = NIC_STATE_INITIALIZED;

                /* setup media mode if the link have been up */
                if (dp->mii_state == MII_STATE_LINKUP) {
                        if ((dp->gc.gc_set_media)(dp) != GEM_SUCCESS) {
                                mutex_exit(&dp->intrlock);
                                goto err_reset;
                        }
                }

                /* enable mac address and rx filter */
                dp->rxmode |= RXMODE_ENABLE;
                if ((*dp->gc.gc_set_rx_filter)(dp) != GEM_SUCCESS) {
                        mutex_exit(&dp->intrlock);
                        goto err_reset;
                }
                dp->nic_state = NIC_STATE_ONLINE;

                /* restart tx timeout watcher */
                dp->timeout_id = timeout((void (*)(void *))gem_tx_timeout,
                    (void *)dp,
                    dp->gc.gc_tx_timeout_interval);

                /* now the nic is fully functional */
                if (dp->mii_state == MII_STATE_LINKUP) {
                        if (gem_mac_start(dp) != GEM_SUCCESS) {
                                mutex_exit(&dp->intrlock);
                                goto err_reset;
                        }
                }
                mutex_exit(&dp->intrlock);
        }

        return (DDI_SUCCESS);

err_reset:
        if (dp->intr_watcher_id) {
                while (untimeout(dp->intr_watcher_id) == -1)
                        ;
                dp->intr_watcher_id = 0;
        }
        mutex_enter(&dp->intrlock);
        (*dp->gc.gc_reset_chip)(dp);
        dp->nic_state = NIC_STATE_STOPPED;
        mutex_exit(&dp->intrlock);

err:
        return (DDI_FAILURE);
}

/*
 * misc routines for PCI
 */
uint8_t
gem_search_pci_cap(dev_info_t *dip,
    ddi_acc_handle_t conf_handle, uint8_t target)
{
        uint8_t         pci_cap_ptr;
        uint32_t        pci_cap;

        /* search power management capablities */
        pci_cap_ptr = pci_config_get8(conf_handle, PCI_CONF_CAP_PTR);
        while (pci_cap_ptr) {
                /* read pci capability header */
                pci_cap = pci_config_get32(conf_handle, pci_cap_ptr);
                if ((pci_cap & 0xff) == target) {
                        /* found */
                        break;
                }
                /* get next_ptr */
                pci_cap_ptr = (pci_cap >> 8) & 0xff;
        }
        return (pci_cap_ptr);
}

int
gem_pci_set_power_state(dev_info_t *dip,
    ddi_acc_handle_t conf_handle, uint_t new_mode)
{
        uint8_t         pci_cap_ptr;
        uint32_t        pmcsr;
        uint_t          unit;
        const char      *drv_name;

        ASSERT(new_mode < 4);

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

        /* search power management capablities */
        pci_cap_ptr = gem_search_pci_cap(dip, conf_handle, PCI_CAP_ID_PM);

        if (pci_cap_ptr == 0) {
                cmn_err(CE_CONT,
                    "!%s%d: doesn't have pci power management capability",
                    drv_name, unit);
                return (DDI_FAILURE);
        }

        /* read power management capabilities */
        pmcsr = pci_config_get32(conf_handle, pci_cap_ptr + PCI_PMCSR);

        DPRINTF(0, (CE_CONT,
            "!%s%d: pmc found at 0x%x: pmcsr: 0x%08x",
            drv_name, unit, pci_cap_ptr, pmcsr));

        /*
         * Is the resuested power mode supported?
         */
        /* not yet */

        /*
         * move to new mode
         */
        pmcsr = (pmcsr & ~PCI_PMCSR_STATE_MASK) | new_mode;
        pci_config_put32(conf_handle, pci_cap_ptr + PCI_PMCSR, pmcsr);

        return (DDI_SUCCESS);
}

/*
 * select suitable register for by specified address space or register
 * offset in PCI config space
 */
int
gem_pci_regs_map_setup(dev_info_t *dip, uint32_t which, uint32_t mask,
    struct ddi_device_acc_attr *attrp,
    caddr_t *basep, ddi_acc_handle_t *hp)
{
        struct pci_phys_spec    *regs;
        uint_t          len;
        uint_t          unit;
        uint_t          n;
        uint_t          i;
        int             ret;
        const char      *drv_name;

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

        /* Search IO-range or memory-range to be mapped */
        regs = NULL;
        len  = 0;

        if ((ret = ddi_prop_lookup_int_array(
            DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", (void *)&regs, &len)) != DDI_PROP_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s%d: failed to get reg property (ret:%d)",
                    drv_name, unit, ret);
                return (DDI_FAILURE);
        }
        n = len / (sizeof (struct pci_phys_spec) / sizeof (int));

        ASSERT(regs != NULL && len > 0);

#if GEM_DEBUG_LEVEL > 0
        for (i = 0; i < n; i++) {
                cmn_err(CE_CONT,
                    "!%s%d: regs[%d]: %08x.%08x.%08x.%08x.%08x",
                    drv_name, unit, i,
                    regs[i].pci_phys_hi,
                    regs[i].pci_phys_mid,
                    regs[i].pci_phys_low,
                    regs[i].pci_size_hi,
                    regs[i].pci_size_low);
        }
#endif
        for (i = 0; i < n; i++) {
                if ((regs[i].pci_phys_hi & mask) == which) {
                        /* it's the requested space */
                        ddi_prop_free(regs);
                        goto address_range_found;
                }
        }
        ddi_prop_free(regs);
        return (DDI_FAILURE);

address_range_found:
        if ((ret = ddi_regs_map_setup(dip, i, basep, 0, 0, attrp, hp))
            != DDI_SUCCESS) {
                cmn_err(CE_CONT,
                    "!%s%d: ddi_regs_map_setup failed (ret:%d)",
                    drv_name, unit, ret);
        }

        return (ret);
}

void
gem_mod_init(struct dev_ops *dop, char *name)
{
        mac_init_ops(dop, name);
}

void
gem_mod_fini(struct dev_ops *dop)
{
        mac_fini_ops(dop);
}