root/usr/src/uts/common/io/usb/clients/usbecm/usbecm.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * USB Ethernet Control Model
 *
 * USB-IF defines three ethernet network related specifications: EEM,
 * ECM and NCM. This driver focuses specifically on ECM compatible
 * devices. This kind of devices generally have one pair of bulk
 * endpoints for in/out packet data and one interrupt endpoint for
 * device notification.
 *
 * Devices which don't report ECM compatibility through descriptors but
 * implement the ECM functions may also bind to this driver. This driver
 * will try to find at least a bulk in endpoint and a bulk out endpoint
 * in this case. If the non-compatible devices use vendor specific data
 * format, this driver will not function.
 *
 * This driver is a normal USBA client driver. It's also a GLDv3 driver,
 * which provides the necessary interfaces the GLDv3 framework requires.
 *
 */

#include <sys/types.h>
#include <sys/strsun.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/byteorder.h>
#include <sys/usb/usba/usbai_version.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/clients/usbcdc/usb_cdc.h>
#include <sys/usb/clients/usbecm/usbecm.h>
#include <sys/mac_provider.h>
#include <sys/strsubr.h>
#include <sys/ethernet.h>
#include <sys/mac_ether.h> /* MAC_PLUGIN_IDENT_ETHER */
#include <sys/random.h> /* random_get_bytes */
#include <sys/sdt.h>    /* sdt */
#include <inet/nd.h>

/* MAC callbacks */
static int      usbecm_m_stat(void *arg, uint_t stat, uint64_t *val);
static int      usbecm_m_start(void *arg);
static void     usbecm_m_stop(void *arg);
static int      usbecm_m_unicst(void *arg, const uint8_t *macaddr);
static int      usbecm_m_multicst(void *arg, boolean_t add, const uint8_t *m);
static int      usbecm_m_promisc(void *arg, boolean_t on);
static void     usbecm_m_ioctl(void *arg, queue_t *wq, mblk_t *mp);
static mblk_t   *usbecm_m_tx(void *arg, mblk_t *mp);
static int      usbecm_m_getprop(void *arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf);
static int      usbecm_m_setprop(void *arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf);

static int      usbecm_usb_init(usbecm_state_t *ecmp);
static int      usbecm_mac_init(usbecm_state_t *ecmp);
static int      usbecm_mac_fini(usbecm_state_t *ecmp);


/* utils */
static void     generate_ether_addr(uint8_t *mac_addr);
static int      usbecm_rx_start(usbecm_state_t *ecmp);

static void     usbecm_pipe_start_polling(usbecm_state_t *ecmp);
static void     usbecm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
static void     usbecm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
static void     usbecm_parse_intr_data(usbecm_state_t *ecmp, mblk_t *data);

static int      usbecm_reconnect_event_cb(dev_info_t *dip);
static int      usbecm_disconnect_event_cb(dev_info_t *dip);

static int      usbecm_open_pipes(usbecm_state_t *ecmp);
static void     usbecm_close_pipes(usbecm_state_t *ecmp);

static int      usbecm_ctrl_read(usbecm_state_t *ecmp, uchar_t request,
    uint16_t value, mblk_t **data, int len);
static int      usbecm_ctrl_write(usbecm_state_t *ecmp, uchar_t request,
    uint16_t value, mblk_t **data);
static int      usbecm_send_data(usbecm_state_t *ecmp, mblk_t *data);
static int      usbecm_send_zero_data(usbecm_state_t *ecmp);
static int      usbecm_get_statistics(usbecm_state_t *ecmp, uint32_t fs,
    uint32_t *stat_data);

static int      usbecm_create_pm_components(usbecm_state_t *ecmp);
static void     usbecm_destroy_pm_components(usbecm_state_t *ecmp);
static int      usbecm_power(dev_info_t *dip, int comp, int level);
static void     usbecm_pm_set_busy(usbecm_state_t *ecmp);
static void     usbecm_pm_set_idle(usbecm_state_t *ecmp);

static int      usbecm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int      usbecm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

static int      usbecm_suspend(usbecm_state_t *ecmp);
static int      usbecm_resume(usbecm_state_t *ecmp);
static int      usbecm_restore_device_state(usbecm_state_t *ecmp);
static void     usbecm_cleanup(usbecm_state_t *ecmp);

/* Driver identification */
static char usbecm_ident[] = "usbecm 1.0";

/* Global state pointer for managing per-device soft states */
void *usbecm_statep;

/* print levels */
static uint_t   usbecm_errlevel = USB_LOG_L3;
static uint_t   usbecm_errmask = 0xffffffff;
static uint_t   usbecm_instance_debug = (uint_t)-1;

/*
 * to prevent upper layers packet flood from exhausting system
 * resources(USBA does not set limitation of requests on a pipe),
 * we set a upper limit for the transfer queue length.
 */
static  int     usbecm_tx_max = 32;

#define SUN_SP_VENDOR_ID        0x0430
#define SUN_SP_PRODUCT_ID       0xa4a2

static uint8_t  usbecm_broadcast[ETHERADDRL] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

static usb_event_t usbecm_events = {
        usbecm_disconnect_event_cb,
        usbecm_reconnect_event_cb,
        NULL, NULL
};

#define ECM_DS_OP_VALID(op) ((ecmp->ecm_ds_ops) && (ecmp->ecm_ds_ops->op))

/*
 * MAC Call Back entries
 */
static mac_callbacks_t usbecm_m_callbacks = {
        MC_IOCTL | MC_SETPROP | MC_GETPROP,
        usbecm_m_stat,          /* Get the value of a statistic */
        usbecm_m_start,         /* Start the device */
        usbecm_m_stop,          /* Stop the device */
        usbecm_m_promisc,       /* Enable or disable promiscuous mode */
        usbecm_m_multicst,      /* Enable or disable a multicast addr */
        usbecm_m_unicst,        /* Set the unicast MAC address */
        usbecm_m_tx,            /* Transmit a packet */
        NULL,
        usbecm_m_ioctl,         /* Process an unknown ioctl */
        NULL,                   /* mc_getcapab */
        NULL,                   /* mc_open */
        NULL,                   /* mc_close */
        usbecm_m_setprop,       /* mc_setprop */
        usbecm_m_getprop,       /* mc_getprop */
        NULL
};


/*
 *  Module Loading Data & Entry Points
 *     Can't use DDI_DEFINE_STREAM_OPS, since it does
 *     not provide devo_power entry.
 */
static struct cb_ops cb_usbecm = {
        nulldev,                /* cb_open */
        nulldev,                /* cb_close */
        nodev,                  /* cb_strategy */
        nodev,                  /* cb_print */
        nodev,                  /* cb_dump */
        nodev,                  /* cb_read */
        nodev,                  /* cb_write */
        nodev,                  /* cb_ioctl */
        nodev,                  /* cb_devmap */
        nodev,                  /* cb_mmap */
        nodev,                  /* cb_segmap */
        nochpoll,               /* cb_chpoll */
        ddi_prop_op,            /* cb_prop_op */
        NULL,                   /* cb_stream */
        D_MP,                   /* cb_flag */
        CB_REV,                 /* cb_rev */
        nodev,                  /* cb_aread */
        nodev,                  /* cb_awrite */
};

static struct dev_ops usbecm_devops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        NULL,                   /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        usbecm_attach,          /* devo_attach */
        usbecm_detach,          /* devo_detach */
        nodev,                  /* devo_reset */
        &(cb_usbecm),           /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        usbecm_power,           /* devo_power */
        ddi_quiesce_not_needed  /* devo_quiesce */
};

static struct modldrv usbecm_modldrv = {
        &mod_driverops,         /* drv_modops */
        usbecm_ident,           /* drv_linkinfo */
        &usbecm_devops          /* drv_dev_ops */
};

static struct modlinkage usbecm_ml = {
        MODREV_1,               /* ml_rev */
        &usbecm_modldrv, NULL   /* ml_linkage */
};


/*
 * Device operations
 */
/*
 * Binding the driver to a device.
 *
 * Concurrency: Until usbecm_attach() returns with success,
 * the only other entry point that can be executed is getinfo().
 * Thus no locking here yet.
 */
static int
usbecm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        char strbuf[32];
        int instance;
        int err;
        usbecm_state_t *ecmp = NULL;

        switch (cmd) {
        case DDI_ATTACH:
                break;

        case DDI_RESUME:
                ecmp = (usbecm_state_t *)ddi_get_soft_state(usbecm_statep,
                    ddi_get_instance(dip));

                (void) usbecm_resume(ecmp);

                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }

        instance = ddi_get_instance(dip);

        if (ddi_soft_state_zalloc(usbecm_statep, instance) == DDI_SUCCESS) {
                ecmp = ddi_get_soft_state(usbecm_statep, instance);
        }
        if (ecmp == NULL) {
                cmn_err(CE_WARN, "usbecm_attach: fail to get soft state");

                return (DDI_FAILURE);
        }

        ecmp->ecm_dip = dip;

        ecmp->ecm_lh = usb_alloc_log_hdl(ecmp->ecm_dip, "usbecm",
            &usbecm_errlevel, &usbecm_errmask, &usbecm_instance_debug, 0);

        if (usbecm_usb_init(ecmp) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_attach: failed to init usb");

                goto fail;
        }

        if (ECM_DS_OP_VALID(ecm_ds_init)) {
                if (ecmp->ecm_ds_ops->ecm_ds_init(ecmp) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                            "usbecm_attach: failed to init DS");

                        goto fail;
                }
        }

        if (usbecm_mac_init(ecmp) != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_attach: failed to init mac");

                goto fail;
        }
        ecmp->ecm_init_flags |= USBECM_INIT_MAC;

        /*
         * Create minor node of type usb_net. Not necessary to create
         * DDI_NT_NET since it's created in mac_register(). Otherwise,
         * system will panic.
         */
        (void) snprintf(strbuf, sizeof (strbuf), "usbecm%d", instance);
        err = ddi_create_minor_node(dip, strbuf, S_IFCHR,
            instance + 1, "usb_net", 0);
        if (err != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "failed to create minor node");

                goto fail;
        }

        /* always busy. May change to a more precise PM in future */
        usbecm_pm_set_busy(ecmp);

        ddi_report_dev(dip);

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_attach: succeed!");

        return (DDI_SUCCESS);

fail:
        USB_DPRINTF_L1(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_attach: Attach fail");

        usbecm_cleanup(ecmp);
        ddi_prop_remove_all(dip);
        ddi_soft_state_free(usbecm_statep, instance);

        return (DDI_FAILURE);

}


/*
 * Detach the driver from a device.
 *
 * Concurrency: Will be called only after a successful attach
 * (and not concurrently).
 */
static int
usbecm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        usbecm_state_t *ecmp = NULL;
        int instance;

        instance = ddi_get_instance(dip);
        ecmp = ddi_get_soft_state(usbecm_statep, instance);
        ASSERT(ecmp != NULL);

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_detach: entry ");

        switch (cmd) {
        case DDI_DETACH:
                break;

        case DDI_SUSPEND:

                return (usbecm_suspend(ecmp));

        default:
                return (DDI_FAILURE);
        }

        usbecm_pm_set_idle(ecmp);

        if (ECM_DS_OP_VALID(ecm_ds_fini)) {
                if (ecmp->ecm_ds_ops->ecm_ds_fini(ecmp) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                            "usbecm_detach: deinitialize DS fail!");

                        return (DDI_FAILURE);
                }
        }

        if (usbecm_mac_fini(ecmp) != 0) {

                return (DDI_FAILURE);
        }

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_detach: exit");

        usbecm_cleanup(ecmp);
        ddi_soft_state_free(usbecm_statep, instance);

        return (DDI_SUCCESS);
}


/*
 * Mac Call Back functions
 */

/*
 * Read device statistic information.
 */
