/* $FreeBSD: head/sys/dev/usb/usb_handle_request.c 246759 2013-02-13 12:35:17Z hselasky $ */ /*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #include /* 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); }