643 lines
16 KiB
C
643 lines
16 KiB
C
/**
|
|
* @defgroup usbd_control_file Generic USB Control Requests
|
|
*
|
|
* @ingroup USBD
|
|
*
|
|
* @brief <b>Generic USB Control Requests</b>
|
|
*
|
|
* @author @htmlonly © @endhtmlonly 2016
|
|
* Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
|
|
*
|
|
* @author @htmlonly © @endhtmlonly 2010
|
|
* Gareth McMullin <gareth@blacksphere.co.nz>
|
|
*
|
|
* @date 11 September 2016
|
|
*
|
|
* LGPL License Terms @ref lgpl_license
|
|
*/
|
|
|
|
/*
|
|
* This file is part of the unicore-mx project.
|
|
*
|
|
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
|
|
* Copyright (C) 2015-2017 Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**@{*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unicore-mx/usbd/usbd.h>
|
|
#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 */
|
|
/**@}*/
|
|
|