/** * @defgroup usbd_control_file Generic USB Control Requests * * @ingroup USBD * * @brief Generic USB Control Requests * * @author @htmlonly © @endhtmlonly 2016 * Kuldeep Singh Dhaka * * @author @htmlonly © @endhtmlonly 2010 * Gareth McMullin * * @date 11 September 2016 * * LGPL License Terms @ref lgpl_license */ /* * This file is part of the unicore-mx project. * * Copyright (C) 2010 Gareth McMullin * Copyright (C) 2015-2017 Kuldeep Singh Dhaka * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ /**@{*/ #include #include #include #include "usbd_private.h" enum usbd_control_result { USBD_REQ_STALL = 0, /**< Stall the Request */ USBD_REQ_HANDLED = 1, /**< Request has been handled */ USBD_REQ_NEXT = 2, /**< Request to be served to next handler */ }; /** * Control request arguments. */ struct usbd_control_arg { usbd_device *device; const struct usb_setup_data *setup; /**< setup data */ /** Complete callback (after status stage) */ usbd_control_transfer_callback complete; void *buf; /** Buffer */ size_t len; /**< @a buf length */ }; static inline uint8_t descriptor_type(uint16_t wValue) { return wValue >> 8; } static inline uint8_t descriptor_index(uint16_t wValue) { return wValue & 0xFF; } /** * Handle GET_DESCRIPTOR(string) request from host * @param[in] arg Arguments */ static enum usbd_control_result standard_get_descriptor_string(struct usbd_control_arg *arg) { const struct usbd_info_string *string = NULL; const struct usb_string_descriptor *send = NULL; uint8_t index = descriptor_index(arg->setup->wValue); uint16_t lang_id = arg->setup->wIndex; unsigned i, avail_lang; const struct usbd_info *info = arg->device->info; /* Search for the string data */ if (arg->device->current_config != NULL) { for (i = 0; i < info->device.desc->bNumConfigurations; i++) { if (arg->device->current_config == info->config[i].desc) { string = info->config[i].string; break; } } } else { string = info->device.string; } if (string == NULL) { /* No string descriptor */ return USBD_REQ_STALL; } if (index > string->count) { /* Out of range */ return USBD_REQ_STALL; } if (!index) { send = string->lang_list; } else { /* Search for the data for the specified language id */ avail_lang = (string->lang_list->bLength - 2) / 2; for (i = 0; i < avail_lang; i++) { if (string->lang_list->wData[i] == lang_id) { send = string->data[i][index - 1]; break; } } } if (send == NULL) { /* No string descriptor to send */ return USBD_REQ_STALL; } arg->buf = (void *) send; arg->len = MIN(arg->setup->wLength, send->bLength); /* we have done it! */ return USBD_REQ_HANDLED; } /** * Handle GET_DESCRIPTOR(configuration) request from host * @param[in] arg Arguments */ static enum usbd_control_result standard_get_descriptor_config(struct usbd_control_arg *arg) { uint8_t index = descriptor_index(arg->setup->wValue); const struct usb_config_descriptor *cfg; const struct usbd_info *info = arg->device->info; if (index >= info->device.desc->bNumConfigurations) { return USBD_REQ_STALL; } cfg = info->config[index].desc; arg->buf = (void *) cfg; arg->len = MIN(arg->setup->wLength, cfg->wTotalLength); return USBD_REQ_HANDLED; } /** * Handle GET_DESCRIPTOR(device) request from host * @param[in] arg Arguments */ static enum usbd_control_result standard_get_descriptor_device(struct usbd_control_arg *arg) { const struct usb_device_descriptor *dd = arg->device->info->device.desc; arg->buf = (void *) dd; arg->len = MIN(arg->setup->wLength, dd->bLength); return USBD_REQ_HANDLED; } /** * Handle GET_DESCRIPTOR request from host * @param[in] arg Arguments */ static enum usbd_control_result standard_get_descriptor(struct usbd_control_arg *arg) { uint8_t type = descriptor_type(arg->setup->wValue); LOGF_LN("GET_DESCRIPTOR: type = %"PRIu8", index = %"PRIu8, type, descriptor_index(arg->setup->wValue)); switch (type) { case USB_DT_DEVICE: return standard_get_descriptor_device(arg); case USB_DT_CONFIGURATION: return standard_get_descriptor_config(arg); case USB_DT_STRING: return standard_get_descriptor_string(arg); } return USBD_REQ_STALL; } static uint8_t _set_addr_value; /** * Callback when the SET_ADDRESS stage as completed * @param[in] dev USB Device * @return OK */ static usbd_control_transfer_feedback _set_address_complete(usbd_device *dev, const usbd_control_transfer_callback_arg *arg) { (void) arg; dev->backend->set_address(dev, _set_addr_value); return USBD_CONTROL_TRANSFER_OK; } /** * Handle SET_ADDRESS request from host * @param[in] arg Arguments * @return Result */ static enum usbd_control_result standard_set_address(struct usbd_control_arg *arg) { uint8_t new_addr = arg->setup->wValue; LOGF_LN("SET_ADDRESS: %"PRIu8, new_addr); /* Control Out should only be used */ if (arg->setup->bmRequestType != 0x00) { return USBD_REQ_STALL; } /* * Some parts require the address to be set before status stage. */ if (arg->device->backend->set_address_before_status) { arg->device->backend->set_address(arg->device, new_addr); } else { /* Store the new address in EP0 buffer for complete callback */ _set_addr_value = new_addr; arg->complete = _set_address_complete; } return USBD_REQ_HANDLED; } /** * Search for the configuration with bConfigurationValue == @a value * @return the found configuration (success) * @return NULL on failure (not found) */ static const struct usb_config_descriptor* search_config(usbd_device *dev, uint8_t value) { unsigned i; for (i = 0; i < dev->info->device.desc->bNumConfigurations; i++) { const struct usb_config_descriptor *cfg = dev->info->config[i].desc; if (cfg->bConfigurationValue == value) { return cfg; } } return NULL; } /** * Handle SET_CONFIGURATION from host * @param[in] arg Arguments * @return Result */ static enum usbd_control_result standard_set_configuration(struct usbd_control_arg *arg) { const struct usb_config_descriptor *cfg = NULL; uint8_t bConfigValue = arg->setup->wValue; struct usbd_device *dev = arg->device; LOGF_LN("SET_CONFIGURATION: %"PRIu8, bConfigValue); if (bConfigValue > 0) { cfg = search_config(dev, bConfigValue); if (cfg == NULL) { return USBD_REQ_STALL; } } dev->current_config = cfg; #if (USBD_INTERFACE_MAX > 0) if (cfg != NULL) { /* Make all pointer NULL */ memset(dev->current_iface, 0, sizeof(dev->current_iface)); } #endif /* (USBD_INTERFACE_MAX > 0) */ /* We have no use for old non EP0 transfer now */ LOG_LN("SET_CONFIGURATION: Removing all non endpoint 0 transfer"); usbd_purge_all_non_ep0_transfer(dev, USBD_ERR_CONFIG_CHANGE); /* Make sure that while backend is preparing the endpoint, no transfers * are submitted to backend. * When preperation is over, transfers will be schedule in the order they * were submitted! */ LOG_LN("SET_CONFIGURATION: force_all_new_urb_to_waiting = true"); dev->urbs.force_all_new_urb_to_waiting = true; if (dev->backend->ep_prepare_start) { dev->backend->ep_prepare_start(dev); } if (dev->callback.set_config != NULL) { dev->callback.set_config(dev, cfg); } if (dev->backend->ep_prepare_end) { dev->backend->ep_prepare_end(dev); } /* Now all transfer are allowed */ LOG_LN("SET_CONFIGURATION: force_all_new_urb_to_waiting = false"); dev->urbs.force_all_new_urb_to_waiting = false; /* Schedule new transfer that have been submitted in set_config callback */ usbd_urb_schedule(dev); return USBD_REQ_HANDLED; } /** * Handle GET_CONFIGURATION from host * @param[in] arg Arguments * @return Result */ static enum usbd_control_result standard_get_configuration(struct usbd_control_arg *arg) { const uint8_t *value; static const uint8_t ZERO = 0; const struct usb_config_descriptor *config; config = arg->device->current_config; value = (config != NULL) ? &config->bConfigurationValue /* configured state */ : &ZERO /* address stage */; arg->buf = (void *) value; arg->len = MIN(arg->setup->wLength, 1); return USBD_REQ_HANDLED; } /** * Search for Interface with @a bInterfaceNumber and * @a bAlternateSetting in @a cfg * @param[in] cfg Configuration descriptor * @param[in] bInterfaceNumber Interface number * @param[in] bAlternateSetting Alternate setting number */ static const struct usb_interface_descriptor * search_iface(const struct usb_config_descriptor *cfg, uint8_t bInterfaceNumber, uint8_t bAlternateSetting) { unsigned i = cfg->bLength; while (i < cfg->wTotalLength) { const struct usb_interface_descriptor *iface; iface = ((const void *) cfg) + i; if (iface->bDescriptorType == USB_DT_INTERFACE && iface->bInterfaceNumber == bInterfaceNumber && iface->bAlternateSetting == bAlternateSetting) { return iface; } i += iface->bLength; } return NULL; } /** * Handle SET_INTERFACE from host * @param[in] arg Arguments * @return Result */ static enum usbd_control_result standard_set_interface(struct usbd_control_arg *arg) { const struct usb_interface_descriptor *iface; uint8_t index = arg->setup->wIndex; uint8_t alt_set = arg->setup->wValue; LOGF_LN("SET_INTERFACE: num = %"PRIu8", alt-set = %"PRIu8, index, alt_set); /* STALL since the device is in address stage (or unconfigured) */ if (arg->device->current_config == NULL) { return USBD_REQ_STALL; } #if (USBD_INTERFACE_MAX == 0) if (alt_set) { /* SET_INTERFACE for non-zero alternate setting not supported * because we do not have backing storage to store the information */ return USBD_REQ_STALL; } #else /* (USBD_INTERFACE_MAX == 0) */ if ((index >= USBD_INTERFACE_MAX) && alt_set) { /* SET_INTERFACE for the interface is not possible because * the array is not sufficiently big to store the value */ return USBD_REQ_STALL; } #endif /* (USBD_INTERFACE_MAX == 0) */ /* search for the interface alternate setting */ iface = search_iface(arg->device->current_config, index, alt_set); if (iface == NULL) { /* interface alternate setting not found */ return USBD_REQ_STALL; } #if (USBD_INTERFACE_MAX > 0) arg->device->current_iface[index] = iface; #endif /* (USBD_INTERFACE_MAX > 0) */ if (arg->device->callback.set_interface != NULL) { arg->device->callback.set_interface(arg->device, iface); } return USBD_REQ_HANDLED; } /** * Handle GET_INTERFACE from host * @param[in] arg Arguments * @return Result */ static enum usbd_control_result standard_get_interface(struct usbd_control_arg *arg) { #if (USBD_INTERFACE_MAX == 0) /* We have no backing information to provide to host */ return USBD_REQ_STALL; #else /* (USBD_INTERFACE_MAX == 0) */ uint8_t index = arg->setup->wIndex; const struct usb_interface_descriptor *iface; /* STALL since the device is in address stage (or unconfigured) */ if (arg->device->current_config == NULL) { return USBD_REQ_STALL; } /* We have no backing information to provide to host */ if (index >= USBD_INTERFACE_MAX) { /* SET_INTERFACE for the interface is not possible because * the array is not sufficiently big to store the value */ return USBD_REQ_STALL; } iface = arg->device->current_iface[index]; if (iface == NULL) { /* SET_INTERFACE not called yet. * USB specs not documented the behaviour. resorting to STALL */ return USBD_REQ_STALL; } arg->buf = (void *) &iface->bAlternateSetting; arg->len = MIN(arg->setup->wLength, 1); return USBD_REQ_HANDLED; #endif /* (USBD_INTERFACE_MAX == 0) */ } static enum usbd_control_result standard_device_get_status(struct usbd_control_arg *arg) { static const uint16_t VALUE = 0; arg->buf = (void *) &VALUE; arg->len = MIN(arg->setup->wLength, 2); return USBD_REQ_HANDLED; } static enum usbd_control_result standard_interface_get_status(struct usbd_control_arg *arg) { static const uint16_t VALUE = 0; arg->buf = (void *) &VALUE; arg->len = MIN(arg->setup->wLength, 2); return USBD_REQ_HANDLED; } static enum usbd_control_result standard_endpoint_get_status(struct usbd_control_arg *arg) { static const uint16_t NOT_HALTED = 0; static const uint16_t HALTED = 1; bool stalled = usbd_get_ep_stall(arg->device, arg->setup->wIndex); arg->buf = (void *) (stalled ? &HALTED : &NOT_HALTED); arg->len = MIN(arg->setup->wLength, 2); return USBD_REQ_HANDLED; } static enum usbd_control_result standard_request_device(struct usbd_control_arg *arg) { switch (arg->setup->bRequest) { case USB_REQ_CLEAR_FEATURE: case USB_REQ_SET_FEATURE: if (arg->setup->wValue == USB_FEATURE_DEVICE_REMOTE_WAKEUP) { /* Device wakeup code goes here. */ } if (arg->setup->wValue == USB_FEATURE_TEST_MODE) { /* Test mode code goes here. */ } break; case USB_REQ_SET_ADDRESS: return standard_set_address(arg); case USB_REQ_SET_CONFIGURATION: return standard_set_configuration(arg); case USB_REQ_GET_CONFIGURATION: return standard_get_configuration(arg); case USB_REQ_GET_DESCRIPTOR: return standard_get_descriptor(arg); case USB_REQ_GET_STATUS: /* * GET_STATUS always responds with zero reply. * The application may override this behaviour. */ return standard_device_get_status(arg); case USB_REQ_SET_DESCRIPTOR: /* SET_DESCRIPTOR is optional and not implemented. */ break; } return USBD_REQ_STALL; } static enum usbd_control_result standard_request_interface(struct usbd_control_arg *arg) { switch (arg->setup->bRequest) { case USB_REQ_CLEAR_FEATURE: case USB_REQ_SET_FEATURE: /* not defined */ break; case USB_REQ_GET_INTERFACE: return standard_get_interface(arg); case USB_REQ_SET_INTERFACE: return standard_set_interface(arg); case USB_REQ_GET_STATUS: return standard_interface_get_status(arg); } return USBD_REQ_STALL; } static enum usbd_control_result standard_request_endpoint(struct usbd_control_arg *arg) { switch (arg->setup->bRequest) { case USB_REQ_CLEAR_FEATURE: if (arg->setup->wValue == USB_FEATURE_ENDPOINT_HALT) { uint8_t ep_addr = arg->setup->wIndex; usbd_set_ep_dtog(arg->device, ep_addr, false); usbd_set_ep_stall(arg->device, ep_addr, false); return USBD_REQ_HANDLED; } break; case USB_REQ_SET_FEATURE: if (arg->setup->wValue == USB_FEATURE_ENDPOINT_HALT) { usbd_set_ep_stall(arg->device, arg->setup->wIndex, true); return USBD_REQ_HANDLED; } break; case USB_REQ_GET_STATUS: return standard_endpoint_get_status(arg); case USB_REQ_SET_SYNCH_FRAME: /* FIXME: SYNCH_FRAME is not implemented. */ /* * SYNCH_FRAME is used for synchronization of isochronous * endpoints which are not yet implemented. */ break; } return USBD_REQ_STALL; } static enum usbd_control_result standard_request(struct usbd_control_arg *arg) { switch (arg->setup->bmRequestType & USB_REQ_TYPE_RECIPIENT) { case USB_REQ_TYPE_DEVICE: return standard_request_device(arg); case USB_REQ_TYPE_INTERFACE: return standard_request_interface(arg); case USB_REQ_TYPE_ENDPOINT: return standard_request_endpoint(arg); default: return USBD_REQ_STALL; } } void usbd_ep0_setup(usbd_device *dev, const struct usb_setup_data *setup_data) { /* Only handle Standard request. */ if ((setup_data->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_STANDARD) { goto stall; } struct usbd_control_arg arg = { .device = dev, .setup = setup_data, .complete = NULL, .buf = NULL, .len = 0 }; if (standard_request(&arg) == USBD_REQ_HANDLED) { usbd_ep0_transfer(dev, setup_data, arg.buf, arg.len, arg.complete); return; } stall: usbd_ep0_stall(dev); } /* Do not appear to belong to the API, so are omitted from docs */ /**@}*/