root/usr/src/uts/common/io/usbgem/usbgem.c
/*
 * usbgem.c: General USB to Fast Ethernet mac driver framework
 *
 * Copyright (c) 2002-2012 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 2019 Joyent, Inc.
 * Copyright 2022 Garrett D'Amore
 */

/*
 * Change log
 */

/*
 * TODO:
 *      implement DELAYED_START
 */

/*
 * 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/usb/usba.h>
#include <inet/common.h>
#include <inet/led.h>
#include <inet/mi.h>
#include <inet/nd.h>

/* supplement definitions */
extern const char *usb_str_cr(usb_cr_t);

#include <sys/note.h>

#include "usbgem_mii.h"
#include "usbgem.h"

char    ident[] = "usb general ethernet mac driver v" VERSION;

/* Debugging support */
#ifdef USBGEM_DEBUG_LEVEL
static int usbgem_debug = USBGEM_DEBUG_LEVEL;
#define DPRINTF(n, args)        if (usbgem_debug > (n)) cmn_err args
#else
#define DPRINTF(n, args)
#endif

/*
 * Useful macros and typedefs
 */
#define ROUNDUP(x, a)           (((x) + (a) - 1) & ~((a) - 1))
#define DEFAULT_PIPE(dp)        ((dp)->reg_data->dev_default_ph)
#define VTAG_SIZE       4
#define BOOLEAN(x)      ((x) != 0)
/*
 * configuration parameters
 */
#define USBDRV_MAJOR_VER        2
#define USBDRV_MINOR_VER        0

#define ETHERHEADERL    (sizeof (struct ether_header))
#define MAXPKTLEN(dp)   ((dp)->mtu + ETHERHEADERL)
#define MAXPKTBUF(dp)   ((dp)->mtu + ETHERHEADERL + ETHERFCSL)

#define WATCH_INTERVAL_FAST     drv_usectohz(100*1000)

#define STOP_GRACEFUL   B_TRUE

/*
 * Private functions
 */
static int usbgem_open_pipes(struct usbgem_dev *dp);
static int usbgem_close_pipes(struct usbgem_dev *dp);
static void usbgem_intr_cb(usb_pipe_handle_t, usb_intr_req_t *);
static void usbgem_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
static void usbgem_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);

static int usbgem_mii_start(struct usbgem_dev *);
static void usbgem_mii_stop(struct usbgem_dev *);

/* local buffer management */
static int usbgem_init_rx_buf(struct usbgem_dev *);

/* internal mac interfaces */
static void usbgem_tx_timeout(struct usbgem_dev *);
static void usbgem_mii_link_watcher(struct usbgem_dev *);
static int usbgem_mac_init(struct usbgem_dev *);
static int usbgem_mac_start(struct usbgem_dev *);
static int usbgem_mac_stop(struct usbgem_dev *, int, boolean_t);
static void usbgem_mac_ioctl(struct usbgem_dev *, queue_t *, mblk_t *);

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

static int usbgem_ctrl_retry = 5;

/* usb event support */
static int usbgem_disconnect_cb(dev_info_t *dip);
static int usbgem_reconnect_cb(dev_info_t *dip);
int usbgem_suspend(dev_info_t *dip);
int usbgem_resume(dev_info_t *dip);

static uint8_t usbgem_bcastaddr[] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
        &mod_miscops,
        "usbgem v" VERSION,
};

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

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

        DPRINTF(2, (CE_CONT, "!usbgem: _init: called"));
        status = mod_install(&modlinkage);

        return (status);
}

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

        DPRINTF(2, (CE_CONT, "!usbgem: _fini: called"));
        status = mod_remove(&modlinkage);
        return (status);
}

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

/* ============================================================== */
/*
 * Ether CRC calculation utilities
 */
/* ============================================================== */
/*
 * Ether CRC calculation according to 21143 data sheet
 */
#define CRC32_POLY_LE   0xedb88320
uint32_t
usbgem_ether_crc_le(const uint8_t *addr)
{
        int             idx;
        int             bit;
        uint_t          data;
        uint32_t        crc = 0xffffffff;

        crc = 0xffffffff;
        for (idx = 0; idx < ETHERADDRL; idx++) {
                for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) {
                        crc = (crc >> 1) ^
                            (((crc ^ data) & 1) ? CRC32_POLY_LE : 0);
                }
        }
        return  (crc);
}

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

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

int
usbgem_prop_get_int(struct usbgem_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
usbgem_population(uint32_t x)
{
        int     i;
        int     cnt;

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

static clock_t
usbgem_timestamp_nz()
{
        clock_t now;
        now = ddi_get_lbolt();
        return (now ? now : (clock_t)1);
}

/* ============================================================== */
/*
 * hardware operations
 */
/* ============================================================== */
static int
usbgem_hal_reset_chip(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_reset_chip)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_init_chip(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_init_chip)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_attach_chip(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_attach_chip)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_set_rx_filter(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_set_rx_filter)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_set_media(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_set_media)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_start_chip(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_start_chip)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_stop_chip(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_stop_chip)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}

static int
usbgem_hal_get_stats(struct usbgem_dev *dp)
{
        int     err;

        sema_p(&dp->hal_op_lock);
        err = (*dp->ugc.usbgc_get_stats)(dp);
        sema_v(&dp->hal_op_lock);
        return (err);
}


/* ============================================================== */
/*
 * USB pipe management
 */
/* ============================================================== */
static boolean_t
usbgem_rx_start_unit(struct usbgem_dev *dp, usb_bulk_req_t *req)
{
        mblk_t  *mp;
        int     err;
        usb_flags_t     flags;

        ASSERT(req);

        mp = allocb(dp->rx_buf_len, BPRI_MED);
        if (mp == NULL) {
                cmn_err(CE_WARN, "!%s: %s: failed to allocate mblk",
                    dp->name, __func__);
                goto err;
        }

        req->bulk_len = dp->rx_buf_len;
        req->bulk_data = mp;
        req->bulk_client_private = (usb_opaque_t)dp;
        req->bulk_timeout = 0;
        req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK;
        req->bulk_cb = usbgem_bulkin_cb;
        req->bulk_exc_cb = usbgem_bulkin_cb;
        req->bulk_completion_reason = 0;
        req->bulk_cb_flags = 0;

        flags = 0;
        err = usb_pipe_bulk_xfer(dp->bulkin_pipe, req, flags);

        if (err != USB_SUCCESS) {
                cmn_err(CE_WARN, "%s: failed to bulk_xfer for rx, err:%d",
                    dp->name, err);

                /* free req and mp */
                usb_free_bulk_req(req);
                goto err;
        }
        return (B_TRUE);
err:
        return (B_FALSE);
}

/* ============================================================== */
/*
 * Rx/Tx buffer management
 */
/* ============================================================== */
static int
usbgem_init_rx_buf(struct usbgem_dev *dp)
{
        int     i;
        usb_bulk_req_t  *req;

        ASSERT(dp->mac_state == MAC_STATE_ONLINE);

        for (i = 0; i < dp->ugc.usbgc_rx_list_max; i++) {
                req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP);
                if (req == NULL) {
                        cmn_err(CE_WARN,
                            "!%s: %s: failed to allocate bulkreq for rx",
                            dp->name, __func__);
                        return (USB_FAILURE);
                }
                if (!usbgem_rx_start_unit(dp, req)) {
                        return (USB_FAILURE);
                }
                mutex_enter(&dp->rxlock);
                dp->rx_busy_cnt++;
                mutex_exit(&dp->rxlock);
        }
        return (USB_SUCCESS);
}

/* ============================================================== */
/*
 * memory resource management
 */
/* ============================================================== */
static int
usbgem_free_memory(struct usbgem_dev *dp)
{
        usb_bulk_req_t  *req;

        /* free all tx requst structure */
        while ((req = dp->tx_free_list) != NULL) {
                dp->tx_free_list =
                    (usb_bulk_req_t *)req->bulk_client_private;
                req->bulk_data = NULL;
                usb_free_bulk_req(req);
        }
        return (USB_SUCCESS);
}

static int
usbgem_alloc_memory(struct usbgem_dev *dp)
{
        int     i;
        usb_bulk_req_t  *req;

        /* allocate tx requests */
        dp->tx_free_list = NULL;
        for (i = 0; i < dp->ugc.usbgc_tx_list_max; i++) {
                req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP);
                if (req == NULL) {
                        cmn_err(CE_WARN,
                            "%s:%s failed to allocate tx requests",
                            dp->name, __func__);

                        /* free partially allocated tx requests */
                        (void) usbgem_free_memory(dp);
                        return (USB_FAILURE);
                }

                /* add the new one allocated into tx free list */
                req->bulk_client_private = (usb_opaque_t)dp->tx_free_list;
                dp->tx_free_list = req;
        }

        return (USB_SUCCESS);
}

/* ========================================================== */
/*
 * Start transmission.
 * Return zero on success,
 */
/* ========================================================== */

#ifdef TXTIMEOUT_TEST
static int usbgem_send_cnt = 0;
#endif

/*
 * usbgem_send is used only to send data packet into ethernet line.
 */
static mblk_t *
usbgem_send_common(struct usbgem_dev *dp, mblk_t *mp, uint32_t flags)
{
        int             err;
        mblk_t          *new;
        usb_bulk_req_t  *req;
        int             mcast;
        int             bcast;
        int             len;
        boolean_t       intr;
        usb_flags_t     usb_flags = 0;
#ifdef USBGEM_DEBUG_LEVEL
        usb_pipe_state_t        p_state;
#endif
        DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        intr = (flags & 1) != 0;
        len = msgdsize(mp);
        bcast = 0;
        mcast = 0;
        if (mp->b_rptr[0] & 1) {
                if (bcmp(mp->b_rptr, &usbgem_bcastaddr, ETHERADDRL) == 0) {
                        bcast = 1;
                } else {
                        mcast = 1;
                }
        }
        new = (*dp->ugc.usbgc_tx_make_packet)(dp, mp);
        if (new == NULL) {
                /*
                 * no memory resource. we don't stop downstream,
                 * we just discard the packet.
                 */
                DPRINTF(0, (CE_CONT, "!%s: %s: no memory",
                    dp->name, __func__));
                freemsg(mp);

                mutex_enter(&dp->txlock);
                dp->stats.noxmtbuf++;
                dp->stats.errxmt++;
                mutex_exit(&dp->txlock);

                return (NULL);
        }

        ASSERT(new->b_cont == NULL);

        mutex_enter(&dp->txlock);
        if (dp->tx_free_list == NULL) {
                /*
                 * no tx free slot
                 */
                ASSERT(dp->tx_busy_cnt == dp->ugc.usbgc_tx_list_max);
                mutex_exit(&dp->txlock);

                DPRINTF(4, (CE_CONT, "!%s: %s: no free slot",
                    dp->name, __func__));
                if (new && new != mp) {
                        /* free reallocated message */
                        freemsg(new);
                }
                return (mp);
        }
        req = dp->tx_free_list;
        dp->tx_free_list = (usb_bulk_req_t *)req->bulk_client_private;
        dp->tx_busy_cnt++;

        if (dp->tx_free_list == NULL) {
                intr = B_TRUE;
        }
        if (intr) {
                dp->tx_intr_pended++;
        }
        DB_TCI(new) = intr;
#ifdef USBGEM_DEBUG_LEVEL
        new->b_datap->db_cksum32 = dp->tx_seq_num;
        dp->tx_seq_num++;
#endif
        dp->stats.obytes += len;
        dp->stats.opackets++;
        if (bcast | mcast) {
                dp->stats.obcast += bcast;
                dp->stats.omcast += mcast;
        }
        mutex_exit(&dp->txlock);

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

        req->bulk_len = (long)new->b_wptr - (long)new->b_rptr;
        req->bulk_data = new;
        req->bulk_client_private = (usb_opaque_t)dp;
        req->bulk_timeout = dp->bulkout_timeout;        /* in second */
        req->bulk_attributes = 0;
        req->bulk_cb = usbgem_bulkout_cb;
        req->bulk_exc_cb = usbgem_bulkout_cb;
        req->bulk_completion_reason = 0;
        req->bulk_cb_flags = 0;

        if (intr) {
                usb_flags = USB_FLAGS_SLEEP;
        }
        if ((err = usb_pipe_bulk_xfer(dp->bulkout_pipe, req, usb_flags))
            != USB_SUCCESS) {

                /* failed to transfer the packet, discard it. */
                freemsg(new);
                req->bulk_data = NULL;

                /* recycle the request block */
                mutex_enter(&dp->txlock);
                dp->tx_busy_cnt--;
                req->bulk_client_private = (usb_opaque_t)dp->tx_free_list;
                dp->tx_free_list = req;
                mutex_exit(&dp->txlock);

                cmn_err(CE_NOTE,
                    "%s: %s: usb_pipe_bulk_xfer: failed: err:%d",
                    dp->name, __func__, err);

                /* we use another flag to indicate error state. */
                if (dp->fatal_error == (clock_t)0) {
                        dp->fatal_error = usbgem_timestamp_nz();
                }
        } else {
                /* record the start time */
                dp->tx_start_time = ddi_get_lbolt();
        }

        if (err == USB_SUCCESS && (usb_flags & USB_FLAGS_SLEEP)) {
                usbgem_bulkout_cb(dp->bulkout_pipe, req);
        }

        if (new != mp) {
                freemsg(mp);
        }
        return (NULL);
}