static int
usbecm_m_stat(void *arg, uint_t stat, uint64_t *val)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        uint32_t        stats;
        int             rval;
        uint32_t        fs;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_stat: entry, stat=%d", stat);

        /*
         * Some of the stats are MII specific. We try to
         * resolve all the statistics we understand. If
         * the usb device can't provide it, return ENOTSUP.
         */
        switch (stat) {
        case MAC_STAT_IFSPEED:
                /* return link speed */
                mutex_enter(&ecmp->ecm_mutex);
                if (ecmp->ecm_stat.es_downspeed) {
                        *val = ecmp->ecm_stat.es_downspeed;
                } else {
                        *val = 10 * 1000000ull; /* set a default value */
                }
                mutex_exit(&ecmp->ecm_mutex);

                return (0);
        case ETHER_STAT_LINK_DUPLEX:
                *val = LINK_DUPLEX_FULL;

                return (0);

        case ETHER_STAT_SQE_ERRORS:
                *val = 0;

                return (0);

        /* Map MAC/Ether stats to ECM statistics */
        case MAC_STAT_NORCVBUF:
                fs = ECM_RCV_NO_BUFFER;

                break;
        case MAC_STAT_NOXMTBUF:
                fs = ECM_XMIT_ERROR;

                break;
        case MAC_STAT_IERRORS:
                fs = ECM_RCV_ERROR;

                break;
        case MAC_STAT_OERRORS:
                fs = ECM_XMIT_ERROR;

                break;
        case MAC_STAT_RBYTES:
                fs = ECM_DIRECTED_BYTES_RCV;

                break;
        case MAC_STAT_IPACKETS:
                fs = ECM_RCV_OK; /* frames */

                break;
        case MAC_STAT_OBYTES:
                fs = ECM_DIRECTED_BYTES_XMIT;

                break;
        case MAC_STAT_OPACKETS:
                fs = ECM_XMIT_OK; /* frames */

                break;
        case MAC_STAT_MULTIRCV:
                fs = ECM_MULTICAST_FRAMES_RCV;

                break;
        case MAC_STAT_BRDCSTRCV:
                fs = ECM_BROADCAST_FRAMES_RCV;

                break;
        case MAC_STAT_MULTIXMT:
                fs = ECM_MULTICAST_FRAMES_XMIT;

                break;
        case MAC_STAT_BRDCSTXMT:
                fs = ECM_BROADCAST_FRAMES_XMIT;

                break;
        case MAC_STAT_COLLISIONS:
                fs = ECM_XMIT_MAX_COLLISIONS;

                break;
        case MAC_STAT_OVERFLOWS:
                fs = ECM_RCV_OVERRUN;

                break;
        case MAC_STAT_UNDERFLOWS:
                fs = ECM_XMIT_UNDERRUN;

                break;
        case ETHER_STAT_FCS_ERRORS:
                fs = ECM_RCV_CRC_ERROR;

                break;
        case ETHER_STAT_ALIGN_ERRORS:
                fs = ECM_RCV_ERROR_ALIGNMENT;

                break;
        case ETHER_STAT_DEFER_XMTS:
                fs = ECM_XMIT_DEFERRED;

                break;
        case ETHER_STAT_FIRST_COLLISIONS:
                fs = ECM_XMIT_ONE_COLLISION;

                break;
        case ETHER_STAT_MULTI_COLLISIONS:
                fs = ECM_XMIT_MORE_COLLISIONS;

                break;
        case ETHER_STAT_TX_LATE_COLLISIONS:
                fs = ECM_XMIT_LATE_COLLISIONS;

                break;

        default:
                return (ENOTSUP);
        }

        /*
         * we need to access device to get required stats,
         * so check device state first
         */
        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_stat: device not ONLINE");

                mutex_exit(&ecmp->ecm_mutex);

                return (EIO);
        }
        mutex_exit(&ecmp->ecm_mutex);

        rval = usbecm_get_statistics(ecmp,
            ECM_STAT_SELECTOR(fs), &stats);
        if (rval != USB_SUCCESS) {
                mutex_enter(&ecmp->ecm_mutex);
                switch (stat) {
                case MAC_STAT_IERRORS:
                        *val = ecmp->ecm_stat.es_ierrors;

                        break;
                case MAC_STAT_OERRORS:
                        *val = ecmp->ecm_stat.es_oerrors;

                        break;
                case MAC_STAT_RBYTES:
                        *val = ecmp->ecm_stat.es_ibytes;

                        break;
                case MAC_STAT_IPACKETS:
                        *val = ecmp->ecm_stat.es_ipackets;

                        break;
                case MAC_STAT_OBYTES:
                        *val = ecmp->ecm_stat.es_obytes;

                        break;
                case MAC_STAT_OPACKETS:
                        *val = ecmp->ecm_stat.es_opackets;

                        break;
                case MAC_STAT_MULTIRCV:
                        *val = ecmp->ecm_stat.es_multircv;

                        break;
                case MAC_STAT_MULTIXMT:
                        *val = ecmp->ecm_stat.es_multixmt;

                        break;
                case MAC_STAT_BRDCSTRCV:
                        *val = ecmp->ecm_stat.es_brdcstrcv;

                        break;
                case MAC_STAT_BRDCSTXMT:
                        *val = ecmp->ecm_stat.es_brdcstxmt;

                        break;
                case ETHER_STAT_MACXMT_ERRORS:
                        *val = ecmp->ecm_stat.es_macxmt_err;
                        break;
                default:
                        *val = 0;

                        break;
                }
                mutex_exit(&ecmp->ecm_mutex);
        } else {
                *val = stats;
        }

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_stat: end");

        return (0);
}


/*
 * Start the device:
 *      - Set proper altsettings of the data interface
 *      - Open status and data endpoints
 *      - Start status polling
 *      - Get bulk-in ep ready to receive data from ethernet
 *
 * Concurrency: Presumably fully concurrent, must lock.
 */
static int
usbecm_m_start(void *arg)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        int rval;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_start: entry");

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);
        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_start: device not online");
                rval = ENODEV;
                mutex_exit(&ecmp->ecm_mutex);

                goto fail;
        }
        mutex_exit(&ecmp->ecm_mutex);

        if (usbecm_open_pipes(ecmp) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_start: open pipes fail");
                rval = EIO;

                goto fail;
        }

        mutex_enter(&ecmp->ecm_mutex);
        if (usbecm_rx_start(ecmp) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_start: fail to start_rx");
                mutex_exit(&ecmp->ecm_mutex);
                rval = EIO;

                goto fail;
        }
        ecmp->ecm_mac_state = USBECM_MAC_STARTED;
        mutex_exit(&ecmp->ecm_mutex);

        /* set the device to receive all multicast/broadcast pkts */
        rval = usbecm_ctrl_write(ecmp, CDC_ECM_SET_ETH_PKT_FLT,
            CDC_ECM_PKT_TYPE_DIRECTED | CDC_ECM_PKT_TYPE_ALL_MCAST |
            CDC_ECM_PKT_TYPE_BCAST, NULL);
        if (rval != USB_SUCCESS) {
                USB_DPRINTF_L3(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_start: set packet filters fail,"
                    " rval=%d, continue", rval);
        }

        if (ECM_DS_OP_VALID(ecm_ds_start)) {
                if (ecmp->ecm_ds_ops->ecm_ds_start(ecmp) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                            "usbecm_m_start: Can't start hardware");

                        goto fail;
                }
        }

        usb_release_access(ecmp->ecm_ser_acc);

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_start: end");

        /*
         * To mark the link as RUNNING.
         *
         * ECM spec doesn't provide a way for host to get the status
         * of the physical link initiatively. Only the device can
         * report the link state through interrupt endpoints.
         */
        mac_link_update(ecmp->ecm_mh, LINK_STATE_UP);
        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_stat.es_linkstate = LINK_STATE_UP;
        mutex_exit(&ecmp->ecm_mutex);

        return (DDI_SUCCESS);
fail:
        usb_release_access(ecmp->ecm_ser_acc);

        return (rval);
}

/*
 * Stop the device.
 */
static void
usbecm_m_stop(void *arg)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_stop: entry");

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);
        if (ECM_DS_OP_VALID(ecm_ds_stop)) {
                if (ecmp->ecm_ds_ops->ecm_ds_stop(ecmp) != USB_SUCCESS) {
                        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                            "usbecm_m_stop: fail to stop hardware");
                }
        }

        usbecm_close_pipes(ecmp);
        usb_release_access(ecmp->ecm_ser_acc);

        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_mac_state = USBECM_MAC_STOPPED;
        mutex_exit(&ecmp->ecm_mutex);

        mac_link_update(ecmp->ecm_mh, LINK_STATE_DOWN);
        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_stat.es_linkstate = LINK_STATE_DOWN;
        mutex_exit(&ecmp->ecm_mutex);

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_stop: end");
}

/*
 * Change the MAC address of the device.
 */
/*ARGSUSED*/
static int
usbecm_m_unicst(void *arg, const uint8_t *macaddr)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        uint16_t        filter;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_unicst: entry");

        /*
         * The device doesn't support to set a different MAC addr.
         * Hence, it's not necessary to stop the device first if
         * the mac addresses are identical. And we just set unicast
         * filter only.
         */
        if (bcmp(macaddr, ecmp->ecm_srcaddr, ETHERADDRL) != 0) {
                USB_DPRINTF_L3(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_unicst: not supported to set a"
                    " different MAC addr");

                return (DDI_FAILURE);
        }
        mutex_enter(&ecmp->ecm_mutex);
        filter = ecmp->ecm_pkt_flt |= CDC_ECM_PKT_TYPE_DIRECTED;
        mutex_exit(&ecmp->ecm_mutex);

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);
        rval = usbecm_ctrl_write(ecmp, CDC_ECM_SET_ETH_PKT_FLT,
            filter, NULL);
        usb_release_access(ecmp->ecm_ser_acc);

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_unicst: rval = %d", rval);

        /* some devices may not support this request, we just return success */
        return (DDI_SUCCESS);
}

/*
 * Enable/disable multicast.
 */
/*ARGSUSED*/
static int
usbecm_m_multicst(void *arg, boolean_t add, const uint8_t *m)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        uint16_t        filter;
        int     rval = 0;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_multicst: entry");
        mutex_enter(&ecmp->ecm_mutex);

        /*
         * To simplify the implementation, we support switching
         * all multicast on/off feature only
         */
        if (add == B_TRUE) {
                ecmp->ecm_pkt_flt |= CDC_ECM_PKT_TYPE_ALL_MCAST;
        } else {
                ecmp->ecm_pkt_flt &= ~CDC_ECM_PKT_TYPE_ALL_MCAST;
        }
        filter = ecmp->ecm_pkt_flt;
        mutex_exit(&ecmp->ecm_mutex);

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);
        if (ecmp->ecm_compatibility &&
            (ecmp->ecm_desc.wNumberMCFilters & 0x7F)) {
        /* Device supports SetEthernetMulticastFilters request */
                rval = usbecm_ctrl_write(ecmp, CDC_ECM_SET_ETH_PKT_FLT,
                    filter, NULL);
                USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_multicst: rval = %d", rval);
        }
        usb_release_access(ecmp->ecm_ser_acc);

        /* some devices may not support this request, we just return success */
        return (DDI_SUCCESS);
}

/*
 * Enable/disable promiscuous mode.
 */
static int
usbecm_m_promisc(void *arg, boolean_t on)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        uint16_t        filter;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_promisc: entry");

        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_promisc: device not ONLINE");
                mutex_exit(&ecmp->ecm_mutex);

                return (DDI_FAILURE);
        }


        if (on == B_TRUE) {
                ecmp->ecm_pkt_flt |= CDC_ECM_PKT_TYPE_PROMISC;
        } else {
                ecmp->ecm_pkt_flt &= ~CDC_ECM_PKT_TYPE_PROMISC;
        }
        filter = ecmp->ecm_pkt_flt;
        mutex_exit(&ecmp->ecm_mutex);

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);
        rval = usbecm_ctrl_write(ecmp, CDC_ECM_SET_ETH_PKT_FLT,
            filter, NULL);
        usb_release_access(ecmp->ecm_ser_acc);

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_promisc: rval=%d", rval);

        /*
         * devices may not support this request, we just
         * return success to let upper layer to do further
         * operation.
         */
        return (DDI_SUCCESS);
}

