/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* USB Serial CDC ACM driver
*
* 1. General Concepts
* -------------------
*
* 1.1 Overview
* ------------
* This driver supports devices that comply with the USB Communication
* Device Class Abstract Control Model (USB CDC ACM) specification,
* which is available at http://www.usb.org. Given the broad nature
* of communication equipment, this driver supports the following
* types of devices:
* + Telecommunications devices: analog modems, mobile phones;
* + Networking devices: cable modems;
* Except the above mentioned acm devices, this driver also supports
* some devices which provide modem-like function and have pairs of
*
* There are three classes that make up the definition for communication
* devices: the Communication Device Class, the Communication Interface
* Class and the Data Interface Class. The Communication Device Class
* is a device level definition and is used by the host to properly
* identify a communication device that may present several different
* types of interfaces. The Communication Interface Class defines a
* general-purpose mechanism that can be used to enable all types of
* communication services on the Universal Serial Bus (USB). The Data
* Interface Class defines a general-purpose mechanism to enable bulk
* transfer on the USB when the data does not meet the requirements
* for any other class.
*
* 1.2 Interface Definitions
* -------------------------
* Communication Class Interface is used for device management and,
* optionally, call management. Device management includes the requests
* that manage the operational state of a device, the device responses,
* and event notifications. In Abstract Control Model, the device can
* provide an internal implementation of call management over the Data
* Class interface or the Communication Class interface.
*
* The Data Class defines a data interface as an interface with a class
* type of Data Class. Data transmission on a communication device is
* not restricted to interfaces using the Data Class. Rather, a data
* defined by any other class. The data could be:
* + Some form of raw data from a communication line.
* + Legacy modem data.
* + Data using a proprietary format.
*
* 1.3 Endpoint Requirements
* -------------------------
* The Communication Class interface requires one endpoint, the management
* element. Optionally, it can have an additional endpoint, the notification
* element. The management element uses the default endpoint for all
* standard and Communication Class-specific requests. The notification
* element normally uses an interrupt endpoint.
*
* The type of endpoints belonging to a Data Class interface are restricted
* to bulk, and are expected to exist in pairs of the same type (one In and
* one Out).
*
* 1.4 ACM Function Characteristics
* --------------------------------
* With Abstract Control Model, the USB device understands standard
* V.25ter (AT) commands. The device contains a Datapump and micro-
* controller that handles the AT commands and relay controls. The
* device uses both a Data Class interface and a Communication Class.
* interface.
*
* A Communication Class interface of type Abstract Control Model will
* consist of a minimum of two pipes; one is used to implement the
* management element and the other to implement a notification element.
* In addition, the device can use two pipes to implement channels over
* which to carry unspecified data, typically over a Data Class interface.
*
* 1.5 ACM Serial Emulation
* ------------------------
* The Abstract Control Model can bridge the gap between legacy modem
* devices and USB devices. To support certain types of legacy applications,
* two problems need to be addressed. The first is supporting specific
* legacy control signals and state variables which are addressed
* directly by the various carrier modulation standards. To support these
* requirement, additional requests and notifications have been created.
* Please refer to macro, beginning with USB_CDC_REQ_* and
* USB_CDC_NOTIFICATION_*.
*
* The second significant item which is needed to bridge the gap between
* legacy modem designs and the Abstract Control Model is a means to
* multiplex call control (AT commands) on the Data Class interface.
* Legacy modem designs are limited by only supporting one channel for
* both "AT" commands and the actual data. To allow this type of
* functionality, the device must have a means to specify this limitation
* to the host.
*
* When describing this type of device, the Communication Class interface
* would still specify a Abstract Control Model, but call control would
* actually occur over the Data Class interface. To describe this
* particular characteristic, the Call Management Functional Descriptor
* would have bit D1 of bmCapabilities set.
*
* -----------------------------
* Some devices don't conform to USB CDC specification, but they provide
* supports this kind of device and exports term nodes by their pipes.
*
* 2. Implementation
* -----------------
*
* 2.1 Overview
* ------------
* It is a device-specific driver (DSD) working with USB generic serial
* driver (GSD). It implements the USB-to-serial device-specific driver
* interface (DSDI) which is offered by GSD. The interface is defined
* by ds_ops_t structure.
*
* 2.2 Port States
* ---------------
* For USB CDC ACM devices, this driver is attached to its interface,
* and exports one port for each interface. For other modem-like devices,
* this driver can dynamically find the ports in the current device,
* be operated independently.
*
* port_state:
*
* attach_ports
* |
* |
* |
* v
* USBSACM_PORT_CLOSED
* | ^
* | |
* V |
* open_port close_port
* | ^
* | |
* V |
* USBSACM_PORT_OPEN
*
*
* 2.3 Pipe States
* ---------------
* its own interrupt pipes (traced by usbsacm_port structure), which are
* opened during attach. The pipe status is as following:
*
* pipe_state:
*
* usbsacm_init_alloc_ports usbsacm_free_ports
* | ^
* v |
* |---->------ USBSACM_PORT_CLOSED ------>------+
* ^ |
* | |
* | v
* +------<------ USBSACM_PIPE_IDLE ------<------|
* | |
* V ^
* | |
* +-----------------+ +-----------+
* | |
* V ^
* | |
* | |
* | bulkin_cb/bulkout_cb
* V |
* | ^
* | |
* +----->----- USBSACM_PIPE_BUSY ---->------+
*
*
* To get its status in a timely way, acm driver can get the status
* of the device by polling the interrupt pipe.
*
*/
#include <sys/byteorder.h>
#define USBDRV_MINOR_VER 0
/* devops entry points */
void **);
/* DSD operations */
static int usbsacm_ds_attach(ds_attach_info_t *);
static void usbsacm_ds_detach(ds_hdl_t);
/* standard UART operations */
ds_port_params_t *);
/* data xfer */
/* fifo operations */
static int usbsacm_wait_tx_drain(usbsacm_port_t *, int);
/* power management and CPR */
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 *);
/* event handling */
/* pipe callbacks */
/* interrupt pipe */
/* Utility functions */
/* data transfer routines */
static int usbsacm_rx_start(usbsacm_port_t *);
static void usbsacm_tx_start(usbsacm_port_t *);
/* Initialize or release resources */
static int usbsacm_init_alloc_ports(usbsacm_state_t *);
static void usbsacm_free_ports(usbsacm_state_t *);
static void usbsacm_cleanup(usbsacm_state_t *);
/* analysis functional descriptors */
static int usbsacm_get_descriptors(usbsacm_state_t *);
/* hotplug */
static int usbsacm_restore_device_state(usbsacm_state_t *);
static int usbsacm_restore_port_state(usbsacm_state_t *);
/* pipe operations */
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 *);
/* vendor-specific commands */
mblk_t **);
static int usbsacm_set_line_coding(usbsacm_port_t *,
static int usbsacm_reg2mctl(uint8_t);
/* misc */
/*
* Standard STREAMS driver definitions
*/
0, /* module id */
"usbsacm", /* module name */
USBSER_MIN_PKTSZ, /* min pkt size */
USBSER_MAX_PKTSZ, /* max pkt size */
USBSER_HIWAT, /* hi watermark */
USBSER_LOWAT /* low watermark */
};
NULL,
NULL,
};
NULL,
NULL,
NULL,
};
};
/* cb_ops structure */
nodev, /* cb_open */
nodev, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
nodev, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
&usbsacm_str_info, /* cb_stream */
};
/* dev_ops structure */
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
usbsacm_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
usbsacm_attach, /* devo_attach */
usbsacm_detach, /* devo_detach */
nodev, /* devo_reset */
&usbsacm_cb_ops, /* devo_cb_ops */
usbser_power, /* devo_power */
ddi_quiesce_not_needed, /* devo_quiesce */
};
extern struct mod_ops mod_driverops;
/* modldrv structure */
&mod_driverops, /* type of module - driver */
"USB Serial CDC ACM driver",
};
/* modlinkage structure */
&modldrv,
};
/*
* DSD definitions
*/
NULL, /* NULL if h/w doesn't support loopback */
};
/*
* baud code -> baud rate (0 means unsupported rate)
*/
static int usbsacm_speedtab[] = {
0, /* B0 */
50, /* B50 */
75, /* B75 */
110, /* B110 */
134, /* B134 */
150, /* B150 */
200, /* B200 */
300, /* B300 */
600, /* B600 */
1200, /* B1200 */
1800, /* B1800 */
2400, /* B2400 */
4800, /* B4800 */
9600, /* B9600 */
19200, /* B19200 */
38400, /* B38400 */
57600, /* B57600 */
76800, /* B76800 */
115200, /* B115200 */
153600, /* B153600 */
230400, /* B230400 */
307200, /* B307200 */
460800, /* B460800 */
921600 /* B921600 */
};
/*
* usbsacm driver's entry points
* -----------------------------
*/
/*
* Module-wide initialization routine.
*/
int
_init(void)
{
int error;
usbser_soft_state_size(), 1);
}
return (error);
}
/*
* Module-wide tear-down routine.
*/
int
_fini(void)
{
int error;
}
return (error);
}
int
{
}
/*
* Device configuration entry points
*/
static int
{
}
static int
{
}
int
void **result)
{
}
static int
{
}
/*
* usbsacm_ds_detach:
* attach device instance, called from GSD attach
* initialize state and device, including:
* state variables, locks, device node
* device registration with system
* power management
*/
static int
{
KM_SLEEP);
/* registers usbsacm with the USBA framework */
0) != USB_SUCCESS) {
goto fail;
}
/* Get the configuration information of device */
USB_PARSE_LVL_CFG, 0) != USB_SUCCESS) {
goto fail;
}
/* Create power management components */
"usbsacm_ds_attach: create pm components failed.");
goto fail;
}
/* Register to get callbacks for USB events */
!= USB_SUCCESS) {
"usbsacm_ds_attach: register event callback failed.");
goto fail;
}
/*
* If devices conform to acm spec, driver will attach using class id;
* if not, using device id.
*/
"usbif,class2.2") == 0) ||
"usb,class2.2.0") == 0))) {
} else {
"usbsacm_ds_attach: A nonstandard device is attaching to "
"usbsacm driver. This device doesn't conform to "
"usb cdc spec.");
}
/* initialize state variables */
"usbsacm_ds_attach: initialize port structure failed.");
goto fail;
}
/* Get max data size of bulk transfer */
"usbsacm_ds_attach: get max size of transfer failed.");
goto fail;
}
return (USB_SUCCESS);
fail:
return (USB_FAILURE);
}
/*
* usbsacm_ds_detach:
* detach device instance, called from GSD detach
*/
static void
{
"usbsacm_ds_detach:");
}
/*
* usbsacm_ds_register_cb:
* GSD routine call ds_register_cb to register interrupt callbacks
* for the given port
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_register_cb: acmp = 0x%p port_num = %d",
/* Check if port number is greater than actual port number. */
"usbsacm_ds_register_cb: port number is wrong.");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* usbsacm_ds_unregister_cb:
* GSD routine call ds_unregister_cb to unregister
* interrupt callbacks for the given port
*/
/*ARGSUSED*/
static void
{
"usbsacm_ds_unregister_cb: ");
/* Release callback function */
}
}
/*
* usbsacm_ds_open_port:
* GSD routine call ds_open_port
* to open the given port
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_open_port: port_num = %d", port_num);
/* Check the status of the given port and device */
return (USB_FAILURE);
}
/* open pipes of port */
"usbsacm_ds_open_port: open pipes failed.");
return (USB_FAILURE);
}
/* data receipt */
"usbsacm_ds_open_port: start receive data failed.");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* usbsacm_ds_close_port:
* GSD routine call ds_close_port
* to close the given port
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_close_port: acmp = 0x%p", (void *)acmp);
return (rval);
}
/*
* usbsacm_ds_usb_power:
* GSD routine call ds_usb_power
* to set power level of the component
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_usb_power: ");
/* check if pm is NULL */
"usbsacm_ds_usb_power: pm is NULL.");
return (USB_FAILURE);
}
/*
* check if we are transitioning to a legal power level
*/
"usbsacm_ds_usb_power: "
"illegal power level %d, pwr_states=%x",
return (USB_FAILURE);
}
/*
* if we are about to raise power and asked to lower power, fail
*/
"usbsacm_ds_usb_power: wrong condition.");
return (USB_FAILURE);
}
/*
* Set the power status of device by request level.
*/
switch (level) {
case USB_DEV_OS_PWR_OFF:
break;
case USB_DEV_OS_PWR_1:
break;
case USB_DEV_OS_PWR_2:
break;
case USB_DEV_OS_FULL_PWR:
/*
* If usbser dev_state is DISCONNECTED or SUSPENDED, it shows
* that the usb serial device is disconnected/suspended while it
* is under power down state, now the device is powered up
* before it is reconnected/resumed. xxx_pwrlvl3() will set dev
* state to ONLINE, we need to set the dev state back to
*/
if ((rval == USB_SUCCESS) &&
((*new_state == USB_DEV_DISCONNECTED) ||
(*new_state == USB_DEV_SUSPENDED))) {
}
break;
}
return (rval);
}
/*
* usbsacm_ds_suspend:
* GSD routine call ds_suspend
* during CPR suspend
*/
static int
{
"usbsacm_ds_suspend: ");
/*
* If the device is suspended while it is under PWRED_DOWN state, we
* need to keep the PWRED_DOWN state so that it could be powered up
* later. In the mean while, usbser dev state will be changed to
* SUSPENDED state.
*/
/* set device status to suspend */
}
return (state);
}
/*
* usbsacm_ds_resume:
* GSD routine call ds_resume
* during CPR resume
*/
/*ARGSUSED*/
static int
{
int current_state;
int ret;
"usbsacm_ds_resume: ");
/* restore the status of device */
if (current_state != USB_DEV_ONLINE) {
} else {
}
return (ret);
}
/*
* usbsacm_ds_disconnect:
* GSD routine call ds_disconnect
* to disconnect USB device
*/
static int
{
"usbsacm_ds_disconnect: ");
/*
* If the device is disconnected while it is under PWRED_DOWN state, we
* need to keep the PWRED_DOWN state so that it could be powered up
* later. In the mean while, usbser dev state will be changed to
* DISCONNECTED state.
*/
/* set device status to disconnected */
}
return (state);
}
/*
* usbsacm_ds_reconnect:
* GSD routine call ds_reconnect
* to reconnect USB device
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_reconnect: ");
return (usbsacm_restore_device_state(acmp));
}
/*
* usbsacm_ds_set_port_params:
* GSD routine call ds_set_port_params
* to set one or more port parameters
*/
/*ARGSUSED*/
static int
{
int i;
int ret;
"usbsacm_ds_set_port_params: acmp = 0x%p", (void *)acmp);
/*
* If device conform to acm spec, check if it support to set port param.
*/
"usbsacm_ds_set_port_params: "
"don't support Set_Line_Coding.");
return (USB_FAILURE);
}
/* Get parameter information from ds_port_params_t */
case DS_PARAM_BAUD:
/* Data terminal rate, in bits per second. */
/* if we don't support this speed, return USB_FAILURE */
"usbsacm_ds_set_port_params: "
" error baud rate");
return (USB_FAILURE);
}
break;
case DS_PARAM_PARITY:
/* Parity Type */
} else {
}
} else {
}
break;
case DS_PARAM_STOPB:
/* Stop bit */
} else {
}
break;
case DS_PARAM_CHARSZ:
/* Data Bits */
case CS5:
break;
case CS6:
break;
case CS7:
break;
case CS8:
default:
break;
}
break;
default:
"usbsacm_ds_set_port_params: "
"parameter 0x%x isn't supported",
break;
}
}
}
/*
* If device don't conform to acm spec, return success directly.
*/
ret = USB_SUCCESS;
}
return (ret);
}
/*
* usbsacm_ds_set_modem_ctl:
* GSD routine call ds_set_modem_ctl
* to set modem control of the given port
*/
/*ARGSUSED*/
static int
{
int ret;
"usbsacm_ds_set_modem_ctl: mask = 0x%x val = 0x%x",
/*
* If device conform to acm spec, check if it support to set modem
* controls.
*/
"usbsacm_ds_set_modem_ctl: "
"don't support Set_Control_Line_State.");
return (USB_FAILURE);
}
}
/*
* If device don't conform to acm spec, return success directly.
*/
ret = USB_SUCCESS;
}
return (ret);
}
/*
* usbsacm_ds_get_modem_ctl:
* GSD routine call ds_get_modem_ctl
*/
/*ARGSUSED*/
static int
{
/*
* If device conform to acm spec, polling function can modify the value
* of acm_mctlin; else set to default value.
*/
if (acmp->acm_compatibility) {
} else {
}
"usbsacm_ds_get_modem_ctl: val = 0x%x", *valp);
return (USB_SUCCESS);
}
/*
* usbsacm_ds_tx:
* GSD routine call ds_break_ctl
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_break_ctl: ");
/*
* If device conform to acm spec, check if it support to send break.
*/
"usbsacm_ds_break_ctl: don't support send break.");
return (USB_FAILURE);
}
}
/*
* usbsacm_ds_tx:
* GSD routine call ds_tx
* to data transmit
*/
/*ARGSUSED*/
static int
{
/* sanity checks */
return (USB_SUCCESS);
}
return (USB_SUCCESS);
}
/* put mblk to tail of mblk chain */
return (USB_SUCCESS);
}
/*
* usbsacm_ds_rx:
* GSD routine call ds_rx;
* to data receipt
*/
/*ARGSUSED*/
static mblk_t *
{
"usbsacm_ds_rx: acmp = 0x%p", (void *)acmp);
return (mp);
}
/*
* usbsacm_ds_stop:
* GSD routine call ds_stop;
* but acm spec don't define this function
*/
/*ARGSUSED*/
static void
{
"usbsacm_ds_stop: don't support!");
}
/*
* usbsacm_ds_start:
* GSD routine call ds_start;
* but acm spec don't define this function
*/
/*ARGSUSED*/
static void
{
"usbsacm_ds_start: don't support!");
}
/*
* usbsacm_ds_fifo_flush:
* GSD routine call ds_fifo_flush
* to flush FIFOs
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_fifo_flush: ");
return (ret);
}
/*
* usbsacm_ds_fifo_drain:
* GSD routine call ds_fifo_drain
* to wait until empty output FIFO
*/
/*ARGSUSED*/
static int
{
"usbsacm_ds_fifo_drain: ");
"usbsacm_ds_fifo_drain: fifo drain failed.");
return (USB_FAILURE);
}
return (rval);
}
/*
* usbsacm_fifo_flush_locked:
* flush FIFOs of the given ports
*/
/*ARGSUSED*/
static int
{
"usbsacm_fifo_flush_locked: ");
/* flush transmit FIFO if DS_TX is set */
}
/* flush received FIFO if DS_RX is set */
}
return (USB_SUCCESS);
}
/*
* usbsacm_get_bulk_pipe_number:
* Calculate the number of bulk in or out pipes in current device.
*/
static int
{
int count = 0;
int i, skip;
int ep_num;
int if_num;
"usbsacm_get_bulk_pipe_number: ");
/* search each interface which have bulk endpoint */
for (i = 0; i < if_num; i++) {
/*
* search endpoints in current interface,
* which type is input parameter 'dir'
*/
/*
* If not found, skip the internal loop
* and search the next interface.
*/
break;
}
count++;
}
cur_if++;
}
return (count);
}
/*
* port management
* ---------------
* initialize, release port.
*
*
* usbsacm_init_ports_status:
* Initialize the port status for the current device.
*/
static int
{
int i, skip;
int if_num;
int intr_if_no = 0;
int ep_num;
"usbsacm_init_ports_status: acmp = 0x%p", (void *)acmp);
/* Initialize the port status to default value */
for (i = 0; i < acmp->acm_port_cnt; i++) {
}
/*
* If device conform to cdc acm spec, parse function descriptors.
*/
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* endpoints and fill port structure.
*/
/* search each interface which have bulk in and out */
for (i = 0; i < if_num; i++) {
/* search interrupt pipe. */
intr_if_no = i;
}
continue;
}
cur_port->acm_data_if_no = i;
cur_port++;
intr_if_no = 0;
}
cur_if++;
}
return (USB_SUCCESS);
}
/*
* usbsacm_init_alloc_ports:
* Allocate memory and initialize the port state for the current device.
*/
static int
{
if (acmp->acm_compatibility) {
} else {
"usbsacm_init_alloc_ports: count_in = %d, count_out = %d",
}
if (acmp->acm_port_cnt == 0) {
"usbsacm_init_alloc_ports: port count is zero.");
return (USB_FAILURE);
}
/* allocate memory for ports */
sizeof (usbsacm_port_t), KM_SLEEP);
"usbsacm_init_alloc_ports: allocate memory failed.");
return (USB_FAILURE);
}
/* fill the status of port structure. */
if (rval != USB_SUCCESS) {
}
return (rval);
}
/*
* usbsacm_free_ports:
* Release ports and deallocate memory.
*/
static void
{
int i;
"usbsacm_free_ports: ");
/* Release memory and data structure for each port */
for (i = 0; i < acmp->acm_port_cnt; i++) {
}
acmp->acm_port_cnt);
}
/*
* usbsacm_get_descriptors:
* analysis functional descriptors of acm device
*/
static int
{
int i;
int mgmt_cap = 0;
"usbsacm_get_descriptors: ");
/* set default control and data interface */
/* get current interfaces */
"usbsacm_get_descriptors: elements in if_alt is %d",
return (USB_FAILURE);
}
/*
* Based on CDC specification, ACM devices usually include the
* following function descriptors: Header, ACM, Union and Call
* Management function descriptors. This loop search tree data
* structure for each acm class descriptor.
*/
for (i = 0; i < altif->altif_n_cvs; i++) {
continue;
}
/* parse call management functional descriptor. */
}
break;
case USB_CDC_DESCR_TYPE_ACM:
/* parse ACM functional descriptor. */
}
break;
case USB_CDC_DESCR_TYPE_UNION:
/* parse Union functional descriptor. */
}
break;
default:
break;
}
}
/* For usb acm devices, it must satisfy the following options. */
"usbsacm_get_descriptors: # of interfaces %d < 2",
return (USB_FAILURE);
}
if (acm_port->acm_data_if_no == 0 &&
"usbsacm_get_descriptors: Device hasn't call management "
"descriptor and use Union Descriptor.");
}
"usbsacm_get_descriptors: control interface or "
"data interface don't match.");
return (USB_FAILURE);
}
/*
* We usually need both call and data capabilities, but
* some devices, such as Nokia mobile phones, don't provide
* call management descriptor, so we just give a warning
* message.
*/
if (((mgmt_cap & USB_CDC_CALL_MGMT_CAP_CALL_MGMT) == 0) ||
((mgmt_cap & USB_CDC_CALL_MGMT_CAP_DATA_INTERFACE) == 0)) {
"usbsacm_get_descriptors: "
"insufficient mgmt capabilities %x",
mgmt_cap);
}
"usbsacm_get_descriptors: control interface %d or "
"data interface %d out of range.",
return (USB_FAILURE);
}
/* control interface must have interrupt endpoint */
USB_EP_DIR_IN) == NULL) {
"usbsacm_get_descriptors: "
"ctrl interface %d has no interrupt endpoint",
return (USB_FAILURE);
}
/* data interface must have bulk in and out */
USB_EP_DIR_IN) == NULL) {
"usbsacm_get_descriptors: "
"data interface %d has no bulk in endpoint",
return (USB_FAILURE);
}
USB_EP_DIR_OUT) == NULL) {
"usbsacm_get_descriptors: "
"data interface %d has no bulk out endpoint",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* usbsacm_cleanup:
* Release resources of current device during detach.
*/
static void
{
"usbsacm_cleanup: ");
/* free ports */
}
/* unregister callback function */
}
/* destroy power management components */
}
/* free description of device tree. */
}
}
/* detach client device */
}
}
}
/*
* usbsacm_restore_device_state:
* restore device state after CPR resume or reconnect
*/
static int
{
int state;
"usbsacm_restore_device_state: ");
/* Check device status */
return (state);
}
/* Check if we are talking to the same device */
return (state);
}
if (state == USB_DEV_DISCONNECTED) {
"usbsacm_restore_device_state: Device has been reconnected "
"but data may have been lost");
}
/* reconnect pipes */
return (state);
}
/*
* init device state
*/
"usbsacm_restore_device_state: failed");
}
return (state);
}
/*
* usbsacm_restore_port_state:
* restore ports state after CPR resume or reconnect
*/
static int
{
"usbsacm_restore_port_state: ");
/* restore status of all ports */
for (i = 0; i < acmp->acm_port_cnt; i++) {
continue;
}
"usbsacm_restore_port_state: failed.");
}
}
return (ret);
}
/*
* pipe management
* ---------------
*
*
* usbsacm_open_port_pipes:
* Open pipes of one port and set port structure;
* Each port includes three pipes: bulk in, bulk out and interrupt.
*/
static int
{
"usbsacm_open_port_pipes: acmp = 0x%p", (void *)acmp);
/* Get bulk and interrupt endpoint data */
acm_port->acm_ctrl_if_no, 0, 0,
/* Bulk in and out must exist meanwhile. */
"usbsacm_open_port_pipes: look up bulk pipe failed in "
"interface %d port %d",
return (USB_FAILURE);
}
/*
* If device conform to acm spec, it must have an interrupt pipe
* for this port.
*/
"usbsacm_open_port_pipes: look up interrupt pipe failed in "
return (USB_FAILURE);
}
/* Open bulk in endpoint */
"usbsacm_open_port_pipes: open bulkin pipe failed!");
return (USB_FAILURE);
}
/* Open bulk out endpoint */
"usbsacm_open_port_pipes: open bulkout pipe failed!");
return (USB_FAILURE);
}
/* Open interrupt endpoint if found. */
"usbsacm_open_port_pipes: "
"open control pipe failed");
return (USB_FAILURE);
}
}
/* initialize the port structure. */
}
}
return (rval);
}
/*
* usbsacm_close_port_pipes:
* Close pipes of one port and reset port structure to closed;
* Each port includes three pipes: bulk in, bulk out and interrupt.
*/
static void
{
"usbsacm_close_port_pipes: acm_bulkin_state = %d",
/*
* Check the status of the given port. If port is closing or closed,
* return directly.
*/
"usbsacm_close_port_pipes: port is closing or has closed");
return;
}
/* Close pipes */
USB_FLAGS_SLEEP, 0, 0);
USB_FLAGS_SLEEP, 0, 0);
USB_FLAGS_SLEEP, 0, 0);
USB_FLAGS_SLEEP, 0, 0);
}
/* Reset the status of pipes to closed */
}
"usbsacm_close_port_pipes: port has been closed.");
}
/*
* usbsacm_close_pipes:
* close all opened pipes of current devices.
*/
static void
{
int i;
"usbsacm_close_pipes: ");
/* Close all ports */
for (i = 0; i < acmp->acm_port_cnt; i++) {
}
}
/*
* usbsacm_disconnect_pipes:
* this function just call usbsacm_close_pipes.
*/
static void
{
"usbsacm_disconnect_pipes: ");
}
/*
* usbsacm_reconnect_pipes:
* reconnect pipes in CPR resume or reconnect
*/
static int
{
int i;
"usbsacm_reconnect_pipes: ");
/* reopen all ports of current device. */
for (i = 0; i < acmp->acm_port_cnt; i++) {
/*
* If port status is open, reopen it;
* else retain the current status.
*/
"usbsacm_reconnect_pipes: "
"open port %d failed.", i);
return (USB_FAILURE);
}
}
}
return (USB_SUCCESS);
}
/*
* usbsacm_bulkin_cb:
* Bulk In regular and exeception callback;
* USBA framework will call this callback
* after deal with bulkin request.
*/
/*ARGSUSED*/
static void
{
int data_len;
"usbsacm_bulkin_cb: "
"acm_bulkin_state = %d acm_port_state = %d data_len = %d",
/* prevent USBA from freeing data along with the request */
/* save data on the receive list */
/* invoke GSD receive callback */
}
}
/* receive more */
"usbsacm_bulkin_cb: restart rx fail "
}
}
}
/*
* usbsacm_bulkout_cb:
* Bulk Out regular and exeception callback;
* USBA framework will call this callback function
* after deal with bulkout request.
*/
/*ARGSUSED*/
static void
{
int data_len;
"usbsacm_bulkout_cb: acmp = 0x%p", (void *)acmp);
/* put untransferred residue back on the transfer list */
}
/* invoke GSD transmit callback */
}
/* send more */
} else {
}
}
/*
* usbsacm_rx_start:
* start data receipt
*/
static int
{
int data_len;
"usbsacm_rx_start: acm_xfer_sz = 0x%lx acm_bulkin_size = 0x%lx",
/*
* Qualcomm CDMA card won't response the first request,
* if the following code don't multiply by 2.
*/
"usbsacm_rx_start: allocate bulk request failed");
return (USB_FAILURE);
}
/* initialize bulk in request. */
if (rval != USB_SUCCESS) {
"usbsacm_rx_start: bulk transfer failed %d", rval);
}
return (rval);
}
/*
* usbsacm_tx_start:
* start data transmit
*/
static void
{
int rval;
"usbsacm_tx_start: ");
/* check the transmitted data. */
"usbsacm_tx_start: acm_tx_mp is NULL");
return;
}
/* check pipe status */
"usbsacm_tx_start: error state in bulkout endpoint");
return;
}
/* send as much data as port can receive */
if (len == 0) {
"usbsacm_tx_start: data len is 0");
return;
}
/* allocate memory for sending data. */
"usbsacm_tx_start: failure in allocate memory");
return;
}
/*
* copy no more than 'len' bytes from mblk chain to transmit mblk 'data'
*/
data_len = 0;
/* Get the first mblk from chain. */
} else {
}
}
if (data_len <= 0) {
return;
}
/* send request. */
/*
* If send failed, retransmit data when acm_tx_mp is null.
*/
if (rval != USB_SUCCESS) {
}
}
}
/*
* usbsacm_send_data:
* data transfer
*/
static int
{
int rval;
"usbsacm_send_data: data address is 0x%p, length = %d",
"usbsacm_send_data: alloc req failed.");
return (USB_FAILURE);
}
/* initialize the bulk out request */
if (rval != USB_SUCCESS) {
"usbsacm_send_data: Send Data failed.");
/*
* Don't free it in usb_free_bulk_req because it will
* be linked in usbsacm_put_head
*/
}
return (rval);
}
/*
* usbsacm_wait_tx_drain:
* wait until local tx buffer drains.
* 'timeout' is in seconds, zero means wait forever
*/
static int
{
int over = 0;
if (timeout > 0) {
} else {
&acm_port->acm_port_mutex) == 0);
}
}
}
/*
* usbsacm_req_write:
* send command over control pipe
*/
static int
{
"usbsacm_req_write: ");
/* initialize the control request. */
}
/*
* usbsacm_set_line_coding:
* Send USB_CDC_REQ_SET_LINE_CODING request
*/
static int
{
int ret;
/* allocate mblk and copy supplied structure into it */
return (USB_NO_RESOURCES);
}
#ifndef __lock_lint /* warlock gets confused here */
/* LINTED E_BAD_PTR_CAST_ALIGN */
#endif
}
return (ret);
}
/*
* usbsacm_mctl2reg:
* Set Modem control status
*/
static void
{
} else {
}
}
} else {
}
}
}
/*
* usbsacm_reg2mctl:
* Get Modem control status
*/
static int
{
int val = 0;
if (line_ctl & USB_CDC_ACM_CONTROL_RTS) {
}
if (line_ctl & USB_CDC_ACM_CONTROL_DTR) {
}
if (line_ctl & USB_CDC_ACM_CONTROL_DSR) {
}
if (line_ctl & USB_CDC_ACM_CONTROL_RNG) {
}
return (val);
}
/*
* misc routines
* -------------
*
*/
/*
* usbsacm_put_tail:
* link a message block to tail of message
* account for the case when message is null
*/
static void
{
if (*mpp) {
} else {
}
}
/*
* usbsacm_put_head:
* put a message block at the head of the message
* account for the case when message is null
*/
static void
{
if (*mpp) {
}
}
/*
* power management
* ----------------
*
* usbsacm_create_pm_components:
* create PM components
*/
static int
{
"usbsacm_create_pm_components: ");
"usbsacm_create_pm_components: failed");
return (USB_SUCCESS);
}
/*
* Qualcomm CDMA card won't response the following control commands
* after receive USB_REMOTE_WAKEUP_ENABLE. So we just set
* pm_wakeup_enable to 0 for this specific device.
*/
pm->pm_wakeup_enabled = 0;
} else {
}
return (USB_SUCCESS);
}
/*
* usbsacm_destroy_pm_components:
* destroy PM components
*/
static void
{
int rval;
"usbsacm_destroy_pm_components: ");
if (pm->pm_wakeup_enabled) {
if (rval != DDI_SUCCESS) {
"usbsacm_destroy_pm_components: "
"raising power failed (%d)", rval);
}
if (rval != USB_SUCCESS) {
"usbsacm_destroy_pm_components: "
"disable remote wakeup failed (%d)", rval);
}
}
}
}
/*
* usbsacm_pm_set_busy:
* mark device busy and raise power
*/
static void
{
int rval;
"usbsacm_pm_set_busy: pm = 0x%p", (void *)pm);
return;
}
/* if already marked busy, just increment the counter */
if (pm->pm_busy_cnt++ > 0) {
return;
}
(void) pm_busy_component(dip, 0);
return;
}
/* need to raise power */
if (rval != DDI_SUCCESS) {
"usbsacm_pm_set_busy: raising power failed");
}
}
/*
* usbsacm_pm_set_idle:
* mark device idle
*/
static void
{
"usbsacm_pm_set_idle: ");
return;
}
/*
* if more ports use the device, do not mark as yet
*/
if (--pm->pm_busy_cnt > 0) {
return;
}
if (pm) {
(void) pm_idle_component(dip, 0);
}
}
/*
* usbsacm_pwrlvl0:
* Functions to handle power transition for OS levels 0 -> 3
* The same level as OS state, different from USB state
*/
static int
{
int rval;
int i;
"usbsacm_pwrlvl0: ");
switch (acmp->acm_dev_state) {
case USB_DEV_ONLINE:
/* issue USB D3 command to the device */
for (i = 0; i < acmp->acm_port_cnt; i++) {
}
}
}
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
/* allow a disconnect/cpr'ed device to go to lower power */
return (USB_SUCCESS);
case USB_DEV_PWRED_DOWN:
default:
"usbsacm_pwrlvl0: illegal device state");
return (USB_FAILURE);
}
}
/*
* usbsacm_pwrlvl1:
* Functions to handle power transition for OS levels 1 -> 2
*/
static int
{
/* issue USB D2 command to the device */
return (USB_FAILURE);
}
/*
* usbsacm_pwrlvl2:
* Functions to handle power transition for OS levels 2 -> 1
*/
static int
{
/* issue USB D1 command to the device */
return (USB_FAILURE);
}
/*
* usbsacm_pwrlvl3:
* Functions to handle power transition for OS levels 3 -> 0
* The same level as OS state, different from USB state
*/
static int
{
int rval;
int i;
"usbsacm_pwrlvl3: ");
switch (acmp->acm_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
for (i = 0; i < acmp->acm_port_cnt; i++) {
}
}
}
/* FALLTHRU */
case USB_DEV_ONLINE:
/* we are already in full power */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
return (USB_SUCCESS);
default:
"usbsacm_pwrlvl3: illegal device state");
return (USB_FAILURE);
}
}
/*
* usbsacm_pipe_start_polling:
* start polling on the interrupt pipe
*/
static void
{
int rval;
"usbsacm_pipe_start_polling: ");
return;
}
/*
* If it is in interrupt context, usb_alloc_intr_req will return NULL if
* called with SLEEP flag.
*/
if (!intr) {
"usbsacm_pipe_start_polling: alloc req failed.");
return;
}
/* initialize the interrupt request. */
if (rval == USB_SUCCESS) {
} else {
"usbsacm_pipe_start_polling: failed (%d)", rval);
}
}
/*
* usbsacm_intr_cb:
* interrupt pipe normal callback
*/
/*ARGSUSED*/
static void
{
int data_len;
"usbsacm_intr_cb: ");
/* check data length */
if (data_len < 8) {
"usbsacm_intr_cb: %d packet too short", data_len);
return;
}
/* parse interrupt data. */
}
/*
* usbsacm_intr_ex_cb:
* interrupt pipe exception callback
*/
/*ARGSUSED*/
static void
{
"usbsacm_intr_ex_cb: ");
/*
* If completion reason isn't USB_CR_PIPE_CLOSING and
* USB_CR_STOPPED_POLLING, restart polling.
*/
"usbsacm_intr_ex_cb: state = %d",
return;
}
}
}
/*
* usbsacm_parse_intr_data:
* Parse data received from interrupt callback
*/
static void
{
"usbsacm_parse_intr_data: ");
/*
* If Notification type is NETWORK_CONNECTION, wValue is 0 or 1,
* mLength is 0. If Notification type is SERIAL_TYPE, mValue is 0,
* mLength is 2. So we directly get the value from the byte.
*/
"usbsacm_parse_intr_data: unknown request type - 0x%x",
return;
}
/*
* Check the return value of device
*/
switch (bNotification) {
"usbsacm_parse_intr_data: %s network!",
break;
"usbsacm_parse_intr_data: A response is a available.");
break;
/* check the parameter's length. */
if (wLength != 2) {
"usbsacm_parse_intr_data: error data length.");
} else {
/*
* The Data field is a bitmapped value that contains
* the current state of carrier detect, transmission
* carrier, break, ring signal and device overrun
* error.
*/
/*
* Check the serial state of the current port.
*/
if (wData & USB_CDC_ACM_CONTROL_DCD) {
"usbsacm_parse_intr_data: "
"receiver carrier is set.");
}
if (wData & USB_CDC_ACM_CONTROL_DSR) {
"usbsacm_parse_intr_data: "
"transmission carrier is set.");
}
if (wData & USB_CDC_ACM_CONTROL_BREAK) {
"usbsacm_parse_intr_data: "
"break detection mechanism is set.");
}
if (wData & USB_CDC_ACM_CONTROL_RNG) {
"usbsacm_parse_intr_data: "
"ring signal detection is set.");
}
if (wData & USB_CDC_ACM_CONTROL_FRAMING) {
"usbsacm_parse_intr_data: "
"A framing error has occurred.");
}
if (wData & USB_CDC_ACM_CONTROL_PARITY) {
"usbsacm_parse_intr_data: "
"A parity error has occurred.");
}
if (wData & USB_CDC_ACM_CONTROL_OVERRUN) {
"usbsacm_parse_intr_data: "
"Received data has been discarded "
"due to overrun.");
}
}
break;
default:
"usbsacm_parse_intr_data: unknown notification - 0x%x!",
break;
}
}