int
usbgem_restart_nic(struct usbgem_dev *dp)
{
        int     ret;
        int     flags = 0;

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

        ASSERT(dp->mac_state != MAC_STATE_DISCONNECTED);

        /*
         * ensure to stop the nic
         */
        if (dp->mac_state == MAC_STATE_ONLINE) {
                (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL);
        }

        /* now the nic become quiescent, reset the chip */
        if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN, "%s: %s: failed to reset chip",
                    dp->name, __func__);
                goto err;
        }

        /*
         * restore the nic state step by step
         */
        if (dp->nic_state < NIC_STATE_INITIALIZED) {
                goto done;
        }

        if (usbgem_mac_init(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN, "%s: %s: failed to initialize chip",
                    dp->name, __func__);
                goto err;
        }

        /* setup mac address and enable rx filter */
        sema_p(&dp->rxfilter_lock);
        dp->rxmode |= RXMODE_ENABLE;
        ret = usbgem_hal_set_rx_filter(dp);
        sema_v(&dp->rxfilter_lock);
        if (ret != USB_SUCCESS) {
                goto err;
        }

        /*
         * update the link state asynchronously
         */
        cv_signal(&dp->link_watcher_wait_cv);

        /*
         * 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) {
                if (usbgem_hal_set_media(dp) != USB_SUCCESS) {
                        goto err;
                }
                if (dp->nic_state < NIC_STATE_ONLINE) {
                        goto done;
                }

                (void) usbgem_mac_start(dp);

        }
done:
        return (USB_SUCCESS);
err:
        return (USB_FAILURE);
}

static void
usbgem_tx_timeout(struct usbgem_dev *dp)
{
        uint_t  rwlock;
        clock_t now;

        for (; ; ) {
                mutex_enter(&dp->tx_watcher_lock);
                (void) cv_timedwait(&dp->tx_watcher_cv, &dp->tx_watcher_lock,
                    dp->tx_watcher_interval + ddi_get_lbolt());
                mutex_exit(&dp->tx_watcher_lock);

                if (dp->tx_watcher_stop) {
                        break;
                }

                now = ddi_get_lbolt();

                rwlock = RW_READER;
again:
                rw_enter(&dp->dev_state_lock, rwlock);

                if ((dp->mac_state != MAC_STATE_DISCONNECTED &&
                    dp->fatal_error &&
                    now - dp->fatal_error >= dp->ugc.usbgc_tx_timeout) ||
                    (dp->mac_state == MAC_STATE_ONLINE &&
                    dp->mii_state == MII_STATE_LINKUP &&
                    dp->tx_busy_cnt != 0 &&
                    now - dp->tx_start_time >= dp->ugc.usbgc_tx_timeout)) {
                        if (rwlock == RW_READER) {
                                /*
                                 * Upgrade dev_state_lock from shared mode
                                 * to exclusive mode to restart nic
                                 */
                                rwlock = RW_WRITER;
                                rw_exit(&dp->dev_state_lock);
                                goto again;
                        }
                        cmn_err(CE_WARN, "%s: %s: restarting the nic:"
                            " fatal_error:%ld nic_state:%d"
                            " mac_state:%d starttime:%ld",
                            dp->name, __func__,
                            dp->fatal_error ? now - dp->fatal_error: 0,
                            dp->nic_state, dp->mac_state,
                            dp->tx_busy_cnt ? now - dp->tx_start_time : 0);

                        (void) usbgem_restart_nic(dp);
                }

                rw_exit(&dp->dev_state_lock);
        }
}

static int
usbgem_tx_watcher_start(struct usbgem_dev *dp)
{
        int     err;
        kthread_t       *wdth;

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

        /* make a first call of uwgem_lw_link_check() */
        dp->tx_watcher_stop = 0;
        dp->tx_watcher_interval = drv_usectohz(1000*1000);

        wdth = thread_create(NULL, 0, usbgem_tx_timeout, dp, 0, &p0,
            TS_RUN, minclsyspri);
        if (wdth == NULL) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to create a tx_watcher thread",
                    dp->name, __func__);
                return (USB_FAILURE);
        }
        dp->tx_watcher_did = wdth->t_did;

        return (USB_SUCCESS);
}

static void
usbgem_tx_watcher_stop(struct usbgem_dev *dp)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));
        if (dp->tx_watcher_did) {
                /* Ensure timer routine stopped */
                dp->tx_watcher_stop = 1;
                cv_signal(&dp->tx_watcher_cv);
                thread_join(dp->tx_watcher_did);
                dp->tx_watcher_did = 0;
        }
}

/* ================================================================== */
/*
 * Callback handlers
 */
/* ================================================================== */
static void
usbgem_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
        mblk_t  *newmp;
        mblk_t  *mp;
        mblk_t  *tp;
        uint64_t        len = 0;
        int     pkts = 0;
        int     bcast = 0;
        int     mcast = 0;
        boolean_t       busy;
        struct usbgem_dev       *dp;

        dp = (struct usbgem_dev *)req->bulk_client_private;
        mp = req->bulk_data;
        req->bulk_data = NULL;

        DPRINTF(2, (CE_CONT, "!%s: %s: mp:%p, cr:%s(%d)",
            dp->name, __func__, mp,
            usb_str_cr(req->bulk_completion_reason),
            req->bulk_completion_reason));

        /*
         * we cannot acquire dev_state_lock because the routine
         * must be executed during usbgem_mac_stop() to avoid
         * dead lock.
         * we use a simle membar operation to get the state correctly.
         */
        membar_consumer();

        if (req->bulk_completion_reason == USB_CR_OK &&
            dp->nic_state == NIC_STATE_ONLINE) {
                newmp = (*dp->ugc.usbgc_rx_make_packet)(dp, mp);

                if (newmp != mp) {
                        /* the message has been reallocated, free old one */
                        freemsg(mp);
                }

                /* the message may includes one or more ethernet packets */
                for (tp = newmp; tp; tp = tp->b_next) {
                        len += (uintptr_t)tp->b_wptr - (uintptr_t)tp->b_rptr;
                        pkts++;
                        if (tp->b_rptr[0] & 1) {
                                if (bcmp(tp->b_rptr, &usbgem_bcastaddr,
                                    ETHERADDRL) == 0) {
                                        bcast++;
                                } else {
                                        mcast++;
                                }
                        }
                }

                /* send up if it is a valid packet */
                mac_rx(dp->mh, NULL, newmp);
        } else {
                freemsg(mp);
                len = 0;
        }

        mutex_enter(&dp->rxlock);
        /* update rx_active */
        if (dp->rx_active) {
                dp->rx_active = dp->mac_state == MAC_STATE_ONLINE;
        }

        dp->stats.rbytes += len;
        dp->stats.rpackets += pkts;
        if (bcast | mcast) {
                dp->stats.rbcast += bcast;
                dp->stats.rmcast += mcast;
        }
        mutex_exit(&dp->rxlock);

        if (dp->rx_active) {
                /* prepare to receive the next packets */
                if (usbgem_rx_start_unit(dp, req)) {
                        /* we successed */
                        goto done;
                }
                cmn_err(CE_WARN,
                    "!%s: %s: failed to fill next rx packet",
                    dp->name, __func__);
                /*
                 * we use another flag to indicate error state.
                 * if we acquire dev_state_lock for RW_WRITER here,
                 * usbgem_mac_stop() may hang.
                 */
                if (dp->fatal_error == (clock_t)0) {
                        dp->fatal_error = usbgem_timestamp_nz();
                }
        } else {
                /* no need to prepare the next packets */
                usb_free_bulk_req(req);
        }

        mutex_enter(&dp->rxlock);
        dp->rx_active = B_FALSE;
        dp->rx_busy_cnt--;
        if (dp->rx_busy_cnt == 0) {
                /* wake up someone waits for me */
                cv_broadcast(&dp->rx_drain_cv);
        }
        mutex_exit(&dp->rxlock);
done:
        ;
}

static void
usbgem_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
        boolean_t       intr;
        boolean_t       tx_sched;
        struct usbgem_dev       *dp;

        dp = (struct usbgem_dev *)req->bulk_client_private;
        tx_sched = B_FALSE;

        DPRINTF(2, (CE_CONT,
            "!%s: %s: cr:%s(%d) cb_flags:0x%x head:%d tail:%d",
            dp->name, __func__,
            usb_str_cr(req->bulk_completion_reason),
            req->bulk_completion_reason,
            req->bulk_cb_flags,
            dp->tx_busy_cnt));

        /* we have finished to transfer the packet into tx fifo */
        intr = DB_TCI(req->bulk_data);
        freemsg(req->bulk_data);

        if (req->bulk_completion_reason != USB_CR_OK &&
            dp->fatal_error == (clock_t)0) {
                dp->fatal_error = usbgem_timestamp_nz();
        }

        mutex_enter(&dp->txlock);

        if (intr) {
                ASSERT(dp->tx_intr_pended > 0);
                /* find the last interrupt we have scheduled */
                if (--(dp->tx_intr_pended) == 0) {
                        tx_sched = B_TRUE;
                }
        }

        ASSERT(dp->tx_busy_cnt > 0);
        req->bulk_client_private = (usb_opaque_t)dp->tx_free_list;
        dp->tx_free_list = req;
        dp->tx_busy_cnt--;

#ifdef CONFIG_TX_LIMITER
        if (tx_sched) {
                dp->tx_max_packets =
                    min(dp->tx_max_packets + 1, dp->ugc.usbgc_tx_list_max);
        }
#endif
        if (dp->mac_state != MAC_STATE_ONLINE && dp->tx_busy_cnt == 0) {
                cv_broadcast(&dp->tx_drain_cv);
        }

        mutex_exit(&dp->txlock);

        if (tx_sched) {
                mac_tx_update(dp->mh);
        }
}

static void
usbgem_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
{
        struct usbgem_dev       *dp;

        dp = (struct usbgem_dev *)req->intr_client_private;
        dp->stats.intr++;

        if (req->intr_completion_reason == USB_CR_OK) {
                (*dp->ugc.usbgc_interrupt)(dp, req->intr_data);
        }

        /* free the request and data */
        usb_free_intr_req(req);
}

/* ======================================================================== */
/*
 * MII support routines
 */
/* ======================================================================== */
static void
usbgem_choose_forcedmode(struct usbgem_dev *dp)
{
        /* choose media mode */
        if (dp->anadv_1000fdx || dp->anadv_1000hdx) {
                dp->speed = USBGEM_SPD_1000;
                dp->full_duplex = dp->anadv_1000fdx;
        } else if (dp->anadv_100fdx || dp->anadv_100t4) {
                dp->speed = USBGEM_SPD_100;
                dp->full_duplex = B_TRUE;
        } else if (dp->anadv_100hdx) {
                dp->speed = USBGEM_SPD_100;
                dp->full_duplex = B_FALSE;
        } else {
                dp->speed = USBGEM_SPD_10;
                dp->full_duplex = dp->anadv_10fdx;
        }
}

static uint16_t
usbgem_mii_read(struct usbgem_dev *dp, uint_t reg, int *errp)
{
        uint16_t        val;

        sema_p(&dp->hal_op_lock);
        val = (*dp->ugc.usbgc_mii_read)(dp, reg, errp);
        sema_v(&dp->hal_op_lock);

        return (val);
}

static void
usbgem_mii_write(struct usbgem_dev *dp, uint_t reg, uint16_t val, int *errp)
{
        sema_p(&dp->hal_op_lock);
        (*dp->ugc.usbgc_mii_write)(dp, reg, val, errp);
        sema_v(&dp->hal_op_lock);
}

static int
usbgem_mii_probe(struct usbgem_dev *dp)
{
        int     err;

        err = (*dp->ugc.usbgc_mii_probe)(dp);
        return (err);
}

static int
usbgem_mii_init(struct usbgem_dev *dp)
{
        int     err;

        err = (*dp->ugc.usbgc_mii_init)(dp);
        return (err);
}

#define fc_cap_decode(x)        \
        ((((x) & MII_ABILITY_PAUSE) != 0 ? 1 : 0) |     \
        (((x) & MII_ABILITY_ASM_DIR) != 0 ? 2 : 0))