/*
 * IOCTL request: Does not do anything. Will be enhanced
 *      in future.
 */
static void
usbecm_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        struct iocblk   *iocp;
        int cmd;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_ioctl: entry");

        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_ioctl: device not ONLINE");
                mutex_exit(&ecmp->ecm_mutex);

                miocnak(wq, mp, 0, EIO);

                return;
        }
        mutex_exit(&ecmp->ecm_mutex);

        iocp = (void *)mp->b_rptr;
        iocp->ioc_error = 0;
        cmd = iocp->ioc_cmd;

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);

        switch (cmd) {
        default:
                USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "unknown cmd 0x%x", cmd);
                usb_release_access(ecmp->ecm_ser_acc);
                miocnak(wq, mp, 0, EINVAL);

                return;
        }
}

/*
 * callback functions for get/set properties
 *      Does not do anything. Will be enhanced to
 *      support set/get properties in future.
 */
/*ARGSUSED*/
static int
usbecm_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
    uint_t wldp_length, const void *wldp_buf)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        int err = ENOTSUP;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_setprop: entry");

        return (err);
}

/*ARGSUSED*/
static int usbecm_m_getprop(void *arg, const char *pr_name,
    mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        int err = ENOTSUP;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_getprop: entry");

        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                mutex_exit(&ecmp->ecm_mutex);

                return (EIO);
        }
        mutex_exit(&ecmp->ecm_mutex);

        return (err);
}

/*
 * Transmit a data frame.
 */
static mblk_t *
usbecm_m_tx(void *arg, mblk_t *mp)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)arg;
        mblk_t *next;
        int count = 0;

        ASSERT(mp != NULL);

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_m_tx: entry");

        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_tx: device not ONLINE");
                mutex_exit(&ecmp->ecm_mutex);

                return (mp);
        }
        mutex_exit(&ecmp->ecm_mutex);

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);

        /*
         * To make use of the device maximum capability,
         * concatenate msg blocks in a msg to ETHERMAX length.
         */
        while (mp != NULL) {
                next = mp->b_next;
                mp->b_next = NULL;

                if (usbecm_send_data(ecmp, mp) != DDI_SUCCESS) {
                        USB_DPRINTF_L3(PRINT_MASK_OPS, ecmp->ecm_lh,
                            "usbecm_m_tx: send data fail");

                        /* failure statistics */
                        mutex_enter(&ecmp->ecm_mutex);
                        ecmp->ecm_stat.es_oerrors++;
                        mutex_exit(&ecmp->ecm_mutex);

                        mp->b_next = next;

                        break;
                }

                /*
                 * To make it simple, we count all packets, no matter
                 * the device supports ethernet statistics or not.
                 */
                mutex_enter(&ecmp->ecm_mutex);
                ecmp->ecm_stat.es_opackets++;
                ecmp->ecm_stat.es_obytes += MBLKL(mp);
                mutex_exit(&ecmp->ecm_mutex);

                freemsg(mp); /* free this msg upon success */

                mp = next;
                USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_m_tx: %d msgs processed", ++count);
        }

        usb_release_access(ecmp->ecm_ser_acc);

        return (mp);
}

/*
 * usbecm_bulkin_cb:
 *      Bulk In regular and exeception callback;
 *      USBA framework will call this callback
 *      after deal with bulkin request.
 */
/*ARGSUSED*/
static void
usbecm_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
        usbecm_state_t  *ecmp = (usbecm_state_t *)req->bulk_client_private;
        mblk_t          *data, *mp;
        int             data_len;
        int             max_pkt_size = ecmp->ecm_bulkin_sz;

        data = req->bulk_data;
        data_len = (data) ? MBLKL(data) : 0;

        ASSERT(data->b_cont == NULL);

        mutex_enter(&ecmp->ecm_mutex);

        USB_DPRINTF_L4(PRINT_MASK_CB, ecmp->ecm_lh,
            "usbecm_bulkin_cb: state=%d, len=%d", ecmp->ecm_bulkin_state,
            data_len);

        /*
         * may receive a zero length packet according
         * to USB short packet semantics
         */
        if ((ecmp->ecm_dev_state == USB_DEV_ONLINE) &&
            (req->bulk_completion_reason == USB_CR_OK)) {
                if (data_len) {
                        if (ecmp->ecm_rcv_queue == NULL) {
                                ecmp->ecm_rcv_queue = data;
                        } else {
                                if ((msgsize(ecmp->ecm_rcv_queue) + data_len)
                                    > ETHERMAX) {
                                /*
                                 * Exceed the ethernet maximum length, we think
                                 * something is wrong with this frame and hence
                                 * free older data. Accept new data instead.
                                 */
                                        freemsg(ecmp->ecm_rcv_queue);
                                        ecmp->ecm_rcv_queue = data;
                                } else {
                                        linkb(ecmp->ecm_rcv_queue, data);
                                }
                        }
                } else {
                /*
                 * Do not put zero length packet to receive queue.
                 * Otherwise, msgpullup will dupmsg() a zero length
                 * mblk, which will cause memleaks.
                 */
                        freemsg(data);
                }

                /*
                 * ECM V1.2, section 3.3.1, a short(including zero length)
                 * packet signifies end of frame. We can submit this frame
                 * to upper layer now.
                 */
                if ((data_len < max_pkt_size) &&
                    (msgsize(ecmp->ecm_rcv_queue) > 0)) {
                        mp = msgpullup(ecmp->ecm_rcv_queue, -1);
                        freemsg(ecmp->ecm_rcv_queue);
                        ecmp->ecm_rcv_queue = NULL;

                        ecmp->ecm_stat.es_ipackets++;
                        ecmp->ecm_stat.es_ibytes += msgsize(mp);
                        if (mp && (mp->b_rptr[0] & 0x01)) {
                                if (bcmp(mp->b_rptr, usbecm_broadcast,
                                    ETHERADDRL) != 0) {
                                        ecmp->ecm_stat.es_multircv++;
                                } else {
                                        ecmp->ecm_stat.es_brdcstrcv++;
                                }
                        }

                        if (mp) {
                                mutex_exit(&ecmp->ecm_mutex);
                                mac_rx(ecmp->ecm_mh, NULL, mp);
                                mutex_enter(&ecmp->ecm_mutex);
                        }
                }

                /* prevent USBA from freeing data along with the request */
                req->bulk_data = NULL;
        } else if (req->bulk_completion_reason != USB_CR_OK) {
                ecmp->ecm_stat.es_ierrors++;
        }
        mutex_exit(&ecmp->ecm_mutex);

        usb_free_bulk_req(req);

        /* receive more */
        mutex_enter(&ecmp->ecm_mutex);
        if (((ecmp->ecm_bulkin_state == USBECM_PIPE_BUSY) ||
            (ecmp->ecm_bulkin_state == USBECM_PIPE_IDLE)) &&
            (ecmp->ecm_dev_state == USB_DEV_ONLINE)) {
                if (usbecm_rx_start(ecmp) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                            "usbecm_bulkin_cb: restart rx fail "
                            "ecmp_state = %d", ecmp->ecm_bulkin_state);
                }
        } else if (ecmp->ecm_bulkin_state == USBECM_PIPE_BUSY) {
                ecmp->ecm_bulkin_state = USBECM_PIPE_IDLE;
        }
        mutex_exit(&ecmp->ecm_mutex);
}

/*
 * usbsecm_rx_start:
 *      start data receipt
 */
static int
usbecm_rx_start(usbecm_state_t *ecmp)
{
        usb_bulk_req_t  *br;
        int             rval = USB_FAILURE;
        int             data_len;

        ASSERT(mutex_owned(&ecmp->ecm_mutex));

        DTRACE_PROBE2(usbecm_rx__start, int, ecmp->ecm_xfer_sz,
            int, ecmp->ecm_bulkin_sz);

        ecmp->ecm_bulkin_state = USBECM_PIPE_BUSY;
        data_len = ecmp->ecm_bulkin_sz;

        mutex_exit(&ecmp->ecm_mutex);
        br = usb_alloc_bulk_req(ecmp->ecm_dip, data_len, USB_FLAGS_SLEEP);
        if (br == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_rx_start: allocate bulk request failed");

                mutex_enter(&ecmp->ecm_mutex);

                return (USB_FAILURE);
        }
        /* initialize bulk in request. */
        br->bulk_len = data_len;
        br->bulk_timeout = 0;
        br->bulk_cb = usbecm_bulkin_cb;
        br->bulk_exc_cb = usbecm_bulkin_cb;
        br->bulk_client_private = (usb_opaque_t)ecmp;
        br->bulk_attributes = USB_ATTRS_AUTOCLEARING
            | USB_ATTRS_SHORT_XFER_OK;

        rval = usb_pipe_bulk_xfer(ecmp->ecm_bulkin_ph, br, 0);
        mutex_enter(&ecmp->ecm_mutex);
        if (rval != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_rx_start: bulk transfer failed %d", rval);
                usb_free_bulk_req(br);
                ecmp->ecm_bulkin_state = USBECM_PIPE_IDLE;
        }

        return (rval);
}

/*
 * usbecm_bulkout_cb:
 *      Bulk Out regular and exeception callback;
 *      USBA framework will call this callback function
 *      after deal with bulkout request.
 */
/*ARGSUSED*/
static void
usbecm_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)req->bulk_client_private;
        int             data_len;
        boolean_t       need_update = B_FALSE;

        data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0;

        USB_DPRINTF_L4(PRINT_MASK_CB, ecmp->ecm_lh,
            "usbecm_bulkout_cb: data_len = %d, cr=%d", data_len,
            req->bulk_completion_reason);

        mutex_enter(&ecmp->ecm_mutex);
        if ((data_len > 0) && (ecmp->ecm_tx_cnt > 0)) {
                if (ecmp->ecm_tx_cnt == usbecm_tx_max) {
                        need_update = B_TRUE;
                }
                ecmp->ecm_tx_cnt--;
        }
        mutex_exit(&ecmp->ecm_mutex);

        if (req->bulk_completion_reason && (data_len > 0)) {
                mutex_enter(&ecmp->ecm_mutex);
                ecmp->ecm_stat.es_oerrors++;
                mutex_exit(&ecmp->ecm_mutex);

                need_update = B_TRUE;
        }

        /*
         * notify MAC layer to retransfer the failed packet
         * Or notity MAC that we have more buffer now.
         */
        if (need_update) {
                mac_tx_update(ecmp->ecm_mh);
        }

        usb_free_bulk_req(req);
}

