#include <sys/types.h>
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/termio.h>
#include <sys/termiox.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/byteorder.h>
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
#include <sys/usb/usba.h>
#include <sys/usb/usbdevs.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/clients/usbser/usbser.h>
#include <sys/usb/clients/usbser/usbser_dsdi.h>
#include <sys/usb/clients/usbcdc/usb_cdc.h>
#include <sys/usb/clients/usbser/usbsacm/usbsacm.h>
static int usbsacm_attach(dev_info_t *, ddi_attach_cmd_t);
static int usbsacm_detach(dev_info_t *, ddi_detach_cmd_t);
static int usbsacm_getinfo(dev_info_t *, ddi_info_cmd_t, void *,
void **);
static int usbsacm_open(queue_t *, dev_t *, int, int, cred_t *);
static int usbsacm_ds_attach(ds_attach_info_t *);
static void usbsacm_ds_detach(ds_hdl_t);
static int usbsacm_ds_register_cb(ds_hdl_t, uint_t, ds_cb_t *);
static void usbsacm_ds_unregister_cb(ds_hdl_t, uint_t);
static int usbsacm_ds_open_port(ds_hdl_t, uint_t);
static int usbsacm_ds_close_port(ds_hdl_t, uint_t);
static int usbsacm_ds_set_port_params(ds_hdl_t, uint_t,
ds_port_params_t *);
static int usbsacm_ds_set_modem_ctl(ds_hdl_t, uint_t, int, int);
static int usbsacm_ds_get_modem_ctl(ds_hdl_t, uint_t, int, int *);
static int usbsacm_ds_break_ctl(ds_hdl_t, uint_t, int);
static int usbsacm_ds_tx(ds_hdl_t, uint_t, mblk_t *);
static mblk_t *usbsacm_ds_rx(ds_hdl_t, uint_t);
static void usbsacm_ds_stop(ds_hdl_t, uint_t, int);
static void usbsacm_ds_start(ds_hdl_t, uint_t, int);
static int usbsacm_ds_fifo_flush(ds_hdl_t, uint_t, int);
static int usbsacm_ds_fifo_drain(ds_hdl_t, uint_t, int);
static int usbsacm_wait_tx_drain(usbsacm_port_t *, int);
static int usbsacm_fifo_flush_locked(usbsacm_state_t *, uint_t, int);
static int usbsacm_ds_suspend(ds_hdl_t);
static int usbsacm_ds_resume(ds_hdl_t);
static int usbsacm_ds_disconnect(ds_hdl_t);
static int usbsacm_ds_reconnect(ds_hdl_t);
static int usbsacm_ds_usb_power(ds_hdl_t, int, int, int *);
static int usbsacm_create_pm_components(usbsacm_state_t *);
static void usbsacm_destroy_pm_components(usbsacm_state_t *);
static void usbsacm_pm_set_busy(usbsacm_state_t *);
static void usbsacm_pm_set_idle(usbsacm_state_t *);
static int usbsacm_pwrlvl0(usbsacm_state_t *);
static int usbsacm_pwrlvl1(usbsacm_state_t *);
static int usbsacm_pwrlvl2(usbsacm_state_t *);
static int usbsacm_pwrlvl3(usbsacm_state_t *);
static void usbsacm_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
static void usbsacm_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);
static void usbsacm_pipe_start_polling(usbsacm_port_t *acmp);
static void usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
static void usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
static void usbsacm_parse_intr_data(usbsacm_port_t *acmp, mblk_t *data);
static int usbsacm_rx_start(usbsacm_port_t *);
static void usbsacm_tx_start(usbsacm_port_t *);
static int usbsacm_send_data(usbsacm_port_t *, mblk_t *);
static int usbsacm_init_alloc_ports(usbsacm_state_t *);
static void usbsacm_free_ports(usbsacm_state_t *);
static void usbsacm_cleanup(usbsacm_state_t *);
static int usbsacm_get_descriptors(usbsacm_state_t *);
static int usbsacm_restore_device_state(usbsacm_state_t *);
static int usbsacm_restore_port_state(usbsacm_state_t *);
static int usbsacm_open_port_pipes(usbsacm_port_t *);
static void usbsacm_close_port_pipes(usbsacm_port_t *);
static void usbsacm_close_pipes(usbsacm_state_t *);
static void usbsacm_disconnect_pipes(usbsacm_state_t *);
static int usbsacm_reconnect_pipes(usbsacm_state_t *);
static int usbsacm_req_write(usbsacm_port_t *, uchar_t, uint16_t,
mblk_t **);
static int usbsacm_set_line_coding(usbsacm_port_t *,
usb_cdc_line_coding_t *);
static void usbsacm_mctl2reg(int mask, int val, uint8_t *);
static int usbsacm_reg2mctl(uint8_t);
static void usbsacm_put_tail(mblk_t **, mblk_t *);
static void usbsacm_put_head(mblk_t **, mblk_t *);
struct module_info usbsacm_modinfo = {
0,
"usbsacm",
USBSER_MIN_PKTSZ,
USBSER_MAX_PKTSZ,
USBSER_HIWAT,
USBSER_LOWAT
};
static struct qinit usbsacm_rinit = {
NULL,
usbser_rsrv,
usbsacm_open,
usbser_close,
NULL,
&usbsacm_modinfo,
NULL
};
static struct qinit usbsacm_winit = {
usbser_wput,
usbser_wsrv,
NULL,
NULL,
NULL,
&usbsacm_modinfo,
NULL
};
struct streamtab usbsacm_str_info = {
&usbsacm_rinit, &usbsacm_winit, NULL, NULL
};
static struct cb_ops usbsacm_cb_ops = {
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
&usbsacm_str_info,
(int)(D_64BIT | D_NEW | D_MP | D_HOTPLUG)
};
struct dev_ops usbsacm_ops = {
DEVO_REV,
0,
usbsacm_getinfo,
nulldev,
nulldev,
usbsacm_attach,
usbsacm_detach,
nodev,
&usbsacm_cb_ops,
(struct bus_ops *)NULL,
usbser_power,
ddi_quiesce_not_needed,
};
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"USB Serial CDC ACM driver",
&usbsacm_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
static void *usbsacm_statep;
static ds_ops_t usbsacm_ds_ops = {
DS_OPS_VERSION,
usbsacm_ds_attach,
usbsacm_ds_detach,
usbsacm_ds_register_cb,
usbsacm_ds_unregister_cb,
usbsacm_ds_open_port,
usbsacm_ds_close_port,
usbsacm_ds_usb_power,
usbsacm_ds_suspend,
usbsacm_ds_resume,
usbsacm_ds_disconnect,
usbsacm_ds_reconnect,
usbsacm_ds_set_port_params,
usbsacm_ds_set_modem_ctl,
usbsacm_ds_get_modem_ctl,
usbsacm_ds_break_ctl,
NULL,
usbsacm_ds_tx,
usbsacm_ds_rx,
usbsacm_ds_stop,
usbsacm_ds_start,
usbsacm_ds_fifo_flush,
usbsacm_ds_fifo_drain
};
static int usbsacm_speedtab[] = {
0,
50,
75,
110,
134,
150,
200,
300,
600,
1200,
1800,
2400,
4800,
9600,
19200,
38400,
57600,
76800,
115200,
153600,
230400,
307200,
460800,
921600,
1000000,
1152000,
1500000,
2000000,
2500000,
3000000,
3500000,
4000000,
};
static uint_t usbsacm_errlevel = USB_LOG_L4;
static uint_t usbsacm_errmask = 0xffffffff;
static uint_t usbsacm_instance_debug = (uint_t)-1;
int
_init(void)
{
int error;
if ((error = mod_install(&modlinkage)) == 0) {
error = ddi_soft_state_init(&usbsacm_statep,
usbser_soft_state_size(), 1);
}
return (error);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) == 0) {
ddi_soft_state_fini(&usbsacm_statep);
}
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
usbsacm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
return (usbser_attach(dip, cmd, usbsacm_statep, &usbsacm_ds_ops));
}
static int
usbsacm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
return (usbser_detach(dip, cmd, usbsacm_statep));
}
int
usbsacm_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
return (usbser_getinfo(dip, infocmd, arg, result, usbsacm_statep));
}
static int
usbsacm_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr)
{
return (usbser_open(rq, dev, flag, sflag, cr, usbsacm_statep));
}
static int
usbsacm_ds_attach(ds_attach_info_t *aip)
{
usbsacm_state_t *acmp;
acmp = (usbsacm_state_t *)kmem_zalloc(sizeof (usbsacm_state_t),
KM_SLEEP);
acmp->acm_dip = aip->ai_dip;
acmp->acm_usb_events = aip->ai_usb_events;
acmp->acm_ports = NULL;
*aip->ai_hdl = (ds_hdl_t)acmp;
if (usb_client_attach(acmp->acm_dip, USBDRV_VERSION,
0) != USB_SUCCESS) {
goto fail;
}
if (usb_get_dev_data(acmp->acm_dip, &acmp->acm_dev_data,
USB_PARSE_LVL_CFG, 0) != USB_SUCCESS) {
goto fail;
}
acmp->acm_def_ph = acmp->acm_dev_data->dev_default_ph;
acmp->acm_dev_state = USB_DEV_ONLINE;
mutex_init(&acmp->acm_mutex, NULL, MUTEX_DRIVER,
acmp->acm_dev_data->dev_iblock_cookie);
acmp->acm_lh = usb_alloc_log_hdl(acmp->acm_dip, "usbsacm",
&usbsacm_errlevel, &usbsacm_errmask, &usbsacm_instance_debug, 0);
if (usbsacm_create_pm_components(acmp) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_attach: create pm components failed.");
goto fail;
}
if (usb_register_event_cbs(acmp->acm_dip, acmp->acm_usb_events, 0)
!= USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_attach: register event callback failed.");
goto fail;
}
if ((strcmp(DEVI(acmp->acm_dip)->devi_binding_name,
"usbif,class2.2") == 0) ||
((strcmp(DEVI(acmp->acm_dip)->devi_binding_name,
"usb,class2.2.0") == 0))) {
acmp->acm_compatibility = B_TRUE;
} else {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_attach: A nonstandard device is attaching to "
"usbsacm driver. This device doesn't conform to "
"usb cdc spec.");
acmp->acm_compatibility = B_FALSE;
}
if (usbsacm_init_alloc_ports(acmp) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_attach: initialize port structure failed.");
goto fail;
}
*aip->ai_port_cnt = acmp->acm_port_cnt;
if (usb_pipe_get_max_bulk_transfer_size(acmp->acm_dip,
&acmp->acm_xfer_sz) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_attach: get max size of transfer failed.");
goto fail;
}
return (USB_SUCCESS);
fail:
usbsacm_cleanup(acmp);
return (USB_FAILURE);
}
static void
usbsacm_ds_detach(ds_hdl_t hdl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_ds_detach:");
usbsacm_close_pipes(acmp);
usbsacm_cleanup(acmp);
}
static int
usbsacm_ds_register_cb(ds_hdl_t hdl, uint_t port_num, ds_cb_t *cb)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port;
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_ds_register_cb: acmp = 0x%p port_num = %d",
(void *)acmp, port_num);
if (port_num >= acmp->acm_port_cnt) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_ds_register_cb: port number is wrong.");
return (USB_FAILURE);
}
acm_port = &acmp->acm_ports[port_num];
acm_port->acm_cb = *cb;
return (USB_SUCCESS);
}
static void
usbsacm_ds_unregister_cb(ds_hdl_t hdl, uint_t port_num)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_ds_unregister_cb: ");
if (port_num < acmp->acm_port_cnt) {
acm_port = &acmp->acm_ports[port_num];
bzero(&acm_port->acm_cb, sizeof (acm_port->acm_cb));
}
}
static int
usbsacm_ds_open_port(ds_hdl_t hdl, uint_t port_num)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_ds_open_port: port_num = %d", port_num);
mutex_enter(&acm_port->acm_port_mutex);
if ((acmp->acm_dev_state == USB_DEV_DISCONNECTED) ||
(acm_port->acm_port_state != USBSACM_PORT_CLOSED)) {
mutex_exit(&acm_port->acm_port_mutex);
return (USB_FAILURE);
}
mutex_exit(&acm_port->acm_port_mutex);
usbsacm_pm_set_busy(acmp);
if (usbsacm_open_port_pipes(acm_port) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_ds_open_port: open pipes failed.");
return (USB_FAILURE);
}
mutex_enter(&acm_port->acm_port_mutex);
if (usbsacm_rx_start(acm_port) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_ds_open_port: start receive data failed.");
mutex_exit(&acm_port->acm_port_mutex);
return (USB_FAILURE);
}
acm_port->acm_port_state = USBSACM_PORT_OPEN;
mutex_exit(&acm_port->acm_port_mutex);
return (USB_SUCCESS);
}
static int
usbsacm_ds_close_port(ds_hdl_t hdl, uint_t port_num)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
int rval = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_ds_close_port: acmp = 0x%p", (void *)acmp);
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_port_state = USBSACM_PORT_CLOSED;
mutex_exit(&acm_port->acm_port_mutex);
usbsacm_close_port_pipes(acm_port);
mutex_enter(&acm_port->acm_port_mutex);
rval = usbsacm_fifo_flush_locked(acmp, port_num, DS_TX | DS_RX);
mutex_exit(&acm_port->acm_port_mutex);
usbsacm_pm_set_idle(acmp);
return (rval);
}
static int
usbsacm_ds_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_pm_t *pm = acmp->acm_pm;
int rval = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_usb_power: ");
if (pm == NULL) {
USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_usb_power: pm is NULL.");
return (USB_FAILURE);
}
mutex_enter(&acmp->acm_mutex);
if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) {
USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_usb_power: "
"illegal power level %d, pwr_states=%x",
level, pm->pm_pwr_states);
mutex_exit(&acmp->acm_mutex);
return (USB_FAILURE);
}
if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) {
USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_usb_power: wrong condition.");
mutex_exit(&acmp->acm_mutex);
return (USB_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
rval = usbsacm_pwrlvl0(acmp);
break;
case USB_DEV_OS_PWR_1:
rval = usbsacm_pwrlvl1(acmp);
break;
case USB_DEV_OS_PWR_2:
rval = usbsacm_pwrlvl2(acmp);
break;
case USB_DEV_OS_FULL_PWR:
rval = usbsacm_pwrlvl3(acmp);
if ((rval == USB_SUCCESS) &&
((*new_state == USB_DEV_DISCONNECTED) ||
(*new_state == USB_DEV_SUSPENDED))) {
acmp->acm_dev_state = *new_state;
}
break;
}
*new_state = acmp->acm_dev_state;
mutex_exit(&acmp->acm_mutex);
return (rval);
}
static int
usbsacm_ds_suspend(ds_hdl_t hdl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
int state = USB_DEV_SUSPENDED;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_suspend: ");
mutex_enter(&acmp->acm_mutex);
if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) {
acmp->acm_dev_state = USB_DEV_SUSPENDED;
}
mutex_exit(&acmp->acm_mutex);
usbsacm_disconnect_pipes(acmp);
return (state);
}
static int
usbsacm_ds_resume(ds_hdl_t hdl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
int current_state;
int ret;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_ds_resume: ");
mutex_enter(&acmp->acm_mutex);
current_state = acmp->acm_dev_state;
mutex_exit(&acmp->acm_mutex);
if (current_state != USB_DEV_ONLINE) {
ret = usbsacm_restore_device_state(acmp);
} else {
ret = USB_DEV_ONLINE;
}
return (ret);
}
static int
usbsacm_ds_disconnect(ds_hdl_t hdl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
int state = USB_DEV_DISCONNECTED;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_ds_disconnect: ");
mutex_enter(&acmp->acm_mutex);
if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) {
acmp->acm_dev_state = USB_DEV_DISCONNECTED;
}
mutex_exit(&acmp->acm_mutex);
usbsacm_disconnect_pipes(acmp);
return (state);
}
static int
usbsacm_ds_reconnect(ds_hdl_t hdl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_ds_reconnect: ");
return (usbsacm_restore_device_state(acmp));
}
static int
usbsacm_ds_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
int i;
uint_t ui;
ds_port_param_entry_t *pe;
usb_cdc_line_coding_t lc;
int ret;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_set_port_params: acmp = 0x%p", (void *)acmp);
mutex_enter(&acm_port->acm_port_mutex);
if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 &&
acmp->acm_compatibility == B_TRUE) {
mutex_exit(&acm_port->acm_port_mutex);
USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_set_port_params: "
"don't support Set_Line_Coding.");
return (USB_FAILURE);
}
lc = acm_port->acm_line_coding;
mutex_exit(&acm_port->acm_port_mutex);
pe = tp->tp_entries;
for (i = 0; i < tp->tp_cnt; i++, pe++) {
switch (pe->param) {
case DS_PARAM_BAUD:
ui = pe->val.ui;
if ((ui >= NELEM(usbsacm_speedtab)) ||
((ui > 0) && (usbsacm_speedtab[ui] == 0))) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_set_port_params: "
" error baud rate");
return (USB_FAILURE);
}
lc.dwDTERate = LE_32(usbsacm_speedtab[ui]);
break;
case DS_PARAM_PARITY:
if (pe->val.ui & PARENB) {
if (pe->val.ui & PARODD) {
lc.bParityType = USB_CDC_PARITY_ODD;
} else {
lc.bParityType = USB_CDC_PARITY_EVEN;
}
} else {
lc.bParityType = USB_CDC_PARITY_NO;
}
break;
case DS_PARAM_STOPB:
if (pe->val.ui & CSTOPB) {
lc.bCharFormat = USB_CDC_STOP_BITS_2;
} else {
lc.bCharFormat = USB_CDC_STOP_BITS_1;
}
break;
case DS_PARAM_CHARSZ:
switch (pe->val.ui) {
case CS5:
lc.bDataBits = 5;
break;
case CS6:
lc.bDataBits = 6;
break;
case CS7:
lc.bDataBits = 7;
break;
case CS8:
default:
lc.bDataBits = 8;
break;
}
break;
default:
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_set_port_params: "
"parameter 0x%x isn't supported",
pe->param);
break;
}
}
if ((ret = usbsacm_set_line_coding(acm_port, &lc)) == USB_SUCCESS) {
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_line_coding = lc;
mutex_exit(&acm_port->acm_port_mutex);
}
if (acmp->acm_compatibility != B_TRUE) {
ret = USB_SUCCESS;
}
return (ret);
}
static int
usbsacm_ds_set_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int val)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
uint8_t new_mctl;
int ret;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_set_modem_ctl: mask = 0x%x val = 0x%x",
mask, val);
mutex_enter(&acm_port->acm_port_mutex);
if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 &&
acmp->acm_compatibility == B_TRUE) {
mutex_exit(&acm_port->acm_port_mutex);
USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_set_modem_ctl: "
"don't support Set_Control_Line_State.");
return (USB_FAILURE);
}
new_mctl = acm_port->acm_mctlout;
mutex_exit(&acm_port->acm_port_mutex);
usbsacm_mctl2reg(mask, val, &new_mctl);
if ((acmp->acm_compatibility == B_FALSE) || ((ret =
usbsacm_req_write(acm_port, USB_CDC_REQ_SET_CONTROL_LINE_STATE,
new_mctl, NULL)) == USB_SUCCESS)) {
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_mctlout = new_mctl;
mutex_exit(&acm_port->acm_port_mutex);
}
if (acmp->acm_compatibility != B_TRUE) {
ret = USB_SUCCESS;
}
return (ret);
}
static int
usbsacm_ds_get_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int *valp)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
mutex_enter(&acm_port->acm_port_mutex);
*valp = usbsacm_reg2mctl(acm_port->acm_mctlout) & mask;
if (acmp->acm_compatibility) {
*valp |= usbsacm_reg2mctl(acm_port->acm_mctlin) & mask;
*valp |= (mask & (TIOCM_CD | TIOCM_CTS));
} else {
*valp |= (mask & (TIOCM_CD | TIOCM_CTS | TIOCM_DSR | TIOCM_RI));
}
mutex_exit(&acm_port->acm_port_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_get_modem_ctl: val = 0x%x", *valp);
return (USB_SUCCESS);
}
static int
usbsacm_ds_break_ctl(ds_hdl_t hdl, uint_t port_num, int ctl)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_break_ctl: ");
mutex_enter(&acm_port->acm_port_mutex);
if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SEND_BREAK) == 0 &&
acmp->acm_compatibility == B_TRUE) {
mutex_exit(&acm_port->acm_port_mutex);
USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_break_ctl: don't support send break.");
return (USB_FAILURE);
}
mutex_exit(&acm_port->acm_port_mutex);
return (usbsacm_req_write(acm_port, USB_CDC_REQ_SEND_BREAK,
((ctl == DS_ON) ? 0xffff : 0), NULL));
}
static int
usbsacm_ds_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_tx: mp = 0x%p acmp = 0x%p", (void *)mp, (void *)acmp);
if (mp == NULL) {
return (USB_SUCCESS);
}
if (MBLKL(mp) < 1) {
freemsg(mp);
return (USB_SUCCESS);
}
mutex_enter(&acm_port->acm_port_mutex);
usbsacm_put_tail(&acm_port->acm_tx_mp, mp);
usbsacm_tx_start(acm_port);
mutex_exit(&acm_port->acm_port_mutex);
return (USB_SUCCESS);
}
static mblk_t *
usbsacm_ds_rx(ds_hdl_t hdl, uint_t port_num)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
mblk_t *mp;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_rx: acmp = 0x%p", (void *)acmp);
mutex_enter(&acm_port->acm_port_mutex);
mp = acm_port->acm_rx_mp;
acm_port->acm_rx_mp = NULL;
mutex_exit(&acm_port->acm_port_mutex);
return (mp);
}
static void
usbsacm_ds_stop(ds_hdl_t hdl, uint_t port_num, int dir)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_stop: don't support!");
}
static void
usbsacm_ds_start(ds_hdl_t hdl, uint_t port_num, int dir)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_start: don't support!");
}
static int
usbsacm_ds_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
int ret = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_ds_fifo_flush: ");
mutex_enter(&acm_port->acm_port_mutex);
ret = usbsacm_fifo_flush_locked(acmp, port_num, dir);
mutex_exit(&acm_port->acm_port_mutex);
return (ret);
}
static int
usbsacm_ds_fifo_drain(ds_hdl_t hdl, uint_t port_num, int timeout)
{
usbsacm_state_t *acmp = (usbsacm_state_t *)hdl;
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
int rval = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_fifo_drain: ");
mutex_enter(&acm_port->acm_port_mutex);
ASSERT(acm_port->acm_port_state == USBSACM_PORT_OPEN);
if (usbsacm_wait_tx_drain(acm_port, timeout) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_ds_fifo_drain: fifo drain failed.");
mutex_exit(&acm_port->acm_port_mutex);
return (USB_FAILURE);
}
mutex_exit(&acm_port->acm_port_mutex);
return (rval);
}
static int
usbsacm_fifo_flush_locked(usbsacm_state_t *acmp, uint_t port_num, int dir)
{
usbsacm_port_t *acm_port = &acmp->acm_ports[port_num];
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_fifo_flush_locked: ");
if ((dir & DS_TX) && acm_port->acm_tx_mp) {
freemsg(acm_port->acm_tx_mp);
acm_port->acm_tx_mp = NULL;
}
if ((dir & DS_RX) && acm_port->acm_rx_mp) {
freemsg(acm_port->acm_rx_mp);
acm_port->acm_rx_mp = NULL;
}
return (USB_SUCCESS);
}
static int
usbsacm_get_bulk_pipe_number(usbsacm_state_t *acmp, uint_t dir)
{
int count = 0;
int i, skip;
usb_if_data_t *cur_if;
int ep_num;
int if_num;
USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_bulk_pipe_number: ");
cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if;
if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if;
for (i = 0; i < if_num; i++) {
ep_num = cur_if->if_alt->altif_n_ep;
for (skip = 0; skip < ep_num; skip++) {
if (usb_lookup_ep_data(acmp->acm_dip,
acmp->acm_dev_data, i, 0, skip,
USB_EP_ATTR_BULK, dir) == NULL) {
break;
}
count++;
}
cur_if++;
}
return (count);
}
static int
usbsacm_init_ports_status(usbsacm_state_t *acmp)
{
usbsacm_port_t *cur_port;
int i, skip;
int if_num;
int intr_if_no = 0;
int ep_num;
usb_if_data_t *cur_if;
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_init_ports_status: acmp = 0x%p", (void *)acmp);
for (i = 0; i < acmp->acm_port_cnt; i++) {
cur_port = &acmp->acm_ports[i];
cv_init(&cur_port->acm_tx_cv, NULL, CV_DRIVER, NULL);
cur_port->acm_port_state = USBSACM_PORT_CLOSED;
cur_port->acm_line_coding.dwDTERate = LE_32((uint32_t)9600);
cur_port->acm_line_coding.bCharFormat = 0;
cur_port->acm_line_coding.bParityType = USB_CDC_PARITY_NO;
cur_port->acm_line_coding.bDataBits = 8;
cur_port->acm_device = acmp;
mutex_init(&cur_port->acm_port_mutex, NULL, MUTEX_DRIVER,
acmp->acm_dev_data->dev_iblock_cookie);
}
if (acmp->acm_compatibility == B_TRUE) {
if (usbsacm_get_descriptors(acmp) != USB_SUCCESS) {
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if;
if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if;
cur_port = acmp->acm_ports;
for (i = 0; i < if_num; i++) {
ep_num = cur_if->if_alt->altif_n_ep;
for (skip = 0; skip < ep_num; skip++) {
if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
i, 0, skip, USB_EP_ATTR_INTR, USB_EP_DIR_IN) != NULL)) {
intr_if_no = i;
}
if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_IN) == NULL) ||
(usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_OUT) == NULL)) {
continue;
}
cur_port->acm_data_if_no = i;
cur_port->acm_ctrl_if_no = intr_if_no;
cur_port->acm_data_port_no = skip;
cur_port++;
intr_if_no = 0;
}
cur_if++;
}
return (USB_SUCCESS);
}
static int
usbsacm_init_alloc_ports(usbsacm_state_t *acmp)
{
int rval = USB_SUCCESS;
int count_in = 0, count_out = 0;
if (acmp->acm_compatibility) {
acmp->acm_port_cnt = 1;
} else {
count_in = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_IN);
count_out = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_OUT);
USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_init_alloc_ports: count_in = %d, count_out = %d",
count_in, count_out);
acmp->acm_port_cnt = min(count_in, count_out);
}
if (acmp->acm_port_cnt == 0) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_init_alloc_ports: port count is zero.");
return (USB_FAILURE);
}
acmp->acm_ports = (usbsacm_port_t *)kmem_zalloc(acmp->acm_port_cnt *
sizeof (usbsacm_port_t), KM_SLEEP);
if (acmp->acm_ports == NULL) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_init_alloc_ports: allocate memory failed.");
return (USB_FAILURE);
}
rval = usbsacm_init_ports_status(acmp);
if (rval != USB_SUCCESS) {
usbsacm_free_ports(acmp);
}
return (rval);
}
static void
usbsacm_free_ports(usbsacm_state_t *acmp)
{
int i;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_free_ports: ");
for (i = 0; i < acmp->acm_port_cnt; i++) {
cv_destroy(&acmp->acm_ports[i].acm_tx_cv);
mutex_destroy(&acmp->acm_ports[i].acm_port_mutex);
}
kmem_free((caddr_t)acmp->acm_ports, sizeof (usbsacm_port_t) *
acmp->acm_port_cnt);
acmp->acm_ports = NULL;
}
static int
usbsacm_get_descriptors(usbsacm_state_t *acmp)
{
int i;
usb_cfg_data_t *cfg;
usb_alt_if_data_t *altif;
usb_cvs_data_t *cvs;
int mgmt_cap = 0;
int master_if = -1, slave_if = -1;
usbsacm_port_t *acm_port = acmp->acm_ports;
usb_dev_descr_t *dd;
USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: ");
dd = acmp->acm_dev_data->dev_descr;
cfg = acmp->acm_dev_data->dev_curr_cfg;
acm_port->acm_ctrl_if_no = acm_port->acm_data_if_no = 0;
acm_port->acm_ctrl_if_no = acmp->acm_dev_data->dev_curr_if;
if (cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt == 0) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: elements in if_alt is %d",
cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt);
return (USB_FAILURE);
}
altif = &cfg->cfg_if[acm_port->acm_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_CALL_MANAGEMENT:
if (cvs->cvs_buf_len >= 5) {
mgmt_cap = cvs->cvs_buf[3];
acm_port->acm_data_if_no = cvs->cvs_buf[4];
}
break;
case USB_CDC_DESCR_TYPE_ACM:
if (cvs->cvs_buf_len >= 4) {
acm_port->acm_cap = cvs->cvs_buf[3];
}
if (dd->idVendor == USB_VENDOR_SIGMADESIGNS &&
dd->idProduct == USB_PRODUCT_SIGMADESIGNS_ZW090) {
acm_port->acm_cap |=
USB_CDC_ACM_CAP_SERIAL_LINE;
}
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, acmp->acm_lh,
"usbsacm_get_descriptors: # of interfaces %d < 2",
cfg->cfg_n_if);
return (USB_FAILURE);
}
if (acm_port->acm_data_if_no == 0 &&
slave_if != acm_port->acm_data_if_no) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: Device hasn't call management "
"descriptor and use Union Descriptor.");
acm_port->acm_data_if_no = slave_if;
}
if ((master_if != acm_port->acm_ctrl_if_no) ||
(slave_if != acm_port->acm_data_if_no)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: control interface or "
"data interface don't match.");
return (USB_FAILURE);
}
if (((mgmt_cap & USB_CDC_CALL_MGMT_CAP_CALL_MGMT) == 0) ||
((mgmt_cap & USB_CDC_CALL_MGMT_CAP_DATA_INTERFACE) == 0)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: "
"insufficient mgmt capabilities %x",
mgmt_cap);
}
if ((acm_port->acm_ctrl_if_no >= cfg->cfg_n_if) ||
(acm_port->acm_data_if_no >= cfg->cfg_n_if)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: control interface %d or "
"data interface %d out of range.",
acm_port->acm_ctrl_if_no, acm_port->acm_data_if_no);
return (USB_FAILURE);
}
if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_ctrl_if_no, 0, 0, USB_EP_ATTR_INTR,
USB_EP_DIR_IN) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: "
"ctrl interface %d has no interrupt endpoint",
acm_port->acm_data_if_no);
return (USB_FAILURE);
}
if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK,
USB_EP_DIR_IN) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: "
"data interface %d has no bulk in endpoint",
acm_port->acm_data_if_no);
return (USB_FAILURE);
}
if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK,
USB_EP_DIR_OUT) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_get_descriptors: "
"data interface %d has no bulk out endpoint",
acm_port->acm_data_if_no);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
static void
usbsacm_cleanup(usbsacm_state_t *acmp)
{
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_cleanup: ");
if (acmp != NULL) {
if (acmp->acm_ports != NULL) {
usbsacm_free_ports(acmp);
}
if (acmp->acm_usb_events != NULL) {
usb_unregister_event_cbs(acmp->acm_dip,
acmp->acm_usb_events);
}
if (acmp->acm_pm != NULL) {
usbsacm_destroy_pm_components(acmp);
}
if (acmp->acm_def_ph != NULL) {
mutex_destroy(&acmp->acm_mutex);
usb_free_descr_tree(acmp->acm_dip, acmp->acm_dev_data);
acmp->acm_def_ph = NULL;
}
if (acmp->acm_lh != NULL) {
usb_free_log_hdl(acmp->acm_lh);
acmp->acm_lh = NULL;
}
if (acmp->acm_dev_data != NULL) {
usb_client_detach(acmp->acm_dip, acmp->acm_dev_data);
}
kmem_free((caddr_t)acmp, sizeof (usbsacm_state_t));
}
}
static int
usbsacm_restore_device_state(usbsacm_state_t *acmp)
{
int state;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_restore_device_state: ");
mutex_enter(&acmp->acm_mutex);
state = acmp->acm_dev_state;
mutex_exit(&acmp->acm_mutex);
if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) {
return (state);
}
if (usb_check_same_device(acmp->acm_dip, acmp->acm_lh, USB_LOG_L0,
-1, USB_CHK_ALL, NULL) != USB_SUCCESS) {
mutex_enter(&acmp->acm_mutex);
state = acmp->acm_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&acmp->acm_mutex);
return (state);
}
if (state == USB_DEV_DISCONNECTED) {
USB_DPRINTF_L1(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_restore_device_state: Device has been reconnected "
"but data may have been lost");
}
if (usbsacm_reconnect_pipes(acmp) != USB_SUCCESS) {
return (state);
}
mutex_enter(&acmp->acm_mutex);
state = acmp->acm_dev_state = USB_DEV_ONLINE;
mutex_exit(&acmp->acm_mutex);
if ((usbsacm_restore_port_state(acmp) != USB_SUCCESS)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_restore_device_state: failed");
}
return (state);
}
static int
usbsacm_restore_port_state(usbsacm_state_t *acmp)
{
int i, ret = USB_SUCCESS;
usbsacm_port_t *cur_port;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_restore_port_state: ");
for (i = 0; i < acmp->acm_port_cnt; i++) {
cur_port = &acmp->acm_ports[i];
mutex_enter(&cur_port->acm_port_mutex);
if (cur_port->acm_port_state != USBSACM_PORT_OPEN) {
mutex_exit(&cur_port->acm_port_mutex);
continue;
}
mutex_exit(&cur_port->acm_port_mutex);
if ((ret = usbsacm_set_line_coding(cur_port,
&cur_port->acm_line_coding)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_restore_port_state: failed.");
}
}
return (ret);
}
static int
usbsacm_open_port_pipes(usbsacm_port_t *acm_port)
{
int rval = USB_SUCCESS;
usbsacm_state_t *acmp = acm_port->acm_device;
usb_ep_data_t *in_data, *out_data, *intr_pipe;
usb_pipe_policy_t policy;
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: acmp = 0x%p", (void *)acmp);
intr_pipe = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_ctrl_if_no, 0, 0,
USB_EP_ATTR_INTR, USB_EP_DIR_IN);
in_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no,
USB_EP_ATTR_BULK, USB_EP_DIR_IN);
out_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no,
USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
if ((in_data == NULL) || (out_data == NULL)) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: look up bulk pipe failed in "
"interface %d port %d",
acm_port->acm_data_if_no, acm_port->acm_data_port_no);
return (USB_FAILURE);
}
if (acmp->acm_compatibility == B_TRUE && intr_pipe == NULL) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: look up interrupt pipe failed in "
"interface %d", acm_port->acm_ctrl_if_no);
return (USB_FAILURE);
}
policy.pp_max_async_reqs = 2;
if (usb_pipe_open(acmp->acm_dip, &in_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &acm_port->acm_bulkin_ph) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: open bulkin pipe failed!");
return (USB_FAILURE);
}
if (usb_pipe_open(acmp->acm_dip, &out_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &acm_port->acm_bulkout_ph) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: open bulkout pipe failed!");
usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
USB_FLAGS_SLEEP, NULL, NULL);
return (USB_FAILURE);
}
if (intr_pipe != NULL) {
if (usb_pipe_open(acmp->acm_dip, &intr_pipe->ep_descr, &policy,
USB_FLAGS_SLEEP, &acm_port->acm_intr_ph) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_open_port_pipes: "
"open control pipe failed");
usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
USB_FLAGS_SLEEP, NULL, NULL);
usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph,
USB_FLAGS_SLEEP, NULL, NULL);
return (USB_FAILURE);
}
}
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_bulkin_size = in_data->ep_descr.wMaxPacketSize;
acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
if (acm_port->acm_intr_ph != NULL) {
acm_port->acm_intr_state = USBSACM_PIPE_IDLE;
acm_port->acm_intr_ep_descr = intr_pipe->ep_descr;
}
mutex_exit(&acm_port->acm_port_mutex);
if (acm_port->acm_intr_ph != NULL) {
usbsacm_pipe_start_polling(acm_port);
}
return (rval);
}
static void
usbsacm_close_port_pipes(usbsacm_port_t *acm_port)
{
usbsacm_state_t *acmp = acm_port->acm_device;
mutex_enter(&acm_port->acm_port_mutex);
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_close_port_pipes: acm_bulkin_state = %d",
acm_port->acm_bulkin_state);
if ((acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSED) ||
(acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSING)) {
USB_DPRINTF_L2(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_close_port_pipes: port is closing or has closed");
mutex_exit(&acm_port->acm_port_mutex);
return;
}
acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSING;
mutex_exit(&acm_port->acm_port_mutex);
usb_pipe_reset(acmp->acm_dip, acm_port->acm_bulkin_ph,
USB_FLAGS_SLEEP, 0, 0);
usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
USB_FLAGS_SLEEP, 0, 0);
usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph,
USB_FLAGS_SLEEP, 0, 0);
if (acm_port->acm_intr_ph != NULL) {
usb_pipe_stop_intr_polling(acm_port->acm_intr_ph,
USB_FLAGS_SLEEP);
usb_pipe_close(acmp->acm_dip, acm_port->acm_intr_ph,
USB_FLAGS_SLEEP, 0, 0);
}
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSED;
acm_port->acm_bulkin_ph = NULL;
acm_port->acm_bulkout_state = USBSACM_PIPE_CLOSED;
acm_port->acm_bulkout_ph = NULL;
if (acm_port->acm_intr_ph != NULL) {
acm_port->acm_intr_state = USBSACM_PIPE_CLOSED;
acm_port->acm_intr_ph = NULL;
}
mutex_exit(&acm_port->acm_port_mutex);
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_close_port_pipes: port has been closed.");
}
static void
usbsacm_close_pipes(usbsacm_state_t *acmp)
{
int i;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_close_pipes: ");
for (i = 0; i < acmp->acm_port_cnt; i++) {
usbsacm_close_port_pipes(&acmp->acm_ports[i]);
}
}
static void
usbsacm_disconnect_pipes(usbsacm_state_t *acmp)
{
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_disconnect_pipes: ");
usbsacm_close_pipes(acmp);
}
static int
usbsacm_reconnect_pipes(usbsacm_state_t *acmp)
{
usbsacm_port_t *cur_port = acmp->acm_ports;
int i;
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_reconnect_pipes: ");
for (i = 0; i < acmp->acm_port_cnt; i++) {
cur_port = &acmp->acm_ports[i];
mutex_enter(&cur_port->acm_port_mutex);
if (cur_port->acm_port_state == USBSACM_PORT_OPEN) {
mutex_exit(&cur_port->acm_port_mutex);
if (usbsacm_open_port_pipes(cur_port) != USB_SUCCESS) {
USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_reconnect_pipes: "
"open port %d failed.", i);
return (USB_FAILURE);
}
mutex_enter(&cur_port->acm_port_mutex);
}
mutex_exit(&cur_port->acm_port_mutex);
}
return (USB_SUCCESS);
}
static void
usbsacm_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
usbsacm_port_t *acm_port = (usbsacm_port_t *)req->bulk_client_private;
usbsacm_state_t *acmp = acm_port->acm_device;
mblk_t *data;
int data_len;
data = req->bulk_data;
data_len = (data) ? MBLKL(data) : 0;
mutex_enter(&acm_port->acm_port_mutex);
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_bulkin_cb: "
"acm_bulkin_state = %d acm_port_state = %d data_len = %d",
acm_port->acm_bulkin_state, acm_port->acm_port_state, data_len);
if ((acm_port->acm_port_state == USBSACM_PORT_OPEN) && (data_len) &&
(req->bulk_completion_reason == USB_CR_OK)) {
mutex_exit(&acm_port->acm_port_mutex);
req->bulk_data = NULL;
usbsacm_put_tail(&acm_port->acm_rx_mp, data);
if (acm_port->acm_cb.cb_rx) {
acm_port->acm_cb.cb_rx(acm_port->acm_cb.cb_arg);
}
mutex_enter(&acm_port->acm_port_mutex);
}
mutex_exit(&acm_port->acm_port_mutex);
usb_free_bulk_req(req);
mutex_enter(&acm_port->acm_port_mutex);
if (((acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) ||
(acm_port->acm_bulkin_state == USBSACM_PIPE_IDLE)) &&
(acm_port->acm_port_state == USBSACM_PORT_OPEN) &&
(acmp->acm_dev_state == USB_DEV_ONLINE)) {
if (usbsacm_rx_start(acm_port) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_bulkin_cb: restart rx fail "
"acm_port_state = %d", acm_port->acm_port_state);
}
} else if (acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) {
acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
}
mutex_exit(&acm_port->acm_port_mutex);
}
static void
usbsacm_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
usbsacm_port_t *acm_port = (usbsacm_port_t *)req->bulk_client_private;
usbsacm_state_t *acmp = acm_port->acm_device;
int data_len;
mblk_t *data = req->bulk_data;
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_bulkout_cb: acmp = 0x%p", (void *)acmp);
data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0;
if (req->bulk_completion_reason && (data_len > 0)) {
usbsacm_put_head(&acm_port->acm_tx_mp, data);
req->bulk_data = NULL;
}
usb_free_bulk_req(req);
if (acm_port->acm_cb.cb_tx) {
acm_port->acm_cb.cb_tx(acm_port->acm_cb.cb_arg);
}
mutex_enter(&acm_port->acm_port_mutex);
acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
if (acm_port->acm_tx_mp == NULL) {
cv_broadcast(&acm_port->acm_tx_cv);
} else {
usbsacm_tx_start(acm_port);
}
mutex_exit(&acm_port->acm_port_mutex);
}
static int
usbsacm_rx_start(usbsacm_port_t *acm_port)
{
usbsacm_state_t *acmp = acm_port->acm_device;
usb_bulk_req_t *br;
int rval = USB_FAILURE;
int data_len;
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_rx_start: acm_xfer_sz = 0x%lx acm_bulkin_size = 0x%lx",
acmp->acm_xfer_sz, acm_port->acm_bulkin_size);
acm_port->acm_bulkin_state = USBSACM_PIPE_BUSY;
data_len = min(acmp->acm_xfer_sz, acm_port->acm_bulkin_size * 2);
mutex_exit(&acm_port->acm_port_mutex);
br = usb_alloc_bulk_req(acmp->acm_dip, data_len, USB_FLAGS_SLEEP);
if (br == NULL) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_rx_start: allocate bulk request failed");
mutex_enter(&acm_port->acm_port_mutex);
return (USB_FAILURE);
}
br->bulk_len = data_len;
br->bulk_timeout = USBSACM_BULKIN_TIMEOUT;
br->bulk_cb = usbsacm_bulkin_cb;
br->bulk_exc_cb = usbsacm_bulkin_cb;
br->bulk_client_private = (usb_opaque_t)acm_port;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING
| USB_ATTRS_SHORT_XFER_OK;
rval = usb_pipe_bulk_xfer(acm_port->acm_bulkin_ph, br, 0);
mutex_enter(&acm_port->acm_port_mutex);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_rx_start: bulk transfer failed %d", rval);
usb_free_bulk_req(br);
acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
}
return (rval);
}
static void
usbsacm_tx_start(usbsacm_port_t *acm_port)
{
int len;
mblk_t *data;
int data_len;
mblk_t *mp;
int copylen;
int rval;
usbsacm_state_t *acmp = acm_port->acm_device;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_tx_start: ");
if (acm_port->acm_tx_mp == NULL) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_tx_start: acm_tx_mp is NULL");
return;
}
if (acm_port->acm_bulkout_state != USBSACM_PIPE_IDLE) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_tx_start: error state in bulkout endpoint");
return;
}
ASSERT(MBLKL(acm_port->acm_tx_mp) > 0);
len = min(msgdsize(acm_port->acm_tx_mp), acmp->acm_xfer_sz);
if (len == 0) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_tx_start: data len is 0");
return;
}
if ((data = allocb(len, BPRI_LO)) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_tx_start: failure in allocate memory");
return;
}
data_len = 0;
while ((data_len < len) && acm_port->acm_tx_mp) {
mp = acm_port->acm_tx_mp;
copylen = min(MBLKL(mp), len - data_len);
bcopy(mp->b_rptr, data->b_wptr, copylen);
mp->b_rptr += copylen;
data->b_wptr += copylen;
data_len += copylen;
if (MBLKL(mp) < 1) {
acm_port->acm_tx_mp = unlinkb(mp);
freeb(mp);
} else {
ASSERT(data_len == len);
}
}
if (data_len <= 0) {
freeb(data);
return;
}
acm_port->acm_bulkout_state = USBSACM_PIPE_BUSY;
mutex_exit(&acm_port->acm_port_mutex);
rval = usbsacm_send_data(acm_port, data);
mutex_enter(&acm_port->acm_port_mutex);
if (rval != USB_SUCCESS) {
acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
if (acm_port->acm_tx_mp == NULL) {
usbsacm_put_head(&acm_port->acm_tx_mp, data);
}
}
}
static int
usbsacm_send_data(usbsacm_port_t *acm_port, mblk_t *data)
{
usbsacm_state_t *acmp = acm_port->acm_device;
usb_bulk_req_t *br;
int rval;
int data_len = MBLKL(data);
USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_send_data: data address is 0x%p, length = %d",
(void *)data, data_len);
br = usb_alloc_bulk_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP);
if (br == NULL) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_send_data: alloc req failed.");
return (USB_FAILURE);
}
br->bulk_data = data;
br->bulk_len = data_len;
br->bulk_timeout = USBSACM_BULKOUT_TIMEOUT;
br->bulk_cb = usbsacm_bulkout_cb;
br->bulk_exc_cb = usbsacm_bulkout_cb;
br->bulk_client_private = (usb_opaque_t)acm_port;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
rval = usb_pipe_bulk_xfer(acm_port->acm_bulkout_ph, br, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
"usbsacm_send_data: Send Data failed.");
br->bulk_data = NULL;
usb_free_bulk_req(br);
}
return (rval);
}
static int
usbsacm_wait_tx_drain(usbsacm_port_t *acm_port, int timeout)
{
clock_t until;
int over = 0;
until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout);
while (acm_port->acm_tx_mp && !over) {
if (timeout > 0) {
over = (cv_timedwait_sig(&acm_port->acm_tx_cv,
&acm_port->acm_port_mutex, until) <= 0);
} else {
over = (cv_wait_sig(&acm_port->acm_tx_cv,
&acm_port->acm_port_mutex) == 0);
}
}
return ((acm_port->acm_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE);
}
static int
usbsacm_req_write(usbsacm_port_t *acm_port, uchar_t request, uint16_t value,
mblk_t **data)
{
usbsacm_state_t *acmp = acm_port->acm_device;
usb_ctrl_setup_t setup;
usb_cb_flags_t cb_flags;
usb_cr_t cr;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_req_write: ");
setup.bmRequestType = USBSACM_REQ_WRITE_IF;
setup.bRequest = request;
setup.wValue = value;
setup.wIndex = acm_port->acm_ctrl_if_no;
setup.wLength = ((data != NULL) && (*data != NULL)) ? MBLKL(*data) : 0;
setup.attrs = 0;
return (usb_pipe_ctrl_xfer_wait(acmp->acm_def_ph, &setup, data,
&cr, &cb_flags, 0));
}
static int
usbsacm_set_line_coding(usbsacm_port_t *acm_port, usb_cdc_line_coding_t *lc)
{
mblk_t *bp;
int ret;
if ((bp = allocb(USB_CDC_LINE_CODING_LEN, BPRI_HI)) == NULL) {
return (USB_NO_RESOURCES);
}
#ifndef __lock_lint
*((usb_cdc_line_coding_t *)bp->b_wptr) = *lc;
bp->b_wptr += USB_CDC_LINE_CODING_LEN;
#endif
ret = usbsacm_req_write(acm_port, USB_CDC_REQ_SET_LINE_CODING, 0, &bp);
if (bp != NULL) {
freeb(bp);
}
return (ret);
}
static void
usbsacm_mctl2reg(int mask, int val, uint8_t *line_ctl)
{
if (mask & TIOCM_RTS) {
if (val & TIOCM_RTS) {
*line_ctl |= USB_CDC_ACM_CONTROL_RTS;
} else {
*line_ctl &= ~USB_CDC_ACM_CONTROL_RTS;
}
}
if (mask & TIOCM_DTR) {
if (val & TIOCM_DTR) {
*line_ctl |= USB_CDC_ACM_CONTROL_DTR;
} else {
*line_ctl &= ~USB_CDC_ACM_CONTROL_DTR;
}
}
}
static int
usbsacm_reg2mctl(uint8_t line_ctl)
{
int val = 0;
if (line_ctl & USB_CDC_ACM_CONTROL_RTS) {
val |= TIOCM_RTS;
}
if (line_ctl & USB_CDC_ACM_CONTROL_DTR) {
val |= TIOCM_DTR;
}
if (line_ctl & USB_CDC_ACM_CONTROL_DSR) {
val |= TIOCM_DSR;
}
if (line_ctl & USB_CDC_ACM_CONTROL_RNG) {
val |= TIOCM_RI;
}
return (val);
}
static void
usbsacm_put_tail(mblk_t **mpp, mblk_t *bp)
{
if (*mpp) {
linkb(*mpp, bp);
} else {
*mpp = bp;
}
}
static void
usbsacm_put_head(mblk_t **mpp, mblk_t *bp)
{
if (*mpp) {
linkb(bp, *mpp);
}
*mpp = bp;
}
static int
usbsacm_create_pm_components(usbsacm_state_t *acmp)
{
dev_info_t *dip = acmp->acm_dip;
usbsacm_pm_t *pm;
uint_t pwr_states;
usb_dev_descr_t *dev_descr;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_create_pm_components: ");
if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_create_pm_components: failed");
return (USB_SUCCESS);
}
pm = acmp->acm_pm =
(usbsacm_pm_t *)kmem_zalloc(sizeof (usbsacm_pm_t), KM_SLEEP);
pm->pm_pwr_states = (uint8_t)pwr_states;
pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
dev_descr = acmp->acm_dev_data->dev_descr;
if (dev_descr->idVendor == 0x5c6 && dev_descr->idProduct == 0x3100) {
pm->pm_wakeup_enabled = 0;
} else {
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
usbsacm_destroy_pm_components(usbsacm_state_t *acmp)
{
usbsacm_pm_t *pm = acmp->acm_pm;
dev_info_t *dip = acmp->acm_dip;
int rval;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
"usbsacm_destroy_pm_components: ");
if (acmp->acm_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, acmp->acm_lh,
"usbsacm_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, acmp->acm_lh,
"usbsacm_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 (usbsacm_pm_t));
acmp->acm_pm = NULL;
}
static void
usbsacm_pm_set_busy(usbsacm_state_t *acmp)
{
usbsacm_pm_t *pm = acmp->acm_pm;
dev_info_t *dip = acmp->acm_dip;
int rval;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_pm_set_busy: pm = 0x%p", (void *)pm);
if (pm == NULL) {
return;
}
mutex_enter(&acmp->acm_mutex);
if (pm->pm_busy_cnt++ > 0) {
mutex_exit(&acmp->acm_mutex);
return;
}
(void) pm_busy_component(dip, 0);
if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) {
mutex_exit(&acmp->acm_mutex);
return;
}
pm->pm_raise_power = B_TRUE;
mutex_exit(&acmp->acm_mutex);
rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
if (rval != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_pm_set_busy: raising power failed");
}
mutex_enter(&acmp->acm_mutex);
pm->pm_raise_power = B_FALSE;
mutex_exit(&acmp->acm_mutex);
}
static void
usbsacm_pm_set_idle(usbsacm_state_t *acmp)
{
usbsacm_pm_t *pm = acmp->acm_pm;
dev_info_t *dip = acmp->acm_dip;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_pm_set_idle: ");
if (pm == NULL) {
return;
}
mutex_enter(&acmp->acm_mutex);
if (--pm->pm_busy_cnt > 0) {
mutex_exit(&acmp->acm_mutex);
return;
}
if (pm) {
(void) pm_idle_component(dip, 0);
}
mutex_exit(&acmp->acm_mutex);
}
static int
usbsacm_pwrlvl0(usbsacm_state_t *acmp)
{
int rval;
int i;
usbsacm_port_t *cur_port = acmp->acm_ports;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_pwrlvl0: ");
switch (acmp->acm_dev_state) {
case USB_DEV_ONLINE:
rval = usb_set_device_pwrlvl3(acmp->acm_dip);
ASSERT(rval == USB_SUCCESS);
if (cur_port != NULL) {
for (i = 0; i < acmp->acm_port_cnt; i++) {
cur_port = &acmp->acm_ports[i];
if (cur_port->acm_intr_ph != NULL &&
cur_port->acm_port_state !=
USBSACM_PORT_CLOSED) {
mutex_exit(&acmp->acm_mutex);
usb_pipe_stop_intr_polling(
cur_port->acm_intr_ph,
USB_FLAGS_SLEEP);
mutex_enter(&acmp->acm_mutex);
mutex_enter(&cur_port->acm_port_mutex);
cur_port->acm_intr_state =
USBSACM_PIPE_IDLE;
mutex_exit(&cur_port->acm_port_mutex);
}
}
}
acmp->acm_dev_state = USB_DEV_PWRED_DOWN;
acmp->acm_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, acmp->acm_lh,
"usbsacm_pwrlvl0: illegal device state");
return (USB_FAILURE);
}
}
static int
usbsacm_pwrlvl1(usbsacm_state_t *acmp)
{
(void) usb_set_device_pwrlvl2(acmp->acm_dip);
return (USB_FAILURE);
}
static int
usbsacm_pwrlvl2(usbsacm_state_t *acmp)
{
(void) usb_set_device_pwrlvl1(acmp->acm_dip);
return (USB_FAILURE);
}
static int
usbsacm_pwrlvl3(usbsacm_state_t *acmp)
{
int rval;
int i;
usbsacm_port_t *cur_port = acmp->acm_ports;
USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
"usbsacm_pwrlvl3: ");
switch (acmp->acm_dev_state) {
case USB_DEV_PWRED_DOWN:
rval = usb_set_device_pwrlvl0(acmp->acm_dip);
ASSERT(rval == USB_SUCCESS);
if (cur_port != NULL) {
for (i = 0; i < acmp->acm_port_cnt; i++) {
cur_port = &acmp->acm_ports[i];
if (cur_port->acm_intr_ph != NULL &&
cur_port->acm_port_state !=
USBSACM_PORT_CLOSED) {
mutex_exit(&acmp->acm_mutex);
usbsacm_pipe_start_polling(cur_port);
mutex_enter(&acmp->acm_mutex);
}
}
}
acmp->acm_dev_state = USB_DEV_ONLINE;
acmp->acm_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, acmp->acm_lh,
"usbsacm_pwrlvl3: illegal device state");
return (USB_FAILURE);
}
}
static void
usbsacm_pipe_start_polling(usbsacm_port_t *acm_port)
{
usb_intr_req_t *intr;
int rval;
usbsacm_state_t *acmp = acm_port->acm_device;
USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
"usbsacm_pipe_start_polling: ");
if (acm_port->acm_intr_ph == NULL) {
return;
}
intr = usb_alloc_intr_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP);
if (!intr) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_pipe_start_polling: alloc req failed.");
return;
}
intr->intr_attributes = USB_ATTRS_SHORT_XFER_OK |
USB_ATTRS_AUTOCLEARING;
mutex_enter(&acm_port->acm_port_mutex);
intr->intr_len = acm_port->acm_intr_ep_descr.wMaxPacketSize;
mutex_exit(&acm_port->acm_port_mutex);
intr->intr_client_private = (usb_opaque_t)acm_port;
intr->intr_cb = usbsacm_intr_cb;
intr->intr_exc_cb = usbsacm_intr_ex_cb;
rval = usb_pipe_intr_xfer(acm_port->acm_intr_ph, intr, USB_FLAGS_SLEEP);
mutex_enter(&acm_port->acm_port_mutex);
if (rval == USB_SUCCESS) {
acm_port->acm_intr_state = USBSACM_PIPE_BUSY;
} else {
usb_free_intr_req(intr);
acm_port->acm_intr_state = USBSACM_PIPE_IDLE;
USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh,
"usbsacm_pipe_start_polling: failed (%d)", rval);
}
mutex_exit(&acm_port->acm_port_mutex);
}
static void
usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
{
usbsacm_port_t *acm_port = (usbsacm_port_t *)req->intr_client_private;
usbsacm_state_t *acmp = acm_port->acm_device;
mblk_t *data = req->intr_data;
int data_len;
USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_intr_cb: ");
data_len = (data) ? MBLKL(data) : 0;
if (data_len < 8) {
USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_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(&acm_port->acm_port_mutex);
usbsacm_parse_intr_data(acm_port, data);
mutex_exit(&acm_port->acm_port_mutex);
}
static void
usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
{
usbsacm_port_t *acm_port = (usbsacm_port_t *)req->intr_client_private;
usbsacm_state_t *acmp = acm_port->acm_device;
usb_cr_t cr = req->intr_completion_reason;
USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_intr_ex_cb: ");
usb_free_intr_req(req);
if ((cr != USB_CR_PIPE_CLOSING) && (cr != USB_CR_STOPPED_POLLING)) {
mutex_enter(&acmp->acm_mutex);
if (acmp->acm_dev_state != USB_DEV_ONLINE) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_intr_ex_cb: state = %d",
acmp->acm_dev_state);
mutex_exit(&acmp->acm_mutex);
return;
}
mutex_exit(&acmp->acm_mutex);
usbsacm_pipe_start_polling(acm_port);
}
}
static void
usbsacm_parse_intr_data(usbsacm_port_t *acm_port, mblk_t *data)
{
usbsacm_state_t *acmp = acm_port->acm_device;
uint8_t bmRequestType;
uint8_t bNotification;
uint16_t wValue;
uint16_t wLength;
uint16_t wData;
USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
"usbsacm_parse_intr_data: ");
bmRequestType = data->b_rptr[0];
bNotification = data->b_rptr[1];
wValue = data->b_rptr[2];
wLength = data->b_rptr[6];
if (bmRequestType != USB_CDC_NOTIFICATION_REQUEST_TYPE) {
USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_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, acmp->acm_lh,
"usbsacm_parse_intr_data: %s network!",
wValue ? "connected to" :"disconnected from");
break;
case USB_CDC_NOTIFICATION_RESPONSE_AVAILABLE:
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: A response is a available.");
break;
case USB_CDC_NOTIFICATION_SERIAL_STATE:
if (wLength != 2) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: error data length.");
} else {
wData = data->b_rptr[8];
if (wData & USB_CDC_ACM_CONTROL_DCD) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"receiver carrier is set.");
}
if (wData & USB_CDC_ACM_CONTROL_DSR) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"transmission carrier is set.");
acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_DSR;
}
if (wData & USB_CDC_ACM_CONTROL_BREAK) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"break detection mechanism is set.");
}
if (wData & USB_CDC_ACM_CONTROL_RNG) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"ring signal detection is set.");
acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_RNG;
}
if (wData & USB_CDC_ACM_CONTROL_FRAMING) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"A framing error has occurred.");
}
if (wData & USB_CDC_ACM_CONTROL_PARITY) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"A parity error has occurred.");
}
if (wData & USB_CDC_ACM_CONTROL_OVERRUN) {
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: "
"Received data has been discarded "
"due to overrun.");
}
}
break;
default:
USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
"usbsacm_parse_intr_data: unknown notification - 0x%x!",
bNotification);
break;
}
freemsg(data);
}