int
usbgem_mii_config_default(struct usbgem_dev *dp, int *errp)
{
        uint16_t        mii_stat;
        uint16_t        val;

        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 (USB_FAILURE);
        }

        /* Do not change the rest of ability bits in advert reg */
        val = usbgem_mii_read(dp, MII_AN_ADVERT, errp) & ~MII_ABILITY_ALL;
        if (*errp != USB_SUCCESS) {
                goto usberr;
        }

        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));

        /* set technology bits */
        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 capabilities */
        if (dp->anadv_pause) {
                val |= MII_ABILITY_PAUSE;
        }
        if (dp->anadv_asmpause) {
                val |= MII_ABILITY_ASM_DIR;
        }

        DPRINTF(0, (CE_CONT,
            "!%s: %s: setting MII_AN_ADVERT reg:%b, pause:%d, asmpause:%d",
            dp->name, __func__, val, MII_ABILITY_BITS,
            dp->anadv_pause, dp->anadv_asmpause));

        usbgem_mii_write(dp, MII_AN_ADVERT, val, errp);
        if (*errp != USB_SUCCESS) {
                goto usberr;
        }

        if (dp->mii_status & MII_STATUS_XSTATUS) {
                /*
                 * 1000Base-T GMII support
                 */
                if (!dp->anadv_autoneg) {
                        /* enable manual configuration */
                        val = MII_1000TC_CFG_EN;
                        if (dp->anadv_1000t_ms == 2) {
                                val |= MII_1000TC_CFG_VAL;
                        }
                } else {
                        val = 0;
                        if (dp->anadv_1000fdx) {
                                val |= MII_1000TC_ADV_FULL;
                        }
                        if (dp->anadv_1000hdx) {
                                val |= MII_1000TC_ADV_HALF;
                        }
                        switch (dp->anadv_1000t_ms) {
                        case 1:
                                /* slave */
                                val |= MII_1000TC_CFG_EN;
                                break;

                        case 2:
                                /* master */
                                val |= MII_1000TC_CFG_EN | MII_1000TC_CFG_VAL;
                                break;

                        default:
                                /* auto: do nothing */
                                break;
                        }
                }
                DPRINTF(0, (CE_CONT,
                    "!%s: %s: setting MII_1000TC reg:%b",
                    dp->name, __func__, val, MII_1000TC_BITS));

                usbgem_mii_write(dp, MII_1000TC, val, errp);
                if (*errp != USB_SUCCESS) {
                        goto usberr;
                }
        }
        return (USB_SUCCESS);

usberr:
        return (*errp);
}

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

#define USBGEM_LINKUP(dp)       mac_link_update((dp)->mh, LINK_STATE_UP)
#define USBGEM_LINKDOWN(dp)     mac_link_update((dp)->mh, LINK_STATE_DOWN)