static int
usbecm_send_data(usbecm_state_t *ecmp, mblk_t *data)
{
        usb_bulk_req_t  *br;
        int             rval = USB_FAILURE;
        int             data_len = MBLKL(data);
        int             max_pkt_size;
        mblk_t          *new_data = NULL;
        int             new_data_len = 0;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_send_data: length = %d, total len=%d",
            data_len, (int)msgdsize(data));

        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_tx_cnt >= usbecm_tx_max) {
                USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: (%d) exceeds TX max queue length",
                    ecmp->ecm_tx_cnt);
                mutex_exit(&ecmp->ecm_mutex);

                return (USB_FAILURE);
        }
        mutex_exit(&ecmp->ecm_mutex);

        data_len = msgsize(data);
        if (data_len > ETHERMAX) {
                mutex_enter(&ecmp->ecm_mutex);
                ecmp->ecm_stat.es_macxmt_err++;
                mutex_exit(&ecmp->ecm_mutex);

                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: packet too long, %d", data_len);

                return (USB_FAILURE);
        }

        if (data_len < ETHERMIN) {
                mblk_t *tmp;

                USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: short packet, padding to ETHERMIN");

                new_data_len = ETHERMIN;
                if ((new_data = allocb(new_data_len, 0)) == NULL) {
                        USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                            "usbecm_send_data: fail to allocb");

                        return (USB_FAILURE);
                }
                bzero(new_data->b_wptr, new_data_len);
                for (tmp = data; tmp != NULL; tmp = tmp->b_cont) {
                        bcopy(tmp->b_rptr, new_data->b_wptr, MBLKL(tmp));
                        new_data->b_wptr += MBLKL(tmp);
                }

                new_data->b_wptr = new_data->b_rptr + new_data_len;
        }

        br = usb_alloc_bulk_req(ecmp->ecm_dip, 0, USB_FLAGS_SLEEP);
        if (br == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: alloc req failed.");

                return (USB_FAILURE);
        }

        /* initialize the bulk out request */
        if (new_data) {
                br->bulk_data = msgpullup(new_data, -1); /* msg allocated! */
                br->bulk_len = new_data_len;
        } else {
                br->bulk_data = msgpullup(data, -1); /* msg allocated! */
                br->bulk_len = data_len;
        }

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_send_data: bulk_len = %d", br->bulk_len);

        br->bulk_timeout = USBECM_BULKOUT_TIMEOUT;
        br->bulk_cb = usbecm_bulkout_cb;
        br->bulk_exc_cb = usbecm_bulkout_cb;
        br->bulk_client_private = (usb_opaque_t)ecmp;
        br->bulk_attributes = USB_ATTRS_AUTOCLEARING;

        if (br->bulk_data != NULL) {
                if (br->bulk_data->b_rptr[0] & 0x01) {
                        mutex_enter(&ecmp->ecm_mutex);
                        if (bcmp(br->bulk_data->b_rptr, usbecm_broadcast,
                            ETHERADDRL) != 0) {
                                ecmp->ecm_stat.es_multixmt++;
                        } else {
                                ecmp->ecm_stat.es_brdcstxmt++;
                        }
                        mutex_exit(&ecmp->ecm_mutex);
                }
                rval = usb_pipe_bulk_xfer(ecmp->ecm_bulkout_ph, br, 0);
        }

        if (rval != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: Send Data failed.");

                /*
                 * br->bulk_data should be freed because we allocated
                 * it in this function.
                 */
                usb_free_bulk_req(br);

        } else {
                mutex_enter(&ecmp->ecm_mutex);
                ecmp->ecm_tx_cnt++;
                mutex_exit(&ecmp->ecm_mutex);

                /*
                 * ECM V1.2, section 3.3.1, a short(including zero length)
                 * packet signifies end of frame. We should send a zero length
                 * packet to device if the total data lenght is multiple of
                 * bulkout endpoint's max packet size.
                 */
                max_pkt_size = ecmp->ecm_bulk_out_ep->ep_descr.wMaxPacketSize;
                if ((data_len % max_pkt_size) == 0) {
                        if ((rval = usbecm_send_zero_data(ecmp))
                            != USB_SUCCESS) {
                                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                                    "usbecm_send_data: fail to send padding");
                        }
                }
        }

        if (new_data) {
                freemsg(new_data);
        }

        USB_DPRINTF_L4(PRINT_MASK_EVENTS, ecmp->ecm_lh,
            "usbecm_send_data: len(%d) data sent, rval=%d",
            new_data_len ? new_data_len : data_len, rval);

        return (rval);
}

static int
usbecm_send_zero_data(usbecm_state_t *ecmp)
{
        usb_bulk_req_t  *br;
        int             rval = USB_FAILURE;

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_send_zero_data: entry");

        br = usb_alloc_bulk_req(ecmp->ecm_dip, 0, USB_FLAGS_SLEEP);
        if (br == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_data: alloc req failed.");

                return (USB_FAILURE);
        }

        /* initialize the bulk out request */
        br->bulk_len = 0;
        br->bulk_timeout = USBECM_BULKOUT_TIMEOUT;
        br->bulk_cb = usbecm_bulkout_cb;
        br->bulk_exc_cb = usbecm_bulkout_cb;
        br->bulk_client_private = (usb_opaque_t)ecmp;
        br->bulk_attributes = USB_ATTRS_AUTOCLEARING;

        rval = usb_pipe_bulk_xfer(ecmp->ecm_bulkout_ph, br, 0);

        if (rval != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPS, ecmp->ecm_lh,
                    "usbecm_send_zero_data: Send data failed, rval=%d",
                    rval);

                /*
                 * br->bulk_data should be freed because we allocated
                 * it in this function.
                 */
                usb_free_bulk_req(br);

        }

        USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
            "usbecm_send_zero_data: end");

        return (rval);
}

/*
 * Loadable module configuration entry points
 */

/*
 * _init module entry point.
 *
 * Called when the module is being loaded into memory.
 */
int
_init(void)
{
        int err;

        err = ddi_soft_state_init(&usbecm_statep, sizeof (usbecm_state_t), 1);

        if (err != DDI_SUCCESS)
                return (err);

        mac_init_ops(&usbecm_devops, "usbecm");
        err = mod_install(&usbecm_ml);

        if (err != DDI_SUCCESS) {
                mac_fini_ops(&usbecm_devops);
                ddi_soft_state_fini(&usbecm_statep);
        }

        return (err);
}

/*
 * _info module entry point.
 *
 * Called to obtain information about the module.
 */
int
_info(struct modinfo *modinfop)
{
        return (mod_info(&usbecm_ml, modinfop));
}

/*
 * _fini module entry point.
 *
 * Called when the module is being unloaded.
 */
int
_fini(void)
{
        int err;

        err = mod_remove(&usbecm_ml);
        if (err == DDI_SUCCESS) {
                mac_fini_ops(&usbecm_devops);
                ddi_soft_state_fini(&usbecm_statep);
        }

        return (err);
}

/*
 * usbecm_pipe_start_polling:
 *      start polling on the interrupt pipe
 */
static void
usbecm_pipe_start_polling(usbecm_state_t *ecmp)
{
        usb_intr_req_t  *intr;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbecm_pipe_start_polling: ");

        if (ecmp->ecm_intr_ph == NULL) {

                return;
        }

        intr = usb_alloc_intr_req(ecmp->ecm_dip, 0, USB_FLAGS_SLEEP);

        /*
         * If it is in interrupt context, usb_alloc_intr_req will return NULL if
         * called with SLEEP flag.
         */
        if (!intr) {
                USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbecm_pipe_start_polling: alloc req failed.");

                return;
        }

        /* initialize the interrupt request. */
        intr->intr_attributes = USB_ATTRS_SHORT_XFER_OK |
            USB_ATTRS_AUTOCLEARING;
        intr->intr_len = ecmp->ecm_intr_ep->ep_descr.wMaxPacketSize;
        intr->intr_client_private = (usb_opaque_t)ecmp;
        intr->intr_cb = usbecm_intr_cb;
        intr->intr_exc_cb = usbecm_intr_ex_cb;

        rval = usb_pipe_intr_xfer(ecmp->ecm_intr_ph, intr, USB_FLAGS_SLEEP);

        mutex_enter(&ecmp->ecm_mutex);
        if (rval == USB_SUCCESS) {
                ecmp->ecm_intr_state = USBECM_PIPE_BUSY;
        } else {
                usb_free_intr_req(intr);
                ecmp->ecm_intr_state = USBECM_PIPE_IDLE;
                USB_DPRINTF_L3(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbecm_pipe_start_polling: failed (%d)", rval);
        }
        mutex_exit(&ecmp->ecm_mutex);

        USB_DPRINTF_L3(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbecm_pipe_start_polling: end, rval=%d", rval);
}


/*
 * usbsecm_intr_cb:
 *      interrupt pipe normal callback
 */
/*ARGSUSED*/
static void
usbecm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)req->intr_client_private;
        mblk_t          *data = req->intr_data;
        int             data_len;

        data_len = (data) ? MBLKL(data) : 0;

        DTRACE_PROBE2(usbecm_intr__cb, (usb_intr_req_t *), req, int, data_len);

        /* check data length */
        if (data_len < 8) {
                USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_intr_cb: %d packet too short", data_len);
                usb_free_intr_req(req);

                return;
        }
        req->intr_data = NULL;
        usb_free_intr_req(req);

        mutex_enter(&ecmp->ecm_mutex);
        /* parse interrupt data -- notifications */
        usbecm_parse_intr_data(ecmp, data);
        mutex_exit(&ecmp->ecm_mutex);
}


/*
 * usbsecm_intr_ex_cb:
 *      interrupt pipe exception callback
 */
/*ARGSUSED*/
static void
usbecm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
{
        usbecm_state_t *ecmp = (usbecm_state_t *)req->intr_client_private;
        usb_cr_t        cr = req->intr_completion_reason;

        DTRACE_PROBE2(usbecm_intr_ex__cb, int, ecmp->ecm_dev_state,
            (usb_cr_t), cr);

        usb_free_intr_req(req);

        /*
         * If completion reason isn't USB_CR_PIPE_CLOSING and
         * USB_CR_STOPPED_POLLING, restart polling.
         */
        if ((cr != USB_CR_PIPE_CLOSING) && (cr != USB_CR_STOPPED_POLLING)) {
                mutex_enter(&ecmp->ecm_mutex);

                if (ecmp->ecm_dev_state != USB_DEV_ONLINE) {

                        USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                            "usbsecm_intr_ex_cb: state = %d",
                            ecmp->ecm_dev_state);

                        mutex_exit(&ecmp->ecm_mutex);

                        return;
                }
                mutex_exit(&ecmp->ecm_mutex);

                usbecm_pipe_start_polling(ecmp);
        }
}


/*
 * usbsecm_parse_intr_data:
 *      Parse data received from interrupt callback
 */
