190f05028Syq /*
290f05028Syq  * CDDL HEADER START
390f05028Syq  *
490f05028Syq  * The contents of this file are subject to the terms of the
590f05028Syq  * Common Development and Distribution License (the "License").
690f05028Syq  * You may not use this file except in compliance with the License.
790f05028Syq  *
890f05028Syq  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
990f05028Syq  * or http://www.opensolaris.org/os/licensing.
1090f05028Syq  * See the License for the specific language governing permissions
1190f05028Syq  * and limitations under the License.
1290f05028Syq  *
1390f05028Syq  * When distributing Covered Code, include this CDDL HEADER in each
1490f05028Syq  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1590f05028Syq  * If applicable, add the following below this CDDL HEADER, with the
1690f05028Syq  * fields enclosed by brackets "[]" replaced with your own identifying
1790f05028Syq  * information: Portions Copyright [yyyy] [name of copyright owner]
1890f05028Syq  *
1990f05028Syq  * CDDL HEADER END
2090f05028Syq  */
21de81e71eSTim Marsland 
2290f05028Syq /*
239e37f2b5SRaymond Chen  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
2490f05028Syq  * Use is subject to license terms.
2590f05028Syq  */
2680727282SMike Zeller /*
2780727282SMike Zeller  * Copyright 2019 Joyent, Inc.
2880727282SMike Zeller  */
2990f05028Syq 
3090f05028Syq /*
3190f05028Syq  * USB Serial CDC ACM driver
3290f05028Syq  *
3390f05028Syq  * 1. General Concepts
3490f05028Syq  * -------------------
3590f05028Syq  *
3690f05028Syq  * 1.1 Overview
3790f05028Syq  * ------------
3890f05028Syq  * This driver supports devices that comply with the USB Communication
3990f05028Syq  * Device Class Abstract Control Model (USB CDC ACM) specification,
4090f05028Syq  * which is available at http://www.usb.org. Given the broad nature
4190f05028Syq  * of communication equipment, this driver supports the following
4290f05028Syq  * types of devices:
434ee52f77Slg  *	+ Telecommunications devices: analog modems, mobile phones;
444ee52f77Slg  *	+ Networking devices: cable modems;
4590f05028Syq  * Except the above mentioned acm devices, this driver also supports
4690f05028Syq  * some devices which provide modem-like function and have pairs of
4790f05028Syq  * bulk in/out pipes.
4890f05028Syq  *
4990f05028Syq  * There are three classes that make up the definition for communication
5090f05028Syq  * devices: the Communication Device Class, the Communication Interface
5190f05028Syq  * Class and the Data Interface Class. The Communication Device Class
5290f05028Syq  * is a device level definition and is used by the host to properly
5390f05028Syq  * identify a communication device that may present several different
5490f05028Syq  * types of interfaces. The Communication Interface Class defines a
5590f05028Syq  * general-purpose mechanism that can be used to enable all types of
5690f05028Syq  * communication services on the Universal Serial Bus (USB). The Data
5790f05028Syq  * Interface Class defines a general-purpose mechanism to enable bulk
5890f05028Syq  * transfer on the USB when the data does not meet the requirements
5990f05028Syq  * for any other class.
6090f05028Syq  *
6190f05028Syq  * 1.2 Interface Definitions
6290f05028Syq  * -------------------------
6390f05028Syq  * Communication Class Interface is used for device management and,
6490f05028Syq  * optionally, call management. Device management includes the requests
6590f05028Syq  * that manage the operational state of a device, the device responses,
6690f05028Syq  * and event notifications. In Abstract Control Model, the device can
6790f05028Syq  * provide an internal implementation of call management over the Data
6890f05028Syq  * Class interface or the Communication Class interface.
6990f05028Syq  *
7090f05028Syq  * The Data Class defines a data interface as an interface with a class
7190f05028Syq  * type of Data Class. Data transmission on a communication device is
7290f05028Syq  * not restricted to interfaces using the Data Class. Rather, a data
7390f05028Syq  * interface is used to transmit and/or receive data that is not
7490f05028Syq  * defined by any other class. The data could be:
754ee52f77Slg  *	+ Some form of raw data from a communication line.
764ee52f77Slg  *	+ Legacy modem data.
774ee52f77Slg  *	+ Data using a proprietary format.
7890f05028Syq  *
7990f05028Syq  * 1.3 Endpoint Requirements
8090f05028Syq  * -------------------------
8190f05028Syq  * The Communication Class interface requires one endpoint, the management
8290f05028Syq  * element. Optionally, it can have an additional endpoint, the notification
8390f05028Syq  * element. The management element uses the default endpoint for all
8490f05028Syq  * standard and Communication Class-specific requests. The notification
8590f05028Syq  * element normally uses an interrupt endpoint.
8690f05028Syq  *
8790f05028Syq  * The type of endpoints belonging to a Data Class interface are restricted
8890f05028Syq  * to bulk, and are expected to exist in pairs of the same type (one In and
8990f05028Syq  * one Out).
9090f05028Syq  *
9190f05028Syq  * 1.4 ACM Function Characteristics
9290f05028Syq  * --------------------------------
9390f05028Syq  * With Abstract Control Model, the USB device understands standard
9490f05028Syq  * V.25ter (AT) commands. The device contains a Datapump and micro-
9590f05028Syq  * controller that handles the AT commands and relay controls. The
9690f05028Syq  * device uses both a Data Class interface and a Communication Class.
9790f05028Syq  * interface.
9890f05028Syq  *
9990f05028Syq  * A Communication Class interface of type Abstract Control Model will
10090f05028Syq  * consist of a minimum of two pipes; one is used to implement the
10190f05028Syq  * management element and the other to implement a notification element.
10290f05028Syq  * In addition, the device can use two pipes to implement channels over
10390f05028Syq  * which to carry unspecified data, typically over a Data Class interface.
10490f05028Syq  *
10590f05028Syq  * 1.5 ACM Serial Emulation
10690f05028Syq  * ------------------------
10790f05028Syq  * The Abstract Control Model can bridge the gap between legacy modem
10890f05028Syq  * devices and USB devices. To support certain types of legacy applications,
10990f05028Syq  * two problems need to be addressed. The first is supporting specific
11090f05028Syq  * legacy control signals and state variables which are addressed
11190f05028Syq  * directly by the various carrier modulation standards. To support these
11290f05028Syq  * requirement, additional requests and notifications have been created.
11390f05028Syq  * Please refer to macro, beginning with USB_CDC_REQ_* and
11490f05028Syq  * USB_CDC_NOTIFICATION_*.
11590f05028Syq  *
11690f05028Syq  * The second significant item which is needed to bridge the gap between
11790f05028Syq  * legacy modem designs and the Abstract Control Model is a means to
11890f05028Syq  * multiplex call control (AT commands) on the Data Class interface.
11990f05028Syq  * Legacy modem designs are limited by only supporting one channel for
12090f05028Syq  * both "AT" commands and the actual data. To allow this type of
12190f05028Syq  * functionality, the device must have a means to specify this limitation
12290f05028Syq  * to the host.
12390f05028Syq  *
12490f05028Syq  * When describing this type of device, the Communication Class interface
12590f05028Syq  * would still specify a Abstract Control Model, but call control would
12690f05028Syq  * actually occur over the Data Class interface. To describe this
12790f05028Syq  * particular characteristic, the Call Management Functional Descriptor
12890f05028Syq  * would have bit D1 of bmCapabilities set.
12990f05028Syq  *
13090f05028Syq  * 1.6 Other Bulk In/Out Devices
13190f05028Syq  * -----------------------------
13290f05028Syq  * Some devices don't conform to USB CDC specification, but they provide
13390f05028Syq  * modem-like function and have pairs of bulk in/out pipes. This driver
13490f05028Syq  * supports this kind of device and exports term nodes by their pipes.
13590f05028Syq  *
13690f05028Syq  * 2. Implementation
13790f05028Syq  * -----------------
13890f05028Syq  *
13990f05028Syq  * 2.1 Overview
14090f05028Syq  * ------------
14190f05028Syq  * It is a device-specific driver (DSD) working with USB generic serial
14290f05028Syq  * driver (GSD). It implements the USB-to-serial device-specific driver
14390f05028Syq  * interface (DSDI) which is offered by GSD. The interface is defined
14490f05028Syq  * by ds_ops_t structure.
14590f05028Syq  *
14690f05028Syq  * 2.2 Port States
14790f05028Syq  * ---------------
14890f05028Syq  * For USB CDC ACM devices, this driver is attached to its interface,
14990f05028Syq  * and exports one port for each interface. For other modem-like devices,
15090f05028Syq  * this driver can dynamically find the ports in the current device,
15190f05028Syq  * and export one port for each pair bulk in/out pipes. Each port can
15290f05028Syq  * be operated independently.
15390f05028Syq  *
15490f05028Syq  * port_state:
15590f05028Syq  *
15690f05028Syq  *		attach_ports
15790f05028Syq  *		    |
15890f05028Syq  *		    |
15990f05028Syq  *		    |
16090f05028Syq  *		    v
16190f05028Syq  *	    USBSACM_PORT_CLOSED
16290f05028Syq  *		|	    ^
16390f05028Syq  *		|	    |
16490f05028Syq  *		V	    |
16590f05028Syq  *	   open_port	close_port
16690f05028Syq  *		|	    ^
16790f05028Syq  *		|	    |
16890f05028Syq  *		V	    |
16990f05028Syq  *	      USBSACM_PORT_OPEN
17090f05028Syq  *
17190f05028Syq  *
17290f05028Syq  * 2.3 Pipe States
17390f05028Syq  * ---------------
17490f05028Syq  * Each port has its own bulk in/out pipes and some ports could also have
17590f05028Syq  * its own interrupt pipes (traced by usbsacm_port structure), which are
17690f05028Syq  * opened during attach. The pipe status is as following:
17790f05028Syq  *
17890f05028Syq  * pipe_state:
17990f05028Syq  *
18090f05028Syq  *		usbsacm_init_alloc_ports  usbsacm_free_ports
18190f05028Syq  *				|		^
18290f05028Syq  *				v		|
1834ee52f77Slg  *		  |---->------ USBSACM_PORT_CLOSED ------>------+
18490f05028Syq  *		  ^						|
18590f05028Syq  *		  |				reconnect/resume/open_port
18690f05028Syq  *		  |						|
18790f05028Syq  *    disconnect/suspend/close_port				|
18890f05028Syq  *		  |						v
18990f05028Syq  *		  +------<------ USBSACM_PIPE_IDLE ------<------|
19090f05028Syq  *				    |		|
19190f05028Syq  *				    V		^
19290f05028Syq  *				    |		|
19390f05028Syq  *		  +-----------------+		+-----------+
19490f05028Syq  *		  |					    |
19590f05028Syq  *		  V					    ^
19690f05028Syq  *		  |					    |
19790f05028Syq  *	rx_start/tx_start----->------failed------->---------|
19890f05028Syq  *		  |					    |
19990f05028Syq  *		  |				bulkin_cb/bulkout_cb
20090f05028Syq  *		  V					    |
20190f05028Syq  *		  |					    ^
20290f05028Syq  *		  |					    |
20390f05028Syq  *		  +----->----- USBSACM_PIPE_BUSY ---->------+
20490f05028Syq  *
20590f05028Syq  *
20690f05028Syq  * To get its status in a timely way, acm driver can get the status
20790f05028Syq  * of the device by polling the interrupt pipe.
20890f05028Syq  *
20990f05028Syq  */
21090f05028Syq 
21190f05028Syq #include <sys/types.h>
21290f05028Syq #include <sys/param.h>
21390f05028Syq #include <sys/conf.h>
21490f05028Syq #include <sys/stream.h>
21590f05028Syq #include <sys/strsun.h>
21690f05028Syq #include <sys/termio.h>
21790f05028Syq #include <sys/termiox.h>
21890f05028Syq #include <sys/ddi.h>
21990f05028Syq #include <sys/sunddi.h>
22090f05028Syq #include <sys/byteorder.h>
22190f05028Syq #define	USBDRV_MAJOR_VER	2
22290f05028Syq #define	USBDRV_MINOR_VER	0
22390f05028Syq #include <sys/usb/usba.h>
22480727282SMike Zeller #include <sys/usb/usbdevs.h>
22590f05028Syq #include <sys/usb/usba/usba_types.h>
22690f05028Syq #include <sys/usb/clients/usbser/usbser.h>
22790f05028Syq #include <sys/usb/clients/usbser/usbser_dsdi.h>
22890f05028Syq #include <sys/usb/clients/usbcdc/usb_cdc.h>
22990f05028Syq #include <sys/usb/clients/usbser/usbsacm/usbsacm.h>
23090f05028Syq 
23190f05028Syq /* devops entry points */
23290f05028Syq static int	usbsacm_attach(dev_info_t *, ddi_attach_cmd_t);
23390f05028Syq static int	usbsacm_detach(dev_info_t *, ddi_detach_cmd_t);
2344ee52f77Slg static int	usbsacm_getinfo(dev_info_t *, ddi_info_cmd_t, void *,
23590f05028Syq 		void **);
23690f05028Syq static int	usbsacm_open(queue_t *, dev_t *, int, int, cred_t *);
23790f05028Syq 
23890f05028Syq /* DSD operations */
23990f05028Syq static int	usbsacm_ds_attach(ds_attach_info_t *);
24090f05028Syq static void	usbsacm_ds_detach(ds_hdl_t);
24190f05028Syq static int	usbsacm_ds_register_cb(ds_hdl_t, uint_t, ds_cb_t *);
24290f05028Syq static void	usbsacm_ds_unregister_cb(ds_hdl_t, uint_t);
24390f05028Syq static int	usbsacm_ds_open_port(ds_hdl_t, uint_t);
24490f05028Syq static int	usbsacm_ds_close_port(ds_hdl_t, uint_t);
24590f05028Syq 
24690f05028Syq /* standard UART operations */
24790f05028Syq static int	usbsacm_ds_set_port_params(ds_hdl_t, uint_t,
24890f05028Syq 		ds_port_params_t *);
24990f05028Syq static int	usbsacm_ds_set_modem_ctl(ds_hdl_t, uint_t, int, int);
25090f05028Syq static int	usbsacm_ds_get_modem_ctl(ds_hdl_t, uint_t, int, int *);
25190f05028Syq static int	usbsacm_ds_break_ctl(ds_hdl_t, uint_t, int);
25290f05028Syq 
25390f05028Syq /* data xfer */
25490f05028Syq static int	usbsacm_ds_tx(ds_hdl_t, uint_t, mblk_t *);
25590f05028Syq static mblk_t	*usbsacm_ds_rx(ds_hdl_t, uint_t);
25690f05028Syq static void	usbsacm_ds_stop(ds_hdl_t, uint_t, int);
25790f05028Syq static void	usbsacm_ds_start(ds_hdl_t, uint_t, int);
25890f05028Syq 
25990f05028Syq /* fifo operations */
26090f05028Syq static int	usbsacm_ds_fifo_flush(ds_hdl_t, uint_t, int);
26190f05028Syq static int	usbsacm_ds_fifo_drain(ds_hdl_t, uint_t, int);
26290f05028Syq static int	usbsacm_wait_tx_drain(usbsacm_port_t *, int);
26390f05028Syq static int	usbsacm_fifo_flush_locked(usbsacm_state_t *, uint_t, int);
26490f05028Syq 
26590f05028Syq /* power management and CPR */
26690f05028Syq static int	usbsacm_ds_suspend(ds_hdl_t);
26790f05028Syq static int	usbsacm_ds_resume(ds_hdl_t);
26890f05028Syq static int	usbsacm_ds_disconnect(ds_hdl_t);
26990f05028Syq static int	usbsacm_ds_reconnect(ds_hdl_t);
27090f05028Syq static int	usbsacm_ds_usb_power(ds_hdl_t, int, int, int *);
27190f05028Syq static int	usbsacm_create_pm_components(usbsacm_state_t *);
27290f05028Syq static void	usbsacm_destroy_pm_components(usbsacm_state_t *);
27390f05028Syq static void	usbsacm_pm_set_busy(usbsacm_state_t *);
27490f05028Syq static void	usbsacm_pm_set_idle(usbsacm_state_t *);
27590f05028Syq static int	usbsacm_pwrlvl0(usbsacm_state_t *);
27690f05028Syq static int	usbsacm_pwrlvl1(usbsacm_state_t *);
27790f05028Syq static int	usbsacm_pwrlvl2(usbsacm_state_t *);
27890f05028Syq static int	usbsacm_pwrlvl3(usbsacm_state_t *);
27990f05028Syq 
28090f05028Syq /* event handling */
28190f05028Syq /* pipe callbacks */
28290f05028Syq static void	usbsacm_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
28390f05028Syq static void	usbsacm_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);
28490f05028Syq 
28590f05028Syq /* interrupt pipe */
28690f05028Syq static void	usbsacm_pipe_start_polling(usbsacm_port_t *acmp);
28790f05028Syq static void	usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
28890f05028Syq static void	usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req);
28990f05028Syq static void	usbsacm_parse_intr_data(usbsacm_port_t *acmp, mblk_t *data);
29090f05028Syq 
29190f05028Syq /* Utility functions */
29290f05028Syq /* data transfer routines */
29390f05028Syq static int	usbsacm_rx_start(usbsacm_port_t *);
29490f05028Syq static void	usbsacm_tx_start(usbsacm_port_t *);
29590f05028Syq static int	usbsacm_send_data(usbsacm_port_t *, mblk_t *);
29690f05028Syq 
29790f05028Syq /* Initialize or release resources */
29890f05028Syq static int	usbsacm_init_alloc_ports(usbsacm_state_t *);
29990f05028Syq static void	usbsacm_free_ports(usbsacm_state_t *);
30090f05028Syq static void	usbsacm_cleanup(usbsacm_state_t *);
30190f05028Syq 
30290f05028Syq /* analysis functional descriptors */
30390f05028Syq static int	usbsacm_get_descriptors(usbsacm_state_t *);
30490f05028Syq 
30590f05028Syq /* hotplug */
30690f05028Syq static int	usbsacm_restore_device_state(usbsacm_state_t *);
30790f05028Syq static int	usbsacm_restore_port_state(usbsacm_state_t *);
30890f05028Syq 
30990f05028Syq /* pipe operations */
31090f05028Syq static int	usbsacm_open_port_pipes(usbsacm_port_t *);
31190f05028Syq static void	usbsacm_close_port_pipes(usbsacm_port_t *);
31290f05028Syq static void	usbsacm_close_pipes(usbsacm_state_t *);
31390f05028Syq static void	usbsacm_disconnect_pipes(usbsacm_state_t *);
31490f05028Syq static int	usbsacm_reconnect_pipes(usbsacm_state_t *);
31590f05028Syq 
31690f05028Syq /* vendor-specific commands */
31790f05028Syq static int	usbsacm_req_write(usbsacm_port_t *, uchar_t, uint16_t,
31890f05028Syq 		mblk_t **);
31990f05028Syq static int	usbsacm_set_line_coding(usbsacm_port_t *,
32090f05028Syq 		usb_cdc_line_coding_t *);
32190f05028Syq static void	usbsacm_mctl2reg(int mask, int val, uint8_t *);
32290f05028Syq static int	usbsacm_reg2mctl(uint8_t);
32390f05028Syq 
32490f05028Syq /* misc */
32590f05028Syq static void	usbsacm_put_tail(mblk_t **, mblk_t *);
32690f05028Syq static void	usbsacm_put_head(mblk_t **, mblk_t *);
32790f05028Syq 
32890f05028Syq 
32990f05028Syq /*
33090f05028Syq  * Standard STREAMS driver definitions
33190f05028Syq  */
33290f05028Syq struct module_info usbsacm_modinfo = {
33390f05028Syq 	0,			/* module id */
33490f05028Syq 	"usbsacm",		/* module name */
33590f05028Syq 	USBSER_MIN_PKTSZ,	/* min pkt size */
33690f05028Syq 	USBSER_MAX_PKTSZ,	/* max pkt size */
33790f05028Syq 	USBSER_HIWAT,		/* hi watermark */
33890f05028Syq 	USBSER_LOWAT		/* low watermark */
33990f05028Syq };
34090f05028Syq 
34190f05028Syq static struct qinit usbsacm_rinit = {
34290f05028Syq 	NULL,
34390f05028Syq 	usbser_rsrv,
34490f05028Syq 	usbsacm_open,
34590f05028Syq 	usbser_close,
34690f05028Syq 	NULL,
34790f05028Syq 	&usbsacm_modinfo,
34890f05028Syq 	NULL
34990f05028Syq };
35090f05028Syq 
35190f05028Syq static struct qinit usbsacm_winit = {
35290f05028Syq 	usbser_wput,
35390f05028Syq 	usbser_wsrv,
35490f05028Syq 	NULL,
35590f05028Syq 	NULL,
35690f05028Syq 	NULL,
35790f05028Syq 	&usbsacm_modinfo,
35890f05028Syq 	NULL
35990f05028Syq };
36090f05028Syq 
36190f05028Syq 
36290f05028Syq struct streamtab usbsacm_str_info = {
36390f05028Syq 	&usbsacm_rinit, &usbsacm_winit, NULL, NULL
36490f05028Syq };
36590f05028Syq 
36690f05028Syq /* cb_ops structure */
36790f05028Syq static struct cb_ops usbsacm_cb_ops = {
36890f05028Syq 	nodev,			/* cb_open */
36990f05028Syq 	nodev,			/* cb_close */
37090f05028Syq 	nodev,			/* cb_strategy */
37190f05028Syq 	nodev,			/* cb_print */
37290f05028Syq 	nodev,			/* cb_dump */
37390f05028Syq 	nodev,			/* cb_read */
37490f05028Syq 	nodev,			/* cb_write */
37590f05028Syq 	nodev,			/* cb_ioctl */
37690f05028Syq 	nodev,			/* cb_devmap */
37790f05028Syq 	nodev,			/* cb_mmap */
37890f05028Syq 	nodev,			/* cb_segmap */
37990f05028Syq 	nochpoll,		/* cb_chpoll */
38090f05028Syq 	ddi_prop_op,		/* cb_prop_op */
38190f05028Syq 	&usbsacm_str_info,	/* cb_stream */
38290f05028Syq 	(int)(D_64BIT | D_NEW | D_MP | D_HOTPLUG)	/* cb_flag */
38390f05028Syq };
38490f05028Syq 
38590f05028Syq /* dev_ops structure */
38690f05028Syq struct dev_ops usbsacm_ops = {
38790f05028Syq 	DEVO_REV,		/* devo_rev */
38890f05028Syq 	0,			/* devo_refcnt */
38990f05028Syq 	usbsacm_getinfo,	/* devo_getinfo */
39090f05028Syq 	nulldev,		/* devo_identify */
39190f05028Syq 	nulldev,		/* devo_probe */
39290f05028Syq 	usbsacm_attach,		/* devo_attach */
39390f05028Syq 	usbsacm_detach,		/* devo_detach */
39490f05028Syq 	nodev,			/* devo_reset */
39590f05028Syq 	&usbsacm_cb_ops,	/* devo_cb_ops */
39690f05028Syq 	(struct bus_ops *)NULL,	/* devo_bus_ops */
39719397407SSherry Moore 	usbser_power,		/* devo_power */
398be529ebcSRaymond Chen 	ddi_quiesce_not_needed,	/* devo_quiesce */
39990f05028Syq };
40090f05028Syq 
40190f05028Syq extern struct mod_ops mod_driverops;
40290f05028Syq /* modldrv structure */
40390f05028Syq static struct modldrv modldrv = {
40490f05028Syq 	&mod_driverops,		/* type of module - driver */
40577e51571Sgongtian zhao - Sun Microsystems - Beijing China 	"USB Serial CDC ACM driver",
40690f05028Syq 	&usbsacm_ops,
40790f05028Syq };
40890f05028Syq 
40990f05028Syq /* modlinkage structure */
41090f05028Syq static struct modlinkage modlinkage = {
41190f05028Syq 	MODREV_1,
41290f05028Syq 	&modldrv,
41390f05028Syq 	NULL
41490f05028Syq };
41590f05028Syq 
41690f05028Syq static void	*usbsacm_statep;	/* soft state */
41790f05028Syq 
41890f05028Syq /*
41990f05028Syq  * DSD definitions
42090f05028Syq  */
421e8ed0869SJohn Beck static ds_ops_t usbsacm_ds_ops = {
42290f05028Syq 	DS_OPS_VERSION,
42390f05028Syq 	usbsacm_ds_attach,
42490f05028Syq 	usbsacm_ds_detach,
42590f05028Syq 	usbsacm_ds_register_cb,
42690f05028Syq 	usbsacm_ds_unregister_cb,
42790f05028Syq 	usbsacm_ds_open_port,
42890f05028Syq 	usbsacm_ds_close_port,
42990f05028Syq 	usbsacm_ds_usb_power,
43090f05028Syq 	usbsacm_ds_suspend,
43190f05028Syq 	usbsacm_ds_resume,
43290f05028Syq 	usbsacm_ds_disconnect,
43390f05028Syq 	usbsacm_ds_reconnect,
43490f05028Syq 	usbsacm_ds_set_port_params,
43590f05028Syq 	usbsacm_ds_set_modem_ctl,
43690f05028Syq 	usbsacm_ds_get_modem_ctl,
43790f05028Syq 	usbsacm_ds_break_ctl,
43890f05028Syq 	NULL,			/* NULL if h/w doesn't support loopback */
43990f05028Syq 	usbsacm_ds_tx,
44090f05028Syq 	usbsacm_ds_rx,
44190f05028Syq 	usbsacm_ds_stop,
44290f05028Syq 	usbsacm_ds_start,
44390f05028Syq 	usbsacm_ds_fifo_flush,
44490f05028Syq 	usbsacm_ds_fifo_drain
44590f05028Syq };
44690f05028Syq 
44790f05028Syq /*
44890f05028Syq  * baud code -> baud rate (0 means unsupported rate)
44990f05028Syq  */
45090f05028Syq static int usbsacm_speedtab[] = {
451*d9c3e05cSJoshua M. Clulow 	0,		/* B0 */
452*d9c3e05cSJoshua M. Clulow 	50,		/* B50 */
453*d9c3e05cSJoshua M. Clulow 	75,		/* B75 */
454*d9c3e05cSJoshua M. Clulow 	110,		/* B110 */
455*d9c3e05cSJoshua M. Clulow 	134,		/* B134 */
456*d9c3e05cSJoshua M. Clulow 	150,		/* B150 */
457*d9c3e05cSJoshua M. Clulow 	200,		/* B200 */
458*d9c3e05cSJoshua M. Clulow 	300,		/* B300 */
459*d9c3e05cSJoshua M. Clulow 	600,		/* B600 */
460*d9c3e05cSJoshua M. Clulow 	1200,		/* B1200 */
461*d9c3e05cSJoshua M. Clulow 	1800,		/* B1800 */
462*d9c3e05cSJoshua M. Clulow 	2400,		/* B2400 */
463*d9c3e05cSJoshua M. Clulow 	4800,		/* B4800 */
464*d9c3e05cSJoshua M. Clulow 	9600,		/* B9600 */
465*d9c3e05cSJoshua M. Clulow 	19200,		/* B19200 */
466*d9c3e05cSJoshua M. Clulow 	38400,		/* B38400 */
467*d9c3e05cSJoshua M. Clulow 	57600,		/* B57600 */
468*d9c3e05cSJoshua M. Clulow 	76800,		/* B76800 */
469*d9c3e05cSJoshua M. Clulow 	115200,		/* B115200 */
470*d9c3e05cSJoshua M. Clulow 	153600,		/* B153600 */
471*d9c3e05cSJoshua M. Clulow 	230400,		/* B230400 */
472*d9c3e05cSJoshua M. Clulow 	307200,		/* B307200 */
473*d9c3e05cSJoshua M. Clulow 	460800,		/* B460800 */
474*d9c3e05cSJoshua M. Clulow 	921600,		/* B921600 */
475*d9c3e05cSJoshua M. Clulow 	1000000,	/* B1000000 */
476*d9c3e05cSJoshua M. Clulow 	1152000,	/* B1152000 */
477*d9c3e05cSJoshua M. Clulow 	1500000,	/* B1500000 */
478*d9c3e05cSJoshua M. Clulow 	2000000,	/* B2000000 */
479*d9c3e05cSJoshua M. Clulow 	2500000,	/* B2500000 */
480*d9c3e05cSJoshua M. Clulow 	3000000,	/* B3000000 */
481*d9c3e05cSJoshua M. Clulow 	3500000,	/* B3500000 */
482*d9c3e05cSJoshua M. Clulow 	4000000,	/* B4000000 */
48390f05028Syq };
48490f05028Syq 
48590f05028Syq 
48690f05028Syq static uint_t	usbsacm_errlevel = USB_LOG_L4;
48790f05028Syq static uint_t	usbsacm_errmask = 0xffffffff;
48890f05028Syq static uint_t	usbsacm_instance_debug = (uint_t)-1;
48990f05028Syq 
49090f05028Syq 
49190f05028Syq /*
49290f05028Syq  * usbsacm driver's entry points
49390f05028Syq  * -----------------------------
49490f05028Syq  */
49590f05028Syq /*
49690f05028Syq  * Module-wide initialization routine.
49790f05028Syq  */
49890f05028Syq int
_init(void)49990f05028Syq _init(void)
50090f05028Syq {
50190f05028Syq 	int    error;
50290f05028Syq 
50390f05028Syq 	if ((error = mod_install(&modlinkage)) == 0) {
50490f05028Syq 
50590f05028Syq 		error = ddi_soft_state_init(&usbsacm_statep,
50690f05028Syq 		    usbser_soft_state_size(), 1);
50790f05028Syq 	}
50890f05028Syq 
50990f05028Syq 	return (error);
51090f05028Syq }
51190f05028Syq 
51290f05028Syq 
51390f05028Syq /*
51490f05028Syq  * Module-wide tear-down routine.
51590f05028Syq  */
51690f05028Syq int
_fini(void)51790f05028Syq _fini(void)
51890f05028Syq {
51990f05028Syq 	int    error;
52090f05028Syq 
52190f05028Syq 	if ((error = mod_remove(&modlinkage)) == 0) {
52290f05028Syq 		ddi_soft_state_fini(&usbsacm_statep);
52390f05028Syq 	}
52490f05028Syq 
52590f05028Syq 	return (error);
52690f05028Syq }
52790f05028Syq 
52890f05028Syq 
52990f05028Syq int
_info(struct modinfo * modinfop)53090f05028Syq _info(struct modinfo *modinfop)
53190f05028Syq {
53290f05028Syq 	return (mod_info(&modlinkage, modinfop));
53390f05028Syq }
53490f05028Syq 
53590f05028Syq 
53690f05028Syq /*
53790f05028Syq  * Device configuration entry points
53890f05028Syq  */
53990f05028Syq static int
usbsacm_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)54090f05028Syq usbsacm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
54190f05028Syq {
542e8ed0869SJohn Beck 	return (usbser_attach(dip, cmd, usbsacm_statep, &usbsacm_ds_ops));
54390f05028Syq }
54490f05028Syq 
54590f05028Syq 
54690f05028Syq static int
usbsacm_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)54790f05028Syq usbsacm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
54890f05028Syq {
54990f05028Syq 	return (usbser_detach(dip, cmd, usbsacm_statep));
55090f05028Syq }
55190f05028Syq 
55290f05028Syq 
55390f05028Syq int
usbsacm_getinfo(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)55490f05028Syq usbsacm_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
55580727282SMike Zeller     void **result)
55690f05028Syq {
55790f05028Syq 	return (usbser_getinfo(dip, infocmd, arg, result, usbsacm_statep));
55890f05028Syq }
55990f05028Syq 
56090f05028Syq 
56190f05028Syq static int
usbsacm_open(queue_t * rq,dev_t * dev,int flag,int sflag,cred_t * cr)56290f05028Syq usbsacm_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr)
56390f05028Syq {
56490f05028Syq 	return (usbser_open(rq, dev, flag, sflag, cr, usbsacm_statep));
56590f05028Syq }
56690f05028Syq 
56790f05028Syq /*
56890f05028Syq  * usbsacm_ds_detach:
56990f05028Syq  *	attach device instance, called from GSD attach
57090f05028Syq  *	initialize state and device, including:
57190f05028Syq  *		state variables, locks, device node
57290f05028Syq  *		device registration with system
57390f05028Syq  *		power management
57490f05028Syq  */
57590f05028Syq static int
usbsacm_ds_attach(ds_attach_info_t * aip)57690f05028Syq usbsacm_ds_attach(ds_attach_info_t *aip)
57790f05028Syq {
57890f05028Syq 	usbsacm_state_t	*acmp;
57990f05028Syq 
58090f05028Syq 	acmp = (usbsacm_state_t *)kmem_zalloc(sizeof (usbsacm_state_t),
58190f05028Syq 	    KM_SLEEP);
58290f05028Syq 	acmp->acm_dip = aip->ai_dip;
58390f05028Syq 	acmp->acm_usb_events = aip->ai_usb_events;
58490f05028Syq 	acmp->acm_ports = NULL;
58590f05028Syq 	*aip->ai_hdl = (ds_hdl_t)acmp;
58690f05028Syq 
58790f05028Syq 	/* registers usbsacm with the USBA framework */
58890f05028Syq 	if (usb_client_attach(acmp->acm_dip, USBDRV_VERSION,
58990f05028Syq 	    0) != USB_SUCCESS) {
59090f05028Syq 
59190f05028Syq 		goto fail;
59290f05028Syq 	}
59390f05028Syq 
59490f05028Syq 	/* Get the configuration information of device */
59590f05028Syq 	if (usb_get_dev_data(acmp->acm_dip, &acmp->acm_dev_data,
59690f05028Syq 	    USB_PARSE_LVL_CFG, 0) != USB_SUCCESS) {
59790f05028Syq 
59890f05028Syq 		goto fail;
59990f05028Syq 	}
60090f05028Syq 	acmp->acm_def_ph = acmp->acm_dev_data->dev_default_ph;
60190f05028Syq 	acmp->acm_dev_state = USB_DEV_ONLINE;
60290f05028Syq 	mutex_init(&acmp->acm_mutex, NULL, MUTEX_DRIVER,
60390f05028Syq 	    acmp->acm_dev_data->dev_iblock_cookie);
60490f05028Syq 
60590f05028Syq 	acmp->acm_lh = usb_alloc_log_hdl(acmp->acm_dip, "usbsacm",
60690f05028Syq 	    &usbsacm_errlevel, &usbsacm_errmask, &usbsacm_instance_debug, 0);
60790f05028Syq 
60890f05028Syq 	/* Create power management components */
60990f05028Syq 	if (usbsacm_create_pm_components(acmp) != USB_SUCCESS) {
61090f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
61190f05028Syq 		    "usbsacm_ds_attach: create pm components failed.");
61290f05028Syq 
61390f05028Syq 		goto fail;
61490f05028Syq 	}
61590f05028Syq 
61690f05028Syq 	/* Register to get callbacks for USB events */
61790f05028Syq 	if (usb_register_event_cbs(acmp->acm_dip, acmp->acm_usb_events, 0)
61890f05028Syq 	    != USB_SUCCESS) {
61990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
62090f05028Syq 		    "usbsacm_ds_attach: register event callback failed.");
62190f05028Syq 
62290f05028Syq 		goto fail;
62390f05028Syq 	}
62490f05028Syq 
62590f05028Syq 	/*
62690f05028Syq 	 * If devices conform to acm spec, driver will attach using class id;
62790f05028Syq 	 * if not, using device id.
62890f05028Syq 	 */
62990f05028Syq 	if ((strcmp(DEVI(acmp->acm_dip)->devi_binding_name,
63090f05028Syq 	    "usbif,class2.2") == 0) ||
63190f05028Syq 	    ((strcmp(DEVI(acmp->acm_dip)->devi_binding_name,
63290f05028Syq 	    "usb,class2.2.0") == 0))) {
63390f05028Syq 
63490f05028Syq 		acmp->acm_compatibility = B_TRUE;
63590f05028Syq 	} else {
63690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
63790f05028Syq 		    "usbsacm_ds_attach: A nonstandard device is attaching to "
63890f05028Syq 		    "usbsacm driver. This device doesn't conform to "
63990f05028Syq 		    "usb cdc spec.");
64090f05028Syq 
64190f05028Syq 		acmp->acm_compatibility = B_FALSE;
64290f05028Syq 	}
64390f05028Syq 
64490f05028Syq 	/* initialize state variables */
64590f05028Syq 	if (usbsacm_init_alloc_ports(acmp) != USB_SUCCESS) {
64690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
64790f05028Syq 		    "usbsacm_ds_attach: initialize port structure failed.");
64890f05028Syq 
64990f05028Syq 		goto fail;
65090f05028Syq 	}
65190f05028Syq 	*aip->ai_port_cnt = acmp->acm_port_cnt;
65290f05028Syq 
65390f05028Syq 	/* Get max data size of bulk transfer */
65490f05028Syq 	if (usb_pipe_get_max_bulk_transfer_size(acmp->acm_dip,
65590f05028Syq 	    &acmp->acm_xfer_sz) != USB_SUCCESS) {
65690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
65790f05028Syq 		    "usbsacm_ds_attach: get max size of transfer failed.");
65890f05028Syq 
65990f05028Syq 		goto fail;
66090f05028Syq 	}
66190f05028Syq 
66290f05028Syq 	return (USB_SUCCESS);
66390f05028Syq fail:
66490f05028Syq 	usbsacm_cleanup(acmp);
66590f05028Syq 
66690f05028Syq 	return (USB_FAILURE);
66790f05028Syq }
66890f05028Syq 
66990f05028Syq 
67090f05028Syq /*
67190f05028Syq  * usbsacm_ds_detach:
67290f05028Syq  *	detach device instance, called from GSD detach
67390f05028Syq  */
67490f05028Syq static void
usbsacm_ds_detach(ds_hdl_t hdl)67590f05028Syq usbsacm_ds_detach(ds_hdl_t hdl)
67690f05028Syq {
67790f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
67890f05028Syq 
67990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
68090f05028Syq 	    "usbsacm_ds_detach:");
68190f05028Syq 
68290f05028Syq 	usbsacm_close_pipes(acmp);
68390f05028Syq 	usbsacm_cleanup(acmp);
68490f05028Syq }
68590f05028Syq 
68690f05028Syq 
68790f05028Syq /*
68890f05028Syq  * usbsacm_ds_register_cb:
68990f05028Syq  *	GSD routine call ds_register_cb to register interrupt callbacks
69090f05028Syq  *	for the given port
69190f05028Syq  */
69290f05028Syq /*ARGSUSED*/
69390f05028Syq static int
usbsacm_ds_register_cb(ds_hdl_t hdl,uint_t port_num,ds_cb_t * cb)69490f05028Syq usbsacm_ds_register_cb(ds_hdl_t hdl, uint_t port_num, ds_cb_t *cb)
69590f05028Syq {
69690f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
69790f05028Syq 	usbsacm_port_t	*acm_port;
69890f05028Syq 
69990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
70090f05028Syq 	    "usbsacm_ds_register_cb: acmp = 0x%p port_num = %d",
701112116d8Sfb 	    (void *)acmp, port_num);
70290f05028Syq 
70390f05028Syq 	/* Check if port number is greater than actual port number. */
70490f05028Syq 	if (port_num >= acmp->acm_port_cnt) {
70590f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
70690f05028Syq 		    "usbsacm_ds_register_cb: port number is wrong.");
70790f05028Syq 
70890f05028Syq 		return (USB_FAILURE);
70990f05028Syq 	}
71090f05028Syq 	acm_port = &acmp->acm_ports[port_num];
71190f05028Syq 	acm_port->acm_cb = *cb;
71290f05028Syq 
71390f05028Syq 	return (USB_SUCCESS);
71490f05028Syq }
71590f05028Syq 
71690f05028Syq 
71790f05028Syq /*
71890f05028Syq  * usbsacm_ds_unregister_cb:
71990f05028Syq  *	GSD routine call ds_unregister_cb to unregister
72090f05028Syq  *	interrupt callbacks for the given port
72190f05028Syq  */
72290f05028Syq /*ARGSUSED*/
72390f05028Syq static void
usbsacm_ds_unregister_cb(ds_hdl_t hdl,uint_t port_num)72490f05028Syq usbsacm_ds_unregister_cb(ds_hdl_t hdl, uint_t port_num)
72590f05028Syq {
72690f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
72790f05028Syq 	usbsacm_port_t	*acm_port;
72890f05028Syq 
72990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
73090f05028Syq 	    "usbsacm_ds_unregister_cb: ");
73190f05028Syq 
73290f05028Syq 	if (port_num < acmp->acm_port_cnt) {
73390f05028Syq 		/* Release callback function */
73490f05028Syq 		acm_port = &acmp->acm_ports[port_num];
73590f05028Syq 		bzero(&acm_port->acm_cb, sizeof (acm_port->acm_cb));
73690f05028Syq 	}
73790f05028Syq }
73890f05028Syq 
73990f05028Syq 
74090f05028Syq /*
74190f05028Syq  * usbsacm_ds_open_port:
74290f05028Syq  *	GSD routine call ds_open_port
74390f05028Syq  *	to open the given port
74490f05028Syq  */
74590f05028Syq /*ARGSUSED*/
74690f05028Syq static int
usbsacm_ds_open_port(ds_hdl_t hdl,uint_t port_num)74790f05028Syq usbsacm_ds_open_port(ds_hdl_t hdl, uint_t port_num)
74890f05028Syq {
74990f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
75090f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
75190f05028Syq 
75290f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
75390f05028Syq 	    "usbsacm_ds_open_port: port_num = %d", port_num);
75490f05028Syq 
75590f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
75690f05028Syq 	/* Check the status of the given port and device */
75790f05028Syq 	if ((acmp->acm_dev_state == USB_DEV_DISCONNECTED) ||
75890f05028Syq 	    (acm_port->acm_port_state != USBSACM_PORT_CLOSED)) {
75990f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
76090f05028Syq 
76190f05028Syq 		return (USB_FAILURE);
76290f05028Syq 	}
76390f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
76490f05028Syq 
76590f05028Syq 	usbsacm_pm_set_busy(acmp);
76690f05028Syq 
76790f05028Syq 	/* open pipes of port */
76890f05028Syq 	if (usbsacm_open_port_pipes(acm_port) != USB_SUCCESS) {
76990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
77090f05028Syq 		    "usbsacm_ds_open_port: open pipes failed.");
77190f05028Syq 
77290f05028Syq 		return (USB_FAILURE);
77390f05028Syq 	}
77490f05028Syq 
77590f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
77690f05028Syq 	/* data receipt */
77790f05028Syq 	if (usbsacm_rx_start(acm_port) != USB_SUCCESS) {
77890f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
77990f05028Syq 		    "usbsacm_ds_open_port: start receive data failed.");
78090f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
78190f05028Syq 
78290f05028Syq 		return (USB_FAILURE);
78390f05028Syq 	}
78490f05028Syq 	acm_port->acm_port_state = USBSACM_PORT_OPEN;
78590f05028Syq 
78690f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
78790f05028Syq 
78890f05028Syq 	return (USB_SUCCESS);
78990f05028Syq }
79090f05028Syq 
79190f05028Syq 
79290f05028Syq /*
79390f05028Syq  * usbsacm_ds_close_port:
79490f05028Syq  *	GSD routine call ds_close_port
79590f05028Syq  *	to close the given port
79690f05028Syq  */
79790f05028Syq /*ARGSUSED*/
79890f05028Syq static int
usbsacm_ds_close_port(ds_hdl_t hdl,uint_t port_num)79990f05028Syq usbsacm_ds_close_port(ds_hdl_t hdl, uint_t port_num)
80090f05028Syq {
80190f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
80290f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
80390f05028Syq 	int		rval = USB_SUCCESS;
80490f05028Syq 
80590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
806112116d8Sfb 	    "usbsacm_ds_close_port: acmp = 0x%p", (void *)acmp);
80790f05028Syq 
80890f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
80990f05028Syq 	acm_port->acm_port_state = USBSACM_PORT_CLOSED;
81090f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
81190f05028Syq 
81290f05028Syq 	usbsacm_close_port_pipes(acm_port);
81390f05028Syq 
81490f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
81590f05028Syq 	rval = usbsacm_fifo_flush_locked(acmp, port_num, DS_TX | DS_RX);
81690f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
81790f05028Syq 
81890f05028Syq 	usbsacm_pm_set_idle(acmp);
81990f05028Syq 
82090f05028Syq 	return (rval);
82190f05028Syq }
82290f05028Syq 
82390f05028Syq 
82490f05028Syq /*
82590f05028Syq  * usbsacm_ds_usb_power:
82690f05028Syq  *	GSD routine call ds_usb_power
82790f05028Syq  *	to set power level of the component
82890f05028Syq  */
82990f05028Syq /*ARGSUSED*/
83090f05028Syq static int
usbsacm_ds_usb_power(ds_hdl_t hdl,int comp,int level,int * new_state)83190f05028Syq usbsacm_ds_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state)
83290f05028Syq {
83390f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
83490f05028Syq 	usbsacm_pm_t	*pm = acmp->acm_pm;
83590f05028Syq 	int		rval = USB_SUCCESS;
83690f05028Syq 
83790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
83890f05028Syq 	    "usbsacm_ds_usb_power: ");
83990f05028Syq 
84090f05028Syq 	/* check if pm is NULL */
84190f05028Syq 	if (pm == NULL) {
84290f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
84390f05028Syq 		    "usbsacm_ds_usb_power: pm is NULL.");
84490f05028Syq 
84590f05028Syq 		return (USB_FAILURE);
84690f05028Syq 	}
84790f05028Syq 
84890f05028Syq 	mutex_enter(&acmp->acm_mutex);
84990f05028Syq 	/*
85090f05028Syq 	 * check if we are transitioning to a legal power level
85190f05028Syq 	 */
85290f05028Syq 	if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) {
85390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
85490f05028Syq 		    "usbsacm_ds_usb_power: "
85590f05028Syq 		    "illegal power level %d, pwr_states=%x",
85690f05028Syq 		    level, pm->pm_pwr_states);
85790f05028Syq 		mutex_exit(&acmp->acm_mutex);
85890f05028Syq 
85990f05028Syq 		return (USB_FAILURE);
86090f05028Syq 	}
86190f05028Syq 
86290f05028Syq 	/*
86390f05028Syq 	 * if we are about to raise power and asked to lower power, fail
86490f05028Syq 	 */
86590f05028Syq 	if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) {
86690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
86790f05028Syq 		    "usbsacm_ds_usb_power: wrong condition.");
86890f05028Syq 		mutex_exit(&acmp->acm_mutex);
86990f05028Syq 
87090f05028Syq 		return (USB_FAILURE);
87190f05028Syq 	}
87290f05028Syq 
87390f05028Syq 	/*
87490f05028Syq 	 * Set the power status of device by request level.
87590f05028Syq 	 */
87690f05028Syq 	switch (level) {
87790f05028Syq 	case USB_DEV_OS_PWR_OFF:
87890f05028Syq 		rval = usbsacm_pwrlvl0(acmp);
87990f05028Syq 
88090f05028Syq 		break;
88190f05028Syq 	case USB_DEV_OS_PWR_1:
88290f05028Syq 		rval = usbsacm_pwrlvl1(acmp);
88390f05028Syq 
88490f05028Syq 		break;
88590f05028Syq 	case USB_DEV_OS_PWR_2:
88690f05028Syq 		rval = usbsacm_pwrlvl2(acmp);
88790f05028Syq 
88890f05028Syq 		break;
88990f05028Syq 	case USB_DEV_OS_FULL_PWR:
89090f05028Syq 		rval = usbsacm_pwrlvl3(acmp);
8914ee52f77Slg 		/*
8924ee52f77Slg 		 * If usbser dev_state is DISCONNECTED or SUSPENDED, it shows
8934ee52f77Slg 		 * that the usb serial device is disconnected/suspended while it
8944ee52f77Slg 		 * is under power down state, now the device is powered up
8954ee52f77Slg 		 * before it is reconnected/resumed. xxx_pwrlvl3() will set dev
8964ee52f77Slg 		 * state to ONLINE, we need to set the dev state back to
8974ee52f77Slg 		 * DISCONNECTED/SUSPENDED.
8984ee52f77Slg 		 */
8994ee52f77Slg 		if ((rval == USB_SUCCESS) &&
9004ee52f77Slg 		    ((*new_state == USB_DEV_DISCONNECTED) ||
9014ee52f77Slg 		    (*new_state == USB_DEV_SUSPENDED))) {
9024ee52f77Slg 			acmp->acm_dev_state = *new_state;
9034ee52f77Slg 		}
90490f05028Syq 
90590f05028Syq 		break;
90690f05028Syq 	}
90790f05028Syq 
90890f05028Syq 	*new_state = acmp->acm_dev_state;
90990f05028Syq 	mutex_exit(&acmp->acm_mutex);
91090f05028Syq 
91190f05028Syq 	return (rval);
91290f05028Syq }
91390f05028Syq 
91490f05028Syq 
91590f05028Syq /*
91690f05028Syq  * usbsacm_ds_suspend:
91790f05028Syq  *	GSD routine call ds_suspend
91890f05028Syq  *	during CPR suspend
91990f05028Syq  */
92090f05028Syq static int
usbsacm_ds_suspend(ds_hdl_t hdl)92190f05028Syq usbsacm_ds_suspend(ds_hdl_t hdl)
92290f05028Syq {
92390f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
9244ee52f77Slg 	int		state = USB_DEV_SUSPENDED;
92590f05028Syq 
92690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
92790f05028Syq 	    "usbsacm_ds_suspend: ");
9284ee52f77Slg 	/*
9294ee52f77Slg 	 * If the device is suspended while it is under PWRED_DOWN state, we
9304ee52f77Slg 	 * need to keep the PWRED_DOWN state so that it could be powered up
9314ee52f77Slg 	 * later. In the mean while, usbser dev state will be changed to
9324ee52f77Slg 	 * SUSPENDED state.
9334ee52f77Slg 	 */
93490f05028Syq 	mutex_enter(&acmp->acm_mutex);
9354ee52f77Slg 	if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) {
9364ee52f77Slg 		/* set device status to suspend */
9374ee52f77Slg 		acmp->acm_dev_state = USB_DEV_SUSPENDED;
9384ee52f77Slg 	}
93990f05028Syq 	mutex_exit(&acmp->acm_mutex);
94090f05028Syq 
94190f05028Syq 	usbsacm_disconnect_pipes(acmp);
94290f05028Syq 
94390f05028Syq 	return (state);
94490f05028Syq }
94590f05028Syq 
94690f05028Syq /*
94790f05028Syq  * usbsacm_ds_resume:
94890f05028Syq  *	GSD routine call ds_resume
94990f05028Syq  *	during CPR resume
95090f05028Syq  */
95190f05028Syq /*ARGSUSED*/
95290f05028Syq static int
usbsacm_ds_resume(ds_hdl_t hdl)95390f05028Syq usbsacm_ds_resume(ds_hdl_t hdl)
95490f05028Syq {
95590f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
95690f05028Syq 	int		current_state;
95790f05028Syq 	int		ret;
95890f05028Syq 
95990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
96090f05028Syq 	    "usbsacm_ds_resume: ");
96190f05028Syq 
96290f05028Syq 	mutex_enter(&acmp->acm_mutex);
96390f05028Syq 	current_state = acmp->acm_dev_state;
96490f05028Syq 	mutex_exit(&acmp->acm_mutex);
96590f05028Syq 
96690f05028Syq 	/* restore the status of device */
96790f05028Syq 	if (current_state != USB_DEV_ONLINE) {
96890f05028Syq 		ret = usbsacm_restore_device_state(acmp);
96990f05028Syq 	} else {
97090f05028Syq 		ret = USB_DEV_ONLINE;
97190f05028Syq 	}
97290f05028Syq 
97390f05028Syq 	return (ret);
97490f05028Syq }
97590f05028Syq 
97690f05028Syq /*
97790f05028Syq  * usbsacm_ds_disconnect:
97890f05028Syq  *	GSD routine call ds_disconnect
97990f05028Syq  *	to disconnect USB device
98090f05028Syq  */
98190f05028Syq static int
usbsacm_ds_disconnect(ds_hdl_t hdl)98290f05028Syq usbsacm_ds_disconnect(ds_hdl_t hdl)
98390f05028Syq {
98490f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
9854ee52f77Slg 	int		state = USB_DEV_DISCONNECTED;
98690f05028Syq 
98790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
98890f05028Syq 	    "usbsacm_ds_disconnect: ");
98990f05028Syq 
9904ee52f77Slg 	/*
9914ee52f77Slg 	 * If the device is disconnected while it is under PWRED_DOWN state, we
9924ee52f77Slg 	 * need to keep the PWRED_DOWN state so that it could be powered up
9934ee52f77Slg 	 * later. In the mean while, usbser dev state will be changed to
9944ee52f77Slg 	 * DISCONNECTED state.
9954ee52f77Slg 	 */
99690f05028Syq 	mutex_enter(&acmp->acm_mutex);
9974ee52f77Slg 	if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) {
9984ee52f77Slg 		/* set device status to disconnected */
9994ee52f77Slg 		acmp->acm_dev_state = USB_DEV_DISCONNECTED;
10004ee52f77Slg 	}
100190f05028Syq 	mutex_exit(&acmp->acm_mutex);
100290f05028Syq 
100390f05028Syq 	usbsacm_disconnect_pipes(acmp);
100490f05028Syq 
100590f05028Syq 	return (state);
100690f05028Syq }
100790f05028Syq 
100890f05028Syq 
100990f05028Syq /*
101090f05028Syq  * usbsacm_ds_reconnect:
101190f05028Syq  *	GSD routine call ds_reconnect
101290f05028Syq  *	to reconnect USB device
101390f05028Syq  */
101490f05028Syq /*ARGSUSED*/
101590f05028Syq static int
usbsacm_ds_reconnect(ds_hdl_t hdl)101690f05028Syq usbsacm_ds_reconnect(ds_hdl_t hdl)
101790f05028Syq {
101890f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
101990f05028Syq 
102090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
102190f05028Syq 	    "usbsacm_ds_reconnect: ");
102290f05028Syq 
102390f05028Syq 	return (usbsacm_restore_device_state(acmp));
102490f05028Syq }
102590f05028Syq 
102690f05028Syq 
102790f05028Syq /*
102890f05028Syq  * usbsacm_ds_set_port_params:
102990f05028Syq  *	GSD routine call ds_set_port_params
103090f05028Syq  *	to set one or more port parameters
103190f05028Syq  */
103290f05028Syq /*ARGSUSED*/
103390f05028Syq static int
usbsacm_ds_set_port_params(ds_hdl_t hdl,uint_t port_num,ds_port_params_t * tp)103490f05028Syq usbsacm_ds_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp)
103590f05028Syq {
103690f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
103790f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
103890f05028Syq 	int		i;
103990f05028Syq 	uint_t		ui;
104090f05028Syq 	ds_port_param_entry_t *pe;
104190f05028Syq 	usb_cdc_line_coding_t lc;
104290f05028Syq 	int		ret;
104390f05028Syq 
104490f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
1045112116d8Sfb 	    "usbsacm_ds_set_port_params: acmp = 0x%p", (void *)acmp);
104690f05028Syq 
104790f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
104890f05028Syq 	/*
104990f05028Syq 	 * If device conform to acm spec, check if it support to set port param.
105090f05028Syq 	 */
105190f05028Syq 	if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 &&
105290f05028Syq 	    acmp->acm_compatibility == B_TRUE) {
105390f05028Syq 
105490f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
105590f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
105690f05028Syq 		    "usbsacm_ds_set_port_params: "
105790f05028Syq 		    "don't support Set_Line_Coding.");
105890f05028Syq 
105990f05028Syq 		return (USB_FAILURE);
106090f05028Syq 	}
106190f05028Syq 
106290f05028Syq 	lc = acm_port->acm_line_coding;
106390f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
106490f05028Syq 	pe = tp->tp_entries;
106590f05028Syq 	/* Get parameter information from ds_port_params_t */
106690f05028Syq 	for (i = 0; i < tp->tp_cnt; i++, pe++) {
106790f05028Syq 		switch (pe->param) {
106890f05028Syq 		case DS_PARAM_BAUD:
106990f05028Syq 			/* Data terminal rate, in bits per second. */
107090f05028Syq 			ui = pe->val.ui;
107190f05028Syq 
107290f05028Syq 			/* if we don't support this speed, return USB_FAILURE */
107390f05028Syq 			if ((ui >= NELEM(usbsacm_speedtab)) ||
107490f05028Syq 			    ((ui > 0) && (usbsacm_speedtab[ui] == 0))) {
107590f05028Syq 				USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
107690f05028Syq 				    "usbsacm_ds_set_port_params: "
107790f05028Syq 				    " error baud rate");
107890f05028Syq 
107990f05028Syq 				return (USB_FAILURE);
108090f05028Syq 			}
108190f05028Syq 			lc.dwDTERate = LE_32(usbsacm_speedtab[ui]);
108290f05028Syq 
108390f05028Syq 			break;
108490f05028Syq 		case DS_PARAM_PARITY:
108590f05028Syq 			/* Parity Type */
108690f05028Syq 			if (pe->val.ui & PARENB) {
108790f05028Syq 				if (pe->val.ui & PARODD) {
108890f05028Syq 					lc.bParityType = USB_CDC_PARITY_ODD;
108990f05028Syq 				} else {
109090f05028Syq 					lc.bParityType = USB_CDC_PARITY_EVEN;
109190f05028Syq 				}
109290f05028Syq 			} else {
109390f05028Syq 				lc.bParityType = USB_CDC_PARITY_NO;
109490f05028Syq 			}
109590f05028Syq 
109690f05028Syq 			break;
109790f05028Syq 		case DS_PARAM_STOPB:
109890f05028Syq 			/* Stop bit */
109990f05028Syq 			if (pe->val.ui & CSTOPB) {
110090f05028Syq 				lc.bCharFormat = USB_CDC_STOP_BITS_2;
110190f05028Syq 			} else {
110290f05028Syq 				lc.bCharFormat = USB_CDC_STOP_BITS_1;
110390f05028Syq 			}
110490f05028Syq 
110590f05028Syq 			break;
110690f05028Syq 		case DS_PARAM_CHARSZ:
110790f05028Syq 			/* Data Bits */
110890f05028Syq 			switch (pe->val.ui) {
110990f05028Syq 			case CS5:
111090f05028Syq 				lc.bDataBits = 5;
111190f05028Syq 				break;
111290f05028Syq 			case CS6:
111390f05028Syq 				lc.bDataBits = 6;
111490f05028Syq 				break;
111590f05028Syq 			case CS7:
111690f05028Syq 				lc.bDataBits = 7;
111790f05028Syq 				break;
111890f05028Syq 			case CS8:
111990f05028Syq 			default:
112090f05028Syq 				lc.bDataBits = 8;
112190f05028Syq 				break;
112290f05028Syq 			}
112390f05028Syq 
112490f05028Syq 			break;
112590f05028Syq 		default:
112690f05028Syq 			USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
112790f05028Syq 			    "usbsacm_ds_set_port_params: "
112890f05028Syq 			    "parameter 0x%x isn't supported",
112990f05028Syq 			    pe->param);
113090f05028Syq 
113190f05028Syq 			break;
113290f05028Syq 		}
113390f05028Syq 	}
113490f05028Syq 
113590f05028Syq 	if ((ret = usbsacm_set_line_coding(acm_port, &lc)) == USB_SUCCESS) {
113690f05028Syq 		mutex_enter(&acm_port->acm_port_mutex);
113790f05028Syq 		acm_port->acm_line_coding = lc;
113890f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
113990f05028Syq 	}
114090f05028Syq 
114190f05028Syq 	/*
114290f05028Syq 	 * If device don't conform to acm spec, return success directly.
114390f05028Syq 	 */
114490f05028Syq 	if (acmp->acm_compatibility != B_TRUE) {
114590f05028Syq 		ret = USB_SUCCESS;
114690f05028Syq 	}
114790f05028Syq 
114890f05028Syq 	return (ret);
114990f05028Syq }
115090f05028Syq 
115190f05028Syq 
115290f05028Syq /*
115390f05028Syq  * usbsacm_ds_set_modem_ctl:
115490f05028Syq  *	GSD routine call ds_set_modem_ctl
115590f05028Syq  *	to set modem control of the given port
115690f05028Syq  */
115790f05028Syq /*ARGSUSED*/
115890f05028Syq static int
usbsacm_ds_set_modem_ctl(ds_hdl_t hdl,uint_t port_num,int mask,int val)115990f05028Syq usbsacm_ds_set_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int val)
116090f05028Syq {
116190f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
116290f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
116390f05028Syq 	uint8_t		new_mctl;
116490f05028Syq 	int		ret;
116590f05028Syq 
116690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
116790f05028Syq 	    "usbsacm_ds_set_modem_ctl: mask = 0x%x val = 0x%x",
116890f05028Syq 	    mask, val);
116990f05028Syq 
117090f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
117190f05028Syq 	/*
117290f05028Syq 	 * If device conform to acm spec, check if it support to set modem
117390f05028Syq 	 * controls.
117490f05028Syq 	 */
117590f05028Syq 	if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 &&
11764ee52f77Slg 	    acmp->acm_compatibility == B_TRUE) {
117790f05028Syq 
117890f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
117990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
118090f05028Syq 		    "usbsacm_ds_set_modem_ctl: "
118190f05028Syq 		    "don't support Set_Control_Line_State.");
118290f05028Syq 
118390f05028Syq 		return (USB_FAILURE);
118490f05028Syq 	}
118590f05028Syq 
118690f05028Syq 	new_mctl = acm_port->acm_mctlout;
118790f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
118890f05028Syq 
118990f05028Syq 	usbsacm_mctl2reg(mask, val, &new_mctl);
119090f05028Syq 
119190f05028Syq 	if ((acmp->acm_compatibility == B_FALSE) || ((ret =
119290f05028Syq 	    usbsacm_req_write(acm_port, USB_CDC_REQ_SET_CONTROL_LINE_STATE,
119390f05028Syq 	    new_mctl, NULL)) == USB_SUCCESS)) {
119490f05028Syq 		mutex_enter(&acm_port->acm_port_mutex);
119590f05028Syq 		acm_port->acm_mctlout = new_mctl;
119690f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
119790f05028Syq 	}
119890f05028Syq 
119990f05028Syq 	/*
120090f05028Syq 	 * If device don't conform to acm spec, return success directly.
120190f05028Syq 	 */
120290f05028Syq 	if (acmp->acm_compatibility != B_TRUE) {
120390f05028Syq 		ret = USB_SUCCESS;
120490f05028Syq 	}
120590f05028Syq 
120690f05028Syq 	return (ret);
120790f05028Syq }
120890f05028Syq 
120990f05028Syq 
121090f05028Syq /*
121190f05028Syq  * usbsacm_ds_get_modem_ctl:
121290f05028Syq  *	GSD routine call ds_get_modem_ctl
121390f05028Syq  *	to get modem control/status of the given port
121490f05028Syq  */
121590f05028Syq /*ARGSUSED*/
121690f05028Syq static int
usbsacm_ds_get_modem_ctl(ds_hdl_t hdl,uint_t port_num,int mask,int * valp)121790f05028Syq usbsacm_ds_get_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int *valp)
121890f05028Syq {
121990f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
122090f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
122190f05028Syq 
122290f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
122390f05028Syq 	*valp = usbsacm_reg2mctl(acm_port->acm_mctlout) & mask;
122490f05028Syq 	/*
122590f05028Syq 	 * If device conform to acm spec, polling function can modify the value
122690f05028Syq 	 * of acm_mctlin; else set to default value.
122790f05028Syq 	 */
122890f05028Syq 	if (acmp->acm_compatibility) {
122990f05028Syq 		*valp |= usbsacm_reg2mctl(acm_port->acm_mctlin) & mask;
123090f05028Syq 		*valp |= (mask & (TIOCM_CD | TIOCM_CTS));
123190f05028Syq 	} else {
123290f05028Syq 		*valp |= (mask & (TIOCM_CD | TIOCM_CTS | TIOCM_DSR | TIOCM_RI));
123390f05028Syq 	}
123490f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
123590f05028Syq 
123690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
123790f05028Syq 	    "usbsacm_ds_get_modem_ctl: val = 0x%x", *valp);
123890f05028Syq 
123990f05028Syq 	return (USB_SUCCESS);
124090f05028Syq }
124190f05028Syq 
124290f05028Syq 
124390f05028Syq /*
124490f05028Syq  * usbsacm_ds_tx:
124590f05028Syq  *	GSD routine call ds_break_ctl
124690f05028Syq  *	to set/clear break
124790f05028Syq  */
124890f05028Syq /*ARGSUSED*/
124990f05028Syq static int
usbsacm_ds_break_ctl(ds_hdl_t hdl,uint_t port_num,int ctl)125090f05028Syq usbsacm_ds_break_ctl(ds_hdl_t hdl, uint_t port_num, int ctl)
125190f05028Syq {
125290f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
125390f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
125490f05028Syq 
125590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
125690f05028Syq 	    "usbsacm_ds_break_ctl: ");
125790f05028Syq 
125890f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
125990f05028Syq 	/*
126090f05028Syq 	 * If device conform to acm spec, check if it support to send break.
126190f05028Syq 	 */
126290f05028Syq 	if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SEND_BREAK) == 0 &&
12634ee52f77Slg 	    acmp->acm_compatibility == B_TRUE) {
126490f05028Syq 
126590f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
126690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh,
126790f05028Syq 		    "usbsacm_ds_break_ctl: don't support send break.");
126890f05028Syq 
126990f05028Syq 		return (USB_FAILURE);
127090f05028Syq 	}
127190f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
127290f05028Syq 
127390f05028Syq 	return (usbsacm_req_write(acm_port, USB_CDC_REQ_SEND_BREAK,
12744ee52f77Slg 	    ((ctl == DS_ON) ? 0xffff : 0), NULL));
127590f05028Syq }
127690f05028Syq 
127790f05028Syq 
127890f05028Syq /*
127990f05028Syq  * usbsacm_ds_tx:
128090f05028Syq  *	GSD routine call ds_tx
128190f05028Syq  *	to data transmit
128290f05028Syq  */
128390f05028Syq /*ARGSUSED*/
128490f05028Syq static int
usbsacm_ds_tx(ds_hdl_t hdl,uint_t port_num,mblk_t * mp)128590f05028Syq usbsacm_ds_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp)
128690f05028Syq {
128790f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
128890f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
128990f05028Syq 
129090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
1291112116d8Sfb 	    "usbsacm_ds_tx: mp = 0x%p acmp = 0x%p", (void *)mp, (void *)acmp);
129290f05028Syq 
129390f05028Syq 	/* sanity checks */
129490f05028Syq 	if (mp == NULL) {
129590f05028Syq 
129690f05028Syq 		return (USB_SUCCESS);
129790f05028Syq 	}
129822eb7cb5Sgd 	if (MBLKL(mp) < 1) {
129990f05028Syq 		freemsg(mp);
130090f05028Syq 
130190f05028Syq 		return (USB_SUCCESS);
130290f05028Syq 	}
130390f05028Syq 
130490f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
130590f05028Syq 	/* put mblk to tail of mblk chain */
130690f05028Syq 	usbsacm_put_tail(&acm_port->acm_tx_mp, mp);
130790f05028Syq 	usbsacm_tx_start(acm_port);
130890f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
130990f05028Syq 
131090f05028Syq 	return (USB_SUCCESS);
131190f05028Syq }
131290f05028Syq 
131390f05028Syq 
131490f05028Syq /*
131590f05028Syq  * usbsacm_ds_rx:
131690f05028Syq  *	GSD routine call ds_rx;
131790f05028Syq  *	to data receipt
131890f05028Syq  */
131990f05028Syq /*ARGSUSED*/
132090f05028Syq static mblk_t *
usbsacm_ds_rx(ds_hdl_t hdl,uint_t port_num)132190f05028Syq usbsacm_ds_rx(ds_hdl_t hdl, uint_t port_num)
132290f05028Syq {
132390f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
132490f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
132590f05028Syq 	mblk_t		*mp;
132690f05028Syq 
132790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
1328112116d8Sfb 	    "usbsacm_ds_rx: acmp = 0x%p", (void *)acmp);
132990f05028Syq 
133090f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
133190f05028Syq 
133290f05028Syq 	mp = acm_port->acm_rx_mp;
133390f05028Syq 	acm_port->acm_rx_mp = NULL;
133490f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
133590f05028Syq 
133690f05028Syq 	return (mp);
133790f05028Syq }
133890f05028Syq 
133990f05028Syq 
134090f05028Syq /*
134190f05028Syq  * usbsacm_ds_stop:
134290f05028Syq  *	GSD routine call ds_stop;
134390f05028Syq  *	but acm spec don't define this function
134490f05028Syq  */
134590f05028Syq /*ARGSUSED*/
134690f05028Syq static void
usbsacm_ds_stop(ds_hdl_t hdl,uint_t port_num,int dir)134790f05028Syq usbsacm_ds_stop(ds_hdl_t hdl, uint_t port_num, int dir)
134890f05028Syq {
134990f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
135090f05028Syq 
135190f05028Syq 	USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
135290f05028Syq 	    "usbsacm_ds_stop: don't support!");
135390f05028Syq }
135490f05028Syq 
135590f05028Syq 
135690f05028Syq /*
135790f05028Syq  * usbsacm_ds_start:
135890f05028Syq  *	GSD routine call ds_start;
135990f05028Syq  *	but acm spec don't define this function
136090f05028Syq  */
136190f05028Syq /*ARGSUSED*/
136290f05028Syq static void
usbsacm_ds_start(ds_hdl_t hdl,uint_t port_num,int dir)136390f05028Syq usbsacm_ds_start(ds_hdl_t hdl, uint_t port_num, int dir)
136490f05028Syq {
136590f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
136690f05028Syq 
136790f05028Syq 	USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
136890f05028Syq 	    "usbsacm_ds_start: don't support!");
136990f05028Syq }
137090f05028Syq 
137190f05028Syq 
137290f05028Syq /*
137390f05028Syq  * usbsacm_ds_fifo_flush:
137490f05028Syq  *	GSD routine call ds_fifo_flush
137590f05028Syq  *	to flush FIFOs
137690f05028Syq  */
137790f05028Syq /*ARGSUSED*/
137890f05028Syq static int
usbsacm_ds_fifo_flush(ds_hdl_t hdl,uint_t port_num,int dir)137990f05028Syq usbsacm_ds_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir)
138090f05028Syq {
138190f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
138290f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
138390f05028Syq 	int		ret = USB_SUCCESS;
138490f05028Syq 
138590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
138690f05028Syq 	    "usbsacm_ds_fifo_flush: ");
138790f05028Syq 
138890f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
138990f05028Syq 	ret = usbsacm_fifo_flush_locked(acmp, port_num, dir);
139090f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
139190f05028Syq 
139290f05028Syq 	return (ret);
139390f05028Syq }
139490f05028Syq 
139590f05028Syq 
139690f05028Syq /*
139790f05028Syq  * usbsacm_ds_fifo_drain:
139890f05028Syq  *	GSD routine call ds_fifo_drain
139990f05028Syq  *	to wait until empty output FIFO
140090f05028Syq  */
140190f05028Syq /*ARGSUSED*/
140290f05028Syq static int
usbsacm_ds_fifo_drain(ds_hdl_t hdl,uint_t port_num,int timeout)140390f05028Syq usbsacm_ds_fifo_drain(ds_hdl_t hdl, uint_t port_num, int timeout)
140490f05028Syq {
140590f05028Syq 	usbsacm_state_t	*acmp = (usbsacm_state_t *)hdl;
140690f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
140790f05028Syq 	int		rval = USB_SUCCESS;
140890f05028Syq 
140990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
141090f05028Syq 	    "usbsacm_ds_fifo_drain: ");
141190f05028Syq 
141290f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
141390f05028Syq 	ASSERT(acm_port->acm_port_state == USBSACM_PORT_OPEN);
141490f05028Syq 
141590f05028Syq 	if (usbsacm_wait_tx_drain(acm_port, timeout) != USB_SUCCESS) {
141690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
141790f05028Syq 		    "usbsacm_ds_fifo_drain: fifo drain failed.");
141890f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
141990f05028Syq 
142090f05028Syq 		return (USB_FAILURE);
142190f05028Syq 	}
142290f05028Syq 
142390f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
142490f05028Syq 
142590f05028Syq 	return (rval);
142690f05028Syq }
142790f05028Syq 
142890f05028Syq 
142990f05028Syq /*
143090f05028Syq  * usbsacm_fifo_flush_locked:
143190f05028Syq  *	flush FIFOs of the given ports
143290f05028Syq  */
143390f05028Syq /*ARGSUSED*/
143490f05028Syq static int
usbsacm_fifo_flush_locked(usbsacm_state_t * acmp,uint_t port_num,int dir)143590f05028Syq usbsacm_fifo_flush_locked(usbsacm_state_t *acmp, uint_t port_num, int dir)
143690f05028Syq {
143790f05028Syq 	usbsacm_port_t	*acm_port = &acmp->acm_ports[port_num];
143890f05028Syq 
143990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
144090f05028Syq 	    "usbsacm_fifo_flush_locked: ");
144190f05028Syq 
144290f05028Syq 	/* flush transmit FIFO if DS_TX is set */
144390f05028Syq 	if ((dir & DS_TX) && acm_port->acm_tx_mp) {
144490f05028Syq 		freemsg(acm_port->acm_tx_mp);
144590f05028Syq 		acm_port->acm_tx_mp = NULL;
144690f05028Syq 	}
144790f05028Syq 	/* flush received FIFO if DS_RX is set */
144890f05028Syq 	if ((dir & DS_RX) && acm_port->acm_rx_mp) {
144990f05028Syq 		freemsg(acm_port->acm_rx_mp);
145090f05028Syq 		acm_port->acm_rx_mp = NULL;
145190f05028Syq 	}
145290f05028Syq 
145390f05028Syq 	return (USB_SUCCESS);
145490f05028Syq }
145590f05028Syq 
145690f05028Syq 
145790f05028Syq /*
145890f05028Syq  * usbsacm_get_bulk_pipe_number:
145990f05028Syq  *	Calculate the number of bulk in or out pipes in current device.
146090f05028Syq  */
146190f05028Syq static int
usbsacm_get_bulk_pipe_number(usbsacm_state_t * acmp,uint_t dir)146290f05028Syq usbsacm_get_bulk_pipe_number(usbsacm_state_t *acmp, uint_t dir)
146390f05028Syq {
146490f05028Syq 	int		count = 0;
146590f05028Syq 	int		i, skip;
146690f05028Syq 	usb_if_data_t	*cur_if;
146790f05028Syq 	int		ep_num;
146890f05028Syq 	int		if_num;
146990f05028Syq 
147090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
147190f05028Syq 	    "usbsacm_get_bulk_pipe_number: ");
147290f05028Syq 
147390f05028Syq 	cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if;
147490f05028Syq 	if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if;
147590f05028Syq 
147690f05028Syq 	/* search each interface which have bulk endpoint */
147790f05028Syq 	for (i = 0; i < if_num; i++) {
147890f05028Syq 		ep_num = cur_if->if_alt->altif_n_ep;
147990f05028Syq 
148090f05028Syq 		/*
148190f05028Syq 		 * search endpoints in current interface,
148290f05028Syq 		 * which type is input parameter 'dir'
148390f05028Syq 		 */
148490f05028Syq 		for (skip = 0; skip < ep_num; skip++) {
14854ee52f77Slg 			if (usb_lookup_ep_data(acmp->acm_dip,
14869e37f2b5SRaymond Chen 			    acmp->acm_dev_data, i, 0, skip,
14874ee52f77Slg 			    USB_EP_ATTR_BULK, dir) == NULL) {
14884ee52f77Slg 
14894ee52f77Slg 				/*
14904ee52f77Slg 				 * If not found, skip the internal loop
14914ee52f77Slg 				 * and search the next interface.
14924ee52f77Slg 				 */
14934ee52f77Slg 				break;
14944ee52f77Slg 			}
14954ee52f77Slg 			count++;
149690f05028Syq 		}
149790f05028Syq 
149890f05028Syq 		cur_if++;
149990f05028Syq 	}
150090f05028Syq 
150190f05028Syq 	return (count);
150290f05028Syq }
150390f05028Syq 
150490f05028Syq 
150590f05028Syq /*
150690f05028Syq  * port management
150790f05028Syq  * ---------------
150890f05028Syq  *	initialize, release port.
150990f05028Syq  *
151090f05028Syq  *
151190f05028Syq  * usbsacm_init_ports_status:
151290f05028Syq  *	Initialize the port status for the current device.
151390f05028Syq  */
151490f05028Syq static int
usbsacm_init_ports_status(usbsacm_state_t * acmp)151590f05028Syq usbsacm_init_ports_status(usbsacm_state_t *acmp)
151690f05028Syq {
151790f05028Syq 	usbsacm_port_t	*cur_port;
151890f05028Syq 	int		i, skip;
151990f05028Syq 	int		if_num;
152090f05028Syq 	int		intr_if_no = 0;
152190f05028Syq 	int		ep_num;
152290f05028Syq 	usb_if_data_t	*cur_if;
152390f05028Syq 
152490f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
1525112116d8Sfb 	    "usbsacm_init_ports_status: acmp = 0x%p", (void *)acmp);
152690f05028Syq 
152790f05028Syq 	/* Initialize the port status to default value */
152890f05028Syq 	for (i = 0; i < acmp->acm_port_cnt; i++) {
152990f05028Syq 		cur_port = &acmp->acm_ports[i];
153090f05028Syq 
153190f05028Syq 		cv_init(&cur_port->acm_tx_cv, NULL, CV_DRIVER, NULL);
153290f05028Syq 
153390f05028Syq 		cur_port->acm_port_state = USBSACM_PORT_CLOSED;
153490f05028Syq 
153590f05028Syq 		cur_port->acm_line_coding.dwDTERate = LE_32((uint32_t)9600);
153690f05028Syq 		cur_port->acm_line_coding.bCharFormat = 0;
153790f05028Syq 		cur_port->acm_line_coding.bParityType = USB_CDC_PARITY_NO;
153890f05028Syq 		cur_port->acm_line_coding.bDataBits = 8;
153990f05028Syq 		cur_port->acm_device = acmp;
154090f05028Syq 		mutex_init(&cur_port->acm_port_mutex, NULL, MUTEX_DRIVER,
154190f05028Syq 		    acmp->acm_dev_data->dev_iblock_cookie);
154290f05028Syq 	}
154390f05028Syq 
154490f05028Syq 	/*
154590f05028Syq 	 * If device conform to cdc acm spec, parse function descriptors.
154690f05028Syq 	 */
154790f05028Syq 	if (acmp->acm_compatibility == B_TRUE) {
154890f05028Syq 
154990f05028Syq 		if (usbsacm_get_descriptors(acmp) != USB_SUCCESS) {
155090f05028Syq 
155190f05028Syq 			return (USB_FAILURE);
155290f05028Syq 		}
155390f05028Syq 
155490f05028Syq 		return (USB_SUCCESS);
155590f05028Syq 	}
155690f05028Syq 
155790f05028Syq 	/*
155890f05028Syq 	 * If device don't conform to spec, search pairs of bulk in/out
155990f05028Syq 	 * endpoints and fill port structure.
156090f05028Syq 	 */
156190f05028Syq 	cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if;
156290f05028Syq 	if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if;
156390f05028Syq 	cur_port = acmp->acm_ports;
156490f05028Syq 
156590f05028Syq 	/* search each interface which have bulk in and out */
156690f05028Syq 	for (i = 0; i < if_num; i++) {
15674ee52f77Slg 		ep_num = cur_if->if_alt->altif_n_ep;
156890f05028Syq 
15694ee52f77Slg 		for (skip = 0; skip < ep_num; skip++) {
157090f05028Syq 
157190f05028Syq 		/* search interrupt pipe. */
157290f05028Syq 		if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
157390f05028Syq 		    i, 0, skip, USB_EP_ATTR_INTR, USB_EP_DIR_IN) != NULL)) {
157490f05028Syq 
157590f05028Syq 			intr_if_no = i;
157690f05028Syq 		}
157790f05028Syq 
157890f05028Syq 		/* search pair of bulk in/out endpoints. */
157990f05028Syq 		if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
158090f05028Syq 		    i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_IN) == NULL) ||
158190f05028Syq 		    (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
158290f05028Syq 		    i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_OUT) == NULL)) {
158390f05028Syq 
158490f05028Syq 			continue;
158590f05028Syq 		}
158690f05028Syq 
158790f05028Syq 		cur_port->acm_data_if_no = i;
158890f05028Syq 		cur_port->acm_ctrl_if_no = intr_if_no;
158990f05028Syq 		cur_port->acm_data_port_no = skip;
159090f05028Syq 		cur_port++;
159190f05028Syq 		intr_if_no = 0;
15924ee52f77Slg 		}
159390f05028Syq 
15944ee52f77Slg 		cur_if++;
159590f05028Syq 	}
159690f05028Syq 
159790f05028Syq 	return (USB_SUCCESS);
159890f05028Syq }
159990f05028Syq 
160090f05028Syq 
160190f05028Syq /*
160290f05028Syq  * usbsacm_init_alloc_ports:
160390f05028Syq  *	Allocate memory and initialize the port state for the current device.
160490f05028Syq  */
160590f05028Syq static int
usbsacm_init_alloc_ports(usbsacm_state_t * acmp)160690f05028Syq usbsacm_init_alloc_ports(usbsacm_state_t *acmp)
160790f05028Syq {
16084ee52f77Slg 	int		rval = USB_SUCCESS;
160990f05028Syq 	int		count_in = 0, count_out = 0;
161090f05028Syq 
161190f05028Syq 	if (acmp->acm_compatibility) {
161290f05028Syq 		acmp->acm_port_cnt = 1;
161390f05028Syq 	} else {
161490f05028Syq 		/* Calculate the number of the bulk in/out endpoints */
161590f05028Syq 		count_in = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_IN);
161690f05028Syq 		count_out = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_OUT);
161790f05028Syq 
161890f05028Syq 		USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh,
161990f05028Syq 		    "usbsacm_init_alloc_ports: count_in = %d, count_out = %d",
162090f05028Syq 		    count_in, count_out);
162190f05028Syq 
162290f05028Syq 		acmp->acm_port_cnt = min(count_in, count_out);
162390f05028Syq 	}
162490f05028Syq 
162590f05028Syq 	/* return if not found any pair of bulk in/out endpoint. */
162690f05028Syq 	if (acmp->acm_port_cnt == 0) {
162790f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
162890f05028Syq 		    "usbsacm_init_alloc_ports: port count is zero.");
162990f05028Syq 
163090f05028Syq 		return (USB_FAILURE);
163190f05028Syq 	}
163290f05028Syq 
163390f05028Syq 	/* allocate memory for ports */
163490f05028Syq 	acmp->acm_ports = (usbsacm_port_t *)kmem_zalloc(acmp->acm_port_cnt *
163590f05028Syq 	    sizeof (usbsacm_port_t), KM_SLEEP);
163690f05028Syq 	if (acmp->acm_ports == NULL) {
163790f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
163890f05028Syq 		    "usbsacm_init_alloc_ports: allocate memory failed.");
163990f05028Syq 
164090f05028Syq 		return (USB_FAILURE);
164190f05028Syq 	}
164290f05028Syq 
164390f05028Syq 	/* fill the status of port structure. */
164490f05028Syq 	rval = usbsacm_init_ports_status(acmp);
164590f05028Syq 	if (rval != USB_SUCCESS) {
164690f05028Syq 		usbsacm_free_ports(acmp);
164790f05028Syq 	}
164890f05028Syq 
164990f05028Syq 	return (rval);
165090f05028Syq }
165190f05028Syq 
165290f05028Syq 
165390f05028Syq /*
165490f05028Syq  * usbsacm_free_ports:
165590f05028Syq  *	Release ports and deallocate memory.
165690f05028Syq  */
165790f05028Syq static void
usbsacm_free_ports(usbsacm_state_t * acmp)165890f05028Syq usbsacm_free_ports(usbsacm_state_t *acmp)
165990f05028Syq {
166090f05028Syq 	int		i;
166190f05028Syq 
166290f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
166390f05028Syq 	    "usbsacm_free_ports: ");
166490f05028Syq 
166590f05028Syq 	/* Release memory and data structure for each port */
166690f05028Syq 	for (i = 0; i < acmp->acm_port_cnt; i++) {
166790f05028Syq 		cv_destroy(&acmp->acm_ports[i].acm_tx_cv);
166890f05028Syq 		mutex_destroy(&acmp->acm_ports[i].acm_port_mutex);
166990f05028Syq 	}
167090f05028Syq 	kmem_free((caddr_t)acmp->acm_ports, sizeof (usbsacm_port_t) *
167190f05028Syq 	    acmp->acm_port_cnt);
167290f05028Syq 	acmp->acm_ports = NULL;
167390f05028Syq }
167490f05028Syq 
167590f05028Syq 
167690f05028Syq /*
167790f05028Syq  * usbsacm_get_descriptors:
167890f05028Syq  *	analysis functional descriptors of acm device
167990f05028Syq  */
168090f05028Syq static int
usbsacm_get_descriptors(usbsacm_state_t * acmp)168190f05028Syq usbsacm_get_descriptors(usbsacm_state_t *acmp)
168290f05028Syq {
168390f05028Syq 	int			i;
168490f05028Syq 	usb_cfg_data_t		*cfg;
168590f05028Syq 	usb_alt_if_data_t	*altif;
168690f05028Syq 	usb_cvs_data_t		*cvs;
168790f05028Syq 	int			mgmt_cap = 0;
168890f05028Syq 	int			master_if = -1, slave_if = -1;
168990f05028Syq 	usbsacm_port_t		*acm_port = acmp->acm_ports;
169080727282SMike Zeller 	usb_dev_descr_t		*dd;
169190f05028Syq 
169290f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
169390f05028Syq 	    "usbsacm_get_descriptors: ");
169490f05028Syq 
169580727282SMike Zeller 	dd = acmp->acm_dev_data->dev_descr;
169690f05028Syq 	cfg = acmp->acm_dev_data->dev_curr_cfg;
169790f05028Syq 	/* set default control and data interface */
169890f05028Syq 	acm_port->acm_ctrl_if_no = acm_port->acm_data_if_no = 0;
169990f05028Syq 
170090f05028Syq 	/* get current interfaces */
170190f05028Syq 	acm_port->acm_ctrl_if_no = acmp->acm_dev_data->dev_curr_if;
170290f05028Syq 	if (cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt == 0) {
170390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
170490f05028Syq 		    "usbsacm_get_descriptors: elements in if_alt is %d",
170590f05028Syq 		    cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt);
170690f05028Syq 
170790f05028Syq 		return (USB_FAILURE);
170890f05028Syq 	}
170990f05028Syq 
171090f05028Syq 	altif = &cfg->cfg_if[acm_port->acm_ctrl_if_no].if_alt[0];
171190f05028Syq 
171290f05028Syq 	/*
171390f05028Syq 	 * Based on CDC specification, ACM devices usually include the
171490f05028Syq 	 * following function descriptors: Header, ACM, Union and Call
171590f05028Syq 	 * Management function descriptors. This loop search tree data
171690f05028Syq 	 * structure for each acm class descriptor.
171790f05028Syq 	 */
171890f05028Syq 	for (i = 0; i < altif->altif_n_cvs; i++) {
171990f05028Syq 
172090f05028Syq 		cvs = &altif->altif_cvs[i];
172190f05028Syq 
172290f05028Syq 		if ((cvs->cvs_buf == NULL) ||
172390f05028Syq 		    (cvs->cvs_buf[1] != USB_CDC_CS_INTERFACE)) {
172490f05028Syq 			continue;
172590f05028Syq 		}
172690f05028Syq 
172790f05028Syq 		switch (cvs->cvs_buf[2]) {
172890f05028Syq 		case USB_CDC_DESCR_TYPE_CALL_MANAGEMENT:
172990f05028Syq 			/* parse call management functional descriptor. */
173090f05028Syq 			if (cvs->cvs_buf_len >= 5) {
173190f05028Syq 				mgmt_cap = cvs->cvs_buf[3];
173290f05028Syq 				acm_port->acm_data_if_no = cvs->cvs_buf[4];
173390f05028Syq 			}
173490f05028Syq 			break;
173590f05028Syq 		case USB_CDC_DESCR_TYPE_ACM:
173690f05028Syq 			/* parse ACM functional descriptor. */
173790f05028Syq 			if (cvs->cvs_buf_len >= 4) {
173890f05028Syq 				acm_port->acm_cap = cvs->cvs_buf[3];
173990f05028Syq 			}
174080727282SMike Zeller 
174180727282SMike Zeller 			/*
174280727282SMike Zeller 			 * The Sigma Designs, Inc. USB device does not report
174380727282SMike Zeller 			 * itself as implementing the full ACM spec. However,
174480727282SMike Zeller 			 * it does function as a usb serial modem, so we opt to
174580727282SMike Zeller 			 * scribble in the reported functionality if we
174680727282SMike Zeller 			 * determine the USB device matches this vendor
174780727282SMike Zeller 			 * and product ID.
174880727282SMike Zeller 			 */
174980727282SMike Zeller 			if (dd->idVendor == USB_VENDOR_SIGMADESIGNS &&
175080727282SMike Zeller 			    dd->idProduct == USB_PRODUCT_SIGMADESIGNS_ZW090) {
175180727282SMike Zeller 				acm_port->acm_cap |=
175280727282SMike Zeller 				    USB_CDC_ACM_CAP_SERIAL_LINE;
175380727282SMike Zeller 			}
175490f05028Syq 			break;
175590f05028Syq 		case USB_CDC_DESCR_TYPE_UNION:
175690f05028Syq 			/* parse Union functional descriptor. */
175790f05028Syq 			if (cvs->cvs_buf_len >= 5) {
175890f05028Syq 				master_if = cvs->cvs_buf[3];
175990f05028Syq 				slave_if = cvs->cvs_buf[4];
176090f05028Syq 			}
176190f05028Syq 			break;
176290f05028Syq 		default:
176390f05028Syq 			break;
176490f05028Syq 		}
176590f05028Syq 	}
176690f05028Syq 
176790f05028Syq 	/* For usb acm devices, it must satisfy the following options. */
176890f05028Syq 	if (cfg->cfg_n_if < 2) {
176990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
177090f05028Syq 		    "usbsacm_get_descriptors: # of interfaces %d < 2",
177190f05028Syq 		    cfg->cfg_n_if);
177290f05028Syq 
177390f05028Syq 		return (USB_FAILURE);
177490f05028Syq 	}
177590f05028Syq 
177690f05028Syq 	if (acm_port->acm_data_if_no == 0 &&
17774ee52f77Slg 	    slave_if != acm_port->acm_data_if_no) {
177890f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
177990f05028Syq 		    "usbsacm_get_descriptors: Device hasn't call management "
178090f05028Syq 		    "descriptor and use Union Descriptor.");
178190f05028Syq 
178290f05028Syq 		acm_port->acm_data_if_no = slave_if;
178390f05028Syq 	}
178490f05028Syq 
178590f05028Syq 	if ((master_if != acm_port->acm_ctrl_if_no) ||
178690f05028Syq 	    (slave_if != acm_port->acm_data_if_no)) {
178790f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
178890f05028Syq 		    "usbsacm_get_descriptors: control interface or "
178990f05028Syq 		    "data interface don't match.");
179090f05028Syq 
179190f05028Syq 		return (USB_FAILURE);
179290f05028Syq 	}
179390f05028Syq 
179490f05028Syq 	/*
179590f05028Syq 	 * We usually need both call and data capabilities, but
179690f05028Syq 	 * some devices, such as Nokia mobile phones, don't provide
179790f05028Syq 	 * call management descriptor, so we just give a warning
179890f05028Syq 	 * message.
179990f05028Syq 	 */
180090f05028Syq 	if (((mgmt_cap & USB_CDC_CALL_MGMT_CAP_CALL_MGMT) == 0) ||
180190f05028Syq 	    ((mgmt_cap & USB_CDC_CALL_MGMT_CAP_DATA_INTERFACE) == 0)) {
180290f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
180390f05028Syq 		    "usbsacm_get_descriptors: "
180490f05028Syq 		    "insufficient mgmt capabilities %x",
180590f05028Syq 		    mgmt_cap);
180690f05028Syq 	}
180790f05028Syq 
180890f05028Syq 	if ((acm_port->acm_ctrl_if_no >= cfg->cfg_n_if) ||
180990f05028Syq 	    (acm_port->acm_data_if_no >= cfg->cfg_n_if)) {
181090f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
181190f05028Syq 		    "usbsacm_get_descriptors: control interface %d or "
181290f05028Syq 		    "data interface %d out of range.",
181390f05028Syq 		    acm_port->acm_ctrl_if_no, acm_port->acm_data_if_no);
181490f05028Syq 
181590f05028Syq 		return (USB_FAILURE);
181690f05028Syq 	}
181790f05028Syq 
181890f05028Syq 	/* control interface must have interrupt endpoint */
181990f05028Syq 	if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
182090f05028Syq 	    acm_port->acm_ctrl_if_no, 0, 0, USB_EP_ATTR_INTR,
182190f05028Syq 	    USB_EP_DIR_IN) == NULL) {
182290f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
182390f05028Syq 		    "usbsacm_get_descriptors: "
182490f05028Syq 		    "ctrl interface %d has no interrupt endpoint",
182590f05028Syq 		    acm_port->acm_data_if_no);
182690f05028Syq 
182790f05028Syq 		return (USB_FAILURE);
182890f05028Syq 	}
182990f05028Syq 
183090f05028Syq 	/* data interface must have bulk in and out */
183190f05028Syq 	if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
183290f05028Syq 	    acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK,
183390f05028Syq 	    USB_EP_DIR_IN) == NULL) {
183490f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
183590f05028Syq 		    "usbsacm_get_descriptors: "
183690f05028Syq 		    "data interface %d has no bulk in endpoint",
183790f05028Syq 		    acm_port->acm_data_if_no);
183890f05028Syq 
183990f05028Syq 		return (USB_FAILURE);
184090f05028Syq 	}
184190f05028Syq 	if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
184290f05028Syq 	    acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK,
184390f05028Syq 	    USB_EP_DIR_OUT) == NULL) {
184490f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
184590f05028Syq 		    "usbsacm_get_descriptors: "
184690f05028Syq 		    "data interface %d has no bulk out endpoint",
184790f05028Syq 		    acm_port->acm_data_if_no);
184890f05028Syq 
184990f05028Syq 		return (USB_FAILURE);
185090f05028Syq 	}
185190f05028Syq 
185290f05028Syq 	return (USB_SUCCESS);
185390f05028Syq }
185490f05028Syq 
185590f05028Syq 
185690f05028Syq /*
185790f05028Syq  * usbsacm_cleanup:
185890f05028Syq  *	Release resources of current device during detach.
185990f05028Syq  */
186090f05028Syq static void
usbsacm_cleanup(usbsacm_state_t * acmp)186190f05028Syq usbsacm_cleanup(usbsacm_state_t *acmp)
186290f05028Syq {
186390f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
186490f05028Syq 	    "usbsacm_cleanup: ");
186590f05028Syq 
186690f05028Syq 	if (acmp != NULL) {
186790f05028Syq 		/* free ports */
186890f05028Syq 		if (acmp->acm_ports != NULL) {
186990f05028Syq 			usbsacm_free_ports(acmp);
187090f05028Syq 		}
187190f05028Syq 
187290f05028Syq 		/* unregister callback function */
187390f05028Syq 		if (acmp->acm_usb_events != NULL) {
187490f05028Syq 			usb_unregister_event_cbs(acmp->acm_dip,
18754ee52f77Slg 			    acmp->acm_usb_events);
187690f05028Syq 		}
187790f05028Syq 
187890f05028Syq 		/* destroy power management components */
187990f05028Syq 		if (acmp->acm_pm != NULL) {
188090f05028Syq 			usbsacm_destroy_pm_components(acmp);
188190f05028Syq 		}
188290f05028Syq 
188390f05028Syq 		/* free description of device tree. */
188490f05028Syq 		if (acmp->acm_def_ph != NULL) {
188590f05028Syq 			mutex_destroy(&acmp->acm_mutex);
188690f05028Syq 
188790f05028Syq 			usb_free_descr_tree(acmp->acm_dip, acmp->acm_dev_data);
188890f05028Syq 			acmp->acm_def_ph = NULL;
188990f05028Syq 		}
189090f05028Syq 
189190f05028Syq 		if (acmp->acm_lh != NULL) {
189290f05028Syq 			usb_free_log_hdl(acmp->acm_lh);
189390f05028Syq 			acmp->acm_lh = NULL;
189490f05028Syq 		}
189590f05028Syq 
189690f05028Syq 		/* detach client device */
189790f05028Syq 		if (acmp->acm_dev_data != NULL) {
189890f05028Syq 			usb_client_detach(acmp->acm_dip, acmp->acm_dev_data);
189990f05028Syq 		}
190090f05028Syq 
190190f05028Syq 		kmem_free((caddr_t)acmp, sizeof (usbsacm_state_t));
190290f05028Syq 	}
190390f05028Syq }
190490f05028Syq 
190590f05028Syq 
190690f05028Syq /*
190790f05028Syq  * usbsacm_restore_device_state:
190890f05028Syq  *	restore device state after CPR resume or reconnect
190990f05028Syq  */
191090f05028Syq static int
usbsacm_restore_device_state(usbsacm_state_t * acmp)191190f05028Syq usbsacm_restore_device_state(usbsacm_state_t *acmp)
191290f05028Syq {
191390f05028Syq 	int	state;
191490f05028Syq 
191590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
191690f05028Syq 	    "usbsacm_restore_device_state: ");
191790f05028Syq 
191890f05028Syq 	mutex_enter(&acmp->acm_mutex);
191990f05028Syq 	state = acmp->acm_dev_state;
192090f05028Syq 	mutex_exit(&acmp->acm_mutex);
192190f05028Syq 
192290f05028Syq 	/* Check device status */
192390f05028Syq 	if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) {
192490f05028Syq 
192590f05028Syq 		return (state);
192690f05028Syq 	}
192790f05028Syq 
192890f05028Syq 	/* Check if we are talking to the same device */
192990f05028Syq 	if (usb_check_same_device(acmp->acm_dip, acmp->acm_lh, USB_LOG_L0,
193090f05028Syq 	    -1, USB_CHK_ALL, NULL) != USB_SUCCESS) {
193190f05028Syq 		mutex_enter(&acmp->acm_mutex);
193290f05028Syq 		state = acmp->acm_dev_state = USB_DEV_DISCONNECTED;
193390f05028Syq 		mutex_exit(&acmp->acm_mutex);
193490f05028Syq 
193590f05028Syq 		return (state);
193690f05028Syq 	}
193790f05028Syq 
193890f05028Syq 	if (state == USB_DEV_DISCONNECTED) {
193990f05028Syq 		USB_DPRINTF_L1(PRINT_MASK_ALL, acmp->acm_lh,
194090f05028Syq 		    "usbsacm_restore_device_state: Device has been reconnected "
194190f05028Syq 		    "but data may have been lost");
194290f05028Syq 	}
194390f05028Syq 
194490f05028Syq 	/* reconnect pipes */
194590f05028Syq 	if (usbsacm_reconnect_pipes(acmp) != USB_SUCCESS) {
194690f05028Syq 
194790f05028Syq 		return (state);
194890f05028Syq 	}
194990f05028Syq 
195090f05028Syq 	/*
195190f05028Syq 	 * init device state
195290f05028Syq 	 */
195390f05028Syq 	mutex_enter(&acmp->acm_mutex);
195490f05028Syq 	state = acmp->acm_dev_state = USB_DEV_ONLINE;
195590f05028Syq 	mutex_exit(&acmp->acm_mutex);
195690f05028Syq 
195790f05028Syq 	if ((usbsacm_restore_port_state(acmp) != USB_SUCCESS)) {
195890f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
195990f05028Syq 		    "usbsacm_restore_device_state: failed");
196090f05028Syq 	}
196190f05028Syq 
196290f05028Syq 	return (state);
196390f05028Syq }
196490f05028Syq 
196590f05028Syq 
196690f05028Syq /*
196790f05028Syq  * usbsacm_restore_port_state:
196890f05028Syq  *	restore ports state after CPR resume or reconnect
196990f05028Syq  */
197090f05028Syq static int
usbsacm_restore_port_state(usbsacm_state_t * acmp)197190f05028Syq usbsacm_restore_port_state(usbsacm_state_t *acmp)
197290f05028Syq {
197390f05028Syq 	int		i, ret = USB_SUCCESS;
197490f05028Syq 	usbsacm_port_t	*cur_port;
197590f05028Syq 
197690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
197790f05028Syq 	    "usbsacm_restore_port_state: ");
197890f05028Syq 
197990f05028Syq 	/* restore status of all ports */
198090f05028Syq 	for (i = 0; i < acmp->acm_port_cnt; i++) {
198190f05028Syq 		cur_port = &acmp->acm_ports[i];
198290f05028Syq 		mutex_enter(&cur_port->acm_port_mutex);
198390f05028Syq 		if (cur_port->acm_port_state != USBSACM_PORT_OPEN) {
198490f05028Syq 			mutex_exit(&cur_port->acm_port_mutex);
198590f05028Syq 
198690f05028Syq 			continue;
198790f05028Syq 		}
198890f05028Syq 		mutex_exit(&cur_port->acm_port_mutex);
198990f05028Syq 
199090f05028Syq 		if ((ret = usbsacm_set_line_coding(cur_port,
199190f05028Syq 		    &cur_port->acm_line_coding)) != USB_SUCCESS) {
199290f05028Syq 			USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh,
199390f05028Syq 			    "usbsacm_restore_port_state: failed.");
199490f05028Syq 		}
199590f05028Syq 	}
199690f05028Syq 
199790f05028Syq 	return (ret);
199890f05028Syq }
199990f05028Syq 
200090f05028Syq 
200190f05028Syq /*
200290f05028Syq  * pipe management
200390f05028Syq  * ---------------
200490f05028Syq  *
200590f05028Syq  *
200690f05028Syq  * usbsacm_open_port_pipes:
200790f05028Syq  *	Open pipes of one port and set port structure;
200890f05028Syq  *	Each port includes three pipes: bulk in, bulk out and interrupt.
200990f05028Syq  */
201090f05028Syq static int
usbsacm_open_port_pipes(usbsacm_port_t * acm_port)201190f05028Syq usbsacm_open_port_pipes(usbsacm_port_t *acm_port)
201290f05028Syq {
201390f05028Syq 	int		rval = USB_SUCCESS;
201490f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
201590f05028Syq 	usb_ep_data_t	*in_data, *out_data, *intr_pipe;
201690f05028Syq 	usb_pipe_policy_t policy;
201790f05028Syq 
201890f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
2019112116d8Sfb 	    "usbsacm_open_port_pipes: acmp = 0x%p", (void *)acmp);
202090f05028Syq 
202190f05028Syq 	/* Get bulk and interrupt endpoint data */
202290f05028Syq 	intr_pipe = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
202390f05028Syq 	    acm_port->acm_ctrl_if_no, 0, 0,
202490f05028Syq 	    USB_EP_ATTR_INTR, USB_EP_DIR_IN);
202590f05028Syq 	in_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
202690f05028Syq 	    acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no,
202790f05028Syq 	    USB_EP_ATTR_BULK, USB_EP_DIR_IN);
202890f05028Syq 	out_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data,
202990f05028Syq 	    acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no,
203090f05028Syq 	    USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
203190f05028Syq 
203290f05028Syq 	/* Bulk in and out must exist meanwhile. */
203390f05028Syq 	if ((in_data == NULL) || (out_data == NULL)) {
203490f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
203590f05028Syq 		    "usbsacm_open_port_pipes: look up bulk pipe failed in "
203690f05028Syq 		    "interface %d port %d",
203790f05028Syq 		    acm_port->acm_data_if_no, acm_port->acm_data_port_no);
203890f05028Syq 
203990f05028Syq 		return (USB_FAILURE);
204090f05028Syq 	}
204190f05028Syq 
204290f05028Syq 	/*
204390f05028Syq 	 * If device conform to acm spec, it must have an interrupt pipe
204490f05028Syq 	 * for this port.
204590f05028Syq 	 */
204690f05028Syq 	if (acmp->acm_compatibility == B_TRUE && intr_pipe == NULL) {
204790f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
204890f05028Syq 		    "usbsacm_open_port_pipes: look up interrupt pipe failed in "
204990f05028Syq 		    "interface %d", acm_port->acm_ctrl_if_no);
205090f05028Syq 
205190f05028Syq 		return (USB_FAILURE);
205290f05028Syq 	}
205390f05028Syq 
205490f05028Syq 	policy.pp_max_async_reqs = 2;
205590f05028Syq 
205690f05028Syq 	/* Open bulk in endpoint */
205790f05028Syq 	if (usb_pipe_open(acmp->acm_dip, &in_data->ep_descr, &policy,
205890f05028Syq 	    USB_FLAGS_SLEEP, &acm_port->acm_bulkin_ph) != USB_SUCCESS) {
205990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
206090f05028Syq 		    "usbsacm_open_port_pipes: open bulkin pipe failed!");
206190f05028Syq 
206290f05028Syq 		return (USB_FAILURE);
206390f05028Syq 	}
206490f05028Syq 
206590f05028Syq 	/* Open bulk out endpoint */
206690f05028Syq 	if (usb_pipe_open(acmp->acm_dip, &out_data->ep_descr, &policy,
206790f05028Syq 	    USB_FLAGS_SLEEP, &acm_port->acm_bulkout_ph) != USB_SUCCESS) {
206890f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
206990f05028Syq 		    "usbsacm_open_port_pipes: open bulkout pipe failed!");
207090f05028Syq 
207190f05028Syq 		usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
207290f05028Syq 		    USB_FLAGS_SLEEP, NULL, NULL);
207390f05028Syq 
207490f05028Syq 		return (USB_FAILURE);
207590f05028Syq 	}
207690f05028Syq 
207790f05028Syq 	/* Open interrupt endpoint if found. */
207890f05028Syq 	if (intr_pipe != NULL) {
207990f05028Syq 
208090f05028Syq 		if (usb_pipe_open(acmp->acm_dip, &intr_pipe->ep_descr, &policy,
208190f05028Syq 		    USB_FLAGS_SLEEP, &acm_port->acm_intr_ph) != USB_SUCCESS) {
208290f05028Syq 			USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
208390f05028Syq 			    "usbsacm_open_port_pipes: "
208490f05028Syq 			    "open control pipe failed");
208590f05028Syq 
208690f05028Syq 			usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
208790f05028Syq 			    USB_FLAGS_SLEEP, NULL, NULL);
208890f05028Syq 			usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph,
208990f05028Syq 			    USB_FLAGS_SLEEP, NULL, NULL);
209090f05028Syq 
209190f05028Syq 			return (USB_FAILURE);
209290f05028Syq 		}
209390f05028Syq 	}
209490f05028Syq 
209590f05028Syq 	/* initialize the port structure. */
209690f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
209790f05028Syq 	acm_port->acm_bulkin_size = in_data->ep_descr.wMaxPacketSize;
209890f05028Syq 	acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
209990f05028Syq 	acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
210090f05028Syq 	if (acm_port->acm_intr_ph != NULL) {
210190f05028Syq 		acm_port->acm_intr_state = USBSACM_PIPE_IDLE;
210290f05028Syq 		acm_port->acm_intr_ep_descr = intr_pipe->ep_descr;
210390f05028Syq 	}
210490f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
210590f05028Syq 
210690f05028Syq 	if (acm_port->acm_intr_ph != NULL) {
210790f05028Syq 
210890f05028Syq 		usbsacm_pipe_start_polling(acm_port);
210990f05028Syq 	}
211090f05028Syq 
211190f05028Syq 	return (rval);
211290f05028Syq }
211390f05028Syq 
211490f05028Syq 
211590f05028Syq /*
211690f05028Syq  * usbsacm_close_port_pipes:
211790f05028Syq  *	Close pipes of one port and reset port structure to closed;
211890f05028Syq  *	Each port includes three pipes: bulk in, bulk out and interrupt.
211990f05028Syq  */
212090f05028Syq static void
usbsacm_close_port_pipes(usbsacm_port_t * acm_port)212190f05028Syq usbsacm_close_port_pipes(usbsacm_port_t	*acm_port)
212290f05028Syq {
212390f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
212490f05028Syq 
212590f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
212690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
212790f05028Syq 	    "usbsacm_close_port_pipes: acm_bulkin_state = %d",
212890f05028Syq 	    acm_port->acm_bulkin_state);
212990f05028Syq 
213090f05028Syq 	/*
213190f05028Syq 	 * Check the status of the given port. If port is closing or closed,
213290f05028Syq 	 * return directly.
213390f05028Syq 	 */
213490f05028Syq 	if ((acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSED) ||
213590f05028Syq 	    (acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSING)) {
213690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_CLOSE, acmp->acm_lh,
213790f05028Syq 		    "usbsacm_close_port_pipes: port is closing or has closed");
213890f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
213990f05028Syq 
214090f05028Syq 		return;
214190f05028Syq 	}
214290f05028Syq 
214390f05028Syq 	acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSING;
214490f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
214590f05028Syq 
214690f05028Syq 	/* Close pipes */
214790f05028Syq 	usb_pipe_reset(acmp->acm_dip, acm_port->acm_bulkin_ph,
214890f05028Syq 	    USB_FLAGS_SLEEP, 0, 0);
214990f05028Syq 	usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph,
215090f05028Syq 	    USB_FLAGS_SLEEP, 0, 0);
215190f05028Syq 	usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph,
215290f05028Syq 	    USB_FLAGS_SLEEP, 0, 0);
215390f05028Syq 	if (acm_port->acm_intr_ph != NULL) {
215490f05028Syq 		usb_pipe_stop_intr_polling(acm_port->acm_intr_ph,
215590f05028Syq 		    USB_FLAGS_SLEEP);
215690f05028Syq 		usb_pipe_close(acmp->acm_dip, acm_port->acm_intr_ph,
215790f05028Syq 		    USB_FLAGS_SLEEP, 0, 0);
215890f05028Syq 	}
215990f05028Syq 
216090f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
216190f05028Syq 	/* Reset the status of pipes to closed */
216290f05028Syq 	acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSED;
216390f05028Syq 	acm_port->acm_bulkin_ph = NULL;
216490f05028Syq 	acm_port->acm_bulkout_state = USBSACM_PIPE_CLOSED;
216590f05028Syq 	acm_port->acm_bulkout_ph = NULL;
216690f05028Syq 	if (acm_port->acm_intr_ph != NULL) {
216790f05028Syq 		acm_port->acm_intr_state = USBSACM_PIPE_CLOSED;
216890f05028Syq 		acm_port->acm_intr_ph = NULL;
216990f05028Syq 	}
217090f05028Syq 
217190f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
217290f05028Syq 
217390f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
217490f05028Syq 	    "usbsacm_close_port_pipes: port has been closed.");
217590f05028Syq }
217690f05028Syq 
217790f05028Syq 
217890f05028Syq /*
217990f05028Syq  * usbsacm_close_pipes:
218090f05028Syq  *	close all opened pipes of current devices.
218190f05028Syq  */
218290f05028Syq static void
usbsacm_close_pipes(usbsacm_state_t * acmp)218390f05028Syq usbsacm_close_pipes(usbsacm_state_t *acmp)
218490f05028Syq {
218590f05028Syq 	int		i;
218690f05028Syq 
218790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
218890f05028Syq 	    "usbsacm_close_pipes: ");
218990f05028Syq 
219090f05028Syq 	/* Close all ports */
219190f05028Syq 	for (i = 0; i < acmp->acm_port_cnt; i++) {
219290f05028Syq 		usbsacm_close_port_pipes(&acmp->acm_ports[i]);
219390f05028Syq 	}
219490f05028Syq }
219590f05028Syq 
219690f05028Syq 
219790f05028Syq /*
219890f05028Syq  * usbsacm_disconnect_pipes:
219990f05028Syq  *	this function just call usbsacm_close_pipes.
220090f05028Syq  */
220190f05028Syq static void
usbsacm_disconnect_pipes(usbsacm_state_t * acmp)220290f05028Syq usbsacm_disconnect_pipes(usbsacm_state_t *acmp)
220390f05028Syq {
220490f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
220590f05028Syq 	    "usbsacm_disconnect_pipes: ");
220690f05028Syq 
220790f05028Syq 	usbsacm_close_pipes(acmp);
220890f05028Syq }
220990f05028Syq 
221090f05028Syq 
221190f05028Syq /*
221290f05028Syq  * usbsacm_reconnect_pipes:
221390f05028Syq  *	reconnect pipes in CPR resume or reconnect
221490f05028Syq  */
221590f05028Syq static int
usbsacm_reconnect_pipes(usbsacm_state_t * acmp)221690f05028Syq usbsacm_reconnect_pipes(usbsacm_state_t *acmp)
221790f05028Syq {
221890f05028Syq 	usbsacm_port_t	*cur_port = acmp->acm_ports;
221990f05028Syq 	int		i;
222090f05028Syq 
222190f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
222290f05028Syq 	    "usbsacm_reconnect_pipes: ");
222390f05028Syq 
222490f05028Syq 	/* reopen all ports of current device. */
222590f05028Syq 	for (i = 0; i < acmp->acm_port_cnt; i++) {
222690f05028Syq 		cur_port = &acmp->acm_ports[i];
222790f05028Syq 
222890f05028Syq 		mutex_enter(&cur_port->acm_port_mutex);
222990f05028Syq 		/*
223090f05028Syq 		 * If port status is open, reopen it;
223190f05028Syq 		 * else retain the current status.
223290f05028Syq 		 */
223390f05028Syq 		if (cur_port->acm_port_state == USBSACM_PORT_OPEN) {
223490f05028Syq 
223590f05028Syq 			mutex_exit(&cur_port->acm_port_mutex);
223690f05028Syq 			if (usbsacm_open_port_pipes(cur_port) != USB_SUCCESS) {
223790f05028Syq 				USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh,
223890f05028Syq 				    "usbsacm_reconnect_pipes: "
223990f05028Syq 				    "open port %d failed.", i);
224090f05028Syq 
224190f05028Syq 				return (USB_FAILURE);
224290f05028Syq 			}
224390f05028Syq 			mutex_enter(&cur_port->acm_port_mutex);
224490f05028Syq 		}
224590f05028Syq 		mutex_exit(&cur_port->acm_port_mutex);
224690f05028Syq 	}
224790f05028Syq 
224890f05028Syq 	return (USB_SUCCESS);
224990f05028Syq }
225090f05028Syq 
225190f05028Syq /*
225290f05028Syq  * usbsacm_bulkin_cb:
225390f05028Syq  *	Bulk In regular and exeception callback;
225490f05028Syq  *	USBA framework will call this callback
225590f05028Syq  *	after deal with bulkin request.
225690f05028Syq  */
225790f05028Syq /*ARGSUSED*/
225890f05028Syq static void
usbsacm_bulkin_cb(usb_pipe_handle_t pipe,usb_bulk_req_t * req)225990f05028Syq usbsacm_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
226090f05028Syq {
226190f05028Syq 	usbsacm_port_t	*acm_port = (usbsacm_port_t *)req->bulk_client_private;
226290f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
226390f05028Syq 	mblk_t		*data;
226490f05028Syq 	int		data_len;
226590f05028Syq 
226690f05028Syq 	data = req->bulk_data;
226790f05028Syq 	data_len = (data) ? MBLKL(data) : 0;
226890f05028Syq 
226990f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
227090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
227190f05028Syq 	    "usbsacm_bulkin_cb: "
227290f05028Syq 	    "acm_bulkin_state = %d acm_port_state = %d data_len = %d",
227390f05028Syq 	    acm_port->acm_bulkin_state, acm_port->acm_port_state, data_len);
227490f05028Syq 
227590f05028Syq 	if ((acm_port->acm_port_state == USBSACM_PORT_OPEN) && (data_len) &&
227690f05028Syq 	    (req->bulk_completion_reason == USB_CR_OK)) {
227790f05028Syq 		mutex_exit(&acm_port->acm_port_mutex);
227890f05028Syq 		/* prevent USBA from freeing data along with the request */
227990f05028Syq 		req->bulk_data = NULL;
228090f05028Syq 
228190f05028Syq 		/* save data on the receive list */
228290f05028Syq 		usbsacm_put_tail(&acm_port->acm_rx_mp, data);
228390f05028Syq 
228490f05028Syq 		/* invoke GSD receive callback */
228590f05028Syq 		if (acm_port->acm_cb.cb_rx) {
228690f05028Syq 			acm_port->acm_cb.cb_rx(acm_port->acm_cb.cb_arg);
228790f05028Syq 		}
228890f05028Syq 		mutex_enter(&acm_port->acm_port_mutex);
228990f05028Syq 	}
229090f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
229190f05028Syq 
229290f05028Syq 	usb_free_bulk_req(req);
229390f05028Syq 
229490f05028Syq 	/* receive more */
229590f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
229690f05028Syq 	if (((acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) ||
229790f05028Syq 	    (acm_port->acm_bulkin_state == USBSACM_PIPE_IDLE)) &&
229890f05028Syq 	    (acm_port->acm_port_state == USBSACM_PORT_OPEN) &&
229990f05028Syq 	    (acmp->acm_dev_state == USB_DEV_ONLINE)) {
230090f05028Syq 		if (usbsacm_rx_start(acm_port) != USB_SUCCESS) {
230190f05028Syq 			USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
230290f05028Syq 			    "usbsacm_bulkin_cb: restart rx fail "
230390f05028Syq 			    "acm_port_state = %d", acm_port->acm_port_state);
230490f05028Syq 		}
230590f05028Syq 	} else if (acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) {
230690f05028Syq 		acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
230790f05028Syq 	}
230890f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
230990f05028Syq }
231090f05028Syq 
231190f05028Syq 
231290f05028Syq /*
231390f05028Syq  * usbsacm_bulkout_cb:
231490f05028Syq  *	Bulk Out regular and exeception callback;
231590f05028Syq  *	USBA framework will call this callback function
231690f05028Syq  *	after deal with bulkout request.
231790f05028Syq  */
231890f05028Syq /*ARGSUSED*/
231990f05028Syq static void
usbsacm_bulkout_cb(usb_pipe_handle_t pipe,usb_bulk_req_t * req)232090f05028Syq usbsacm_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
232190f05028Syq {
232290f05028Syq 	usbsacm_port_t	*acm_port = (usbsacm_port_t *)req->bulk_client_private;
232390f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
232490f05028Syq 	int		data_len;
232590f05028Syq 	mblk_t		*data = req->bulk_data;
232690f05028Syq 
232790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
2328112116d8Sfb 	    "usbsacm_bulkout_cb: acmp = 0x%p", (void *)acmp);
232990f05028Syq 
233090f05028Syq 	data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0;
233190f05028Syq 
233290f05028Syq 	/* put untransferred residue back on the transfer list */
233390f05028Syq 	if (req->bulk_completion_reason && (data_len > 0)) {
233490f05028Syq 		usbsacm_put_head(&acm_port->acm_tx_mp, data);
233590f05028Syq 		req->bulk_data = NULL;
233690f05028Syq 	}
233790f05028Syq 
233890f05028Syq 	usb_free_bulk_req(req);
233990f05028Syq 
234090f05028Syq 	/* invoke GSD transmit callback */
234190f05028Syq 	if (acm_port->acm_cb.cb_tx) {
234290f05028Syq 		acm_port->acm_cb.cb_tx(acm_port->acm_cb.cb_arg);
234390f05028Syq 	}
234490f05028Syq 
234590f05028Syq 	/* send more */
234690f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
234790f05028Syq 	acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
234890f05028Syq 	if (acm_port->acm_tx_mp == NULL) {
234990f05028Syq 		cv_broadcast(&acm_port->acm_tx_cv);
235090f05028Syq 	} else {
235190f05028Syq 		usbsacm_tx_start(acm_port);
235290f05028Syq 	}
235390f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
235490f05028Syq }
235590f05028Syq 
235690f05028Syq 
235790f05028Syq /*
235890f05028Syq  * usbsacm_rx_start:
235990f05028Syq  *	start data receipt
236090f05028Syq  */
236190f05028Syq static int
usbsacm_rx_start(usbsacm_port_t * acm_port)236290f05028Syq usbsacm_rx_start(usbsacm_port_t *acm_port)
236390f05028Syq {
236490f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
236590f05028Syq 	usb_bulk_req_t	*br;
236690f05028Syq 	int		rval = USB_FAILURE;
236790f05028Syq 	int		data_len;
236890f05028Syq 
236990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
2370112116d8Sfb 	    "usbsacm_rx_start: acm_xfer_sz = 0x%lx acm_bulkin_size = 0x%lx",
237190f05028Syq 	    acmp->acm_xfer_sz, acm_port->acm_bulkin_size);
237290f05028Syq 
237390f05028Syq 	acm_port->acm_bulkin_state = USBSACM_PIPE_BUSY;
237490f05028Syq 	/*
237590f05028Syq 	 * Qualcomm CDMA card won't response the first request,
237690f05028Syq 	 * if the following code don't multiply by 2.
237790f05028Syq 	 */
237890f05028Syq 	data_len = min(acmp->acm_xfer_sz, acm_port->acm_bulkin_size * 2);
237990f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
238090f05028Syq 
238190f05028Syq 	br = usb_alloc_bulk_req(acmp->acm_dip, data_len, USB_FLAGS_SLEEP);
238290f05028Syq 	if (br == NULL) {
238390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
238490f05028Syq 		    "usbsacm_rx_start: allocate bulk request failed");
238590f05028Syq 
238690f05028Syq 		mutex_enter(&acm_port->acm_port_mutex);
238790f05028Syq 
238890f05028Syq 		return (USB_FAILURE);
238990f05028Syq 	}
239090f05028Syq 	/* initialize bulk in request. */
239190f05028Syq 	br->bulk_len = data_len;
239290f05028Syq 	br->bulk_timeout = USBSACM_BULKIN_TIMEOUT;
239390f05028Syq 	br->bulk_cb = usbsacm_bulkin_cb;
239490f05028Syq 	br->bulk_exc_cb = usbsacm_bulkin_cb;
239590f05028Syq 	br->bulk_client_private = (usb_opaque_t)acm_port;
239690f05028Syq 	br->bulk_attributes = USB_ATTRS_AUTOCLEARING
239790f05028Syq 	    | USB_ATTRS_SHORT_XFER_OK;
239890f05028Syq 
239990f05028Syq 	rval = usb_pipe_bulk_xfer(acm_port->acm_bulkin_ph, br, 0);
240090f05028Syq 
240190f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
240290f05028Syq 	if (rval != USB_SUCCESS) {
240390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
240490f05028Syq 		    "usbsacm_rx_start: bulk transfer failed %d", rval);
240590f05028Syq 		usb_free_bulk_req(br);
240690f05028Syq 		acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE;
240790f05028Syq 	}
240890f05028Syq 
240990f05028Syq 	return (rval);
241090f05028Syq }
241190f05028Syq 
241290f05028Syq 
241390f05028Syq /*
241490f05028Syq  * usbsacm_tx_start:
241590f05028Syq  *	start data transmit
241690f05028Syq  */
241790f05028Syq static void
usbsacm_tx_start(usbsacm_port_t * acm_port)241890f05028Syq usbsacm_tx_start(usbsacm_port_t *acm_port)
241990f05028Syq {
242090f05028Syq 	int		len;		/* bytes we can transmit */
242190f05028Syq 	mblk_t		*data;		/* data to be transmitted */
242290f05028Syq 	int		data_len;	/* bytes in 'data' */
242390f05028Syq 	mblk_t		*mp;		/* current msgblk */
242490f05028Syq 	int		copylen;	/* bytes copy from 'mp' to 'data' */
242590f05028Syq 	int		rval;
242690f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
242790f05028Syq 
242890f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
242990f05028Syq 	    "usbsacm_tx_start: ");
243090f05028Syq 
243190f05028Syq 	/* check the transmitted data. */
243290f05028Syq 	if (acm_port->acm_tx_mp == NULL) {
243390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
243490f05028Syq 		    "usbsacm_tx_start: acm_tx_mp is NULL");
243590f05028Syq 
243690f05028Syq 		return;
243790f05028Syq 	}
243890f05028Syq 
243990f05028Syq 	/* check pipe status */
244090f05028Syq 	if (acm_port->acm_bulkout_state != USBSACM_PIPE_IDLE) {
244190f05028Syq 
244290f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
244390f05028Syq 		    "usbsacm_tx_start: error state in bulkout endpoint");
244490f05028Syq 
244590f05028Syq 		return;
244690f05028Syq 	}
244790f05028Syq 	ASSERT(MBLKL(acm_port->acm_tx_mp) > 0);
244890f05028Syq 
244990f05028Syq 	/* send as much data as port can receive */
245090f05028Syq 	len = min(msgdsize(acm_port->acm_tx_mp), acmp->acm_xfer_sz);
245190f05028Syq 
245290f05028Syq 	if (len == 0) {
245390f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
245490f05028Syq 		    "usbsacm_tx_start: data len is 0");
245590f05028Syq 
245690f05028Syq 		return;
245790f05028Syq 	}
245890f05028Syq 
245990f05028Syq 	/* allocate memory for sending data. */
246090f05028Syq 	if ((data = allocb(len, BPRI_LO)) == NULL) {
246190f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
246290f05028Syq 		    "usbsacm_tx_start: failure in allocate memory");
246390f05028Syq 
246490f05028Syq 		return;
246590f05028Syq 	}
246690f05028Syq 
246790f05028Syq 	/*
246890f05028Syq 	 * copy no more than 'len' bytes from mblk chain to transmit mblk 'data'
246990f05028Syq 	 */
247090f05028Syq 	data_len = 0;
247190f05028Syq 	while ((data_len < len) && acm_port->acm_tx_mp) {
247290f05028Syq 		/* Get the first mblk from chain. */
247390f05028Syq 		mp = acm_port->acm_tx_mp;
247490f05028Syq 		copylen = min(MBLKL(mp), len - data_len);
247590f05028Syq 		bcopy(mp->b_rptr, data->b_wptr, copylen);
247690f05028Syq 		mp->b_rptr += copylen;
247790f05028Syq 		data->b_wptr += copylen;
247890f05028Syq 		data_len += copylen;
247990f05028Syq 
248022eb7cb5Sgd 		if (MBLKL(mp) < 1) {
248190f05028Syq 			acm_port->acm_tx_mp = unlinkb(mp);
248290f05028Syq 			freeb(mp);
248390f05028Syq 		} else {
248490f05028Syq 			ASSERT(data_len == len);
248590f05028Syq 		}
248690f05028Syq 	}
248790f05028Syq 
248890f05028Syq 	if (data_len <= 0) {
248990f05028Syq 		freeb(data);
249090f05028Syq 
249190f05028Syq 		return;
249290f05028Syq 	}
249390f05028Syq 
249490f05028Syq 	acm_port->acm_bulkout_state = USBSACM_PIPE_BUSY;
249590f05028Syq 
249690f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
249790f05028Syq 	/* send request. */
249890f05028Syq 	rval = usbsacm_send_data(acm_port, data);
249990f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
250090f05028Syq 
250190f05028Syq 	/*
250290f05028Syq 	 * If send failed, retransmit data when acm_tx_mp is null.
250390f05028Syq 	 */
250490f05028Syq 	if (rval != USB_SUCCESS) {
250590f05028Syq 		acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE;
250690f05028Syq 		if (acm_port->acm_tx_mp == NULL) {
250790f05028Syq 			usbsacm_put_head(&acm_port->acm_tx_mp, data);
250890f05028Syq 		}
250990f05028Syq 	}
251090f05028Syq }
251190f05028Syq 
251290f05028Syq 
251390f05028Syq /*
251490f05028Syq  * usbsacm_send_data:
251590f05028Syq  *	data transfer
251690f05028Syq  */
251790f05028Syq static int
usbsacm_send_data(usbsacm_port_t * acm_port,mblk_t * data)251890f05028Syq usbsacm_send_data(usbsacm_port_t *acm_port, mblk_t *data)
251990f05028Syq {
252090f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
252190f05028Syq 	usb_bulk_req_t	*br;
252290f05028Syq 	int		rval;
252390f05028Syq 	int		data_len = MBLKL(data);
252490f05028Syq 
252590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh,
252690f05028Syq 	    "usbsacm_send_data: data address is 0x%p, length = %d",
2527112116d8Sfb 	    (void *)data, data_len);
252890f05028Syq 
252990f05028Syq 	br = usb_alloc_bulk_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP);
253090f05028Syq 	if (br == NULL) {
253190f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
253290f05028Syq 		    "usbsacm_send_data: alloc req failed.");
253390f05028Syq 
253490f05028Syq 		return (USB_FAILURE);
253590f05028Syq 	}
253690f05028Syq 
253790f05028Syq 	/* initialize the bulk out request */
253890f05028Syq 	br->bulk_data = data;
253990f05028Syq 	br->bulk_len = data_len;
254090f05028Syq 	br->bulk_timeout = USBSACM_BULKOUT_TIMEOUT;
254190f05028Syq 	br->bulk_cb = usbsacm_bulkout_cb;
254290f05028Syq 	br->bulk_exc_cb = usbsacm_bulkout_cb;
254390f05028Syq 	br->bulk_client_private = (usb_opaque_t)acm_port;
254490f05028Syq 	br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
254590f05028Syq 
254690f05028Syq 	rval = usb_pipe_bulk_xfer(acm_port->acm_bulkout_ph, br, 0);
254790f05028Syq 
254890f05028Syq 	if (rval != USB_SUCCESS) {
254990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh,
255090f05028Syq 		    "usbsacm_send_data: Send Data failed.");
255190f05028Syq 
255290f05028Syq 		/*
255390f05028Syq 		 * Don't free it in usb_free_bulk_req because it will
255490f05028Syq 		 * be linked in usbsacm_put_head
255590f05028Syq 		 */
255690f05028Syq 		br->bulk_data = NULL;
255790f05028Syq 
255890f05028Syq 		usb_free_bulk_req(br);
255990f05028Syq 	}
256090f05028Syq 
256190f05028Syq 	return (rval);
256290f05028Syq }
256390f05028Syq 
256490f05028Syq /*
256590f05028Syq  * usbsacm_wait_tx_drain:
256690f05028Syq  *	wait until local tx buffer drains.
256790f05028Syq  *	'timeout' is in seconds, zero means wait forever
256890f05028Syq  */
256990f05028Syq static int
usbsacm_wait_tx_drain(usbsacm_port_t * acm_port,int timeout)257090f05028Syq usbsacm_wait_tx_drain(usbsacm_port_t *acm_port, int timeout)
257190f05028Syq {
257290f05028Syq 	clock_t		until;
257390f05028Syq 	int		over = 0;
257490f05028Syq 
257590f05028Syq 	until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout);
257690f05028Syq 
257790f05028Syq 	while (acm_port->acm_tx_mp && !over) {
257890f05028Syq 		if (timeout > 0) {
257990f05028Syq 			over = (cv_timedwait_sig(&acm_port->acm_tx_cv,
258090f05028Syq 			    &acm_port->acm_port_mutex, until) <= 0);
258190f05028Syq 		} else {
258290f05028Syq 			over = (cv_wait_sig(&acm_port->acm_tx_cv,
258390f05028Syq 			    &acm_port->acm_port_mutex) == 0);
258490f05028Syq 		}
258590f05028Syq 	}
258690f05028Syq 
258790f05028Syq 	return ((acm_port->acm_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE);
258890f05028Syq }
258990f05028Syq 
259090f05028Syq 
259190f05028Syq /*
259290f05028Syq  * usbsacm_req_write:
259390f05028Syq  *	send command over control pipe
259490f05028Syq  */
259590f05028Syq static int
usbsacm_req_write(usbsacm_port_t * acm_port,uchar_t request,uint16_t value,mblk_t ** data)259690f05028Syq usbsacm_req_write(usbsacm_port_t *acm_port, uchar_t request, uint16_t value,
259790f05028Syq     mblk_t **data)
259890f05028Syq {
259990f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
260090f05028Syq 	usb_ctrl_setup_t setup;
260190f05028Syq 	usb_cb_flags_t	cb_flags;
260290f05028Syq 	usb_cr_t	cr;
260390f05028Syq 
260490f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
260590f05028Syq 	    "usbsacm_req_write: ");
260690f05028Syq 
260790f05028Syq 	/* initialize the control request. */
260890f05028Syq 	setup.bmRequestType = USBSACM_REQ_WRITE_IF;
260990f05028Syq 	setup.bRequest = request;
261090f05028Syq 	setup.wValue = value;
261190f05028Syq 	setup.wIndex = acm_port->acm_ctrl_if_no;
261290f05028Syq 	setup.wLength = ((data != NULL) && (*data != NULL)) ? MBLKL(*data) : 0;
261390f05028Syq 	setup.attrs = 0;
261490f05028Syq 
261590f05028Syq 	return (usb_pipe_ctrl_xfer_wait(acmp->acm_def_ph, &setup, data,
261690f05028Syq 	    &cr, &cb_flags, 0));
261790f05028Syq }
261890f05028Syq 
261990f05028Syq 
262090f05028Syq /*
262190f05028Syq  * usbsacm_set_line_coding:
262290f05028Syq  *	Send USB_CDC_REQ_SET_LINE_CODING request
262390f05028Syq  */
262490f05028Syq static int
usbsacm_set_line_coding(usbsacm_port_t * acm_port,usb_cdc_line_coding_t * lc)262590f05028Syq usbsacm_set_line_coding(usbsacm_port_t *acm_port, usb_cdc_line_coding_t *lc)
262690f05028Syq {
262790f05028Syq 	mblk_t		*bp;
262890f05028Syq 	int		ret;
262990f05028Syq 
263090f05028Syq 	/* allocate mblk and copy supplied structure into it */
263190f05028Syq 	if ((bp = allocb(USB_CDC_LINE_CODING_LEN, BPRI_HI)) == NULL) {
263290f05028Syq 
263390f05028Syq 		return (USB_NO_RESOURCES);
263490f05028Syq 	}
263590f05028Syq 
263690f05028Syq #ifndef __lock_lint /* warlock gets confused here */
2637d29f5a71Szhigang lu - Sun Microsystems - Beijing China 	/* LINTED E_BAD_PTR_CAST_ALIGN */
263890f05028Syq 	*((usb_cdc_line_coding_t *)bp->b_wptr) = *lc;
263990f05028Syq 	bp->b_wptr += USB_CDC_LINE_CODING_LEN;
264090f05028Syq #endif
264190f05028Syq 
264290f05028Syq 	ret = usbsacm_req_write(acm_port, USB_CDC_REQ_SET_LINE_CODING, 0, &bp);
264390f05028Syq 
264490f05028Syq 	if (bp != NULL) {
264590f05028Syq 		freeb(bp);
264690f05028Syq 	}
264790f05028Syq 
264890f05028Syq 	return (ret);
264990f05028Syq }
265090f05028Syq 
265190f05028Syq 
265290f05028Syq 
265390f05028Syq /*
265490f05028Syq  * usbsacm_mctl2reg:
265590f05028Syq  *	Set Modem control status
265690f05028Syq  */
265790f05028Syq static void
usbsacm_mctl2reg(int mask,int val,uint8_t * line_ctl)265890f05028Syq usbsacm_mctl2reg(int mask, int val, uint8_t *line_ctl)
265990f05028Syq {
266090f05028Syq 	if (mask & TIOCM_RTS) {
266190f05028Syq 		if (val & TIOCM_RTS) {
266290f05028Syq 			*line_ctl |= USB_CDC_ACM_CONTROL_RTS;
266390f05028Syq 		} else {
266490f05028Syq 			*line_ctl &= ~USB_CDC_ACM_CONTROL_RTS;
266590f05028Syq 		}
266690f05028Syq 	}
266790f05028Syq 	if (mask & TIOCM_DTR) {
266890f05028Syq 		if (val & TIOCM_DTR) {
266990f05028Syq 			*line_ctl |= USB_CDC_ACM_CONTROL_DTR;
267090f05028Syq 		} else {
267190f05028Syq 			*line_ctl &= ~USB_CDC_ACM_CONTROL_DTR;
267290f05028Syq 		}
267390f05028Syq 	}
267490f05028Syq }
267590f05028Syq 
267690f05028Syq 
267790f05028Syq /*
267890f05028Syq  * usbsacm_reg2mctl:
267990f05028Syq  *	Get Modem control status
268090f05028Syq  */
268190f05028Syq static int
usbsacm_reg2mctl(uint8_t line_ctl)268290f05028Syq usbsacm_reg2mctl(uint8_t line_ctl)
268390f05028Syq {
268490f05028Syq 	int	val = 0;
268590f05028Syq 
268690f05028Syq 	if (line_ctl & USB_CDC_ACM_CONTROL_RTS) {
268790f05028Syq 		val |= TIOCM_RTS;
268890f05028Syq 	}
268990f05028Syq 	if (line_ctl & USB_CDC_ACM_CONTROL_DTR) {
269090f05028Syq 		val |= TIOCM_DTR;
269190f05028Syq 	}
269290f05028Syq 	if (line_ctl & USB_CDC_ACM_CONTROL_DSR) {
269390f05028Syq 		val |= TIOCM_DSR;
269490f05028Syq 	}
269590f05028Syq 	if (line_ctl & USB_CDC_ACM_CONTROL_RNG) {
269690f05028Syq 		val |= TIOCM_RI;
269790f05028Syq 	}
269890f05028Syq 
269990f05028Syq 	return (val);
270090f05028Syq }
270190f05028Syq 
270290f05028Syq 
270390f05028Syq /*
270490f05028Syq  * misc routines
270590f05028Syq  * -------------
270690f05028Syq  *
270790f05028Syq  */
270890f05028Syq 
270990f05028Syq /*
271090f05028Syq  * usbsacm_put_tail:
271190f05028Syq  *	link a message block to tail of message
271290f05028Syq  *	account for the case when message is null
271390f05028Syq  */
271490f05028Syq static void
usbsacm_put_tail(mblk_t ** mpp,mblk_t * bp)271590f05028Syq usbsacm_put_tail(mblk_t **mpp, mblk_t *bp)
271690f05028Syq {
271790f05028Syq 	if (*mpp) {
271890f05028Syq 		linkb(*mpp, bp);
271990f05028Syq 	} else {
272090f05028Syq 		*mpp = bp;
272190f05028Syq 	}
272290f05028Syq }
272390f05028Syq 
272490f05028Syq 
272590f05028Syq /*
272690f05028Syq  * usbsacm_put_head:
272790f05028Syq  *	put a message block at the head of the message
272890f05028Syq  *	account for the case when message is null
272990f05028Syq  */
273090f05028Syq static void
usbsacm_put_head(mblk_t ** mpp,mblk_t * bp)273190f05028Syq usbsacm_put_head(mblk_t **mpp, mblk_t *bp)
273290f05028Syq {
273390f05028Syq 	if (*mpp) {
273490f05028Syq 		linkb(bp, *mpp);
273590f05028Syq 	}
273690f05028Syq 	*mpp = bp;
273790f05028Syq }
273890f05028Syq 
273990f05028Syq 
274090f05028Syq /*
274190f05028Syq  * power management
274290f05028Syq  * ----------------
274390f05028Syq  *
274490f05028Syq  * usbsacm_create_pm_components:
274590f05028Syq  *	create PM components
274690f05028Syq  */
274790f05028Syq static int
usbsacm_create_pm_components(usbsacm_state_t * acmp)274890f05028Syq usbsacm_create_pm_components(usbsacm_state_t *acmp)
274990f05028Syq {
275090f05028Syq 	dev_info_t	*dip = acmp->acm_dip;
275190f05028Syq 	usbsacm_pm_t	*pm;
275290f05028Syq 	uint_t		pwr_states;
275390f05028Syq 	usb_dev_descr_t *dev_descr;
275490f05028Syq 
275590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
275690f05028Syq 	    "usbsacm_create_pm_components: ");
275790f05028Syq 
275890f05028Syq 	if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
275990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
276090f05028Syq 		    "usbsacm_create_pm_components: failed");
276190f05028Syq 
276290f05028Syq 		return (USB_SUCCESS);
276390f05028Syq 	}
276490f05028Syq 
276590f05028Syq 	pm = acmp->acm_pm =
276690f05028Syq 	    (usbsacm_pm_t *)kmem_zalloc(sizeof (usbsacm_pm_t), KM_SLEEP);
276790f05028Syq 
276890f05028Syq 	pm->pm_pwr_states = (uint8_t)pwr_states;
276990f05028Syq 	pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
277090f05028Syq 	/*
277190f05028Syq 	 * Qualcomm CDMA card won't response the following control commands
277290f05028Syq 	 * after receive USB_REMOTE_WAKEUP_ENABLE. So we just set
277390f05028Syq 	 * pm_wakeup_enable to 0 for this specific device.
277490f05028Syq 	 */
277590f05028Syq 	dev_descr = acmp->acm_dev_data->dev_descr;
277690f05028Syq 	if (dev_descr->idVendor == 0x5c6 && dev_descr->idProduct == 0x3100) {
277790f05028Syq 		pm->pm_wakeup_enabled = 0;
277890f05028Syq 	} else {
277990f05028Syq 		pm->pm_wakeup_enabled = (usb_handle_remote_wakeup(dip,
278090f05028Syq 		    USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS);
278190f05028Syq 	}
278290f05028Syq 
278390f05028Syq 	(void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
278490f05028Syq 
278590f05028Syq 	return (USB_SUCCESS);
278690f05028Syq }
278790f05028Syq 
278890f05028Syq 
278990f05028Syq /*
279090f05028Syq  * usbsacm_destroy_pm_components:
279190f05028Syq  *	destroy PM components
279290f05028Syq  */
279390f05028Syq static void
usbsacm_destroy_pm_components(usbsacm_state_t * acmp)279490f05028Syq usbsacm_destroy_pm_components(usbsacm_state_t *acmp)
279590f05028Syq {
279690f05028Syq 	usbsacm_pm_t	*pm = acmp->acm_pm;
279790f05028Syq 	dev_info_t	*dip = acmp->acm_dip;
279890f05028Syq 	int		rval;
279990f05028Syq 
280090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh,
280190f05028Syq 	    "usbsacm_destroy_pm_components: ");
280290f05028Syq 
280390f05028Syq 	if (acmp->acm_dev_state != USB_DEV_DISCONNECTED) {
280490f05028Syq 		if (pm->pm_wakeup_enabled) {
280590f05028Syq 			rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
280690f05028Syq 			if (rval != DDI_SUCCESS) {
280790f05028Syq 				USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
280890f05028Syq 				    "usbsacm_destroy_pm_components: "
280990f05028Syq 				    "raising power failed (%d)", rval);
281090f05028Syq 			}
281190f05028Syq 
281290f05028Syq 			rval = usb_handle_remote_wakeup(dip,
281390f05028Syq 			    USB_REMOTE_WAKEUP_DISABLE);
281490f05028Syq 			if (rval != USB_SUCCESS) {
281590f05028Syq 				USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
281690f05028Syq 				    "usbsacm_destroy_pm_components: "
281790f05028Syq 				    "disable remote wakeup failed (%d)", rval);
281890f05028Syq 			}
281990f05028Syq 		}
282090f05028Syq 
282190f05028Syq 		(void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
282290f05028Syq 	}
282390f05028Syq 	kmem_free((caddr_t)pm, sizeof (usbsacm_pm_t));
282490f05028Syq 	acmp->acm_pm = NULL;
282590f05028Syq }
282690f05028Syq 
282790f05028Syq 
282890f05028Syq /*
282990f05028Syq  * usbsacm_pm_set_busy:
283090f05028Syq  *	mark device busy and raise power
283190f05028Syq  */
283290f05028Syq static void
usbsacm_pm_set_busy(usbsacm_state_t * acmp)283390f05028Syq usbsacm_pm_set_busy(usbsacm_state_t *acmp)
283490f05028Syq {
283590f05028Syq 	usbsacm_pm_t	*pm = acmp->acm_pm;
283690f05028Syq 	dev_info_t	*dip = acmp->acm_dip;
283790f05028Syq 	int		rval;
283890f05028Syq 
283990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
2840112116d8Sfb 	    "usbsacm_pm_set_busy: pm = 0x%p", (void *)pm);
284190f05028Syq 
284290f05028Syq 	if (pm == NULL) {
284390f05028Syq 
284490f05028Syq 		return;
284590f05028Syq 	}
284690f05028Syq 
284790f05028Syq 	mutex_enter(&acmp->acm_mutex);
284890f05028Syq 	/* if already marked busy, just increment the counter */
284990f05028Syq 	if (pm->pm_busy_cnt++ > 0) {
285090f05028Syq 		mutex_exit(&acmp->acm_mutex);
285190f05028Syq 
285290f05028Syq 		return;
285390f05028Syq 	}
285490f05028Syq 
285590f05028Syq 	(void) pm_busy_component(dip, 0);
285690f05028Syq 
285790f05028Syq 	if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) {
285890f05028Syq 		mutex_exit(&acmp->acm_mutex);
285990f05028Syq 
286090f05028Syq 		return;
286190f05028Syq 	}
286290f05028Syq 
286390f05028Syq 	/* need to raise power	*/
286490f05028Syq 	pm->pm_raise_power = B_TRUE;
286590f05028Syq 	mutex_exit(&acmp->acm_mutex);
286690f05028Syq 
286790f05028Syq 	rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
286890f05028Syq 	if (rval != DDI_SUCCESS) {
286990f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
287090f05028Syq 		    "usbsacm_pm_set_busy: raising power failed");
287190f05028Syq 	}
287290f05028Syq 
287390f05028Syq 	mutex_enter(&acmp->acm_mutex);
287490f05028Syq 	pm->pm_raise_power = B_FALSE;
287590f05028Syq 	mutex_exit(&acmp->acm_mutex);
287690f05028Syq }
287790f05028Syq 
287890f05028Syq 
287990f05028Syq /*
288090f05028Syq  * usbsacm_pm_set_idle:
288190f05028Syq  *	mark device idle
288290f05028Syq  */
288390f05028Syq static void
usbsacm_pm_set_idle(usbsacm_state_t * acmp)288490f05028Syq usbsacm_pm_set_idle(usbsacm_state_t *acmp)
288590f05028Syq {
288690f05028Syq 	usbsacm_pm_t	*pm = acmp->acm_pm;
288790f05028Syq 	dev_info_t	*dip = acmp->acm_dip;
288890f05028Syq 
288990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
289090f05028Syq 	    "usbsacm_pm_set_idle: ");
289190f05028Syq 
289290f05028Syq 	if (pm == NULL) {
289390f05028Syq 
289490f05028Syq 		return;
289590f05028Syq 	}
289690f05028Syq 
289790f05028Syq 	/*
289890f05028Syq 	 * if more ports use the device, do not mark as yet
289990f05028Syq 	 */
290090f05028Syq 	mutex_enter(&acmp->acm_mutex);
290190f05028Syq 	if (--pm->pm_busy_cnt > 0) {
290290f05028Syq 		mutex_exit(&acmp->acm_mutex);
290390f05028Syq 
290490f05028Syq 		return;
290590f05028Syq 	}
290690f05028Syq 
290790f05028Syq 	if (pm) {
290890f05028Syq 		(void) pm_idle_component(dip, 0);
290990f05028Syq 	}
291090f05028Syq 	mutex_exit(&acmp->acm_mutex);
291190f05028Syq }
291290f05028Syq 
291390f05028Syq 
291490f05028Syq /*
291590f05028Syq  * usbsacm_pwrlvl0:
291690f05028Syq  *	Functions to handle power transition for OS levels 0 -> 3
291790f05028Syq  *	The same level as OS state, different from USB state
291890f05028Syq  */
291990f05028Syq static int
usbsacm_pwrlvl0(usbsacm_state_t * acmp)292090f05028Syq usbsacm_pwrlvl0(usbsacm_state_t *acmp)
292190f05028Syq {
292290f05028Syq 	int		rval;
292390f05028Syq 	int		i;
292490f05028Syq 	usbsacm_port_t	*cur_port = acmp->acm_ports;
292590f05028Syq 
292690f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
292790f05028Syq 	    "usbsacm_pwrlvl0: ");
292890f05028Syq 
292990f05028Syq 	switch (acmp->acm_dev_state) {
293090f05028Syq 	case USB_DEV_ONLINE:
293190f05028Syq 		/* issue USB D3 command to the device */
293290f05028Syq 		rval = usb_set_device_pwrlvl3(acmp->acm_dip);
293390f05028Syq 		ASSERT(rval == USB_SUCCESS);
293490f05028Syq 
293590f05028Syq 		if (cur_port != NULL) {
29364ee52f77Slg 			for (i = 0; i < acmp->acm_port_cnt; i++) {
29374ee52f77Slg 				cur_port = &acmp->acm_ports[i];
29384ee52f77Slg 				if (cur_port->acm_intr_ph != NULL &&
29394ee52f77Slg 				    cur_port->acm_port_state !=
29404ee52f77Slg 				    USBSACM_PORT_CLOSED) {
29414ee52f77Slg 
29424ee52f77Slg 					mutex_exit(&acmp->acm_mutex);
29434ee52f77Slg 					usb_pipe_stop_intr_polling(
29444ee52f77Slg 					    cur_port->acm_intr_ph,
29454ee52f77Slg 					    USB_FLAGS_SLEEP);
29464ee52f77Slg 					mutex_enter(&acmp->acm_mutex);
29474ee52f77Slg 
29484ee52f77Slg 					mutex_enter(&cur_port->acm_port_mutex);
29494ee52f77Slg 					cur_port->acm_intr_state =
29504ee52f77Slg 					    USBSACM_PIPE_IDLE;
29514ee52f77Slg 					mutex_exit(&cur_port->acm_port_mutex);
29524ee52f77Slg 				}
295390f05028Syq 			}
295490f05028Syq 		}
295590f05028Syq 
295690f05028Syq 		acmp->acm_dev_state = USB_DEV_PWRED_DOWN;
295790f05028Syq 		acmp->acm_pm->pm_cur_power = USB_DEV_OS_PWR_OFF;
295890f05028Syq 
295990f05028Syq 		/* FALLTHRU */
296090f05028Syq 	case USB_DEV_DISCONNECTED:
296190f05028Syq 	case USB_DEV_SUSPENDED:
296290f05028Syq 		/* allow a disconnect/cpr'ed device to go to lower power */
296390f05028Syq 
296490f05028Syq 		return (USB_SUCCESS);
296590f05028Syq 	case USB_DEV_PWRED_DOWN:
296690f05028Syq 	default:
296790f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
296890f05028Syq 		    "usbsacm_pwrlvl0: illegal device state");
296990f05028Syq 
297090f05028Syq 		return (USB_FAILURE);
297190f05028Syq 	}
297290f05028Syq }
297390f05028Syq 
297490f05028Syq 
297590f05028Syq /*
297690f05028Syq  * usbsacm_pwrlvl1:
297790f05028Syq  *	Functions to handle power transition for OS levels 1 -> 2
297890f05028Syq  */
297990f05028Syq static int
usbsacm_pwrlvl1(usbsacm_state_t * acmp)298090f05028Syq usbsacm_pwrlvl1(usbsacm_state_t *acmp)
298190f05028Syq {
298290f05028Syq 	/* issue USB D2 command to the device */
298390f05028Syq 	(void) usb_set_device_pwrlvl2(acmp->acm_dip);
298490f05028Syq 
298590f05028Syq 	return (USB_FAILURE);
298690f05028Syq }
298790f05028Syq 
298890f05028Syq 
298990f05028Syq /*
299090f05028Syq  * usbsacm_pwrlvl2:
299190f05028Syq  *	Functions to handle power transition for OS levels 2 -> 1
299290f05028Syq  */
299390f05028Syq static int
usbsacm_pwrlvl2(usbsacm_state_t * acmp)299490f05028Syq usbsacm_pwrlvl2(usbsacm_state_t *acmp)
299590f05028Syq {
299690f05028Syq 	/* issue USB D1 command to the device */
299790f05028Syq 	(void) usb_set_device_pwrlvl1(acmp->acm_dip);
299890f05028Syq 
299990f05028Syq 	return (USB_FAILURE);
300090f05028Syq }
300190f05028Syq 
300290f05028Syq 
300390f05028Syq /*
300490f05028Syq  * usbsacm_pwrlvl3:
300590f05028Syq  *	Functions to handle power transition for OS levels 3 -> 0
300690f05028Syq  *	The same level as OS state, different from USB state
300790f05028Syq  */
300890f05028Syq static int
usbsacm_pwrlvl3(usbsacm_state_t * acmp)300990f05028Syq usbsacm_pwrlvl3(usbsacm_state_t *acmp)
301090f05028Syq {
301190f05028Syq 	int		rval;
301290f05028Syq 	int		i;
301390f05028Syq 	usbsacm_port_t	*cur_port = acmp->acm_ports;
301490f05028Syq 
301590f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh,
301690f05028Syq 	    "usbsacm_pwrlvl3: ");
301790f05028Syq 
301890f05028Syq 	switch (acmp->acm_dev_state) {
301990f05028Syq 	case USB_DEV_PWRED_DOWN:
302090f05028Syq 		/* Issue USB D0 command to the device here */
302190f05028Syq 		rval = usb_set_device_pwrlvl0(acmp->acm_dip);
302290f05028Syq 		ASSERT(rval == USB_SUCCESS);
302390f05028Syq 
302490f05028Syq 		if (cur_port != NULL) {
30254ee52f77Slg 			for (i = 0; i < acmp->acm_port_cnt; i++) {
30264ee52f77Slg 				cur_port = &acmp->acm_ports[i];
30274ee52f77Slg 				if (cur_port->acm_intr_ph != NULL &&
30284ee52f77Slg 				    cur_port->acm_port_state !=
30294ee52f77Slg 				    USBSACM_PORT_CLOSED) {
30304ee52f77Slg 
30314ee52f77Slg 					mutex_exit(&acmp->acm_mutex);
30324ee52f77Slg 					usbsacm_pipe_start_polling(cur_port);
30334ee52f77Slg 					mutex_enter(&acmp->acm_mutex);
30344ee52f77Slg 				}
303590f05028Syq 			}
303690f05028Syq 		}
303790f05028Syq 
303890f05028Syq 		acmp->acm_dev_state = USB_DEV_ONLINE;
303990f05028Syq 		acmp->acm_pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
304090f05028Syq 
304190f05028Syq 		/* FALLTHRU */
304290f05028Syq 	case USB_DEV_ONLINE:
304390f05028Syq 		/* we are already in full power */
304490f05028Syq 
304590f05028Syq 		/* FALLTHRU */
304690f05028Syq 	case USB_DEV_DISCONNECTED:
304790f05028Syq 	case USB_DEV_SUSPENDED:
304890f05028Syq 
304990f05028Syq 		return (USB_SUCCESS);
305090f05028Syq 	default:
305190f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh,
305290f05028Syq 		    "usbsacm_pwrlvl3: illegal device state");
305390f05028Syq 
305490f05028Syq 		return (USB_FAILURE);
305590f05028Syq 	}
305690f05028Syq }
305790f05028Syq 
305890f05028Syq 
305990f05028Syq /*
306090f05028Syq  * usbsacm_pipe_start_polling:
306190f05028Syq  *	start polling on the interrupt pipe
306290f05028Syq  */
306390f05028Syq static void
usbsacm_pipe_start_polling(usbsacm_port_t * acm_port)306490f05028Syq usbsacm_pipe_start_polling(usbsacm_port_t *acm_port)
306590f05028Syq {
306690f05028Syq 	usb_intr_req_t	*intr;
306790f05028Syq 	int		rval;
306890f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
306990f05028Syq 
307090f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh,
307190f05028Syq 	    "usbsacm_pipe_start_polling: ");
307290f05028Syq 
307390f05028Syq 	if (acm_port->acm_intr_ph == NULL) {
307490f05028Syq 
307590f05028Syq 		return;
307690f05028Syq 	}
307790f05028Syq 
307890f05028Syq 	intr = usb_alloc_intr_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP);
307990f05028Syq 
308090f05028Syq 	/*
308190f05028Syq 	 * If it is in interrupt context, usb_alloc_intr_req will return NULL if
308290f05028Syq 	 * called with SLEEP flag.
308390f05028Syq 	 */
308490f05028Syq 	if (!intr) {
308590f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh,
308690f05028Syq 		    "usbsacm_pipe_start_polling: alloc req failed.");
308790f05028Syq 
308890f05028Syq 		return;
308990f05028Syq 	}
309090f05028Syq 
309190f05028Syq 	/* initialize the interrupt request. */
309290f05028Syq 	intr->intr_attributes = USB_ATTRS_SHORT_XFER_OK |
30934ee52f77Slg 	    USB_ATTRS_AUTOCLEARING;
309490f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
309590f05028Syq 	intr->intr_len = acm_port->acm_intr_ep_descr.wMaxPacketSize;
309690f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
309790f05028Syq 	intr->intr_client_private = (usb_opaque_t)acm_port;
309890f05028Syq 	intr->intr_cb = usbsacm_intr_cb;
309990f05028Syq 	intr->intr_exc_cb = usbsacm_intr_ex_cb;
310090f05028Syq 
310190f05028Syq 	rval = usb_pipe_intr_xfer(acm_port->acm_intr_ph, intr, USB_FLAGS_SLEEP);
310290f05028Syq 
310390f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
310490f05028Syq 	if (rval == USB_SUCCESS) {
310590f05028Syq 		acm_port->acm_intr_state = USBSACM_PIPE_BUSY;
310690f05028Syq 	} else {
310790f05028Syq 		usb_free_intr_req(intr);
310890f05028Syq 		acm_port->acm_intr_state = USBSACM_PIPE_IDLE;
310990f05028Syq 		USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh,
311090f05028Syq 		    "usbsacm_pipe_start_polling: failed (%d)", rval);
311190f05028Syq 	}
311290f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
311390f05028Syq }
311490f05028Syq 
311590f05028Syq 
311690f05028Syq /*
311790f05028Syq  * usbsacm_intr_cb:
311890f05028Syq  *	interrupt pipe normal callback
311990f05028Syq  */
312090f05028Syq /*ARGSUSED*/
312190f05028Syq static void
usbsacm_intr_cb(usb_pipe_handle_t ph,usb_intr_req_t * req)312290f05028Syq usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
312390f05028Syq {
312490f05028Syq 	usbsacm_port_t	*acm_port = (usbsacm_port_t *)req->intr_client_private;
312590f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
312690f05028Syq 	mblk_t		*data = req->intr_data;
312790f05028Syq 	int		data_len;
312890f05028Syq 
312990f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh,
313090f05028Syq 	    "usbsacm_intr_cb: ");
313190f05028Syq 
313290f05028Syq 	data_len = (data) ? MBLKL(data) : 0;
313390f05028Syq 
313490f05028Syq 	/* check data length */
313590f05028Syq 	if (data_len < 8) {
313690f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh,
313790f05028Syq 		    "usbsacm_intr_cb: %d packet too short", data_len);
313890f05028Syq 		usb_free_intr_req(req);
313990f05028Syq 
314090f05028Syq 		return;
314190f05028Syq 	}
314290f05028Syq 	req->intr_data = NULL;
314390f05028Syq 	usb_free_intr_req(req);
314490f05028Syq 
314590f05028Syq 	mutex_enter(&acm_port->acm_port_mutex);
314690f05028Syq 	/* parse interrupt data. */
314790f05028Syq 	usbsacm_parse_intr_data(acm_port, data);
314890f05028Syq 	mutex_exit(&acm_port->acm_port_mutex);
314990f05028Syq }
315090f05028Syq 
315190f05028Syq 
315290f05028Syq /*
315390f05028Syq  * usbsacm_intr_ex_cb:
315490f05028Syq  *	interrupt pipe exception callback
315590f05028Syq  */
315690f05028Syq /*ARGSUSED*/
315790f05028Syq static void
usbsacm_intr_ex_cb(usb_pipe_handle_t ph,usb_intr_req_t * req)315890f05028Syq usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req)
315990f05028Syq {
316090f05028Syq 	usbsacm_port_t	*acm_port = (usbsacm_port_t *)req->intr_client_private;
316190f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
316290f05028Syq 	usb_cr_t	cr = req->intr_completion_reason;
316390f05028Syq 
316490f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh,
316590f05028Syq 	    "usbsacm_intr_ex_cb: ");
316690f05028Syq 
316790f05028Syq 	usb_free_intr_req(req);
316890f05028Syq 
316990f05028Syq 	/*
317090f05028Syq 	 * If completion reason isn't USB_CR_PIPE_CLOSING and
317190f05028Syq 	 * USB_CR_STOPPED_POLLING, restart polling.
317290f05028Syq 	 */
317390f05028Syq 	if ((cr != USB_CR_PIPE_CLOSING) && (cr != USB_CR_STOPPED_POLLING)) {
317490f05028Syq 		mutex_enter(&acmp->acm_mutex);
317590f05028Syq 
317690f05028Syq 		if (acmp->acm_dev_state != USB_DEV_ONLINE) {
317790f05028Syq 
317890f05028Syq 			USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
317990f05028Syq 			    "usbsacm_intr_ex_cb: state = %d",
318090f05028Syq 			    acmp->acm_dev_state);
318190f05028Syq 
318290f05028Syq 			mutex_exit(&acmp->acm_mutex);
318390f05028Syq 
318490f05028Syq 			return;
318590f05028Syq 		}
318690f05028Syq 		mutex_exit(&acmp->acm_mutex);
318790f05028Syq 
318890f05028Syq 		usbsacm_pipe_start_polling(acm_port);
318990f05028Syq 	}
319090f05028Syq }
319190f05028Syq 
319290f05028Syq 
319390f05028Syq /*
319490f05028Syq  * usbsacm_parse_intr_data:
319590f05028Syq  *	Parse data received from interrupt callback
319690f05028Syq  */
319790f05028Syq static void
usbsacm_parse_intr_data(usbsacm_port_t * acm_port,mblk_t * data)319890f05028Syq usbsacm_parse_intr_data(usbsacm_port_t *acm_port, mblk_t *data)
319990f05028Syq {
320090f05028Syq 	usbsacm_state_t	*acmp = acm_port->acm_device;
320190f05028Syq 	uint8_t		bmRequestType;
320290f05028Syq 	uint8_t		bNotification;
320390f05028Syq 	uint16_t	wValue;
320490f05028Syq 	uint16_t	wLength;
320590f05028Syq 	uint16_t	wData;
320690f05028Syq 
320790f05028Syq 	USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh,
320890f05028Syq 	    "usbsacm_parse_intr_data: ");
320990f05028Syq 
321090f05028Syq 	bmRequestType = data->b_rptr[0];
321190f05028Syq 	bNotification = data->b_rptr[1];
321290f05028Syq 	/*
321390f05028Syq 	 * If Notification type is NETWORK_CONNECTION, wValue is 0 or 1,
321490f05028Syq 	 * mLength is 0. If Notification type is SERIAL_TYPE, mValue is 0,
321590f05028Syq 	 * mLength is 2. So we directly get the value from the byte.
321690f05028Syq 	 */
321790f05028Syq 	wValue = data->b_rptr[2];
321890f05028Syq 	wLength = data->b_rptr[6];
321990f05028Syq 
322090f05028Syq 	if (bmRequestType != USB_CDC_NOTIFICATION_REQUEST_TYPE) {
322190f05028Syq 		USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh,
322290f05028Syq 		    "usbsacm_parse_intr_data: unknown request type - 0x%x",
322390f05028Syq 		    bmRequestType);
322490f05028Syq 
32259e37f2b5SRaymond Chen 		freemsg(data);
32269e37f2b5SRaymond Chen 
322790f05028Syq 		return;
322890f05028Syq 	}
322990f05028Syq 
323090f05028Syq 	/*
323190f05028Syq 	 * Check the return value of device
323290f05028Syq 	 */
323390f05028Syq 	switch (bNotification) {
323490f05028Syq 	case USB_CDC_NOTIFICATION_NETWORK_CONNECTION:
323590f05028Syq 		USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
323690f05028Syq 		    "usbsacm_parse_intr_data: %s network!",
323790f05028Syq 		    wValue ? "connected to" :"disconnected from");
323890f05028Syq 
323990f05028Syq 		break;
324090f05028Syq 	case USB_CDC_NOTIFICATION_RESPONSE_AVAILABLE:
324190f05028Syq 		USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
324290f05028Syq 		    "usbsacm_parse_intr_data: A response is a available.");
324390f05028Syq 
324490f05028Syq 		break;
324590f05028Syq 	case USB_CDC_NOTIFICATION_SERIAL_STATE:
324690f05028Syq 		/* check the parameter's length. */
324790f05028Syq 		if (wLength != 2) {
324890f05028Syq 
324990f05028Syq 			USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
325090f05028Syq 			    "usbsacm_parse_intr_data: error data length.");
325190f05028Syq 		} else {
325290f05028Syq 			/*
325390f05028Syq 			 * The Data field is a bitmapped value that contains
325490f05028Syq 			 * the current state of carrier detect, transmission
325590f05028Syq 			 * carrier, break, ring signal and device overrun
325690f05028Syq 			 * error.
325790f05028Syq 			 */
325890f05028Syq 			wData = data->b_rptr[8];
325990f05028Syq 			/*
326090f05028Syq 			 * Check the serial state of the current port.
326190f05028Syq 			 */
326290f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_DCD) {
326390f05028Syq 
326490f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
326590f05028Syq 				    "usbsacm_parse_intr_data: "
326690f05028Syq 				    "receiver carrier is set.");
326790f05028Syq 			}
326890f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_DSR) {
326990f05028Syq 
327090f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
327190f05028Syq 				    "usbsacm_parse_intr_data: "
327290f05028Syq 				    "transmission carrier is set.");
327390f05028Syq 
327490f05028Syq 				acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_DSR;
327590f05028Syq 			}
327690f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_BREAK) {
327790f05028Syq 
327890f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
327990f05028Syq 				    "usbsacm_parse_intr_data: "
328090f05028Syq 				    "break detection mechanism is set.");
328190f05028Syq 			}
328290f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_RNG) {
328390f05028Syq 
328490f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
328590f05028Syq 				    "usbsacm_parse_intr_data: "
328690f05028Syq 				    "ring signal detection is set.");
328790f05028Syq 
328890f05028Syq 				acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_RNG;
328990f05028Syq 			}
329090f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_FRAMING) {
329190f05028Syq 
329290f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
329390f05028Syq 				    "usbsacm_parse_intr_data: "
329490f05028Syq 				    "A framing error has occurred.");
329590f05028Syq 			}
329690f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_PARITY) {
329790f05028Syq 
329890f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
329990f05028Syq 				    "usbsacm_parse_intr_data: "
330090f05028Syq 				    "A parity error has occurred.");
330190f05028Syq 			}
330290f05028Syq 			if (wData & USB_CDC_ACM_CONTROL_OVERRUN) {
330390f05028Syq 
330490f05028Syq 				USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
330590f05028Syq 				    "usbsacm_parse_intr_data: "
330690f05028Syq 				    "Received data has been discarded "
330790f05028Syq 				    "due to overrun.");
330890f05028Syq 			}
330990f05028Syq 		}
331090f05028Syq 
331190f05028Syq 		break;
331290f05028Syq 	default:
331390f05028Syq 		USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh,
331490f05028Syq 		    "usbsacm_parse_intr_data: unknown notification - 0x%x!",
331590f05028Syq 		    bNotification);
331690f05028Syq 
331790f05028Syq 		break;
331890f05028Syq 	}
331990f05028Syq 
332090f05028Syq 	freemsg(data);
332190f05028Syq }
3322