static uint8_t usbgem_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 boolean_t
usbgem_mii_link_check(struct usbgem_dev *dp, int *oldstatep, int *newstatep)
{
        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;
        int             err;
        uint_t          rwlock;

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

        if (dp->mii_state != MII_STATE_LINKUP) {
                rwlock = RW_WRITER;
        } else {
                rwlock = RW_READER;
        }
again:
        rw_enter(&dp->dev_state_lock, rwlock);

        /* save old mii state */
        *oldstatep = dp->mii_state;

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                /* stop periodic execution of the link watcher */
                dp->mii_interval = 0;
                tx_sched = B_FALSE;
                goto next;
        }

        now = ddi_get_lbolt();
        diff = now - dp->mii_last_check;
        dp->mii_last_check = now;

        /*
         * For NWAM, don't show linkdown state right
         * when the device is attached.
         */
        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:
                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;
                }

                val = usbgem_mii_read(dp, MII_CONTROL, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                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 */
                usbgem_mii_write(dp, MII_CONTROL, 0, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
#if USBGEM_DEBUG_LEVEL > 10
                val = usbgem_mii_read(dp, MII_CONTROL, &err);
                cmn_err(CE_CONT, "!%s: readback control %b",
                    dp->name, val, MII_CONTROL_BITS);
#endif
                /* As resetting PHY has completed, configure PHY registers */
                if ((*dp->ugc.usbgc_mii_config)(dp, &err) != USB_SUCCESS) {
                        /* we failed to configure PHY */
                        goto usberr;
                }

                /* prepare for forced mode */
                usbgem_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 = dp->ugc.usbgc_mii_linkdown_timeout;
                        goto next_nowait;
                }

                /* issue an auto-negotiation command */
                goto autonego;

        case MII_STATE_AUTONEGOTIATING:
                /*
                 * Autonegotiation in progress
                 */
                dp->mii_timer -= diff;
                if (dp->mii_timer -
                    (dp->ugc.usbgc_mii_an_timeout - dp->ugc.usbgc_mii_an_wait)
                    > 0) {
                        /* wait for minimum time (2.3 - 2.5 sec) */
                        dp->mii_interval = WATCH_INTERVAL_FAST;
                        goto next;
                }

                /* read PHY status */
                status = usbgem_mii_read(dp, MII_STATUS, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                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 has been timed out,
                                 * Reset PHY and try again.
                                 */
                                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 for a while.
                         */
                        dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval;
                        goto next;
                }

                /*
                 * Auto-negotiation has been completed. Let's go to AN_DONE.
                 */
                dp->mii_state = MII_STATE_AN_DONE;
                dp->mii_supress_msg = B_FALSE;
                DPRINTF(0, (CE_CONT,
                    "!%s: auto-negotiation completed, MII_STATUS:%b",
                    dp->name, status, MII_STATUS_BITS));

                if (dp->ugc.usbgc_mii_an_delay > 0) {
                        dp->mii_timer = dp->ugc.usbgc_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 has 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;
                }

                /*
                 * Setup speed and duplex mode according with
                 * the result of auto negotiation.
                 */

                /*
                 * Read registers required to determin current
                 * duplex mode and media speed.
                 */
                if (dp->ugc.usbgc_mii_an_delay > 0) {
                        /* the 'status' variable is not initialized yet */
                        status = usbgem_mii_read(dp, MII_STATUS, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                }
                advert = usbgem_mii_read(dp, MII_AN_ADVERT, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                lpable = usbgem_mii_read(dp, MII_AN_LPABLE, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                exp = usbgem_mii_read(dp, MII_AN_EXPANSION, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                if (exp == 0xffff) {
                        /* some phys don't have exp register */
                        exp = 0;
                }

                ctl1000 = 0;
                stat1000 = 0;
                if (dp->mii_status & MII_STATUS_XSTATUS) {
                        ctl1000 = usbgem_mii_read(dp, MII_1000TC, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                        stat1000 = usbgem_mii_read(dp, MII_1000TS, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                }
                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: "
                    "status:%b, advert:%b, lpable:%b, exp:%b",
                    dp->name,
                    status, MII_STATUS_BITS,
                    advert, MII_ABILITY_BITS,
                    lpable, MII_ABILITY_BITS,
                    exp, MII_AN_EXP_BITS);

                DPRINTF(0, (CE_CONT, "!%s: MII_STATUS:%b",
                    dp->name, status, MII_STATUS_BITS));

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

                if (usbgem_population(lpable) <= 1 &&
                    (exp & MII_AN_EXP_LPCANAN) == 0) {
                        if ((advert & MII_ABILITY_TECH) != lpable) {
                                cmn_err(CE_WARN,
                                    "!%s: but the link partner doesn't seem"
                                    " to have auto-negotiation capability."
                                    " please check the link configuration.",
                                    dp->name);
                        }
                        /*
                         * it should be a result of pararell detection,
                         * which cannot detect duplex mode.
                         */
                        if ((advert & lpable) == 0 &&
                            lpable & MII_ABILITY_10BASE_T) {
                                /* no common technology, try 10M half mode */
                                lpable |= advert & MII_ABILITY_10BASE_T;
                                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 = USBGEM_SPD_1000;
                        dp->full_duplex = B_TRUE;
                } else if ((ctl1000 & MII_1000TC_ADV_HALF) &&
                    (stat1000 & MII_1000TS_LP_HALF)) {
                        /* 1000BaseT & half duplex */
                        dp->speed = USBGEM_SPD_1000;
                        dp->full_duplex = B_FALSE;
                } else if ((val & MII_ABILITY_100BASE_TX_FD)) {
                        /* 100BaseTx & fullduplex */
                        dp->speed = USBGEM_SPD_100;
                        dp->full_duplex = B_TRUE;
                } else if ((val & MII_ABILITY_100BASE_T4)) {
                        /* 100BaseTx & fullduplex */
                        dp->speed = USBGEM_SPD_100;
                        dp->full_duplex = B_TRUE;
                } else if ((val & MII_ABILITY_100BASE_TX)) {
                        /* 100BaseTx & half duplex */
                        dp->speed = USBGEM_SPD_100;
                        dp->full_duplex = B_FALSE;
                } else if ((val & MII_ABILITY_10BASE_T_FD)) {
                        /* 10BaseT & full duplex */
                        dp->speed = USBGEM_SPD_10;
                        dp->full_duplex = B_TRUE;
                } else if ((val & MII_ABILITY_10BASE_T)) {
                        /* 10BaseT & half duplex */
                        dp->speed = USBGEM_SPD_10;
                        dp->full_duplex = B_FALSE;
                } else {
                        /*
                         * the link partner doesn't seem to have
                         * auto-negotiation capability and our PHY
                         * could not report current mode correctly.
                         * We guess current mode by mii_control register.
                         */
                        val = usbgem_mii_read(dp, MII_CONTROL, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }

                        /* select 100m half or 10m half */
                        dp->speed = (val & MII_CONTROL_100MB) ?
                            USBGEM_SPD_100 : USBGEM_SPD_10;
                        dp->full_duplex = B_FALSE;
                        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,
                            usbgem_speed_value[dp->speed],
                            dp->full_duplex ? "full" : "half");
                }

                if (dp->full_duplex) {
                        dp->flow_control =
                            usbgem_fc_result[fc_cap_decode(advert)]
                            [fc_cap_decode(lpable)];
                } else {
                        dp->flow_control = FLOW_CONTROL_NONE;
                }
                dp->mii_state = MII_STATE_MEDIA_SETUP;
                dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout;
                goto next_nowait;

        case MII_STATE_MEDIA_SETUP:
                DPRINTF(2, (CE_CONT, "!%s: setup midia mode", dp->name));

                /* assume the link state is down */
                dp->mii_state = MII_STATE_LINKDOWN;
                dp->mii_supress_msg = B_FALSE;

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

                if ((!dp->anadv_autoneg) ||
                    dp->ugc.usbgc_mii_an_oneshot || fix_phy) {

                        /*
                         * write the result of auto negotiation back.
                         */
                        val = usbgem_mii_read(dp, MII_CONTROL, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                        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 USBGEM_SPD_1000:
                                val |= MII_CONTROL_1000MB;
                                break;

                        case USBGEM_SPD_100:
                                val |= MII_CONTROL_100MB;
                                break;

                        default:
                                cmn_err(CE_WARN, "%s: unknown speed:%d",
                                    dp->name, dp->speed);
                                /* FALLTHROUGH */

                        case USBGEM_SPD_10:
                                /* for USBGEM_SPD_10, do nothing */
                                break;
                        }

                        if (dp->mii_status & MII_STATUS_XSTATUS) {
                                usbgem_mii_write(dp,
                                    MII_1000TC, MII_1000TC_CFG_EN, &err);
                                if (err != USB_SUCCESS) {
                                        goto usberr;
                                }
                        }
                        usbgem_mii_write(dp, MII_CONTROL, val, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                }
                /*
                 * XXX -- nic state should be one of
                 * NIC_STATE_DISCONNECTED
                 * NIC_STATE_STOPPED
                 * NIC_STATE_INITIALIZED
                 * NIC_STATE_ONLINE
                 */
                if (dp->nic_state >= NIC_STATE_INITIALIZED) {
                        /* notify the result of autonegotiation to mac */
                        if (usbgem_hal_set_media(dp) != USB_SUCCESS) {
                                goto usberr;
                        }
                }
                goto next_nowait;

        case MII_STATE_LINKDOWN:
                status = usbgem_mii_read(dp, MII_STATUS, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                if (status & MII_STATUS_LINKUP) {
                        /*
                         * Link is going up
                         */
                        dp->mii_state = MII_STATE_LINKUP;
                        dp->mii_supress_msg = B_FALSE;

                        DPRINTF(0, (CE_CONT,
                            "!%s: link up detected: status:%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,
                            usbgem_speed_value[dp->speed],
                            dp->full_duplex ? "full" : "half",
                            usbgem_fc_type[dp->flow_control]);

                        dp->mii_interval =
                            dp->ugc.usbgc_mii_link_watch_interval;

                        if (dp->ugc.usbgc_mii_hw_link_detection &&
                            dp->nic_state == NIC_STATE_ONLINE) {
                                dp->mii_interval = 0;
                        }

                        if (dp->nic_state == NIC_STATE_ONLINE) {
                                if (dp->mac_state == MAC_STATE_INITIALIZED) {
                                        (void) usbgem_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) {
                                /*
                                 * the link down timer expired.
                                 * need to restart auto-negotiation.
                                 */
                                linkdown_action =
                                    dp->ugc.usbgc_mii_linkdown_timeout_action;
                                goto restart_autonego;
                        }
                }
                /* don't change mii_state */
                goto next;

        case MII_STATE_LINKUP:
                if (rwlock == RW_READER) {
                        /* first pass, read mii status */
                        status = usbgem_mii_read(dp, MII_STATUS, &err);
                        if (err != USB_SUCCESS) {
                                goto usberr;
                        }
                }
                if ((status & MII_STATUS_LINKUP) == 0) {
                        /*
                         * Link is going down
                         */
                        cmn_err(CE_NOTE,
                            "!%s: link down detected: status:%b",
                            dp->name, status, MII_STATUS_BITS);
                        /*
                         * Acquire exclusive lock to change mii_state
                         */
                        if (rwlock == RW_READER) {
                                rwlock = RW_WRITER;
                                rw_exit(&dp->dev_state_lock);
                                goto again;
                        }

                        dp->mii_state = MII_STATE_LINKDOWN;
                        dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout;

                        /*
                         * As we may change the state of the device,
                         * let us acquire exclusive lock for the state.
                         */
                        if (dp->nic_state == NIC_STATE_ONLINE &&
                            dp->mac_state == MAC_STATE_ONLINE &&
                            dp->ugc.usbgc_mii_stop_mac_on_linkdown) {
                                (void) usbgem_restart_nic(dp);
                                /* drain tx */
                                tx_sched = B_TRUE;
                        }

                        if (dp->anadv_autoneg) {
                                /* need to restart auto-negotiation */
                                linkdown_action =
                                    dp->ugc.usbgc_mii_linkdown_action;
                                goto restart_autonego;
                        }
                        /*
                         * don't use hw link down detection until the link
                         * status become stable for a while.
                         */
                        dp->mii_interval =
                            dp->ugc.usbgc_mii_link_watch_interval;

                        goto next;
                }

                /*
                 * still link up, no need to change mii_state
                 */
                if (dp->ugc.usbgc_mii_hw_link_detection &&
                    dp->nic_state == NIC_STATE_ONLINE) {
                        /*
                         * no need to check link status periodicly
                         * if nic can generate interrupts when link go down.
                         */
                        dp->mii_interval = 0;
                }
                goto next;
        }
        /* NOTREACHED */
        cmn_err(CE_PANIC, "!%s: %s: not reached", dp->name, __func__);

        /*
         * Actions for new state.
         */
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->ugc.usbgc_mii_an_oneshot) {
                        goto autonego;
                }
                /* PHY will restart autonego automatically */
                dp->mii_state = MII_STATE_AUTONEGOTIATING;
                dp->mii_timer = dp->ugc.usbgc_mii_an_timeout;
                dp->mii_interval = dp->ugc.usbgc_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_PANIC, "!%s: unknowm linkdown action: %d",
                    dp->name, dp->ugc.usbgc_mii_linkdown_action);
        }
        /* 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->ugc.usbgc_mii_reset_timeout;
        if (!dp->ugc.usbgc_mii_dont_reset) {
                usbgem_mii_write(dp, MII_CONTROL, MII_CONTROL_RESET, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
        }
        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->ugc.usbgc_mii_an_timeout;

        /* start/restart autoneg */
        val = usbgem_mii_read(dp, MII_CONTROL, &err) &
            ~(MII_CONTROL_ISOLATE | MII_CONTROL_PWRDN | MII_CONTROL_RESET);
        if (err != USB_SUCCESS) {
                goto usberr;
        }
        if (val & MII_CONTROL_ANE) {
                val |= MII_CONTROL_RSAN;
        }
        usbgem_mii_write(dp, MII_CONTROL,
            val | dp->ugc.usbgc_mii_an_cmd | MII_CONTROL_ANE, &err);
        if (err != USB_SUCCESS) {
                goto usberr;
        }

        dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval;
        goto next;

usberr:
        dp->mii_state = MII_STATE_UNKNOWN;
        dp->mii_interval = dp->ugc.usbgc_mii_link_watch_interval;
        tx_sched = B_TRUE;

next:
        *newstatep = dp->mii_state;
        rw_exit(&dp->dev_state_lock);
        return (tx_sched);
}

static void
usbgem_mii_link_watcher(struct usbgem_dev *dp)
{
        int             old_mii_state;
        int             new_mii_state;
        boolean_t       tx_sched;

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

        for (; ; ) {

                mutex_enter(&dp->link_watcher_lock);
                if (dp->mii_interval) {
                        (void) cv_timedwait(&dp->link_watcher_wait_cv,
                            &dp->link_watcher_lock,
                            dp->mii_interval + ddi_get_lbolt());
                } else {
                        cv_wait(&dp->link_watcher_wait_cv,
                            &dp->link_watcher_lock);
                }
                mutex_exit(&dp->link_watcher_lock);

                if (dp->link_watcher_stop) {
                        break;
                }

                /* we block callbacks from disconnect/suspend and restart */
                tx_sched = usbgem_mii_link_check(dp,
                    &old_mii_state, &new_mii_state);

                /*
                 * gld v2 notifier functions are not able to
                 * be called with any locks in this layer.
                 */
                if (tx_sched) {
                        /* kick potentially stopped downstream */
                        mac_tx_update(dp->mh);
                }

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

        thread_exit();
}

void
usbgem_mii_update_link(struct usbgem_dev *dp)
{
        cv_signal(&dp->link_watcher_wait_cv);
}

int
usbgem_mii_probe_default(struct usbgem_dev *dp)
{
        int             phy;
        uint16_t        status;
        uint16_t        xstatus;
        int             err;
        uint16_t        adv;
        uint16_t        adv_org;

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

        /*
         * Scan PHY
         */
        dp->mii_status = 0;

        /* Try default phy first */
        if (dp->mii_phy_addr) {
                status = usbgem_mii_read(dp, MII_STATUS, &err);
                if (err != USB_SUCCESS) {
                        goto usberr;
                }
                if (status != 0xffff && status != 0x0000) {
                        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 (USB_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->ugc.usbgc_mii_addr_min; phy < 32; phy++) {
                dp->mii_phy_addr = phy;
                status = usbgem_mii_read(dp, MII_STATUS, &err);
                if (err != USB_SUCCESS) {
                        DPRINTF(0, (CE_CONT,
                            "!%s: %s: mii_read(status) failed",
                            dp->name, __func__));
                        goto usberr;
                }

                if (status != 0xffff && status != 0x0000) {
                        usbgem_mii_write(dp, MII_CONTROL, 0, &err);
                        if (err != USB_SUCCESS) {
                                DPRINTF(0, (CE_CONT,
                                    "!%s: %s: mii_write(control) failed",
                                    dp->name, __func__));
                                goto usberr;
                        }
                        goto PHY_found;
                }
        }
        for (phy = dp->ugc.usbgc_mii_addr_min; phy < 32; phy++) {
                dp->mii_phy_addr = phy;
                usbgem_mii_write(dp, MII_CONTROL, 0, &err);
                if (err != USB_SUCCESS) {
                        DPRINTF(0, (CE_CONT,
                            "!%s: %s: mii_write(control) failed",
                            dp->name, __func__));
                        goto usberr;
                }
                status = usbgem_mii_read(dp, MII_STATUS, &err);
                if (err != USB_SUCCESS) {
                        DPRINTF(0, (CE_CONT,
                            "!%s: %s: mii_read(status) failed",
                            dp->name, __func__));
                        goto usberr;
                }

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

        cmn_err(CE_NOTE, "!%s: no MII PHY found", dp->name);
        return (USB_FAILURE);

PHY_found:
        dp->mii_status = status;
        dp->mii_status_ro = ~status;
        dp->mii_phy_id = usbgem_mii_read(dp, MII_PHYIDH, &err) << 16;
        if (err != USB_SUCCESS) {
                DPRINTF(0, (CE_CONT,
                    "!%s: %s: mii_read(PHYIDH) failed",
                    dp->name, __func__));
                goto usberr;
        }
        dp->mii_phy_id |= usbgem_mii_read(dp, MII_PHYIDL, &err);
        if (err != USB_SUCCESS) {
                DPRINTF(0, (CE_CONT,
                    "!%s: %s: mii_read(PHYIDL) failed",
                    dp->name, __func__));
                goto usberr;
        }

        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, exp:%b",
            dp->name,
            usbgem_mii_read(dp, MII_CONTROL, &err), MII_CONTROL_BITS,
            status, MII_STATUS_BITS,
            usbgem_mii_read(dp, MII_AN_ADVERT, &err), MII_ABILITY_BITS,
            usbgem_mii_read(dp, MII_AN_LPABLE, &err), MII_ABILITY_BITS,
            usbgem_mii_read(dp, MII_AN_EXPANSION, &err), MII_AN_EXP_BITS);

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

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

        /* check if the phy can advertize pause abilities */
        adv_org = usbgem_mii_read(dp, MII_AN_ADVERT, &err);
        if (err != USB_SUCCESS) {
                goto usberr;
        }

        usbgem_mii_write(dp, MII_AN_ADVERT,
            MII_ABILITY_PAUSE | MII_ABILITY_ASM_DIR, &err);
        if (err != USB_SUCCESS) {
                goto usberr;
        }

        adv = usbgem_mii_read(dp, MII_AN_ADVERT, &err);
        if (err != USB_SUCCESS) {
                goto usberr;
        }

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

        if ((adv & MII_ABILITY_ASM_DIR) == 0) {
                dp->ugc.usbgc_flow_control &= ~2;
        }

        usbgem_mii_write(dp, MII_AN_ADVERT, adv_org, &err);
        if (err != USB_SUCCESS) {
                goto usberr;
        }
        return (USB_SUCCESS);

usberr:
        return (USB_FAILURE);
}

int
usbgem_mii_init_default(struct usbgem_dev *dp)
{
        /* ENPTY */
        return (USB_SUCCESS);
}

static int
usbgem_mii_start(struct usbgem_dev *dp)
{
        int     err;
        kthread_t       *lwth;

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

        /* make a first call of usbgem_mii_link_check() */
        dp->link_watcher_stop = 0;
        dp->mii_state = MII_STATE_UNKNOWN;
        dp->mii_interval = drv_usectohz(1000*1000); /* 1sec */
        dp->mii_last_check = ddi_get_lbolt();
        dp->linkup_delay = 600 * drv_usectohz(1000*1000); /* 10 minutes */

        lwth = thread_create(NULL, 0, usbgem_mii_link_watcher, dp, 0, &p0,
            TS_RUN, minclsyspri);
        if (lwth == NULL) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to create a link watcher thread",
                    dp->name, __func__);
                return (USB_FAILURE);
        }
        dp->link_watcher_did = lwth->t_did;

        return (USB_SUCCESS);
}

static void
usbgem_mii_stop(struct usbgem_dev *dp)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        /* Ensure timer routine stopped */
        dp->link_watcher_stop = 1;
        cv_signal(&dp->link_watcher_wait_cv);
        thread_join(dp->link_watcher_did);
}

/* ============================================================== */
/*
 * internal mac register operation interface
 */
/* ============================================================== */
/*
 * usbgem_mac_init: cold start
 */
static int
usbgem_mac_init(struct usbgem_dev *dp)
{
        int     err;

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

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                /* pretend we succeeded */
                return (USB_SUCCESS);
        }

        ASSERT(dp->mac_state == MAC_STATE_STOPPED);

        /* reset fatal error timestamp */
        dp->fatal_error = (clock_t)0;

        /* reset tx side state */
        mutex_enter(&dp->txlock);
        dp->tx_busy_cnt = 0;
        dp->tx_max_packets = dp->ugc.usbgc_tx_list_max;
        mutex_exit(&dp->txlock);

        /* reset rx side state */
        mutex_enter(&dp->rxlock);
        dp->rx_busy_cnt = 0;
        mutex_exit(&dp->rxlock);

        err = usbgem_hal_init_chip(dp);
        if (err == USB_SUCCESS) {
                dp->mac_state = MAC_STATE_INITIALIZED;
        }

        return (err);
}

/*
 * usbgem_mac_start: warm start
 */
static int
usbgem_mac_start(struct usbgem_dev *dp)
{
        int     err;
        int     i;
        usb_flags_t     flags = 0;
        usb_intr_req_t  *req;
#ifdef USBGEM_DEBUG_LEVEL
        usb_pipe_state_t        p_state;
#endif
        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                /* do nothing but don't return failure */
                return (USB_SUCCESS);
        }

        if (dp->mac_state != MAC_STATE_INITIALIZED) {
                /* don't return failer */
                DPRINTF(0, (CE_CONT,
                    "!%s: %s: mac_state(%d) is not MAC_STATE_INITIALIZED",
                    dp->name, __func__, dp->mac_state));
                goto x;
        }

        dp->mac_state = MAC_STATE_ONLINE;

        if (usbgem_hal_start_chip(dp) != USB_SUCCESS) {
                cmn_err(CE_NOTE,
                    "!%s: %s: usb error was detected during start_chip",
                    dp->name, __func__);
                goto x;
        }

#ifdef USBGEM_DEBUG_LEVEL
        usb_pipe_get_state(dp->intr_pipe, &p_state, 0);
        ASSERT(p_state == USB_PIPE_STATE_IDLE);
#endif /* USBGEM_DEBUG_LEVEL */

        if (dp->ugc.usbgc_interrupt && dp->intr_pipe) {

                /* make a request for interrupt */

                req = usb_alloc_intr_req(dp->dip, 0, USB_FLAGS_SLEEP);
                if (req == NULL) {
                        cmn_err(CE_WARN, "!%s: %s: failed to allocate intreq",
                            dp->name, __func__);
                        goto x;
                }
                req->intr_data = NULL;
                req->intr_client_private = (usb_opaque_t)dp;
                req->intr_timeout = 0;
                req->intr_attributes =
                    USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING;
                req->intr_len = dp->ep_intr->wMaxPacketSize;
                req->intr_cb = usbgem_intr_cb;
                req->intr_exc_cb = usbgem_intr_cb;
                req->intr_completion_reason = 0;
                req->intr_cb_flags = 0;

                err = usb_pipe_intr_xfer(dp->intr_pipe, req, flags);
                if (err != USB_SUCCESS) {
                        cmn_err(CE_WARN,
                            "%s: err:%d failed to start polling of intr pipe",
                            dp->name, err);
                        goto x;
                }
        }

        /* kick to receive the first packet */
        if (usbgem_init_rx_buf(dp) != USB_SUCCESS) {
                goto err_stop_intr;
        }
        dp->rx_active = B_TRUE;

        return (USB_SUCCESS);

err_stop_intr:
        /* stop the interrupt pipe */
        DPRINTF(0, (CE_CONT, "!%s: %s: FAULURE", dp->name, __func__));
        if (dp->ugc.usbgc_interrupt && dp->intr_pipe) {
                usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP);
        }
x:
        ASSERT(dp->mac_state == MAC_STATE_ONLINE);
        /* we use another flag to indicate error state. */
        if (dp->fatal_error == (clock_t)0) {
                dp->fatal_error = usbgem_timestamp_nz();
        }
        return (USB_FAILURE);
}

static int
usbgem_mac_stop(struct usbgem_dev *dp, int new_state, boolean_t graceful)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        /*
         * we must have writer lock for dev_state_lock
         */
        ASSERT(new_state == MAC_STATE_STOPPED ||
            new_state == MAC_STATE_DISCONNECTED);

        /* stop polling interrupt pipe */
        if (dp->ugc.usbgc_interrupt && dp->intr_pipe) {
                usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP);
        }

        if (new_state == MAC_STATE_STOPPED || graceful) {
                /* stop the nic hardware completely */
                if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) {
                        (void) usbgem_hal_reset_chip(dp);
                }
        }

        /* stop preparing new rx packets and sending new packets */
        dp->mac_state = new_state;

        /* other processors must get mac_state correctly after here */
        membar_producer();

        /* cancel all requests we have sent */
        usb_pipe_reset(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0);
        usb_pipe_reset(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0);

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

        /*
         * Here all rx packets has been cancelled and their call back
         * function has been exeuted, because we called usb_pipe_reset
         * synchronously.
         * So actually we just ensure rx_busy_cnt == 0.
         */
        mutex_enter(&dp->rxlock);
        while (dp->rx_busy_cnt > 0) {
                cv_wait(&dp->rx_drain_cv, &dp->rxlock);
        }
        mutex_exit(&dp->rxlock);

        DPRINTF(0, (CE_CONT, "!%s: %s: rx_busy_cnt is %d now",
            dp->name, __func__, dp->rx_busy_cnt));

        mutex_enter(&dp->txlock);
        while (dp->tx_busy_cnt > 0) {
                cv_wait(&dp->tx_drain_cv, &dp->txlock);
        }
        mutex_exit(&dp->txlock);

        DPRINTF(0, (CE_CONT, "!%s: %s: tx_busy_cnt is %d now",
            dp->name, __func__, dp->tx_busy_cnt));

        return (USB_SUCCESS);
}

