gadget-kspconsole/unicore-mx/lib/usbd/usbd_ep0.c
Daniele Lacamera b8a7ffcd31 Initial import
2023-11-27 15:16:45 +01:00

643 lines
16 KiB
C

/**
* @defgroup usbd_control_file Generic USB Control Requests
*
* @ingroup USBD
*
* @brief <b>Generic USB Control Requests</b>
*
* @author @htmlonly &copy; @endhtmlonly 2016
* Kuldeep Singh Dhaka <kuldeepdhaka9@gmail.com>
*
* @author @htmlonly &copy; @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 */
/**@}*/