static void
usbecm_parse_intr_data(usbecm_state_t *ecmp, mblk_t *data)
{
        uint8_t         bmRequestType;
        uint8_t         bNotification;
        uint16_t        wValue;
        uint16_t        wLength;
        int             linkstate;

        bmRequestType = data->b_rptr[0];
        bNotification = data->b_rptr[1];
        /*
         * If Notification type is NETWORK_CONNECTION, wValue is 0 or 1,
         * mLength is 0. If Notification type is SERIAL_TYPE, mValue is 0,
         * mLength is 2. So we directly get the value from the byte.
         */
        wValue = data->b_rptr[2];
        wLength = data->b_rptr[6];

        if (ecmp->ecm_compatibility) {
                if (bmRequestType != USB_CDC_NOTIFICATION_REQUEST_TYPE) {
                        USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                            "usbsecm_parse_intr_data: unknown request "
                            "type - 0x%x", bmRequestType);

                        freemsg(data);

                        return;
                }
        } else {
                /* non-compatible device specific parsing */
                if (ECM_DS_OP_VALID(ecm_ds_intr_cb)) {
                        if (ecmp->ecm_ds_ops->ecm_ds_intr_cb(ecmp, data)
                            != USB_SUCCESS) {
                                USB_DPRINTF_L2(PRINT_MASK_CB, ecmp->ecm_lh,
                                    "usbsecm_parse_intr_data: unknown request"
                                    "type - 0x%x", bmRequestType);
                        }
                }
                freemsg(data);

                return;
        }

        /*
         * Check the return value of compatible devices
         */
        switch (bNotification) {
        case USB_CDC_NOTIFICATION_NETWORK_CONNECTION:
                USB_DPRINTF_L3(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_parse_intr_data: %s network!",
                    wValue ? "connected to" :"disconnected from");

                linkstate = wValue ? LINK_STATE_UP:LINK_STATE_DOWN;
                if (ecmp->ecm_stat.es_linkstate == linkstate) {
                /* no changes to previous state */
                        break;
                }

                ecmp->ecm_stat.es_linkstate = linkstate;
                mutex_exit(&ecmp->ecm_mutex);
                mac_link_update(ecmp->ecm_mh, linkstate);
                mutex_enter(&ecmp->ecm_mutex);

                break;
        case USB_CDC_NOTIFICATION_RESPONSE_AVAILABLE:
                USB_DPRINTF_L3(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_parse_intr_data: A response is a available.");

                break;
        case USB_CDC_NOTIFICATION_SPEED_CHANGE:
                USB_DPRINTF_L3(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_parse_intr_data: speed change");

                /* check the parameter's length. */
                if (wLength != 8) {
                        USB_DPRINTF_L3(PRINT_MASK_CB, ecmp->ecm_lh,
                            "usbsecm_parse_intr_data: error data length.");
                } else {
                        uint32_t        us_rate, ds_rate;
                        uint8_t         *sp;

                        sp = &data->b_rptr[8];
                        LE_TO_UINT32(sp, us_rate);
                        sp = &data->b_rptr[12];
                        LE_TO_UINT32(sp, ds_rate);
                        ecmp->ecm_stat.es_upspeed = us_rate;
                        ecmp->ecm_stat.es_downspeed = ds_rate;
                }

                break;
        default:
                USB_DPRINTF_L3(PRINT_MASK_CB, ecmp->ecm_lh,
                    "usbsecm_parse_intr_data: unknown notification - 0x%x!",
                    bNotification);

                break;
        }

        freemsg(data);
}

/*
 * usbecm_restore_device_state:
 *      restore device state after CPR resume or reconnect
 */
static int
usbecm_restore_device_state(usbecm_state_t *ecmp)
{
        int     state;

        USB_DPRINTF_L4(PRINT_MASK_EVENTS, ecmp->ecm_lh,
            "usbecm_restore_device_state: ");

        mutex_enter(&ecmp->ecm_mutex);
        state = ecmp->ecm_dev_state;
        mutex_exit(&ecmp->ecm_mutex);

        /* Check device status */
        if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) {

                return (state);
        }

        /* Check if we are talking to the same device */
        if (usb_check_same_device(ecmp->ecm_dip, ecmp->ecm_lh, USB_LOG_L0,
            -1, USB_CHK_ALL, NULL) != USB_SUCCESS) {
                mutex_enter(&ecmp->ecm_mutex);
                state = ecmp->ecm_dev_state = USB_DEV_DISCONNECTED;
                mutex_exit(&ecmp->ecm_mutex);

                return (state);
        }

        if (state == USB_DEV_DISCONNECTED) {
                USB_DPRINTF_L1(PRINT_MASK_EVENTS, ecmp->ecm_lh,
                    "usbecm_restore_device_state: Device has been reconnected "
                    "but data may have been lost");
        }

        /* if MAC was started, restarted it */
        mutex_enter(&ecmp->ecm_mutex);
        if (ecmp->ecm_mac_state == USBECM_MAC_STARTED) {
                USB_DPRINTF_L3(PRINT_MASK_EVENTS, ecmp->ecm_lh,
                    "usbecm_restore_device_state: MAC was started");

                mutex_exit(&ecmp->ecm_mutex);
                /* Do the same operation as usbecm_m_start() does */
                if (usbecm_open_pipes(ecmp) != USB_SUCCESS) {

                        return (state);
                }

                mutex_enter(&ecmp->ecm_mutex);
                if (usbecm_rx_start(ecmp) != USB_SUCCESS) {
                        mutex_exit(&ecmp->ecm_mutex);

                        return (state);
                }
        }
        mutex_exit(&ecmp->ecm_mutex);

        /*
         * init device state
         */
        mutex_enter(&ecmp->ecm_mutex);
        state = ecmp->ecm_dev_state = USB_DEV_ONLINE;
        mutex_exit(&ecmp->ecm_mutex);

        return (state);
}

/*
 * usbecm_reconnect_event_cb:
 *     called upon when the device is hotplugged back
 */
/*ARGSUSED*/
static int
usbecm_reconnect_event_cb(dev_info_t *dip)
{
        usbecm_state_t  *ecmp =
            (usbecm_state_t *)ddi_get_soft_state(usbecm_statep,
            ddi_get_instance(dip));

        ASSERT(ecmp != NULL);

        USB_DPRINTF_L4(PRINT_MASK_EVENTS, ecmp->ecm_lh,
            "usbecm_reconnect_event_cb: entry");

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);

        mutex_enter(&ecmp->ecm_mutex);
        ASSERT(ecmp->ecm_dev_state == USB_DEV_DISCONNECTED);

        mutex_exit(&ecmp->ecm_mutex);

        if (usbecm_restore_device_state(ecmp) != USB_DEV_ONLINE) {
                usb_release_access(ecmp->ecm_ser_acc);

                return (USB_FAILURE);
        }

        usb_release_access(ecmp->ecm_ser_acc);

        return (USB_SUCCESS);
}


/*
 * usbecm_disconnect_event_cb:
 *      callback for disconnect events
 */
/*ARGSUSED*/
static int
usbecm_disconnect_event_cb(dev_info_t *dip)
{
        usbecm_state_t  *ecmp = (usbecm_state_t *)ddi_get_soft_state(
            usbecm_statep, ddi_get_instance(dip));

        USB_DPRINTF_L4(PRINT_MASK_EVENTS, ecmp->ecm_lh,
            "usbecm_disconnect_event_cb: entry");

        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);

        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_dev_state = USB_DEV_DISCONNECTED;
        mutex_exit(&ecmp->ecm_mutex);

        usbecm_close_pipes(ecmp);

        usb_release_access(ecmp->ecm_ser_acc);

        USB_DPRINTF_L4(PRINT_MASK_EVENTS, ecmp->ecm_lh,
            "usbecm_disconnect_event_cb: End");

        return (USB_SUCCESS);
}

/*
 * power management
 * ----------------
 *
 * usbecm_create_pm_components:
 *      create PM components
 */
static int
usbecm_create_pm_components(usbecm_state_t *ecmp)
{
        dev_info_t      *dip = ecmp->ecm_dip;
        usbecm_pm_t     *pm;
        uint_t          pwr_states;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_create_pm_components: entry");

        if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_create_pm_components: failed");

                /* don't fail the attach process */
                return (USB_SUCCESS);
        }

        pm = ecmp->ecm_pm =
            (usbecm_pm_t *)kmem_zalloc(sizeof (usbecm_pm_t), KM_SLEEP);

        pm->pm_pwr_states = (uint8_t)pwr_states;
        pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
        pm->pm_wakeup_enabled = (usb_handle_remote_wakeup(dip,
            USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS);

        (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);

        return (USB_SUCCESS);
}

/*
 * usbecm_cleanup:
 *      Release resources of current device during detach.
 */
static void
usbecm_cleanup(usbecm_state_t *ecmp)
{
        USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
            "usbecm_cleanup: ");

        if (ecmp == NULL) {

                return;
        }

        usbecm_close_pipes(ecmp);

        /* unregister callback function */
        if (ecmp->ecm_init_flags & USBECM_INIT_EVENTS) {
                USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
                    "usbecm_cleanup: unregister events");

                usb_unregister_event_cbs(ecmp->ecm_dip, &usbecm_events);
        }

        /* destroy power management components */
        if (ecmp->ecm_pm != NULL) {
                USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
                    "usbecm_cleanup: destroy pm");
                usbecm_destroy_pm_components(ecmp);
        }

        /* free description of device tree. */
        if (ecmp->ecm_def_ph != NULL) {
                mutex_destroy(&ecmp->ecm_mutex);

                usb_free_descr_tree(ecmp->ecm_dip, ecmp->ecm_dev_data);
                ecmp->ecm_def_ph = NULL;
        }

        if (ecmp->ecm_lh != NULL) {
                usb_free_log_hdl(ecmp->ecm_lh);
                ecmp->ecm_lh = NULL;
        }

        /* detach client device */
        if (ecmp->ecm_dev_data != NULL) {
                usb_client_detach(ecmp->ecm_dip, ecmp->ecm_dev_data);
        }

        if (ecmp->ecm_init_flags & USBECM_INIT_MAC) {
                (void) usbecm_mac_fini(ecmp);
        }

        if (ecmp->ecm_init_flags & USBECM_INIT_SER) {
                usb_fini_serialization(ecmp->ecm_ser_acc);
        }

        ddi_prop_remove_all(ecmp->ecm_dip);
        ddi_remove_minor_node(ecmp->ecm_dip, NULL);
}

/*
 * usbecm_destroy_pm_components:
 *      destroy PM components
 */
static void
usbecm_destroy_pm_components(usbecm_state_t *ecmp)
{
        usbecm_pm_t     *pm = ecmp->ecm_pm;
        dev_info_t      *dip = ecmp->ecm_dip;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_destroy_pm_components: ");

        if (ecmp->ecm_dev_state != USB_DEV_DISCONNECTED) {
                if (pm->pm_wakeup_enabled) {
                        rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
                        if (rval != DDI_SUCCESS) {
                                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                                    "usbecm_destroy_pm_components: "
                                    "raising power failed (%d)", rval);
                        }

                        rval = usb_handle_remote_wakeup(dip,
                            USB_REMOTE_WAKEUP_DISABLE);
                        if (rval != USB_SUCCESS) {
                                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                                    "usbecm_destroy_pm_components: "
                                    "disable remote wakeup failed (%d)", rval);
                        }
                }

                (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
        }
        kmem_free((caddr_t)pm, sizeof (usbecm_pm_t));
        ecmp->ecm_pm = NULL;
}

/*
 * usbecm_pm_set_busy:
 *      mark device busy and raise power
 */
static void
usbecm_pm_set_busy(usbecm_state_t *ecmp)
{
        usbecm_pm_t     *pm = ecmp->ecm_pm;
        dev_info_t      *dip = ecmp->ecm_dip;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_pm_set_busy: pm = 0x%p", (void *)pm);

        if (pm == NULL) {

                return;
        }

        mutex_enter(&ecmp->ecm_mutex);
        /* if already marked busy, just increment the counter */
        if (pm->pm_busy_cnt++ > 0) {
                mutex_exit(&ecmp->ecm_mutex);

                return;
        }

        (void) pm_busy_component(dip, 0);

        if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) {
                mutex_exit(&ecmp->ecm_mutex);

                return;
        }

        /* need to raise power  */
        pm->pm_raise_power = B_TRUE;
        mutex_exit(&ecmp->ecm_mutex);

        rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
        if (rval != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_pm_set_busy: raising power failed");
        }

        mutex_enter(&ecmp->ecm_mutex);
        pm->pm_raise_power = B_FALSE;
        mutex_exit(&ecmp->ecm_mutex);
}


/*
 * usbecm_pm_set_idle:
 *      mark device idle
 */
static void
usbecm_pm_set_idle(usbecm_state_t *ecmp)
{
        usbecm_pm_t     *pm = ecmp->ecm_pm;
        dev_info_t      *dip = ecmp->ecm_dip;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_pm_set_idle: ");

        if (pm == NULL) {

                return;
        }

        mutex_enter(&ecmp->ecm_mutex);
        if (--pm->pm_busy_cnt > 0) {
                mutex_exit(&ecmp->ecm_mutex);

                return;
        }

        if (pm) {
                (void) pm_idle_component(dip, 0);
        }
        mutex_exit(&ecmp->ecm_mutex);
}


/*
 * usbecm_pwrlvl0:
 *      Functions to handle power transition for OS levels 0 -> 3
 *      The same level as OS state, different from USB state
 */
