#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>
#include <sys/random.h>
#include <sys/sdt.h>
#include <inet/nd.h>
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);
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);
static char usbecm_ident[] = "usbecm 1.0";
void *usbecm_statep;
static uint_t usbecm_errlevel = USB_LOG_L3;
static uint_t usbecm_errmask = 0xffffffff;
static uint_t usbecm_instance_debug = (uint_t)-1;
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))
static mac_callbacks_t usbecm_m_callbacks = {
MC_IOCTL | MC_SETPROP | MC_GETPROP,
usbecm_m_stat,
usbecm_m_start,
usbecm_m_stop,
usbecm_m_promisc,
usbecm_m_multicst,
usbecm_m_unicst,
usbecm_m_tx,
NULL,
usbecm_m_ioctl,
NULL,
NULL,
NULL,
usbecm_m_setprop,
usbecm_m_getprop,
NULL
};
static struct cb_ops cb_usbecm = {
nulldev,
nulldev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_MP,
CB_REV,
nodev,
nodev,
};
static struct dev_ops usbecm_devops = {
DEVO_REV,
0,
NULL,
nulldev,
nulldev,
usbecm_attach,
usbecm_detach,
nodev,
&(cb_usbecm),
(struct bus_ops *)NULL,
usbecm_power,
ddi_quiesce_not_needed
};
static struct modldrv usbecm_modldrv = {
&mod_driverops,
usbecm_ident,
&usbecm_devops
};
static struct modlinkage usbecm_ml = {
MODREV_1,
&usbecm_modldrv, NULL
};
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;
(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;
}
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);
}
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);
}
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);
switch (stat) {
case MAC_STAT_IFSPEED:
mutex_enter(&ecmp->ecm_mutex);
if (ecmp->ecm_stat.es_downspeed) {
*val = ecmp->ecm_stat.es_downspeed;
} else {
*val = 10 * 1000000ull;
}
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);
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;
break;
case MAC_STAT_OBYTES:
fs = ECM_DIRECTED_BYTES_XMIT;
break;
case MAC_STAT_OPACKETS:
fs = ECM_XMIT_OK;
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);
}
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);
}
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);
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");
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);
}
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");
}
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");
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);
return (DDI_SUCCESS);
}
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);
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)) {
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);
return (DDI_SUCCESS);
}
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);
return (DDI_SUCCESS);
}
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;
}
}
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);
}
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);
}
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);
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");
mutex_enter(&ecmp->ecm_mutex);
ecmp->ecm_stat.es_oerrors++;
mutex_exit(&ecmp->ecm_mutex);
mp->b_next = next;
break;
}
mutex_enter(&ecmp->ecm_mutex);
ecmp->ecm_stat.es_opackets++;
ecmp->ecm_stat.es_obytes += MBLKL(mp);
mutex_exit(&ecmp->ecm_mutex);
freemsg(mp);
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);
}
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);
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) {
freemsg(ecmp->ecm_rcv_queue);
ecmp->ecm_rcv_queue = data;
} else {
linkb(ecmp->ecm_rcv_queue, data);
}
}
} else {
freemsg(data);
}
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);
}
}
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);
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);
}
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);
}
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);
}
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;
}
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);
}
if (new_data) {
br->bulk_data = msgpullup(new_data, -1);
br->bulk_len = new_data_len;
} else {
br->bulk_data = msgpullup(data, -1);
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.");
usb_free_bulk_req(br);
} else {
mutex_enter(&ecmp->ecm_mutex);
ecmp->ecm_tx_cnt++;
mutex_exit(&ecmp->ecm_mutex);
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);
}
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);
usb_free_bulk_req(br);
}
USB_DPRINTF_L4(PRINT_MASK_OPS, ecmp->ecm_lh,
"usbecm_send_zero_data: end");
return (rval);
}
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);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&usbecm_ml, modinfop));
}
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);
}
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 (!intr) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, ecmp->ecm_lh,
"usbecm_pipe_start_polling: alloc req failed.");
return;
}
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);
}
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);
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);
usbecm_parse_intr_data(ecmp, data);
mutex_exit(&ecmp->ecm_mutex);
}
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 ((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);
}
}
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];
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 {
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;
}
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) {
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");
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);
}
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);
if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) {
return (state);
}
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");
}
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);
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);
mutex_enter(&ecmp->ecm_mutex);
state = ecmp->ecm_dev_state = USB_DEV_ONLINE;
mutex_exit(&ecmp->ecm_mutex);
return (state);
}
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);
}
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);
}
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");
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);
}
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);
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);
}
if (ecmp->ecm_pm != NULL) {
USB_DPRINTF_L4(PRINT_MASK_CLOSE, ecmp->ecm_lh,
"usbecm_cleanup: destroy pm");
usbecm_destroy_pm_components(ecmp);
}
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;
}
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);
}
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;
}
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 (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;
}
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);
}
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);
}
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:
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;
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
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);
}
}
static int
usbecm_pwrlvl1(usbecm_state_t *ecmp)
{
(void) usb_set_device_pwrlvl2(ecmp->ecm_dip);
return (USB_FAILURE);
}
static int
usbecm_pwrlvl2(usbecm_state_t *ecmp)
{
(void) usb_set_device_pwrlvl1(ecmp->ecm_dip);
return (USB_FAILURE);
}
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:
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;
case USB_DEV_ONLINE:
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);
}
}
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");
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);
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 (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);
}
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);
}
static int
usbecm_mac_init(usbecm_state_t *ecmp)
{
mac_register_t *macp;
int err;
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);
}
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;
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);
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);
}
void
label_to_mac(char *hex, unsigned char *mac)
{
int i;
char c;
for (i = 0; i < 6; i++) {
if (!isdigit(hex[2*i])) {
c = (toupper(hex[2 * i]) - 'A' + 10);
} else {
c = (hex[2 * i] - '0');
}
mac[i] = c * 16;
if (!isdigit(hex[2*i + 1])) {
c = (toupper(hex[2 * i + 1]) - 'A' + 10);
} else {
c = hex[2 * i + 1] - '0';
}
mac[i] += c;
}
}
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;
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;
ecmp->ecm_ctrl_if_no = ecmp->ecm_data_if_no = 0;
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];
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:
if (cvs->cvs_buf_len != 5) {
return (USB_FAILURE);
}
break;
case USB_CDC_DESCR_TYPE_ETHERNET:
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);
}
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);
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:
if (cvs->cvs_buf_len >= 5) {
master_if = cvs->cvs_buf[3];
slave_if = cvs->cvs_buf[4];
}
break;
default:
break;
}
}
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);
}
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);
}
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;
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;
ecmp->ecm_pkt_flt = CDC_ECM_PKT_TYPE_DIRECTED;
return (USB_SUCCESS);
}
static void
generate_ether_addr(uint8_t *mac_addr)
{
(void) random_get_bytes(mac_addr, 6);
mac_addr [0] &= 0xfe;
mac_addr [0] |= 0x02;
}
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 (alt = 0; alt < alt_num; alt++) {
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;
}
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;
cur_if = ecmp->ecm_dev_data->dev_curr_cfg->cfg_if;
if_num = ecmp->ecm_dev_data->dev_curr_cfg->cfg_n_if;
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);
}
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)) {
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_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);
}
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);
}
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;
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);
}
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) {
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;
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 (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;
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);
}
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);
}
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);
}
}
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);
}
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);
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);
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 ");
}
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);
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: ");
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: ");
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));
}
static int
usbecm_get_statistics(usbecm_state_t *ecmp, uint32_t fs, uint32_t *stat_data)
{
mblk_t *data = NULL;
uint32_t stat;
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);
}