freebsd-dev/sys/dev/usb/usb_handle_request.c
Hans Petter Selasky a18a7a414a Resolve a LOR after r246616. Protect control requests using the USB device
enumeration lock. Make sure all callers of usbd_enum_lock() check the return
value. Remove the control transfer specific lock. Bump the FreeBSD version
number, hence external USB modules may need to be recompiled due to a USB
device structure change.

MFC after:	1 week
2013-02-13 12:35:17 +00:00

812 lines
19 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.
*/
#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#else
#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/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 "usb_if.h"
#define USB_DEBUG_VAR usb_debug
#include <dev/usb/usb_core.h>
#include <dev/usb/usb_process.h>
#include <dev/usb/usb_busdma.h>
#include <dev/usb/usb_transfer.h>
#include <dev/usb/usb_device.h>
#include <dev/usb/usb_debug.h>
#include <dev/usb/usb_dynamic.h>
#include <dev/usb/usb_hub.h>
#include <dev/usb/usb_controller.h>
#include <dev/usb/usb_bus.h>
#endif /* USB_GLOBAL_INCLUDE_FILE */
/* function prototypes */
static uint8_t usb_handle_get_stall(struct usb_device *, uint8_t);
static usb_error_t usb_handle_remote_wakeup(struct usb_xfer *, uint8_t);
static usb_error_t usb_handle_request(struct usb_xfer *);
static usb_error_t usb_handle_set_config(struct usb_xfer *, uint8_t);
static usb_error_t usb_handle_set_stall(struct usb_xfer *, uint8_t,
uint8_t);
static usb_error_t usb_handle_iface_request(struct usb_xfer *, void **,
uint16_t *, struct usb_device_request, uint16_t,
uint8_t);
/*------------------------------------------------------------------------*
* usb_handle_request_callback
*
* This function is the USB callback for generic USB Device control
* transfers.
*------------------------------------------------------------------------*/
void
usb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error)
{
usb_error_t err;
/* check the current transfer state */
switch (USB_GET_STATE(xfer)) {
case USB_ST_SETUP:
case USB_ST_TRANSFERRED:
/* handle the request */
err = usb_handle_request(xfer);
if (err) {
if (err == USB_ERR_BAD_CONTEXT) {
/* we need to re-setup the control transfer */
usb_needs_explore(xfer->xroot->bus, 0);
break;
}
goto tr_restart;
}
usbd_transfer_submit(xfer);
break;
default:
/* check if a control transfer is active */
if (xfer->flags_int.control_rem != 0xFFFF) {
/* handle the request */
err = usb_handle_request(xfer);
}
if (xfer->error != USB_ERR_CANCELLED) {
/* should not happen - try stalling */
goto tr_restart;
}
break;
}
return;
tr_restart:
/*
* If a control transfer is active, stall it, and wait for the
* next control transfer.
*/
usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request));
xfer->nframes = 1;
xfer->flags.manual_status = 1;
xfer->flags.force_short_xfer = 0;
usbd_xfer_set_stall(xfer); /* cancel previous transfer, if any */
usbd_transfer_submit(xfer);
}
/*------------------------------------------------------------------------*
* usb_handle_set_config
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no)
{
struct usb_device *udev = xfer->xroot->udev;
usb_error_t err = 0;
uint8_t do_unlock;
/*
* We need to protect against other threads doing probe and
* attach:
*/
USB_XFER_UNLOCK(xfer);
/* Prevent re-enumeration */
do_unlock = usbd_enum_lock(udev);
if (conf_no == USB_UNCONFIG_NO) {
conf_no = USB_UNCONFIG_INDEX;
} else {
/*
* The relationship between config number and config index
* is very simple in our case:
*/
conf_no--;
}
if (usbd_set_config_index(udev, conf_no)) {
DPRINTF("set config %d failed\n", conf_no);
err = USB_ERR_STALLED;
goto done;
}
if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) {
DPRINTF("probe and attach failed\n");
err = USB_ERR_STALLED;
goto done;
}
done:
if (do_unlock)
usbd_enum_unlock(udev);
USB_XFER_LOCK(xfer);
return (err);
}
static usb_error_t
usb_check_alt_setting(struct usb_device *udev,
struct usb_interface *iface, uint8_t alt_index)
{
uint8_t do_unlock;
usb_error_t err = 0;
/* Prevent re-enumeration */
do_unlock = usbd_enum_lock(udev);
if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc))
err = USB_ERR_INVAL;
if (do_unlock)
usbd_enum_unlock(udev);
return (err);
}
/*------------------------------------------------------------------------*
* usb_handle_iface_request
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_handle_iface_request(struct usb_xfer *xfer,
void **ppdata, uint16_t *plen,
struct usb_device_request req, uint16_t off, uint8_t state)
{
struct usb_interface *iface;
struct usb_interface *iface_parent; /* parent interface */
struct usb_device *udev = xfer->xroot->udev;
int error;
uint8_t iface_index;
uint8_t temp_state;
uint8_t do_unlock;
if ((req.bmRequestType & 0x1F) == UT_INTERFACE) {
iface_index = req.wIndex[0]; /* unicast */
} else {
iface_index = 0; /* broadcast */
}
/*
* We need to protect against other threads doing probe and
* attach:
*/
USB_XFER_UNLOCK(xfer);
/* Prevent re-enumeration */
do_unlock = usbd_enum_lock(udev);
error = ENXIO;
tr_repeat:
iface = usbd_get_iface(udev, iface_index);
if ((iface == NULL) ||
(iface->idesc == NULL)) {
/* end of interfaces non-existing interface */
goto tr_stalled;
}
/* set initial state */
temp_state = state;
/* forward request to interface, if any */
if ((error != 0) &&
(error != ENOTTY) &&
(iface->subdev != NULL) &&
device_is_attached(iface->subdev)) {
#if 0
DEVMETHOD(usb_handle_request, NULL); /* dummy */
#endif
error = USB_HANDLE_REQUEST(iface->subdev,
&req, ppdata, plen,
off, &temp_state);
}
iface_parent = usbd_get_iface(udev, iface->parent_iface_index);
if ((iface_parent == NULL) ||
(iface_parent->idesc == NULL)) {
/* non-existing interface */
iface_parent = NULL;
}
/* forward request to parent interface, if any */
if ((error != 0) &&
(error != ENOTTY) &&
(iface_parent != NULL) &&
(iface_parent->subdev != NULL) &&
((req.bmRequestType & 0x1F) == UT_INTERFACE) &&
(iface_parent->subdev != iface->subdev) &&
device_is_attached(iface_parent->subdev)) {
error = USB_HANDLE_REQUEST(iface_parent->subdev,
&req, ppdata, plen, off, &temp_state);
}
if (error == 0) {
/* negativly adjust pointer and length */
*ppdata = ((uint8_t *)(*ppdata)) - off;
*plen += off;
if ((state == USB_HR_NOT_COMPLETE) &&
(temp_state == USB_HR_COMPLETE_OK))
goto tr_short;
else
goto tr_valid;
} else if (error == ENOTTY) {
goto tr_stalled;
}
if ((req.bmRequestType & 0x1F) != UT_INTERFACE) {
iface_index++; /* iterate */
goto tr_repeat;
}
if (state != USB_HR_NOT_COMPLETE) {
/* we are complete */
goto tr_valid;
}
switch (req.bmRequestType) {
case UT_WRITE_INTERFACE:
switch (req.bRequest) {
case UR_SET_INTERFACE:
/*
* We assume that the endpoints are the same
* accross the alternate settings.
*
* Reset the endpoints, because re-attaching
* only a part of the device is not possible.
*/
error = usb_check_alt_setting(udev,
iface, req.wValue[0]);
if (error) {
DPRINTF("alt setting does not exist %s\n",
usbd_errstr(error));
goto tr_stalled;
}
error = usb_reset_iface_endpoints(udev, iface_index);
if (error) {
DPRINTF("alt setting failed %s\n",
usbd_errstr(error));
goto tr_stalled;
}
/* update the current alternate setting */
iface->alt_index = req.wValue[0];
break;
default:
goto tr_stalled;
}
break;
case UT_READ_INTERFACE:
switch (req.bRequest) {
case UR_GET_INTERFACE:
*ppdata = &iface->alt_index;
*plen = 1;
break;
default:
goto tr_stalled;
}
break;
default:
goto tr_stalled;
}
tr_valid:
if (do_unlock)
usbd_enum_unlock(udev);
USB_XFER_LOCK(xfer);
return (0);
tr_short:
if (do_unlock)
usbd_enum_unlock(udev);
USB_XFER_LOCK(xfer);
return (USB_ERR_SHORT_XFER);
tr_stalled:
if (do_unlock)
usbd_enum_unlock(udev);
USB_XFER_LOCK(xfer);
return (USB_ERR_STALLED);
}
/*------------------------------------------------------------------------*
* usb_handle_stall
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall)
{
struct usb_device *udev = xfer->xroot->udev;
usb_error_t err;
USB_XFER_UNLOCK(xfer);
err = usbd_set_endpoint_stall(udev,
usbd_get_ep_by_addr(udev, ep), do_stall);
USB_XFER_LOCK(xfer);
return (err);
}
/*------------------------------------------------------------------------*
* usb_handle_get_stall
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static uint8_t
usb_handle_get_stall(struct usb_device *udev, uint8_t ea_val)
{
struct usb_endpoint *ep;
uint8_t halted;
ep = usbd_get_ep_by_addr(udev, ea_val);
if (ep == NULL) {
/* nothing to do */
return (0);
}
USB_BUS_LOCK(udev->bus);
halted = ep->is_stalled;
USB_BUS_UNLOCK(udev->bus);
return (halted);
}
/*------------------------------------------------------------------------*
* usb_handle_remote_wakeup
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on)
{
struct usb_device *udev;
struct usb_bus *bus;
udev = xfer->xroot->udev;
bus = udev->bus;
USB_BUS_LOCK(bus);
if (is_on) {
udev->flags.remote_wakeup = 1;
} else {
udev->flags.remote_wakeup = 0;
}
USB_BUS_UNLOCK(bus);
#if USB_HAVE_POWERD
/* In case we are out of sync, update the power state. */
usb_bus_power_update(udev->bus);
#endif
return (0); /* success */
}
/*------------------------------------------------------------------------*
* usb_handle_request
*
* Internal state sequence:
*
* USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR
*
* Returns:
* 0: Ready to start hardware
* Else: Stall current transfer, if any
*------------------------------------------------------------------------*/
static usb_error_t
usb_handle_request(struct usb_xfer *xfer)
{
struct usb_device_request req;
struct usb_device *udev;
const void *src_zcopy; /* zero-copy source pointer */
const void *src_mcopy; /* non zero-copy source pointer */
uint16_t off; /* data offset */
uint16_t rem; /* data remainder */
uint16_t max_len; /* max fragment length */
uint16_t wValue;
uint8_t state;
uint8_t is_complete = 1;
usb_error_t err;
union {
uWord wStatus;
uint8_t buf[2];
} temp;
/*
* Filter the USB transfer state into
* something which we understand:
*/
switch (USB_GET_STATE(xfer)) {
case USB_ST_SETUP:
state = USB_HR_NOT_COMPLETE;
if (!xfer->flags_int.control_act) {
/* nothing to do */
goto tr_stalled;
}
break;
case USB_ST_TRANSFERRED:
if (!xfer->flags_int.control_act) {
state = USB_HR_COMPLETE_OK;
} else {
state = USB_HR_NOT_COMPLETE;
}
break;
default:
state = USB_HR_COMPLETE_ERR;
break;
}
/* reset frame stuff */
usbd_xfer_set_frame_len(xfer, 0, 0);
usbd_xfer_set_frame_offset(xfer, 0, 0);
usbd_xfer_set_frame_offset(xfer, sizeof(req), 1);
/* get the current request, if any */
usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));
if (xfer->flags_int.control_rem == 0xFFFF) {
/* first time - not initialised */
rem = UGETW(req.wLength);
off = 0;
} else {
/* not first time - initialised */
rem = xfer->flags_int.control_rem;
off = UGETW(req.wLength) - rem;
}
/* set some defaults */
max_len = 0;
src_zcopy = NULL;
src_mcopy = NULL;
udev = xfer->xroot->udev;
/* get some request fields decoded */
wValue = UGETW(req.wValue);
DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x "
"off=0x%x rem=0x%x, state=%d\n", req.bmRequestType,
req.bRequest, wValue, UGETW(req.wIndex), off, rem, state);
/* demultiplex the control request */
switch (req.bmRequestType) {
case UT_READ_DEVICE:
if (state != USB_HR_NOT_COMPLETE) {
break;
}
switch (req.bRequest) {
case UR_GET_DESCRIPTOR:
goto tr_handle_get_descriptor;
case UR_GET_CONFIG:
goto tr_handle_get_config;
case UR_GET_STATUS:
goto tr_handle_get_status;
default:
goto tr_stalled;
}
break;
case UT_WRITE_DEVICE:
switch (req.bRequest) {
case UR_SET_ADDRESS:
goto tr_handle_set_address;
case UR_SET_CONFIG:
goto tr_handle_set_config;
case UR_CLEAR_FEATURE:
switch (wValue) {
case UF_DEVICE_REMOTE_WAKEUP:
goto tr_handle_clear_wakeup;
default:
goto tr_stalled;
}
break;
case UR_SET_FEATURE:
switch (wValue) {
case UF_DEVICE_REMOTE_WAKEUP:
goto tr_handle_set_wakeup;
default:
goto tr_stalled;
}
break;
default:
goto tr_stalled;
}
break;
case UT_WRITE_ENDPOINT:
switch (req.bRequest) {
case UR_CLEAR_FEATURE:
switch (wValue) {
case UF_ENDPOINT_HALT:
goto tr_handle_clear_halt;
default:
goto tr_stalled;
}
break;
case UR_SET_FEATURE:
switch (wValue) {
case UF_ENDPOINT_HALT:
goto tr_handle_set_halt;
default:
goto tr_stalled;
}
break;
default:
goto tr_stalled;
}
break;
case UT_READ_ENDPOINT:
switch (req.bRequest) {
case UR_GET_STATUS:
goto tr_handle_get_ep_status;
default:
goto tr_stalled;
}
break;
default:
/* we use "USB_ADD_BYTES" to de-const the src_zcopy */
err = usb_handle_iface_request(xfer,
USB_ADD_BYTES(&src_zcopy, 0),
&max_len, req, off, state);
if (err == 0) {
is_complete = 0;
goto tr_valid;
} else if (err == USB_ERR_SHORT_XFER) {
goto tr_valid;
}
/*
* Reset zero-copy pointer and max length
* variable in case they were unintentionally
* set:
*/
src_zcopy = NULL;
max_len = 0;
/*
* Check if we have a vendor specific
* descriptor:
*/
goto tr_handle_get_descriptor;
}
goto tr_valid;
tr_handle_get_descriptor:
err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len);
if (err)
goto tr_stalled;
if (src_zcopy == NULL)
goto tr_stalled;
goto tr_valid;
tr_handle_get_config:
temp.buf[0] = udev->curr_config_no;
src_mcopy = temp.buf;
max_len = 1;
goto tr_valid;
tr_handle_get_status:
wValue = 0;
USB_BUS_LOCK(udev->bus);
if (udev->flags.remote_wakeup) {
wValue |= UDS_REMOTE_WAKEUP;
}
if (udev->flags.self_powered) {
wValue |= UDS_SELF_POWERED;
}
USB_BUS_UNLOCK(udev->bus);
USETW(temp.wStatus, wValue);
src_mcopy = temp.wStatus;
max_len = sizeof(temp.wStatus);
goto tr_valid;
tr_handle_set_address:
if (state == USB_HR_NOT_COMPLETE) {
if (wValue >= 0x80) {
/* invalid value */
goto tr_stalled;
} else if (udev->curr_config_no != 0) {
/* we are configured ! */
goto tr_stalled;
}
} else if (state != USB_HR_NOT_COMPLETE) {
udev->address = (wValue & 0x7F);
goto tr_bad_context;
}
goto tr_valid;
tr_handle_set_config:
if (state == USB_HR_NOT_COMPLETE) {
if (usb_handle_set_config(xfer, req.wValue[0])) {
goto tr_stalled;
}
}
goto tr_valid;
tr_handle_clear_halt:
if (state == USB_HR_NOT_COMPLETE) {
if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) {
goto tr_stalled;
}
}
goto tr_valid;
tr_handle_clear_wakeup:
if (state == USB_HR_NOT_COMPLETE) {
if (usb_handle_remote_wakeup(xfer, 0)) {
goto tr_stalled;
}
}
goto tr_valid;
tr_handle_set_halt:
if (state == USB_HR_NOT_COMPLETE) {
if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) {
goto tr_stalled;
}
}
goto tr_valid;
tr_handle_set_wakeup:
if (state == USB_HR_NOT_COMPLETE) {
if (usb_handle_remote_wakeup(xfer, 1)) {
goto tr_stalled;
}
}
goto tr_valid;
tr_handle_get_ep_status:
if (state == USB_HR_NOT_COMPLETE) {
temp.wStatus[0] =
usb_handle_get_stall(udev, req.wIndex[0]);
temp.wStatus[1] = 0;
src_mcopy = temp.wStatus;
max_len = sizeof(temp.wStatus);
}
goto tr_valid;
tr_valid:
if (state != USB_HR_NOT_COMPLETE) {
goto tr_stalled;
}
/* subtract offset from length */
max_len -= off;
/* Compute the real maximum data length */
if (max_len > xfer->max_data_length) {
max_len = usbd_xfer_max_len(xfer);
}
if (max_len > rem) {
max_len = rem;
}
/*
* If the remainder is greater than the maximum data length,
* we need to truncate the value for the sake of the
* comparison below:
*/
if (rem > xfer->max_data_length) {
rem = usbd_xfer_max_len(xfer);
}
if ((rem != max_len) && (is_complete != 0)) {
/*
* If we don't transfer the data we can transfer, then
* the transfer is short !
*/
xfer->flags.force_short_xfer = 1;
xfer->nframes = 2;
} else {
/*
* Default case
*/
xfer->flags.force_short_xfer = 0;
xfer->nframes = max_len ? 2 : 1;
}
if (max_len > 0) {
if (src_mcopy) {
src_mcopy = USB_ADD_BYTES(src_mcopy, off);
usbd_copy_in(xfer->frbuffers + 1, 0,
src_mcopy, max_len);
usbd_xfer_set_frame_len(xfer, 1, max_len);
} else {
usbd_xfer_set_frame_data(xfer, 1,
USB_ADD_BYTES(src_zcopy, off), max_len);
}
} else {
/* the end is reached, send status */
xfer->flags.manual_status = 0;
usbd_xfer_set_frame_len(xfer, 1, 0);
}
DPRINTF("success\n");
return (0); /* success */
tr_stalled:
DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ?
"complete" : "stalled");
return (USB_ERR_STALLED);
tr_bad_context:
DPRINTF("bad context\n");
return (USB_ERR_BAD_CONTEXT);
}