static int
usbecm_pwrlvl0(usbecm_state_t *ecmp)
{
        int             rval;

        ASSERT(mutex_owned(&ecmp->ecm_mutex));

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_pwrlvl0: ");

        switch (ecmp->ecm_dev_state) {
        case USB_DEV_ONLINE:
                /* issue USB D3 command to the device */
                rval = usb_set_device_pwrlvl3(ecmp->ecm_dip);
                ASSERT(rval == USB_SUCCESS);
                if ((ecmp->ecm_intr_ph != NULL) &&
                    (ecmp->ecm_intr_state == USBECM_PIPE_BUSY)) {
                        mutex_exit(&ecmp->ecm_mutex);
                        usb_pipe_stop_intr_polling(ecmp->ecm_intr_ph,
                            USB_FLAGS_SLEEP);
                        mutex_enter(&ecmp->ecm_mutex);

                        ecmp->ecm_intr_state = USBECM_PIPE_IDLE;
                }
                ecmp->ecm_dev_state = USB_DEV_PWRED_DOWN;
                ecmp->ecm_pm->pm_cur_power = USB_DEV_OS_PWR_OFF;

                /* FALLTHRU */
        case USB_DEV_DISCONNECTED:
        case USB_DEV_SUSPENDED:
                /* allow a disconnect/cpr'ed device to go to lower power */

                return (USB_SUCCESS);
        case USB_DEV_PWRED_DOWN:
        default:
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_pwrlvl0: illegal device state");

                return (USB_FAILURE);
        }
}


/*
 * usbecm_pwrlvl1:
 *      Functions to handle power transition for OS levels 1 -> 2
 */
static int
usbecm_pwrlvl1(usbecm_state_t *ecmp)
{
        /* issue USB D2 command to the device */
        (void) usb_set_device_pwrlvl2(ecmp->ecm_dip);

        return (USB_FAILURE);
}


/*
 * usbecm_pwrlvl2:
 *      Functions to handle power transition for OS levels 2 -> 1
 */
static int
usbecm_pwrlvl2(usbecm_state_t *ecmp)
{
        /* issue USB D1 command to the device */
        (void) usb_set_device_pwrlvl1(ecmp->ecm_dip);

        return (USB_FAILURE);
}


/*
 * usbecm_pwrlvl3:
 *      Functions to handle power transition for OS levels 3 -> 0
 *      The same level as OS state, different from USB state
 */
static int
usbecm_pwrlvl3(usbecm_state_t *ecmp)
{
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_pwrlvl3: ");

        ASSERT(mutex_owned(&ecmp->ecm_mutex));

        switch (ecmp->ecm_dev_state) {
        case USB_DEV_PWRED_DOWN:
                /* Issue USB D0 command to the device here */
                rval = usb_set_device_pwrlvl0(ecmp->ecm_dip);
                ASSERT(rval == USB_SUCCESS);

                if (ecmp->ecm_intr_ph != NULL &&
                    ecmp->ecm_intr_state == USBECM_PIPE_IDLE) {
                        mutex_exit(&ecmp->ecm_mutex);
                        usbecm_pipe_start_polling(ecmp);
                        mutex_enter(&ecmp->ecm_mutex);
                }

                ecmp->ecm_dev_state = USB_DEV_ONLINE;
                ecmp->ecm_pm->pm_cur_power = USB_DEV_OS_FULL_PWR;

                /* FALLTHRU */
        case USB_DEV_ONLINE:
                /* we are already in full power */

                /* FALLTHRU */
        case USB_DEV_DISCONNECTED:
        case USB_DEV_SUSPENDED:

                return (USB_SUCCESS);
        default:
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_pwrlvl3: illegal device state");

                return (USB_FAILURE);
        }
}

/*ARGSUSED*/
static int
usbecm_power(dev_info_t *dip, int comp, int level)
{
        usbecm_state_t  *ecmp;
        usbecm_pm_t     *pm;
        int             rval = USB_SUCCESS;

        ecmp = ddi_get_soft_state(usbecm_statep, ddi_get_instance(dip));
        pm = ecmp->ecm_pm;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_power: entry");

        /* check if pm is NULL */
        if (pm == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_power: pm is NULL.");

                return (USB_FAILURE);
        }

        mutex_enter(&ecmp->ecm_mutex);
        /*
         * check if we are transitioning to a legal power level
         */
        if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) {
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_power: "
                    "illegal power level %d, pwr_states=%x",
                    level, pm->pm_pwr_states);
                mutex_exit(&ecmp->ecm_mutex);

                return (USB_FAILURE);
        }

        /*
         * if we are about to raise power and asked to lower power, fail
         */
        if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) {
                USB_DPRINTF_L2(PRINT_MASK_PM, ecmp->ecm_lh,
                    "usbecm_power: wrong condition.");
                mutex_exit(&ecmp->ecm_mutex);

                return (USB_FAILURE);
        }

        /*
         * Set the power status of device by request level.
         */
        switch (level) {
        case USB_DEV_OS_PWR_OFF:
                rval = usbecm_pwrlvl0(ecmp);

                break;
        case USB_DEV_OS_PWR_1:
                rval = usbecm_pwrlvl1(ecmp);

                break;
        case USB_DEV_OS_PWR_2:
                rval = usbecm_pwrlvl2(ecmp);

                break;
        case USB_DEV_OS_FULL_PWR:
                rval = usbecm_pwrlvl3(ecmp);

                break;
        }

        mutex_exit(&ecmp->ecm_mutex);

        return (rval);
}

/*
 * Register with the MAC layer.
 */
static int
usbecm_mac_init(usbecm_state_t *ecmp)
{
        mac_register_t *macp;
        int err;

        /*
         * Initialize mac structure
         */
        macp = mac_alloc(MAC_VERSION);
        if (macp == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "failed to allocate MAC structure");

                return (USB_FAILURE);
        }

        /*
         * Initialize pointer to device specific functions
         */
        macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        macp->m_driver = ecmp;
        macp->m_dip = ecmp->ecm_dip;

        macp->m_src_addr = ecmp->ecm_srcaddr;
        macp->m_callbacks = &usbecm_m_callbacks;
        macp->m_min_sdu = 0;
        macp->m_max_sdu = ETHERMTU;

        /*
         * Register the macp to mac
         */
        err = mac_register(macp, &ecmp->ecm_mh);
        mac_free(macp);

        if (err != DDI_SUCCESS) {
                USB_DPRINTF_L1(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "failed to register MAC structure");

                return (USB_FAILURE);
        }

        mac_link_update(ecmp->ecm_mh, LINK_STATE_DOWN);
        ecmp->ecm_stat.es_linkstate = LINK_STATE_DOWN;
        ecmp->ecm_tx_cnt = 0;

        return (USB_SUCCESS);
}

static int
usbecm_mac_fini(usbecm_state_t *ecmp)
{
        int rval = DDI_SUCCESS;

        if ((ecmp->ecm_init_flags & USBECM_INIT_MAC) == 0) {
                return (DDI_SUCCESS);
        }

        ecmp->ecm_init_flags &= ~USBECM_INIT_MAC;
        if ((rval = mac_disable(ecmp->ecm_mh)) != 0) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "failed to disable MAC");

                return (rval);
        }

        (void) mac_unregister(ecmp->ecm_mh);

        return (rval);
}

static int
usbecm_resume(usbecm_state_t *ecmp)
{
        int             current_state;
        int             ret;

        USB_DPRINTF_L4(PRINT_MASK_PM, ecmp->ecm_lh,
            "usbecm_resume: ");

        mutex_enter(&ecmp->ecm_mutex);
        current_state = ecmp->ecm_dev_state;
        mutex_exit(&ecmp->ecm_mutex);

        /* restore the status of device */
        if (current_state != USB_DEV_ONLINE) {
                ret = usbecm_restore_device_state(ecmp);
        } else {
                ret = USB_DEV_ONLINE;
        }

        return (ret);
}

static int
usbecm_suspend(usbecm_state_t *ecmp)
{
        (void) usb_serialize_access(ecmp->ecm_ser_acc, USB_WAIT, 0);

        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_dev_state = USB_DEV_SUSPENDED;
        mutex_exit(&ecmp->ecm_mutex);

        usbecm_close_pipes(ecmp);

        usb_release_access(ecmp->ecm_ser_acc);

        return (0);
}

/*
 * Translate MAC address from string to 6 bytes array int value
 * Can't use ether_aton() since it requires format of x:x:x:x:x:x
 */
void
label_to_mac(char *hex, unsigned char *mac)
{
        int i;
        char c;

        /* can only count 6 bytes! */
        for (i = 0; i < 6; i++) {
                /* upper 4 bits */
                if (!isdigit(hex[2*i])) {
                        c = (toupper(hex[2 * i]) - 'A' + 10);
                } else {
                        c = (hex[2 * i] - '0');
                }
                mac[i] = c * 16;

                /* lower 4 bits */
                if (!isdigit(hex[2*i + 1])) {
                        c = (toupper(hex[2 * i + 1]) - 'A' + 10);
                } else {
                        c = hex[2 * i + 1] - '0';
                }
                mac[i] += c;
        }
}

/*
 * usbecm_get_descriptors:
 *      parse functional descriptors of ecm compatible device
 */
