a18a7a414a
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
812 lines
19 KiB
C
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);
|
|
}
|