static int
usbgem_add_multicast(struct usbgem_dev *dp, const uint8_t *ep)
{
        int     cnt;
        int     err;

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

        sema_p(&dp->rxfilter_lock);
        if (dp->mc_count_req++ < USBGEM_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->ugc.usbgc_multicast_hash) {
                        dp->mc_list[cnt].hash =
                            (*dp->ugc.usbgc_multicast_hash)(dp, 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;
        }

        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                /* tell new multicast list to the hardware */
                err = usbgem_hal_set_rx_filter(dp);
        }
        sema_v(&dp->rxfilter_lock);

        return (err);
}

static int
usbgem_remove_multicast(struct usbgem_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__));

        sema_p(&dp->rxfilter_lock);
        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;
        }

        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                err = usbgem_hal_set_rx_filter(dp);
        }
        sema_v(&dp->rxfilter_lock);

        return (err);
}


/* ============================================================== */
/*
 * ioctl
 */
/* ============================================================== */
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  */
};


static int
usbgem_get_def_val(struct usbgem_dev *dp, mac_prop_id_t pr_num,
    uint_t pr_valsize, void *pr_val)
{
        link_flowctrl_t fl;
        int err = 0;

        ASSERT(pr_valsize > 0);
        switch (pr_num) {
        case MAC_PROP_AUTONEG:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG);
                break;

        case MAC_PROP_FLOWCTRL:
                if (pr_valsize < sizeof (link_flowctrl_t)) {
                        return (EINVAL);
                }
                switch (dp->ugc.usbgc_flow_control) {
                case FLOW_CONTROL_NONE:
                        fl = LINK_FLOWCTRL_NONE;
                        break;
                case FLOW_CONTROL_SYMMETRIC:
                        fl = LINK_FLOWCTRL_BI;
                        break;
                case FLOW_CONTROL_TX_PAUSE:
                        fl = LINK_FLOWCTRL_TX;
                        break;
                case FLOW_CONTROL_RX_PAUSE:
                        fl = LINK_FLOWCTRL_RX;
                        break;
                }
                bcopy(&fl, pr_val, sizeof (fl));
                break;

        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_EN_1000FDX_CAP:
                *(uint8_t *)pr_val =
                    (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD);
                break;

        case MAC_PROP_ADV_1000HDX_CAP:
        case MAC_PROP_EN_1000HDX_CAP:
                *(uint8_t *)pr_val =
                    (dp->mii_xstatus & MII_XSTATUS_1000BASET) ||
                    (dp->mii_xstatus & MII_XSTATUS_1000BASEX);
                break;

        case MAC_PROP_ADV_100T4_CAP:
        case MAC_PROP_EN_100T4_CAP:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4);
                break;

        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_EN_100FDX_CAP:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD);
                break;

        case MAC_PROP_ADV_100HDX_CAP:
        case MAC_PROP_EN_100HDX_CAP:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX);
                break;

        case MAC_PROP_ADV_10FDX_CAP:
        case MAC_PROP_EN_10FDX_CAP:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_10_FD);
                break;

        case MAC_PROP_ADV_10HDX_CAP:
        case MAC_PROP_EN_10HDX_CAP:
                *(uint8_t *)pr_val =
                    BOOLEAN(dp->mii_status & MII_STATUS_10);
                break;

        default:
                err = ENOTSUP;
                break;
        }
        return (err);
}

static void
usbgem_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    mac_prop_info_handle_t prh)
{
        struct usbgem_dev *dp = arg;
        link_flowctrl_t fl;

        /*
         * By default permissions are read/write unless specified
         * otherwise by the driver.
         */

        switch (pr_num) {
        case MAC_PROP_DUPLEX:
        case MAC_PROP_SPEED:
        case MAC_PROP_STATUS:
        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_ADV_1000HDX_CAP:
        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_ADV_100HDX_CAP:
        case MAC_PROP_ADV_10FDX_CAP:
        case MAC_PROP_ADV_10HDX_CAP:
        case MAC_PROP_ADV_100T4_CAP:
        case MAC_PROP_EN_100T4_CAP:
                mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                break;

        case MAC_PROP_EN_1000FDX_CAP:
                if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(
                            dp->mii_xstatus & MII_XSTATUS_1000BASET_FD));
                } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD)
                    == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(
                            dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_EN_1000HDX_CAP:
                if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(
                            dp->mii_xstatus & MII_XSTATUS_1000BASET));
                } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(
                            dp->mii_xstatus & MII_XSTATUS_1000BASEX));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_EN_100FDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_EN_100HDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_EN_10FDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(dp->mii_status & MII_STATUS_10_FD));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_EN_10HDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_10) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(dp->mii_status & MII_STATUS_10));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_AUTONEG:
                if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) {
                        mac_prop_info_set_default_uint8(prh,
                            BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG));
                } else {
                        mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
                }
                break;

        case MAC_PROP_FLOWCTRL:
                switch (dp->ugc.usbgc_flow_control) {
                case FLOW_CONTROL_NONE:
                        fl = LINK_FLOWCTRL_NONE;
                        break;
                case FLOW_CONTROL_SYMMETRIC:
                        fl = LINK_FLOWCTRL_BI;
                        break;
                case FLOW_CONTROL_TX_PAUSE:
                        fl = LINK_FLOWCTRL_TX;
                        break;
                case FLOW_CONTROL_RX_PAUSE:
                        fl = LINK_FLOWCTRL_RX;
                        break;
                }
                mac_prop_info_set_default_link_flowctrl(prh, fl);
                break;

        case MAC_PROP_MTU:
                mac_prop_info_set_range_uint32(prh,
                    dp->ugc.usbgc_min_mtu, dp->ugc.usbgc_max_mtu);
                break;

        case MAC_PROP_PRIVATE:
                break;
        }
}

