freebsd-dev/sys/dev/usb/usb_device.c
Hans Petter Selasky 963169b4af This commit adds full support for USB 3.0 devices in host and device
mode in the USB core.  The patch mostly consists of updating the USB
HUB code to support USB 3.0 HUBs. This patch also add some more USB
controller methods to support more active-alike USB controllers like
the XHCI which needs to be informed about various device state events.

USB 3.0 HUBs are not tested yet, due to lack of hardware, but are
believed to work.

After this update the initial device descriptor is only read twice
when we know that the bMaxPacketSize is too small for a single packet
transfer of this descriptor.

Approved by:    thompsa (mentor)
2010-10-04 23:18:05 +00:00

2686 lines
68 KiB
C

/* $FreeBSD$ */
/*-
* Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/stdint.h>
#include <sys/stddef.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/linker_set.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <sys/sx.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/priv.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usb_ioctl.h>
#if USB_HAVE_UGEN
#include <sys/sbuf.h>
#endif
#include "usbdevs.h"
#define USB_DEBUG_VAR usb_debug
#include <dev/usb/usb_core.h>
#include <dev/usb/usb_debug.h>
#include <dev/usb/usb_process.h>
#include <dev/usb/usb_device.h>
#include <dev/usb/usb_busdma.h>
#include <dev/usb/usb_transfer.h>
#include <dev/usb/usb_request.h>
#include <dev/usb/usb_dynamic.h>
#include <dev/usb/usb_hub.h>
#include <dev/usb/usb_util.h>
#include <dev/usb/usb_msctest.h>
#if USB_HAVE_UGEN
#include <dev/usb/usb_dev.h>
#include <dev/usb/usb_generic.h>
#endif
#include <dev/usb/quirk/usb_quirk.h>
#include <dev/usb/usb_controller.h>
#include <dev/usb/usb_bus.h>
/* function prototypes */
static void usb_init_endpoint(struct usb_device *, uint8_t,
struct usb_endpoint_descriptor *,
struct usb_endpoint_ss_comp_descriptor *,
struct usb_endpoint *);
static void usb_unconfigure(struct usb_device *, uint8_t);
static void usb_detach_device_sub(struct usb_device *, device_t *,
uint8_t);
static uint8_t usb_probe_and_attach_sub(struct usb_device *,
struct usb_attach_arg *);
static void usb_init_attach_arg(struct usb_device *,
struct usb_attach_arg *);
static void usb_suspend_resume_sub(struct usb_device *, device_t,
uint8_t);
static void usbd_clear_stall_proc(struct usb_proc_msg *_pm);
static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t);
static void usbd_set_device_strings(struct usb_device *);
#if USB_HAVE_UGEN
static void usb_notify_addq(const char *type, struct usb_device *);
static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t);
static struct cdev *usb_make_dev(struct usb_device *, int, int);
static void usb_cdev_create(struct usb_device *);
static void usb_cdev_free(struct usb_device *);
static void usb_cdev_cleanup(void *);
#endif
/* This variable is global to allow easy access to it: */
int usb_template = 0;
TUNABLE_INT("hw.usb.usb_template", &usb_template);
SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RW,
&usb_template, 0, "Selected USB device side template");
/* English is default language */
static int usb_lang_id = 0x0009;
static int usb_lang_mask = 0x00FF;
TUNABLE_INT("hw.usb.usb_lang_id", &usb_lang_id);
SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RW,
&usb_lang_id, 0, "Preferred USB language ID");
TUNABLE_INT("hw.usb.usb_lang_mask", &usb_lang_mask);
SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RW,
&usb_lang_mask, 0, "Preferred USB language mask");
static const char* statestr[USB_STATE_MAX] = {
[USB_STATE_DETACHED] = "DETACHED",
[USB_STATE_ATTACHED] = "ATTACHED",
[USB_STATE_POWERED] = "POWERED",
[USB_STATE_ADDRESSED] = "ADDRESSED",
[USB_STATE_CONFIGURED] = "CONFIGURED",
};
const char *
usb_statestr(enum usb_dev_state state)
{
return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN");
}
const char *
usb_get_manufacturer(struct usb_device *udev)
{
return (udev->manufacturer ? udev->manufacturer : "Unknown");
}
const char *
usb_get_product(struct usb_device *udev)
{
return (udev->product ? udev->product : "");
}
const char *
usb_get_serial(struct usb_device *udev)
{
return (udev->serial ? udev->serial : "");
}
/*------------------------------------------------------------------------*
* usbd_get_ep_by_addr
*
* This function searches for an USB ep by endpoint address and
* direction.
*
* Returns:
* NULL: Failure
* Else: Success
*------------------------------------------------------------------------*/
struct usb_endpoint *
usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val)
{
struct usb_endpoint *ep = udev->endpoints;
struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max;
enum {
EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR),
};
/*
* According to the USB specification not all bits are used
* for the endpoint address. Keep defined bits only:
*/
ea_val &= EA_MASK;
/*
* Iterate accross all the USB endpoints searching for a match
* based on the endpoint address:
*/
for (; ep != ep_end; ep++) {
if (ep->edesc == NULL) {
continue;
}
/* do the mask and check the value */
if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) {
goto found;
}
}
/*
* The default endpoint is always present and is checked separately:
*/
if ((udev->ctrl_ep.edesc) &&
((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) {
ep = &udev->ctrl_ep;
goto found;
}
return (NULL);
found:
return (ep);
}
/*------------------------------------------------------------------------*
* usbd_get_endpoint
*
* This function searches for an USB endpoint based on the information
* given by the passed "struct usb_config" pointer.
*
* Return values:
* NULL: No match.
* Else: Pointer to "struct usb_endpoint".
*------------------------------------------------------------------------*/
struct usb_endpoint *
usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index,
const struct usb_config *setup)
{
struct usb_endpoint *ep = udev->endpoints;
struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max;
uint8_t index = setup->ep_index;
uint8_t ea_mask;
uint8_t ea_val;
uint8_t type_mask;
uint8_t type_val;
DPRINTFN(10, "udev=%p iface_index=%d address=0x%x "
"type=0x%x dir=0x%x index=%d\n",
udev, iface_index, setup->endpoint,
setup->type, setup->direction, setup->ep_index);
/* check USB mode */
if (setup->usb_mode != USB_MODE_DUAL &&
udev->flags.usb_mode != setup->usb_mode) {
/* wrong mode - no endpoint */
return (NULL);
}
/* setup expected endpoint direction mask and value */
if (setup->direction == UE_DIR_RX) {
ea_mask = (UE_DIR_IN | UE_DIR_OUT);
ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ?
UE_DIR_OUT : UE_DIR_IN;
} else if (setup->direction == UE_DIR_TX) {
ea_mask = (UE_DIR_IN | UE_DIR_OUT);
ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ?
UE_DIR_IN : UE_DIR_OUT;
} else if (setup->direction == UE_DIR_ANY) {
/* match any endpoint direction */
ea_mask = 0;
ea_val = 0;
} else {
/* match the given endpoint direction */
ea_mask = (UE_DIR_IN | UE_DIR_OUT);
ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT));
}
/* setup expected endpoint address */
if (setup->endpoint == UE_ADDR_ANY) {
/* match any endpoint address */
} else {
/* match the given endpoint address */
ea_mask |= UE_ADDR;
ea_val |= (setup->endpoint & UE_ADDR);
}
/* setup expected endpoint type */
if (setup->type == UE_BULK_INTR) {
/* this will match BULK and INTERRUPT endpoints */
type_mask = 2;
type_val = 2;
} else if (setup->type == UE_TYPE_ANY) {
/* match any endpoint type */
type_mask = 0;
type_val = 0;
} else {
/* match the given endpoint type */
type_mask = UE_XFERTYPE;
type_val = (setup->type & UE_XFERTYPE);
}
/*
* Iterate accross all the USB endpoints searching for a match
* based on the endpoint address. Note that we are searching
* the endpoints from the beginning of the "udev->endpoints" array.
*/
for (; ep != ep_end; ep++) {
if ((ep->edesc == NULL) ||
(ep->iface_index != iface_index)) {
continue;
}
/* do the masks and check the values */
if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) &&
((ep->edesc->bmAttributes & type_mask) == type_val)) {
if (!index--) {
goto found;
}
}
}
/*
* Match against default endpoint last, so that "any endpoint", "any
* address" and "any direction" returns the first endpoint of the
* interface. "iface_index" and "direction" is ignored:
*/
if ((udev->ctrl_ep.edesc) &&
((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) &&
((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) &&
(!index)) {
ep = &udev->ctrl_ep;
goto found;
}
return (NULL);
found:
return (ep);
}
/*------------------------------------------------------------------------*
* usbd_interface_count
*
* This function stores the number of USB interfaces excluding
* alternate settings, which the USB config descriptor reports into
* the unsigned 8-bit integer pointed to by "count".
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_interface_count(struct usb_device *udev, uint8_t *count)
{
if (udev->cdesc == NULL) {
*count = 0;
return (USB_ERR_NOT_CONFIGURED);
}
*count = udev->ifaces_max;
return (USB_ERR_NORMAL_COMPLETION);
}
/*------------------------------------------------------------------------*
* usb_init_endpoint
*
* This function will initialise the USB endpoint structure pointed to by
* the "endpoint" argument. The structure pointed to by "endpoint" must be
* zeroed before calling this function.
*------------------------------------------------------------------------*/
static void
usb_init_endpoint(struct usb_device *udev, uint8_t iface_index,
struct usb_endpoint_descriptor *edesc,
struct usb_endpoint_ss_comp_descriptor *ecomp,
struct usb_endpoint *ep)
{
struct usb_bus_methods *methods;
methods = udev->bus->methods;
(methods->endpoint_init) (udev, edesc, ep);
/* initialise USB endpoint structure */
ep->edesc = edesc;
ep->ecomp = ecomp;
ep->iface_index = iface_index;
TAILQ_INIT(&ep->endpoint_q.head);
ep->endpoint_q.command = &usbd_pipe_start;
/* the pipe is not supported by the hardware */
if (ep->methods == NULL)
return;
/* clear stall, if any */
if (methods->clear_stall != NULL) {
USB_BUS_LOCK(udev->bus);
(methods->clear_stall) (udev, ep);
USB_BUS_UNLOCK(udev->bus);
}
}
/*-----------------------------------------------------------------------*
* usb_endpoint_foreach
*
* This function will iterate all the USB endpoints except the control
* endpoint. This function is NULL safe.
*
* Return values:
* NULL: End of USB endpoints
* Else: Pointer to next USB endpoint
*------------------------------------------------------------------------*/
struct usb_endpoint *
usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep)
{
struct usb_endpoint *ep_end;
/* be NULL safe */
if (udev == NULL)
return (NULL);
ep_end = udev->endpoints + udev->endpoints_max;
/* get next endpoint */
if (ep == NULL)
ep = udev->endpoints;
else
ep++;
/* find next allocated ep */
while (ep != ep_end) {
if (ep->edesc != NULL)
return (ep);
ep++;
}
return (NULL);
}
/*------------------------------------------------------------------------*
* usb_unconfigure
*
* This function will free all USB interfaces and USB endpoints belonging
* to an USB device.
*
* Flag values, see "USB_UNCFG_FLAG_XXX".
*------------------------------------------------------------------------*/
static void
usb_unconfigure(struct usb_device *udev, uint8_t flag)
{
uint8_t do_unlock;
/* automatic locking */
if (usbd_enum_is_locked(udev)) {
do_unlock = 0;
} else {
do_unlock = 1;
usbd_enum_lock(udev);
}
/* detach all interface drivers */
usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag);
#if USB_HAVE_UGEN
/* free all FIFOs except control endpoint FIFOs */
usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag);
/*
* Free all cdev's, if any.
*/
usb_cdev_free(udev);
#endif
#if USB_HAVE_COMPAT_LINUX
/* free Linux compat device, if any */
if (udev->linux_endpoint_start) {
usb_linux_free_device(udev);
udev->linux_endpoint_start = NULL;
}
#endif
usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE);
/* free "cdesc" after "ifaces" and "endpoints", if any */
if (udev->cdesc != NULL) {
if (udev->flags.usb_mode != USB_MODE_DEVICE)
free(udev->cdesc, M_USB);
udev->cdesc = NULL;
}
/* set unconfigured state */
udev->curr_config_no = USB_UNCONFIG_NO;
udev->curr_config_index = USB_UNCONFIG_INDEX;
if (do_unlock)
usbd_enum_unlock(udev);
}
/*------------------------------------------------------------------------*
* usbd_set_config_index
*
* This function selects configuration by index, independent of the
* actual configuration number. This function should not be used by
* USB drivers.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_set_config_index(struct usb_device *udev, uint8_t index)
{
struct usb_status ds;
struct usb_config_descriptor *cdp;
uint16_t power;
uint16_t max_power;
uint8_t selfpowered;
uint8_t do_unlock;
usb_error_t err;
DPRINTFN(6, "udev=%p index=%d\n", udev, index);
/* automatic locking */
if (usbd_enum_is_locked(udev)) {
do_unlock = 0;
} else {
do_unlock = 1;
usbd_enum_lock(udev);
}
usb_unconfigure(udev, 0);
if (index == USB_UNCONFIG_INDEX) {
/*
* Leave unallocated when unconfiguring the
* device. "usb_unconfigure()" will also reset
* the current config number and index.
*/
err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO);
if (udev->state == USB_STATE_CONFIGURED)
usb_set_device_state(udev, USB_STATE_ADDRESSED);
goto done;
}
/* get the full config descriptor */
if (udev->flags.usb_mode == USB_MODE_DEVICE) {
/* save some memory */
err = usbd_req_get_descriptor_ptr(udev, &cdp,
(UDESC_CONFIG << 8) | index);
} else {
/* normal request */
err = usbd_req_get_config_desc_full(udev,
NULL, &cdp, M_USB, index);
}
if (err) {
goto done;
}
/* set the new config descriptor */
udev->cdesc = cdp;
/* Figure out if the device is self or bus powered. */
selfpowered = 0;
if ((!udev->flags.uq_bus_powered) &&
(cdp->bmAttributes & UC_SELF_POWERED) &&
(udev->flags.usb_mode == USB_MODE_HOST)) {
/* May be self powered. */
if (cdp->bmAttributes & UC_BUS_POWERED) {
/* Must ask device. */
err = usbd_req_get_device_status(udev, NULL, &ds);
if (err) {
DPRINTFN(0, "could not read "
"device status: %s\n",
usbd_errstr(err));
} else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) {
selfpowered = 1;
}
DPRINTF("status=0x%04x \n",
UGETW(ds.wStatus));
} else
selfpowered = 1;
}
DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, "
"selfpowered=%d, power=%d\n",
udev, cdp,
udev->address, cdp->bConfigurationValue, cdp->bmAttributes,
selfpowered, cdp->bMaxPower * 2);
/* Check if we have enough power. */
power = cdp->bMaxPower * 2;
if (udev->parent_hub) {
max_power = udev->parent_hub->hub->portpower;
} else {
max_power = USB_MAX_POWER;
}
if (power > max_power) {
DPRINTFN(0, "power exceeded %d > %d\n", power, max_power);
err = USB_ERR_NO_POWER;
goto done;
}
/* Only update "self_powered" in USB Host Mode */
if (udev->flags.usb_mode == USB_MODE_HOST) {
udev->flags.self_powered = selfpowered;
}
udev->power = power;
udev->curr_config_no = cdp->bConfigurationValue;
udev->curr_config_index = index;
usb_set_device_state(udev, USB_STATE_CONFIGURED);
/* Set the actual configuration value. */
err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue);
if (err) {
goto done;
}
err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC);
if (err) {
goto done;
}
err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT);
if (err) {
goto done;
}
#if USB_HAVE_UGEN
/* create device nodes for each endpoint */
usb_cdev_create(udev);
#endif
done:
DPRINTF("error=%s\n", usbd_errstr(err));
if (err) {
usb_unconfigure(udev, 0);
}
if (do_unlock)
usbd_enum_unlock(udev);
return (err);
}
/*------------------------------------------------------------------------*
* usb_config_parse
*
* This function will allocate and free USB interfaces and USB endpoints,
* parse the USB configuration structure and initialise the USB endpoints
* and interfaces. If "iface_index" is not equal to
* "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the
* alternate_setting to be selected for the given interface. Else the
* "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be
* "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function
* is typically called when setting the configuration or when setting
* an alternate interface.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd)
{
struct usb_idesc_parse_state ips;
struct usb_interface_descriptor *id;
struct usb_endpoint_descriptor *ed;
struct usb_interface *iface;
struct usb_endpoint *ep;
usb_error_t err;
uint8_t ep_curr;
uint8_t ep_max;
uint8_t temp;
uint8_t do_init;
uint8_t alt_index;
if (iface_index != USB_IFACE_INDEX_ANY) {
/* parameter overload */
alt_index = cmd;
cmd = USB_CFG_INIT;
} else {
/* not used */
alt_index = 0;
}
err = 0;
DPRINTFN(5, "iface_index=%d cmd=%d\n",
iface_index, cmd);
if (cmd == USB_CFG_FREE)
goto cleanup;
if (cmd == USB_CFG_INIT) {
sx_assert(&udev->enum_sx, SA_LOCKED);
/* check for in-use endpoints */
ep = udev->endpoints;
ep_max = udev->endpoints_max;
while (ep_max--) {
/* look for matching endpoints */
if ((iface_index == USB_IFACE_INDEX_ANY) ||
(iface_index == ep->iface_index)) {
if (ep->refcount_alloc != 0) {
/*
* This typically indicates a
* more serious error.
*/
err = USB_ERR_IN_USE;
} else {
/* reset endpoint */
memset(ep, 0, sizeof(*ep));
/* make sure we don't zero the endpoint again */
ep->iface_index = USB_IFACE_INDEX_ANY;
}
}
ep++;
}
if (err)
return (err);
}
memset(&ips, 0, sizeof(ips));
ep_curr = 0;
ep_max = 0;
while ((id = usb_idesc_foreach(udev->cdesc, &ips))) {
/* check for interface overflow */
if (ips.iface_index == USB_IFACE_MAX)
break; /* crazy */
iface = udev->ifaces + ips.iface_index;
/* check for specific interface match */
if (cmd == USB_CFG_INIT) {
if ((iface_index != USB_IFACE_INDEX_ANY) &&
(iface_index != ips.iface_index)) {
/* wrong interface */
do_init = 0;
} else if (alt_index != ips.iface_index_alt) {
/* wrong alternate setting */
do_init = 0;
} else {
/* initialise interface */
do_init = 1;
}
} else
do_init = 0;
/* check for new interface */
if (ips.iface_index_alt == 0) {
/* update current number of endpoints */
ep_curr = ep_max;
}
/* check for init */
if (do_init) {
/* setup the USB interface structure */
iface->idesc = id;
/* default setting */
iface->parent_iface_index = USB_IFACE_INDEX_ANY;
/* set alternate index */
iface->alt_index = alt_index;
}
DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints);
ed = (struct usb_endpoint_descriptor *)id;
temp = ep_curr;
/* iterate all the endpoint descriptors */
while ((ed = usb_edesc_foreach(udev->cdesc, ed))) {
if (temp == USB_EP_MAX)
break; /* crazy */
ep = udev->endpoints + temp;
if (do_init) {
void *ecomp;
ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed);
if (ecomp != NULL)
DPRINTFN(5, "Found endpoint companion descriptor\n");
usb_init_endpoint(udev,
ips.iface_index, ed, ecomp, ep);
}
temp ++;
/* find maximum number of endpoints */
if (ep_max < temp)
ep_max = temp;
/* optimalisation */
id = (struct usb_interface_descriptor *)ed;
}
}
/* NOTE: It is valid to have no interfaces and no endpoints! */
if (cmd == USB_CFG_ALLOC) {
udev->ifaces_max = ips.iface_index;
udev->ifaces = NULL;
if (udev->ifaces_max != 0) {
udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max,
M_USB, M_WAITOK | M_ZERO);
if (udev->ifaces == NULL) {
err = USB_ERR_NOMEM;
goto done;
}
}
if (ep_max != 0) {
udev->endpoints = malloc(sizeof(*ep) * ep_max,
M_USB, M_WAITOK | M_ZERO);
if (udev->endpoints == NULL) {
err = USB_ERR_NOMEM;
goto done;
}
} else {
udev->endpoints = NULL;
}
USB_BUS_LOCK(udev->bus);
udev->endpoints_max = ep_max;
/* reset any ongoing clear-stall */
udev->ep_curr = NULL;
USB_BUS_UNLOCK(udev->bus);
}
done:
if (err) {
if (cmd == USB_CFG_ALLOC) {
cleanup:
USB_BUS_LOCK(udev->bus);
udev->endpoints_max = 0;
/* reset any ongoing clear-stall */
udev->ep_curr = NULL;
USB_BUS_UNLOCK(udev->bus);
/* cleanup */
if (udev->ifaces != NULL)
free(udev->ifaces, M_USB);
if (udev->endpoints != NULL)
free(udev->endpoints, M_USB);
udev->ifaces = NULL;
udev->endpoints = NULL;
udev->ifaces_max = 0;
}
}
return (err);
}
/*------------------------------------------------------------------------*
* usbd_set_alt_interface_index
*
* This function will select an alternate interface index for the
* given interface index. The interface should not be in use when this
* function is called. That means there should not be any open USB
* transfers. Else an error is returned. If the alternate setting is
* already set this function will simply return success. This function
* is called in Host mode and Device mode!
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_set_alt_interface_index(struct usb_device *udev,
uint8_t iface_index, uint8_t alt_index)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
usb_error_t err;
uint8_t do_unlock;
/* automatic locking */
if (usbd_enum_is_locked(udev)) {
do_unlock = 0;
} else {
do_unlock = 1;
usbd_enum_lock(udev);
}
if (iface == NULL) {
err = USB_ERR_INVAL;
goto done;
}
if (iface->alt_index == alt_index) {
/*
* Optimise away duplicate setting of
* alternate setting in USB Host Mode!
*/
err = 0;
goto done;
}
#if USB_HAVE_UGEN
/*
* Free all generic FIFOs for this interface, except control
* endpoint FIFOs:
*/
usb_fifo_free_wrap(udev, iface_index, 0);
#endif
err = usb_config_parse(udev, iface_index, alt_index);
if (err) {
goto done;
}
if (iface->alt_index != alt_index) {
/* the alternate setting does not exist */
err = USB_ERR_INVAL;
goto done;
}
err = usbd_req_set_alt_interface_no(udev, NULL, iface_index,
iface->idesc->bAlternateSetting);
done:
if (do_unlock)
usbd_enum_unlock(udev);
return (err);
}
/*------------------------------------------------------------------------*
* usbd_set_endpoint_stall
*
* This function is used to make a BULK or INTERRUPT endpoint send
* STALL tokens in USB device mode.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep,
uint8_t do_stall)
{
struct usb_xfer *xfer;
uint8_t et;
uint8_t was_stalled;
if (ep == NULL) {
/* nothing to do */
DPRINTF("Cannot find endpoint\n");
/*
* Pretend that the clear or set stall request is
* successful else some USB host stacks can do
* strange things, especially when a control endpoint
* stalls.
*/
return (0);
}
et = (ep->edesc->bmAttributes & UE_XFERTYPE);
if ((et != UE_BULK) &&
(et != UE_INTERRUPT)) {
/*
* Should not stall control
* nor isochronous endpoints.
*/
DPRINTF("Invalid endpoint\n");
return (0);
}
USB_BUS_LOCK(udev->bus);
/* store current stall state */
was_stalled = ep->is_stalled;
/* check for no change */
if (was_stalled && do_stall) {
/* if the endpoint is already stalled do nothing */
USB_BUS_UNLOCK(udev->bus);
DPRINTF("No change\n");
return (0);
}
/* set stalled state */
ep->is_stalled = 1;
if (do_stall || (!was_stalled)) {
if (!was_stalled) {
/* lookup the current USB transfer, if any */
xfer = ep->endpoint_q.curr;
} else {
xfer = NULL;
}
/*
* If "xfer" is non-NULL the "set_stall" method will
* complete the USB transfer like in case of a timeout
* setting the error code "USB_ERR_STALLED".
*/
(udev->bus->methods->set_stall) (udev, xfer, ep, &do_stall);
}
if (!do_stall) {
ep->toggle_next = 0; /* reset data toggle */
ep->is_stalled = 0; /* clear stalled state */
(udev->bus->methods->clear_stall) (udev, ep);
/* start up the current or next transfer, if any */
usb_command_wrapper(&ep->endpoint_q, ep->endpoint_q.curr);
}
USB_BUS_UNLOCK(udev->bus);
return (0);
}
/*------------------------------------------------------------------------*
* usb_reset_iface_endpoints - used in USB device side mode
*------------------------------------------------------------------------*/
usb_error_t
usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index)
{
struct usb_endpoint *ep;
struct usb_endpoint *ep_end;
ep = udev->endpoints;
ep_end = udev->endpoints + udev->endpoints_max;
for (; ep != ep_end; ep++) {
if ((ep->edesc == NULL) ||
(ep->iface_index != iface_index)) {
continue;
}
/* simulate a clear stall from the peer */
usbd_set_endpoint_stall(udev, ep, 0);
}
return (0);
}
/*------------------------------------------------------------------------*
* usb_detach_device_sub
*
* This function will try to detach an USB device. If it fails a panic
* will result.
*
* Flag values, see "USB_UNCFG_FLAG_XXX".
*------------------------------------------------------------------------*/
static void
usb_detach_device_sub(struct usb_device *udev, device_t *ppdev,
uint8_t flag)
{
device_t dev;
int err;
dev = *ppdev;
if (dev) {
/*
* NOTE: It is important to clear "*ppdev" before deleting
* the child due to some device methods being called late
* during the delete process !
*/
*ppdev = NULL;
device_printf(dev, "at %s, port %d, addr %d "
"(disconnected)\n",
device_get_nameunit(udev->parent_dev),
udev->port_no, udev->address);
if (device_is_attached(dev)) {
if (udev->flags.peer_suspended) {
err = DEVICE_RESUME(dev);
if (err) {
device_printf(dev, "Resume failed\n");
}
}
if (device_detach(dev)) {
goto error;
}
}
if (device_delete_child(udev->parent_dev, dev)) {
goto error;
}
}
return;
error:
/* Detach is not allowed to fail in the USB world */
panic("A USB driver would not detach\n");
}
/*------------------------------------------------------------------------*
* usb_detach_device
*
* The following function will detach the matching interfaces.
* This function is NULL safe.
*
* Flag values, see "USB_UNCFG_FLAG_XXX".
*------------------------------------------------------------------------*/
void
usb_detach_device(struct usb_device *udev, uint8_t iface_index,
uint8_t flag)
{
struct usb_interface *iface;
uint8_t i;
if (udev == NULL) {
/* nothing to do */
return;
}
DPRINTFN(4, "udev=%p\n", udev);
sx_assert(&udev->enum_sx, SA_LOCKED);
/*
* First detach the child to give the child's detach routine a
* chance to detach the sub-devices in the correct order.
* Then delete the child using "device_delete_child()" which
* will detach all sub-devices from the bottom and upwards!
*/
if (iface_index != USB_IFACE_INDEX_ANY) {
i = iface_index;
iface_index = i + 1;
} else {
i = 0;
iface_index = USB_IFACE_MAX;
}
/* do the detach */
for (; i != iface_index; i++) {
iface = usbd_get_iface(udev, i);
if (iface == NULL) {
/* looks like the end of the USB interfaces */
break;
}
usb_detach_device_sub(udev, &iface->subdev, flag);
}
}
/*------------------------------------------------------------------------*
* usb_probe_and_attach_sub
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static uint8_t
usb_probe_and_attach_sub(struct usb_device *udev,
struct usb_attach_arg *uaa)
{
struct usb_interface *iface;
device_t dev;
int err;
iface = uaa->iface;
if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) {
/* leave interface alone */
return (0);
}
dev = iface->subdev;
if (dev) {
/* clean up after module unload */
if (device_is_attached(dev)) {
/* already a device there */
return (0);
}
/* clear "iface->subdev" as early as possible */
iface->subdev = NULL;
if (device_delete_child(udev->parent_dev, dev)) {
/*
* Panic here, else one can get a double call
* to device_detach(). USB devices should
* never fail on detach!
*/
panic("device_delete_child() failed\n");
}
}
if (uaa->temp_dev == NULL) {
/* create a new child */
uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1);
if (uaa->temp_dev == NULL) {
device_printf(udev->parent_dev,
"Device creation failed\n");
return (1); /* failure */
}
device_set_ivars(uaa->temp_dev, uaa);
device_quiet(uaa->temp_dev);
}
/*
* Set "subdev" before probe and attach so that "devd" gets
* the information it needs.
*/
iface->subdev = uaa->temp_dev;
if (device_probe_and_attach(iface->subdev) == 0) {
/*
* The USB attach arguments are only available during probe
* and attach !
*/
uaa->temp_dev = NULL;
device_set_ivars(iface->subdev, NULL);
if (udev->flags.peer_suspended) {
err = DEVICE_SUSPEND(iface->subdev);
if (err)
device_printf(iface->subdev, "Suspend failed\n");
}
return (0); /* success */
} else {
/* No USB driver found */
iface->subdev = NULL;
}
return (1); /* failure */
}
/*------------------------------------------------------------------------*
* usbd_set_parent_iface
*
* Using this function will lock the alternate interface setting on an
* interface. It is typically used for multi interface drivers. In USB
* device side mode it is assumed that the alternate interfaces all
* have the same endpoint descriptors. The default parent index value
* is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not
* locked.
*------------------------------------------------------------------------*/
void
usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index,
uint8_t parent_index)
{
struct usb_interface *iface;
iface = usbd_get_iface(udev, iface_index);
if (iface) {
iface->parent_iface_index = parent_index;
}
}
static void
usb_init_attach_arg(struct usb_device *udev,
struct usb_attach_arg *uaa)
{
bzero(uaa, sizeof(*uaa));
uaa->device = udev;
uaa->usb_mode = udev->flags.usb_mode;
uaa->port = udev->port_no;
uaa->dev_state = UAA_DEV_READY;
uaa->info.idVendor = UGETW(udev->ddesc.idVendor);
uaa->info.idProduct = UGETW(udev->ddesc.idProduct);
uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice);
uaa->info.bDeviceClass = udev->ddesc.bDeviceClass;
uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass;
uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol;
uaa->info.bConfigIndex = udev->curr_config_index;
uaa->info.bConfigNum = udev->curr_config_no;
}
/*------------------------------------------------------------------------*
* usb_probe_and_attach
*
* This function is called from "uhub_explore_sub()",
* "usb_handle_set_config()" and "usb_handle_request()".
*
* Returns:
* 0: Success
* Else: A control transfer failed
*------------------------------------------------------------------------*/
usb_error_t
usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index)
{
struct usb_attach_arg uaa;
struct usb_interface *iface;
uint8_t i;
uint8_t j;
uint8_t do_unlock;
if (udev == NULL) {
DPRINTF("udev == NULL\n");
return (USB_ERR_INVAL);
}
/* automatic locking */
if (usbd_enum_is_locked(udev)) {
do_unlock = 0;
} else {
do_unlock = 1;
usbd_enum_lock(udev);
}
if (udev->curr_config_index == USB_UNCONFIG_INDEX) {
/* do nothing - no configuration has been set */
goto done;
}
/* setup USB attach arguments */
usb_init_attach_arg(udev, &uaa);
/* Check if only one interface should be probed: */
if (iface_index != USB_IFACE_INDEX_ANY) {
i = iface_index;
j = i + 1;
} else {
i = 0;
j = USB_IFACE_MAX;
}
/* Do the probe and attach */
for (; i != j; i++) {
iface = usbd_get_iface(udev, i);
if (iface == NULL) {
/*
* Looks like the end of the USB
* interfaces !
*/
DPRINTFN(2, "end of interfaces "
"at %u\n", i);
break;
}
if (iface->idesc == NULL) {
/* no interface descriptor */
continue;
}
uaa.iface = iface;
uaa.info.bInterfaceClass =
iface->idesc->bInterfaceClass;
uaa.info.bInterfaceSubClass =
iface->idesc->bInterfaceSubClass;
uaa.info.bInterfaceProtocol =
iface->idesc->bInterfaceProtocol;
uaa.info.bIfaceIndex = i;
uaa.info.bIfaceNum =
iface->idesc->bInterfaceNumber;
uaa.use_generic = 0;
uaa.driver_info = 0; /* reset driver_info */
DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n",
uaa.info.bInterfaceClass,
uaa.info.bInterfaceSubClass,
uaa.info.bInterfaceProtocol,
uaa.info.bIfaceIndex,
uaa.info.bIfaceNum);
/* try specific interface drivers first */
if (usb_probe_and_attach_sub(udev, &uaa)) {
/* ignore */
}
/* try generic interface drivers last */
uaa.use_generic = 1;
uaa.driver_info = 0; /* reset driver_info */
if (usb_probe_and_attach_sub(udev, &uaa)) {
/* ignore */
}
}
if (uaa.temp_dev) {
/* remove the last created child; it is unused */
if (device_delete_child(udev->parent_dev, uaa.temp_dev)) {
DPRINTFN(0, "device delete child failed\n");
}
}
done:
if (do_unlock)
usbd_enum_unlock(udev);
return (0);
}
/*------------------------------------------------------------------------*
* usb_suspend_resume_sub
*
* This function is called when the suspend or resume methods should
* be executed on an USB device.
*------------------------------------------------------------------------*/
static void
usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend)
{
int err;
if (dev == NULL) {
return;
}
if (!device_is_attached(dev)) {
return;
}
if (do_suspend) {
err = DEVICE_SUSPEND(dev);
} else {
err = DEVICE_RESUME(dev);
}
if (err) {
device_printf(dev, "%s failed\n",
do_suspend ? "Suspend" : "Resume");
}
}
/*------------------------------------------------------------------------*
* usb_suspend_resume
*
* The following function will suspend or resume the USB device.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend)
{
struct usb_interface *iface;
uint8_t i;
if (udev == NULL) {
/* nothing to do */
return (0);
}
DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend);
sx_assert(&udev->sr_sx, SA_LOCKED);
USB_BUS_LOCK(udev->bus);
/* filter the suspend events */
if (udev->flags.peer_suspended == do_suspend) {
USB_BUS_UNLOCK(udev->bus);
/* nothing to do */
return (0);
}
udev->flags.peer_suspended = do_suspend;
USB_BUS_UNLOCK(udev->bus);
/* do the suspend or resume */
for (i = 0; i != USB_IFACE_MAX; i++) {
iface = usbd_get_iface(udev, i);
if (iface == NULL) {
/* looks like the end of the USB interfaces */
break;
}
usb_suspend_resume_sub(udev, iface->subdev, do_suspend);
}
return (0);
}
/*------------------------------------------------------------------------*
* usbd_clear_stall_proc
*
* This function performs generic USB clear stall operations.
*------------------------------------------------------------------------*/
static void
usbd_clear_stall_proc(struct usb_proc_msg *_pm)
{
struct usb_clear_stall_msg *pm = (void *)_pm;
struct usb_device *udev = pm->udev;
/* Change lock */
USB_BUS_UNLOCK(udev->bus);
mtx_lock(&udev->device_mtx);
/* Start clear stall callback */
usbd_transfer_start(udev->ctrl_xfer[1]);
/* Change lock */
mtx_unlock(&udev->device_mtx);
USB_BUS_LOCK(udev->bus);
}
/*------------------------------------------------------------------------*
* usb_alloc_device
*
* This function allocates a new USB device. This function is called
* when a new device has been put in the powered state, but not yet in
* the addressed state. Get initial descriptor, set the address, get
* full descriptor and get strings.
*
* Return values:
* 0: Failure
* Else: Success
*------------------------------------------------------------------------*/
struct usb_device *
usb_alloc_device(device_t parent_dev, struct usb_bus *bus,
struct usb_device *parent_hub, uint8_t depth, uint8_t port_index,
uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode)
{
struct usb_attach_arg uaa;
struct usb_device *udev;
struct usb_device *adev;
struct usb_device *hub;
uint8_t *scratch_ptr;
size_t scratch_size;
usb_error_t err;
uint8_t device_index;
uint8_t config_index;
uint8_t config_quirk;
uint8_t set_config_failed;
DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, "
"port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n",
parent_dev, bus, parent_hub, depth, port_index, port_no,
speed, mode);
/*
* Find an unused device index. In USB Host mode this is the
* same as the device address.
*
* Device index zero is not used and device index 1 should
* always be the root hub.
*/
for (device_index = USB_ROOT_HUB_ADDR;
(device_index != bus->devices_max) &&
(bus->devices[device_index] != NULL);
device_index++) /* nop */;
if (device_index == bus->devices_max) {
device_printf(bus->bdev,
"No free USB device index for new device\n");
return (NULL);
}
if (depth > 0x10) {
device_printf(bus->bdev,
"Invalid device depth\n");
return (NULL);
}
udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO);
if (udev == NULL) {
return (NULL);
}
/* initialise our SX-lock */
sx_init_flags(&udev->ctrl_sx, "USB device SX lock", SX_DUPOK);
/* initialise our SX-lock */
sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK);
sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_DUPOK);
cv_init(&udev->ctrlreq_cv, "WCTRL");
cv_init(&udev->ref_cv, "UGONE");
/* initialise our mutex */
mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF);
/* initialise generic clear stall */
udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc;
udev->cs_msg[0].udev = udev;
udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc;
udev->cs_msg[1].udev = udev;
/* initialise some USB device fields */
udev->parent_hub = parent_hub;
udev->parent_dev = parent_dev;
udev->port_index = port_index;
udev->port_no = port_no;
udev->depth = depth;
udev->bus = bus;
udev->address = USB_START_ADDR; /* default value */
udev->plugtime = (usb_ticks_t)ticks;
/*
* We need to force the power mode to "on" because there are plenty
* of USB devices out there that do not work very well with
* automatic suspend and resume!
*/
udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON);
udev->pwr_save.last_xfer_time = ticks;
/* we are not ready yet */
udev->refcount = 1;
/* set up default endpoint descriptor */
udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc);
udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT;
udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT;
udev->ctrl_ep_desc.bmAttributes = UE_CONTROL;
udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET;
udev->ctrl_ep_desc.wMaxPacketSize[1] = 0;
udev->ctrl_ep_desc.bInterval = 0;
/* set up default endpoint companion descriptor */
udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc);
udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP;
udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET;
udev->speed = speed;
udev->flags.usb_mode = mode;
/* search for our High Speed USB HUB, if any */
adev = udev;
hub = udev->parent_hub;
while (hub) {
if (hub->speed == USB_SPEED_HIGH) {
udev->hs_hub_addr = hub->address;
udev->parent_hs_hub = hub;
udev->hs_port_no = adev->port_no;
break;
}
adev = hub;
hub = hub->parent_hub;
}
/* init the default endpoint */
usb_init_endpoint(udev, 0,
&udev->ctrl_ep_desc,
&udev->ctrl_ep_comp_desc,
&udev->ctrl_ep);
/* set device index */
udev->device_index = device_index;
#if USB_HAVE_UGEN
/* Create ugen name */
snprintf(udev->ugen_name, sizeof(udev->ugen_name),
USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev),
device_index);
LIST_INIT(&udev->pd_list);
/* Create the control endpoint device */
udev->ctrl_dev = usb_make_dev(udev, 0, FREAD|FWRITE);
/* Create a link from /dev/ugenX.X to the default endpoint */
make_dev_alias(udev->ctrl_dev, "%s", udev->ugen_name);
#endif
/* Initialise device */
if (bus->methods->device_init != NULL) {
err = (bus->methods->device_init) (udev);
if (err != 0) {
DPRINTFN(0, "device init %d failed "
"(%s, ignored)\n", device_index,
usbd_errstr(err));
goto done;
}
}
/* set powered device state after device init is complete */
usb_set_device_state(udev, USB_STATE_POWERED);
if (udev->flags.usb_mode == USB_MODE_HOST) {
err = usbd_req_set_address(udev, NULL, device_index);
/*
* This is the new USB device address from now on, if
* the set address request didn't set it already.
*/
if (udev->address == USB_START_ADDR)
udev->address = device_index;
/*
* We ignore any set-address errors, hence there are
* buggy USB devices out there that actually receive
* the SETUP PID, but manage to set the address before
* the STATUS stage is ACK'ed. If the device responds
* to the subsequent get-descriptor at the new
* address, then we know that the set-address command
* was successful.
*/
if (err) {
DPRINTFN(0, "set address %d failed "
"(%s, ignored)\n", udev->address,
usbd_errstr(err));
}
} else {
/* We are not self powered */
udev->flags.self_powered = 0;
/* Set unconfigured state */
udev->curr_config_no = USB_UNCONFIG_NO;
udev->curr_config_index = USB_UNCONFIG_INDEX;
/* Setup USB descriptors */
err = (usb_temp_setup_by_index_p) (udev, usb_template);
if (err) {
DPRINTFN(0, "setting up USB template failed maybe the USB "
"template module has not been loaded\n");
goto done;
}
}
usb_set_device_state(udev, USB_STATE_ADDRESSED);
/* setup the device descriptor and the initial "wMaxPacketSize" */
err = usbd_setup_device_desc(udev, NULL);
if (err != 0) {
/* XXX try to re-enumerate the device */
err = usbd_req_re_enumerate(udev, NULL);
if (err)
goto done;
}
/*
* Setup temporary USB attach args so that we can figure out some
* basic quirks for this device.
*/
usb_init_attach_arg(udev, &uaa);
if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) {
udev->flags.uq_bus_powered = 1;
}
if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) {
udev->flags.no_strings = 1;
}
/*
* Workaround for buggy USB devices.
*
* It appears that some string-less USB chips will crash and
* disappear if any attempts are made to read any string
* descriptors.
*
* Try to detect such chips by checking the strings in the USB
* device descriptor. If no strings are present there we
* simply disable all USB strings.
*/
scratch_ptr = udev->bus->scratch[0].data;
scratch_size = sizeof(udev->bus->scratch[0].data);
if (udev->ddesc.iManufacturer ||
udev->ddesc.iProduct ||
udev->ddesc.iSerialNumber) {
/* read out the language ID string */
err = usbd_req_get_string_desc(udev, NULL,
(char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE);
} else {
err = USB_ERR_INVAL;
}
if (err || (scratch_ptr[0] < 4)) {
udev->flags.no_strings = 1;
} else {
uint16_t langid;
uint16_t pref;
uint16_t mask;
uint8_t x;
/* load preferred value and mask */
pref = usb_lang_id;
mask = usb_lang_mask;
/* align length correctly */
scratch_ptr[0] &= ~1;
/* fix compiler warning */
langid = 0;
/* search for preferred language */
for (x = 2; (x < scratch_ptr[0]); x += 2) {
langid = UGETW(scratch_ptr + x);
if ((langid & mask) == pref)
break;
}
if (x >= scratch_ptr[0]) {
/* pick the first language as the default */
DPRINTFN(1, "Using first language\n");
langid = UGETW(scratch_ptr + 2);
}
DPRINTFN(1, "Language selected: 0x%04x\n", langid);
udev->langid = langid;
}
/* assume 100mA bus powered for now. Changed when configured. */
udev->power = USB_MIN_POWER;
/* fetch the vendor and product strings from the device */
usbd_set_device_strings(udev);
if (udev->flags.usb_mode == USB_MODE_DEVICE) {
/* USB device mode setup is complete */
err = 0;
goto config_done;
}
/*
* Most USB devices should attach to config index 0 by
* default
*/
if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) {
config_index = 0;
config_quirk = 1;
} else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) {
config_index = 1;
config_quirk = 1;
} else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) {
config_index = 2;
config_quirk = 1;
} else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) {
config_index = 3;
config_quirk = 1;
} else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) {
config_index = 4;
config_quirk = 1;
} else {
config_index = 0;
config_quirk = 0;
}
set_config_failed = 0;
repeat_set_config:
DPRINTF("setting config %u\n", config_index);
/* get the USB device configured */
err = usbd_set_config_index(udev, config_index);
if (err) {
if (udev->ddesc.bNumConfigurations != 0) {
if (!set_config_failed) {
set_config_failed = 1;
/* XXX try to re-enumerate the device */
err = usbd_req_re_enumerate(udev, NULL);
if (err == 0)
goto repeat_set_config;
}
DPRINTFN(0, "Failure selecting configuration index %u:"
"%s, port %u, addr %u (ignored)\n",
config_index, usbd_errstr(err), udev->port_no,
udev->address);
}
/*
* Some USB devices do not have any configurations. Ignore any
* set config failures!
*/
err = 0;
goto config_done;
}
if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) {
if ((udev->cdesc->bNumInterface < 2) &&
usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) {
DPRINTFN(0, "Found no endpoints, trying next config\n");
config_index++;
goto repeat_set_config;
}
if (config_index == 0) {
/*
* Try to figure out if we have an
* auto-install disk there:
*/
if (usb_iface_is_cdrom(udev, 0)) {
DPRINTFN(0, "Found possible auto-install "
"disk (trying next config)\n");
config_index++;
goto repeat_set_config;
}
}
}
EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa);
if (uaa.dev_state != UAA_DEV_READY) {
/* leave device unconfigured */
usb_unconfigure(udev, 0);
}
config_done:
DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n",
udev->address, udev, udev->parent_hub);
/* register our device - we are ready */
usb_bus_port_set_device(bus, parent_hub ?
parent_hub->hub->ports + port_index : NULL, udev, device_index);
#if USB_HAVE_UGEN
/* Symlink the ugen device name */
udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name);
/* Announce device */
printf("%s: <%s> at %s\n", udev->ugen_name,
usb_get_manufacturer(udev),
device_get_nameunit(udev->bus->bdev));
usb_notify_addq("ATTACH", udev);
#endif
done:
if (err) {
/*
* Free USB device and all subdevices, if any.
*/
usb_free_device(udev, 0);
udev = NULL;
}
return (udev);
}
#if USB_HAVE_UGEN
static struct cdev *
usb_make_dev(struct usb_device *udev, int ep, int mode)
{
struct usb_fs_privdata* pd;
char devname[20];
/* Store information to locate ourselves again later */
pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV,
M_WAITOK | M_ZERO);
pd->bus_index = device_get_unit(udev->bus->bdev);
pd->dev_index = udev->device_index;
pd->ep_addr = ep;
pd->mode = mode;
/* Now, create the device itself */
snprintf(devname, sizeof(devname), "%u.%u.%u",
pd->bus_index, pd->dev_index, pd->ep_addr);
pd->cdev = make_dev(&usb_devsw, 0, UID_ROOT,
GID_OPERATOR, 0600, USB_DEVICE_DIR "/%s", devname);
pd->cdev->si_drv1 = pd;
return (pd->cdev);
}
static void
usb_cdev_create(struct usb_device *udev)
{
struct usb_config_descriptor *cd;
struct usb_endpoint_descriptor *ed;
struct usb_descriptor *desc;
struct usb_fs_privdata* pd;
struct cdev *dev;
int inmode, outmode, inmask, outmask, mode;
uint8_t ep;
KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries"));
DPRINTFN(2, "Creating device nodes\n");
if (usbd_get_mode(udev) == USB_MODE_DEVICE) {
inmode = FWRITE;
outmode = FREAD;
} else { /* USB_MODE_HOST */
inmode = FREAD;
outmode = FWRITE;
}
inmask = 0;
outmask = 0;
desc = NULL;
/*
* Collect all used endpoint numbers instead of just
* generating 16 static endpoints.
*/
cd = usbd_get_config_descriptor(udev);
while ((desc = usb_desc_foreach(cd, desc))) {
/* filter out all endpoint descriptors */
if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
(desc->bLength >= sizeof(*ed))) {
ed = (struct usb_endpoint_descriptor *)desc;
/* update masks */
ep = ed->bEndpointAddress;
if (UE_GET_DIR(ep) == UE_DIR_OUT)
outmask |= 1 << UE_GET_ADDR(ep);
else
inmask |= 1 << UE_GET_ADDR(ep);
}
}
/* Create all available endpoints except EP0 */
for (ep = 1; ep < 16; ep++) {
mode = inmask & (1 << ep) ? inmode : 0;
mode |= outmask & (1 << ep) ? outmode : 0;
if (mode == 0)
continue; /* no IN or OUT endpoint */
dev = usb_make_dev(udev, ep, mode);
pd = dev->si_drv1;
LIST_INSERT_HEAD(&udev->pd_list, pd, pd_next);
}
}
static void
usb_cdev_free(struct usb_device *udev)
{
struct usb_fs_privdata* pd;
struct cdev* pcdev;
DPRINTFN(2, "Freeing device nodes\n");
while ((pd = LIST_FIRST(&udev->pd_list)) != NULL) {
KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt"));
pcdev = pd->cdev;
pd->cdev = NULL;
LIST_REMOVE(pd, pd_next);
if (pcdev != NULL)
destroy_dev_sched_cb(pcdev, usb_cdev_cleanup, pd);
}
}
static void
usb_cdev_cleanup(void* arg)
{
free(arg, M_USBDEV);
}
#endif
/*------------------------------------------------------------------------*
* usb_free_device
*
* This function is NULL safe and will free an USB device and its
* children devices, if any.
*
* Flag values: Reserved, set to zero.
*------------------------------------------------------------------------*/
void
usb_free_device(struct usb_device *udev, uint8_t flag)
{
struct usb_bus *bus;
if (udev == NULL)
return; /* already freed */
DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no);
bus = udev->bus;
usb_set_device_state(udev, USB_STATE_DETACHED);
#if USB_HAVE_UGEN
usb_notify_addq("DETACH", udev);
printf("%s: <%s> at %s (disconnected)\n", udev->ugen_name,
usb_get_manufacturer(udev), device_get_nameunit(bus->bdev));
/* Destroy UGEN symlink, if any */
if (udev->ugen_symlink) {
usb_free_symlink(udev->ugen_symlink);
udev->ugen_symlink = NULL;
}
#endif
/*
* Unregister our device first which will prevent any further
* references:
*/
usb_bus_port_set_device(bus, udev->parent_hub ?
udev->parent_hub->hub->ports + udev->port_index : NULL,
NULL, USB_ROOT_HUB_ADDR);
#if USB_HAVE_UGEN
/* wait for all pending references to go away: */
mtx_lock(&usb_ref_lock);
udev->refcount--;
while (udev->refcount != 0) {
cv_wait(&udev->ref_cv, &usb_ref_lock);
}
mtx_unlock(&usb_ref_lock);
destroy_dev_sched_cb(udev->ctrl_dev, usb_cdev_cleanup,
udev->ctrl_dev->si_drv1);
#endif
if (udev->flags.usb_mode == USB_MODE_DEVICE) {
/* stop receiving any control transfers (Device Side Mode) */
usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX);
}
/* the following will get the device unconfigured in software */
usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0);
/* unsetup any leftover default USB transfers */
usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX);
/* template unsetup, if any */
(usb_temp_unsetup_p) (udev);
/*
* Make sure that our clear-stall messages are not queued
* anywhere:
*/
USB_BUS_LOCK(udev->bus);
usb_proc_mwait(&udev->bus->non_giant_callback_proc,
&udev->cs_msg[0], &udev->cs_msg[1]);
USB_BUS_UNLOCK(udev->bus);
sx_destroy(&udev->ctrl_sx);
sx_destroy(&udev->enum_sx);
sx_destroy(&udev->sr_sx);
cv_destroy(&udev->ctrlreq_cv);
cv_destroy(&udev->ref_cv);
mtx_destroy(&udev->device_mtx);
#if USB_HAVE_UGEN
KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries"));
#endif
/* Uninitialise device */
if (bus->methods->device_uninit != NULL)
(bus->methods->device_uninit) (udev);
/* free device */
free(udev->serial, M_USB);
free(udev->manufacturer, M_USB);
free(udev->product, M_USB);
free(udev, M_USB);
}
/*------------------------------------------------------------------------*
* usbd_get_iface
*
* This function is the safe way to get the USB interface structure
* pointer by interface index.
*
* Return values:
* NULL: Interface not present.
* Else: Pointer to USB interface structure.
*------------------------------------------------------------------------*/
struct usb_interface *
usbd_get_iface(struct usb_device *udev, uint8_t iface_index)
{
struct usb_interface *iface = udev->ifaces + iface_index;
if (iface_index >= udev->ifaces_max)
return (NULL);
return (iface);
}
/*------------------------------------------------------------------------*
* usbd_find_descriptor
*
* This function will lookup the first descriptor that matches the
* criteria given by the arguments "type" and "subtype". Descriptors
* will only be searched within the interface having the index
* "iface_index". If the "id" argument points to an USB descriptor,
* it will be skipped before the search is started. This allows
* searching for multiple descriptors using the same criteria. Else
* the search is started after the interface descriptor.
*
* Return values:
* NULL: End of descriptors
* Else: A descriptor matching the criteria
*------------------------------------------------------------------------*/
void *
usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index,
uint8_t type, uint8_t type_mask,
uint8_t subtype, uint8_t subtype_mask)
{
struct usb_descriptor *desc;
struct usb_config_descriptor *cd;
struct usb_interface *iface;
cd = usbd_get_config_descriptor(udev);
if (cd == NULL) {
return (NULL);
}
if (id == NULL) {
iface = usbd_get_iface(udev, iface_index);
if (iface == NULL) {
return (NULL);
}
id = usbd_get_interface_descriptor(iface);
if (id == NULL) {
return (NULL);
}
}
desc = (void *)id;
while ((desc = usb_desc_foreach(cd, desc))) {
if (desc->bDescriptorType == UDESC_INTERFACE) {
break;
}
if (((desc->bDescriptorType & type_mask) == type) &&
((desc->bDescriptorSubtype & subtype_mask) == subtype)) {
return (desc);
}
}
return (NULL);
}
/*------------------------------------------------------------------------*
* usb_devinfo
*
* This function will dump information from the device descriptor
* belonging to the USB device pointed to by "udev", to the string
* pointed to by "dst_ptr" having a maximum length of "dst_len" bytes
* including the terminating zero.
*------------------------------------------------------------------------*/
void
usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len)
{
struct usb_device_descriptor *udd = &udev->ddesc;
uint16_t bcdDevice;
uint16_t bcdUSB;
bcdUSB = UGETW(udd->bcdUSB);
bcdDevice = UGETW(udd->bcdDevice);
if (udd->bDeviceClass != 0xFF) {
snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/"
"%x.%02x, addr %d",
usb_get_manufacturer(udev),
usb_get_product(udev),
udd->bDeviceClass, udd->bDeviceSubClass,
(bcdUSB >> 8), bcdUSB & 0xFF,
(bcdDevice >> 8), bcdDevice & 0xFF,
udev->address);
} else {
snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/"
"%x.%02x, addr %d",
usb_get_manufacturer(udev),
usb_get_product(udev),
(bcdUSB >> 8), bcdUSB & 0xFF,
(bcdDevice >> 8), bcdDevice & 0xFF,
udev->address);
}
}
#ifdef USB_VERBOSE
/*
* Descriptions of of known vendors and devices ("products").
*/
struct usb_knowndev {
uint16_t vendor;
uint16_t product;
uint32_t flags;
const char *vendorname;
const char *productname;
};
#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */
#include "usbdevs.h"
#include "usbdevs_data.h"
#endif /* USB_VERBOSE */
static void
usbd_set_device_strings(struct usb_device *udev)
{
struct usb_device_descriptor *udd = &udev->ddesc;
#ifdef USB_VERBOSE
const struct usb_knowndev *kdp;
#endif
char *temp_ptr;
size_t temp_size;
uint16_t vendor_id;
uint16_t product_id;
temp_ptr = (char *)udev->bus->scratch[0].data;
temp_size = sizeof(udev->bus->scratch[0].data);
vendor_id = UGETW(udd->idVendor);
product_id = UGETW(udd->idProduct);
/* get serial number string */
usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
udev->ddesc.iSerialNumber);
udev->serial = strdup(temp_ptr, M_USB);
/* get manufacturer string */
usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
udev->ddesc.iManufacturer);
usb_trim_spaces(temp_ptr);
if (temp_ptr[0] != '\0')
udev->manufacturer = strdup(temp_ptr, M_USB);
/* get product string */
usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
udev->ddesc.iProduct);
usb_trim_spaces(temp_ptr);
if (temp_ptr[0] != '\0')
udev->product = strdup(temp_ptr, M_USB);
#ifdef USB_VERBOSE
if (udev->manufacturer == NULL || udev->product == NULL) {
for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) {
if (kdp->vendor == vendor_id &&
(kdp->product == product_id ||
(kdp->flags & USB_KNOWNDEV_NOPROD) != 0))
break;
}
if (kdp->vendorname != NULL) {
/* XXX should use pointer to knowndevs string */
if (udev->manufacturer == NULL) {
udev->manufacturer = strdup(kdp->vendorname,
M_USB);
}
if (udev->product == NULL &&
(kdp->flags & USB_KNOWNDEV_NOPROD) == 0) {
udev->product = strdup(kdp->productname,
M_USB);
}
}
}
#endif
/* Provide default strings if none were found */
if (udev->manufacturer == NULL) {
snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id);
udev->manufacturer = strdup(temp_ptr, M_USB);
}
if (udev->product == NULL) {
snprintf(temp_ptr, temp_size, "product 0x%04x", product_id);
udev->product = strdup(temp_ptr, M_USB);
}
}
/*
* Returns:
* See: USB_MODE_XXX
*/
enum usb_hc_mode
usbd_get_mode(struct usb_device *udev)
{
return (udev->flags.usb_mode);
}
/*
* Returns:
* See: USB_SPEED_XXX
*/
enum usb_dev_speed
usbd_get_speed(struct usb_device *udev)
{
return (udev->speed);
}
uint32_t
usbd_get_isoc_fps(struct usb_device *udev)
{
; /* indent fix */
switch (udev->speed) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
return (1000);
default:
return (8000);
}
}
struct usb_device_descriptor *
usbd_get_device_descriptor(struct usb_device *udev)
{
if (udev == NULL)
return (NULL); /* be NULL safe */
return (&udev->ddesc);
}
struct usb_config_descriptor *
usbd_get_config_descriptor(struct usb_device *udev)
{
if (udev == NULL)
return (NULL); /* be NULL safe */
return (udev->cdesc);
}
/*------------------------------------------------------------------------*
* usb_test_quirk - test a device for a given quirk
*
* Return values:
* 0: The USB device does not have the given quirk.
* Else: The USB device has the given quirk.
*------------------------------------------------------------------------*/
uint8_t
usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk)
{
uint8_t found;
found = (usb_test_quirk_p) (&uaa->info, quirk);
return (found);
}
struct usb_interface_descriptor *
usbd_get_interface_descriptor(struct usb_interface *iface)
{
if (iface == NULL)
return (NULL); /* be NULL safe */
return (iface->idesc);
}
uint8_t
usbd_get_interface_altindex(struct usb_interface *iface)
{
return (iface->alt_index);
}
uint8_t
usbd_get_bus_index(struct usb_device *udev)
{
return ((uint8_t)device_get_unit(udev->bus->bdev));
}
uint8_t
usbd_get_device_index(struct usb_device *udev)
{
return (udev->device_index);
}
#if USB_HAVE_UGEN
/*------------------------------------------------------------------------*
* usb_notify_addq
*
* This function will generate events for dev.
*------------------------------------------------------------------------*/
#ifndef BURN_BRIDGES
static void
usb_notify_addq_compat(const char *type, struct usb_device *udev)
{
char *data = NULL;
const char *ntype;
struct malloc_type *mt;
const size_t buf_size = 512;
/* Convert notify type */
if (strcmp(type, "ATTACH") == 0)
ntype = "+";
else if (strcmp(type, "DETACH") == 0)
ntype = "-";
else
return;
mtx_lock(&malloc_mtx);
mt = malloc_desc2type("bus"); /* XXX M_BUS */
mtx_unlock(&malloc_mtx);
if (mt == NULL)
return;
data = malloc(buf_size, mt, M_NOWAIT);
if (data == NULL)
return;
/* String it all together. */
snprintf(data, buf_size,
"%s"
"%s "
"vendor=0x%04x "
"product=0x%04x "
"devclass=0x%02x "
"devsubclass=0x%02x "
"sernum=\"%s\" "
"release=0x%04x "
"at "
"port=%u "
"on "
"%s\n",
ntype,
udev->ugen_name,
UGETW(udev->ddesc.idVendor),
UGETW(udev->ddesc.idProduct),
udev->ddesc.bDeviceClass,
udev->ddesc.bDeviceSubClass,
usb_get_serial(udev),
UGETW(udev->ddesc.bcdDevice),
udev->port_no,
udev->parent_hub != NULL ?
udev->parent_hub->ugen_name :
device_get_nameunit(device_get_parent(udev->bus->bdev)));
devctl_queue_data(data);
}
#endif
static void
usb_notify_addq(const char *type, struct usb_device *udev)
{
struct usb_interface *iface;
struct sbuf *sb;
int i;
#ifndef BURN_BRIDGES
usb_notify_addq_compat(type, udev);
#endif
/* announce the device */
sb = sbuf_new_auto();
sbuf_printf(sb,
"cdev=%s "
"vendor=0x%04x "
"product=0x%04x "
"devclass=0x%02x "
"devsubclass=0x%02x "
"sernum=\"%s\" "
"release=0x%04x "
"mode=%s "
"port=%u "
"parent=%s\n",
udev->ugen_name,
UGETW(udev->ddesc.idVendor),
UGETW(udev->ddesc.idProduct),
udev->ddesc.bDeviceClass,
udev->ddesc.bDeviceSubClass,
usb_get_serial(udev),
UGETW(udev->ddesc.bcdDevice),
(udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
udev->port_no,
udev->parent_hub != NULL ?
udev->parent_hub->ugen_name :
device_get_nameunit(device_get_parent(udev->bus->bdev)));
sbuf_finish(sb);
devctl_notify("USB", "DEVICE", type, sbuf_data(sb));
sbuf_delete(sb);
/* announce each interface */
for (i = 0; i < USB_IFACE_MAX; i++) {
iface = usbd_get_iface(udev, i);
if (iface == NULL)
break; /* end of interfaces */
if (iface->idesc == NULL)
continue; /* no interface descriptor */
sb = sbuf_new_auto();
sbuf_printf(sb,
"cdev=%s "
"vendor=0x%04x "
"product=0x%04x "
"devclass=0x%02x "
"devsubclass=0x%02x "
"sernum=\"%s\" "
"release=0x%04x "
"mode=%s "
"interface=%d "
"endpoints=%d "
"intclass=0x%02x "
"intsubclass=0x%02x "
"intprotocol=0x%02x\n",
udev->ugen_name,
UGETW(udev->ddesc.idVendor),
UGETW(udev->ddesc.idProduct),
udev->ddesc.bDeviceClass,
udev->ddesc.bDeviceSubClass,
usb_get_serial(udev),
UGETW(udev->ddesc.bcdDevice),
(udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
iface->idesc->bInterfaceNumber,
iface->idesc->bNumEndpoints,
iface->idesc->bInterfaceClass,
iface->idesc->bInterfaceSubClass,
iface->idesc->bInterfaceProtocol);
sbuf_finish(sb);
devctl_notify("USB", "INTERFACE", type, sbuf_data(sb));
sbuf_delete(sb);
}
}
/*------------------------------------------------------------------------*
* usb_fifo_free_wrap
*
* This function will free the FIFOs.
*
* Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag
* is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free
* all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and
* "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non
* control endpoint FIFOs. If "iface_index" is not set to
* "USB_IFACE_INDEX_ANY" the flag has no effect.
*------------------------------------------------------------------------*/
static void
usb_fifo_free_wrap(struct usb_device *udev,
uint8_t iface_index, uint8_t flag)
{
struct usb_fifo *f;
uint16_t i;
/*
* Free any USB FIFOs on the given interface:
*/
for (i = 0; i != USB_FIFO_MAX; i++) {
f = udev->fifo[i];
if (f == NULL) {
continue;
}
/* Check if the interface index matches */
if (iface_index == f->iface_index) {
if (f->methods != &usb_ugen_methods) {
/*
* Don't free any non-generic FIFOs in
* this case.
*/
continue;
}
if ((f->dev_ep_index == 0) &&
(f->fs_xfer == NULL)) {
/* no need to free this FIFO */
continue;
}
} else if (iface_index == USB_IFACE_INDEX_ANY) {
if ((f->methods == &usb_ugen_methods) &&
(f->dev_ep_index == 0) &&
(!(flag & USB_UNCFG_FLAG_FREE_EP0)) &&
(f->fs_xfer == NULL)) {
/* no need to free this FIFO */
continue;
}
} else {
/* no need to free this FIFO */
continue;
}
/* free this FIFO */
usb_fifo_free(f);
}
}
#endif
/*------------------------------------------------------------------------*
* usb_peer_can_wakeup
*
* Return values:
* 0: Peer cannot do resume signalling.
* Else: Peer can do resume signalling.
*------------------------------------------------------------------------*/
uint8_t
usb_peer_can_wakeup(struct usb_device *udev)
{
const struct usb_config_descriptor *cdp;
cdp = udev->cdesc;
if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) {
return (cdp->bmAttributes & UC_REMOTE_WAKEUP);
}
return (0); /* not supported */
}
void
usb_set_device_state(struct usb_device *udev, enum usb_dev_state state)
{
KASSERT(state < USB_STATE_MAX, ("invalid udev state"));
DPRINTF("udev %p state %s -> %s\n", udev,
usb_statestr(udev->state), usb_statestr(state));
udev->state = state;
if (udev->bus->methods->device_state_change != NULL)
(udev->bus->methods->device_state_change) (udev);
}
enum usb_dev_state
usb_get_device_state(struct usb_device *udev)
{
if (udev == NULL)
return (USB_STATE_DETACHED);
return (udev->state);
}
uint8_t
usbd_device_attached(struct usb_device *udev)
{
return (udev->state > USB_STATE_DETACHED);
}
/* The following function locks enumerating the given USB device. */
void
usbd_enum_lock(struct usb_device *udev)
{
sx_xlock(&udev->enum_sx);
sx_xlock(&udev->sr_sx);
/*
* NEWBUS LOCK NOTE: We should check if any parent SX locks
* are locked before locking Giant. Else the lock can be
* locked multiple times.
*/
mtx_lock(&Giant);
}
/* The following function unlocks enumerating the given USB device. */
void
usbd_enum_unlock(struct usb_device *udev)
{
mtx_unlock(&Giant);
sx_xunlock(&udev->enum_sx);
sx_xunlock(&udev->sr_sx);
}
/* The following function locks suspend and resume. */
void
usbd_sr_lock(struct usb_device *udev)
{
sx_xlock(&udev->sr_sx);
/*
* NEWBUS LOCK NOTE: We should check if any parent SX locks
* are locked before locking Giant. Else the lock can be
* locked multiple times.
*/
mtx_lock(&Giant);
}
/* The following function unlocks suspend and resume. */
void
usbd_sr_unlock(struct usb_device *udev)
{
mtx_unlock(&Giant);
sx_xunlock(&udev->sr_sx);
}
/*
* The following function checks the enumerating lock for the given
* USB device.
*/
uint8_t
usbd_enum_is_locked(struct usb_device *udev)
{
return (sx_xlocked(&udev->enum_sx));
}