diff -r 491af2bffe0f -r 1fd565334dfc components/libusb/ugen/src/libusbugen.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/libusb/ugen/src/libusbugen.c Wed May 11 12:40:48 2011 -0700 @@ -0,0 +1,3016 @@ +/* + * 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 (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * This library can either be used stand-alone or as plugin + * to the libusb wrapper library. + * The libusb API 0.1.0.10 has been implemented using all original code + * which was not derived from opensource. + * + * XXX issues: + * - timeout thru signal + * - usb_interrupt/bulk_write/read should have common code + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "usb.h" +#include "libusbugen_impl.h" + +/* global variables */ +usb_bus_t *usb_busses = NULL; +const char *libusb_version = "1.1"; + +/* error handling */ +static char usb_error_string[1024]; +static int usb_error_errno; +static usb_error_type_t usb_error_type = USB_ERROR_TYPE_NONE; + +/* debugging */ +static int libusb_debug = DEBUG_NONE; + +/* api binding */ +static int libusb_api = API_RELAXED; + +/* internal function prototypes */ +static void usb_error_str(int x, char *format, ...); +static int usb_error(int x); +static void usb_dprintf(int level, char *format, ...); +static void usb_dump_data(char *data, int size); + +static int usb_open_ep0(usb_dev_handle_impl_t *hdl); +static void usb_close_ep0(usb_dev_handle_impl_t *hdl); +static int usb_check_access(usb_dev_handle *dev); +static int usb_search_dev_usb(usb_device_t **new_devices); +static int usb_do_io(int fd, int stat_fd, char *data, size_t size, int flag); +static int usb_send_msg(int fd, int stat_fd, int requesttype, int request, + int value, int index, char *data, int size); +static int usb_check_device_and_status_open(usb_dev_handle *dev, + int ep, int ep_type, int mode); +static int usb_get_status(int fd); +static void usb_set_ep_iface_alts(usb_dev_handle_impl_t *hdl, + usb_dev_handle_info_t *info, int index, int interface, int alternate); + +static int usb_setup_all_configs(usb_dev_handle_impl_t *hdl); +static void usb_free_all_configs(usb_device_t *device); +static int usb_parse_config(usb_dev_handle_impl_t *hdl, uint_t index); +static void usb_free_config(usb_device_t *device, uint_t index); +static int usb_parse_interface(usb_dev_handle_impl_t *hdl, uint_t index, + uint_t iface, char *cloud); +static void usb_free_interface(usb_device_t *device, uint_t index, + uint_t iface); +static int usb_parse_alternate(usb_dev_handle_impl_t *hdl, uint_t index, + uint_t iface, uint_t alt, char *cloud); +static void usb_free_alternate(usb_device_t *device, uint_t index, + uint_t iface, uint_t alt); +static int usb_parse_endpoint(usb_dev_handle_impl_t *hdl, int index, + int iface, int alt, int ep, char *cloud); +static void usb_find_extra(uchar_t *buf, size_t buflen, + unsigned char **extra, int *extralen); + +static void usb_close_all_eps(usb_dev_handle_impl_t *hdl); +static void usb_add_device(usb_device_t **list, usb_device_t *dev); +static void usb_remove_device(usb_device_t **list, usb_device_t *dev); +static int usb_check_device_in_list(usb_device_t *list, usb_device_t *dev); + +static void usb_free_dev(usb_device_t *dev); +static void usb_free_bus(usb_bus_t *bus); + +static size_t usb_parse_dev_descr(uchar_t *buf, size_t buflen, + struct usb_device_descriptor *ret_descr, size_t ret_buf_len); +static size_t usb_parse_cfg_descr(uchar_t *buf, size_t buflen, + usb_cfg_descr_t *ret_descr, size_t ret_buf_len, + unsigned char **extra, int *extralen); +static size_t usb_parse_if_descr(uchar_t *buf, size_t buflen, + uint_t if_number, uint_t alt_if_setting, + usb_if_descr_t *ret_descr, size_t ret_buf_len, + unsigned char **extra, int *extralen); +static size_t usb_parse_ep_descr(uchar_t *buf, size_t buflen, + uint_t if_number, uint_t alt_if_setting, uint_t ep_index, + usb_ep_descr_t *ret_descr, size_t ret_buf_len, + unsigned char **extra, int *extralen); +static uchar_t usb_ep_index(uint8_t ep_addr); + +/* + * libusb_init: + * + * Returns: 0 or ENOSUP if we cannot support any bus + */ +int +libusb_init(void) +{ + return (0); +} + +/* + * libusb_fini: + * + * Returns: always returns 0 + */ +int +libusb_fini(void) +{ + return (0); +} + +/* + * Entry points: + * + * usb_set_debug: + * sets debug level for tracing + */ +void +usb_set_debug(int level) +{ + if (getenv("SUN_LIBUSBUGEN_DEBUG")) { + + level = atoi(getenv("SUN_LIBUSBUGEN_DEBUG")); + + } else if (getenv("SUN_LIBUSB_DEBUG")) { + + level = atoi(getenv("SUN_LIBUSB_DEBUG")); + } + + if (level < 0) + return; + + libusb_debug = level; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_set_debug(): " + "Setting debugging level to %d (%s)\n", + level, level ? "on" : "off"); +} + +/* + * usb_init: + * not much to initialize. just set debug level + */ +void +usb_init(void) +{ + if (getenv("LIBUSB_API_STRICT")) { + libusb_api = API_STRICT; + } + + usb_set_debug(libusb_debug); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_init(): " + "libusb version=%s\n", libusb_version); +} + +/* + * usb_get_busses: + * Returns: usb_busses pointer + */ +usb_bus_t * +usb_get_busses(void) +{ + return (usb_busses); +} + +/* + * usb_find_busses: + * finds all busses in the system. On solaris we have + * only one flat name space, /dev/usb + * + * Returns: change in number of busses or negative errno + */ +int +usb_find_busses(void) +{ + usb_bus_t *bus; + + /* we only have one name space for all USB busses */ + if (usb_busses == NULL) { + /* never freed */ + if ((bus = calloc(sizeof (*bus), 1)) == NULL) { + + return (usb_error(ENOMEM)); + } + + (void) strncpy(bus->dirname, "/dev/usb", + sizeof (bus->dirname)); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_find_busses(): " + "found %s\n", bus->dirname); + + usb_busses = bus; + + return (1); + } + + return (0); +} + +/* + * usb_find_devices: + * finds all devices that have appeared and removes devices + * from the list that are no longer there + * + * Returns: change in number of devices or a negative errno + */ +int +usb_find_devices(void) +{ + int i, rval, found; + int changes = 0; + usb_device_t *new_devices; + usb_device_t *dev, *next_dev, *new, *next_new; + + new_devices = NULL; + + rval = usb_search_dev_usb(&new_devices); + if (rval != 0) { + + return (usb_error(rval)); + } + + /* is any of devices on the new list also on the old list? */ + for (dev = usb_busses->devices; dev != NULL; dev = next_dev) { + next_dev = dev->next; + found = 0; + for (new = new_devices; new != NULL; new = next_new) { + next_new = new->next; + if (strncmp(dev->filename, new->filename, + sizeof (dev->filename)) == 0) { + usb_remove_device(&new_devices, new); + usb_free_dev(new); + found++; + break; + } + } + + /* the device must have been hot removed */ + if (!found) { + usb_remove_device(&usb_busses->devices, dev); + usb_free_dev(dev); + } + } + + /* add all new_devices to the old_devices list */ + usb_dprintf(DEBUG_DETAILED, "moving new to old\n"); + + for (new = new_devices; new != NULL; new = next_new) { + next_new = new->next; + usb_remove_device(&new_devices, new); + usb_add_device(&usb_busses->devices, new); + (void) usb_close(usb_open(new)); + changes++; + } + + usb_dprintf(DEBUG_DETAILED, "usb_devices list:\n"); + for (i = 0, dev = usb_busses->devices; dev != NULL; + i++, dev = next_dev) { + next_dev = dev->next; + usb_dprintf(DEBUG_DETAILED, "%d: %s\n", i, dev->filename); + } + + return (changes); +} + +/* + * usb_device: + * included because sane uses this + * Returns: usb_device structure associated with handle + */ +struct usb_device * +usb_device(usb_dev_handle *dev) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + + return ((hdl != NULL) ? hdl->device : NULL); +} + +/* + * usb_open: + * opens the device for access + * + * Returns: a usb device handle or NULL + */ +usb_dev_handle * +usb_open(usb_device_t *dev) +{ + usb_dev_handle_impl_t *hdl; + usb_dev_handle_info_t *info; + int i, rval; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_open():\n"); + + if (usb_check_device_in_list(usb_busses->devices, + dev) == 0) { + usb_dprintf(DEBUG_ERRORS, "usb_open(): " + "illegal usb_device pointer\n"); + + return (NULL); + } + + /* create a handle and info structure */ + if ((hdl = calloc(sizeof (*hdl), 1)) == NULL) { + + return (NULL); + } + hdl->device = dev; + if ((info = calloc(sizeof (*info), 1)) == NULL) { + free(hdl); + + return (NULL); + } + + hdl->info = info; + + /* set all file descriptors to "closed" */ + for (i = 0; i < USB_MAXENDPOINTS; i++) { + hdl->info->ep_fd[i] = -1; + hdl->info->ep_status_fd[i] = -1; + if (i > 0) { + hdl->info->ep_interface[i] = -1; + } + } + + /* open default control ep and keep it open */ + if ((rval = usb_open_ep0(hdl)) != 0) { + usb_dprintf(DEBUG_ERRORS, "usb_open_ep0 failed: %d\n", rval); + free(info); + free(hdl); + + return (NULL); + } + + /* + * setup config info: trees of configs, interfaces, alternates + * and endpoints structures + */ + if (usb_setup_all_configs(hdl) != 0) { + usb_free_all_configs(hdl->device); + usb_close_ep0(hdl); + free(info); + free(hdl); + + return (NULL); + } + + /* set when the app claims an interface */ + info->configuration_value = + info->claimed_interface = info->alternate = -1; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_open(): hdl=0x%x\n", (int)hdl); + + return ((usb_dev_handle *)hdl); +} + +/* + * usb_close: + * closes the device and free resources + * + * Returns: 0 + */ +int +usb_close(usb_dev_handle *dev) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_close(): hdl=0x%x\n", hdl); + + if (hdl) { + info = hdl->info; + + usb_dprintf(DEBUG_DETAILED, + "usb_close(): claimed %d\n", + info->claimed_interface); + + if (info->claimed_interface != -1) { + (void) usb_release_interface(dev, + info->claimed_interface); + } + usb_close_all_eps(hdl); + usb_close_ep0(hdl); + + free(info); + free(hdl); + + return (0); + } + + return (usb_error(EINVAL)); +} + +/* + * usb_control_msg: + * sends a control message + * + * Returns: actual number of data bytes transferred or + * a negative errno + */ +/*ARGSUSED*/ +int +usb_control_msg(usb_dev_handle *dev, int requesttype, int request, + int value, int index, char *data, int size, int timeout) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int additional; + int rval; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_control_msg():\n"); + + if ((hdl == NULL) || (size < 0)) { + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* + * no need to do validation since ep0 is always open and + * we do not need to claim an interface first + * + * usb_send_msg returns #bytes xferred or negative errno + */ + rval = usb_send_msg(info->ep_fd[0], info->ep_status_fd[0], + requesttype, request, value, index, data, size); + + /* less than setup bytes transferred? */ + if (rval < 8) { + usb_error_str(errno, + "error sending control message: %d", rval); + + return ((rval >= 0) ? + usb_error(EIO) : usb_error(-rval)); + } + + rval -= 8; /* substract setup length */ + + /* for IN requests, now transfer the remaining bytes */ + if ((size) && (requesttype & USB_DEV_REQ_DEV_TO_HOST)) { + additional = usb_do_io(info->ep_fd[0], + info->ep_status_fd[0], data, size, READ); + } else { + additional = rval; + } + + usb_dprintf(DEBUG_FUNCTIONS, + "usb_control_msg(): additional 0x%x bytes\n", additional); + + return (additional); +} + +/* + * usb_bulk_write: + * writes to a bulk endpoint + * + * Returns: actual number of data bytes transferred or negative errno + */ +/* ARGSUSED */ +int +usb_bulk_write(usb_dev_handle *dev, int ep, char *data, int size, + int timeout) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int sent, rval; + int ep_index = usb_ep_index(ep); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_bulk_write(): ep=0x%x\n", ep); + + if ((hdl == NULL) || (data == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_bulk_write(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* do some validation first */ + if ((rval = usb_check_device_and_status_open(dev, ep, + USB_ENDPOINT_TYPE_BULK, O_WRONLY)) != 0) { + usb_dprintf(DEBUG_ERRORS, + "usb_check_device_and_status_open() failed\n"); + + return (usb_error(rval)); + } + + /* now write out the bytes */ + sent = usb_do_io(info->ep_fd[ep_index], info->ep_status_fd[ep_index], + data, size, WRITE); + + return (sent); +} + +/* + * usb_bulk_read: + * reads from a bulk endpoint + * + * Returns: actual number of data bytes transferred or negative errno + */ +/* ARGSUSED */ +int +usb_bulk_read(usb_dev_handle *dev, int ep, char *data, int size, + int timeout) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int ep_index, received, rval; + + ep |= USB_ENDPOINT_IN; + ep_index = usb_ep_index(ep); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_bulk_read(): ep=0x%x\n", ep); + + if ((hdl == NULL) || (data == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_bulk_read(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* do some validation first */ + if ((rval = usb_check_device_and_status_open(dev, ep, + USB_ENDPOINT_TYPE_BULK, O_RDONLY)) != 0) { + usb_dprintf(DEBUG_ERRORS, + "usb_check_device_and_status_open() failed\n"); + + return (usb_error(rval)); + } + + /* now read in the bytes */ + received = usb_do_io(info->ep_fd[ep_index], + info->ep_status_fd[ep_index], + data, size, READ); + + return (received); +} + +/* + * usb_interrupt_write: + * writes data to an interrupt endpoint + * + * Returns: actual number of data bytes transferred or negative errno + */ +/* ARGSUSED */ +int +usb_interrupt_write(usb_dev_handle *dev, int ep, char *data, int size, + int timeout) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int sent, rval; + int ep_index = usb_ep_index(ep); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_interrupt_write(): ep=0x%x\n", ep); + + if ((hdl == NULL) || (data == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_interrupt_write(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* do some validation first */ + if ((rval = usb_check_device_and_status_open(dev, ep, + USB_ENDPOINT_TYPE_INTERRUPT, O_WRONLY)) != 0) { + usb_dprintf(DEBUG_ERRORS, + "usb_check_device_and_status_open() failed\n"); + + return (usb_error(rval)); + } + + /* now transfer the bytes */ + sent = usb_do_io(info->ep_fd[ep_index], + info->ep_status_fd[ep_index], + data, size, WRITE); + + return (sent); +} + +/* + * usb_interrupt_read: + * reads data from an interrupt endpoint + * + * Returns: actual number of data bytes transferred or negative errno + */ +/* ARGSUSED */ +int +usb_interrupt_read(usb_dev_handle *dev, int ep, char *data, int size, + int timeout) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int ep_index, received, rval; + + ep |= USB_ENDPOINT_IN; + ep_index = usb_ep_index(ep); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_interrrupt_read(): ep=0x%x\n", ep); + + if ((hdl == NULL) || (data == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_interrupt_read(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* do some validation first */ + if ((rval = usb_check_device_and_status_open(dev, ep, + USB_ENDPOINT_TYPE_INTERRUPT, O_RDONLY)) != 0) { + usb_dprintf(DEBUG_ERRORS, + "usb_check_device_and_status_open() failed\n"); + + return (usb_error(rval)); + } + + /* now transfer the bytes */ + received = usb_do_io(info->ep_fd[ep_index], + info->ep_status_fd[ep_index], + data, size, READ); + + /* close the endpoint so we stop polling the endpoint now */ + (void) close(info->ep_fd[ep_index]); + (void) close(info->ep_status_fd[ep_index]); + info->ep_fd[ep_index] = -1; + info->ep_status_fd[ep_index] = -1; + + return (received); +} + +/* + * usb_get_string: + * gets a raw unicode string + * + * Returns: number of bytes transferred or negative errno + */ +int +usb_get_string(usb_dev_handle *dev, int index, int langid, char *buf, + size_t buflen) +{ + /* + * We can't use usb_get_descriptor() because it's lacking the index + * parameter. This will be fixed in libusb 1.0 + */ + usb_dprintf(DEBUG_FUNCTIONS, + "usb_get_string(): index=%d langid=0x%x len=%d\n", + index, langid, buflen); + + if ((dev == NULL) || (buf == NULL) || (buflen == 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_get_string(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + + + return (usb_control_msg(dev, USB_DEV_REQ_DEV_TO_HOST, + USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, + langid, buf, (int)buflen, 1000)); +} + +/* + * usb_get_string_simple: + * gets a cooked ascii string + * + * Returns: number of bytes transferred or negative errno + */ +int +usb_get_string_simple(usb_dev_handle *dev, int index, char *buf, + size_t buflen) +{ + char tbuf[256]; + int ret, langid, si, di; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_get_string_simple(): index=%d\n", + index); + + if ((dev == NULL) || (buf == NULL) || (buflen == 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_get_string_simple(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + + (void) memset(buf, 0, buflen); + + /* + * Asking for the zero'th index is special - it returns a string + * descriptor that contains all the language IDs supported by the + * device. Typically there aren't many - often only one. The + * language IDs are 16 bit numbers, and they start at the third byte + * in the descriptor. See USB 2.0 specification, section 9.6.7, for + * more information on this. + */ + ret = usb_get_string(dev, index, 0, tbuf, sizeof (tbuf)); + usb_dprintf(DEBUG_DETAILED, "usb_get_string() returned %d\n", ret); + + if (ret < 4) { + langid = 0x409; + } else { + langid = tbuf[2] | (tbuf[3] << 8); + } + + usb_dprintf(DEBUG_DETAILED, "using langid=0x%x\n", langid); + + ret = usb_get_string(dev, index, langid, tbuf, sizeof (tbuf)); + if (ret < 0) { + + return (ret); + } + if (tbuf[1] != USB_DT_STRING) { + + return (-EIO); + } + if (tbuf[0] > ret) { + + return (-EFBIG); + } + + for (di = 0, si = 2; si < tbuf[0]; si += 2) { + if (di >= ((int)buflen - 1)) { + break; + } + + if (tbuf[si + 1]) { /* high byte */ + buf[di++] = '?'; + } else { + buf[di++] = tbuf[si]; + } + } + + buf[di] = 0; + + return (di); +} + +/* + * usb_get_descriptor_by_endpoint: + * usb_get_descriptor: + * get a descriptor. by_endpoint does not appear to make + * much sense. + * + * Returns: number of bytes transferred or negative errno + */ +int +usb_get_descriptor_by_endpoint(usb_dev_handle *udev, int ep, + uchar_t type, uchar_t index, void *buf, int size) +{ + if ((udev == NULL) || (buf == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_get_descriptor_by_endpoint(): " + "NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + + (void) memset(buf, 0, size); + + return (usb_control_msg(udev, + ep | USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (type << 8) + index, 0, buf, size, 1000)); +} + +int +usb_get_descriptor(usb_dev_handle *udev, uchar_t type, + uchar_t index, void *buf, int size) +{ + if ((udev == NULL) || (buf == NULL) || (size <= 0)) { + usb_dprintf(DEBUG_ERRORS, + "usb_get_string_simple(): NULL handle or data\n"); + + return (usb_error(EINVAL)); + } + + (void) memset(buf, 0, size); + + return (usb_control_msg(udev, + USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (type << 8) + index, 0, buf, size, 1000)); +} + +/* + * usb_set_altinterface: + * switches to the alternate interface for the device + * Note that ugen does not really need this. It does implicit + * cfg and alt switches when the endpoint is opened. + * + * Returns: 0 or negative errno + */ +/* ARGSUSED */ +int +usb_set_altinterface(usb_dev_handle *dev, int alt) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + usb_device_specific_t *dev_specific; + int index, iface, err; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_set_altinterface(): " + "hdl=0x%x alt=%d\n", hdl, alt); + + if (hdl == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_set_altinterface(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + + info = hdl->info; + + if (libusb_api == API_RELAXED) { + if (info->claimed_interface == -1) { + /* + * usb_claim_interface() should always be called + * prior usb_set_altinterface(), but some apps + * do not do this, hence we call it here assuming + * the default interface. + */ + if ((err = usb_claim_interface(dev, 0))) { + + return (err); + } + } + } + + iface = info->claimed_interface; + dev_specific = (usb_device_specific_t *)(hdl->device->dev); + + usb_dprintf(DEBUG_DETAILED, "claimed=%d, cfgvalue=%d, hdl=0x%x\n", + info->claimed_interface, info->configuration_value, + dev_specific->claimed_interfaces[iface], hdl); + + if ((info->claimed_interface == -1) || + (info->configuration_value == -1) || + (hdl != dev_specific->claimed_interfaces[iface])) { + + return (usb_error(EACCES)); + } + + usb_close_all_eps(hdl); + + /* find the conf index */ + for (index = 0; index < hdl->device->descriptor.bNumConfigurations; + index++) { + if (info->configuration_value == + hdl->device->config[index].bConfigurationValue) { + break; + } + } + + usb_dprintf(DEBUG_DETAILED, + "cfg value=%d index=%d, iface=%d, alt=%d #alts=%d\n", + info->configuration_value, index, iface, alt, + hdl->device->config[index].interface[iface].num_altsetting); + + if ((alt < 0) || (alt >= hdl->device-> + config[index].interface[iface].num_altsetting)) { + + return (usb_error(EINVAL)); + } + + info->alternate = alt; + + usb_set_ep_iface_alts(hdl, info, index, iface, alt); + + return (0); +} + +/* + * usb_set_configuration: + * sets the configuration for the device. + * ugen implicitly switches configuration and rejects + * set configuration requests + * + * Returns: 0 + */ +/* ARGSUSED */ +int +usb_set_configuration(usb_dev_handle *dev, int configuration) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + int index, i; + + usb_dprintf(DEBUG_FUNCTIONS, + "usb_set_configuration(): config = %d\n", configuration); + + if (hdl == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_set_configuration(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + /* find the conf index */ + for (index = 0; index < hdl->device->descriptor.bNumConfigurations; + index++) { + if (configuration == + hdl->device->config[index].bConfigurationValue) { + break; + } + } + + if (index >= hdl->device->descriptor.bNumConfigurations) { + usb_dprintf(DEBUG_ERRORS, + "usb_set_configuration(): invalid\n"); + + return (usb_error(EINVAL)); + } + + usb_close_all_eps(hdl); + info->configuration_value = configuration; + info->configuration_index = index; + + /* reset ep arrays */ + for (i = 0; i < USB_MAXENDPOINTS; i++) { + info->ep_interface[i] = -1; + } + if (info->claimed_interface != -1) { + (void) usb_release_interface(dev, info->claimed_interface); + } + + return (0); +} + +/* + * usb_clear_halt: + * clears a halted endpoint + * ugen has auto clearing but we send the request anyways + * + * Returns: 0 or negative errno + */ +/* ARGSUSED */ +int +usb_clear_halt(usb_dev_handle *dev, unsigned int ep) +{ + int rval; + int index = usb_ep_index(ep); + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_clear_halt(): ep=0x%x\n", ep); + + if (dev == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_clear_halt(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + + usb_dprintf(DEBUG_DETAILED, "index=0x%x, ep_intf=%d\n", + index, info->ep_interface[index]); + + if (info->ep_interface[index] == -1) { + + return (usb_error(EINVAL)); + } + + + /* only check for ep > 0 */ + if (ep && ((rval = usb_check_access(dev) != 0))) { + + return (usb_error(rval)); + } + + rval = usb_control_msg(dev, + USB_DEV_REQ_HOST_TO_DEV | USB_RECIP_ENDPOINT, + USB_REQ_CLEAR_FEATURE, 0, ep, NULL, 0, 0); + + if (rval < 0) { + usb_error_str(errno, "could not clear feature on ep=0x%x", ep); + } + + return (rval); +} + +/* + * usb_claim_interface: + * ugen does not have a claim interface concept but all endpoints + * are opened exclusively. This provides some exclusion. However, + * the interrupt IN endpoint is closed after the read + * + * Returns: 0 or negative errno + */ +int +usb_claim_interface(usb_dev_handle *dev, int interface) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + usb_device_specific_t *dev_specific; + int index; + + if (hdl == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_claim_interface(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + info = hdl->info; + dev_specific = (usb_device_specific_t *)(hdl->device->dev); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_claim_interface(): hdl=0x%x: " + "interface = %d\n", hdl, interface); + + if (info->configuration_value == -1) { + index = 0; + } else { + /* find the conf index */ + for (index = 0; + index < hdl->device->descriptor.bNumConfigurations; + index++) { + if (info->configuration_value == + hdl->device->config[index].bConfigurationValue) { + break; + } + } + } + info->configuration_value = + hdl->device->config[index].bConfigurationValue; + info->configuration_index = index; + + usb_dprintf(DEBUG_DETAILED, "configuration_value=%d, index=%d\n", + info->configuration_value, index); + + /* is this a valid interface? */ + if ((interface < 0) || (interface > 255) || + (interface >= hdl->device->config[index].bNumInterfaces)) { + + return (usb_error(EINVAL)); + } + + /* already claimed? */ + if (dev_specific->claimed_interfaces[interface] == hdl) { + + return (0); + } + + if (info->claimed_interface != -1) { + + return (usb_error(EBUSY)); + } + + if (dev_specific->claimed_interfaces[interface] != 0) { + + return (usb_error(EBUSY)); + } + + usb_dprintf(DEBUG_DETAILED, "usb_claim_interface(): hdl=0x%x: " + "interface = %d, claimed by this udev=%d, by hdl=0x%x\n", + hdl, interface, info->claimed_interface, + dev_specific->claimed_interfaces[interface]); + + + info->claimed_interface = interface; + info->alternate = 0; + dev_specific->claimed_interfaces[interface] = hdl; + + usb_set_ep_iface_alts(hdl, info, index, interface, 0); + + return (0); +} + +/* + * usb_release_interface: + * releases the acquired interface + * + * Returns: 0 or negative errno + */ +/* ARGSUSED */ +int +usb_release_interface(usb_dev_handle *dev, int interface) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info; + usb_device_specific_t *dev_specific; + + if (hdl == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_release_interface(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + + info = hdl->info; + dev_specific = (usb_device_specific_t *)(hdl->device->dev); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_release_interface(): hdl=0x%x: " + "interface = %d\n", hdl, interface); + + if ((info->claimed_interface == -1) || + (info->claimed_interface != interface)) { + + return (usb_error(EINVAL)); + } + + usb_dprintf(DEBUG_DETAILED, "usb_release_interface(): hdl=0x%x: " + "interface = %d, claimed by this udev=%d, by hdl=0x%x\n", + hdl, interface, info->claimed_interface, + dev_specific->claimed_interfaces[interface]); + + dev_specific->claimed_interfaces[interface] = 0; + info->claimed_interface = -1; + + return (0); +} + +/* + * usb_resetep + * resets the endpoint + * + * Returns: 0 or negative errno + */ +int +usb_resetep(usb_dev_handle *dev, unsigned int ep) +{ + usb_dprintf(DEBUG_FUNCTIONS, "usb_resetep(): ep=0x%x\n", ep); + + return (usb_clear_halt(dev, ep)); +} + +/* + * usb_reset: + * resets the device + * Returns: -ENOTSUP + */ +/* ARGSUSED */ +int +usb_reset(usb_dev_handle * dev) +{ + int rval; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_reset():\n"); + + if (dev == NULL) { + usb_dprintf(DEBUG_ERRORS, + "usb_reset(): NULL handle\n"); + + return (usb_error(EINVAL)); + } + + if ((rval = usb_check_access(dev)) != 0) { + + return (usb_error(rval)); + } + + return (usb_error(ENOTSUP)); +} + +/* + * Helper functions + * + * usb_send_msg: + * creates setup data and send it + * + * Returns: number of bytes transferred or negative errno + */ +static int +usb_send_msg(int fd, int stat_fd, int requesttype, int request, int value, + int index, char *data, int size) +{ + uint8_t req[8]; + int rval; + + usb_dprintf(DEBUG_DETAILED, "usb_send_msg():\n" + "\trequesttype 0x%x\n" + "\trequest 0x%x\n" + "\tvalue 0x%x\n" + "\tindex 0x%x\n" + "\tsize 0x%x\n", + requesttype, request, value, index, size); + + req[0] = (uint8_t)requesttype; + req[1] = (uint8_t)request; + req[2] = (uint8_t)value; + req[3] = (uint8_t)(value >> 8); + req[4] = (uint8_t)index; + req[5] = (uint8_t)(index >> 8); + req[6] = (uint8_t)size; + req[7] = (uint8_t)(size >> 8); + + if (requesttype & USB_DEV_REQ_DEV_TO_HOST) { + rval = usb_do_io(fd, stat_fd, (char *)&req, + sizeof (req), WRITE); + } else { + /* append the write data */ + char *buffer; + + if ((buffer = malloc(size + 8)) == NULL) { + + return (usb_error(ENOMEM)); + } + + (void) memcpy(buffer, &req, 8); + (void) memcpy(&buffer[8], data, size); + rval = usb_do_io(fd, stat_fd, buffer, + (uint_t)sizeof (req) + size, WRITE); + free(buffer); + } + usb_dprintf(DEBUG_FUNCTIONS, "usb_send_msg(): rval=%d\n", rval); + + return (rval); +} + +/* + * usb_do_io: + * performs i/o to/from an endpoint and check the + * status of the device if error or short xfer. + * + * Returns: bytes transferred or negative errno + */ +static int +usb_do_io(int fd, int stat_fd, char *data, size_t size, int flag) +{ + int error; + ssize_t ret; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_do_io(): size=0x%x flag=%d\n", + size, flag); + + if (size == 0) { + + return (0); + } + + switch (flag) { + case READ: + ret = read(fd, data, size); + usb_dump_data(data, (int)size); + break; + case WRITE: + usb_dump_data(data, (int)size); + ret = write(fd, data, size); + break; + } + if (ret < 0) { + int save_errno = errno; + + /* usb_get_status will do a read and overwrite errno */ + error = usb_get_status(stat_fd); + usb_error_str(save_errno, "error %d doing io: errno=%d", + error, save_errno); + + return (-save_errno); + } + + usb_dprintf(DEBUG_FUNCTIONS, "usb_do_io(): amount=%d\n", ret); + + return ((int)ret); +} + +/* + * usb_check_access: + * basically checks if the interface has been claimed + * + * Returns: 0 or EACCES/EINVAL + */ +static int +usb_check_access(usb_dev_handle *dev) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info = hdl->info; + + if (hdl == NULL) { + + return (EINVAL); + } + info = hdl->info; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_check_access(): " + "hdl=0x%x conf=%d claimed=%d alternate=%d\n", + (int)hdl, info->configuration_value, + info->claimed_interface, info->alternate); + + if ((info->configuration_value == -1) || + (info->claimed_interface == -1) || + (info->alternate == -1)) { + + return (EACCES); + } + + return (0); +} + +/* + * usb_set_ep_iface_alts: + * initialize ep_interface arrays + */ +static void +usb_set_ep_iface_alts(usb_dev_handle_impl_t *hdl, usb_dev_handle_info_t *info, + int index, int interface, int alternate) +{ + struct usb_interface_descriptor *if_descr; + struct usb_endpoint_descriptor *ep_descr; + int i; + + /* reinitialize endpoint arrays */ + for (i = 0; i < USB_MAXENDPOINTS; i++) { + info->ep_interface[i] = -1; + } + + /* + * for the current config, this interface and alt, + * which endpoints are available + */ + if_descr = &hdl->device->config[index].interface[interface]. + altsetting[alternate]; + + usb_dprintf(DEBUG_DETAILED, "cfg%d.%d.%d has %d endpoints\n", + info->configuration_value, interface, alternate, + if_descr->bNumEndpoints); + + for (i = 0; i < if_descr->bNumEndpoints; i++) { + ep_descr = &hdl->device->config[index].interface[interface]. + altsetting[alternate].endpoint[i]; + info->ep_interface[usb_ep_index( + ep_descr->bEndpointAddress)] = interface; + } + + usb_dprintf(DEBUG_DETAILED, "ep_interface:\n"); + for (i = 0; i < USB_MAXENDPOINTS; i++) { + usb_dprintf(DEBUG_DETAILED, "%d ", + info->ep_interface[i]); + } + usb_dprintf(DEBUG_DETAILED, "\n"); +} + +/* + * usb_check_device_and_status_open: + * Make sure that the endpoint and status device for the endpoint + * can be opened, or have already been opened. + * + * Returns: errno + */ +static int +usb_check_device_and_status_open(usb_dev_handle *dev, int ep, int ep_type, + int mode) +{ + usb_dev_handle_impl_t *hdl = (usb_dev_handle_impl_t *)dev; + usb_dev_handle_info_t *info = hdl->info; + char *filename, *statfilename, cfg_num[16], alt_num[16]; + int fd, fdstat, index, rval; + + index = usb_ep_index(ep); + + usb_dprintf(DEBUG_FUNCTIONS, + "usb_check_device_and_status_open(): " + "ep=0x%x mode=%d index=%d\n", ep, mode, index); + + if ((rval = usb_check_access(dev)) != 0) { + + return (rval); + } + + if ((index < 0) || (index > 31)) { + + return (EINVAL); + } + + usb_dprintf(DEBUG_DETAILED, "claimed=%d ep_interface=%d\n", + info->claimed_interface, info->ep_interface[index]); + + if (info->claimed_interface != info->ep_interface[index]) { + + return (EINVAL); + } + + if ((info->ep_fd[index] > 0) && (info->ep_status_fd[index] > 0)) { + + return (0); + } + + if (ep == 0) { + /* should already be open */ + + return (0); + } + + if ((filename = malloc(PATH_MAX+1)) == NULL) { + + return (ENOMEM); + } + if ((statfilename = malloc(PATH_MAX+1)) == NULL) { + free(filename); + + return (ENOMEM); + } + + usb_dprintf(DEBUG_DETAILED, "cfgvalue=%d\n", + info->configuration_value); + + /* create filename */ + if (info->configuration_index > 0) { + (void) snprintf(cfg_num, sizeof (cfg_num), "cfg%d", + info->configuration_value); + } else { + (void) memset(cfg_num, 0, sizeof (cfg_num)); + } + + if (info->alternate > 0) { + (void) snprintf(alt_num, sizeof (alt_num), ".%d", + info->alternate); + } else { + (void) memset(alt_num, 0, sizeof (alt_num)); + } + + (void) snprintf(filename, PATH_MAX, "%s/%s/%sif%d%s%s%d", + hdl->device->bus->dirname, hdl->device->filename, + cfg_num, info->ep_interface[index], + alt_num, (ep & USB_EP_DIR_MASK) ? "in" : + "out", (ep & USB_EP_NUM_MASK)); + + usb_dprintf(DEBUG_DETAILED, + "usb_check_device_and_status_open: %s\n", filename); + + /* + * for interrupt IN endpoints, we need to enable one xfer + * mode before opening the endpoint + */ + (void) snprintf(statfilename, PATH_MAX, "%sstat", filename); + + if ((ep_type == USB_ENDPOINT_TYPE_INTERRUPT) && + (ep & USB_ENDPOINT_IN)) { + char control = USB_EP_INTR_ONE_XFER; + ssize_t count; + + /* open the status device node for the ep first RDWR */ + if ((fdstat = open(statfilename, O_RDWR)) == -1) { + usb_error_str(errno, "can't open %s RDWR: %d", + statfilename, errno); + + /* might be an older ugen version, try RDONLY */ + if ((fdstat = open(statfilename, + O_RDONLY)) == -1) { + usb_error_str(errno, + "can't open %s RDONLY: %d", + filename, errno); + free(filename); + free(statfilename); + + return (errno); + } + } else { + count = write(fdstat, &control, sizeof (control)); + + if (count != 1) { + /* this should have worked */ + usb_error_str(errno, "can't write to %s: %d", + filename, errno); + free(filename); + free(statfilename); + (void) close(fdstat); + + return (errno); + } + } + } else { + if ((fdstat = open(statfilename, O_RDONLY)) == -1) { + usb_error_str(errno, "can't open %s: %d", + statfilename, errno); + free(filename); + free(statfilename); + + return (errno); + } + } + + /* open the ep */ + if ((fd = open(filename, mode)) == -1) { + usb_error_str(errno, "can't open %s: %d", + filename, errno); + (void) close(fdstat); + free(filename); + free(statfilename); + + return (errno); + } + + free(filename); + free(statfilename); + info->ep_fd[index] = fd; + info->ep_status_fd[index] = fdstat; + + + return (0); +} + +/* + * usb_ep_index: + * creates an index from endpoint address that can + * be used to index into endpoint lists + * + * Returns: ep index (a number between 0 and 31) + */ +static uchar_t +usb_ep_index(uint8_t ep_addr) +{ + return ((ep_addr & USB_EP_NUM_MASK) + + ((ep_addr & USB_EP_DIR_MASK) ? 16 : 0)); +} + +/* + * usb_open_ep0: + * opens default pipe + * + * Returns: errno + */ +static int +usb_open_ep0(usb_dev_handle_impl_t *hdl) +{ + char *filename; + usb_device_specific_t *dev_specific = + (usb_device_specific_t *)(hdl->device->dev); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_open_ep0():\n"); + + if (dev_specific->ep0_fd) { + dev_specific->ref_count++; + hdl->info->ep_fd[0] = dev_specific->ep0_fd; + hdl->info->ep_status_fd[0] = dev_specific->ep0_fd_stat; + + usb_dprintf(DEBUG_DETAILED, + "usb_open_ep0(): already open, ref=%d\n", + dev_specific->ref_count); + + return (0); + } + + if ((filename = malloc(PATH_MAX+1)) == NULL) { + + return (ENOMEM); + } + + (void) snprintf(filename, PATH_MAX, "%s/%s/cntrl0", + hdl->device->bus->dirname, hdl->device->filename); + + usb_dprintf(DEBUG_DETAILED, "opening %s\n", filename); + + hdl->info->ep_fd[0] = open(filename, O_RDWR); + if (hdl->info->ep_fd[0] < 0) { + usb_dprintf(DEBUG_ERRORS, "opening ep0 failed, %d\n", + hdl->info->ep_fd[0]); + free(filename); + + return (errno); + } + + (void) snprintf(filename, PATH_MAX, "%s/%s/cntrl0stat", + hdl->device->bus->dirname, hdl->device->filename); + + usb_dprintf(DEBUG_DETAILED, "opening %s\n", filename); + + hdl->info->ep_status_fd[0] = open(filename, O_RDONLY); + if (hdl->info->ep_status_fd[0] < 0) { + free(filename); + + return (errno); + } + + /* allow sharing between multiple opens */ + dev_specific->ep0_fd = hdl->info->ep_fd[0]; + dev_specific->ep0_fd_stat = hdl->info->ep_status_fd[0]; + dev_specific->ref_count++; + + usb_dprintf(DEBUG_DETAILED, "ep0 opened\n"); + + free(filename); + + return (0); +} + +/* + * usb_close_ep0: + * closes default ep0 + */ +static void +usb_close_ep0(usb_dev_handle_impl_t *hdl) +{ + usb_device_specific_t *dev_specific = + (usb_device_specific_t *)(hdl->device->dev); + + usb_dprintf(DEBUG_FUNCTIONS, "usb_close_ep0():\n"); + + if (dev_specific->ep0_fd) { + if (--(dev_specific->ref_count) > 0) { + usb_dprintf(DEBUG_DETAILED, + "usb_close_ep0(): ref_count=%d\n", + dev_specific->ref_count); + + return; + } + } + + if (hdl->info->ep_fd[0] != -1) { + (void) close(hdl->info->ep_fd[0]); + hdl->info->ep_fd[0] = -1; + } + if (hdl->info->ep_status_fd[0] != -1) { + (void) close(hdl->info->ep_status_fd[0]); + hdl->info->ep_status_fd[0] = -1; + } + dev_specific->ep0_fd = 0; + dev_specific->ep0_fd_stat = 0; +} + +/* + * usb_close_all_eps: + * closes all open endpoints except 0 + */ +static void +usb_close_all_eps(usb_dev_handle_impl_t *hdl) +{ + int i; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_close_all_eps():\n"); + + for (i = 1; i < USB_MAXENDPOINTS; i++) { + if (hdl->info->ep_fd[i] != -1) { + (void) close(hdl->info->ep_fd[i]); + hdl->info->ep_fd[i] = -1; + } + if (hdl->info->ep_status_fd[i] != -1) { + (void) close(hdl->info->ep_status_fd[i]); + hdl->info->ep_status_fd[i] = -1; + } + } +} + +/* + * usb_setup_all_configs: + * parses config cloud for each config + * + * Returns: errno + */ +static int +usb_setup_all_configs(usb_dev_handle_impl_t *hdl) +{ + char buffer[USB_DEV_DESCR_SIZE]; + int rval, len; + uint_t index; + + if (hdl->device->config) { + + return (0); + } + + usb_dprintf(DEBUG_FUNCTIONS, "usb_setup_all_configs():\n"); + + /* get device descriptor */ + rval = usb_control_msg((usb_dev_handle *)hdl, + USB_DEV_REQ_DEV_TO_HOST | USB_TYPE_STANDARD, + USB_REQ_GET_DESCRIPTOR, USB_DESCR_TYPE_SETUP_DEV, + 0, buffer, USB_DEV_DESCR_SIZE, 0); + + usb_dprintf(DEBUG_DETAILED, "dev descr rval=%d\n", rval); + + if (rval != USB_DEV_DESCR_SIZE) { + + return (EIO); + } + + /* parse device descriptor */ + rval = (int)usb_parse_dev_descr((uchar_t *)buffer, sizeof (buffer), + (struct usb_device_descriptor *)&hdl->device->descriptor, + sizeof (struct usb_device_descriptor)); + + usb_dprintf(DEBUG_DETAILED, "parse dev descr rval=%d\n", rval); + + if (rval != (int)sizeof (struct usb_device_descriptor)) { + + return (EIO); + } + + /* allocate config array */ + len = (int) sizeof (struct usb_config_descriptor) * + hdl->device->descriptor.bNumConfigurations; + + if ((hdl->device->config = calloc(len, 1)) == NULL) { + + return (ENOMEM); + } + + /* parse each config cloud */ + for (index = 0; index < hdl->device->descriptor.bNumConfigurations; + index++) { + if ((rval = usb_parse_config(hdl, index)) != 0) { + + return (rval); + } + } + + return (0); +} + +/* + * usb_free_all_configs: + * frees all allocated resources + */ +static void +usb_free_all_configs(usb_device_t *dev) +{ + uint_t index; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_free_all_configs(): " + "dev=0x%x config=0x%x #conf=%d\n", + dev, dev->config, dev->descriptor.bNumConfigurations); + + if (dev->config) { + for (index = 0; index < dev->descriptor.bNumConfigurations; + index++) { + usb_free_config(dev, index); + } + free(dev->config); + dev->config = NULL; + } +} + +/* + * usb_parse_config: + * parse config descriptor and get cloud + * + * Returns: errno + */ +static int +usb_parse_config(usb_dev_handle_impl_t *hdl, uint_t index) +{ + int rval; + uint_t iface, len; + char buffer[USB_CFG_DESCR_SIZE]; + char *cloud; + unsigned char *extra; + int extralen; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_parse_config(): index=%d\n", + index); + + rval = usb_control_msg((usb_dev_handle *)hdl, + USB_DEV_REQ_DEV_TO_HOST | USB_TYPE_STANDARD, + USB_REQ_GET_DESCRIPTOR, USB_DESCR_TYPE_SETUP_CFG | index, + 0, buffer, USB_CFG_DESCR_SIZE, 0); + + usb_dprintf(DEBUG_DETAILED, "config descr rval=%d expecting %d\n", + rval, USB_CFG_DESCR_SIZE); + + if (rval < USB_CFG_DESCR_SIZE) { + + return (EIO); + } + + rval = (int)usb_parse_cfg_descr((uchar_t *)buffer, sizeof (buffer), + (usb_cfg_descr_t *)&hdl->device->config[index], + sizeof (usb_cfg_descr_t), &extra, &extralen); + + usb_dprintf(DEBUG_DETAILED, "config descr rval=%d expecting %d\n", + rval, sizeof (usb_cfg_descr_t)); + + if (rval < USB_CFG_DESCR_SIZE) { + + return (EIO); + } + + usb_dprintf(DEBUG_DETAILED, + "cfg%d: len=%d type=%d total=%d #if=%d cf=%d\n", index, + hdl->device->config[index].bLength, + hdl->device->config[index].bDescriptorType, + hdl->device->config[index].wTotalLength, + hdl->device->config[index].bNumInterfaces, + hdl->device->config[index].bConfigurationValue); + + if ((cloud = malloc(hdl->device->config[index].wTotalLength)) == + NULL) { + + return (ENOMEM); + } + + /* get complete cloud */ + rval = usb_control_msg((usb_dev_handle *)hdl, + USB_DEV_REQ_DEV_TO_HOST | USB_TYPE_STANDARD, + USB_REQ_GET_DESCRIPTOR, USB_DESCR_TYPE_SETUP_CFG | index, + 0, (char *)cloud, + hdl->device->config[index].wTotalLength, 0); + + if (rval != hdl->device->config[index].wTotalLength) { + free(cloud); + + return (EIO); + } + + /* parse descriptor again to get extra descriptors */ + rval = (int)usb_parse_cfg_descr((uchar_t *)cloud, + hdl->device->config[index].wTotalLength, + (usb_cfg_descr_t *)&hdl->device->config[index], + sizeof (usb_cfg_descr_t), &extra, &extralen); + + if (extralen) { + usb_dprintf(DEBUG_DETAILED, + "cfg%d: extra descriptors length=%d:\n", + index, extralen); + usb_dump_data((char *)extra, extralen); + + if ((hdl->device->config[index].extra = + calloc(extralen, 1)) == NULL) { + free(cloud); + + return (ENOMEM); + } + + (void) memcpy(hdl->device->config[index].extra, extra, + extralen); + hdl->device->config[index].extralen = extralen; + } + + /* allocate interface array */ + len = hdl->device->config[index].bNumInterfaces * + (int)sizeof (struct usb_interface); + if ((hdl->device->config[index].interface = calloc(len, 1)) == + NULL) { + free(cloud); + + return (ENOMEM); + } + + for (iface = 0; iface < hdl->device->config[index].bNumInterfaces; + iface++) { + rval = usb_parse_interface(hdl, index, iface, cloud); + if (rval != 0) { + free(cloud); + + return (rval); + } + } + free(cloud); + + return (0); +} + +/* + * usb_free_config: + * frees all allocated config resources + */ +static void +usb_free_config(usb_device_t *device, uint_t index) +{ + uint_t iface; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_free_config(): index=%d\n", + index); + + if (device->config[index].interface) { + for (iface = 0; iface < device->config[index].bNumInterfaces; + iface++) { + usb_free_interface(device, index, iface); + } + if (device->config[index].extralen) { + free(device->config[index].extra); + } + free(device->config[index].interface); + } +} + +/* + * usb_parse_interface: + * parse an interface descriptor + * + * Returns: errno + */ +static int +usb_parse_interface(usb_dev_handle_impl_t *hdl, uint_t index, uint_t iface, + char *cloud) +{ + usb_if_descr_t if_descr; + int rval; + uint_t alt, max_alt, len; + unsigned char *extra; + int extralen; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_parse_interface(): " + "index=%d, iface=%d\n", index, iface); + + /* count the number of alternates for this interface */ + for (max_alt = alt = 0; alt < USB_MAXALTSETTING; alt++) { + rval = (int)usb_parse_if_descr((uchar_t *)cloud, + hdl->device->config[index].wTotalLength, + iface, alt, &if_descr, sizeof (if_descr), + &extra, &extralen); + + usb_dprintf(DEBUG_DETAILED, "usb_parse_interface: " + "alt %d: rval=%d expecting %d\n", + alt, rval, sizeof (if_descr)); + + if (rval != (int)sizeof (if_descr)) { + + break; + } + max_alt = alt; + } + + usb_dprintf(DEBUG_DETAILED, + "usb_parse_interface: max_alt=%d\n", max_alt); + + /* allocate alt interface setting array */ + len = ++max_alt * (int)sizeof (struct usb_interface_descriptor); + if ((hdl->device->config[index].interface[iface].altsetting = + calloc(len, 1)) == NULL) { + + return (ENOMEM); + } + hdl->device->config[index].interface[iface].num_altsetting = + max_alt; + for (alt = 0; alt < max_alt; alt++) { + rval = usb_parse_alternate(hdl, index, iface, alt, cloud); + if (rval != 0) { + + return (rval); + } + } + + return (0); +} + +/* + * usb_free_interface: + * frees interface resources + * + * Returns: errno + */ +static void +usb_free_interface(usb_device_t *device, uint_t index, uint_t iface) +{ + uint_t alt, max_alt; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_free_interface(): " + "index=%d, iface=%d\n", index, iface); + + if (device->config[index].interface[iface].altsetting) { + max_alt = device->config[index].interface[iface]. + num_altsetting; + for (alt = 0; alt < max_alt; alt++) { + usb_free_alternate(device, index, iface, alt); + } + free(device->config[index].interface[iface].altsetting); + } +} + +/* + * usb_parse_alternate: + * parses each alternate descriptor + * + * Returns: errno + */ +static int +usb_parse_alternate(usb_dev_handle_impl_t *hdl, uint_t index, uint_t iface, + uint_t alt, char *cloud) +{ + uint_t ep, len; + usb_if_descr_t if_descr; + int rval; + unsigned char *extra; + int extralen; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_parse_alternate(): " + "index=%d, iface=%d, alt=%d\n", index, iface, alt); + + rval = (int)usb_parse_if_descr((uchar_t *)cloud, + hdl->device->config[index].wTotalLength, + iface, alt, &if_descr, sizeof (if_descr), &extra, &extralen); + + if (rval != (int)sizeof (if_descr)) { + usb_dprintf(DEBUG_ERRORS, "usb_parse_alternate: rval=%d\n", + rval); + + return (EIO); + } + + usb_dprintf(DEBUG_DETAILED, + "cfg%d.if%d.%d: len=%d type=%d num=%d alt=%d #ep=%d c=%d" + " sc=%d p=%d i=%d\n", index, iface, alt, + if_descr.bLength, + if_descr.bDescriptorType, + if_descr.bInterfaceNumber, + if_descr.bAlternateSetting, + if_descr.bNumEndpoints, + if_descr.bInterfaceClass, + if_descr.bInterfaceSubClass, + if_descr.bInterfaceProtocol, + if_descr.iInterface); + + (void) memcpy( + &hdl->device->config[index].interface[iface].altsetting[alt], + &if_descr, sizeof (if_descr)); + + if (extralen) { + usb_dprintf(DEBUG_DETAILED, + "cfg%d.if%d.%d: extralen=%d:\n", index, iface, alt, + extralen); + usb_dump_data((char *)extra, extralen); + + if ((hdl->device->config[index].interface[iface]. + altsetting[alt].extra = calloc(extralen, 1)) == NULL) { + + return (ENOMEM); + } + (void) memcpy( + hdl->device->config[index].interface[iface]. + altsetting[alt].extra, extra, extralen); + hdl->device->config[index].interface[iface]. + altsetting[alt].extralen = extralen; + } + + if (if_descr.bNumEndpoints == 0) { + + return (0); + } + + /* allocate endpoint array for this alternate */ + len = if_descr.bNumEndpoints * + (int)sizeof (struct usb_endpoint_descriptor); + if ((hdl->device->config[index].interface[iface].altsetting[alt]. + endpoint = calloc(len, 1)) == NULL) { + + return (ENOMEM); + } + + for (ep = 0; ep < if_descr.bNumEndpoints; ep++) { + rval = usb_parse_endpoint(hdl, index, iface, alt, ep, cloud); + + if (rval != 0) { + + return (rval); + } + } + + return (0); +} + +/* + * usb_free_alternate: + * frees all alternate resources + */ +static void +usb_free_alternate(usb_device_t *device, uint_t index, uint_t iface, + uint_t alt) +{ + usb_dprintf(DEBUG_FUNCTIONS, "usb_free_alternate(): " + "index=%d, iface=%d, alt=%d\n", index, iface, alt); + + if (device->config[index].interface[iface].altsetting[alt]. + endpoint) { + uint_t ep; + struct usb_interface_descriptor *if_descr = + &device->config[index]. + interface[iface].altsetting[alt]; + + for (ep = 0; ep < if_descr->bNumEndpoints; ep++) { + if (if_descr->extralen) { + free(if_descr->extra); + } + } + + if (device->config[index].interface[iface].altsetting[alt]. + extralen) { + free(device->config[index].interface[iface]. + altsetting[alt].extra); + } + free(device->config[index].interface[iface].altsetting[alt]. + endpoint); + } +} + +/* + * usb_parse_endpoint: + * parses an endpoint descriptor + * + * Returns: errno + */ +static int +usb_parse_endpoint(usb_dev_handle_impl_t *hdl, int index, int iface, + int alt, int ep, char *cloud) +{ + usb_ep_descr_t ep_descr; + int rval; + unsigned char *extra; + int extralen; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_parse_endpoint(): " + "index=%d, iface=%d, alt=%d, ep=0x%x\n", + index, iface, alt, ep); + + rval = (int)usb_parse_ep_descr((uchar_t *)cloud, + hdl->device->config[index].wTotalLength, + iface, alt, ep, &ep_descr, sizeof (ep_descr), + &extra, &extralen); + + if (rval < USB_EP_DESCR_SIZE) { + usb_dprintf(DEBUG_ERRORS, "usb_parse_endpoint: rval=%d, " + "expecting %d\n", rval, USB_EP_DESCR_SIZE); + + return (rval); + } + + usb_dprintf(DEBUG_DETAILED, + "\tl=%d t=%d a=0x%x attr=0x%x max=%d int=%d\n", + ep_descr.bLength, ep_descr.bDescriptorType, + ep_descr.bEndpointAddress, ep_descr.bmAttributes, + ep_descr.wMaxPacketSize, ep_descr.bInterval); + + (void) memcpy(&hdl->device-> + config[index].interface[iface].altsetting[alt].endpoint[ep], + &ep_descr, sizeof (ep_descr)); + + if (extralen) { + usb_dprintf(DEBUG_DETAILED, + "cfg%d.if%d.%d.ep%d: extralen=%d:\n", + index, iface, alt, ep, extralen); + usb_dump_data((char *)extra, extralen); + + if ((hdl->device->config[index].interface[iface]. + altsetting[alt].endpoint[ep].extra = + calloc(extralen, 1)) == NULL) { + + return (ENOMEM); + } + (void) memcpy(hdl->device->config[index].interface[iface]. + altsetting[alt].endpoint[ep].extra, extra, extralen); + hdl->device->config[index].interface[iface]. + altsetting[alt].endpoint[ep].extralen = extralen; + } + + return (0); +} + +/* + * usb_add_device: + * adds dev to the beginning of the list + */ +static void +usb_add_device(usb_device_t **list, usb_device_t *dev) +{ + if (*list) { + dev->next = *list; + dev->next->prev = dev; + } else { + dev->next = NULL; + } + dev->prev = NULL; + *list = dev; +} + +/* + * usb_remove_device: + * removes dev from a list + */ +static void +usb_remove_device(usb_device_t **list, usb_device_t *dev) +{ + if (dev->prev) { + dev->prev->next = dev->next; + } else { + *list = dev->next; + } + if (dev->next) { + dev->next->prev = dev->prev; + } + dev->prev = dev->next = NULL; +} + +/* + * usb_check_device_in_list: + * checks if dev is in list + * + * Returns: 1 (yes), 0 (no) + */ +static int +usb_check_device_in_list(usb_device_t *list, usb_device_t *dev) +{ + usb_device_t *d = list; + + while (d != NULL) { + if (d == dev) { + + return (1); + } + d = d->next; + } + + return (0); +} + +/* + * usb_free_bus: + * frees the entire bus structure, not used, just for + * completeness + */ +static void +usb_free_bus(usb_bus_t *bus) +{ + free(bus); +} + +/* + * usb_free_dev: + * frees all configs and then the device structure itself + */ +static void +usb_free_dev(usb_device_t *dev) +{ + usb_dprintf(DEBUG_FUNCTIONS, "usb_free_dev(): 0x%x\n", (int)dev); + + usb_free_all_configs(dev); + free(dev->dev); + free(dev); +} + +/* + * usb_get_device_status: + * gets status of device + * + * Returns: ugen dev status values + */ +static int +usb_get_device_status(int fd) +{ + int status, error; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_get_device_status():\n"); + + error = (int)read(fd, &status, (size_t)sizeof (status)); + if (error != (int)sizeof (status)) { + usb_error_str(errno, "Could not read device status: %d", + error); + + return (USB_DEV_STAT_UNAVAILABLE); + } else { + switch (status) { + case USB_DEV_STAT_ONLINE: + usb_dprintf(DEBUG_DETAILED, "Device is available\n"); + break; + case USB_DEV_STAT_DISCONNECTED: + usb_dprintf(DEBUG_DETAILED, "Device has been " + "disconnected\n"); + break; + case USB_DEV_STAT_RESUMED: + usb_dprintf(DEBUG_DETAILED, + "Device has been resumed\n"); + break; + case USB_DEV_STAT_UNAVAILABLE: + usb_dprintf(DEBUG_DETAILED, + "Device is powered down\n"); + break; + default: + usb_dprintf(DEBUG_DETAILED, + "Device status=%d\n", status); + } + } + + return (status); +} + +/* + * usb_search_dev_usb: + * finds all names of devices in the /usb/dev tree + * this will be the VID/PID and instance no + * + * Returns: errno + */ +static int +usb_search_dev_usb(usb_device_t **new_devices) +{ + DIR *dir, *subdir; + struct dirent *dir_entry, *subdir_entry; + char *device, *filename; + usb_bus_t *bus = usb_busses; + struct stat statbuf; + regex_t regex; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_search_dev_usb():\n"); + + if ((device = malloc(PATH_MAX+1)) == NULL) { + + return (ENOMEM); + } + + if ((filename = malloc(PATH_MAX+1)) == NULL) { + free(device); + + return (ENOMEM); + } + + if (!(dir = opendir(bus->dirname))) { + free(device); + free(filename); + + usb_error_str(errno, + "couldn't opendir %s: %d", bus->dirname, errno); + + return (errno); + } + + /* make sure we only open ugen directories */ + if ((regcomp(®ex, "/dev/usb/[0-9a-f]+[.][0-9a-f]+", + REG_EXTENDED) != 0)) { + + return (EINVAL); + } + + /* search for devices */ + while ((dir_entry = readdir(dir)) != NULL) { + + usb_dprintf(DEBUG_FUNCTIONS, "usb_search_dev_usb(): dir=%s\n", + dir_entry->d_name); + + if (dir_entry->d_name[0] == '.') { + continue; + } + (void) snprintf(device, PATH_MAX, "%s/%s", bus->dirname, + dir_entry->d_name); + + /* + * make sure we don't accidentily open /dev/usb/hid* nodes + * which will get them unlinked from the virtual console + */ + if (lstat(device, &statbuf) == -1) { + continue; + } + if (!S_ISDIR(statbuf.st_mode)) { + continue; + } + + if (regexec(®ex, device, 0, NULL, 0) != 0) { + continue; + } + + usb_dprintf(DEBUG_FUNCTIONS, "checking %s\n", device); + + /* need to search instances */ + if (!(subdir = opendir(device))) { + continue; + } + + while ((subdir_entry = readdir(subdir)) != NULL) { + usb_device_t *dev; + usb_device_specific_t *dev_specific; + int fd; + + if (subdir_entry->d_name[0] == '.') { + continue; + } + + if ((dev = calloc(sizeof (*dev), 1)) == NULL) { + free(device); + free(filename); + regfree(®ex); + (void) closedir(subdir); + (void) closedir(dir); + + return (ENOMEM); + } + if ((dev_specific = calloc(sizeof (*dev_specific), + 1)) == NULL) { + free(device); + free(filename); + free(dev); + regfree(®ex); + (void) closedir(subdir); + (void) closedir(dir); + + return (ENOMEM); + } + + dev->dev = (void *)dev_specific; + dev->bus = bus; + (void) snprintf(dev->filename, PATH_MAX, "%s/%s", + dir_entry->d_name, subdir_entry->d_name); + + /* See if the device is online */ + (void) snprintf(filename, PATH_MAX, "%s/%s/devstat", + bus->dirname, dev->filename); + + usb_dprintf(DEBUG_DETAILED, "filename %s\n", filename); + usb_dprintf(DEBUG_DETAILED, "dev filename %s\n", + dev->filename); + + if ((fd = open(filename, O_RDONLY|O_EXCL)) < 0) { + usb_dprintf(DEBUG_ERRORS, + "usb_search_dev_usb: Couldn't open %s\n", + filename); + free(dev_specific); + free(dev); + continue; + } + if (usb_get_device_status(fd) != USB_DEV_STAT_ONLINE) { + (void) close(fd); + usb_error_str(EIO, "Device %s is not online", + dev->filename); + + free(dev_specific); + free(dev); + continue; + } + (void) close(fd); + + usb_add_device(new_devices, dev); + } + + (void) closedir(subdir); + } + + regfree(®ex); + free(filename); + free(device); + (void) closedir(dir); + + return (0); +} + +/* + * usb_get_status: + * gets status of endpoint + * + * Returns: ugen's last cmd status + */ +static int +usb_get_status(int fd) +{ + int status, error; + + usb_dprintf(DEBUG_FUNCTIONS, "usb_get_status(): fd=%d\n", fd); + + error = (int)read(fd, &status, sizeof (status)); + if (error == (int)sizeof (status)) { + switch (status) { + case USB_LC_STAT_NOERROR: + usb_dprintf(DEBUG_DETAILED, "No Error\n"); + break; + case USB_LC_STAT_CRC: + usb_dprintf(DEBUG_ERRORS, "CRC Timeout Detected\n"); + break; + case USB_LC_STAT_BITSTUFFING: + usb_dprintf(DEBUG_ERRORS, "Bit Stuffing Violation\n"); + break; + case USB_LC_STAT_DATA_TOGGLE_MM: + usb_dprintf(DEBUG_ERRORS, "Data Toggle Mismatch\n"); + break; + case USB_LC_STAT_STALL: + usb_dprintf(DEBUG_ERRORS, "End Point Stalled\n"); + break; + case USB_LC_STAT_DEV_NOT_RESP: + usb_dprintf(DEBUG_ERRORS, "Device is Not Responding\n"); + break; + case USB_LC_STAT_PID_CHECKFAILURE: + usb_dprintf(DEBUG_ERRORS, "PID Check Failure\n"); + break; + case USB_LC_STAT_UNEXP_PID: + usb_dprintf(DEBUG_ERRORS, "Unexpected PID\n"); + break; + case USB_LC_STAT_DATA_OVERRUN: + usb_dprintf(DEBUG_ERRORS, "Data Exceeded Size\n"); + break; + case USB_LC_STAT_DATA_UNDERRUN: + usb_dprintf(DEBUG_ERRORS, "Less data received\n"); + break; + case USB_LC_STAT_BUFFER_OVERRUN: + usb_dprintf(DEBUG_ERRORS, "Buffer Size Exceeded\n"); + break; + case USB_LC_STAT_BUFFER_UNDERRUN: + usb_dprintf(DEBUG_ERRORS, "Buffer Underrun\n"); + break; + case USB_LC_STAT_TIMEOUT: + usb_dprintf(DEBUG_ERRORS, "Command Timed Out\n"); + break; + case USB_LC_STAT_NOT_ACCESSED: + usb_dprintf(DEBUG_ERRORS, "Not Accessed by h/w\n"); + break; + case USB_LC_STAT_UNSPECIFIED_ERR: + usb_dprintf(DEBUG_ERRORS, "Unspecified Error\n"); + break; + case USB_LC_STAT_NO_BANDWIDTH: + usb_dprintf(DEBUG_ERRORS, "No Bandwidth\n"); + break; + case USB_LC_STAT_HW_ERR: + usb_dprintf(DEBUG_ERRORS, + "Host Controller h/w Error\n"); + break; + case USB_LC_STAT_SUSPENDED: + usb_dprintf(DEBUG_ERRORS, "Device was Suspended\n"); + break; + case USB_LC_STAT_DISCONNECTED: + usb_dprintf(DEBUG_ERRORS, "Device was Disconnected\n"); + break; + case USB_LC_STAT_INTR_BUF_FULL: + usb_dprintf(DEBUG_ERRORS, + "Interrupt buffer was full\n"); + break; + case USB_LC_STAT_INVALID_REQ: + usb_dprintf(DEBUG_ERRORS, "Request was Invalid\n"); + break; + case USB_LC_STAT_INTERRUPTED: + usb_dprintf(DEBUG_ERRORS, "Request was Interrupted\n"); + break; + case USB_LC_STAT_NO_RESOURCES: + usb_dprintf(DEBUG_ERRORS, "No resources available for " + "request\n"); + break; + case USB_LC_STAT_INTR_POLLING_FAILED: + usb_dprintf(DEBUG_ERRORS, "Failed to Restart Poll"); + break; + default: + usb_dprintf(DEBUG_ERRORS, "Error Not Determined %d\n", + status); + break; + } + } + + return (status); +} + +/* + * Descriptor parsing functions, taken from USBA code + * + * usb_parse_data: + * take a raw buffer and pads it according to format + * + * Returns: USB_PARSE_ERROR or length parsed + */ +static size_t +usb_parse_data(char *format, uchar_t *data, size_t datalen, + void *structure, size_t structlen) +{ + int fmt; + size_t counter = 1; + int multiplier = 0; + uchar_t *dataend = data + datalen; + char *structstart = (char *)structure; + void *structend = (void *)((intptr_t)structstart + structlen); + + if ((format == NULL) || (data == NULL) || (structure == NULL)) { + + return (USB_PARSE_ERROR); + } + + while ((fmt = *format) != '\0') { + + /* + * Could some one pass a "format" that is greater than + * the structlen? Conversely, one could pass a ret_buf_len + * that is less than the "format" length. + * If so, we need to protect against writing over memory. + */ + if (counter++ > structlen) { + break; + } + + if (fmt == 'c') { + uint8_t *cp = (uint8_t *)structure; + + cp = (uint8_t *)(((uintptr_t)cp + _CHAR_ALIGNMENT - 1) & + ~(_CHAR_ALIGNMENT - 1)); + if (((data + 1) > dataend) || + ((cp + 1) > (uint8_t *)structend)) + break; + + *cp++ = *data++; + structure = (void *)cp; + if (multiplier) { + multiplier--; + } + if (multiplier == 0) { + format++; + } + } else if (fmt == 's') { + uint16_t *sp = (uint16_t *)structure; + + sp = (uint16_t *) + (((uintptr_t)sp + _SHORT_ALIGNMENT - 1) & + ~(_SHORT_ALIGNMENT - 1)); + if (((data + 2) > dataend) || + ((sp + 1) > (uint16_t *)structend)) + break; + + *sp++ = (data[1] << 8) + data[0]; + data += 2; + structure = (void *)sp; + if (multiplier) { + multiplier--; + } + if (multiplier == 0) { + format++; + } + } else if (fmt == 'l') { + uint32_t *lp = (uint32_t *)structure; + + lp = (uint32_t *) + (((uintptr_t)lp + _INT_ALIGNMENT - 1) & + ~(_INT_ALIGNMENT - 1)); + if (((data + 4) > dataend) || + ((lp + 1) > (uint32_t *)structend)) + break; + + *lp++ = ((((( + (uint32_t)data[3] << 8) | data[2]) << 8) | + data[1]) << 8) | data[0]; + data += 4; + structure = (void *)lp; + if (multiplier) { + multiplier--; + } + if (multiplier == 0) { + format++; + } + } else if (fmt == 'L') { + uint64_t *llp = (uint64_t *)structure; + + llp = (uint64_t *) + (((uintptr_t)llp + _LONG_LONG_ALIGNMENT - 1) & + ~(_LONG_LONG_ALIGNMENT - 1)); + if (((data + 8) > dataend) || + ((llp + 1) >= (uint64_t *)structend)) + break; + + *llp++ = (((((((((((((data[7] << 8) | + data[6]) << 8) | data[5]) << 8) | + data[4]) << 8) | data[3]) << 8) | + data[2]) << 8) | data[1]) << 8) | + data[0]; + data += 8; + structure = (void *)llp; + if (multiplier) { + multiplier--; + } + if (multiplier == 0) { + format++; + } + } else if (isdigit(fmt)) { + multiplier = (multiplier * 10) + (fmt - '0'); + counter--; + format++; + } else { + multiplier = 0; + break; + } + } + + return ((intptr_t)structure - (intptr_t)structstart); +} + +/* + * usb_nth_descr: + * finds pointer to n-th descriptor of + * type descr_type, unless the end of the buffer or a descriptor + * of type stop_descr_type1 or stop_descr_type2 is encountered first. + * + * Returns: returns pointer to n-th descriptor + */ +static uchar_t * +usb_nth_descr(uchar_t *buf, size_t buflen, int descr_type, uint_t n, + int stop_descr_type1, int stop_descr_type2) +{ + uchar_t *bufstart = buf; + uchar_t *bufend = buf + buflen; + + if (buf == NULL) { + + return (NULL); + } + + while (buf + 2 <= bufend) { + if ((buf != bufstart) && ((buf[1] == stop_descr_type1) || + (buf[1] == stop_descr_type2))) { + + return (NULL); + } + + if ((descr_type == USB_DESCR_TYPE_ANY) || + (buf[1] == descr_type)) { + if (n-- == 0) { + + return (buf); + } + } + + /* + * Check for a bad buffer. + * If buf[0] is 0, then this will be an infite loop + */ + INCREMENT_BUF(buf); + } + + return (NULL); +} + +/* + * usb_parse_dev_descr: + * parse device descriptor + * + * Returns: #bytes parsed + */ +static size_t +usb_parse_dev_descr(uchar_t *buf, size_t buflen, + struct usb_device_descriptor *ret_descr, size_t ret_buf_len) +{ + if ((buf == NULL) || (ret_descr == NULL) || + (buflen < 2) || (buf[1] != USB_DESCR_TYPE_DEV)) { + + return (USB_PARSE_ERROR); + } + + return (usb_parse_data("ccsccccssscccc", + buf, buflen, ret_descr, ret_buf_len)); +} + +/* + * usb_parse_cfg_descr: + * parse config descriptor + * + * Returns: #bytes parsed + */ +static size_t +usb_parse_cfg_descr(uchar_t *buf, size_t buflen, usb_cfg_descr_t *ret_descr, + size_t ret_buf_len, unsigned char **extra, int *extralen) +{ + size_t rval; + + if ((buf == NULL) || (ret_descr == NULL) || + (buflen < 2) || (buf[1] != USB_DESCR_TYPE_CFG)) { + + return (USB_PARSE_ERROR); + } + + rval = usb_parse_data("ccsccccc", + buf, buflen, ret_descr, ret_buf_len); + + usb_find_extra(buf, buflen, extra, extralen); + + return (rval); +} + +/* + * usb_parse_if_descr: + * parse interface descriptor + * + * Returns: #bytes parsed + */ +static size_t +usb_parse_if_descr(uchar_t *buf, size_t buflen, uint_t if_number, + uint_t alt_if_setting, usb_if_descr_t *ret_descr, size_t ret_buf_len, + unsigned char **extra, int *extralen) +{ + uchar_t *bufend = buf + buflen; + size_t rval; + + if ((buf == NULL) || (ret_descr == NULL)) { + + return (USB_PARSE_ERROR); + } + + while (buf + 4 <= bufend) { + if ((buf[1] == USB_DESCR_TYPE_IF) && + (buf[2] == if_number) && + (buf[3] == alt_if_setting)) { + + rval = usb_parse_data("ccccccccc", + buf, ((uintptr_t)bufend - (uintptr_t)buf), + ret_descr, ret_buf_len); + + usb_find_extra(buf, + ((uintptr_t)bufend - (uintptr_t)buf), + extra, extralen); + + return (rval); + } + + /* + * Check for a bad buffer. + * If buf[0] is 0, then this will be an infinite loop + */ + INCREMENT_BUF(buf); + } + + return (USB_PARSE_ERROR); +} + + +/* + * usb_parse_ep_descr: + * parse config descriptor + * the endpoint index is relative to the interface. index 0 is + * the first endpoint + * + * Returns: #bytes parsed + */ +size_t +usb_parse_ep_descr(uchar_t *buf, size_t buflen, uint_t if_number, + uint_t alt_if_setting, uint_t ep_index, usb_ep_descr_t *ret_descr, + size_t ret_buf_len, unsigned char **extra, int *extralen) +{ + uchar_t *bufend = buf + buflen; + size_t rval; + + if ((buf == NULL) || (ret_descr == NULL)) { + + return (USB_PARSE_ERROR); + } + + while ((buf + 4) <= bufend) { + if (buf[1] == USB_DESCR_TYPE_IF && + buf[2] == if_number && + buf[3] == alt_if_setting) { + if ((buf = usb_nth_descr(buf, + (uintptr_t)bufend - (uintptr_t)buf, + USB_DESCR_TYPE_EP, ep_index, + USB_DESCR_TYPE_IF, -1)) == NULL) { + + break; + } + + rval = usb_parse_data("ccccsccc", buf, + (uintptr_t)bufend - (uintptr_t)buf, + ret_descr, ret_buf_len); + usb_find_extra(buf, (uintptr_t)bufend - (uintptr_t)buf, + extra, extralen); + + return (rval); + } + + /* + * Check for a bad buffer. + * If buf[0] is 0, then this will be an infinite loop + */ + INCREMENT_BUF(buf); + } + + return (USB_PARSE_ERROR); +} + +/* + * extra descriptor handling + * + * usb_find_extra: + * finds any non-standard descriptor after the current + * standard descriptor and puts a pointer in extra argument + * and the length in extralen + */ +static void +usb_find_extra(uchar_t *buf, size_t buflen, + unsigned char **extra, int *extralen) +{ + uchar_t *next = buf + buf[0]; + + *extralen = 0; + *extra = next; + + while (((uintptr_t)next - (uintptr_t)buf + 1) < buflen) { + if ((next[1] == USB_DT_CONFIG) || + (next[1] == USB_DT_INTERFACE) || + (next[1] == USB_DT_ENDPOINT)) { + *extralen = (int)((uintptr_t)next - + (uintptr_t)buf - buf[0]); + + return; + } + next += next[0]; + } +} + +/* + * error handling + * + * usb_strerror: + * lookup error string + * + * Returns: error string + */ +char * +usb_strerror(void) +{ + usb_dprintf(DEBUG_FUNCTIONS, "usb_strerror(): " + "usb_error_type=%d, errno=%d\n", usb_error_type, usb_error_errno); + + switch (usb_error_type) { + case USB_ERROR_TYPE_NONE: + return ("No error"); + + case USB_ERROR_TYPE_STRING: + return (usb_error_string); + + case USB_ERROR_TYPE_ERRNO: + if (usb_error_errno > 0) { + + return (strerror(usb_error_errno)); + } + default: + break; + } + + return ("Unknown error"); +} + +/* + * usb_error: + * stores the error number in the global usb_error_errno + * + * Returns: negative error number + */ +static int +usb_error(int x) +{ + usb_dprintf(DEBUG_FUNCTIONS, "usb_error(): error=%d\n", x); + + usb_error_type = USB_ERROR_TYPE_ERRNO; + usb_error_errno = x; + + return (-x); +} + +/* + * usb_error_str: + * creates error string + */ +static void +usb_error_str(int x, char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + usb_error_type = USB_ERROR_TYPE_ERRNO; + usb_error_errno = x; + + (void) vsnprintf(usb_error_string, sizeof (usb_error_string), + format, ap); + + usb_dprintf(DEBUG_ERRORS, "USB error (%d): %s\n", x, usb_error_string); + + va_end(ap); +} + +/* + * usb_dprintf: + * prints out tracing messages according to level + */ +static void +usb_dprintf(int level, char *format, ...) +{ + va_list ap; + char buf[512]; + + va_start(ap, format); + + (void) vsnprintf(buf, sizeof (buf), format, ap); + if (libusb_debug >= level) { + (void) fprintf(stderr, buf); + } + va_end(ap); +} + +/* + * usb_dump_data: + * print data buffer + */ +static void +usb_dump_data(char *data, int size) +{ + int i; + + if (libusb_debug >= DEBUG_DATA_DUMP) { + (void) fprintf(stderr, "data dump:"); + for (i = 0; i < size; i++) { + if (i % 16 == 0) { + (void) fprintf(stderr, "\n%08x ", i); + } + (void) fprintf(stderr, "%02x ", (uchar_t)data[i]); + } + (void) fprintf(stderr, "\n"); + } +}