static int
usbgem_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_valsize, const void *pr_val)
{
        struct usbgem_dev *dp = arg;
        int err = 0;
        boolean_t       update = B_FALSE;
        link_flowctrl_t flowctrl;
        uint32_t cur_mtu, new_mtu;

        rw_enter(&dp->dev_state_lock, RW_WRITER);
        switch (pr_num) {
        case MAC_PROP_EN_1000FDX_CAP:
                if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0 ||
                    (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD) == 0) {
                        if (dp->anadv_1000fdx != *(uint8_t *)pr_val) {
                                dp->anadv_1000fdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_EN_1000HDX_CAP:
                if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0 ||
                    (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) {
                        if (dp->anadv_1000hdx != *(uint8_t *)pr_val) {
                                dp->anadv_1000hdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_EN_100FDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) {
                        if (dp->anadv_100fdx != *(uint8_t *)pr_val) {
                                dp->anadv_100fdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_EN_100HDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) {
                        if (dp->anadv_100hdx != *(uint8_t *)pr_val) {
                                dp->anadv_100hdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_EN_10FDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) {
                        if (dp->anadv_10fdx != *(uint8_t *)pr_val) {
                                dp->anadv_10fdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_EN_10HDX_CAP:
                if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) {
                        if (dp->anadv_10hdx != *(uint8_t *)pr_val) {
                                dp->anadv_10hdx = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_AUTONEG:
                if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) {
                        if (dp->anadv_autoneg != *(uint8_t *)pr_val) {
                                dp->anadv_autoneg = *(uint8_t *)pr_val;
                                update = B_TRUE;
                        }
                } else {
                        err = ENOTSUP;
                }
                break;

        case MAC_PROP_FLOWCTRL:
                bcopy(pr_val, &flowctrl, sizeof (flowctrl));

                switch (flowctrl) {
                default:
                        err = EINVAL;
                        break;

                case LINK_FLOWCTRL_NONE:
                        if (dp->flow_control != FLOW_CONTROL_NONE) {
                                dp->flow_control = FLOW_CONTROL_NONE;
                                update = B_TRUE;
                        }
                        break;

                case LINK_FLOWCTRL_RX:
                        if (dp->flow_control != FLOW_CONTROL_RX_PAUSE) {
                                dp->flow_control = FLOW_CONTROL_RX_PAUSE;
                                update = B_TRUE;
                        }
                        break;

                case LINK_FLOWCTRL_TX:
                        if (dp->flow_control != FLOW_CONTROL_TX_PAUSE) {
                                dp->flow_control = FLOW_CONTROL_TX_PAUSE;
                                update = B_TRUE;
                        }
                        break;

                case LINK_FLOWCTRL_BI:
                        if (dp->flow_control != FLOW_CONTROL_SYMMETRIC) {
                                dp->flow_control = FLOW_CONTROL_SYMMETRIC;
                                update = B_TRUE;
                        }
                        break;
                }
                break;

        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_ADV_1000HDX_CAP:
        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_ADV_100HDX_CAP:
        case MAC_PROP_ADV_10FDX_CAP:
        case MAC_PROP_ADV_10HDX_CAP:
        case MAC_PROP_STATUS:
        case MAC_PROP_SPEED:
        case MAC_PROP_DUPLEX:
                err = ENOTSUP; /* read-only prop. Can't set this. */
                break;

        case MAC_PROP_MTU:
                bcopy(pr_val, &new_mtu, sizeof (new_mtu));
                if (new_mtu != dp->mtu) {
                        err = EINVAL;
                }
                break;

        case MAC_PROP_PRIVATE:
                err = ENOTSUP;
                break;

        default:
                err = ENOTSUP;
                break;
        }

        if (update) {
                /* sync with PHY */
                usbgem_choose_forcedmode(dp);
                dp->mii_state = MII_STATE_UNKNOWN;
                cv_signal(&dp->link_watcher_wait_cv);
        }
        rw_exit(&dp->dev_state_lock);
        return (err);
}

static int
usbgem_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_valsize, void *pr_val)
{
        struct usbgem_dev *dp = arg;
        int err = 0;
        link_flowctrl_t flowctrl;
        uint64_t tmp = 0;

        if (pr_valsize == 0) {
                return (EINVAL);
        }

        bzero(pr_val, pr_valsize);
        rw_enter(&dp->dev_state_lock, RW_READER);
        switch (pr_num) {
        case MAC_PROP_DUPLEX:
                if (pr_valsize >= sizeof (link_duplex_t)) {
                        if (dp->mii_state != MII_STATE_LINKUP) {
                                *(link_duplex_t *)pr_val = LINK_DUPLEX_UNKNOWN;
                        } else if (dp->full_duplex) {
                                *(link_duplex_t *)pr_val = LINK_DUPLEX_FULL;
                        } else {
                                *(link_duplex_t *)pr_val = LINK_DUPLEX_HALF;
                        }
                } else {
                        err = EINVAL;
                }
                break;
        case MAC_PROP_SPEED:
                if (pr_valsize >= sizeof (uint64_t)) {
                        switch (dp->speed) {
                        case USBGEM_SPD_1000:
                                tmp = 1000000000;
                                break;
                        case USBGEM_SPD_100:
                                tmp = 100000000;
                                break;
                        case USBGEM_SPD_10:
                                tmp = 10000000;
                                break;
                        default:
                                tmp = 0;
                        }
                        bcopy(&tmp, pr_val, sizeof (tmp));
                } else {
                        err = EINVAL;
                }
                break;

        case MAC_PROP_AUTONEG:
                *(uint8_t *)pr_val = dp->anadv_autoneg;
                break;

        case MAC_PROP_FLOWCTRL:
                if (pr_valsize >= sizeof (link_flowctrl_t)) {
                        switch (dp->flow_control) {
                        case FLOW_CONTROL_NONE:
                                flowctrl = LINK_FLOWCTRL_NONE;
                                break;
                        case FLOW_CONTROL_RX_PAUSE:
                                flowctrl = LINK_FLOWCTRL_RX;
                                break;
                        case FLOW_CONTROL_TX_PAUSE:
                                flowctrl = LINK_FLOWCTRL_TX;
                                break;
                        case FLOW_CONTROL_SYMMETRIC:
                                flowctrl = LINK_FLOWCTRL_BI;
                                break;
                        }
                        bcopy(&flowctrl, pr_val, sizeof (flowctrl));
                } else {
                        err = EINVAL;
                }
                break;

        case MAC_PROP_ADV_1000FDX_CAP:
        case MAC_PROP_ADV_1000HDX_CAP:
        case MAC_PROP_ADV_100FDX_CAP:
        case MAC_PROP_ADV_100HDX_CAP:
        case MAC_PROP_ADV_10FDX_CAP:
        case MAC_PROP_ADV_10HDX_CAP:
        case MAC_PROP_ADV_100T4_CAP:
                usbgem_get_def_val(dp, pr_num, pr_valsize, pr_val);
                break;

        case MAC_PROP_EN_1000FDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_1000fdx;
                break;

        case MAC_PROP_EN_1000HDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_1000hdx;
                break;

        case MAC_PROP_EN_100FDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_100fdx;
                break;

        case MAC_PROP_EN_100HDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_100hdx;
                break;

        case MAC_PROP_EN_10FDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_10fdx;
                break;

        case MAC_PROP_EN_10HDX_CAP:
                *(uint8_t *)pr_val = dp->anadv_10hdx;
                break;

        case MAC_PROP_EN_100T4_CAP:
                *(uint8_t *)pr_val = dp->anadv_100t4;
                break;

        case MAC_PROP_PRIVATE:
                err = ENOTSUP;
                break;

        default:
                err = ENOTSUP;
                break;
        }

        rw_exit(&dp->dev_state_lock);
        return (err);
}

static void
usbgem_mac_ioctl(struct usbgem_dev *dp, queue_t *wq, mblk_t *mp)
{
        struct iocblk   *iocp;
        enum ioc_reply  status;

        DPRINTF(1, (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;

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

        miocnak(wq, mp, 0, EINVAL);
}

static int
usbgem_mac_xcvr_inuse(struct usbgem_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      usbgem_m_getstat(void *, uint_t, uint64_t *);
static int      usbgem_m_start(void *);
static void     usbgem_m_stop(void *);
static int      usbgem_m_setpromisc(void *, boolean_t);
static int      usbgem_m_multicst(void *, boolean_t, const uint8_t *);
static int      usbgem_m_unicst(void *, const uint8_t *);
static mblk_t   *usbgem_m_tx(void *, mblk_t *);
static void     usbgem_m_ioctl(void *, queue_t *, mblk_t *);
static int      usbgem_m_setprop(void *, const char *, mac_prop_id_t,
    uint_t, const void *);
static int      usbgem_m_getprop(void *, const char *, mac_prop_id_t,
    uint_t, void *);

static mac_callbacks_t gem_m_callbacks = {
        MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
        usbgem_m_getstat,
        usbgem_m_start,
        usbgem_m_stop,
        usbgem_m_setpromisc,
        usbgem_m_multicst,
        usbgem_m_unicst,
        usbgem_m_tx,
        NULL,
        usbgem_m_ioctl,
        NULL, /* m_getcapab */
        NULL,
        NULL,
        usbgem_m_setprop,
        usbgem_m_getprop,
        usbgem_m_propinfo,
};

static int
usbgem_m_start(void *arg)
{
        int     ret;
        int     err;
        struct usbgem_dev *dp = arg;

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

        err = EIO;

        rw_enter(&dp->dev_state_lock, RW_WRITER);
        dp->nic_state = NIC_STATE_ONLINE;

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                err = 0;
                goto x;
        }
        if (usbgem_mac_init(dp) != USB_SUCCESS) {
                goto x;
        }

        /* initialize rx filter state */
        sema_p(&dp->rxfilter_lock);
        dp->mc_count = 0;
        dp->mc_count_req = 0;

        bcopy(dp->dev_addr.ether_addr_octet,
            dp->cur_addr.ether_addr_octet, ETHERADDRL);
        dp->rxmode |= RXMODE_ENABLE;

        ret = usbgem_hal_set_rx_filter(dp);
        sema_v(&dp->rxfilter_lock);

        if (ret != USB_SUCCESS) {
                goto x;
        }

        if (dp->mii_state == MII_STATE_LINKUP) {
                /* setup media mode if the link have been up */
                if (usbgem_hal_set_media(dp) != USB_SUCCESS) {
                        goto x;
                }
                if (usbgem_mac_start(dp) != USB_SUCCESS) {
                        goto x;
                }
        }

        err = 0;
x:
        rw_exit(&dp->dev_state_lock);
        return (err);
}

static void
usbgem_m_stop(void *arg)
{
        struct usbgem_dev       *dp = arg;

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

        /* stop rx gracefully */
        rw_enter(&dp->dev_state_lock, RW_READER);
        sema_p(&dp->rxfilter_lock);
        dp->rxmode &= ~RXMODE_ENABLE;

        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                (void) usbgem_hal_set_rx_filter(dp);
        }
        sema_v(&dp->rxfilter_lock);
        rw_exit(&dp->dev_state_lock);

        /* make the nic state inactive */
        rw_enter(&dp->dev_state_lock, RW_WRITER);
        dp->nic_state = NIC_STATE_STOPPED;

        /* stop mac completely */
        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL);
        }
        rw_exit(&dp->dev_state_lock);
}

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

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

        rw_enter(&dp->dev_state_lock, RW_READER);
        if (add) {
                ret = usbgem_add_multicast(dp, ep);
        } else {
                ret = usbgem_remove_multicast(dp, ep);
        }
        rw_exit(&dp->dev_state_lock);

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

        return (err);
}

static int
usbgem_m_setpromisc(void *arg, boolean_t on)
{
        int     err;
        struct usbgem_dev       *dp = arg;

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

        rw_enter(&dp->dev_state_lock, RW_READER);

        sema_p(&dp->rxfilter_lock);
        if (on) {
                dp->rxmode |= RXMODE_PROMISC;
        } else {
                dp->rxmode &= ~RXMODE_PROMISC;
        }

        err = 0;
        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) {
                        err = EIO;
                }
        }
        sema_v(&dp->rxfilter_lock);

        rw_exit(&dp->dev_state_lock);

        return (err);
}

int
usbgem_m_getstat(void *arg, uint_t stat, uint64_t *valp)
{
        uint64_t        val;
        struct usbgem_dev       *dp = arg;
        struct usbgem_stats     *gstp = &dp->stats;

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

        rw_enter(&dp->dev_state_lock, RW_READER);
        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                rw_exit(&dp->dev_state_lock);
                return (0);
        }

        (void) usbgem_hal_get_stats(dp);
        rw_exit(&dp->dev_state_lock);

        switch (stat) {
        case MAC_STAT_IFSPEED:
                val = usbgem_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 = usbgem_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 = dp->ugc.usbgc_flow_control > FLOW_CONTROL_SYMMETRIC;
                break;

        case ETHER_STAT_CAP_PAUSE:
                val = dp->ugc.usbgc_flow_control != FLOW_CONTROL_NONE;
                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 = dp->anadv_asmpause;
                break;

        case ETHER_STAT_ADV_CAP_PAUSE:
                val = dp->anadv_pause;
                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_ASM_DIR);
                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;
#ifdef NEVER    /* it doesn't make sense */
        case ETHER_STAT_CAP_REMFAULT:
                val = B_TRUE;
                break;

        case ETHER_STAT_ADV_REMFAULT:
                val = dp->anadv_remfault;
                break;
#endif
        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
                *valp = 0;
                return (ENOTSUP);
        }

        *valp = val;

        return (0);
}

static int
usbgem_m_unicst(void *arg, const uint8_t *mac)
{
        int     err;
        struct usbgem_dev       *dp = arg;

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

        rw_enter(&dp->dev_state_lock, RW_READER);

        sema_p(&dp->rxfilter_lock);
        bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL);
        dp->rxmode |= RXMODE_ENABLE;

        err = 0;
        if (dp->mac_state != MAC_STATE_DISCONNECTED) {
                if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) {
                        err = EIO;
                }
        }
        sema_v(&dp->rxfilter_lock);
        rw_exit(&dp->dev_state_lock);

        return (err);
}

/*
 * usbgem_m_tx is used only for sending data packets into ethernet wire.
 */
static mblk_t *
usbgem_m_tx(void *arg, mblk_t *mp_head)
{
        int     limit;
        mblk_t  *mp;
        mblk_t  *nmp;
        struct usbgem_dev       *dp = arg;

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

        mp = mp_head;

        rw_enter(&dp->dev_state_lock, RW_READER);

        if (dp->mii_state != MII_STATE_LINKUP ||
            dp->mac_state != MAC_STATE_ONLINE) {
                /* some nics hate to send packets during the link is down */
                for (; mp; mp = nmp) {
                        nmp = mp->b_next;
                        mp->b_next = NULL;
                        freemsg(mp);
                }
                goto x;
        }

        ASSERT(dp->nic_state == NIC_STATE_ONLINE);

        limit = dp->tx_max_packets;
        for (; limit-- && mp; mp = nmp) {
                nmp = mp->b_next;
                mp->b_next = NULL;
                if (usbgem_send_common(dp, mp,
                    (limit == 0 && nmp) ? 1 : 0)) {
                        mp->b_next = nmp;
                        break;
                }
        }
#ifdef CONFIG_TX_LIMITER
        if (mp == mp_head) {
                /* no packets were sent, descrease allocation limit */
                mutex_enter(&dp->txlock);
                dp->tx_max_packets = max(dp->tx_max_packets - 1, 1);
                mutex_exit(&dp->txlock);
        }
#endif
x:
        rw_exit(&dp->dev_state_lock);

        return (mp);
}

static void
usbgem_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
        struct usbgem_dev       *dp = arg;

        DPRINTF(1, (CE_CONT, "!%s: %s: called",
            ((struct usbgem_dev *)arg)->name, __func__));

        rw_enter(&dp->dev_state_lock, RW_READER);
        usbgem_mac_ioctl((struct usbgem_dev *)arg, wq, mp);
        rw_exit(&dp->dev_state_lock);
}

static void
usbgem_gld3_init(struct usbgem_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 & USBGEM_VLAN) {
                macp->m_margin = VTAG_SIZE;
        }
}

/* ======================================================================== */
/*
 * .conf interface
 */
