/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2019 Joyent, Inc. */ /* * USB Serial CDC ACM driver * * 1. General Concepts * ------------------- * * 1.1 Overview * ------------ * This driver supports devices that comply with the USB Communication * Device Class Abstract Control Model (USB CDC ACM) specification, * which is available at http://www.usb.org. Given the broad nature * of communication equipment, this driver supports the following * types of devices: * + Telecommunications devices: analog modems, mobile phones; * + Networking devices: cable modems; * Except the above mentioned acm devices, this driver also supports * some devices which provide modem-like function and have pairs of * bulk in/out pipes. * * There are three classes that make up the definition for communication * devices: the Communication Device Class, the Communication Interface * Class and the Data Interface Class. The Communication Device Class * is a device level definition and is used by the host to properly * identify a communication device that may present several different * types of interfaces. The Communication Interface Class defines a * general-purpose mechanism that can be used to enable all types of * communication services on the Universal Serial Bus (USB). The Data * Interface Class defines a general-purpose mechanism to enable bulk * transfer on the USB when the data does not meet the requirements * for any other class. * * 1.2 Interface Definitions * ------------------------- * Communication Class Interface is used for device management and, * optionally, call management. Device management includes the requests * that manage the operational state of a device, the device responses, * and event notifications. In Abstract Control Model, the device can * provide an internal implementation of call management over the Data * Class interface or the Communication Class interface. * * The Data Class defines a data interface as an interface with a class * type of Data Class. Data transmission on a communication device is * not restricted to interfaces using the Data Class. Rather, a data * interface is used to transmit and/or receive data that is not * defined by any other class. The data could be: * + Some form of raw data from a communication line. * + Legacy modem data. * + Data using a proprietary format. * * 1.3 Endpoint Requirements * ------------------------- * The Communication Class interface requires one endpoint, the management * element. Optionally, it can have an additional endpoint, the notification * element. The management element uses the default endpoint for all * standard and Communication Class-specific requests. The notification * element normally uses an interrupt endpoint. * * The type of endpoints belonging to a Data Class interface are restricted * to bulk, and are expected to exist in pairs of the same type (one In and * one Out). * * 1.4 ACM Function Characteristics * -------------------------------- * With Abstract Control Model, the USB device understands standard * V.25ter (AT) commands. The device contains a Datapump and micro- * controller that handles the AT commands and relay controls. The * device uses both a Data Class interface and a Communication Class. * interface. * * A Communication Class interface of type Abstract Control Model will * consist of a minimum of two pipes; one is used to implement the * management element and the other to implement a notification element. * In addition, the device can use two pipes to implement channels over * which to carry unspecified data, typically over a Data Class interface. * * 1.5 ACM Serial Emulation * ------------------------ * The Abstract Control Model can bridge the gap between legacy modem * devices and USB devices. To support certain types of legacy applications, * two problems need to be addressed. The first is supporting specific * legacy control signals and state variables which are addressed * directly by the various carrier modulation standards. To support these * requirement, additional requests and notifications have been created. * Please refer to macro, beginning with USB_CDC_REQ_* and * USB_CDC_NOTIFICATION_*. * * The second significant item which is needed to bridge the gap between * legacy modem designs and the Abstract Control Model is a means to * multiplex call control (AT commands) on the Data Class interface. * Legacy modem designs are limited by only supporting one channel for * both "AT" commands and the actual data. To allow this type of * functionality, the device must have a means to specify this limitation * to the host. * * When describing this type of device, the Communication Class interface * would still specify a Abstract Control Model, but call control would * actually occur over the Data Class interface. To describe this * particular characteristic, the Call Management Functional Descriptor * would have bit D1 of bmCapabilities set. * * 1.6 Other Bulk In/Out Devices * ----------------------------- * Some devices don't conform to USB CDC specification, but they provide * modem-like function and have pairs of bulk in/out pipes. This driver * supports this kind of device and exports term nodes by their pipes. * * 2. Implementation * ----------------- * * 2.1 Overview * ------------ * It is a device-specific driver (DSD) working with USB generic serial * driver (GSD). It implements the USB-to-serial device-specific driver * interface (DSDI) which is offered by GSD. The interface is defined * by ds_ops_t structure. * * 2.2 Port States * --------------- * For USB CDC ACM devices, this driver is attached to its interface, * and exports one port for each interface. For other modem-like devices, * this driver can dynamically find the ports in the current device, * and export one port for each pair bulk in/out pipes. Each port can * be operated independently. * * port_state: * * attach_ports * | * | * | * v * USBSACM_PORT_CLOSED * | ^ * | | * V | * open_port close_port * | ^ * | | * V | * USBSACM_PORT_OPEN * * * 2.3 Pipe States * --------------- * Each port has its own bulk in/out pipes and some ports could also have * its own interrupt pipes (traced by usbsacm_port structure), which are * opened during attach. The pipe status is as following: * * pipe_state: * * usbsacm_init_alloc_ports usbsacm_free_ports * | ^ * v | * |---->------ USBSACM_PORT_CLOSED ------>------+ * ^ | * | reconnect/resume/open_port * | | * disconnect/suspend/close_port | * | v * +------<------ USBSACM_PIPE_IDLE ------<------| * | | * V ^ * | | * +-----------------+ +-----------+ * | | * V ^ * | | * rx_start/tx_start----->------failed------->---------| * | | * | bulkin_cb/bulkout_cb * V | * | ^ * | | * +----->----- USBSACM_PIPE_BUSY ---->------+ * * * To get its status in a timely way, acm driver can get the status * of the device by polling the interrupt pipe. * */ #include #include #include #include #include #include #include #include #include #include #define USBDRV_MAJOR_VER 2 #define USBDRV_MINOR_VER 0 #include #include #include #include #include #include #include /* devops entry points */ static int usbsacm_attach(dev_info_t *, ddi_attach_cmd_t); static int usbsacm_detach(dev_info_t *, ddi_detach_cmd_t); static int usbsacm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); static int usbsacm_open(queue_t *, dev_t *, int, int, cred_t *); /* DSD operations */ static int usbsacm_ds_attach(ds_attach_info_t *); static void usbsacm_ds_detach(ds_hdl_t); static int usbsacm_ds_register_cb(ds_hdl_t, uint_t, ds_cb_t *); static void usbsacm_ds_unregister_cb(ds_hdl_t, uint_t); static int usbsacm_ds_open_port(ds_hdl_t, uint_t); static int usbsacm_ds_close_port(ds_hdl_t, uint_t); /* standard UART operations */ static int usbsacm_ds_set_port_params(ds_hdl_t, uint_t, ds_port_params_t *); static int usbsacm_ds_set_modem_ctl(ds_hdl_t, uint_t, int, int); static int usbsacm_ds_get_modem_ctl(ds_hdl_t, uint_t, int, int *); static int usbsacm_ds_break_ctl(ds_hdl_t, uint_t, int); /* data xfer */ static int usbsacm_ds_tx(ds_hdl_t, uint_t, mblk_t *); static mblk_t *usbsacm_ds_rx(ds_hdl_t, uint_t); static void usbsacm_ds_stop(ds_hdl_t, uint_t, int); static void usbsacm_ds_start(ds_hdl_t, uint_t, int); /* fifo operations */ static int usbsacm_ds_fifo_flush(ds_hdl_t, uint_t, int); static int usbsacm_ds_fifo_drain(ds_hdl_t, uint_t, int); static int usbsacm_wait_tx_drain(usbsacm_port_t *, int); static int usbsacm_fifo_flush_locked(usbsacm_state_t *, uint_t, int); /* power management and CPR */ static int usbsacm_ds_suspend(ds_hdl_t); static int usbsacm_ds_resume(ds_hdl_t); static int usbsacm_ds_disconnect(ds_hdl_t); static int usbsacm_ds_reconnect(ds_hdl_t); static int usbsacm_ds_usb_power(ds_hdl_t, int, int, int *); static int usbsacm_create_pm_components(usbsacm_state_t *); static void usbsacm_destroy_pm_components(usbsacm_state_t *); static void usbsacm_pm_set_busy(usbsacm_state_t *); static void usbsacm_pm_set_idle(usbsacm_state_t *); static int usbsacm_pwrlvl0(usbsacm_state_t *); static int usbsacm_pwrlvl1(usbsacm_state_t *); static int usbsacm_pwrlvl2(usbsacm_state_t *); static int usbsacm_pwrlvl3(usbsacm_state_t *); /* event handling */ /* pipe callbacks */ static void usbsacm_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *); static void usbsacm_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *); /* interrupt pipe */ static void usbsacm_pipe_start_polling(usbsacm_port_t *acmp); static void usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req); static void usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req); static void usbsacm_parse_intr_data(usbsacm_port_t *acmp, mblk_t *data); /* Utility functions */ /* data transfer routines */ static int usbsacm_rx_start(usbsacm_port_t *); static void usbsacm_tx_start(usbsacm_port_t *); static int usbsacm_send_data(usbsacm_port_t *, mblk_t *); /* Initialize or release resources */ static int usbsacm_init_alloc_ports(usbsacm_state_t *); static void usbsacm_free_ports(usbsacm_state_t *); static void usbsacm_cleanup(usbsacm_state_t *); /* analysis functional descriptors */ static int usbsacm_get_descriptors(usbsacm_state_t *); /* hotplug */ static int usbsacm_restore_device_state(usbsacm_state_t *); static int usbsacm_restore_port_state(usbsacm_state_t *); /* pipe operations */ static int usbsacm_open_port_pipes(usbsacm_port_t *); static void usbsacm_close_port_pipes(usbsacm_port_t *); static void usbsacm_close_pipes(usbsacm_state_t *); static void usbsacm_disconnect_pipes(usbsacm_state_t *); static int usbsacm_reconnect_pipes(usbsacm_state_t *); /* vendor-specific commands */ static int usbsacm_req_write(usbsacm_port_t *, uchar_t, uint16_t, mblk_t **); static int usbsacm_set_line_coding(usbsacm_port_t *, usb_cdc_line_coding_t *); static void usbsacm_mctl2reg(int mask, int val, uint8_t *); static int usbsacm_reg2mctl(uint8_t); /* misc */ static void usbsacm_put_tail(mblk_t **, mblk_t *); static void usbsacm_put_head(mblk_t **, mblk_t *); /* * Standard STREAMS driver definitions */ struct module_info usbsacm_modinfo = { 0, /* module id */ "usbsacm", /* module name */ USBSER_MIN_PKTSZ, /* min pkt size */ USBSER_MAX_PKTSZ, /* max pkt size */ USBSER_HIWAT, /* hi watermark */ USBSER_LOWAT /* low watermark */ }; static struct qinit usbsacm_rinit = { NULL, usbser_rsrv, usbsacm_open, usbser_close, NULL, &usbsacm_modinfo, NULL }; static struct qinit usbsacm_winit = { usbser_wput, usbser_wsrv, NULL, NULL, NULL, &usbsacm_modinfo, NULL }; struct streamtab usbsacm_str_info = { &usbsacm_rinit, &usbsacm_winit, NULL, NULL }; /* cb_ops structure */ static struct cb_ops usbsacm_cb_ops = { nodev, /* cb_open */ nodev, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ nodev, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ &usbsacm_str_info, /* cb_stream */ (int)(D_64BIT | D_NEW | D_MP | D_HOTPLUG) /* cb_flag */ }; /* dev_ops structure */ struct dev_ops usbsacm_ops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ usbsacm_getinfo, /* devo_getinfo */ nulldev, /* devo_identify */ nulldev, /* devo_probe */ usbsacm_attach, /* devo_attach */ usbsacm_detach, /* devo_detach */ nodev, /* devo_reset */ &usbsacm_cb_ops, /* devo_cb_ops */ (struct bus_ops *)NULL, /* devo_bus_ops */ usbser_power, /* devo_power */ ddi_quiesce_not_needed, /* devo_quiesce */ }; extern struct mod_ops mod_driverops; /* modldrv structure */ static struct modldrv modldrv = { &mod_driverops, /* type of module - driver */ "USB Serial CDC ACM driver", &usbsacm_ops, }; /* modlinkage structure */ static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; static void *usbsacm_statep; /* soft state */ /* * DSD definitions */ static ds_ops_t usbsacm_ds_ops = { DS_OPS_VERSION, usbsacm_ds_attach, usbsacm_ds_detach, usbsacm_ds_register_cb, usbsacm_ds_unregister_cb, usbsacm_ds_open_port, usbsacm_ds_close_port, usbsacm_ds_usb_power, usbsacm_ds_suspend, usbsacm_ds_resume, usbsacm_ds_disconnect, usbsacm_ds_reconnect, usbsacm_ds_set_port_params, usbsacm_ds_set_modem_ctl, usbsacm_ds_get_modem_ctl, usbsacm_ds_break_ctl, NULL, /* NULL if h/w doesn't support loopback */ usbsacm_ds_tx, usbsacm_ds_rx, usbsacm_ds_stop, usbsacm_ds_start, usbsacm_ds_fifo_flush, usbsacm_ds_fifo_drain }; /* * baud code -> baud rate (0 means unsupported rate) */ static int usbsacm_speedtab[] = { 0, /* B0 */ 50, /* B50 */ 75, /* B75 */ 110, /* B110 */ 134, /* B134 */ 150, /* B150 */ 200, /* B200 */ 300, /* B300 */ 600, /* B600 */ 1200, /* B1200 */ 1800, /* B1800 */ 2400, /* B2400 */ 4800, /* B4800 */ 9600, /* B9600 */ 19200, /* B19200 */ 38400, /* B38400 */ 57600, /* B57600 */ 76800, /* B76800 */ 115200, /* B115200 */ 153600, /* B153600 */ 230400, /* B230400 */ 307200, /* B307200 */ 460800, /* B460800 */ 921600 /* B921600 */ }; static uint_t usbsacm_errlevel = USB_LOG_L4; static uint_t usbsacm_errmask = 0xffffffff; static uint_t usbsacm_instance_debug = (uint_t)-1; /* * usbsacm driver's entry points * ----------------------------- */ /* * Module-wide initialization routine. */ int _init(void) { int error; if ((error = mod_install(&modlinkage)) == 0) { error = ddi_soft_state_init(&usbsacm_statep, usbser_soft_state_size(), 1); } return (error); } /* * Module-wide tear-down routine. */ int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) == 0) { ddi_soft_state_fini(&usbsacm_statep); } return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * Device configuration entry points */ static int usbsacm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { return (usbser_attach(dip, cmd, usbsacm_statep, &usbsacm_ds_ops)); } static int usbsacm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { return (usbser_detach(dip, cmd, usbsacm_statep)); } int usbsacm_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { return (usbser_getinfo(dip, infocmd, arg, result, usbsacm_statep)); } static int usbsacm_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr) { return (usbser_open(rq, dev, flag, sflag, cr, usbsacm_statep)); } /* * usbsacm_ds_detach: * attach device instance, called from GSD attach * initialize state and device, including: * state variables, locks, device node * device registration with system * power management */ static int usbsacm_ds_attach(ds_attach_info_t *aip) { usbsacm_state_t *acmp; acmp = (usbsacm_state_t *)kmem_zalloc(sizeof (usbsacm_state_t), KM_SLEEP); acmp->acm_dip = aip->ai_dip; acmp->acm_usb_events = aip->ai_usb_events; acmp->acm_ports = NULL; *aip->ai_hdl = (ds_hdl_t)acmp; /* registers usbsacm with the USBA framework */ if (usb_client_attach(acmp->acm_dip, USBDRV_VERSION, 0) != USB_SUCCESS) { goto fail; } /* Get the configuration information of device */ if (usb_get_dev_data(acmp->acm_dip, &acmp->acm_dev_data, USB_PARSE_LVL_CFG, 0) != USB_SUCCESS) { goto fail; } acmp->acm_def_ph = acmp->acm_dev_data->dev_default_ph; acmp->acm_dev_state = USB_DEV_ONLINE; mutex_init(&acmp->acm_mutex, NULL, MUTEX_DRIVER, acmp->acm_dev_data->dev_iblock_cookie); acmp->acm_lh = usb_alloc_log_hdl(acmp->acm_dip, "usbsacm", &usbsacm_errlevel, &usbsacm_errmask, &usbsacm_instance_debug, 0); /* Create power management components */ if (usbsacm_create_pm_components(acmp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_attach: create pm components failed."); goto fail; } /* Register to get callbacks for USB events */ if (usb_register_event_cbs(acmp->acm_dip, acmp->acm_usb_events, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_attach: register event callback failed."); goto fail; } /* * If devices conform to acm spec, driver will attach using class id; * if not, using device id. */ if ((strcmp(DEVI(acmp->acm_dip)->devi_binding_name, "usbif,class2.2") == 0) || ((strcmp(DEVI(acmp->acm_dip)->devi_binding_name, "usb,class2.2.0") == 0))) { acmp->acm_compatibility = B_TRUE; } else { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_attach: A nonstandard device is attaching to " "usbsacm driver. This device doesn't conform to " "usb cdc spec."); acmp->acm_compatibility = B_FALSE; } /* initialize state variables */ if (usbsacm_init_alloc_ports(acmp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_attach: initialize port structure failed."); goto fail; } *aip->ai_port_cnt = acmp->acm_port_cnt; /* Get max data size of bulk transfer */ if (usb_pipe_get_max_bulk_transfer_size(acmp->acm_dip, &acmp->acm_xfer_sz) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_attach: get max size of transfer failed."); goto fail; } return (USB_SUCCESS); fail: usbsacm_cleanup(acmp); return (USB_FAILURE); } /* * usbsacm_ds_detach: * detach device instance, called from GSD detach */ static void usbsacm_ds_detach(ds_hdl_t hdl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_ds_detach:"); usbsacm_close_pipes(acmp); usbsacm_cleanup(acmp); } /* * usbsacm_ds_register_cb: * GSD routine call ds_register_cb to register interrupt callbacks * for the given port */ /*ARGSUSED*/ static int usbsacm_ds_register_cb(ds_hdl_t hdl, uint_t port_num, ds_cb_t *cb) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_ds_register_cb: acmp = 0x%p port_num = %d", (void *)acmp, port_num); /* Check if port number is greater than actual port number. */ if (port_num >= acmp->acm_port_cnt) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_ds_register_cb: port number is wrong."); return (USB_FAILURE); } acm_port = &acmp->acm_ports[port_num]; acm_port->acm_cb = *cb; return (USB_SUCCESS); } /* * usbsacm_ds_unregister_cb: * GSD routine call ds_unregister_cb to unregister * interrupt callbacks for the given port */ /*ARGSUSED*/ static void usbsacm_ds_unregister_cb(ds_hdl_t hdl, uint_t port_num) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_ds_unregister_cb: "); if (port_num < acmp->acm_port_cnt) { /* Release callback function */ acm_port = &acmp->acm_ports[port_num]; bzero(&acm_port->acm_cb, sizeof (acm_port->acm_cb)); } } /* * usbsacm_ds_open_port: * GSD routine call ds_open_port * to open the given port */ /*ARGSUSED*/ static int usbsacm_ds_open_port(ds_hdl_t hdl, uint_t port_num) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_ds_open_port: port_num = %d", port_num); mutex_enter(&acm_port->acm_port_mutex); /* Check the status of the given port and device */ if ((acmp->acm_dev_state == USB_DEV_DISCONNECTED) || (acm_port->acm_port_state != USBSACM_PORT_CLOSED)) { mutex_exit(&acm_port->acm_port_mutex); return (USB_FAILURE); } mutex_exit(&acm_port->acm_port_mutex); usbsacm_pm_set_busy(acmp); /* open pipes of port */ if (usbsacm_open_port_pipes(acm_port) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_ds_open_port: open pipes failed."); return (USB_FAILURE); } mutex_enter(&acm_port->acm_port_mutex); /* data receipt */ if (usbsacm_rx_start(acm_port) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_ds_open_port: start receive data failed."); mutex_exit(&acm_port->acm_port_mutex); return (USB_FAILURE); } acm_port->acm_port_state = USBSACM_PORT_OPEN; mutex_exit(&acm_port->acm_port_mutex); return (USB_SUCCESS); } /* * usbsacm_ds_close_port: * GSD routine call ds_close_port * to close the given port */ /*ARGSUSED*/ static int usbsacm_ds_close_port(ds_hdl_t hdl, uint_t port_num) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; int rval = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_ds_close_port: acmp = 0x%p", (void *)acmp); mutex_enter(&acm_port->acm_port_mutex); acm_port->acm_port_state = USBSACM_PORT_CLOSED; mutex_exit(&acm_port->acm_port_mutex); usbsacm_close_port_pipes(acm_port); mutex_enter(&acm_port->acm_port_mutex); rval = usbsacm_fifo_flush_locked(acmp, port_num, DS_TX | DS_RX); mutex_exit(&acm_port->acm_port_mutex); usbsacm_pm_set_idle(acmp); return (rval); } /* * usbsacm_ds_usb_power: * GSD routine call ds_usb_power * to set power level of the component */ /*ARGSUSED*/ static int usbsacm_ds_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_pm_t *pm = acmp->acm_pm; int rval = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_usb_power: "); /* check if pm is NULL */ if (pm == NULL) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_usb_power: pm is NULL."); return (USB_FAILURE); } mutex_enter(&acmp->acm_mutex); /* * check if we are transitioning to a legal power level */ if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_usb_power: " "illegal power level %d, pwr_states=%x", level, pm->pm_pwr_states); mutex_exit(&acmp->acm_mutex); return (USB_FAILURE); } /* * if we are about to raise power and asked to lower power, fail */ if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_usb_power: wrong condition."); mutex_exit(&acmp->acm_mutex); return (USB_FAILURE); } /* * Set the power status of device by request level. */ switch (level) { case USB_DEV_OS_PWR_OFF: rval = usbsacm_pwrlvl0(acmp); break; case USB_DEV_OS_PWR_1: rval = usbsacm_pwrlvl1(acmp); break; case USB_DEV_OS_PWR_2: rval = usbsacm_pwrlvl2(acmp); break; case USB_DEV_OS_FULL_PWR: rval = usbsacm_pwrlvl3(acmp); /* * If usbser dev_state is DISCONNECTED or SUSPENDED, it shows * that the usb serial device is disconnected/suspended while it * is under power down state, now the device is powered up * before it is reconnected/resumed. xxx_pwrlvl3() will set dev * state to ONLINE, we need to set the dev state back to * DISCONNECTED/SUSPENDED. */ if ((rval == USB_SUCCESS) && ((*new_state == USB_DEV_DISCONNECTED) || (*new_state == USB_DEV_SUSPENDED))) { acmp->acm_dev_state = *new_state; } break; } *new_state = acmp->acm_dev_state; mutex_exit(&acmp->acm_mutex); return (rval); } /* * usbsacm_ds_suspend: * GSD routine call ds_suspend * during CPR suspend */ static int usbsacm_ds_suspend(ds_hdl_t hdl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; int state = USB_DEV_SUSPENDED; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_suspend: "); /* * If the device is suspended while it is under PWRED_DOWN state, we * need to keep the PWRED_DOWN state so that it could be powered up * later. In the mean while, usbser dev state will be changed to * SUSPENDED state. */ mutex_enter(&acmp->acm_mutex); if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) { /* set device status to suspend */ acmp->acm_dev_state = USB_DEV_SUSPENDED; } mutex_exit(&acmp->acm_mutex); usbsacm_disconnect_pipes(acmp); return (state); } /* * usbsacm_ds_resume: * GSD routine call ds_resume * during CPR resume */ /*ARGSUSED*/ static int usbsacm_ds_resume(ds_hdl_t hdl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; int current_state; int ret; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_ds_resume: "); mutex_enter(&acmp->acm_mutex); current_state = acmp->acm_dev_state; mutex_exit(&acmp->acm_mutex); /* restore the status of device */ if (current_state != USB_DEV_ONLINE) { ret = usbsacm_restore_device_state(acmp); } else { ret = USB_DEV_ONLINE; } return (ret); } /* * usbsacm_ds_disconnect: * GSD routine call ds_disconnect * to disconnect USB device */ static int usbsacm_ds_disconnect(ds_hdl_t hdl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; int state = USB_DEV_DISCONNECTED; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_ds_disconnect: "); /* * If the device is disconnected while it is under PWRED_DOWN state, we * need to keep the PWRED_DOWN state so that it could be powered up * later. In the mean while, usbser dev state will be changed to * DISCONNECTED state. */ mutex_enter(&acmp->acm_mutex); if (acmp->acm_dev_state != USB_DEV_PWRED_DOWN) { /* set device status to disconnected */ acmp->acm_dev_state = USB_DEV_DISCONNECTED; } mutex_exit(&acmp->acm_mutex); usbsacm_disconnect_pipes(acmp); return (state); } /* * usbsacm_ds_reconnect: * GSD routine call ds_reconnect * to reconnect USB device */ /*ARGSUSED*/ static int usbsacm_ds_reconnect(ds_hdl_t hdl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_ds_reconnect: "); return (usbsacm_restore_device_state(acmp)); } /* * usbsacm_ds_set_port_params: * GSD routine call ds_set_port_params * to set one or more port parameters */ /*ARGSUSED*/ static int usbsacm_ds_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; int i; uint_t ui; ds_port_param_entry_t *pe; usb_cdc_line_coding_t lc; int ret; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_set_port_params: acmp = 0x%p", (void *)acmp); mutex_enter(&acm_port->acm_port_mutex); /* * If device conform to acm spec, check if it support to set port param. */ if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 && acmp->acm_compatibility == B_TRUE) { mutex_exit(&acm_port->acm_port_mutex); USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_set_port_params: " "don't support Set_Line_Coding."); return (USB_FAILURE); } lc = acm_port->acm_line_coding; mutex_exit(&acm_port->acm_port_mutex); pe = tp->tp_entries; /* Get parameter information from ds_port_params_t */ for (i = 0; i < tp->tp_cnt; i++, pe++) { switch (pe->param) { case DS_PARAM_BAUD: /* Data terminal rate, in bits per second. */ ui = pe->val.ui; /* if we don't support this speed, return USB_FAILURE */ if ((ui >= NELEM(usbsacm_speedtab)) || ((ui > 0) && (usbsacm_speedtab[ui] == 0))) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_set_port_params: " " error baud rate"); return (USB_FAILURE); } lc.dwDTERate = LE_32(usbsacm_speedtab[ui]); break; case DS_PARAM_PARITY: /* Parity Type */ if (pe->val.ui & PARENB) { if (pe->val.ui & PARODD) { lc.bParityType = USB_CDC_PARITY_ODD; } else { lc.bParityType = USB_CDC_PARITY_EVEN; } } else { lc.bParityType = USB_CDC_PARITY_NO; } break; case DS_PARAM_STOPB: /* Stop bit */ if (pe->val.ui & CSTOPB) { lc.bCharFormat = USB_CDC_STOP_BITS_2; } else { lc.bCharFormat = USB_CDC_STOP_BITS_1; } break; case DS_PARAM_CHARSZ: /* Data Bits */ switch (pe->val.ui) { case CS5: lc.bDataBits = 5; break; case CS6: lc.bDataBits = 6; break; case CS7: lc.bDataBits = 7; break; case CS8: default: lc.bDataBits = 8; break; } break; default: USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_set_port_params: " "parameter 0x%x isn't supported", pe->param); break; } } if ((ret = usbsacm_set_line_coding(acm_port, &lc)) == USB_SUCCESS) { mutex_enter(&acm_port->acm_port_mutex); acm_port->acm_line_coding = lc; mutex_exit(&acm_port->acm_port_mutex); } /* * If device don't conform to acm spec, return success directly. */ if (acmp->acm_compatibility != B_TRUE) { ret = USB_SUCCESS; } return (ret); } /* * usbsacm_ds_set_modem_ctl: * GSD routine call ds_set_modem_ctl * to set modem control of the given port */ /*ARGSUSED*/ static int usbsacm_ds_set_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int val) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; uint8_t new_mctl; int ret; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_set_modem_ctl: mask = 0x%x val = 0x%x", mask, val); mutex_enter(&acm_port->acm_port_mutex); /* * If device conform to acm spec, check if it support to set modem * controls. */ if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SERIAL_LINE) == 0 && acmp->acm_compatibility == B_TRUE) { mutex_exit(&acm_port->acm_port_mutex); USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_set_modem_ctl: " "don't support Set_Control_Line_State."); return (USB_FAILURE); } new_mctl = acm_port->acm_mctlout; mutex_exit(&acm_port->acm_port_mutex); usbsacm_mctl2reg(mask, val, &new_mctl); if ((acmp->acm_compatibility == B_FALSE) || ((ret = usbsacm_req_write(acm_port, USB_CDC_REQ_SET_CONTROL_LINE_STATE, new_mctl, NULL)) == USB_SUCCESS)) { mutex_enter(&acm_port->acm_port_mutex); acm_port->acm_mctlout = new_mctl; mutex_exit(&acm_port->acm_port_mutex); } /* * If device don't conform to acm spec, return success directly. */ if (acmp->acm_compatibility != B_TRUE) { ret = USB_SUCCESS; } return (ret); } /* * usbsacm_ds_get_modem_ctl: * GSD routine call ds_get_modem_ctl * to get modem control/status of the given port */ /*ARGSUSED*/ static int usbsacm_ds_get_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int *valp) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; mutex_enter(&acm_port->acm_port_mutex); *valp = usbsacm_reg2mctl(acm_port->acm_mctlout) & mask; /* * If device conform to acm spec, polling function can modify the value * of acm_mctlin; else set to default value. */ if (acmp->acm_compatibility) { *valp |= usbsacm_reg2mctl(acm_port->acm_mctlin) & mask; *valp |= (mask & (TIOCM_CD | TIOCM_CTS)); } else { *valp |= (mask & (TIOCM_CD | TIOCM_CTS | TIOCM_DSR | TIOCM_RI)); } mutex_exit(&acm_port->acm_port_mutex); USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_get_modem_ctl: val = 0x%x", *valp); return (USB_SUCCESS); } /* * usbsacm_ds_tx: * GSD routine call ds_break_ctl * to set/clear break */ /*ARGSUSED*/ static int usbsacm_ds_break_ctl(ds_hdl_t hdl, uint_t port_num, int ctl) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_break_ctl: "); mutex_enter(&acm_port->acm_port_mutex); /* * If device conform to acm spec, check if it support to send break. */ if ((acm_port->acm_cap & USB_CDC_ACM_CAP_SEND_BREAK) == 0 && acmp->acm_compatibility == B_TRUE) { mutex_exit(&acm_port->acm_port_mutex); USB_DPRINTF_L2(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_break_ctl: don't support send break."); return (USB_FAILURE); } mutex_exit(&acm_port->acm_port_mutex); return (usbsacm_req_write(acm_port, USB_CDC_REQ_SEND_BREAK, ((ctl == DS_ON) ? 0xffff : 0), NULL)); } /* * usbsacm_ds_tx: * GSD routine call ds_tx * to data transmit */ /*ARGSUSED*/ static int usbsacm_ds_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_tx: mp = 0x%p acmp = 0x%p", (void *)mp, (void *)acmp); /* sanity checks */ if (mp == NULL) { return (USB_SUCCESS); } if (MBLKL(mp) < 1) { freemsg(mp); return (USB_SUCCESS); } mutex_enter(&acm_port->acm_port_mutex); /* put mblk to tail of mblk chain */ usbsacm_put_tail(&acm_port->acm_tx_mp, mp); usbsacm_tx_start(acm_port); mutex_exit(&acm_port->acm_port_mutex); return (USB_SUCCESS); } /* * usbsacm_ds_rx: * GSD routine call ds_rx; * to data receipt */ /*ARGSUSED*/ static mblk_t * usbsacm_ds_rx(ds_hdl_t hdl, uint_t port_num) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; mblk_t *mp; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_rx: acmp = 0x%p", (void *)acmp); mutex_enter(&acm_port->acm_port_mutex); mp = acm_port->acm_rx_mp; acm_port->acm_rx_mp = NULL; mutex_exit(&acm_port->acm_port_mutex); return (mp); } /* * usbsacm_ds_stop: * GSD routine call ds_stop; * but acm spec don't define this function */ /*ARGSUSED*/ static void usbsacm_ds_stop(ds_hdl_t hdl, uint_t port_num, int dir) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_stop: don't support!"); } /* * usbsacm_ds_start: * GSD routine call ds_start; * but acm spec don't define this function */ /*ARGSUSED*/ static void usbsacm_ds_start(ds_hdl_t hdl, uint_t port_num, int dir) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_start: don't support!"); } /* * usbsacm_ds_fifo_flush: * GSD routine call ds_fifo_flush * to flush FIFOs */ /*ARGSUSED*/ static int usbsacm_ds_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; int ret = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_ds_fifo_flush: "); mutex_enter(&acm_port->acm_port_mutex); ret = usbsacm_fifo_flush_locked(acmp, port_num, dir); mutex_exit(&acm_port->acm_port_mutex); return (ret); } /* * usbsacm_ds_fifo_drain: * GSD routine call ds_fifo_drain * to wait until empty output FIFO */ /*ARGSUSED*/ static int usbsacm_ds_fifo_drain(ds_hdl_t hdl, uint_t port_num, int timeout) { usbsacm_state_t *acmp = (usbsacm_state_t *)hdl; usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; int rval = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_fifo_drain: "); mutex_enter(&acm_port->acm_port_mutex); ASSERT(acm_port->acm_port_state == USBSACM_PORT_OPEN); if (usbsacm_wait_tx_drain(acm_port, timeout) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_ds_fifo_drain: fifo drain failed."); mutex_exit(&acm_port->acm_port_mutex); return (USB_FAILURE); } mutex_exit(&acm_port->acm_port_mutex); return (rval); } /* * usbsacm_fifo_flush_locked: * flush FIFOs of the given ports */ /*ARGSUSED*/ static int usbsacm_fifo_flush_locked(usbsacm_state_t *acmp, uint_t port_num, int dir) { usbsacm_port_t *acm_port = &acmp->acm_ports[port_num]; USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_fifo_flush_locked: "); /* flush transmit FIFO if DS_TX is set */ if ((dir & DS_TX) && acm_port->acm_tx_mp) { freemsg(acm_port->acm_tx_mp); acm_port->acm_tx_mp = NULL; } /* flush received FIFO if DS_RX is set */ if ((dir & DS_RX) && acm_port->acm_rx_mp) { freemsg(acm_port->acm_rx_mp); acm_port->acm_rx_mp = NULL; } return (USB_SUCCESS); } /* * usbsacm_get_bulk_pipe_number: * Calculate the number of bulk in or out pipes in current device. */ static int usbsacm_get_bulk_pipe_number(usbsacm_state_t *acmp, uint_t dir) { int count = 0; int i, skip; usb_if_data_t *cur_if; int ep_num; int if_num; USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_bulk_pipe_number: "); cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if; if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if; /* search each interface which have bulk endpoint */ for (i = 0; i < if_num; i++) { ep_num = cur_if->if_alt->altif_n_ep; /* * search endpoints in current interface, * which type is input parameter 'dir' */ for (skip = 0; skip < ep_num; skip++) { if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, i, 0, skip, USB_EP_ATTR_BULK, dir) == NULL) { /* * If not found, skip the internal loop * and search the next interface. */ break; } count++; } cur_if++; } return (count); } /* * port management * --------------- * initialize, release port. * * * usbsacm_init_ports_status: * Initialize the port status for the current device. */ static int usbsacm_init_ports_status(usbsacm_state_t *acmp) { usbsacm_port_t *cur_port; int i, skip; int if_num; int intr_if_no = 0; int ep_num; usb_if_data_t *cur_if; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_init_ports_status: acmp = 0x%p", (void *)acmp); /* Initialize the port status to default value */ for (i = 0; i < acmp->acm_port_cnt; i++) { cur_port = &acmp->acm_ports[i]; cv_init(&cur_port->acm_tx_cv, NULL, CV_DRIVER, NULL); cur_port->acm_port_state = USBSACM_PORT_CLOSED; cur_port->acm_line_coding.dwDTERate = LE_32((uint32_t)9600); cur_port->acm_line_coding.bCharFormat = 0; cur_port->acm_line_coding.bParityType = USB_CDC_PARITY_NO; cur_port->acm_line_coding.bDataBits = 8; cur_port->acm_device = acmp; mutex_init(&cur_port->acm_port_mutex, NULL, MUTEX_DRIVER, acmp->acm_dev_data->dev_iblock_cookie); } /* * If device conform to cdc acm spec, parse function descriptors. */ if (acmp->acm_compatibility == B_TRUE) { if (usbsacm_get_descriptors(acmp) != USB_SUCCESS) { return (USB_FAILURE); } return (USB_SUCCESS); } /* * If device don't conform to spec, search pairs of bulk in/out * endpoints and fill port structure. */ cur_if = acmp->acm_dev_data->dev_curr_cfg->cfg_if; if_num = acmp->acm_dev_data->dev_curr_cfg->cfg_n_if; cur_port = acmp->acm_ports; /* search each interface which have bulk in and out */ for (i = 0; i < if_num; i++) { ep_num = cur_if->if_alt->altif_n_ep; for (skip = 0; skip < ep_num; skip++) { /* search interrupt pipe. */ if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, i, 0, skip, USB_EP_ATTR_INTR, USB_EP_DIR_IN) != NULL)) { intr_if_no = i; } /* search pair of bulk in/out endpoints. */ if ((usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_IN) == NULL) || (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, i, 0, skip, USB_EP_ATTR_BULK, USB_EP_DIR_OUT) == NULL)) { continue; } cur_port->acm_data_if_no = i; cur_port->acm_ctrl_if_no = intr_if_no; cur_port->acm_data_port_no = skip; cur_port++; intr_if_no = 0; } cur_if++; } return (USB_SUCCESS); } /* * usbsacm_init_alloc_ports: * Allocate memory and initialize the port state for the current device. */ static int usbsacm_init_alloc_ports(usbsacm_state_t *acmp) { int rval = USB_SUCCESS; int count_in = 0, count_out = 0; if (acmp->acm_compatibility) { acmp->acm_port_cnt = 1; } else { /* Calculate the number of the bulk in/out endpoints */ count_in = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_IN); count_out = usbsacm_get_bulk_pipe_number(acmp, USB_EP_DIR_OUT); USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_init_alloc_ports: count_in = %d, count_out = %d", count_in, count_out); acmp->acm_port_cnt = min(count_in, count_out); } /* return if not found any pair of bulk in/out endpoint. */ if (acmp->acm_port_cnt == 0) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_init_alloc_ports: port count is zero."); return (USB_FAILURE); } /* allocate memory for ports */ acmp->acm_ports = (usbsacm_port_t *)kmem_zalloc(acmp->acm_port_cnt * sizeof (usbsacm_port_t), KM_SLEEP); if (acmp->acm_ports == NULL) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_init_alloc_ports: allocate memory failed."); return (USB_FAILURE); } /* fill the status of port structure. */ rval = usbsacm_init_ports_status(acmp); if (rval != USB_SUCCESS) { usbsacm_free_ports(acmp); } return (rval); } /* * usbsacm_free_ports: * Release ports and deallocate memory. */ static void usbsacm_free_ports(usbsacm_state_t *acmp) { int i; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_free_ports: "); /* Release memory and data structure for each port */ for (i = 0; i < acmp->acm_port_cnt; i++) { cv_destroy(&acmp->acm_ports[i].acm_tx_cv); mutex_destroy(&acmp->acm_ports[i].acm_port_mutex); } kmem_free((caddr_t)acmp->acm_ports, sizeof (usbsacm_port_t) * acmp->acm_port_cnt); acmp->acm_ports = NULL; } /* * usbsacm_get_descriptors: * analysis functional descriptors of acm device */ static int usbsacm_get_descriptors(usbsacm_state_t *acmp) { int i; usb_cfg_data_t *cfg; usb_alt_if_data_t *altif; usb_cvs_data_t *cvs; int mgmt_cap = 0; int master_if = -1, slave_if = -1; usbsacm_port_t *acm_port = acmp->acm_ports; usb_dev_descr_t *dd; USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: "); dd = acmp->acm_dev_data->dev_descr; cfg = acmp->acm_dev_data->dev_curr_cfg; /* set default control and data interface */ acm_port->acm_ctrl_if_no = acm_port->acm_data_if_no = 0; /* get current interfaces */ acm_port->acm_ctrl_if_no = acmp->acm_dev_data->dev_curr_if; if (cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt == 0) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: elements in if_alt is %d", cfg->cfg_if[acm_port->acm_ctrl_if_no].if_n_alt); return (USB_FAILURE); } altif = &cfg->cfg_if[acm_port->acm_ctrl_if_no].if_alt[0]; /* * Based on CDC specification, ACM devices usually include the * following function descriptors: Header, ACM, Union and Call * Management function descriptors. This loop search tree data * structure for each acm class descriptor. */ for (i = 0; i < altif->altif_n_cvs; i++) { cvs = &altif->altif_cvs[i]; if ((cvs->cvs_buf == NULL) || (cvs->cvs_buf[1] != USB_CDC_CS_INTERFACE)) { continue; } switch (cvs->cvs_buf[2]) { case USB_CDC_DESCR_TYPE_CALL_MANAGEMENT: /* parse call management functional descriptor. */ if (cvs->cvs_buf_len >= 5) { mgmt_cap = cvs->cvs_buf[3]; acm_port->acm_data_if_no = cvs->cvs_buf[4]; } break; case USB_CDC_DESCR_TYPE_ACM: /* parse ACM functional descriptor. */ if (cvs->cvs_buf_len >= 4) { acm_port->acm_cap = cvs->cvs_buf[3]; } /* * The Sigma Designs, Inc. USB device does not report * itself as implementing the full ACM spec. However, * it does function as a usb serial modem, so we opt to * scribble in the reported functionality if we * determine the USB device matches this vendor * and product ID. */ if (dd->idVendor == USB_VENDOR_SIGMADESIGNS && dd->idProduct == USB_PRODUCT_SIGMADESIGNS_ZW090) { acm_port->acm_cap |= USB_CDC_ACM_CAP_SERIAL_LINE; } break; case USB_CDC_DESCR_TYPE_UNION: /* parse Union functional descriptor. */ if (cvs->cvs_buf_len >= 5) { master_if = cvs->cvs_buf[3]; slave_if = cvs->cvs_buf[4]; } break; default: break; } } /* For usb acm devices, it must satisfy the following options. */ if (cfg->cfg_n_if < 2) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: # of interfaces %d < 2", cfg->cfg_n_if); return (USB_FAILURE); } if (acm_port->acm_data_if_no == 0 && slave_if != acm_port->acm_data_if_no) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: Device hasn't call management " "descriptor and use Union Descriptor."); acm_port->acm_data_if_no = slave_if; } if ((master_if != acm_port->acm_ctrl_if_no) || (slave_if != acm_port->acm_data_if_no)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: control interface or " "data interface don't match."); return (USB_FAILURE); } /* * We usually need both call and data capabilities, but * some devices, such as Nokia mobile phones, don't provide * call management descriptor, so we just give a warning * message. */ if (((mgmt_cap & USB_CDC_CALL_MGMT_CAP_CALL_MGMT) == 0) || ((mgmt_cap & USB_CDC_CALL_MGMT_CAP_DATA_INTERFACE) == 0)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: " "insufficient mgmt capabilities %x", mgmt_cap); } if ((acm_port->acm_ctrl_if_no >= cfg->cfg_n_if) || (acm_port->acm_data_if_no >= cfg->cfg_n_if)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: control interface %d or " "data interface %d out of range.", acm_port->acm_ctrl_if_no, acm_port->acm_data_if_no); return (USB_FAILURE); } /* control interface must have interrupt endpoint */ if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_ctrl_if_no, 0, 0, USB_EP_ATTR_INTR, USB_EP_DIR_IN) == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: " "ctrl interface %d has no interrupt endpoint", acm_port->acm_data_if_no); return (USB_FAILURE); } /* data interface must have bulk in and out */ if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK, USB_EP_DIR_IN) == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: " "data interface %d has no bulk in endpoint", acm_port->acm_data_if_no); return (USB_FAILURE); } if (usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_data_if_no, 0, 0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT) == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_get_descriptors: " "data interface %d has no bulk out endpoint", acm_port->acm_data_if_no); return (USB_FAILURE); } return (USB_SUCCESS); } /* * usbsacm_cleanup: * Release resources of current device during detach. */ static void usbsacm_cleanup(usbsacm_state_t *acmp) { USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_cleanup: "); if (acmp != NULL) { /* free ports */ if (acmp->acm_ports != NULL) { usbsacm_free_ports(acmp); } /* unregister callback function */ if (acmp->acm_usb_events != NULL) { usb_unregister_event_cbs(acmp->acm_dip, acmp->acm_usb_events); } /* destroy power management components */ if (acmp->acm_pm != NULL) { usbsacm_destroy_pm_components(acmp); } /* free description of device tree. */ if (acmp->acm_def_ph != NULL) { mutex_destroy(&acmp->acm_mutex); usb_free_descr_tree(acmp->acm_dip, acmp->acm_dev_data); acmp->acm_def_ph = NULL; } if (acmp->acm_lh != NULL) { usb_free_log_hdl(acmp->acm_lh); acmp->acm_lh = NULL; } /* detach client device */ if (acmp->acm_dev_data != NULL) { usb_client_detach(acmp->acm_dip, acmp->acm_dev_data); } kmem_free((caddr_t)acmp, sizeof (usbsacm_state_t)); } } /* * usbsacm_restore_device_state: * restore device state after CPR resume or reconnect */ static int usbsacm_restore_device_state(usbsacm_state_t *acmp) { int state; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_restore_device_state: "); mutex_enter(&acmp->acm_mutex); state = acmp->acm_dev_state; mutex_exit(&acmp->acm_mutex); /* Check device status */ if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) { return (state); } /* Check if we are talking to the same device */ if (usb_check_same_device(acmp->acm_dip, acmp->acm_lh, USB_LOG_L0, -1, USB_CHK_ALL, NULL) != USB_SUCCESS) { mutex_enter(&acmp->acm_mutex); state = acmp->acm_dev_state = USB_DEV_DISCONNECTED; mutex_exit(&acmp->acm_mutex); return (state); } if (state == USB_DEV_DISCONNECTED) { USB_DPRINTF_L1(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_restore_device_state: Device has been reconnected " "but data may have been lost"); } /* reconnect pipes */ if (usbsacm_reconnect_pipes(acmp) != USB_SUCCESS) { return (state); } /* * init device state */ mutex_enter(&acmp->acm_mutex); state = acmp->acm_dev_state = USB_DEV_ONLINE; mutex_exit(&acmp->acm_mutex); if ((usbsacm_restore_port_state(acmp) != USB_SUCCESS)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_restore_device_state: failed"); } return (state); } /* * usbsacm_restore_port_state: * restore ports state after CPR resume or reconnect */ static int usbsacm_restore_port_state(usbsacm_state_t *acmp) { int i, ret = USB_SUCCESS; usbsacm_port_t *cur_port; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_restore_port_state: "); /* restore status of all ports */ for (i = 0; i < acmp->acm_port_cnt; i++) { cur_port = &acmp->acm_ports[i]; mutex_enter(&cur_port->acm_port_mutex); if (cur_port->acm_port_state != USBSACM_PORT_OPEN) { mutex_exit(&cur_port->acm_port_mutex); continue; } mutex_exit(&cur_port->acm_port_mutex); if ((ret = usbsacm_set_line_coding(cur_port, &cur_port->acm_line_coding)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_restore_port_state: failed."); } } return (ret); } /* * pipe management * --------------- * * * usbsacm_open_port_pipes: * Open pipes of one port and set port structure; * Each port includes three pipes: bulk in, bulk out and interrupt. */ static int usbsacm_open_port_pipes(usbsacm_port_t *acm_port) { int rval = USB_SUCCESS; usbsacm_state_t *acmp = acm_port->acm_device; usb_ep_data_t *in_data, *out_data, *intr_pipe; usb_pipe_policy_t policy; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: acmp = 0x%p", (void *)acmp); /* Get bulk and interrupt endpoint data */ intr_pipe = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_ctrl_if_no, 0, 0, USB_EP_ATTR_INTR, USB_EP_DIR_IN); in_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no, USB_EP_ATTR_BULK, USB_EP_DIR_IN); out_data = usb_lookup_ep_data(acmp->acm_dip, acmp->acm_dev_data, acm_port->acm_data_if_no, 0, acm_port->acm_data_port_no, USB_EP_ATTR_BULK, USB_EP_DIR_OUT); /* Bulk in and out must exist meanwhile. */ if ((in_data == NULL) || (out_data == NULL)) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: look up bulk pipe failed in " "interface %d port %d", acm_port->acm_data_if_no, acm_port->acm_data_port_no); return (USB_FAILURE); } /* * If device conform to acm spec, it must have an interrupt pipe * for this port. */ if (acmp->acm_compatibility == B_TRUE && intr_pipe == NULL) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: look up interrupt pipe failed in " "interface %d", acm_port->acm_ctrl_if_no); return (USB_FAILURE); } policy.pp_max_async_reqs = 2; /* Open bulk in endpoint */ if (usb_pipe_open(acmp->acm_dip, &in_data->ep_descr, &policy, USB_FLAGS_SLEEP, &acm_port->acm_bulkin_ph) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: open bulkin pipe failed!"); return (USB_FAILURE); } /* Open bulk out endpoint */ if (usb_pipe_open(acmp->acm_dip, &out_data->ep_descr, &policy, USB_FLAGS_SLEEP, &acm_port->acm_bulkout_ph) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: open bulkout pipe failed!"); usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph, USB_FLAGS_SLEEP, NULL, NULL); return (USB_FAILURE); } /* Open interrupt endpoint if found. */ if (intr_pipe != NULL) { if (usb_pipe_open(acmp->acm_dip, &intr_pipe->ep_descr, &policy, USB_FLAGS_SLEEP, &acm_port->acm_intr_ph) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_open_port_pipes: " "open control pipe failed"); usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph, USB_FLAGS_SLEEP, NULL, NULL); usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph, USB_FLAGS_SLEEP, NULL, NULL); return (USB_FAILURE); } } /* initialize the port structure. */ mutex_enter(&acm_port->acm_port_mutex); acm_port->acm_bulkin_size = in_data->ep_descr.wMaxPacketSize; acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE; acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE; if (acm_port->acm_intr_ph != NULL) { acm_port->acm_intr_state = USBSACM_PIPE_IDLE; acm_port->acm_intr_ep_descr = intr_pipe->ep_descr; } mutex_exit(&acm_port->acm_port_mutex); if (acm_port->acm_intr_ph != NULL) { usbsacm_pipe_start_polling(acm_port); } return (rval); } /* * usbsacm_close_port_pipes: * Close pipes of one port and reset port structure to closed; * Each port includes three pipes: bulk in, bulk out and interrupt. */ static void usbsacm_close_port_pipes(usbsacm_port_t *acm_port) { usbsacm_state_t *acmp = acm_port->acm_device; mutex_enter(&acm_port->acm_port_mutex); USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_close_port_pipes: acm_bulkin_state = %d", acm_port->acm_bulkin_state); /* * Check the status of the given port. If port is closing or closed, * return directly. */ if ((acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSED) || (acm_port->acm_bulkin_state == USBSACM_PIPE_CLOSING)) { USB_DPRINTF_L2(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_close_port_pipes: port is closing or has closed"); mutex_exit(&acm_port->acm_port_mutex); return; } acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSING; mutex_exit(&acm_port->acm_port_mutex); /* Close pipes */ usb_pipe_reset(acmp->acm_dip, acm_port->acm_bulkin_ph, USB_FLAGS_SLEEP, 0, 0); usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkin_ph, USB_FLAGS_SLEEP, 0, 0); usb_pipe_close(acmp->acm_dip, acm_port->acm_bulkout_ph, USB_FLAGS_SLEEP, 0, 0); if (acm_port->acm_intr_ph != NULL) { usb_pipe_stop_intr_polling(acm_port->acm_intr_ph, USB_FLAGS_SLEEP); usb_pipe_close(acmp->acm_dip, acm_port->acm_intr_ph, USB_FLAGS_SLEEP, 0, 0); } mutex_enter(&acm_port->acm_port_mutex); /* Reset the status of pipes to closed */ acm_port->acm_bulkin_state = USBSACM_PIPE_CLOSED; acm_port->acm_bulkin_ph = NULL; acm_port->acm_bulkout_state = USBSACM_PIPE_CLOSED; acm_port->acm_bulkout_ph = NULL; if (acm_port->acm_intr_ph != NULL) { acm_port->acm_intr_state = USBSACM_PIPE_CLOSED; acm_port->acm_intr_ph = NULL; } mutex_exit(&acm_port->acm_port_mutex); USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_close_port_pipes: port has been closed."); } /* * usbsacm_close_pipes: * close all opened pipes of current devices. */ static void usbsacm_close_pipes(usbsacm_state_t *acmp) { int i; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_close_pipes: "); /* Close all ports */ for (i = 0; i < acmp->acm_port_cnt; i++) { usbsacm_close_port_pipes(&acmp->acm_ports[i]); } } /* * usbsacm_disconnect_pipes: * this function just call usbsacm_close_pipes. */ static void usbsacm_disconnect_pipes(usbsacm_state_t *acmp) { USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_disconnect_pipes: "); usbsacm_close_pipes(acmp); } /* * usbsacm_reconnect_pipes: * reconnect pipes in CPR resume or reconnect */ static int usbsacm_reconnect_pipes(usbsacm_state_t *acmp) { usbsacm_port_t *cur_port = acmp->acm_ports; int i; USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_reconnect_pipes: "); /* reopen all ports of current device. */ for (i = 0; i < acmp->acm_port_cnt; i++) { cur_port = &acmp->acm_ports[i]; mutex_enter(&cur_port->acm_port_mutex); /* * If port status is open, reopen it; * else retain the current status. */ if (cur_port->acm_port_state == USBSACM_PORT_OPEN) { mutex_exit(&cur_port->acm_port_mutex); if (usbsacm_open_port_pipes(cur_port) != USB_SUCCESS) { USB_DPRINTF_L4(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_reconnect_pipes: " "open port %d failed.", i); return (USB_FAILURE); } mutex_enter(&cur_port->acm_port_mutex); } mutex_exit(&cur_port->acm_port_mutex); } return (USB_SUCCESS); } /* * usbsacm_bulkin_cb: * Bulk In regular and exeception callback; * USBA framework will call this callback * after deal with bulkin request. */ /*ARGSUSED*/ static void usbsacm_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) { usbsacm_port_t *acm_port = (usbsacm_port_t *)req->bulk_client_private; usbsacm_state_t *acmp = acm_port->acm_device; mblk_t *data; int data_len; data = req->bulk_data; data_len = (data) ? MBLKL(data) : 0; mutex_enter(&acm_port->acm_port_mutex); USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_bulkin_cb: " "acm_bulkin_state = %d acm_port_state = %d data_len = %d", acm_port->acm_bulkin_state, acm_port->acm_port_state, data_len); if ((acm_port->acm_port_state == USBSACM_PORT_OPEN) && (data_len) && (req->bulk_completion_reason == USB_CR_OK)) { mutex_exit(&acm_port->acm_port_mutex); /* prevent USBA from freeing data along with the request */ req->bulk_data = NULL; /* save data on the receive list */ usbsacm_put_tail(&acm_port->acm_rx_mp, data); /* invoke GSD receive callback */ if (acm_port->acm_cb.cb_rx) { acm_port->acm_cb.cb_rx(acm_port->acm_cb.cb_arg); } mutex_enter(&acm_port->acm_port_mutex); } mutex_exit(&acm_port->acm_port_mutex); usb_free_bulk_req(req); /* receive more */ mutex_enter(&acm_port->acm_port_mutex); if (((acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) || (acm_port->acm_bulkin_state == USBSACM_PIPE_IDLE)) && (acm_port->acm_port_state == USBSACM_PORT_OPEN) && (acmp->acm_dev_state == USB_DEV_ONLINE)) { if (usbsacm_rx_start(acm_port) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_bulkin_cb: restart rx fail " "acm_port_state = %d", acm_port->acm_port_state); } } else if (acm_port->acm_bulkin_state == USBSACM_PIPE_BUSY) { acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE; } mutex_exit(&acm_port->acm_port_mutex); } /* * usbsacm_bulkout_cb: * Bulk Out regular and exeception callback; * USBA framework will call this callback function * after deal with bulkout request. */ /*ARGSUSED*/ static void usbsacm_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) { usbsacm_port_t *acm_port = (usbsacm_port_t *)req->bulk_client_private; usbsacm_state_t *acmp = acm_port->acm_device; int data_len; mblk_t *data = req->bulk_data; USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_bulkout_cb: acmp = 0x%p", (void *)acmp); data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0; /* put untransferred residue back on the transfer list */ if (req->bulk_completion_reason && (data_len > 0)) { usbsacm_put_head(&acm_port->acm_tx_mp, data); req->bulk_data = NULL; } usb_free_bulk_req(req); /* invoke GSD transmit callback */ if (acm_port->acm_cb.cb_tx) { acm_port->acm_cb.cb_tx(acm_port->acm_cb.cb_arg); } /* send more */ mutex_enter(&acm_port->acm_port_mutex); acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE; if (acm_port->acm_tx_mp == NULL) { cv_broadcast(&acm_port->acm_tx_cv); } else { usbsacm_tx_start(acm_port); } mutex_exit(&acm_port->acm_port_mutex); } /* * usbsacm_rx_start: * start data receipt */ static int usbsacm_rx_start(usbsacm_port_t *acm_port) { usbsacm_state_t *acmp = acm_port->acm_device; usb_bulk_req_t *br; int rval = USB_FAILURE; int data_len; USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_rx_start: acm_xfer_sz = 0x%lx acm_bulkin_size = 0x%lx", acmp->acm_xfer_sz, acm_port->acm_bulkin_size); acm_port->acm_bulkin_state = USBSACM_PIPE_BUSY; /* * Qualcomm CDMA card won't response the first request, * if the following code don't multiply by 2. */ data_len = min(acmp->acm_xfer_sz, acm_port->acm_bulkin_size * 2); mutex_exit(&acm_port->acm_port_mutex); br = usb_alloc_bulk_req(acmp->acm_dip, data_len, USB_FLAGS_SLEEP); if (br == NULL) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_rx_start: allocate bulk request failed"); mutex_enter(&acm_port->acm_port_mutex); return (USB_FAILURE); } /* initialize bulk in request. */ br->bulk_len = data_len; br->bulk_timeout = USBSACM_BULKIN_TIMEOUT; br->bulk_cb = usbsacm_bulkin_cb; br->bulk_exc_cb = usbsacm_bulkin_cb; br->bulk_client_private = (usb_opaque_t)acm_port; br->bulk_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK; rval = usb_pipe_bulk_xfer(acm_port->acm_bulkin_ph, br, 0); mutex_enter(&acm_port->acm_port_mutex); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_rx_start: bulk transfer failed %d", rval); usb_free_bulk_req(br); acm_port->acm_bulkin_state = USBSACM_PIPE_IDLE; } return (rval); } /* * usbsacm_tx_start: * start data transmit */ static void usbsacm_tx_start(usbsacm_port_t *acm_port) { int len; /* bytes we can transmit */ mblk_t *data; /* data to be transmitted */ int data_len; /* bytes in 'data' */ mblk_t *mp; /* current msgblk */ int copylen; /* bytes copy from 'mp' to 'data' */ int rval; usbsacm_state_t *acmp = acm_port->acm_device; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_tx_start: "); /* check the transmitted data. */ if (acm_port->acm_tx_mp == NULL) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_tx_start: acm_tx_mp is NULL"); return; } /* check pipe status */ if (acm_port->acm_bulkout_state != USBSACM_PIPE_IDLE) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_tx_start: error state in bulkout endpoint"); return; } ASSERT(MBLKL(acm_port->acm_tx_mp) > 0); /* send as much data as port can receive */ len = min(msgdsize(acm_port->acm_tx_mp), acmp->acm_xfer_sz); if (len == 0) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_tx_start: data len is 0"); return; } /* allocate memory for sending data. */ if ((data = allocb(len, BPRI_LO)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_tx_start: failure in allocate memory"); return; } /* * copy no more than 'len' bytes from mblk chain to transmit mblk 'data' */ data_len = 0; while ((data_len < len) && acm_port->acm_tx_mp) { /* Get the first mblk from chain. */ mp = acm_port->acm_tx_mp; copylen = min(MBLKL(mp), len - data_len); bcopy(mp->b_rptr, data->b_wptr, copylen); mp->b_rptr += copylen; data->b_wptr += copylen; data_len += copylen; if (MBLKL(mp) < 1) { acm_port->acm_tx_mp = unlinkb(mp); freeb(mp); } else { ASSERT(data_len == len); } } if (data_len <= 0) { freeb(data); return; } acm_port->acm_bulkout_state = USBSACM_PIPE_BUSY; mutex_exit(&acm_port->acm_port_mutex); /* send request. */ rval = usbsacm_send_data(acm_port, data); mutex_enter(&acm_port->acm_port_mutex); /* * If send failed, retransmit data when acm_tx_mp is null. */ if (rval != USB_SUCCESS) { acm_port->acm_bulkout_state = USBSACM_PIPE_IDLE; if (acm_port->acm_tx_mp == NULL) { usbsacm_put_head(&acm_port->acm_tx_mp, data); } } } /* * usbsacm_send_data: * data transfer */ static int usbsacm_send_data(usbsacm_port_t *acm_port, mblk_t *data) { usbsacm_state_t *acmp = acm_port->acm_device; usb_bulk_req_t *br; int rval; int data_len = MBLKL(data); USB_DPRINTF_L4(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_send_data: data address is 0x%p, length = %d", (void *)data, data_len); br = usb_alloc_bulk_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP); if (br == NULL) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_send_data: alloc req failed."); return (USB_FAILURE); } /* initialize the bulk out request */ br->bulk_data = data; br->bulk_len = data_len; br->bulk_timeout = USBSACM_BULKOUT_TIMEOUT; br->bulk_cb = usbsacm_bulkout_cb; br->bulk_exc_cb = usbsacm_bulkout_cb; br->bulk_client_private = (usb_opaque_t)acm_port; br->bulk_attributes = USB_ATTRS_AUTOCLEARING; rval = usb_pipe_bulk_xfer(acm_port->acm_bulkout_ph, br, 0); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_EVENTS, acmp->acm_lh, "usbsacm_send_data: Send Data failed."); /* * Don't free it in usb_free_bulk_req because it will * be linked in usbsacm_put_head */ br->bulk_data = NULL; usb_free_bulk_req(br); } return (rval); } /* * usbsacm_wait_tx_drain: * wait until local tx buffer drains. * 'timeout' is in seconds, zero means wait forever */ static int usbsacm_wait_tx_drain(usbsacm_port_t *acm_port, int timeout) { clock_t until; int over = 0; until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout); while (acm_port->acm_tx_mp && !over) { if (timeout > 0) { over = (cv_timedwait_sig(&acm_port->acm_tx_cv, &acm_port->acm_port_mutex, until) <= 0); } else { over = (cv_wait_sig(&acm_port->acm_tx_cv, &acm_port->acm_port_mutex) == 0); } } return ((acm_port->acm_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE); } /* * usbsacm_req_write: * send command over control pipe */ static int usbsacm_req_write(usbsacm_port_t *acm_port, uchar_t request, uint16_t value, mblk_t **data) { usbsacm_state_t *acmp = acm_port->acm_device; usb_ctrl_setup_t setup; usb_cb_flags_t cb_flags; usb_cr_t cr; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_req_write: "); /* initialize the control request. */ setup.bmRequestType = USBSACM_REQ_WRITE_IF; setup.bRequest = request; setup.wValue = value; setup.wIndex = acm_port->acm_ctrl_if_no; setup.wLength = ((data != NULL) && (*data != NULL)) ? MBLKL(*data) : 0; setup.attrs = 0; return (usb_pipe_ctrl_xfer_wait(acmp->acm_def_ph, &setup, data, &cr, &cb_flags, 0)); } /* * usbsacm_set_line_coding: * Send USB_CDC_REQ_SET_LINE_CODING request */ static int usbsacm_set_line_coding(usbsacm_port_t *acm_port, usb_cdc_line_coding_t *lc) { mblk_t *bp; int ret; /* allocate mblk and copy supplied structure into it */ if ((bp = allocb(USB_CDC_LINE_CODING_LEN, BPRI_HI)) == NULL) { return (USB_NO_RESOURCES); } #ifndef __lock_lint /* warlock gets confused here */ /* LINTED E_BAD_PTR_CAST_ALIGN */ *((usb_cdc_line_coding_t *)bp->b_wptr) = *lc; bp->b_wptr += USB_CDC_LINE_CODING_LEN; #endif ret = usbsacm_req_write(acm_port, USB_CDC_REQ_SET_LINE_CODING, 0, &bp); if (bp != NULL) { freeb(bp); } return (ret); } /* * usbsacm_mctl2reg: * Set Modem control status */ static void usbsacm_mctl2reg(int mask, int val, uint8_t *line_ctl) { if (mask & TIOCM_RTS) { if (val & TIOCM_RTS) { *line_ctl |= USB_CDC_ACM_CONTROL_RTS; } else { *line_ctl &= ~USB_CDC_ACM_CONTROL_RTS; } } if (mask & TIOCM_DTR) { if (val & TIOCM_DTR) { *line_ctl |= USB_CDC_ACM_CONTROL_DTR; } else { *line_ctl &= ~USB_CDC_ACM_CONTROL_DTR; } } } /* * usbsacm_reg2mctl: * Get Modem control status */ static int usbsacm_reg2mctl(uint8_t line_ctl) { int val = 0; if (line_ctl & USB_CDC_ACM_CONTROL_RTS) { val |= TIOCM_RTS; } if (line_ctl & USB_CDC_ACM_CONTROL_DTR) { val |= TIOCM_DTR; } if (line_ctl & USB_CDC_ACM_CONTROL_DSR) { val |= TIOCM_DSR; } if (line_ctl & USB_CDC_ACM_CONTROL_RNG) { val |= TIOCM_RI; } return (val); } /* * misc routines * ------------- * */ /* * usbsacm_put_tail: * link a message block to tail of message * account for the case when message is null */ static void usbsacm_put_tail(mblk_t **mpp, mblk_t *bp) { if (*mpp) { linkb(*mpp, bp); } else { *mpp = bp; } } /* * usbsacm_put_head: * put a message block at the head of the message * account for the case when message is null */ static void usbsacm_put_head(mblk_t **mpp, mblk_t *bp) { if (*mpp) { linkb(bp, *mpp); } *mpp = bp; } /* * power management * ---------------- * * usbsacm_create_pm_components: * create PM components */ static int usbsacm_create_pm_components(usbsacm_state_t *acmp) { dev_info_t *dip = acmp->acm_dip; usbsacm_pm_t *pm; uint_t pwr_states; usb_dev_descr_t *dev_descr; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_create_pm_components: "); if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_create_pm_components: failed"); return (USB_SUCCESS); } pm = acmp->acm_pm = (usbsacm_pm_t *)kmem_zalloc(sizeof (usbsacm_pm_t), KM_SLEEP); pm->pm_pwr_states = (uint8_t)pwr_states; pm->pm_cur_power = USB_DEV_OS_FULL_PWR; /* * Qualcomm CDMA card won't response the following control commands * after receive USB_REMOTE_WAKEUP_ENABLE. So we just set * pm_wakeup_enable to 0 for this specific device. */ dev_descr = acmp->acm_dev_data->dev_descr; if (dev_descr->idVendor == 0x5c6 && dev_descr->idProduct == 0x3100) { pm->pm_wakeup_enabled = 0; } else { pm->pm_wakeup_enabled = (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS); } (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); return (USB_SUCCESS); } /* * usbsacm_destroy_pm_components: * destroy PM components */ static void usbsacm_destroy_pm_components(usbsacm_state_t *acmp) { usbsacm_pm_t *pm = acmp->acm_pm; dev_info_t *dip = acmp->acm_dip; int rval; USB_DPRINTF_L4(PRINT_MASK_CLOSE, acmp->acm_lh, "usbsacm_destroy_pm_components: "); if (acmp->acm_dev_state != USB_DEV_DISCONNECTED) { if (pm->pm_wakeup_enabled) { rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); if (rval != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_destroy_pm_components: " "raising power failed (%d)", rval); } rval = usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_DISABLE); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_destroy_pm_components: " "disable remote wakeup failed (%d)", rval); } } (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF); } kmem_free((caddr_t)pm, sizeof (usbsacm_pm_t)); acmp->acm_pm = NULL; } /* * usbsacm_pm_set_busy: * mark device busy and raise power */ static void usbsacm_pm_set_busy(usbsacm_state_t *acmp) { usbsacm_pm_t *pm = acmp->acm_pm; dev_info_t *dip = acmp->acm_dip; int rval; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pm_set_busy: pm = 0x%p", (void *)pm); if (pm == NULL) { return; } mutex_enter(&acmp->acm_mutex); /* if already marked busy, just increment the counter */ if (pm->pm_busy_cnt++ > 0) { mutex_exit(&acmp->acm_mutex); return; } (void) pm_busy_component(dip, 0); if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) { mutex_exit(&acmp->acm_mutex); return; } /* need to raise power */ pm->pm_raise_power = B_TRUE; mutex_exit(&acmp->acm_mutex); rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); if (rval != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pm_set_busy: raising power failed"); } mutex_enter(&acmp->acm_mutex); pm->pm_raise_power = B_FALSE; mutex_exit(&acmp->acm_mutex); } /* * usbsacm_pm_set_idle: * mark device idle */ static void usbsacm_pm_set_idle(usbsacm_state_t *acmp) { usbsacm_pm_t *pm = acmp->acm_pm; dev_info_t *dip = acmp->acm_dip; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pm_set_idle: "); if (pm == NULL) { return; } /* * if more ports use the device, do not mark as yet */ mutex_enter(&acmp->acm_mutex); if (--pm->pm_busy_cnt > 0) { mutex_exit(&acmp->acm_mutex); return; } if (pm) { (void) pm_idle_component(dip, 0); } mutex_exit(&acmp->acm_mutex); } /* * usbsacm_pwrlvl0: * Functions to handle power transition for OS levels 0 -> 3 * The same level as OS state, different from USB state */ static int usbsacm_pwrlvl0(usbsacm_state_t *acmp) { int rval; int i; usbsacm_port_t *cur_port = acmp->acm_ports; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pwrlvl0: "); switch (acmp->acm_dev_state) { case USB_DEV_ONLINE: /* issue USB D3 command to the device */ rval = usb_set_device_pwrlvl3(acmp->acm_dip); ASSERT(rval == USB_SUCCESS); if (cur_port != NULL) { for (i = 0; i < acmp->acm_port_cnt; i++) { cur_port = &acmp->acm_ports[i]; if (cur_port->acm_intr_ph != NULL && cur_port->acm_port_state != USBSACM_PORT_CLOSED) { mutex_exit(&acmp->acm_mutex); usb_pipe_stop_intr_polling( cur_port->acm_intr_ph, USB_FLAGS_SLEEP); mutex_enter(&acmp->acm_mutex); mutex_enter(&cur_port->acm_port_mutex); cur_port->acm_intr_state = USBSACM_PIPE_IDLE; mutex_exit(&cur_port->acm_port_mutex); } } } acmp->acm_dev_state = USB_DEV_PWRED_DOWN; acmp->acm_pm->pm_cur_power = USB_DEV_OS_PWR_OFF; /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* allow a disconnect/cpr'ed device to go to lower power */ return (USB_SUCCESS); case USB_DEV_PWRED_DOWN: default: USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pwrlvl0: illegal device state"); return (USB_FAILURE); } } /* * usbsacm_pwrlvl1: * Functions to handle power transition for OS levels 1 -> 2 */ static int usbsacm_pwrlvl1(usbsacm_state_t *acmp) { /* issue USB D2 command to the device */ (void) usb_set_device_pwrlvl2(acmp->acm_dip); return (USB_FAILURE); } /* * usbsacm_pwrlvl2: * Functions to handle power transition for OS levels 2 -> 1 */ static int usbsacm_pwrlvl2(usbsacm_state_t *acmp) { /* issue USB D1 command to the device */ (void) usb_set_device_pwrlvl1(acmp->acm_dip); return (USB_FAILURE); } /* * usbsacm_pwrlvl3: * Functions to handle power transition for OS levels 3 -> 0 * The same level as OS state, different from USB state */ static int usbsacm_pwrlvl3(usbsacm_state_t *acmp) { int rval; int i; usbsacm_port_t *cur_port = acmp->acm_ports; USB_DPRINTF_L4(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pwrlvl3: "); switch (acmp->acm_dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ rval = usb_set_device_pwrlvl0(acmp->acm_dip); ASSERT(rval == USB_SUCCESS); if (cur_port != NULL) { for (i = 0; i < acmp->acm_port_cnt; i++) { cur_port = &acmp->acm_ports[i]; if (cur_port->acm_intr_ph != NULL && cur_port->acm_port_state != USBSACM_PORT_CLOSED) { mutex_exit(&acmp->acm_mutex); usbsacm_pipe_start_polling(cur_port); mutex_enter(&acmp->acm_mutex); } } } acmp->acm_dev_state = USB_DEV_ONLINE; acmp->acm_pm->pm_cur_power = USB_DEV_OS_FULL_PWR; /* FALLTHRU */ case USB_DEV_ONLINE: /* we are already in full power */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: return (USB_SUCCESS); default: USB_DPRINTF_L2(PRINT_MASK_PM, acmp->acm_lh, "usbsacm_pwrlvl3: illegal device state"); return (USB_FAILURE); } } /* * usbsacm_pipe_start_polling: * start polling on the interrupt pipe */ static void usbsacm_pipe_start_polling(usbsacm_port_t *acm_port) { usb_intr_req_t *intr; int rval; usbsacm_state_t *acmp = acm_port->acm_device; USB_DPRINTF_L4(PRINT_MASK_ATTA, acmp->acm_lh, "usbsacm_pipe_start_polling: "); if (acm_port->acm_intr_ph == NULL) { return; } intr = usb_alloc_intr_req(acmp->acm_dip, 0, USB_FLAGS_SLEEP); /* * If it is in interrupt context, usb_alloc_intr_req will return NULL if * called with SLEEP flag. */ if (!intr) { USB_DPRINTF_L2(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_pipe_start_polling: alloc req failed."); return; } /* initialize the interrupt request. */ intr->intr_attributes = USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING; mutex_enter(&acm_port->acm_port_mutex); intr->intr_len = acm_port->acm_intr_ep_descr.wMaxPacketSize; mutex_exit(&acm_port->acm_port_mutex); intr->intr_client_private = (usb_opaque_t)acm_port; intr->intr_cb = usbsacm_intr_cb; intr->intr_exc_cb = usbsacm_intr_ex_cb; rval = usb_pipe_intr_xfer(acm_port->acm_intr_ph, intr, USB_FLAGS_SLEEP); mutex_enter(&acm_port->acm_port_mutex); if (rval == USB_SUCCESS) { acm_port->acm_intr_state = USBSACM_PIPE_BUSY; } else { usb_free_intr_req(intr); acm_port->acm_intr_state = USBSACM_PIPE_IDLE; USB_DPRINTF_L3(PRINT_MASK_OPEN, acmp->acm_lh, "usbsacm_pipe_start_polling: failed (%d)", rval); } mutex_exit(&acm_port->acm_port_mutex); } /* * usbsacm_intr_cb: * interrupt pipe normal callback */ /*ARGSUSED*/ static void usbsacm_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req) { usbsacm_port_t *acm_port = (usbsacm_port_t *)req->intr_client_private; usbsacm_state_t *acmp = acm_port->acm_device; mblk_t *data = req->intr_data; int data_len; USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_intr_cb: "); data_len = (data) ? MBLKL(data) : 0; /* check data length */ if (data_len < 8) { USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_intr_cb: %d packet too short", data_len); usb_free_intr_req(req); return; } req->intr_data = NULL; usb_free_intr_req(req); mutex_enter(&acm_port->acm_port_mutex); /* parse interrupt data. */ usbsacm_parse_intr_data(acm_port, data); mutex_exit(&acm_port->acm_port_mutex); } /* * usbsacm_intr_ex_cb: * interrupt pipe exception callback */ /*ARGSUSED*/ static void usbsacm_intr_ex_cb(usb_pipe_handle_t ph, usb_intr_req_t *req) { usbsacm_port_t *acm_port = (usbsacm_port_t *)req->intr_client_private; usbsacm_state_t *acmp = acm_port->acm_device; usb_cr_t cr = req->intr_completion_reason; USB_DPRINTF_L4(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_intr_ex_cb: "); usb_free_intr_req(req); /* * If completion reason isn't USB_CR_PIPE_CLOSING and * USB_CR_STOPPED_POLLING, restart polling. */ if ((cr != USB_CR_PIPE_CLOSING) && (cr != USB_CR_STOPPED_POLLING)) { mutex_enter(&acmp->acm_mutex); if (acmp->acm_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_intr_ex_cb: state = %d", acmp->acm_dev_state); mutex_exit(&acmp->acm_mutex); return; } mutex_exit(&acmp->acm_mutex); usbsacm_pipe_start_polling(acm_port); } } /* * usbsacm_parse_intr_data: * Parse data received from interrupt callback */ static void usbsacm_parse_intr_data(usbsacm_port_t *acm_port, mblk_t *data) { usbsacm_state_t *acmp = acm_port->acm_device; uint8_t bmRequestType; uint8_t bNotification; uint16_t wValue; uint16_t wLength; uint16_t wData; USB_DPRINTF_L4(PRINT_MASK_ALL, acmp->acm_lh, "usbsacm_parse_intr_data: "); bmRequestType = data->b_rptr[0]; bNotification = data->b_rptr[1]; /* * If Notification type is NETWORK_CONNECTION, wValue is 0 or 1, * mLength is 0. If Notification type is SERIAL_TYPE, mValue is 0, * mLength is 2. So we directly get the value from the byte. */ wValue = data->b_rptr[2]; wLength = data->b_rptr[6]; if (bmRequestType != USB_CDC_NOTIFICATION_REQUEST_TYPE) { USB_DPRINTF_L2(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: unknown request type - 0x%x", bmRequestType); freemsg(data); return; } /* * Check the return value of device */ switch (bNotification) { case USB_CDC_NOTIFICATION_NETWORK_CONNECTION: USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: %s network!", wValue ? "connected to" :"disconnected from"); break; case USB_CDC_NOTIFICATION_RESPONSE_AVAILABLE: USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: A response is a available."); break; case USB_CDC_NOTIFICATION_SERIAL_STATE: /* check the parameter's length. */ if (wLength != 2) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: error data length."); } else { /* * The Data field is a bitmapped value that contains * the current state of carrier detect, transmission * carrier, break, ring signal and device overrun * error. */ wData = data->b_rptr[8]; /* * Check the serial state of the current port. */ if (wData & USB_CDC_ACM_CONTROL_DCD) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "receiver carrier is set."); } if (wData & USB_CDC_ACM_CONTROL_DSR) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "transmission carrier is set."); acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_DSR; } if (wData & USB_CDC_ACM_CONTROL_BREAK) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "break detection mechanism is set."); } if (wData & USB_CDC_ACM_CONTROL_RNG) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "ring signal detection is set."); acm_port->acm_mctlin |= USB_CDC_ACM_CONTROL_RNG; } if (wData & USB_CDC_ACM_CONTROL_FRAMING) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "A framing error has occurred."); } if (wData & USB_CDC_ACM_CONTROL_PARITY) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "A parity error has occurred."); } if (wData & USB_CDC_ACM_CONTROL_OVERRUN) { USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: " "Received data has been discarded " "due to overrun."); } } break; default: USB_DPRINTF_L3(PRINT_MASK_CB, acmp->acm_lh, "usbsacm_parse_intr_data: unknown notification - 0x%x!", bNotification); break; } freemsg(data); }