static int
usbecm_get_descriptors(usbecm_state_t *ecmp)
{
        int                     i;
        usb_cfg_data_t          *cfg;
        usb_alt_if_data_t       *altif;
        usb_cvs_data_t          *cvs;
        int16_t                 master_if = -1, slave_if = -1;
        usb_cdc_ecm_descr_t     ecm_desc;
        usb_ep_data_t           *ep_data;
        usb_dev_descr_t         *usb_dev_desc;

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_get_descriptors: ");

        usb_dev_desc = ecmp->ecm_dev_data->dev_descr;

        /*
         * Special treatment of Sun's SP Ethernet device.
         */
        if ((usb_dev_desc->idVendor == SUN_SP_VENDOR_ID) &&
            (usb_dev_desc->idProduct == SUN_SP_PRODUCT_ID)) {
                if (usb_set_cfg(ecmp->ecm_dip, ecmp->ecm_cfg_index,
                    USB_FLAGS_SLEEP, NULL, NULL) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                            "usbecm_get_descriptors: fail to set cfg ");
                } else {
                        usb_free_dev_data(ecmp->ecm_dip, ecmp->ecm_dev_data);
                        if (usb_get_dev_data(ecmp->ecm_dip, &ecmp->ecm_dev_data,
                            USB_PARSE_LVL_ALL, 0) != USB_SUCCESS) {
                                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                                    "usbecm_get_descriptors: fail to get"
                                    " dev_data");

                                return (USB_FAILURE);
                        }
                }
        }

        cfg = ecmp->ecm_dev_data->dev_curr_cfg;

        /* set default control and data interface */
        ecmp->ecm_ctrl_if_no = ecmp->ecm_data_if_no = 0;

        /* get current interfaces */
        ecmp->ecm_ctrl_if_no = ecmp->ecm_dev_data->dev_curr_if;
        if (cfg->cfg_if[ecmp->ecm_ctrl_if_no].if_n_alt == 0) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: elements in if_alt is %d",
                    cfg->cfg_if[ecmp->ecm_ctrl_if_no].if_n_alt);

                return (USB_FAILURE);
        }

        altif = &cfg->cfg_if[ecmp->ecm_ctrl_if_no].if_alt[0];

        /*
         * Based on CDC specification, ECM devices usually include the
         * following function descriptors: Header, Union and ECM
         * Contry Selection function descriptors. This loop search tree data
         * structure for each ecm class descriptor.
         */
        for (i = 0; i < altif->altif_n_cvs; i++) {
                cvs = &altif->altif_cvs[i];

                if ((cvs->cvs_buf == NULL) ||
                    (cvs->cvs_buf[1] != USB_CDC_CS_INTERFACE)) {
                        continue;
                }

                switch (cvs->cvs_buf[2]) {
                case USB_CDC_DESCR_TYPE_HEADER:
                        /*
                         * parse header functional descriptor
                         * Just to check integrity.
                         */
                        if (cvs->cvs_buf_len != 5) {
                                return (USB_FAILURE);
                        }
                        break;
                case USB_CDC_DESCR_TYPE_ETHERNET:
                        /* parse ECM functional descriptor */
                        if (cvs->cvs_buf_len >= USB_CDC_ECM_LEN) {
                                char buf[USB_MAXSTRINGLEN];

                                if (usb_parse_data("4cl2sc", cvs->cvs_buf,
                                    cvs->cvs_buf_len, (void *)&ecm_desc,
                                    (size_t)USB_CDC_ECM_LEN) <
                                    USB_CDC_ECM_LEN) {

                                        return (USB_FAILURE);
                                }

                                /* get the MAC address */
                                if (usb_get_string_descr(ecmp->ecm_dip,
                                    USB_LANG_ID, ecm_desc.iMACAddress, buf,
                                    USB_MAXSTRINGLEN) != USB_SUCCESS) {

                                        return (USB_FAILURE);
                                }

                                USB_DPRINTF_L3(PRINT_MASK_ATTA, ecmp->ecm_lh,
                                    "usbecm_get_descriptors: macaddr=%s ",
                                    buf);

                                /* expects 12 characters */
                                if (strlen(buf) < 12) {
                                        return (USB_FAILURE);
                                }
                                label_to_mac(buf, ecmp->ecm_srcaddr);

                                bcopy(&ecm_desc, &ecmp->ecm_desc,
                                    USB_CDC_ECM_LEN);
                        }
                        break;
                case USB_CDC_DESCR_TYPE_UNION:
                        /* parse Union functional descriptor. */
                        if (cvs->cvs_buf_len >= 5) {
                                master_if = cvs->cvs_buf[3];
                                slave_if = cvs->cvs_buf[4];
                        }
                        break;
                default:
                        break;
                }
        }

        /* For usb ecm devices, it must satisfy the following options. */
        if (cfg->cfg_n_if < 2) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: # of interfaces %d < 2",
                    cfg->cfg_n_if);

                return (USB_FAILURE);
        }

        if (ecmp->ecm_data_if_no == 0 &&
            slave_if != ecmp->ecm_data_if_no) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: Device has no call management "
                    "descriptor and use Union Descriptor.");

                ecmp->ecm_data_if_no = slave_if;
        }

        if ((master_if != ecmp->ecm_ctrl_if_no) ||
            (slave_if != ecmp->ecm_data_if_no)) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: control interface or "
                    "data interface don't match.");

                return (USB_FAILURE);
        }

        if ((ecmp->ecm_ctrl_if_no >= cfg->cfg_n_if) ||
            (ecmp->ecm_data_if_no >= cfg->cfg_n_if)) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: control interface %d or "
                    "data interface %d out of range.",
                    ecmp->ecm_ctrl_if_no, ecmp->ecm_data_if_no);

                return (USB_FAILURE);
        }

        /* ECM data interface has a minimal of two altsettings */
        if (cfg->cfg_if[ecmp->ecm_data_if_no].if_n_alt < 2) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: elements in if_alt is %d,"
                    " MUST >= 2", cfg->cfg_if[ecmp->ecm_ctrl_if_no].if_n_alt);

                return (USB_FAILURE);
        }

        /* control interface must have interrupt endpoint */
        if ((ep_data = usb_lookup_ep_data(ecmp->ecm_dip, ecmp->ecm_dev_data,
            ecmp->ecm_ctrl_if_no, 0, 0, USB_EP_ATTR_INTR,
            USB_EP_DIR_IN)) == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: "
                    "ctrl interface %d has no interrupt endpoint",
                    ecmp->ecm_data_if_no);

                return (USB_FAILURE);
        }
        ecmp->ecm_intr_ep = ep_data;

        /* data interface alt 1 must have bulk in and out(ECM v1.2,p5) */
        if ((ep_data = usb_lookup_ep_data(ecmp->ecm_dip, ecmp->ecm_dev_data,
            ecmp->ecm_data_if_no, 1, 0, USB_EP_ATTR_BULK,
            USB_EP_DIR_IN)) == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: "
                    "data interface %d has no bulk in endpoint",
                    ecmp->ecm_data_if_no);

                return (USB_FAILURE);
        }
        ecmp->ecm_bulk_in_ep = ep_data;

        if ((ep_data = usb_lookup_ep_data(ecmp->ecm_dip, ecmp->ecm_dev_data,
            ecmp->ecm_data_if_no, 1, 0, USB_EP_ATTR_BULK,
            USB_EP_DIR_OUT)) == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_get_descriptors: "
                    "data interface %d has no bulk out endpoint",
                    ecmp->ecm_data_if_no);

                return (USB_FAILURE);
        }
        ecmp->ecm_bulk_out_ep = ep_data;

        /* set default value for ethernet packet filter */
        ecmp->ecm_pkt_flt = CDC_ECM_PKT_TYPE_DIRECTED;

        return (USB_SUCCESS);
}

/* Generate IEEE802 style MAC address */
static void
generate_ether_addr(uint8_t *mac_addr)
{
        (void) random_get_bytes(mac_addr, 6);
        mac_addr [0] &= 0xfe;   /* unicast only */
        mac_addr [0] |= 0x02;   /* set locally administered bit */
}

/*
 * Find a pair of bulk In/Out endpoints
 */
int usbecm_find_bulk_in_out_eps(usbecm_state_t *ecmp,
    uint16_t ifc, usb_if_data_t *intf)
{
        uint16_t alt, alt_num;
        usb_ep_data_t *intr_ep = NULL;
        usb_ep_data_t *bulk_in, *bulk_out, *ep;

        alt_num = intf->if_n_alt;

        /*
         * for the non-compatible devices, to make it simple, we
         * suppose the devices have this kind of configuration:
         *      INTR In EP(if exists) + BULK In + Bulk Out in the
         *      same altsetting of the same interface
         */
        for (alt = 0; alt < alt_num; alt++) {
                /* search pair of bulk in/out EPs */
                if (((bulk_in = usb_lookup_ep_data(ecmp->ecm_dip,
                    ecmp->ecm_dev_data, ifc, alt, 0,
                    USB_EP_ATTR_BULK,
                    USB_EP_DIR_IN)) == NULL) ||
                    (bulk_out = usb_lookup_ep_data(ecmp->ecm_dip,
                    ecmp->ecm_dev_data, ifc, alt, 0,
                    USB_EP_ATTR_BULK,
                    USB_EP_DIR_OUT)) == NULL) {

                        continue;
                }

                /*
                 * search interrupt pipe.
                 */
                if ((ep = usb_lookup_ep_data(ecmp->ecm_dip,
                    ecmp->ecm_dev_data, ifc, alt, 0,
                    USB_EP_ATTR_INTR, USB_EP_DIR_IN)) != NULL) {
                        intr_ep = ep;
                }


                ecmp->ecm_data_if_no = ifc;
                ecmp->ecm_data_if_alt = alt;
                ecmp->ecm_intr_ep = intr_ep;
                ecmp->ecm_ctrl_if_no = ifc;
                ecmp->ecm_bulk_in_ep = bulk_in;
                ecmp->ecm_bulk_out_ep = bulk_out;

                return (USB_SUCCESS);
        }

        return (USB_FAILURE);
}

static int
usbecm_init_non_compatible_device(usbecm_state_t *ecmp)
{
        usb_if_data_t *cur_if;
        uint16_t if_num, i;

        /*
         * If device don't conform to spec, search pairs of bulk in/out
         * endpoints and fill related structure. We suppose this driver
         * is bound to a interface.
         */
        cur_if = ecmp->ecm_dev_data->dev_curr_cfg->cfg_if;
        if_num = ecmp->ecm_dev_data->dev_curr_cfg->cfg_n_if;

        /* search each interface which have bulk in and out */
        for (i = 0; i < if_num; i++) {
                if (usbecm_find_bulk_in_out_eps(ecmp, i,
                    cur_if) == USB_SUCCESS) {

                        break;
                }
                cur_if++;
        }

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_init_non_compatible_device: ctrl_if=%d,"
            " data_if=%d, alt=%d", ecmp->ecm_ctrl_if_no,
            ecmp->ecm_data_if_no, ecmp->ecm_data_if_alt);

        return (USB_SUCCESS);
}

static boolean_t
usbecm_is_compatible(usbecm_state_t *ecmp)
{
        usb_cfg_data_t *cfg_data;
        usb_if_data_t *intf;
        usb_alt_if_data_t *alt;
        int alt_num, if_num, cfg_num;
        int i, j, cfg_index;

        cfg_num = ecmp->ecm_dev_data->dev_n_cfg;
        USB_DPRINTF_L3(PRINT_MASK_ATTA, ecmp->ecm_lh,
            "usbecm_is_compatible: entry, cfg_num=%d", cfg_num);

        for (cfg_index = 0; cfg_index < cfg_num; cfg_index++) {
                cfg_data = &(ecmp->ecm_dev_data->dev_cfg[cfg_index]);

                USB_DPRINTF_L3(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_is_compatible: cfg_index=%d, value=%d",
                    cfg_index, cfg_data->cfg_descr.bConfigurationValue);

                intf = cfg_data->cfg_if;
                if_num = cfg_data->cfg_n_if;

                for (i = 0; i < if_num; i++) {
                        alt_num = intf->if_n_alt;
                        for (j = 0; j < alt_num; j++) {
                        alt = &intf->if_alt[j];
                        if ((alt->altif_descr.bInterfaceClass == 0x02) &&
                            (alt->altif_descr.bInterfaceSubClass == 0x06)) {
                                ecmp->ecm_cfg_index = cfg_index;

                                USB_DPRINTF_L3(PRINT_MASK_ATTA, ecmp->ecm_lh,
                                    "usbecm_is_compatible: cfg_index=%d",
                                    cfg_index);

                                return (B_TRUE);
                        }
                        }
                        intf++;
                }
        }

        return (B_FALSE);
}


static int
usbecm_usb_init(usbecm_state_t *ecmp)
{

        if (usb_client_attach(ecmp->ecm_dip, USBDRV_VERSION, 0) !=
            USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                "usbecm_usb_init: fail to attach");

                return (USB_FAILURE);
        }

        /* Get the configuration information of device */
        if (usb_get_dev_data(ecmp->ecm_dip, &ecmp->ecm_dev_data,
            USB_PARSE_LVL_ALL, 0) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                "usbecm_usb_init: fail to get_dev_data");

                return (USB_FAILURE);
        }
        ecmp->ecm_def_ph = ecmp->ecm_dev_data->dev_default_ph;
        ecmp->ecm_dev_state = USB_DEV_ONLINE;

        mutex_init(&ecmp->ecm_mutex, NULL, MUTEX_DRIVER,
            ecmp->ecm_dev_data->dev_iblock_cookie);

        if ((strcmp(ddi_binding_name(ecmp->ecm_dip),
            "usbif,class2.6") == 0) ||
            ((strcmp(ddi_binding_name(ecmp->ecm_dip),
            "usb,class2.6.0") == 0))) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_usb_init: A CDC ECM device is attached");
                ecmp->ecm_compatibility = B_TRUE;
        } else if (usb_owns_device(ecmp->ecm_dip) &&
            usbecm_is_compatible(ecmp)) {
                /*
                 * Current Sun SP ECM device has two configurations. Hence
                 * USBA doesn't create interface level compatible names
                 * for it, see usba_ready_device_node(). We have to check
                 * manually to see if compatible interfaces exist, when
                 * the driver owns the entire device.
                 */
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_usb_init: A CDC ECM device is attached");
                ecmp->ecm_compatibility = B_TRUE;
        } else {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_usb_init: A nonstandard device is attached to "
                    "usbecm(4D) driver. This device doesn't conform to "
                    "usb cdc spec.");
                ecmp->ecm_compatibility = B_FALSE;

                /* generate a random MAC addr */
                generate_ether_addr(ecmp->ecm_srcaddr);
        }

        if ((ecmp->ecm_compatibility == B_TRUE) &&
            (usbecm_get_descriptors(ecmp) != USB_SUCCESS)) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_usb_init: A compatible device is attached, but "
                    "fail to get standard descriptors");

                return (USB_FAILURE);
        }

        if (ecmp->ecm_compatibility == B_FALSE) {
                (void) usbecm_init_non_compatible_device(ecmp);
        }

        /* Create power management components */
        if (usbecm_create_pm_components(ecmp) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_usb_init: create pm components failed.");

                return (USB_FAILURE);
        }

        /* Register to get callbacks for USB events */
        if (usb_register_event_cbs(ecmp->ecm_dip, &usbecm_events, 0)
            != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbsecm_attach: register event callback failed.");

                return (USB_FAILURE);
        }
        ecmp->ecm_init_flags |= USBECM_INIT_EVENTS;


        /* Get max data size of bulk transfer */
        if (usb_pipe_get_max_bulk_transfer_size(ecmp->ecm_dip,
            &ecmp->ecm_xfer_sz) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbsecm_ds_attach: get max size of transfer failed.");

                return (USB_FAILURE);
        }


        ecmp->ecm_ser_acc = usb_init_serialization(ecmp->ecm_dip,
            USB_INIT_SER_CHECK_SAME_THREAD);
        ecmp->ecm_init_flags |= USBECM_INIT_SER;

        return (USB_SUCCESS);
}