/* ======================================================================== */
void
usbgem_generate_macaddr(struct usbgem_dev *dp, uint8_t *mac)
{
        extern char     hw_serial[];
        char            *hw_serial_p;
        int             i;
        uint64_t        val;
        uint64_t        key;

        cmn_err(CE_NOTE,
            "!%s: using temp ether address,"
            " do not use this for long time",
            dp->name);

        /* prefer a fixed address for DHCP */
        hw_serial_p = &hw_serial[0];
        val = stoi(&hw_serial_p);

        key = 0;
        for (i = 0; i < USBGEM_NAME_LEN; i++) {
                if (dp->name[i] == 0) {
                        break;
                }
                key ^= dp->name[i];
        }
        key ^= ddi_get_instance(dp->dip);
        val ^= key << 32;

        /* generate a local address */
        mac[0] = 0x02;
        mac[1] = (uint8_t)(val >> 32);
        mac[2] = (uint8_t)(val >> 24);
        mac[3] = (uint8_t)(val >> 16);
        mac[4] = (uint8_t)(val >> 8);
        mac[5] = (uint8_t)val;
}

boolean_t
usbgem_get_mac_addr_conf(struct usbgem_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) {
                usbgem_generate_macaddr(dp, mac);
        }
        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);
        ddi_prop_free(valstr);

        return (B_FALSE);
}

static void
usbgem_read_conf(struct usbgem_dev *dp)
{
        int     val;

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

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

        if ((ddi_prop_exists(DDI_DEV_T_ANY, dp->dip,
            DDI_PROP_DONTPASS, "full-duplex"))) {
                dp->full_duplex =
                    usbgem_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 = usbgem_prop_get_int(dp, "speed", 0)) > 0) {
                dp->anadv_autoneg = B_FALSE;
                switch (val) {
                case 1000:
                        dp->speed = USBGEM_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 = USBGEM_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 = USBGEM_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 = usbgem_prop_get_int(dp,
            "adv_pause", dp->ugc.usbgc_flow_control & 1);
        val |= usbgem_prop_get_int(dp,
            "adv_asmpause", BOOLEAN(dp->ugc.usbgc_flow_control & 2)) << 1;
        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->ugc.usbgc_flow_control);
        }
        dp->anadv_pause = BOOLEAN(val & 1);
        dp->anadv_asmpause = BOOLEAN(val & 2);

        dp->mtu = usbgem_prop_get_int(dp, "mtu", dp->mtu);
        dp->txthr = usbgem_prop_get_int(dp, "txthr", dp->txthr);
        dp->rxthr = usbgem_prop_get_int(dp, "rxthr", dp->rxthr);
        dp->txmaxdma = usbgem_prop_get_int(dp, "txmaxdma", dp->txmaxdma);
        dp->rxmaxdma = usbgem_prop_get_int(dp, "rxmaxdma", dp->rxmaxdma);
#ifdef GEM_CONFIG_POLLING
        dp->poll_pkt_delay =
            usbgem_prop_get_int(dp, "pkt_delay", dp->poll_pkt_delay);

        dp->max_poll_interval[GEM_SPD_10] =
            usbgem_prop_get_int(dp, "max_poll_interval_10",
            dp->max_poll_interval[GEM_SPD_10]);
        dp->max_poll_interval[GEM_SPD_100] =
            usbgem_prop_get_int(dp, "max_poll_interval_100",
            dp->max_poll_interval[GEM_SPD_100]);
        dp->max_poll_interval[GEM_SPD_1000] =
            usbgem_prop_get_int(dp, "max_poll_interval_1000",
            dp->max_poll_interval[GEM_SPD_1000]);

        dp->min_poll_interval[GEM_SPD_10] =
            usbgem_prop_get_int(dp, "min_poll_interval_10",
            dp->min_poll_interval[GEM_SPD_10]);
        dp->min_poll_interval[GEM_SPD_100] =
            usbgem_prop_get_int(dp, "min_poll_interval_100",
            dp->min_poll_interval[GEM_SPD_100]);
        dp->min_poll_interval[GEM_SPD_1000] =
            usbgem_prop_get_int(dp, "min_poll_interval_1000",
            dp->min_poll_interval[GEM_SPD_1000]);
#endif
}

/*
 * attach/detatch/usb support
 */
/* ======================================================================== */
int
usbgem_ctrl_out(struct usbgem_dev *dp,
    uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len,
    void *bp, int size)
{
        mblk_t                  *data;
        usb_ctrl_setup_t        setup;
        usb_cr_t                completion_reason;
        usb_cb_flags_t          cb_flags;
        usb_flags_t             flags;
        int                     i;
        int                     ret;

        DPRINTF(4, (CE_CONT, "!%s: %s "
            "reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x "
            "bp:0x%p nic_state:%d",
            dp->name, __func__, reqt, req, val, ix, len, bp, dp->nic_state));

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                return (USB_PIPE_ERROR);
        }

        data = NULL;
        if (size > 0) {
                if ((data = allocb(size, 0)) == NULL) {
                        return (USB_FAILURE);
                }

                bcopy(bp, data->b_rptr, size);
                data->b_wptr = data->b_rptr + size;
        }

        setup.bmRequestType = reqt;
        setup.bRequest = req;
        setup.wValue = val;
        setup.wIndex = ix;
        setup.wLength = len;
        setup.attrs = 0;        /* attributes */

        for (i = usbgem_ctrl_retry; i > 0; i--) {
                completion_reason = 0;
                cb_flags = 0;

                ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp),
                    &setup, &data, &completion_reason, &cb_flags, 0);

                if (ret == USB_SUCCESS) {
                        break;
                }
                if (i == 1) {
                        cmn_err(CE_WARN,
                            "!%s: %s failed: "
                            "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x "
                            "ret:%d cr:%s(%d), cb_flags:0x%x %s",
                            dp->name, __func__, reqt, req, val, ix, len,
                            ret, usb_str_cr(completion_reason),
                            completion_reason,
                            cb_flags,
                            (i > 1) ? "retrying..." : "fatal");
                }
        }

        if (data != NULL) {
                freemsg(data);
        }

        return (ret);
}

int
usbgem_ctrl_in(struct usbgem_dev *dp,
    uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len,
    void *bp, int size)
{
        mblk_t                  *data;
        usb_ctrl_setup_t        setup;
        usb_cr_t                completion_reason;
        usb_cb_flags_t          cb_flags;
        int                     i;
        int                     ret;
        int                     reclen;

        DPRINTF(4, (CE_CONT,
            "!%s: %s:"
            " reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x"
            " bp:x%p mac_state:%d",
            dp->name, __func__, reqt, req, val, ix, len, bp, dp->mac_state));

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                return (USB_PIPE_ERROR);
        }

        data = NULL;

        setup.bmRequestType = reqt;
        setup.bRequest = req;
        setup.wValue = val;
        setup.wIndex = ix;
        setup.wLength = len;
        setup.attrs = USB_ATTRS_AUTOCLEARING;   /* XXX */

        for (i = usbgem_ctrl_retry; i > 0; i--) {
                completion_reason = 0;
                cb_flags = 0;
                ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp), &setup, &data,
                    &completion_reason, &cb_flags, 0);

                if (ret == USB_SUCCESS) {
                        reclen = msgdsize(data);
                        bcopy(data->b_rptr, bp, min(reclen, size));
                        break;
                }
                if (i == 1) {
                        cmn_err(CE_WARN,
                            "!%s: %s failed: "
                            "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x "
                            "ret:%d cr:%s(%d) cb_flags:0x%x %s",
                            dp->name, __func__,
                            reqt, req, val, ix, len,
                            ret, usb_str_cr(completion_reason),
                            completion_reason,
                            cb_flags,
                            (i > 1) ? "retrying..." : "fatal");
                }
        }

        if (data) {
                freemsg(data);
        }

        return (ret);
}

int
usbgem_ctrl_out_val(struct usbgem_dev *dp,
    uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len,
    uint32_t v)
{
        uint8_t buf[4];

        /* convert to little endian from native byte order */
        switch (len) {
        case 4:
                buf[3] = v >> 24;
                buf[2] = v >> 16;
                /* FALLTHROUGH */
        case 2:
                buf[1] = v >> 8;
                /* FALLTHROUGH */
        case 1:
                buf[0] = v;
        }

        return (usbgem_ctrl_out(dp, reqt, req, val, ix, len, buf, len));
}

int
usbgem_ctrl_in_val(struct usbgem_dev *dp,
    uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len,
    void *valp)
{
        uint8_t         buf[4];
        uint_t          v;
        int             err;

#ifdef SANITY
        bzero(buf, sizeof (buf));
#endif
        err = usbgem_ctrl_in(dp, reqt, req, val, ix, len, buf, len);
        if (err == USB_SUCCESS) {
                v = 0;
                switch (len) {
                case 4:
                        v |= buf[3] << 24;
                        v |= buf[2] << 16;
                        /* FALLTHROUGH */
                case 2:
                        v |= buf[1] << 8;
                        /* FALLTHROUGH */
                case 1:
                        v |= buf[0];
                }

                switch (len) {
                case 4:
                        *(uint32_t *)valp = v;
                        break;
                case 2:
                        *(uint16_t *)valp = v;
                        break;
                case 1:
                        *(uint8_t *)valp = v;
                        break;
                }
        }
        return (err);
}

/*
 * Attach / detach / disconnect / reconnect management
 */
static int
usbgem_open_pipes(struct usbgem_dev *dp)
{
        int                     i;
        int                     ret;
        int                     ifnum;
        int                     alt;
        usb_client_dev_data_t   *reg_data;
        usb_ep_data_t           *ep_tree_node;

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

        ifnum = dp->ugc.usbgc_ifnum;
        alt = dp->ugc.usbgc_alt;

        ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt,
            0, USB_EP_ATTR_BULK, USB_EP_DIR_IN);
        if (ep_tree_node == NULL) {
                cmn_err(CE_WARN, "!%s: %s: ep_bulkin is NULL",
                    dp->name, __func__);
                goto err;
        }
        dp->ep_bulkin = &ep_tree_node->ep_descr;

        ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt,
            0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
        if (ep_tree_node == NULL) {
                cmn_err(CE_WARN, "!%s: %s: ep_bulkout is NULL",
                    dp->name, __func__);
                goto err;
        }
        dp->ep_bulkout = &ep_tree_node->ep_descr;

        ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt,
            0, USB_EP_ATTR_INTR, USB_EP_DIR_IN);
        if (ep_tree_node) {
                dp->ep_intr = &ep_tree_node->ep_descr;
        } else {
                /* don't care */
                DPRINTF(1, (CE_CONT, "!%s: %s: ep_intr is NULL",
                    dp->name, __func__));
                dp->ep_intr = NULL;
        }

        /* XXX -- no need to open default pipe */

        /* open bulk out pipe */
        bzero(&dp->policy_bulkout, sizeof (usb_pipe_policy_t));
        dp->policy_bulkout.pp_max_async_reqs = 1;

        if ((ret = usb_pipe_open(dp->dip,
            dp->ep_bulkout, &dp->policy_bulkout, USB_FLAGS_SLEEP,
            &dp->bulkout_pipe)) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s: %s: err:%x: failed to open bulk-out pipe",
                    dp->name, __func__, ret);
                dp->bulkout_pipe = NULL;
                goto err;
        }
        DPRINTF(1, (CE_CONT, "!%s: %s: bulkout_pipe opened successfully",
            dp->name, __func__));

        /* open bulk in pipe */
        bzero(&dp->policy_bulkin, sizeof (usb_pipe_policy_t));
        dp->policy_bulkin.pp_max_async_reqs = 1;
        if ((ret = usb_pipe_open(dp->dip,
            dp->ep_bulkin, &dp->policy_bulkin, USB_FLAGS_SLEEP,
            &dp->bulkin_pipe)) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s: %s: ret:%x failed to open bulk-in pipe",
                    dp->name, __func__, ret);
                dp->bulkin_pipe = NULL;
                goto err;
        }
        DPRINTF(1, (CE_CONT, "!%s: %s: bulkin_pipe opened successfully",
            dp->name, __func__));

        if (dp->ep_intr) {
                /* open interrupt pipe */
                bzero(&dp->policy_interrupt, sizeof (usb_pipe_policy_t));
                dp->policy_interrupt.pp_max_async_reqs = 1;
                if ((ret = usb_pipe_open(dp->dip, dp->ep_intr,
                    &dp->policy_interrupt, USB_FLAGS_SLEEP,
                    &dp->intr_pipe)) != USB_SUCCESS) {
                        cmn_err(CE_WARN,
                            "!%s: %s: ret:%x failed to open interrupt pipe",
                            dp->name, __func__, ret);
                        dp->intr_pipe = NULL;
                        goto err;
                }
        }
        DPRINTF(1, (CE_CONT, "!%s: %s: intr_pipe opened successfully",
            dp->name, __func__));

        return (USB_SUCCESS);

err:
        if (dp->bulkin_pipe) {
                usb_pipe_close(dp->dip,
                    dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0);
                dp->bulkin_pipe = NULL;
        }
        if (dp->bulkout_pipe) {
                usb_pipe_close(dp->dip,
                    dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0);
                dp->bulkout_pipe = NULL;
        }
        if (dp->intr_pipe) {
                usb_pipe_close(dp->dip,
                    dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0);
                dp->intr_pipe = NULL;
        }

        return (USB_FAILURE);
}

