diff options
Diffstat (limited to 'sys/dev/usb/usb_handle_request.c')
-rw-r--r-- | sys/dev/usb/usb_handle_request.c | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/sys/dev/usb/usb_handle_request.c b/sys/dev/usb/usb_handle_request.c new file mode 100644 index 000000000000..7b50400d7eb9 --- /dev/null +++ b/sys/dev/usb/usb_handle_request.c @@ -0,0 +1,811 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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 + * across 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); +} |