/*
 * Open operation pipes. Each ECM device should have Bulk In, Bulk Out
 * and Interrupt In endpoints
 */
static int
usbecm_open_pipes(usbecm_state_t *ecmp)
{
        int             rval = USB_SUCCESS;
        usb_ep_data_t   *in_data, *out_data, *intr_pipe;
        usb_pipe_policy_t policy;
        int             altif;

        ASSERT(!mutex_owned(&ecmp->ecm_mutex));

        USB_DPRINTF_L4(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbsecm_open_pipes: ecmp = 0x%p", (void *)ecmp);

        if (ecmp->ecm_compatibility == B_TRUE) {
        /* compatible device has minimum of 2 altsetting, select alt 1 */
                altif = 1;
        } else {
                altif = ecmp->ecm_data_if_alt;
        }
        intr_pipe = ecmp->ecm_intr_ep;
        in_data = ecmp->ecm_bulk_in_ep;
        out_data = ecmp->ecm_bulk_out_ep;

        /* Bulk in and out must exist simultaneously. */
        if ((in_data == NULL) || (out_data == NULL)) {
                USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbsecm_open_pipes: look up bulk pipe failed in "
                    "interface %d ",
                    ecmp->ecm_data_if_no);

                return (USB_FAILURE);
        }
        /*
         * If device conform to ecm spec, it must have an interrupt pipe
         * for this device.
         */
        if (ecmp->ecm_compatibility == B_TRUE && intr_pipe == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbecm_open_pipes: look up interrupt pipe failed in "
                    "interface %d", ecmp->ecm_ctrl_if_no);

                return (USB_FAILURE);
        }

        USB_DPRINTF_L3(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbsecm_open_pipes: open intr %02x, bulkin %02x bulkout %02x",
            intr_pipe?intr_pipe->ep_descr.bEndpointAddress:0,
            in_data->ep_descr.bEndpointAddress,
            out_data->ep_descr.bEndpointAddress);

        USB_DPRINTF_L3(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbsecm_open_pipes: set data if(%d) alt(%d) ",
            ecmp->ecm_data_if_no, altif);

        if ((rval = usb_set_alt_if(ecmp->ecm_dip, ecmp->ecm_data_if_no,
            altif, USB_FLAGS_SLEEP, NULL, NULL)) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_open_pipes: set alternate failed (%d)",
                    rval);

                return (rval);
        }

        policy.pp_max_async_reqs = 2;

        /* Open bulk in endpoint */
        if (usb_pipe_open(ecmp->ecm_dip, &in_data->ep_descr, &policy,
            USB_FLAGS_SLEEP, &ecmp->ecm_bulkin_ph) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbecm_open_pipes: open bulkin pipe failed!");

                return (USB_FAILURE);
        }

        /* Open bulk out endpoint */
        if (usb_pipe_open(ecmp->ecm_dip, &out_data->ep_descr, &policy,
            USB_FLAGS_SLEEP, &ecmp->ecm_bulkout_ph) != USB_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                    "usbecm_open_pipes: open bulkout pipe failed!");

                usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_bulkin_ph,
                    USB_FLAGS_SLEEP, NULL, NULL);

                return (USB_FAILURE);
        }

        /* Open interrupt endpoint if found. */
        if (intr_pipe != NULL) {
                if (usb_pipe_open(ecmp->ecm_dip, &intr_pipe->ep_descr, &policy,
                    USB_FLAGS_SLEEP, &ecmp->ecm_intr_ph) != USB_SUCCESS) {
                        USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
                            "usbecm_open_pipes: "
                            "open intr pipe failed");

                        usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_bulkin_ph,
                            USB_FLAGS_SLEEP, NULL, NULL);
                        usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_bulkout_ph,
                            USB_FLAGS_SLEEP, NULL, NULL);

                        return (USB_FAILURE);
                }
        }

        /* initialize the pipe related data */
        mutex_enter(&ecmp->ecm_mutex);
        ecmp->ecm_bulkin_sz = in_data->ep_descr.wMaxPacketSize;
        ecmp->ecm_bulkin_state = USBECM_PIPE_IDLE;
        ecmp->ecm_bulkout_state = USBECM_PIPE_IDLE;
        if (ecmp->ecm_intr_ph != NULL) {
                ecmp->ecm_intr_state = USBECM_PIPE_IDLE;
        }
        mutex_exit(&ecmp->ecm_mutex);

        if (ecmp->ecm_intr_ph != NULL) {

                usbecm_pipe_start_polling(ecmp);
        }

        USB_DPRINTF_L4(PRINT_MASK_OPEN, ecmp->ecm_lh,
            "usbsecm_open_pipes: end");

        return (rval);
}


/*
 * usbsecm_close_pipes:
 *      Close pipes
 *      Each device could include three pipes: bulk in, bulk out and interrupt.
 */
static void
usbecm_close_pipes(usbecm_state_t *ecmp)
{

        mutex_enter(&ecmp->ecm_mutex);

        USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
            "usbsecm_close_pipes: ecm_bulkin_state = %d",
            ecmp->ecm_bulkin_state);

        /*
         * Check the status of the pipes. If pipe is closing or closed,
         * return directly.
         */
        if ((ecmp->ecm_bulkin_state == USBECM_PIPE_CLOSED) ||
            (ecmp->ecm_bulkin_state == USBECM_PIPE_CLOSING)) {
                USB_DPRINTF_L2(PRINT_MASK_CLOSE, ecmp->ecm_lh,
                    "usbsecm_close_pipes: pipe is closing or has closed");
                mutex_exit(&ecmp->ecm_mutex);

                return;
        }

        ecmp->ecm_bulkin_state = USBECM_PIPE_CLOSING;
        mutex_exit(&ecmp->ecm_mutex);

        /* reset the data interface's altsetting to 0 */
        if ((ecmp->ecm_dev_state == USB_DEV_ONLINE) &&
            (usb_set_alt_if(ecmp->ecm_dip, ecmp->ecm_data_if_no,
            0, USB_FLAGS_SLEEP, NULL, NULL) != USB_SUCCESS)) {
                USB_DPRINTF_L2(PRINT_MASK_ATTA, ecmp->ecm_lh,
                    "usbecm_close_pipes: reset alternate failed ");
        }

        /* Close pipes */
        usb_pipe_reset(ecmp->ecm_dip, ecmp->ecm_bulkin_ph,
            USB_FLAGS_SLEEP, NULL, 0);
        usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_bulkin_ph,
            USB_FLAGS_SLEEP, NULL, 0);
        usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_bulkout_ph,
            USB_FLAGS_SLEEP, NULL, 0);

        if (ecmp->ecm_intr_ph != NULL) {
                usb_pipe_stop_intr_polling(ecmp->ecm_intr_ph,
                    USB_FLAGS_SLEEP);
                usb_pipe_close(ecmp->ecm_dip, ecmp->ecm_intr_ph,
                    USB_FLAGS_SLEEP, NULL, 0);
        }

        mutex_enter(&ecmp->ecm_mutex);
        /* Reset the status of pipes to closed */
        ecmp->ecm_bulkin_state = USBECM_PIPE_CLOSED;
        ecmp->ecm_bulkin_ph = NULL;
        ecmp->ecm_bulkout_state = USBECM_PIPE_CLOSED;
        ecmp->ecm_bulkout_ph = NULL;
        if (ecmp->ecm_intr_ph != NULL) {
                ecmp->ecm_intr_state = USBECM_PIPE_CLOSED;
                ecmp->ecm_intr_ph = NULL;
        }

        mutex_exit(&ecmp->ecm_mutex);

        USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
            "usbsecm_close_pipes: pipes have been closed.");
}


static int
usbecm_ctrl_write(usbecm_state_t *ecmp, uchar_t request,
    uint16_t value, mblk_t **data)
{
        usb_ctrl_setup_t setup;
        usb_cb_flags_t  cb_flags;
        usb_cr_t        cr;
        int             rval;

        USB_DPRINTF_L4(PRINT_MASK_ALL, ecmp->ecm_lh,
            "usbecm_ctrl_write: ");

        /* initialize the control request. */
        setup.bmRequestType = USB_DEV_REQ_HOST_TO_DEV |
            USB_DEV_REQ_TYPE_CLASS | USB_DEV_REQ_RCPT_IF;
        setup.bRequest = request;
        setup.wValue = value;
        setup.wIndex = ecmp->ecm_ctrl_if_no;
        setup.wLength = ((data != NULL) && (*data != NULL)) ? MBLKL(*data) : 0;
        setup.attrs = 0;

        rval = usb_pipe_ctrl_xfer_wait(ecmp->ecm_def_ph, &setup, data,
            &cr, &cb_flags, 0);

        USB_DPRINTF_L4(PRINT_MASK_ALL, ecmp->ecm_lh,
            "usbecm_ctrl_write: rval = %d", rval);

        return (rval);
}

static int
usbecm_ctrl_read(usbecm_state_t *ecmp, uchar_t request,
    uint16_t value, mblk_t **data, int len)
{
        usb_ctrl_setup_t setup;
        usb_cb_flags_t  cb_flags;
        usb_cr_t        cr;

        USB_DPRINTF_L4(PRINT_MASK_ALL, ecmp->ecm_lh,
            "usbecm_ctrl_read: ");

        /* initialize the control request. */
        setup.bmRequestType = USB_DEV_REQ_DEV_TO_HOST |
            USB_DEV_REQ_TYPE_CLASS | USB_DEV_REQ_RCPT_IF;
        setup.bRequest = request;
        setup.wValue = value;
        setup.wIndex = ecmp->ecm_ctrl_if_no;
        setup.wLength = (uint16_t)len;
        setup.attrs = 0;

        return (usb_pipe_ctrl_xfer_wait(ecmp->ecm_def_ph, &setup, data,
            &cr, &cb_flags, 0));
}

/* Get specific statistic data from device */
static int
usbecm_get_statistics(usbecm_state_t *ecmp, uint32_t fs, uint32_t *stat_data)
{
        mblk_t *data = NULL;
        uint32_t stat;

        /* first check to see if this stat is collected by device */
        if ((ecmp->ecm_compatibility == B_TRUE) &&
            (ecmp->ecm_desc.bmEthernetStatistics & ECM_STAT_CAP_MASK(fs))) {
                if (usbecm_ctrl_read(ecmp, CDC_ECM_GET_ETH_STAT,
                    ecmp->ecm_ctrl_if_no, &data, 4) != USB_SUCCESS) {

                        return (USB_FAILURE);
                }
                stat = (data->b_rptr[3] << 24) | (data->b_rptr[2] << 16) |
                    (data->b_rptr[1] << 8) | (data->b_rptr[0]);
                *stat_data = stat;

                freemsg(data);

                return (USB_SUCCESS);
        }

        return (USB_FAILURE);
}