static int
usbgem_close_pipes(struct usbgem_dev *dp)
{
        DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__));

        if (dp->intr_pipe) {
                usb_pipe_close(dp->dip,
                    dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0);
                dp->intr_pipe = NULL;
        }
        DPRINTF(1, (CE_CONT, "!%s: %s: 1", dp->name, __func__));

        ASSERT(dp->bulkin_pipe);
        usb_pipe_close(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0);
        dp->bulkin_pipe = NULL;
        DPRINTF(1, (CE_CONT, "!%s: %s: 2", dp->name, __func__));

        ASSERT(dp->bulkout_pipe);
        usb_pipe_close(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0);
        dp->bulkout_pipe = NULL;
        DPRINTF(1, (CE_CONT, "!%s: %s: 3", dp->name, __func__));

        return (USB_SUCCESS);
}

#define FREEZE_GRACEFUL         (B_TRUE)
#define FREEZE_NO_GRACEFUL      (B_FALSE)
static int
usbgem_freeze_device(struct usbgem_dev *dp, boolean_t graceful)
{
        DPRINTF(0, (CE_NOTE, "!%s: %s: called", dp->name, __func__));

        /* stop nic activity */
        (void) usbgem_mac_stop(dp, MAC_STATE_DISCONNECTED, graceful);

        /*
         * Here we free all memory resource allocated, because it will
         * cause to panic the system that we free usb_bulk_req objects
         * during the usb device is disconnected.
         */
        (void) usbgem_free_memory(dp);

        return (USB_SUCCESS);
}

static int
usbgem_disconnect_cb(dev_info_t *dip)
{
        int     ret;
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);

        cmn_err(CE_NOTE, "!%s: the usb device was disconnected (dp=%p)",
            dp->name, (void *)dp);

        /* start serialize */
        rw_enter(&dp->dev_state_lock, RW_WRITER);

        ret = usbgem_freeze_device(dp, 0);

        /* end of serialize */
        rw_exit(&dp->dev_state_lock);

        return (ret);
}

static int
usbgem_recover_device(struct usbgem_dev *dp)
{
        int     err;

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

        err = USB_SUCCESS;

        /* reinitialize the usb connection */
        usbgem_close_pipes(dp);
        if ((err = usbgem_open_pipes(dp)) != USB_SUCCESS) {
                goto x;
        }

        /* initialize nic state */
        dp->mac_state = MAC_STATE_STOPPED;
        dp->mii_state = MII_STATE_UNKNOWN;

        /* allocate memory resources again */
        if ((err = usbgem_alloc_memory(dp)) != USB_SUCCESS) {
                goto x;
        }

        /* restart nic and recover state */
        (void) usbgem_restart_nic(dp);

        usbgem_mii_init(dp);

        /* kick potentially stopped house keeping thread */
        cv_signal(&dp->link_watcher_wait_cv);
x:
        return (err);
}

static int
usbgem_reconnect_cb(dev_info_t *dip)
{
        int     err = USB_SUCCESS;
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);
        DPRINTF(0, (CE_CONT, "!%s: dp=%p", ddi_get_name(dip), dp));
#ifdef notdef
        /* check device changes after disconnect */
        if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1,
            USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) {
                cmn_err(CE_CONT,
                    "!%s: no or different device installed", dp->name);
                return (DDI_SUCCESS);
        }
#endif
        cmn_err(CE_NOTE, "%s: the usb device was reconnected", dp->name);

        /* start serialize */
        rw_enter(&dp->dev_state_lock, RW_WRITER);

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                err = usbgem_recover_device(dp);
        }

        /* end of serialize */
        rw_exit(&dp->dev_state_lock);

        return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
}

int
usbgem_suspend(dev_info_t *dip)
{
        int     err = USB_SUCCESS;
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);

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

        /* start serialize */
        rw_enter(&dp->dev_state_lock, RW_WRITER);

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                err = usbgem_freeze_device(dp, STOP_GRACEFUL);
        }

        /* end of serialize */
        rw_exit(&dp->dev_state_lock);

        return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
}

int
usbgem_resume(dev_info_t *dip)
{
        int     err = USB_SUCCESS;
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);

        DPRINTF(0, (CE_CONT, "!%s: %s: callded", dp->name, __func__));
#ifdef notdef
        /* check device changes after disconnect */
        if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1,
            USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) {
                cmn_err(CE_CONT,
                    "!%s: no or different device installed", dp->name);
                return (DDI_SUCCESS);
        }
#endif
        /* start serialize */
        rw_enter(&dp->dev_state_lock, RW_WRITER);

        if (dp->mac_state == MAC_STATE_DISCONNECTED) {
                err = usbgem_recover_device(dp);
        }

        /* end of serialize */
        rw_exit(&dp->dev_state_lock);

        return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
}

#define USBGEM_LOCAL_DATA_SIZE(gc)      \
        (sizeof (struct usbgem_dev) + USBGEM_MCALLOC)

struct usbgem_dev *
usbgem_do_attach(dev_info_t *dip,
    struct usbgem_conf *gc, void *lp, int lmsize)
{
        struct usbgem_dev       *dp;
        int                     i;
        mac_register_t          *macp = NULL;
        int                     ret;
        int                     unit;
        int                     err;

        unit = ddi_get_instance(dip);

        DPRINTF(2, (CE_CONT, "!usbgem%d: %s: called", unit, __func__));

        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                cmn_err(CE_WARN, "!gem%d: %s: mac_alloc failed",
                    unit, __func__);
                return (NULL);
        }
        /*
         * Allocate soft data structure
         */
        dp = kmem_zalloc(USBGEM_LOCAL_DATA_SIZE(gc), KM_SLEEP);

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

        dp->dip = dip;
        bcopy(gc->usbgc_name, dp->name, USBGEM_NAME_LEN);

        /*
         * register with usb service
         */
        if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "%s: %s: usb_client_attach failed",
                    dp->name, __func__);
                goto err_free_private;
        }

        if (usb_get_dev_data(dip, &dp->reg_data,
            USB_PARSE_LVL_ALL, 0) != USB_SUCCESS) {
                dp->reg_data = NULL;
                goto err_unregister_client;
        }
#ifdef USBGEM_DEBUG_LEVEL
        usb_print_descr_tree(dp->dip, dp->reg_data);
#endif

        if (usbgem_open_pipes(dp) != USB_SUCCESS) {
                /* failed to open pipes */
                cmn_err(CE_WARN, "!%s: %s: failed to open pipes",
                    dp->name, __func__);
                goto err_unregister_client;
        }

        /*
         * Initialize mutexs and condition variables
         */
        mutex_init(&dp->rxlock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&dp->txlock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&dp->rx_drain_cv, NULL, CV_DRIVER, NULL);
        cv_init(&dp->tx_drain_cv, NULL, CV_DRIVER, NULL);
        rw_init(&dp->dev_state_lock, NULL, RW_DRIVER, NULL);
        mutex_init(&dp->link_watcher_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&dp->link_watcher_wait_cv, NULL, CV_DRIVER, NULL);
        sema_init(&dp->hal_op_lock, 1, NULL, SEMA_DRIVER, NULL);
        sema_init(&dp->rxfilter_lock, 1, NULL, SEMA_DRIVER, NULL);

        /*
         * Initialize configuration
         */
        dp->ugc = *gc;

        dp->mtu = ETHERMTU;
        dp->rxmode = 0;
        dp->speed = USBGEM_SPD_10;      /* default is 10Mbps */
        dp->full_duplex = B_FALSE;      /* default is half */
        dp->flow_control = FLOW_CONTROL_NONE;

        dp->nic_state = NIC_STATE_STOPPED;
        dp->mac_state = MAC_STATE_STOPPED;
        dp->mii_state = MII_STATE_UNKNOWN;

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

        /*
         * Get media mode infomation from .conf file
         */
        usbgem_read_conf(dp);

        /* rx_buf_len depend on MTU */
        dp->rx_buf_len = MAXPKTBUF(dp) + dp->ugc.usbgc_rx_header_len;

        /*
         * Reset the chip
         */
        if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to reset the usb device",
                    dp->name, __func__);
                goto err_destroy_locks;
        }

        /*
         * HW dependant paremeter initialization
         */
        if (usbgem_hal_attach_chip(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to attach the usb device",
                    dp->name, __func__);
                goto err_destroy_locks;
        }

        /* allocate resources */
        if (usbgem_alloc_memory(dp) != USB_SUCCESS) {
                goto err_destroy_locks;
        }

        DPRINTF(0, (CE_CONT,
            "!%s: %02x:%02x:%02x:%02x:%02x:%02x",
            dp->name,
            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;

        /* pre-calculated tx timeout in second for performance */
        dp->bulkout_timeout =
            dp->ugc.usbgc_tx_timeout / drv_usectohz(1000*1000);

        usbgem_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;

        dp->mii_status_ro = 0;
        dp->mii_xstatus_ro = 0;

        if (usbgem_mii_probe(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN, "!%s: %s: mii_probe failed",
                    dp->name, __func__);
                goto err_free_memory;
        }

        /* 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);

        if (usbgem_mii_init(dp) != USB_SUCCESS) {
                cmn_err(CE_WARN, "!%s: %s: mii_init failed",
                    dp->name, __func__);
                goto err_free_memory;
        }

        /*
         * 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 (usb_register_hotplug_cbs(dip,
            usbgem_suspend, usbgem_resume) != USB_SUCCESS) {
                cmn_err(CE_WARN,
                    "!%s: %s: failed to register hotplug cbs",
                    dp->name, __func__);
                goto err_unregister_gld;
        }

        /* reset mii and start mii link watcher */
        if (usbgem_mii_start(dp) != USB_SUCCESS) {
                goto err_unregister_hotplug;
        }

        /* start tx watchdow watcher */
        if (usbgem_tx_watcher_start(dp)) {
                goto err_usbgem_mii_stop;
        }

        ddi_set_driver_private(dip, (caddr_t)dp);

        DPRINTF(2, (CE_CONT, "!%s: %s: return: success", dp->name, __func__));

        return (dp);

err_usbgem_mii_stop:
        usbgem_mii_stop(dp);

err_unregister_hotplug:
        usb_unregister_hotplug_cbs(dip);

err_unregister_gld:
        mac_unregister(dp->mh);

err_release_stats:
err_free_memory:
        usbgem_free_memory(dp);

err_destroy_locks:
        cv_destroy(&dp->tx_drain_cv);
        cv_destroy(&dp->rx_drain_cv);
        mutex_destroy(&dp->txlock);
        mutex_destroy(&dp->rxlock);
        rw_destroy(&dp->dev_state_lock);
        mutex_destroy(&dp->link_watcher_lock);
        cv_destroy(&dp->link_watcher_wait_cv);
        sema_destroy(&dp->hal_op_lock);
        sema_destroy(&dp->rxfilter_lock);

        (void) usbgem_close_pipes(dp);

err_unregister_client:
        usb_client_detach(dp->dip, dp->reg_data);

err_free_private:
        if (macp) {
                mac_free(macp);
        }

        kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(gc));

        return (NULL);
}

int
usbgem_do_detach(dev_info_t *dip)
{
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);

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

        /* unregister with hotplug service */
        usb_unregister_hotplug_cbs(dip);

        /* stop tx watchdog watcher */
        usbgem_tx_watcher_stop(dp);

        /* stop the link manager */
        usbgem_mii_stop(dp);

        /* unregister with usb service */
        (void) usbgem_free_memory(dp);
        (void) usbgem_close_pipes(dp);
        usb_client_detach(dp->dip, dp->reg_data);
        dp->reg_data = NULL;

        /* release locks and condition variables */
        mutex_destroy(&dp->txlock);
        mutex_destroy(&dp->rxlock);
        cv_destroy(&dp->tx_drain_cv);
        cv_destroy(&dp->rx_drain_cv);
        rw_destroy(&dp->dev_state_lock);
        mutex_destroy(&dp->link_watcher_lock);
        cv_destroy(&dp->link_watcher_wait_cv);
        sema_destroy(&dp->hal_op_lock);
        sema_destroy(&dp->rxfilter_lock);

        /* release basic memory resources */
        kmem_free((caddr_t)(dp->private), dp->priv_size);
        kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(&dp->ugc));

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

        return (DDI_SUCCESS);
}

int
usbgem_mod_init(struct dev_ops *dop, char *name)
{
        major_t major;
        major = ddi_name_to_major(name);
        if (major == DDI_MAJOR_T_NONE) {
                return (DDI_FAILURE);
        }
        mac_init_ops(dop, name);

        return (DDI_SUCCESS);
}

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

int
usbgem_quiesce(dev_info_t *dip)
{
        struct usbgem_dev       *dp;

        dp = USBGEM_GET_DEV(dip);

        ASSERT(dp != NULL);

        if (dp->mac_state != MAC_STATE_DISCONNECTED &&
            dp->mac_state != MAC_STATE_STOPPED) {
                if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) {
                        (void) usbgem_hal_reset_chip(dp);
                }
        }

        /* devo_quiesce() must return DDI_SUCCESS always */
        return (DDI_SUCCESS);
}