/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2019 Joyent, Inc. */ /* * USBA: Solaris USB Architecture support * * Utility functions */ #define USBA_FRAMEWORK #include #include #include extern void usba_free_evdata(usba_evdata_t *); static mblk_t *usba_get_cfg_cloud(dev_info_t *, usb_pipe_handle_t, int); /* local functions */ static int usba_sync_set_cfg(dev_info_t *, usba_ph_impl_t *, usba_pipe_async_req_t *, usb_flags_t); static int usba_sync_set_alt_if(dev_info_t *, usba_ph_impl_t *, usba_pipe_async_req_t *, usb_flags_t); static int usba_sync_clear_feature(dev_info_t *, usba_ph_impl_t *, usba_pipe_async_req_t *, usb_flags_t); /* * Wrapper functions returning parsed standard descriptors without * getting the config cloud first but by just providing the dip. * * The client can easily retrieve the device and config descriptor from * the usb registration and no separate functions are provided * * These functions return failure if the full descriptor can not be * retrieved. These functions will not access the device. * The caller must allocate the buffer. */ /* * usb_get_if_descr: * Function to get the cooked interface descriptor * This function will not access the device. * * Arguments: * dip - pointer to devinfo of the client * if_index - interface index * alt_setting - alt interface setting * descr - pointer to user allocated interface descr * * Return Values: * USB_SUCCESS - descriptor is valid * USB_FAILURE - full descriptor could not be retrieved * USB_* - refer to usbai.h */ int usb_get_if_descr(dev_info_t *dip, uint_t if_index, uint_t alt_setting, usb_if_descr_t *descr) { uchar_t *usb_cfg; /* buf for config descriptor */ size_t size, cfg_length; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_if_descr: %s, index=0x%x, alt#=0x%x", ddi_node_name(dip), if_index, alt_setting); if ((dip == NULL) || (descr == NULL)) { return (USB_INVALID_ARGS); } usb_cfg = usb_get_raw_cfg_data(dip, &cfg_length); size = usb_parse_if_descr(usb_cfg, cfg_length, if_index, /* interface index */ alt_setting, /* alt interface index */ descr, USB_IF_DESCR_SIZE); if (size != USB_IF_DESCR_SIZE) { USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle, "parsing interface: size (%lu) != USB_IF_DESCR_SIZE (%d)", size, USB_IF_DESCR_SIZE); return (USB_FAILURE); } return (USB_SUCCESS); } /* * usb_get_ep_descr: * Function to get the cooked endpoint descriptor * This function will not access the device. * * Arguments: * dip - pointer to devinfo of the client * if_index - interface index * alt_setting - alternate interface setting * endpoint_index - endpoint index * descr - pointer to user allocated interface descr * * Return Values: * USB_SUCCESS - descriptor is valid * USB_FAILURE - full descriptor could not be retrieved * USB_* - refer to usbai.h */ int usb_get_ep_descr(dev_info_t *dip, uint_t if_index, uint_t alt_setting, uint_t endpoint_index, usb_ep_descr_t *descr) { uchar_t *usb_cfg; /* buf for config descriptor */ size_t size, cfg_length; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_ep_descr: %s, index=0x%x, alt#=0x%x", ddi_node_name(dip), if_index, alt_setting); if ((dip == NULL) || (descr == NULL)) { return (USB_INVALID_ARGS); } usb_cfg = usb_get_raw_cfg_data(dip, &cfg_length); size = usb_parse_ep_descr(usb_cfg, cfg_length, if_index, /* interface index */ alt_setting, /* alt interface index */ endpoint_index, /* ep index */ descr, USB_EP_DESCR_SIZE); if (size != USB_EP_DESCR_SIZE) { USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle, "parsing endpoint: size (%lu) != USB_EP_DESCR_SIZE (%d)", size, USB_EP_DESCR_SIZE); return (USB_FAILURE); } return (USB_SUCCESS); } /* * usb_lookup_ep_data: * usb_get_ep_data (deprecated): * Function to get specific endpoint descriptor data * This function will not access the device. * * Arguments: * dip - pointer to dev info * usb_client_dev_data_t - pointer to registration data * interface - requested interface * alternate - requested alternate * skip - how many to skip * type - endpoint type * direction - endpoint direction or USB_DIR_DONT_CARE * * Return Values: * NULL or an endpoint descriptor pointer */ usb_ep_data_t * usb_lookup_ep_data(dev_info_t *dip, usb_client_dev_data_t *dev_datap, uint_t interface, uint_t alternate, uint_t skip, uint_t type, uint_t dir) { usb_alt_if_data_t *altif_data; int i; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_lookup_ep_data: " "if=%d alt=%d skip=%d type=%d dir=%d", interface, alternate, skip, type, dir); if ((dip == NULL) || (dev_datap == NULL)) { return (NULL); } altif_data = &dev_datap->dev_curr_cfg-> cfg_if[interface].if_alt[alternate]; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "altif=0x%p n_ep=%d", (void *)altif_data, altif_data->altif_n_ep); for (i = 0; i < altif_data->altif_n_ep; i++) { usb_ep_descr_t *ept = &altif_data->altif_ep[i].ep_descr; uint8_t ept_type = ept->bmAttributes & USB_EP_ATTR_MASK; uint8_t ept_dir = ept->bEndpointAddress & USB_EP_DIR_MASK; if (ept->bLength == 0) { continue; } if ((ept_type == type) && ((type == USB_EP_ATTR_CONTROL) || (dir == ept_dir))) { if (skip-- == 0) { USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_ep_data: data=0x%p", (void *)&altif_data->altif_ep[i]); return (&altif_data->altif_ep[i]); } } } USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_ep_data: returning NULL"); return (NULL); } /*ARGSUSED*/ usb_ep_data_t * usb_get_ep_data(dev_info_t *dip, usb_client_dev_data_t *dev_datap, uint_t interface, uint_t alternate, uint_t type, uint_t dir) { return (usb_lookup_ep_data(dip, dev_datap, interface, alternate, 0, type, dir)); } /* * usb_get_string_descr: * Function to read the string descriptor * This function will access the device and block. * * Arguments: * dip - pointer to devinfo of the client * langid - LANGID to read different LOCALEs * index - index to the string * buf - user provided buffer for string descriptor * buflen - user provided length of the buffer * * Return Values: * USB_SUCCESS - descriptor is valid * USB_FAILURE - full descriptor could not be retrieved * USB_* - refer to usbai.h */ int usb_get_string_descr(dev_info_t *dip, uint16_t langid, uint8_t index, char *buf, size_t buflen) { mblk_t *data = NULL; uint16_t length; int rval; usb_cr_t completion_reason; size_t len; usb_cb_flags_t cb_flags; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_string_descr: %s, langid=0x%x index=0x%x", ddi_node_name(dip), langid, index); if ((dip == NULL) || (buf == NULL) || (buflen == 0) || (index == 0)) { return (USB_INVALID_ARGS); } /* * determine the length of the descriptor */ rval = usb_pipe_sync_ctrl_xfer(dip, usba_get_dflt_pipe_handle(dip), USB_DEV_REQ_DEV_TO_HOST, USB_REQ_GET_DESCR, (USB_DESCR_TYPE_STRING << 8) | (index & 0xff), langid, 4, &data, USB_ATTRS_SHORT_XFER_OK, &completion_reason, &cb_flags, USB_FLAGS_SLEEP); if (rval != USB_SUCCESS) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d cr=%d", rval, completion_reason); goto done; } if (MBLKL(data) == 0) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "0 bytes received"); goto done; } ASSERT(data); length = *(data->b_rptr); freemsg(data); data = NULL; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cr=%d, length=%d", rval, completion_reason, length); /* * if length is zero the next control request may fail. * the HCD may not support a zero length control request * and return an mblk_t which is NULL along with rval * being USB_SUCCESS and "cr" being USB_CR_OK */ if (length < 2) { rval = USB_FAILURE; goto done; } rval = usb_pipe_sync_ctrl_xfer(dip, usba_get_dflt_pipe_handle(dip), USB_DEV_REQ_DEV_TO_HOST, USB_REQ_GET_DESCR, (USB_DESCR_TYPE_STRING << 8) | (index & 0xff), langid, length, &data, USB_ATTRS_SHORT_XFER_OK, &completion_reason, &cb_flags, USB_FLAGS_SLEEP); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cb_flags=%d, cr=%d", rval, cb_flags, completion_reason); if ((data == NULL) || (rval != USB_SUCCESS)) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "failed to get string descriptor (rval=%d cr=%d)", rval, completion_reason); goto done; } if ((length = MBLKL(data)) != 0) { len = usba_ascii_string_descr(data->b_rptr, length, buf, buflen); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "buf=%s buflen=%lu", buf, len); ASSERT(len <= buflen); } else { rval = USB_FAILURE; } done: freemsg(data); return (rval); } /* * usb_get_dev_descr: * utility function to get device descriptor from usba_device * * Arguments: * dip - pointer to devinfo of the client * * Return Values: * usb_dev_descr - device descriptor or NULL */ usb_dev_descr_t * usb_get_dev_descr(dev_info_t *dip) { usba_device_t *usba_device; usb_dev_descr_t *usb_dev_descr = NULL; if (dip) { USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_dev_descr: %s", ddi_node_name(dip)); usba_device = usba_get_usba_device(dip); mutex_enter(&usba_device->usb_mutex); usb_dev_descr = usba_device->usb_dev_descr; mutex_exit(&usba_device->usb_mutex); } return (usb_dev_descr); } /* * usb_get_raw_cfg_data: * utility function to get raw config descriptor from usba_device * * Arguments: * dip - pointer to devinfo of the client * length - pointer to copy the cfg length * * Return Values: * usb_cfg - raw config descriptor */ uchar_t * usb_get_raw_cfg_data(dev_info_t *dip, size_t *length) { usba_device_t *usba_device; uchar_t *usb_cfg; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_raw_cfg_data: %s", ddi_node_name(dip)); if ((dip == NULL) || (length == NULL)) { return (NULL); } usba_device = usba_get_usba_device(dip); mutex_enter(&usba_device->usb_mutex); usb_cfg = usba_device->usb_cfg; *length = usba_device->usb_cfg_length; mutex_exit(&usba_device->usb_mutex); return (usb_cfg); } /* * usb_get_addr: * utility function to return current usb address, mostly * for debugging purposes * * Arguments: * dip - pointer to devinfo of the client * * Return Values: * address - USB Device Address */ int usb_get_addr(dev_info_t *dip) { int address = 0; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_addr: %s", ddi_node_name(dip)); if (dip) { usba_device_t *usba_device = usba_get_usba_device(dip); mutex_enter(&usba_device->usb_mutex); address = usba_device->usb_addr; mutex_exit(&usba_device->usb_mutex); } return (address); } /* * usb_set_cfg(): * set configuration, use with caution (issues USB_REQ_SET_CONFIG) * Changing configuration will fail if pipes are still open or when * invoked from a driver bound to an interface on a composite device. * * This function will access the device and block * * Arguments: * dip - pointer to devinfo of the client * cfg_index - config index * cfg_value - config value to be set * flags - USB_FLAGS_SLEEP: * wait for completion * cb - if USB_FLAGS_SLEEP has not been specified * this callback function will be called on * completion. This callback may be NULL * and no notification of completion will then * be provided. * cb_arg - 2nd argument to callback function. * * Return Values: * USB_SUCCESS: - new configuration was set * USB_FAILURE: - new configuration could not be set * USB_BUSY: - some pipes were open or there were children * USB_* - refer to usbai.h */ int usb_set_cfg(dev_info_t *dip, uint_t cfg_index, usb_flags_t usb_flags, void (*cb)( usb_pipe_handle_t ph, usb_opaque_t arg, int rval, usb_cb_flags_t flags), usb_opaque_t cb_arg) { usb_pipe_handle_t ph; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_set_cfg: %s%d, cfg_index = 0x%x, uf = 0x%x", ddi_driver_name(dip), ddi_get_instance(dip), cfg_index, usb_flags); if (dip == NULL) { return (USB_INVALID_ARGS); } if ((usb_flags & USB_FLAGS_SLEEP) && servicing_interrupt()) { return (USB_INVALID_CONTEXT); } if (!usb_owns_device(dip)) { return (USB_INVALID_PERM); } ph = usba_get_dflt_pipe_handle(dip); if (usba_hold_ph_data(ph) == NULL) { return (USB_INVALID_PIPE); } return (usba_pipe_setup_func_call(dip, usba_sync_set_cfg, (usba_ph_impl_t *)ph, (usb_opaque_t)((uintptr_t)cfg_index), usb_flags, cb, cb_arg)); } static int usba_sync_set_cfg(dev_info_t *dip, usba_ph_impl_t *ph_impl, usba_pipe_async_req_t *request, usb_flags_t flags) { int rval; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; usba_device_t *usba_device; int i, ph_open_cnt; uint_t cfg_index = (uint_t)((uintptr_t)(request->arg)); size_t size; usb_cfg_descr_t confdescr; dev_info_t *pdip; usba_device = usba_get_usba_device(dip); /* * default pipe is still open * all other pipes should be closed */ for (ph_open_cnt = 0, i = 1; i < USBA_N_ENDPOINTS; i++) { if (usba_device->usb_ph_list[i].usba_ph_data) { ph_open_cnt++; break; } } if (ph_open_cnt || ddi_get_child(dip)) { usba_release_ph_data(ph_impl); return (USB_BUSY); } /* * check if the configuration meets the * power budget requirement */ if (usba_is_root_hub(dip)) { /* * root hub should never be multi-configured. * the code is here just to ensure */ usba_release_ph_data(ph_impl); return (USB_FAILURE); } pdip = ddi_get_parent(dip); /* * increase the power budget value back to the unconfigured * state to eliminate the influence of the old configuration * before checking the new configuration; but remember to * make a decrement before leaving this routine to restore * the power consumption state of the device no matter it * is in the new or old configuration */ usba_hubdi_incr_power_budget(pdip, usba_device); if ((usba_hubdi_check_power_budget(pdip, usba_device, cfg_index)) != USB_SUCCESS) { usba_hubdi_decr_power_budget(pdip, usba_device); usba_release_ph_data(ph_impl); return (USB_FAILURE); } size = usb_parse_cfg_descr(usba_device->usb_cfg_array[cfg_index], USB_CFG_DESCR_SIZE, &confdescr, USB_CFG_DESCR_SIZE); /* hubdi should ensure that this descriptor is correct */ ASSERT(size == USB_CFG_DESCR_SIZE); /* set the configuration */ rval = usb_pipe_sync_ctrl_xfer(dip, (usb_pipe_handle_t)ph_impl, USB_DEV_REQ_HOST_TO_DEV, USB_REQ_SET_CFG, confdescr.bConfigurationValue, 0, 0, NULL, 0, &completion_reason, &cb_flags, flags | USBA_FLAGS_PRIVILEGED | USB_FLAGS_SLEEP); if (rval == USB_SUCCESS) { mutex_enter(&usba_device->usb_mutex); usba_device->usb_cfg_value = confdescr.bConfigurationValue; usba_device->usb_active_cfg_ndx = cfg_index; usba_device->usb_cfg = usba_device->usb_cfg_array[cfg_index]; usba_device->usb_cfg_length = confdescr.wTotalLength; mutex_exit(&usba_device->usb_mutex); /* update the configuration property */ (void) ndi_prop_update_int(DDI_DEV_T_NONE, dip, "configuration#", usba_device->usb_cfg_value); } /* * usba_device->usb_cfg always stores current configuration * descriptor no matter SET_CFG request succeeded or not, * so usba_hubdi_decr_power_budget can be done regardless * of rval above */ usba_hubdi_decr_power_budget(pdip, usba_device); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cb_flags=%d, cr=%d", rval, cb_flags, completion_reason); usba_release_ph_data(ph_impl); return (rval); } /* * usb_get_cfg(): * get configuration value * * Arguments: * dip - pointer to devinfo of the client * cfg_value - current config value * flags - none, always blocks * * Return Values: * USB_SUCCESS: - config value was retrieved * USB_FAILURE: - config value could not be retrieved * USB_* - refer to usbai.h */ int usb_get_cfg(dev_info_t *dip, uint_t *cfgval, usb_flags_t flags) { int rval; usb_cr_t completion_reason; mblk_t *data = NULL; usb_cb_flags_t cb_flags; usb_pipe_handle_t ph; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_cfg: %s uf = 0x%x", ddi_node_name(dip), flags); if ((cfgval == NULL) || (dip == NULL)) { return (USB_INVALID_ARGS); } ph = usba_get_dflt_pipe_handle(dip); /* * get the cfg value */ rval = usb_pipe_sync_ctrl_xfer(dip, ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_RCPT_DEV, USB_REQ_GET_CFG, 0, 0, 1, /* returns one byte of data */ &data, 0, &completion_reason, &cb_flags, flags); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d cb_flags=%d cr=%d", rval, cb_flags, completion_reason); if ((rval == USB_SUCCESS) && data && (MBLKL(data) == 1)) { *cfgval = *(data->b_rptr); } else { *cfgval = 1; if (rval == USB_SUCCESS) { rval = USB_FAILURE; } } USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_cfg: %s cfgval=%d", ddi_node_name(dip), *cfgval); freemsg(data); return (rval); } /* * usb_get_current_cfgidx: * get current current config index */ uint_t usb_get_current_cfgidx(dev_info_t *dip) { usba_device_t *usba_device = usba_get_usba_device(dip); uint_t ndx; mutex_enter(&usba_device->usb_mutex); ndx = usba_device->usb_active_cfg_ndx; mutex_exit(&usba_device->usb_mutex); return (ndx); } /* * usb_get_if_number: * get usb interface number of current OS device node. * * Arguments: * dip - pointer to devinfo of the client * * Return Values: * USB_COMBINED_NODE if the driver is responsible for the entire * device and this dip doesn't correspond to a device node. * USB_DEVICE_NODE if the driver is responsible for the entire device * and this dip corresponds to a device node. * interface number: otherwise. */ int usb_get_if_number(dev_info_t *dip) { int interface_num; usba_device_t *usba_device = usba_get_usba_device(dip); usb_dev_descr_t *usb_dev_descr; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_if_number: dip = 0x%p", (void *)dip); /* not quite right but we can't return a negative return value */ if (dip == NULL) { return (0); } if (usba_device) { usb_dev_descr = usba_device->usb_dev_descr; } else { return (0); } interface_num = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "interface", USB_COMBINED_NODE); if (interface_num == USB_COMBINED_NODE) { if (!(((usb_dev_descr->bDeviceClass == USB_CLASS_HUB) || (usb_dev_descr->bDeviceClass == 0)) && (usba_device->usb_n_cfgs == 1) && (usba_device->usb_n_ifs == 1))) { interface_num = USB_DEVICE_NODE; } } return (interface_num); } boolean_t usb_owns_device(dev_info_t *dip) { int interface_num = usb_get_if_number(dip); return (interface_num < 0 ? B_TRUE : B_FALSE); } /* check whether the interface is in this interface association */ boolean_t usba_check_if_in_ia(dev_info_t *dip, int n_if) { int first_if, if_count; first_if = usb_get_if_number(dip); if_count = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "interface-count", -1); if_count += first_if; return ((n_if >= first_if && n_if < if_count) ? B_TRUE : B_FALSE); } uint8_t usba_get_ifno(dev_info_t *dip) { int interface_num = usb_get_if_number(dip); return (uint8_t)(interface_num < 0 ? 0 : interface_num); } /* * usb_set_alt_if: * set the alternate interface number. Issues USB_REQ_SET_IF * This function will access the device * * Arguments: * dip - pointer to devinfo of the client * if_number - interface number * alt_number - alternate interface number * flags - USB_FLAGS_SLEEP: * wait for completion * cb - if USB_FLAGS_SLEEP has not been specified * this callback function will be called on * completion. This callback may be NULL * and no notification of completion will then * be provided. * cb_arg - 2nd argument to callback function. * * * return values: * USB_SUCCESS - alternate was set * USB_FAILURE - alternate could not be set because pipes * were still open or some access error occurred * USB_* - refer to usbai.h * * Note: * we can't easily check if all pipes to endpoints for this interface * are closed since we don't have a map of which endpoints belong * to which interface. If we had this map, we would need to update * this on each alternative or configuration switch */ int usb_set_alt_if(dev_info_t *dip, uint_t interface, uint_t alt_number, usb_flags_t usb_flags, void (*cb)( usb_pipe_handle_t ph, usb_opaque_t arg, int rval, usb_cb_flags_t flags), usb_opaque_t cb_arg) { usb_pipe_handle_t ph; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_set_alt_if: %s%d, if = %d alt = %d, uf = 0x%x", ddi_driver_name(dip), ddi_get_instance(dip), interface, alt_number, usb_flags); if (dip == NULL) { return (USB_INVALID_ARGS); } if ((usb_flags & USB_FLAGS_SLEEP) && servicing_interrupt()) { return (USB_INVALID_CONTEXT); } ph = usba_get_dflt_pipe_handle(dip); if (usba_hold_ph_data(ph) == NULL) { return (USB_INVALID_PIPE); } return (usba_pipe_setup_func_call(dip, usba_sync_set_alt_if, (usba_ph_impl_t *)ph, (usb_opaque_t)((uintptr_t)((interface << 8) | alt_number)), usb_flags, cb, cb_arg)); } static int usba_sync_set_alt_if(dev_info_t *dip, usba_ph_impl_t *ph_impl, usba_pipe_async_req_t *request, usb_flags_t flags) { int rval; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; usb_opaque_t arg = request->arg; int interface = ((uintptr_t)arg >> 8) & 0xff; int alt_number = (uintptr_t)arg & 0xff; usba_pipe_handle_data_t *ph_data = usba_get_ph_data( (usb_pipe_handle_t)ph_impl); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_set_alt_if: %s, interface#=0x%x, alt#=0x%x, " "uf=0x%x", ddi_node_name(dip), interface, alt_number, flags); /* if we don't own the device, we must own the interface or ia */ if (!usb_owns_device(dip) && !usba_check_if_in_ia(dip, interface) && (interface != usb_get_if_number(dip))) { usba_release_ph_data(ph_data->p_ph_impl); return (USB_INVALID_PERM); } /* set the alternate setting */ rval = usb_pipe_sync_ctrl_xfer(dip, usba_get_dflt_pipe_handle(dip), USB_DEV_REQ_HOST_TO_DEV | USB_DEV_REQ_RCPT_IF, USB_REQ_SET_IF, alt_number, interface, 0, NULL, 0, &completion_reason, &cb_flags, flags | USB_FLAGS_SLEEP); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cb_flags=%d, cr=%d", rval, cb_flags, completion_reason); usba_release_ph_data(ph_data->p_ph_impl); return (rval); } /* * usb_get_alt_if: * get the alternate interface number. Issues USB_REQ_GET_IF * This function will access the device and block * * Arguments: * dip - pointer to devinfo of the client * if_number - interface number * alt_number - alternate interface number * flags - none but USB_FLAGS_SLEEP may be passed * * return values: * USB_SUCCESS: alternate was set * USB_FAILURE: alternate could not be set because pipes * were still open or some access error occurred */ int usb_get_alt_if(dev_info_t *dip, uint_t if_number, uint_t *alt_number, usb_flags_t flags) { int rval; usb_cr_t completion_reason; mblk_t *data = NULL; usb_cb_flags_t cb_flags; usb_pipe_handle_t ph; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_alt_if: %s, interface# = 0x%x, altp = 0x%p, " "uf = 0x%x", ddi_node_name(dip), if_number, (void *)alt_number, flags); if ((alt_number == NULL) || (dip == NULL)) { return (USB_INVALID_ARGS); } ph = usba_get_dflt_pipe_handle(dip); /* * get the alternate setting */ rval = usb_pipe_sync_ctrl_xfer(dip, ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_RCPT_IF, USB_REQ_GET_IF, 0, if_number, 1, /* returns one byte of data */ &data, 0, &completion_reason, &cb_flags, flags); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d cb_flags=%d cr=%d", rval, cb_flags, completion_reason); if ((rval == USB_SUCCESS) && data && (MBLKL(data) == 1)) { *alt_number = *(data->b_rptr); } else { *alt_number = 0; if (rval == USB_SUCCESS) { rval = USB_FAILURE; } } freemsg(data); return (rval); } /* * usba_get_cfg_cloud: * Get descriptor cloud for a given configuration. * * Arguments: * dip - pointer to devinfo of the client * default_ph - default pipe handle * cfg - which configuration to retrieve raw cloud of * * Returns: * on success: mblock containing the raw data. Caller must free. * on failure: NULL */ static mblk_t * usba_get_cfg_cloud(dev_info_t *dip, usb_pipe_handle_t default_ph, int cfg) { usb_cr_t completion_reason; usb_cb_flags_t cb_flags; usb_cfg_descr_t cfg_descr; mblk_t *pdata = NULL; if (usb_pipe_sync_ctrl_xfer(dip, default_ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD, USB_REQ_GET_DESCR, USB_DESCR_TYPE_SETUP_CFG | cfg, 0, USB_CFG_DESCR_SIZE, &pdata, 0, &completion_reason, &cb_flags, 0) != USB_SUCCESS) { freemsg(pdata); return (NULL); } (void) usb_parse_cfg_descr(pdata->b_rptr, MBLKL(pdata), &cfg_descr, USB_CFG_DESCR_SIZE); freemsg(pdata); pdata = NULL; if (usb_pipe_sync_ctrl_xfer(dip, default_ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD, USB_REQ_GET_DESCR, USB_DESCR_TYPE_SETUP_CFG | cfg, 0, cfg_descr.wTotalLength, &pdata, 0, &completion_reason, &cb_flags, 0) != USB_SUCCESS) { freemsg(pdata); return (NULL); } return (pdata); } /* * usb_check_same_device: * Check if the device connected to the port is the same as * the previous device that was in the port. The previous device is * represented by the dip on record for the port. Print a message * if the device is different. If device_string arg is not NULL, it is * included in the message. Can block. * * Arguments: * dip - pointer to devinfo of the client * log_handle - handle to which messages are logged * log_level - one of USB_LOG_* * log_mask - logging mask * check_mask - one mask containing things to check: * USB_CHK_BASIC: empty mask; * these checks are always done. * USB_CHK_VIDPID: * check vid, pid only. * USB_CHK_SERIAL: check match on device * serial number. * USB_CHK_CFG: check all raw config * clouds for a match. * NOTE: descr length and content always checked * device_string - Device string to appear in error message * * return values: * USB_SUCCESS: same device * USB_INVALID_VERSION not same device * USB_FAILURE: Failure processing request * USB_INVALID_ARG: dip is invalid */ int usb_check_same_device(dev_info_t *dip, usb_log_handle_t log_handle, int log_level, int log_mask, uint_t check_mask, char *device_string) { usb_dev_descr_t usb_dev_descr; usba_device_t *usba_device; mblk_t *pdata = NULL; uint16_t length; int rval; char *buf; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; boolean_t match = B_TRUE; usb_pipe_handle_t def_ph; if (dip == NULL) { return (USB_INVALID_ARGS); } usba_device = usba_get_usba_device(dip); length = usba_device->usb_dev_descr->bLength; def_ph = usba_get_dflt_pipe_handle(dip); ASSERT(def_ph); /* get the "new" device descriptor */ rval = usb_pipe_sync_ctrl_xfer(dip, def_ph, USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD, USB_REQ_GET_DESCR, /* bRequest */ USB_DESCR_TYPE_SETUP_DEV, /* wValue */ 0, /* wIndex */ length, /* wLength */ &pdata, 0, &completion_reason, &cb_flags, USB_FLAGS_SLEEP); if (rval != USB_SUCCESS) { if (!((completion_reason == USB_CR_DATA_OVERRUN) && (pdata))) { USB_DPRINTF_L3(DPRINT_MASK_USBA, usbai_log_handle, "getting device descriptor failed (%d)", rval); freemsg(pdata); return (USB_FAILURE); } } ASSERT(pdata != NULL); (void) usb_parse_dev_descr(pdata->b_rptr, MBLKL(pdata), &usb_dev_descr, sizeof (usb_dev_descr_t)); freemsg(pdata); pdata = NULL; /* Always check the device descriptor length. */ if (usb_dev_descr.bLength != length) { match = B_FALSE; } if ((match == B_TRUE) && (check_mask & USB_CHK_VIDPID)) { match = (usba_device->usb_dev_descr->idVendor == usb_dev_descr.idVendor) && (usba_device->usb_dev_descr->idProduct == usb_dev_descr.idProduct); } else if (bcmp((char *)usba_device->usb_dev_descr, (char *)&usb_dev_descr, length) != 0) { match = B_FALSE; } /* if requested & this device has a serial number check and compare */ if ((match == B_TRUE) && ((check_mask & USB_CHK_SERIAL) != 0) && (usba_device->usb_serialno_str != NULL)) { buf = kmem_alloc(USB_MAXSTRINGLEN, KM_SLEEP); if (usb_get_string_descr(dip, USB_LANG_ID, usb_dev_descr.iSerialNumber, buf, USB_MAXSTRINGLEN) == USB_SUCCESS) { match = (strcmp(buf, usba_device->usb_serialno_str) == 0); } kmem_free(buf, USB_MAXSTRINGLEN); } if ((match == B_TRUE) && (check_mask & USB_CHK_CFG)) { uint8_t num_cfgs = usb_dev_descr.bNumConfigurations; uint8_t cfg; mblk_t *cloud; for (cfg = 0; cfg < num_cfgs; cfg++) { cloud = usba_get_cfg_cloud(dip, def_ph, cfg); if (cloud == NULL) { USB_DPRINTF_L3(DPRINT_MASK_USBA, usbai_log_handle, "Could not retrieve config cloud for " "comparison"); break; } if (bcmp((char *)cloud->b_rptr, usba_device->usb_cfg_array[cfg], MBLKL(cloud)) != 0) { freemsg(cloud); break; } freemsg(cloud); } if (cfg != num_cfgs) { match = B_FALSE; } } if (match == B_FALSE) { boolean_t allocated_here = (device_string == NULL); if (allocated_here) { device_string = kmem_zalloc(USB_MAXSTRINGLEN, USB_FLAGS_SLEEP); (void) usba_get_mfg_prod_sn_str(dip, device_string, USB_MAXSTRINGLEN); } if (device_string[0] != '\0') { (void) usb_log(log_handle, log_level, log_mask, "Cannot access %s. Please reconnect.", device_string); } else { (void) usb_log(log_handle, log_level, log_mask, "Device is not identical to the " "previous one this port.\n" "Please disconnect and reconnect"); } if (allocated_here) { kmem_free(device_string, USB_MAXSTRINGLEN); } return (USB_INVALID_VERSION); } return (USB_SUCCESS); } /* * usb_pipe_get_state: * Return the state of the pipe * * Arguments: * pipe_handle - pipe_handle pointer * pipe_state - pointer to copy pipe state to * flags: * not used other than to check context * * Return Values: * USB_SUCCESS - port state returned * USB_* - refer to usbai.h */ int usb_pipe_get_state(usb_pipe_handle_t pipe_handle, usb_pipe_state_t *pipe_state, usb_flags_t usb_flags) { usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle); USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle, "usb_pipe_get_state: ph_data=0x%p uf=0x%x", (void *)ph_data, usb_flags); if (pipe_state == NULL) { if (ph_data) { usba_release_ph_data(ph_data->p_ph_impl); } return (USB_INVALID_ARGS); } if (ph_data == NULL) { *pipe_state = USB_PIPE_STATE_CLOSED; return (USB_SUCCESS); } mutex_enter(&ph_data->p_mutex); *pipe_state = usba_get_ph_state(ph_data); mutex_exit(&ph_data->p_mutex); usba_release_ph_data(ph_data->p_ph_impl); return (USB_SUCCESS); } /* * usba_pipe_get_policy: * Return a pipe's policy * * Arguments: * pipe_handle - pipe_handle pointer * * Return Values: * On success: the pipe's policy * On failure: NULL */ usb_pipe_policy_t *usba_pipe_get_policy(usb_pipe_handle_t pipe_handle) { usb_pipe_policy_t *pp = NULL; usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle); if (ph_data) { pp = &ph_data->p_policy; usba_release_ph_data(ph_data->p_ph_impl); } return (pp); } /* * usb_ep_num: * Return the endpoint number for a given pipe handle * * Arguments: * pipe_handle - pipe_handle pointer * * Return Values: * endpoint number */ int usb_ep_num(usb_pipe_handle_t pipe_handle) { usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle); int ep_num; if (ph_data == NULL) { return (USB_INVALID_PIPE); } mutex_enter(&ph_data->p_mutex); ep_num = ph_data->p_ep.bEndpointAddress & USB_EP_NUM_MASK; mutex_exit(&ph_data->p_mutex); usba_release_ph_data(ph_data->p_ph_impl); return (ep_num); } /* * usb_get_status * Issues USB_REQ_GET_STATUS to device/endpoint/interface * and report in "status" arg. * * status reported for a "device" is * RemoteWakeup enabled * SelfPowered device? * * status reported for an "interface" is NONE. * status reported for an "endpoint" is * HALT set (device STALLED?) * * Arguments: * dip - pointer to devinfo of the client * ph - pipe handle * type - bmRequestType to be used * what - 0 for device, otherwise interface or ep number * status - user supplied pointer for storing the status * flags - USB_FLAGS_SLEEP (mandatory) * * Return Values: * valid usb_status_t or USB_FAILURE */ int usb_get_status(dev_info_t *dip, usb_pipe_handle_t ph, uint_t type, /* bmRequestType */ uint_t what, /* 0, interface, ept number */ uint16_t *status, usb_flags_t flags) { int rval; usb_cr_t completion_reason; mblk_t *data = NULL; usb_cb_flags_t cb_flags; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_get_status: type = 0x%x, what = 0x%x, uf = 0x%x", type, what, flags); if ((status == NULL) || (dip == NULL)) { return (USB_INVALID_ARGS); } if (ph == NULL) { return (USB_INVALID_PIPE); } type |= USB_DEV_REQ_DEV_TO_HOST; /* get the status */ rval = usb_pipe_sync_ctrl_xfer(dip, ph, type, USB_REQ_GET_STATUS, 0, what, USB_GET_STATUS_LEN, /* status is fixed 2 bytes long */ &data, 0, &completion_reason, &cb_flags, flags); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cb_flags=%d, cr=%d", rval, cb_flags, completion_reason); if ((rval == USB_SUCCESS) && data && (MBLKL(data) == USB_GET_STATUS_LEN)) { *status = (*(data->b_rptr + 1) << 8) | *(data->b_rptr); } else { *status = 0; if (rval == USB_SUCCESS) { rval = USB_FAILURE; } } freemsg(data); return (rval); } /* * usb_clear_feature: * Issue USB_REQ_CLEAR_FEATURE to endpoint/device/interface * * Arguments: * dip - pointer to devinfo of the client * ph - pipe handle pointer * type - bmRequestType to be used * feature - feature to be cleared * what - 0 for device, otherwise interface or ep number * flags - none (but will sleep) * * Return Values: * USB_SUCCESS - on doing a successful clear feature * USB_FAILURE - on failure * USB_* - refer to usbai.h */ int usb_clear_feature(dev_info_t *dip, usb_pipe_handle_t ph, uint_t type, /* bmRequestType */ uint_t feature, uint_t what, /* 0, interface, ept number */ usb_flags_t flags) { int rval; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_clear_feature: type = 0x%x, feature = 0x%x, what = 0x%x " "uf = 0x%x", type, feature, what, flags); if (dip == NULL) { return (USB_INVALID_ARGS); } if (ph == NULL) { return (USB_INVALID_PIPE); } /* issue Clear feature */ rval = usb_pipe_sync_ctrl_xfer(dip, ph, type, USB_REQ_CLEAR_FEATURE, feature, what, 0, NULL, 0, &completion_reason, &cb_flags, flags | USB_FLAGS_SLEEP); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "rval=%d, cb_flags=%d, cr=%d", rval, cb_flags, completion_reason); return (rval); } /* * usb_clr_feature: * Issue USB_REQ_CLEAR_FEATURE to endpoint/device/interface * * Arguments: * dip - pointer to devinfo of the client * type - bmRequestType to be used * feature - feature to be cleared * what - 0 for device, otherwise interface or ep number * flags - USB_FLAGS_SLEEP: * wait for completion * cb - if USB_FLAGS_SLEEP has not been specified * this callback function will be called on * completion. This callback may be NULL * and no notification of completion will then * be provided. * cb_arg - 2nd argument to callback function. * * * Return Values: * USB_SUCCESS - on doing a successful clear feature * USB_FAILURE - on failure * USB_* - refer to usbai.h */ int usb_clr_feature( dev_info_t *dip, uint_t type, /* bmRequestType */ uint_t feature, uint_t what, /* 0, interface, ept number */ usb_flags_t flags, void (*cb)( usb_pipe_handle_t ph, usb_opaque_t arg, int rval, usb_cb_flags_t flags), usb_opaque_t cb_arg) { usb_pipe_handle_t ph; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_clr_feature: type = 0x%x, feature = 0x%x, what = 0x%x " "uf = 0x%x", type, feature, what, flags); if (dip == NULL) { return (USB_INVALID_ARGS); } if ((flags & USB_FLAGS_SLEEP) && servicing_interrupt()) { return (USB_INVALID_CONTEXT); } ph = usba_get_dflt_pipe_handle(dip); if (usba_hold_ph_data(ph) == NULL) { return (USB_INVALID_PIPE); } return (usba_pipe_setup_func_call(dip, usba_sync_clear_feature, (usba_ph_impl_t *)ph, (usb_opaque_t)((uintptr_t)((type << 16 | feature << 8 | what))), flags, cb, cb_arg)); } static int usba_sync_clear_feature(dev_info_t *dip, usba_ph_impl_t *ph_impl, usba_pipe_async_req_t *req, usb_flags_t usb_flags) { uint_t n = (uint_t)((uintptr_t)(req->arg)); uint_t type = ((uint_t)n >> 16) & 0xff; uint_t feature = ((uint_t)n >> 8) & 0xff; uint_t what = (uint_t)n & 0xff; int rval; usba_device_t *usba_device; usba_pipe_handle_data_t *ph_data; usba_ph_impl_t *ph_im; uchar_t ep_index; usb_ep_descr_t *eptd; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_sync_clear_feature: " "dip=0x%p ph=0x%p type=0x%x feature=0x%x what=0x%x fl=0x%x", (void *)dip, (void *)ph_impl, type, feature, what, usb_flags); rval = usb_clear_feature(dip, (usb_pipe_handle_t)ph_impl, type, feature, what, usb_flags); /* * Reset data toggle to DATA0 for bulk and interrupt endpoint. * Data toggle synchronization is not supported for isochronous * transfer.Halt feature is not supported by control endpoint. * * From USB2.0 specification: * 1.Section 5.8.5 Bulk Transfer Data Sequences * Removal of the halt condition is achieved via software intervention * through a separate control pipe. This recovery will reset the data * toggle bit to DATA0 for the endpoint on both the host and the device. * * 2.Section 5.7.5 Interrupt Transfer Data Sequences * Removal of the halt condition is achieved via software intervention * through a separate control pipe. This recovery will reset the data * toggle bit to DATA0 for the endpoint on both the host and the device. * * 3.Section 9.4.5 * If the condition causing a halt has been removed, clearing the Halt * feature via a ClearFeature(ENDPOINT_HALT) request results in the * endpoint no longer returning a STALL. For endpoints using data * toggle, regardless of whether an endpoint has the Halt feature set, a * ClearFeature(ENDPOINT_HALT) request always results in the data toggle * being reinitialized to DATA0. * */ if (rval == USB_SUCCESS && feature == 0) { usba_device = usba_get_usba_device(dip); ep_index = usb_get_ep_index((uint8_t)what); ph_im = &usba_device->usb_ph_list[ep_index]; ph_data = usba_get_ph_data((usb_pipe_handle_t)ph_im); eptd = &ph_data->p_ep; if ((eptd->bmAttributes & USB_EP_ATTR_MASK) == USB_EP_ATTR_BULK || (eptd->bmAttributes & USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR) usba_device->usb_hcdi_ops-> usba_hcdi_pipe_reset_data_toggle(ph_data); } usba_release_ph_data(ph_impl); return (rval); } /* * usb_async_req: * function used to dispatch a request to the taskq * * Arguments: * dip - pointer to devinfo node * func - pointer to function issued by taskq * flag - USB_FLAGS_SLEEP mostly * * Return Values: * USB_SUCCESS - on doing a successful taskq invocation * USB_FAILURE - on failure * USB_* - refer to usbai.h */ int usb_async_req(dev_info_t *dip, void (*func)(void *), void *arg, usb_flags_t flag) { int tq_flag; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_async_req: dip=0x%p func=0x%p, arg=0x%p flag=0x%x", (void *)dip, (void *)func, arg, flag); if ((dip == NULL) || (func == NULL)) { return (USB_INVALID_ARGS); } tq_flag = (flag & USB_FLAGS_SLEEP) ? TQ_SLEEP : TQ_NOSLEEP; if (flag & USB_FLAGS_NOQUEUE) { tq_flag |= TQ_NOQUEUE; } if (taskq_dispatch(system_taskq, func, arg, tq_flag) == TASKQID_INVALID) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "usb_async_req: failure"); return (USB_FAILURE); } return (USB_SUCCESS); } /* * usba_async_ph_req: * function used to dispatch a request to the ph taskq * * Arguments: * ph_data - pointer to pipe handle data * func - pointer to function issued by taskq * flag - USB_FLAGS_SLEEP or USB_FLAGS_NOSLEEP * * Return Values: * USB_SUCCESS - on doing a successful taskq invocation * USB_FAILURE - on failure * USB_* - refer to usbai.h * * Note: * If the caller specified USB_FLAGS_NOSLEEP, it must be * capable of reliably recovering from a failure return */ int usba_async_ph_req(usba_pipe_handle_data_t *ph_data, void (*func)(void *), void *arg, usb_flags_t flag) { int tq_flag; taskq_t *taskq; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usba_async_ph_req: ph_data=0x%p func=0x%p, arg=0x%p flag=0x%x", (void *)ph_data, (void *)func, arg, flag); if (func == NULL) { return (USB_INVALID_ARGS); } tq_flag = (flag & USB_FLAGS_SLEEP) ? TQ_SLEEP : TQ_NOSLEEP; if (ph_data && ph_data->p_taskq) { taskq = ph_data->p_taskq; } else { taskq = system_taskq; tq_flag |= TQ_NOQUEUE; } if (taskq_dispatch(taskq, func, arg, tq_flag) == TASKQID_INVALID) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "usba_async_ph_req: failure"); return (USB_FAILURE); } return (USB_SUCCESS); } /* * utility functions to display CR, CB, return values */ typedef struct conv_table { int what; const char *name; } conv_table_t; static const char * usba_get_name(conv_table_t *conv_table, int value) { int i; for (i = 0; conv_table[i].name != NULL; i++) { if (conv_table[i].what == value) { return (conv_table[i].name); } } return ("unknown"); } static conv_table_t cr_table[] = { { USB_CR_OK, "" }, { USB_CR_CRC, "" }, { USB_CR_BITSTUFFING, "" }, { USB_CR_DATA_TOGGLE_MM, "" }, { USB_CR_STALL, "" }, { USB_CR_DEV_NOT_RESP, "" }, { USB_CR_PID_CHECKFAILURE, "" }, { USB_CR_UNEXP_PID, "" }, { USB_CR_DATA_OVERRUN, "" }, { USB_CR_DATA_UNDERRUN, "" }, { USB_CR_BUFFER_OVERRUN, "" }, { USB_CR_BUFFER_UNDERRUN, "" }, { USB_CR_TIMEOUT, "" }, { USB_CR_NOT_ACCESSED, "" }, { USB_CR_NO_RESOURCES, "" }, { USB_CR_UNSPECIFIED_ERR, "" }, { USB_CR_STOPPED_POLLING, "" }, { USB_CR_PIPE_CLOSING, "" }, { USB_CR_PIPE_RESET, "" }, { USB_CR_NOT_SUPPORTED, "" }, { USB_CR_FLUSHED, "" }, { USB_CR_HC_HARDWARE_ERR, "" }, { 0, NULL } }; const char * usb_str_cr(usb_cr_t cr) { return (usba_get_name(cr_table, cr)); } static conv_table_t cb_flags_table[] = { { USB_CB_NO_INFO, "" }, { USB_CB_STALL_CLEARED, "" }, { USB_CB_FUNCTIONAL_STALL, "" }, { USB_CB_PROTOCOL_STALL, "" }, { USB_CB_RESET_PIPE, "" }, { USB_CB_ASYNC_REQ_FAILED, "" }, { USB_CB_NO_RESOURCES, "" }, { USB_CB_SUBMIT_FAILED, "" }, { USB_CB_INTR_CONTEXT, "" }, { 0, NULL } }; /*ARGSUSED*/ char * usb_str_cb_flags(usb_cb_flags_t cb_flags, char *buffer, size_t length) { int i; buffer[0] = '\0'; if (cb_flags == USB_CB_NO_INFO) { (void) strncpy(buffer, cb_flags_table[0].name, length); } else { for (i = 0; cb_flags_table[i].name != NULL; i++) { if (cb_flags & cb_flags_table[i].what) { (void) strncpy(&buffer[strlen(buffer)], cb_flags_table[0].name, length - strlen(buffer) - 1); } } } return (buffer); } static conv_table_t pipe_state_table[] = { { USB_PIPE_STATE_CLOSED, "" }, { USB_PIPE_STATE_IDLE, "" }, { USB_PIPE_STATE_ACTIVE, "" }, { USB_PIPE_STATE_ERROR, "" }, { USB_PIPE_STATE_CLOSING, "" }, { 0, NULL } }; const char * usb_str_pipe_state(usb_pipe_state_t state) { return (usba_get_name(pipe_state_table, state)); } static conv_table_t dev_state[] = { { USB_DEV_ONLINE, "" }, { USB_DEV_DISCONNECTED, "" }, { USB_DEV_SUSPENDED, "" }, { USB_DEV_PWRED_DOWN, "" }, { 0, NULL } }; const char * usb_str_dev_state(int state) { return (usba_get_name(dev_state, state)); } static conv_table_t rval_table[] = { { USB_SUCCESS, "" }, { USB_FAILURE, "" }, { USB_NO_RESOURCES, "" }, { USB_NO_BANDWIDTH, "" }, { USB_NOT_SUPPORTED, "" }, { USB_PIPE_ERROR, "" }, { USB_INVALID_PIPE, "" }, { USB_NO_FRAME_NUMBER, "" }, { USB_INVALID_START_FRAME, "" }, { USB_HC_HARDWARE_ERROR, "" }, { USB_INVALID_REQUEST, "" }, { USB_INVALID_CONTEXT, "" }, { USB_INVALID_VERSION, "" }, { USB_INVALID_ARGS, "" }, { USB_INVALID_PERM, "" }, { USB_BUSY, "" }, { 0, NULL } }; const char * usb_str_rval(int rval) { return (usba_get_name(rval_table, rval)); } /* * function to convert USB return values to close errno */ static struct usb_rval2errno_entry { int rval; int Errno; } usb_rval2errno_table[] = { { USB_SUCCESS, 0 }, { USB_FAILURE, EIO }, { USB_NO_RESOURCES, ENOMEM }, { USB_NO_BANDWIDTH, EAGAIN }, { USB_NOT_SUPPORTED, ENOTSUP }, { USB_PIPE_ERROR, EIO }, { USB_INVALID_PIPE, EINVAL }, { USB_NO_FRAME_NUMBER, EINVAL }, { USB_INVALID_START_FRAME, EINVAL }, { USB_HC_HARDWARE_ERROR, EIO }, { USB_INVALID_REQUEST, EINVAL }, { USB_INVALID_CONTEXT, EINVAL }, { USB_INVALID_VERSION, EINVAL }, { USB_INVALID_ARGS, EINVAL }, { USB_INVALID_PERM, EACCES }, { USB_BUSY, EBUSY }, }; #define USB_RVAL2ERRNO_TABLE_SIZE (sizeof (usb_rval2errno_table) / \ sizeof (struct usb_rval2errno_entry)) int usb_rval2errno(int rval) { int i; for (i = 0; i < USB_RVAL2ERRNO_TABLE_SIZE; i++) { if (usb_rval2errno_table[i].rval == rval) { return (usb_rval2errno_table[i].Errno); } } return (EIO); } /* * serialization */ usb_serialization_t usb_init_serialization( dev_info_t *dip, uint_t flag) { usba_serialization_impl_t *impl_tokenp = kmem_zalloc( sizeof (usba_serialization_impl_t), KM_SLEEP); usba_device_t *usba_device; ddi_iblock_cookie_t cookie = NULL; if (dip) { usba_device = usba_get_usba_device(dip); cookie = usba_hcdi_get_hcdi( usba_device->usb_root_hub_dip)->hcdi_iblock_cookie; } impl_tokenp->s_dip = dip; impl_tokenp->s_flag = flag; mutex_init(&impl_tokenp->s_mutex, NULL, MUTEX_DRIVER, cookie); cv_init(&impl_tokenp->s_cv, NULL, CV_DRIVER, NULL); return ((usb_serialization_t)impl_tokenp); } void usb_fini_serialization( usb_serialization_t tokenp) { usba_serialization_impl_t *impl_tokenp; if (tokenp) { impl_tokenp = (usba_serialization_impl_t *)tokenp; ASSERT(impl_tokenp->s_count == 0); cv_destroy(&impl_tokenp->s_cv); mutex_destroy(&impl_tokenp->s_mutex); kmem_free(impl_tokenp, sizeof (usba_serialization_impl_t)); } } /* * usb_serialize_access() permits single threaded access. * * If tokenp is initialized with USB_INIT_SER_CHECK_SAME_THREAD, * it is reentrant with respect to thread. The thread must * hold and release the same number of times. * * If tokenp is initialized without USB_INIT_SER_CHECK_SAME_THREAD, * it is not reentrant by the same thread. It is something like * a semaphore. */ int usb_serialize_access( usb_serialization_t tokenp, uint_t how_to_wait, uint_t delta_timeout) { int rval = 1; /* Must be initialized > 0 */ clock_t abs_timeout = 0; usba_serialization_impl_t *impl_tokenp; impl_tokenp = (usba_serialization_impl_t *)tokenp; /* * Convert delta timeout in ms to absolute timeout in ticks, if used. */ if ((how_to_wait == USB_TIMEDWAIT) || (how_to_wait == USB_TIMEDWAIT_SIG)) { /* Convert timeout arg (in ms) to hz */ abs_timeout = ddi_get_lbolt() + drv_usectohz(delta_timeout * 1000); } /* Get mutex after calc abs time, to count time waiting for mutex. */ mutex_enter(&impl_tokenp->s_mutex); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_serialize_access: tok=0x%p dip=0x%p cnt=%d thr=0x%p, " "flg=0x%x, abs_tmo=0x%lx", (void *)impl_tokenp, (void *)impl_tokenp->s_dip, impl_tokenp->s_count, (void *)impl_tokenp->s_thread, how_to_wait, abs_timeout); if ((impl_tokenp->s_flag & USB_INIT_SER_CHECK_SAME_THREAD) == 0 || impl_tokenp->s_thread != curthread) { /* * There are three ways to break out of the loop: * 1) Condition met (s_count == 0) - higher prio test * 2) kill(2) signal received (rval == 0) * 3) timeout occurred (rval == -1) * If condition met, whether or not signal or timeout occurred * take access. If condition not met, check other exit means. */ while (impl_tokenp->s_count != 0) { /* cv_timedwait* returns -1 on timeout. */ /* cv_wait*_sig returns 0 on (kill(2)) signal. */ if (rval <= 0) { mutex_exit(&impl_tokenp->s_mutex); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_serialize_access: " "tok=0x%p exit due to %s", (void *)impl_tokenp, ((rval == 0) ? "signal" : "timeout")); return (rval); } switch (how_to_wait) { default: how_to_wait = USB_WAIT; /* FALLTHROUGH */ case USB_WAIT: cv_wait(&impl_tokenp->s_cv, &impl_tokenp->s_mutex); break; case USB_WAIT_SIG: rval = cv_wait_sig(&impl_tokenp->s_cv, &impl_tokenp->s_mutex); break; case USB_TIMEDWAIT: rval = cv_timedwait(&impl_tokenp->s_cv, &impl_tokenp->s_mutex, abs_timeout); break; case USB_TIMEDWAIT_SIG: rval = cv_timedwait_sig(&impl_tokenp->s_cv, &impl_tokenp->s_mutex, abs_timeout); break; } } impl_tokenp->s_thread = curthread; } impl_tokenp->s_count++; ASSERT(!(impl_tokenp->s_count > 1 && (impl_tokenp->s_flag & USB_INIT_SER_CHECK_SAME_THREAD) == 0)); mutex_exit(&impl_tokenp->s_mutex); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_serialize_access exit: tok=0x%p thr=0x%p", (void *)impl_tokenp, (void *)curthread); return (1); } /*ARGSUSED*/ int usb_try_serialize_access( usb_serialization_t tokenp, uint_t flag) { usba_serialization_impl_t *impl_tokenp = (usba_serialization_impl_t *)tokenp; mutex_enter(&impl_tokenp->s_mutex); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_try_serialize_access: tok=0x%p dip=0x%p cnt=%d thr=0x%p", (void *)impl_tokenp, (void *)impl_tokenp->s_dip, impl_tokenp->s_count, (void *)curthread); /* * If lock is not taken (s_count is 0), take it. * If lock is already taken, the thread is owner and lock * is reentrant, take it. * Otherwise, fail the access. */ if (!impl_tokenp->s_count || ((impl_tokenp->s_thread == curthread) && (impl_tokenp->s_flag & USB_INIT_SER_CHECK_SAME_THREAD))) { impl_tokenp->s_thread = curthread; impl_tokenp->s_count++; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_try_serialize_access success: tok=0x%p", (void *)impl_tokenp); mutex_exit(&impl_tokenp->s_mutex); return (USB_SUCCESS); } USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_try_serialize_access failed: " "tok=0x%p dip=0x%p cnt=%d thr=0x%p", (void *)impl_tokenp, (void *)impl_tokenp->s_dip, impl_tokenp->s_count, (void *)impl_tokenp->s_thread); mutex_exit(&impl_tokenp->s_mutex); return (USB_FAILURE); } void usb_release_access( usb_serialization_t tokenp) { usba_serialization_impl_t *impl_tokenp = (usba_serialization_impl_t *)tokenp; mutex_enter(&impl_tokenp->s_mutex); USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_release_access: tok=0x%p dip=0x%p count=%d thr=0x%p", (void *)impl_tokenp, (void *)impl_tokenp->s_dip, impl_tokenp->s_count, (void *)curthread); ASSERT(impl_tokenp->s_count > 0); if (impl_tokenp->s_flag & USB_INIT_SER_CHECK_SAME_THREAD) { if (impl_tokenp->s_thread != curthread) { USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "usb_release_access: release from wrong thread"); } ASSERT(impl_tokenp->s_thread == curthread); } if (--impl_tokenp->s_count == 0) { impl_tokenp->s_thread = NULL; cv_broadcast(&impl_tokenp->s_cv); } mutex_exit(&impl_tokenp->s_mutex); } /* * usb_fail_checkpoint: * fail checkpoint as driver/device could not be quiesced */ /*ARGSUSED*/ void usb_fail_checkpoint(dev_info_t *dip, usb_flags_t flags) { usba_device_t *usba_device = usba_get_usba_device(dip); USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "usb_fail_checkpoint: %s%d", ddi_driver_name(dip), ddi_get_instance(dip)); mutex_enter(&usba_device->usb_mutex); usba_device->usb_no_cpr++; mutex_exit(&usba_device->usb_mutex); } _NOTE(SCHEME_PROTECTS_DATA("unique per call", iocblk)) _NOTE(SCHEME_PROTECTS_DATA("unique per call", datab)) /* * usba_mk_mctl: * create a USB style M_CTL message, given an iocblk and a buffer * returns mblk_t * on success, NULL on failure */ mblk_t * usba_mk_mctl(struct iocblk mctlmsg, void *buf, size_t len) { mblk_t *bp1, *bp2; if ((bp1 = allocb(sizeof (struct iocblk), BPRI_HI)) != NULL) { /* LINTED E_BAD_PTR_CAST_ALIGN */ *((struct iocblk *)bp1->b_datap->db_base) = mctlmsg; bp1->b_datap->db_type = M_CTL; bp1->b_wptr += sizeof (struct iocblk); if (buf != NULL) { if ((bp2 = allocb(len, BPRI_HI)) != NULL) { bp1->b_cont = bp2; bcopy(buf, bp2->b_datap->db_base, len); bp2->b_wptr += len; } else { freemsg(bp1); bp1 = NULL; } } } return (bp1); } #ifdef ALLOCB_TEST #undef allocb mblk_t * usba_test_allocb(size_t size, uint_t pri) { if (ddi_get_lbolt() & 0x1) { return (NULL); } else { return (allocb(size, pri)); } } #endif /* * usb common power management for usb_mid, usb_ia and maybe other simple * drivers. */ /* * functions to handle power transition for OS levels 0 -> 3 */ static int usb_common_pwrlvl0(dev_info_t *dip, uint8_t *pm, int *dev_state) { int rval; switch (*dev_state) { case USB_DEV_ONLINE: /* Issue USB D3 command to the device here */ rval = usb_set_device_pwrlvl3(dip); ASSERT(rval == USB_SUCCESS); *dev_state = USB_DEV_PWRED_DOWN; *pm = USB_DEV_OS_PWR_OFF; /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* allow a disconnected/cpr'ed device to go to low pwr */ return (USB_SUCCESS); case USB_DEV_PWRED_DOWN: default: return (USB_FAILURE); } } /* ARGSUSED */ static int usb_common_pwrlvl1(dev_info_t *dip, uint8_t *pm, int *dev_state) { int rval; /* Issue USB D2 command to the device here */ rval = usb_set_device_pwrlvl2(dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* ARGSUSED */ static int usb_common_pwrlvl2(dev_info_t *dip, uint8_t *pm, int *dev_state) { int rval; /* Issue USB D1 command to the device here */ rval = usb_set_device_pwrlvl1(dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } static int usb_common_pwrlvl3(dev_info_t *dip, uint8_t *pm, int *dev_state) { int rval; switch (*dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ rval = usb_set_device_pwrlvl0(dip); ASSERT(rval == USB_SUCCESS); *dev_state = USB_DEV_ONLINE; *pm = 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: /* allow a disconnected/cpr'ed device to go to low power */ return (USB_SUCCESS); default: USB_DPRINTF_L2(DPRINT_MASK_USBA, usbai_log_handle, "usb_common_pwrlvl3: Illegal state (%s)", usb_str_dev_state(*dev_state)); return (USB_FAILURE); } } /* power management */ int usba_common_power(dev_info_t *dip, uint8_t *pm, int *dev_state, int level) { int rval = DDI_FAILURE; switch (level) { case USB_DEV_OS_PWR_OFF: rval = usb_common_pwrlvl0(dip, pm, dev_state); break; case USB_DEV_OS_PWR_1: rval = usb_common_pwrlvl1(dip, pm, dev_state); break; case USB_DEV_OS_PWR_2: rval = usb_common_pwrlvl2(dip, pm, dev_state); break; case USB_DEV_OS_FULL_PWR: rval = usb_common_pwrlvl3(dip, pm, dev_state); break; } return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * register and unregister for events from our parent for usb_mid and usb_ia * and maybe other nexus driver. * * Note: The cookie fields in usba_device structure is not used. They are * used/shared by children. */ void usba_common_register_events(dev_info_t *dip, uint_t if_num, void (*event_cb)(dev_info_t *, ddi_eventcookie_t, void *, void *)) { int rval; usba_evdata_t *evdata; ddi_eventcookie_t cookie; USB_DPRINTF_L4(DPRINT_MASK_USBA, usbai_log_handle, "usb_common_register_events:"); evdata = usba_get_evdata(dip); /* get event cookie, discard level and icookie for now */ rval = ddi_get_eventcookie(dip, DDI_DEVI_REMOVE_EVENT, &cookie); if (rval == DDI_SUCCESS) { rval = ddi_add_event_handler(dip, cookie, event_cb, NULL, &evdata->ev_rm_cb_id); if (rval != DDI_SUCCESS) { goto fail; } } rval = ddi_get_eventcookie(dip, DDI_DEVI_INSERT_EVENT, &cookie); if (rval == DDI_SUCCESS) { rval = ddi_add_event_handler(dip, cookie, event_cb, NULL, &evdata->ev_ins_cb_id); if (rval != DDI_SUCCESS) { goto fail; } } rval = ddi_get_eventcookie(dip, USBA_PRE_SUSPEND_EVENT, &cookie); if (rval == DDI_SUCCESS) { rval = ddi_add_event_handler(dip, cookie, event_cb, NULL, &evdata->ev_suspend_cb_id); if (rval != DDI_SUCCESS) { goto fail; } } rval = ddi_get_eventcookie(dip, USBA_POST_RESUME_EVENT, &cookie); if (rval == DDI_SUCCESS) { rval = ddi_add_event_handler(dip, cookie, event_cb, NULL, &evdata->ev_resume_cb_id); if (rval != DDI_SUCCESS) { goto fail; } } return; fail: usba_common_unregister_events(dip, if_num); } void usba_common_unregister_events(dev_info_t *dip, uint_t if_num) { usba_evdata_t *evdata; usba_device_t *usba_device = usba_get_usba_device(dip); int i; evdata = usba_get_evdata(dip); if (evdata->ev_rm_cb_id != NULL) { (void) ddi_remove_event_handler(evdata->ev_rm_cb_id); evdata->ev_rm_cb_id = NULL; } if (evdata->ev_ins_cb_id != NULL) { (void) ddi_remove_event_handler(evdata->ev_ins_cb_id); evdata->ev_ins_cb_id = NULL; } if (evdata->ev_suspend_cb_id != NULL) { (void) ddi_remove_event_handler(evdata->ev_suspend_cb_id); evdata->ev_suspend_cb_id = NULL; } if (evdata->ev_resume_cb_id != NULL) { (void) ddi_remove_event_handler(evdata->ev_resume_cb_id); evdata->ev_resume_cb_id = NULL; } /* clear event data for children, required for cfgmadm unconfigure */ mutex_enter(&usba_device->usb_mutex); if (usb_owns_device(dip)) { usba_free_evdata(usba_device->usb_evdata); usba_device->usb_evdata = NULL; usba_device->rm_cookie = NULL; usba_device->ins_cookie = NULL; usba_device->suspend_cookie = NULL; usba_device->resume_cookie = NULL; } else { for (i = 0; i < if_num; i++) { usba_device->usb_client_flags[usba_get_ifno(dip) + i] &= ~USBA_CLIENT_FLAG_EV_CBS; } } mutex_exit(&usba_device->usb_mutex); }