freebsd-nq/sys/dev/usb/usb_request.c
Andrew Thompson 2df1e9a62a If a USB device is suspended and a USB set config request is issued when the
USB enumeration lock is locked, then the USB stack fails to resume the device
because locking the USB enumeration lock is part of the resume procedure. To
solve this issue a new lock is introduced which only protects the suspend and
resume callbacks, which can be dropped inside the usbd_do_request_flags()
function, to allow suspend and resume during so-called enumeration operations.

Submitted by:	Hans Petter Selasky
2010-05-12 22:42:35 +00:00

1631 lines
42 KiB
C

/* $FreeBSD$ */
/*-
* Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
* Copyright (c) 1998 Lennart Augustsson. All rights reserved.
* 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 <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usb_ioctl.h>
#include <dev/usb/usbhid.h>
#define USB_DEBUG_VAR usb_debug
#include <dev/usb/usb_core.h>
#include <dev/usb/usb_busdma.h>
#include <dev/usb/usb_request.h>
#include <dev/usb/usb_process.h>
#include <dev/usb/usb_transfer.h>
#include <dev/usb/usb_debug.h>
#include <dev/usb/usb_device.h>
#include <dev/usb/usb_util.h>
#include <dev/usb/usb_dynamic.h>
#include <dev/usb/usb_controller.h>
#include <dev/usb/usb_bus.h>
#include <sys/ctype.h>
#ifdef USB_DEBUG
static int usb_pr_poll_delay = USB_PORT_RESET_DELAY;
static int usb_pr_recovery_delay = USB_PORT_RESET_RECOVERY;
static int usb_ss_delay = 0;
SYSCTL_INT(_hw_usb, OID_AUTO, pr_poll_delay, CTLFLAG_RW,
&usb_pr_poll_delay, 0, "USB port reset poll delay in ms");
SYSCTL_INT(_hw_usb, OID_AUTO, pr_recovery_delay, CTLFLAG_RW,
&usb_pr_recovery_delay, 0, "USB port reset recovery delay in ms");
SYSCTL_INT(_hw_usb, OID_AUTO, ss_delay, CTLFLAG_RW,
&usb_ss_delay, 0, "USB status stage delay in ms");
#endif
/*------------------------------------------------------------------------*
* usbd_do_request_callback
*
* This function is the USB callback for generic USB Host control
* transfers.
*------------------------------------------------------------------------*/
void
usbd_do_request_callback(struct usb_xfer *xfer, usb_error_t error)
{
; /* workaround for a bug in "indent" */
DPRINTF("st=%u\n", USB_GET_STATE(xfer));
switch (USB_GET_STATE(xfer)) {
case USB_ST_SETUP:
usbd_transfer_submit(xfer);
break;
default:
cv_signal(&xfer->xroot->udev->ctrlreq_cv);
break;
}
}
/*------------------------------------------------------------------------*
* usb_do_clear_stall_callback
*
* This function is the USB callback for generic clear stall requests.
*------------------------------------------------------------------------*/
void
usb_do_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct usb_device_request req;
struct usb_device *udev;
struct usb_endpoint *ep;
struct usb_endpoint *ep_end;
struct usb_endpoint *ep_first;
uint8_t to;
udev = xfer->xroot->udev;
USB_BUS_LOCK(udev->bus);
/* round robin endpoint clear stall */
ep = udev->ep_curr;
ep_end = udev->endpoints + udev->endpoints_max;
ep_first = udev->endpoints;
to = udev->endpoints_max;
switch (USB_GET_STATE(xfer)) {
case USB_ST_TRANSFERRED:
if (ep == NULL)
goto tr_setup; /* device was unconfigured */
if (ep->edesc &&
ep->is_stalled) {
ep->toggle_next = 0;
ep->is_stalled = 0;
/* start up the current or next transfer, if any */
usb_command_wrapper(&ep->endpoint_q,
ep->endpoint_q.curr);
}
ep++;
case USB_ST_SETUP:
tr_setup:
if (to == 0)
break; /* no endpoints - nothing to do */
if ((ep < ep_first) || (ep >= ep_end))
ep = ep_first; /* endpoint wrapped around */
if (ep->edesc &&
ep->is_stalled) {
/* setup a clear-stall packet */
req.bmRequestType = UT_WRITE_ENDPOINT;
req.bRequest = UR_CLEAR_FEATURE;
USETW(req.wValue, UF_ENDPOINT_HALT);
req.wIndex[0] = ep->edesc->bEndpointAddress;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
/* copy in the transfer */
usbd_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
/* set length */
usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
xfer->nframes = 1;
USB_BUS_UNLOCK(udev->bus);
usbd_transfer_submit(xfer);
USB_BUS_LOCK(udev->bus);
break;
}
ep++;
to--;
goto tr_setup;
default:
if (xfer->error == USB_ERR_CANCELLED) {
break;
}
goto tr_setup;
}
/* store current endpoint */
udev->ep_curr = ep;
USB_BUS_UNLOCK(udev->bus);
}
static usb_handle_req_t *
usbd_get_hr_func(struct usb_device *udev)
{
/* figure out if there is a Handle Request function */
if (udev->flags.usb_mode == USB_MODE_DEVICE)
return (usb_temp_get_desc_p);
else if (udev->parent_hub == NULL)
return (udev->bus->methods->roothub_exec);
else
return (NULL);
}
/*------------------------------------------------------------------------*
* usbd_do_request_flags and usbd_do_request
*
* Description of arguments passed to these functions:
*
* "udev" - this is the "usb_device" structure pointer on which the
* request should be performed. It is possible to call this function
* in both Host Side mode and Device Side mode.
*
* "mtx" - if this argument is non-NULL the mutex pointed to by it
* will get dropped and picked up during the execution of this
* function, hence this function sometimes needs to sleep. If this
* argument is NULL it has no effect.
*
* "req" - this argument must always be non-NULL and points to an
* 8-byte structure holding the USB request to be done. The USB
* request structure has a bit telling the direction of the USB
* request, if it is a read or a write.
*
* "data" - if the "wLength" part of the structure pointed to by "req"
* is non-zero this argument must point to a valid kernel buffer which
* can hold at least "wLength" bytes. If "wLength" is zero "data" can
* be NULL.
*
* "flags" - here is a list of valid flags:
*
* o USB_SHORT_XFER_OK: allows the data transfer to be shorter than
* specified
*
* o USB_DELAY_STATUS_STAGE: allows the status stage to be performed
* at a later point in time. This is tunable by the "hw.usb.ss_delay"
* sysctl. This flag is mostly useful for debugging.
*
* o USB_USER_DATA_PTR: treat the "data" pointer like a userland
* pointer.
*
* "actlen" - if non-NULL the actual transfer length will be stored in
* the 16-bit unsigned integer pointed to by "actlen". This
* information is mostly useful when the "USB_SHORT_XFER_OK" flag is
* used.
*
* "timeout" - gives the timeout for the control transfer in
* milliseconds. A "timeout" value less than 50 milliseconds is
* treated like a 50 millisecond timeout. A "timeout" value greater
* than 30 seconds is treated like a 30 second timeout. This USB stack
* does not allow control requests without a timeout.
*
* NOTE: This function is thread safe. All calls to
* "usbd_do_request_flags" will be serialised by the use of an
* internal "sx_lock".
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx,
struct usb_device_request *req, void *data, uint16_t flags,
uint16_t *actlen, usb_timeout_t timeout)
{
usb_handle_req_t *hr_func;
struct usb_xfer *xfer;
const void *desc;
int err = 0;
usb_ticks_t start_ticks;
usb_ticks_t delta_ticks;
usb_ticks_t max_ticks;
uint16_t length;
uint16_t temp;
uint8_t enum_locked;
if (timeout < 50) {
/* timeout is too small */
timeout = 50;
}
if (timeout > 30000) {
/* timeout is too big */
timeout = 30000;
}
length = UGETW(req->wLength);
enum_locked = usbd_enum_is_locked(udev);
DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x "
"wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n",
udev, req->bmRequestType, req->bRequest,
req->wValue[1], req->wValue[0],
req->wIndex[1], req->wIndex[0],
req->wLength[1], req->wLength[0]);
/* Check if the device is still alive */
if (udev->state < USB_STATE_POWERED) {
DPRINTF("usb device has gone\n");
return (USB_ERR_NOT_CONFIGURED);
}
/*
* Set "actlen" to a known value in case the caller does not
* check the return value:
*/
if (actlen)
*actlen = 0;
#if (USB_HAVE_USER_IO == 0)
if (flags & USB_USER_DATA_PTR)
return (USB_ERR_INVAL);
#endif
if ((mtx != NULL) && (mtx != &Giant)) {
mtx_unlock(mtx);
mtx_assert(mtx, MA_NOTOWNED);
}
/*
* We need to allow suspend and resume at this point, else the
* control transfer will timeout if the device is suspended!
*/
if (enum_locked)
usbd_sr_unlock(udev);
/*
* Grab the default sx-lock so that serialisation
* is achieved when multiple threads are involved:
*/
sx_xlock(&udev->ctrl_sx);
hr_func = usbd_get_hr_func(udev);
if (hr_func != NULL) {
DPRINTF("Handle Request function is set\n");
desc = NULL;
temp = 0;
if (!(req->bmRequestType & UT_READ)) {
if (length != 0) {
DPRINTFN(1, "The handle request function "
"does not support writing data!\n");
err = USB_ERR_INVAL;
goto done;
}
}
/* The root HUB code needs the BUS lock locked */
USB_BUS_LOCK(udev->bus);
err = (hr_func) (udev, req, &desc, &temp);
USB_BUS_UNLOCK(udev->bus);
if (err)
goto done;
if (length > temp) {
if (!(flags & USB_SHORT_XFER_OK)) {
err = USB_ERR_SHORT_XFER;
goto done;
}
length = temp;
}
if (actlen)
*actlen = length;
if (length > 0) {
#if USB_HAVE_USER_IO
if (flags & USB_USER_DATA_PTR) {
if (copyout(desc, data, length)) {
err = USB_ERR_INVAL;
goto done;
}
} else
#endif
bcopy(desc, data, length);
}
goto done; /* success */
}
/*
* Setup a new USB transfer or use the existing one, if any:
*/
usbd_ctrl_transfer_setup(udev);
xfer = udev->ctrl_xfer[0];
if (xfer == NULL) {
/* most likely out of memory */
err = USB_ERR_NOMEM;
goto done;
}
USB_XFER_LOCK(xfer);
if (flags & USB_DELAY_STATUS_STAGE)
xfer->flags.manual_status = 1;
else
xfer->flags.manual_status = 0;
if (flags & USB_SHORT_XFER_OK)
xfer->flags.short_xfer_ok = 1;
else
xfer->flags.short_xfer_ok = 0;
xfer->timeout = timeout;
start_ticks = ticks;
max_ticks = USB_MS_TO_TICKS(timeout);
usbd_copy_in(xfer->frbuffers, 0, req, sizeof(*req));
usbd_xfer_set_frame_len(xfer, 0, sizeof(*req));
xfer->nframes = 2;
while (1) {
temp = length;
if (temp > xfer->max_data_length) {
temp = usbd_xfer_max_len(xfer);
}
usbd_xfer_set_frame_len(xfer, 1, temp);
if (temp > 0) {
if (!(req->bmRequestType & UT_READ)) {
#if USB_HAVE_USER_IO
if (flags & USB_USER_DATA_PTR) {
USB_XFER_UNLOCK(xfer);
err = usbd_copy_in_user(xfer->frbuffers + 1,
0, data, temp);
USB_XFER_LOCK(xfer);
if (err) {
err = USB_ERR_INVAL;
break;
}
} else
#endif
usbd_copy_in(xfer->frbuffers + 1,
0, data, temp);
}
xfer->nframes = 2;
} else {
if (xfer->frlengths[0] == 0) {
if (xfer->flags.manual_status) {
#ifdef USB_DEBUG
int temp;
temp = usb_ss_delay;
if (temp > 5000) {
temp = 5000;
}
if (temp > 0) {
usb_pause_mtx(
xfer->xroot->xfer_mtx,
USB_MS_TO_TICKS(temp));
}
#endif
xfer->flags.manual_status = 0;
} else {
break;
}
}
xfer->nframes = 1;
}
usbd_transfer_start(xfer);
while (usbd_transfer_pending(xfer)) {
cv_wait(&udev->ctrlreq_cv,
xfer->xroot->xfer_mtx);
}
err = xfer->error;
if (err) {
break;
}
/* subtract length of SETUP packet, if any */
if (xfer->aframes > 0) {
xfer->actlen -= xfer->frlengths[0];
} else {
xfer->actlen = 0;
}
/* check for short packet */
if (temp > xfer->actlen) {
temp = xfer->actlen;
length = temp;
}
if (temp > 0) {
if (req->bmRequestType & UT_READ) {
#if USB_HAVE_USER_IO
if (flags & USB_USER_DATA_PTR) {
USB_XFER_UNLOCK(xfer);
err = usbd_copy_out_user(xfer->frbuffers + 1,
0, data, temp);
USB_XFER_LOCK(xfer);
if (err) {
err = USB_ERR_INVAL;
break;
}
} else
#endif
usbd_copy_out(xfer->frbuffers + 1,
0, data, temp);
}
}
/*
* Clear "frlengths[0]" so that we don't send the setup
* packet again:
*/
usbd_xfer_set_frame_len(xfer, 0, 0);
/* update length and data pointer */
length -= temp;
data = USB_ADD_BYTES(data, temp);
if (actlen) {
(*actlen) += temp;
}
/* check for timeout */
delta_ticks = ticks - start_ticks;
if (delta_ticks > max_ticks) {
if (!err) {
err = USB_ERR_TIMEOUT;
}
}
if (err) {
break;
}
}
if (err) {
/*
* Make sure that the control endpoint is no longer
* blocked in case of a non-transfer related error:
*/
usbd_transfer_stop(xfer);
}
USB_XFER_UNLOCK(xfer);
done:
sx_xunlock(&udev->ctrl_sx);
if (enum_locked)
usbd_sr_lock(udev);
if ((mtx != NULL) && (mtx != &Giant))
mtx_lock(mtx);
return ((usb_error_t)err);
}
/*------------------------------------------------------------------------*
* usbd_do_request_proc - factored out code
*
* This function is factored out code. It does basically the same like
* usbd_do_request_flags, except it will check the status of the
* passed process argument before doing the USB request. If the
* process is draining the USB_ERR_IOERROR code will be returned. It
* is assumed that the mutex associated with the process is locked
* when calling this function.
*------------------------------------------------------------------------*/
usb_error_t
usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc,
struct usb_device_request *req, void *data, uint16_t flags,
uint16_t *actlen, usb_timeout_t timeout)
{
usb_error_t err;
uint16_t len;
/* get request data length */
len = UGETW(req->wLength);
/* check if the device is being detached */
if (usb_proc_is_gone(pproc)) {
err = USB_ERR_IOERROR;
goto done;
}
/* forward the USB request */
err = usbd_do_request_flags(udev, pproc->up_mtx,
req, data, flags, actlen, timeout);
done:
/* on failure we zero the data */
/* on short packet we zero the unused data */
if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) {
if (err)
memset(data, 0, len);
else if (actlen && *actlen != len)
memset(((uint8_t *)data) + *actlen, 0, len - *actlen);
}
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_reset_port
*
* This function will instruct an USB HUB to perform a reset sequence
* on the specified port number.
*
* Returns:
* 0: Success. The USB device should now be at address zero.
* Else: Failure. No USB device is present and the USB port should be
* disabled.
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port)
{
struct usb_port_status ps;
usb_error_t err;
uint16_t n;
#ifdef USB_DEBUG
uint16_t pr_poll_delay;
uint16_t pr_recovery_delay;
#endif
err = usbd_req_set_port_feature(udev, mtx, port, UHF_PORT_RESET);
if (err) {
goto done;
}
#ifdef USB_DEBUG
/* range check input parameters */
pr_poll_delay = usb_pr_poll_delay;
if (pr_poll_delay < 1) {
pr_poll_delay = 1;
} else if (pr_poll_delay > 1000) {
pr_poll_delay = 1000;
}
pr_recovery_delay = usb_pr_recovery_delay;
if (pr_recovery_delay > 1000) {
pr_recovery_delay = 1000;
}
#endif
n = 0;
while (1) {
#ifdef USB_DEBUG
/* wait for the device to recover from reset */
usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay));
n += pr_poll_delay;
#else
/* wait for the device to recover from reset */
usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY));
n += USB_PORT_RESET_DELAY;
#endif
err = usbd_req_get_port_status(udev, mtx, &ps, port);
if (err) {
goto done;
}
/* if the device disappeared, just give up */
if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) {
goto done;
}
/* check if reset is complete */
if (UGETW(ps.wPortChange) & UPS_C_PORT_RESET) {
break;
}
/* check for timeout */
if (n > 1000) {
n = 0;
break;
}
}
/* clear port reset first */
err = usbd_req_clear_port_feature(
udev, mtx, port, UHF_C_PORT_RESET);
if (err) {
goto done;
}
/* check for timeout */
if (n == 0) {
err = USB_ERR_TIMEOUT;
goto done;
}
#ifdef USB_DEBUG
/* wait for the device to recover from reset */
usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay));
#else
/* wait for the device to recover from reset */
usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY));
#endif
done:
DPRINTFN(2, "port %d reset returning error=%s\n",
port, usbd_errstr(err));
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_get_desc
*
* This function can be used to retrieve USB descriptors. It contains
* some additional logic like zeroing of missing descriptor bytes and
* retrying an USB descriptor in case of failure. The "min_len"
* argument specifies the minimum descriptor length. The "max_len"
* argument specifies the maximum descriptor length. If the real
* descriptor length is less than the minimum length the missing
* byte(s) will be zeroed. The type field, the second byte of the USB
* descriptor, will get forced to the correct type. If the "actlen"
* pointer is non-NULL, the actual length of the transfer will get
* stored in the 16-bit unsigned integer which it is pointing to. The
* first byte of the descriptor will not get updated. If the "actlen"
* pointer is NULL the first byte of the descriptor will get updated
* to reflect the actual length instead. If "min_len" is not equal to
* "max_len" then this function will try to retrive the beginning of
* the descriptor and base the maximum length on the first byte of the
* descriptor.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_desc(struct usb_device *udev,
struct mtx *mtx, uint16_t *actlen, void *desc,
uint16_t min_len, uint16_t max_len,
uint16_t id, uint8_t type, uint8_t index,
uint8_t retries)
{
struct usb_device_request req;
uint8_t *buf;
usb_error_t err;
DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n",
id, type, index, max_len);
req.bmRequestType = UT_READ_DEVICE;
req.bRequest = UR_GET_DESCRIPTOR;
USETW2(req.wValue, type, index);
USETW(req.wIndex, id);
while (1) {
if ((min_len < 2) || (max_len < 2)) {
err = USB_ERR_INVAL;
goto done;
}
USETW(req.wLength, min_len);
err = usbd_do_request_flags(udev, mtx, &req,
desc, 0, NULL, 1000);
if (err) {
if (!retries) {
goto done;
}
retries--;
usb_pause_mtx(mtx, hz / 5);
continue;
}
buf = desc;
if (min_len == max_len) {
/* enforce correct length */
if ((buf[0] > min_len) && (actlen == NULL))
buf[0] = min_len;
/* enforce correct type */
buf[1] = type;
goto done;
}
/* range check */
if (max_len > buf[0]) {
max_len = buf[0];
}
/* zero minimum data */
while (min_len > max_len) {
min_len--;
buf[min_len] = 0;
}
/* set new minimum length */
min_len = max_len;
}
done:
if (actlen != NULL) {
if (err)
*actlen = 0;
else
*actlen = min_len;
}
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_get_string_any
*
* This function will return the string given by "string_index"
* using the first language ID. The maximum length "len" includes
* the terminating zero. The "len" argument should be twice as
* big pluss 2 bytes, compared with the actual maximum string length !
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, char *buf,
uint16_t len, uint8_t string_index)
{
char *s;
uint8_t *temp;
uint16_t i;
uint16_t n;
uint16_t c;
uint8_t swap;
usb_error_t err;
if (len == 0) {
/* should not happen */
return (USB_ERR_NORMAL_COMPLETION);
}
if (string_index == 0) {
/* this is the language table */
buf[0] = 0;
return (USB_ERR_INVAL);
}
if (udev->flags.no_strings) {
buf[0] = 0;
return (USB_ERR_STALLED);
}
err = usbd_req_get_string_desc
(udev, mtx, buf, len, udev->langid, string_index);
if (err) {
buf[0] = 0;
return (err);
}
temp = (uint8_t *)buf;
if (temp[0] < 2) {
/* string length is too short */
buf[0] = 0;
return (USB_ERR_INVAL);
}
/* reserve one byte for terminating zero */
len--;
/* find maximum length */
s = buf;
n = (temp[0] / 2) - 1;
if (n > len) {
n = len;
}
/* skip descriptor header */
temp += 2;
/* reset swap state */
swap = 3;
/* convert and filter */
for (i = 0; (i != n); i++) {
c = UGETW(temp + (2 * i));
/* convert from Unicode, handle buggy strings */
if (((c & 0xff00) == 0) && (swap & 1)) {
/* Little Endian, default */
*s = c;
swap = 1;
} else if (((c & 0x00ff) == 0) && (swap & 2)) {
/* Big Endian */
*s = c >> 8;
swap = 2;
} else {
/* silently skip bad character */
continue;
}
/*
* Filter by default - we don't allow greater and less than
* signs because they might confuse the dmesg printouts!
*/
if ((*s == '<') || (*s == '>') || (!isprint(*s))) {
/* silently skip bad character */
continue;
}
s++;
}
*s = 0; /* zero terminate resulting string */
return (USB_ERR_NORMAL_COMPLETION);
}
/*------------------------------------------------------------------------*
* usbd_req_get_string_desc
*
* If you don't know the language ID, consider using
* "usbd_req_get_string_any()".
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, void *sdesc,
uint16_t max_len, uint16_t lang_id,
uint8_t string_index)
{
return (usbd_req_get_desc(udev, mtx, NULL, sdesc, 2, max_len, lang_id,
UDESC_STRING, string_index, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_get_config_desc_ptr
*
* This function is used in device side mode to retrieve the pointer
* to the generated config descriptor. This saves allocating space for
* an additional config descriptor when setting the configuration.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_descriptor_ptr(struct usb_device *udev,
struct usb_config_descriptor **ppcd, uint16_t wValue)
{
struct usb_device_request req;
usb_handle_req_t *hr_func;
const void *ptr;
uint16_t len;
usb_error_t err;
req.bmRequestType = UT_READ_DEVICE;
req.bRequest = UR_GET_DESCRIPTOR;
USETW(req.wValue, wValue);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
ptr = NULL;
len = 0;
hr_func = usbd_get_hr_func(udev);
if (hr_func == NULL)
err = USB_ERR_INVAL;
else {
USB_BUS_LOCK(udev->bus);
err = (hr_func) (udev, &req, &ptr, &len);
USB_BUS_UNLOCK(udev->bus);
}
if (err)
ptr = NULL;
else if (ptr == NULL)
err = USB_ERR_INVAL;
*ppcd = __DECONST(struct usb_config_descriptor *, ptr);
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_get_config_desc
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx,
struct usb_config_descriptor *d, uint8_t conf_index)
{
usb_error_t err;
DPRINTFN(4, "confidx=%d\n", conf_index);
err = usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d),
sizeof(*d), 0, UDESC_CONFIG, conf_index, 0);
if (err) {
goto done;
}
/* Extra sanity checking */
if (UGETW(d->wTotalLength) < sizeof(*d)) {
err = USB_ERR_INVAL;
}
done:
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_get_config_desc_full
*
* This function gets the complete USB configuration descriptor and
* ensures that "wTotalLength" is correct.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_config_desc_full(struct usb_device *udev, struct mtx *mtx,
struct usb_config_descriptor **ppcd, struct malloc_type *mtype,
uint8_t index)
{
struct usb_config_descriptor cd;
struct usb_config_descriptor *cdesc;
uint16_t len;
usb_error_t err;
DPRINTFN(4, "index=%d\n", index);
*ppcd = NULL;
err = usbd_req_get_config_desc(udev, mtx, &cd, index);
if (err) {
return (err);
}
/* get full descriptor */
len = UGETW(cd.wTotalLength);
if (len < sizeof(*cdesc)) {
/* corrupt descriptor */
return (USB_ERR_INVAL);
}
cdesc = malloc(len, mtype, M_WAITOK);
if (cdesc == NULL) {
return (USB_ERR_NOMEM);
}
err = usbd_req_get_desc(udev, mtx, NULL, cdesc, len, len, 0,
UDESC_CONFIG, index, 3);
if (err) {
free(cdesc, mtype);
return (err);
}
/* make sure that the device is not fooling us: */
USETW(cdesc->wTotalLength, len);
*ppcd = cdesc;
return (0); /* success */
}
/*------------------------------------------------------------------------*
* usbd_req_get_device_desc
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx,
struct usb_device_descriptor *d)
{
DPRINTFN(4, "\n");
return (usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d),
sizeof(*d), 0, UDESC_DEVICE, 0, 3));
}
/*------------------------------------------------------------------------*
* usbd_req_get_alt_interface_no
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_alt_interface_no(struct usb_device *udev, struct mtx *mtx,
uint8_t *alt_iface_no, uint8_t iface_index)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL))
return (USB_ERR_INVAL);
req.bmRequestType = UT_READ_INTERFACE;
req.bRequest = UR_GET_INTERFACE;
USETW(req.wValue, 0);
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, 1);
return (usbd_do_request(udev, mtx, &req, alt_iface_no));
}
/*------------------------------------------------------------------------*
* usbd_req_set_alt_interface_no
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_alt_interface_no(struct usb_device *udev, struct mtx *mtx,
uint8_t iface_index, uint8_t alt_no)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL))
return (USB_ERR_INVAL);
req.bmRequestType = UT_WRITE_INTERFACE;
req.bRequest = UR_SET_INTERFACE;
req.wValue[0] = alt_no;
req.wValue[1] = 0;
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_get_device_status
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_device_status(struct usb_device *udev, struct mtx *mtx,
struct usb_status *st)
{
struct usb_device_request req;
req.bmRequestType = UT_READ_DEVICE;
req.bRequest = UR_GET_STATUS;
USETW(req.wValue, 0);
USETW(req.wIndex, 0);
USETW(req.wLength, sizeof(*st));
return (usbd_do_request(udev, mtx, &req, st));
}
/*------------------------------------------------------------------------*
* usbd_req_get_hub_descriptor
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_hub_descriptor(struct usb_device *udev, struct mtx *mtx,
struct usb_hub_descriptor *hd, uint8_t nports)
{
struct usb_device_request req;
uint16_t len = (nports + 7 + (8 * 8)) / 8;
req.bmRequestType = UT_READ_CLASS_DEVICE;
req.bRequest = UR_GET_DESCRIPTOR;
USETW2(req.wValue, UDESC_HUB, 0);
USETW(req.wIndex, 0);
USETW(req.wLength, len);
return (usbd_do_request(udev, mtx, &req, hd));
}
/*------------------------------------------------------------------------*
* usbd_req_get_hub_status
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx,
struct usb_hub_status *st)
{
struct usb_device_request req;
req.bmRequestType = UT_READ_CLASS_DEVICE;
req.bRequest = UR_GET_STATUS;
USETW(req.wValue, 0);
USETW(req.wIndex, 0);
USETW(req.wLength, sizeof(struct usb_hub_status));
return (usbd_do_request(udev, mtx, &req, st));
}
/*------------------------------------------------------------------------*
* usbd_req_set_address
*
* This function is used to set the address for an USB device. After
* port reset the USB device will respond at address zero.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t addr)
{
struct usb_device_request req;
DPRINTFN(6, "setting device address=%d\n", addr);
req.bmRequestType = UT_WRITE_DEVICE;
req.bRequest = UR_SET_ADDRESS;
USETW(req.wValue, addr);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
/* Setting the address should not take more than 1 second ! */
return (usbd_do_request_flags(udev, mtx, &req, NULL,
USB_DELAY_STATUS_STAGE, NULL, 1000));
}
/*------------------------------------------------------------------------*
* usbd_req_get_port_status
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx,
struct usb_port_status *ps, uint8_t port)
{
struct usb_device_request req;
req.bmRequestType = UT_READ_CLASS_OTHER;
req.bRequest = UR_GET_STATUS;
USETW(req.wValue, 0);
req.wIndex[0] = port;
req.wIndex[1] = 0;
USETW(req.wLength, sizeof *ps);
return (usbd_do_request(udev, mtx, &req, ps));
}
/*------------------------------------------------------------------------*
* usbd_req_clear_hub_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_clear_hub_feature(struct usb_device *udev, struct mtx *mtx,
uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_CLASS_DEVICE;
req.bRequest = UR_CLEAR_FEATURE;
USETW(req.wValue, sel);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_set_hub_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx,
uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_CLASS_DEVICE;
req.bRequest = UR_SET_FEATURE;
USETW(req.wValue, sel);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_clear_port_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_clear_port_feature(struct usb_device *udev, struct mtx *mtx,
uint8_t port, uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_CLASS_OTHER;
req.bRequest = UR_CLEAR_FEATURE;
USETW(req.wValue, sel);
req.wIndex[0] = port;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_set_port_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_port_feature(struct usb_device *udev, struct mtx *mtx,
uint8_t port, uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_CLASS_OTHER;
req.bRequest = UR_SET_FEATURE;
USETW(req.wValue, sel);
req.wIndex[0] = port;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_set_protocol
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx,
uint8_t iface_index, uint16_t report)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL)) {
return (USB_ERR_INVAL);
}
DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n",
iface, report, iface->idesc->bInterfaceNumber);
req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
req.bRequest = UR_SET_PROTOCOL;
USETW(req.wValue, report);
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_set_report
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, void *data, uint16_t len,
uint8_t iface_index, uint8_t type, uint8_t id)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL)) {
return (USB_ERR_INVAL);
}
DPRINTFN(5, "len=%d\n", len);
req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
req.bRequest = UR_SET_REPORT;
USETW2(req.wValue, type, id);
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, len);
return (usbd_do_request(udev, mtx, &req, data));
}
/*------------------------------------------------------------------------*
* usbd_req_get_report
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, void *data,
uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL) || (id == 0)) {
return (USB_ERR_INVAL);
}
DPRINTFN(5, "len=%d\n", len);
req.bmRequestType = UT_READ_CLASS_INTERFACE;
req.bRequest = UR_GET_REPORT;
USETW2(req.wValue, type, id);
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, len);
return (usbd_do_request(udev, mtx, &req, data));
}
/*------------------------------------------------------------------------*
* usbd_req_set_idle
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx,
uint8_t iface_index, uint8_t duration, uint8_t id)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL)) {
return (USB_ERR_INVAL);
}
DPRINTFN(5, "%d %d\n", duration, id);
req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
req.bRequest = UR_SET_IDLE;
USETW2(req.wValue, duration, id);
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_get_report_descriptor
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_report_descriptor(struct usb_device *udev, struct mtx *mtx,
void *d, uint16_t size, uint8_t iface_index)
{
struct usb_interface *iface = usbd_get_iface(udev, iface_index);
struct usb_device_request req;
if ((iface == NULL) || (iface->idesc == NULL)) {
return (USB_ERR_INVAL);
}
req.bmRequestType = UT_READ_INTERFACE;
req.bRequest = UR_GET_DESCRIPTOR;
USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */
req.wIndex[0] = iface->idesc->bInterfaceNumber;
req.wIndex[1] = 0;
USETW(req.wLength, size);
return (usbd_do_request(udev, mtx, &req, d));
}
/*------------------------------------------------------------------------*
* usbd_req_set_config
*
* This function is used to select the current configuration number in
* both USB device side mode and USB host side mode. When setting the
* configuration the function of the interfaces can change.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, uint8_t conf)
{
struct usb_device_request req;
DPRINTF("setting config %d\n", conf);
/* do "set configuration" request */
req.bmRequestType = UT_WRITE_DEVICE;
req.bRequest = UR_SET_CONFIG;
req.wValue[0] = conf;
req.wValue[1] = 0;
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_get_config
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, uint8_t *pconf)
{
struct usb_device_request req;
req.bmRequestType = UT_READ_DEVICE;
req.bRequest = UR_GET_CONFIG;
USETW(req.wValue, 0);
USETW(req.wIndex, 0);
USETW(req.wLength, 1);
return (usbd_do_request(udev, mtx, &req, pconf));
}
/*------------------------------------------------------------------------*
* usbd_req_re_enumerate
*
* NOTE: After this function returns the hardware is in the
* unconfigured state! The application is responsible for setting a
* new configuration.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx)
{
struct usb_device *parent_hub;
usb_error_t err;
uint8_t old_addr;
uint8_t do_retry = 1;
if (udev->flags.usb_mode != USB_MODE_HOST) {
return (USB_ERR_INVAL);
}
old_addr = udev->address;
parent_hub = udev->parent_hub;
if (parent_hub == NULL) {
return (USB_ERR_INVAL);
}
retry:
err = usbd_req_reset_port(parent_hub, mtx, udev->port_no);
if (err) {
DPRINTFN(0, "addr=%d, port reset failed, %s\n",
old_addr, usbd_errstr(err));
goto done;
}
/*
* After that the port has been reset our device should be at
* address zero:
*/
udev->address = USB_START_ADDR;
/* reset "bMaxPacketSize" */
udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET;
/*
* Restore device address:
*/
err = usbd_req_set_address(udev, mtx, old_addr);
if (err) {
/* XXX ignore any errors! */
DPRINTFN(0, "addr=%d, set address failed! (%s, ignored)\n",
old_addr, usbd_errstr(err));
}
/* restore device address */
udev->address = old_addr;
/* allow device time to set new address */
usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_SET_ADDRESS_SETTLE));
/* get the device descriptor */
err = usbd_req_get_desc(udev, mtx, NULL, &udev->ddesc,
USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0);
if (err) {
DPRINTFN(0, "getting device descriptor "
"at addr %d failed, %s\n", udev->address,
usbd_errstr(err));
goto done;
}
/* get the full device descriptor */
err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
if (err) {
DPRINTFN(0, "addr=%d, getting device "
"descriptor failed, %s\n", old_addr,
usbd_errstr(err));
goto done;
}
done:
if (err && do_retry) {
/* give the USB firmware some time to load */
usb_pause_mtx(mtx, hz / 2);
/* no more retries after this retry */
do_retry = 0;
/* try again */
goto retry;
}
/* restore address */
udev->address = old_addr;
return (err);
}
/*------------------------------------------------------------------------*
* usbd_req_clear_device_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_clear_device_feature(struct usb_device *udev, struct mtx *mtx,
uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_DEVICE;
req.bRequest = UR_CLEAR_FEATURE;
USETW(req.wValue, sel);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}
/*------------------------------------------------------------------------*
* usbd_req_set_device_feature
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usbd_req_set_device_feature(struct usb_device *udev, struct mtx *mtx,
uint16_t sel)
{
struct usb_device_request req;
req.bmRequestType = UT_WRITE_DEVICE;
req.bRequest = UR_SET_FEATURE;
USETW(req.wValue, sel);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
return (usbd_do_request(udev, mtx, &req, 0));
}