/** | |
* \file | |
* | |
* \brief USB Device Controller (UDC) | |
* | |
* Copyright (C) 2009 Atmel Corporation. All rights reserved. | |
* | |
* \page License | |
* | |
* 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. | |
* | |
* 3. The name of Atmel may not be used to endorse or promote products derived | |
* from this software without specific prior written permission. | |
* | |
* 4. This software may only be redistributed and used in connection with an | |
* Atmel AVR product. | |
* | |
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED | |
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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 "conf_usb.h" | |
#include "usb_protocol.h" | |
#include "udd.h" | |
#include "udc_desc.h" | |
#include "udi.h" | |
#include "udc.h" | |
/** | |
* \addtogroup udc_group | |
* @{ | |
*/ | |
//! \name Internal variables to manage the USB device | |
//! @{ | |
//! Device status state (see enum usb_device_status in usb_protocol.h) | |
static le16_t udc_device_status; | |
//! Device Configuration number selected by the USB host | |
static uint8_t udc_num_configuration = 0; | |
//! Pointer on the selected speed device configuration | |
static udc_config_speed_t UDC_DESC_STORAGE *udc_ptr_conf; | |
//! Pointer on interface descriptor used by SETUP request. | |
static usb_iface_desc_t UDC_DESC_STORAGE *udc_ptr_iface; | |
//! @} | |
//! \name Internal structure to store the USB device main strings | |
//! @{ | |
/** | |
* \brief Language ID of USB device (US ID by default) | |
*/ | |
COMPILER_WORD_ALIGNED | |
static UDC_DESC_STORAGE usb_str_lgid_desc_t udc_string_desc_languageid = { | |
.desc.bLength = sizeof(usb_str_lgid_desc_t), | |
.desc.bDescriptorType = USB_DT_STRING, | |
.string = {LE16(USB_LANGID_EN_US)} | |
}; | |
/** | |
* \brief USB device manufacture name storage | |
* String is allocated only if USB_DEVICE_MANUFACTURE_NAME is declared | |
* by usb application configuration | |
*/ | |
#ifdef USB_DEVICE_MANUFACTURE_NAME | |
static uint8_t udc_string_manufacturer_name[] = USB_DEVICE_MANUFACTURE_NAME; | |
#define USB_DEVICE_MANUFACTURE_NAME_SIZE (sizeof(udc_string_manufacturer_name)-1) | |
#else | |
#define USB_DEVICE_MANUFACTURE_NAME_SIZE 0 | |
#endif | |
/** | |
* \brief USB device product name storage | |
* String is allocated only if USB_DEVICE_PRODUCT_NAME is declared | |
* by usb application configuration | |
*/ | |
#ifdef USB_DEVICE_PRODUCT_NAME | |
static uint8_t udc_string_product_name[] = USB_DEVICE_PRODUCT_NAME; | |
#define USB_DEVICE_PRODUCT_NAME_SIZE (sizeof(udc_string_product_name)-1) | |
#else | |
#define USB_DEVICE_PRODUCT_NAME_SIZE 0 | |
#endif | |
/** | |
* \brief USB device serial number storage | |
* String is allocated only if USB_DEVICE_SERIAL_NAME is declared | |
* by usb application configuration | |
*/ | |
#ifdef USB_DEVICE_SERIAL_NAME | |
static uint8_t udc_string_serial_name[] = USB_DEVICE_SERIAL_NAME; | |
#define USB_DEVICE_SERIAL_NAME_SIZE (sizeof(udc_string_serial_name)-1) | |
#else | |
#define USB_DEVICE_SERIAL_NAME_SIZE 0 | |
#endif | |
/** | |
* \brief USB device string descriptor | |
* Structure used to transfer ASCII strings to USB String descriptor structure. | |
*/ | |
struct udc_string_desc_t { | |
usb_str_desc_t header; | |
le16_t string[Max(Max(USB_DEVICE_MANUFACTURE_NAME_SIZE, \ | |
USB_DEVICE_PRODUCT_NAME_SIZE), USB_DEVICE_SERIAL_NAME_SIZE)]; | |
}; | |
COMPILER_WORD_ALIGNED | |
static UDC_DESC_STORAGE struct udc_string_desc_t udc_string_desc = { | |
.header.bDescriptorType = USB_DT_STRING | |
}; | |
//! @} | |
usb_iface_desc_t UDC_DESC_STORAGE *udc_get_interface_desc(void) | |
{ | |
return udc_ptr_iface; | |
} | |
/** | |
* \brief Returns a value to check the end of USB Configuration descriptor | |
* | |
* \return address after the last byte of USB Configuration descriptor | |
*/ | |
static usb_conf_desc_t UDC_DESC_STORAGE *udc_get_eof_conf(void) | |
{ | |
return (UDC_DESC_STORAGE usb_conf_desc_t *) ((uint8_t *) | |
udc_ptr_conf->desc + | |
le16_to_cpu(udc_ptr_conf->desc->wTotalLength)); | |
} | |
#if (0!=USB_DEVICE_MAX_EP) | |
/** | |
* \brief Search specific descriptor in global interface descriptor | |
* | |
* \param desc Address of interface descriptor | |
* or previous specific descriptor found | |
* \param desc_id Descriptor ID to search | |
* | |
* \return address of specific descriptor found | |
* \return NULL if it is the end of global interface descriptor | |
*/ | |
static usb_conf_desc_t UDC_DESC_STORAGE *udc_next_desc_in_iface(usb_conf_desc_t | |
UDC_DESC_STORAGE * desc, uint8_t desc_id) | |
{ | |
usb_conf_desc_t UDC_DESC_STORAGE *ptr_eof_desc; | |
ptr_eof_desc = udc_get_eof_conf(); | |
// Go to next descriptor | |
desc = (UDC_DESC_STORAGE usb_conf_desc_t *) ((uint8_t *) desc + | |
desc->bLength); | |
// Check the end of configuration descriptor | |
while (ptr_eof_desc > desc) { | |
// If new interface descriptor is found, | |
// then it is the end of the current global interface descriptor | |
if (USB_DT_INTERFACE == desc->bDescriptorType) | |
break; // End of global interface descriptor | |
if (desc_id == desc->bDescriptorType) | |
return desc; // Specific descriptor found | |
// Go to next descriptor | |
desc = (UDC_DESC_STORAGE usb_conf_desc_t *) ((uint8_t *) desc + | |
desc->bLength); | |
} | |
return NULL; // No specific descriptor found | |
} | |
#endif | |
/** | |
* \brief Search an interface descriptor | |
* This routine updates the internal pointer udc_ptr_iface. | |
* | |
* \param iface_num Interface number to find in Configuration Descriptor | |
* \param setting_num Setting number of interface to find | |
* | |
* \return 1 if found or 0 if not found | |
*/ | |
static bool udc_update_iface_desc(uint8_t iface_num, uint8_t setting_num) | |
{ | |
usb_conf_desc_t UDC_DESC_STORAGE *ptr_end_desc; | |
if (0 == udc_num_configuration) | |
return false; | |
if (iface_num >= udc_ptr_conf->desc->bNumInterfaces) | |
return false; | |
// Start at the beginning of configuration descriptor | |
udc_ptr_iface = (UDC_DESC_STORAGE usb_iface_desc_t *) | |
udc_ptr_conf->desc; | |
// Check the end of configuration descriptor | |
ptr_end_desc = udc_get_eof_conf(); | |
while (ptr_end_desc > | |
(UDC_DESC_STORAGE usb_conf_desc_t *) udc_ptr_iface) { | |
if (USB_DT_INTERFACE == udc_ptr_iface->bDescriptorType) { | |
// A interface descriptor is found | |
// Check interface and alternate setting number | |
if ((iface_num == udc_ptr_iface->bInterfaceNumber) | |
&& (setting_num == | |
udc_ptr_iface-> | |
bAlternateSetting)) | |
return true; // Interface found | |
} | |
// Go to next descriptor | |
udc_ptr_iface = (UDC_DESC_STORAGE usb_iface_desc_t *) ((uint8_t | |
*) udc_ptr_iface + | |
udc_ptr_iface->bLength); | |
} | |
return false; // Interface not found | |
} | |
/** | |
* \brief Disables an usb device interface (UDI) | |
* This routine call the UDI corresponding to interface number | |
* | |
* \param iface_num Interface number to disable | |
* | |
* \return 1 if it is done or 0 if interface is not found | |
*/ | |
static bool udc_iface_disable(uint8_t iface_num) | |
{ | |
udi_api_t UDC_DESC_STORAGE *udi_api; | |
// Select first alternate setting of the interface to update udc_ptr_iface | |
// before call iface->getsetting() | |
if (!udc_update_iface_desc(iface_num, 0)) | |
return false; | |
// Select the interface with the current alternate setting | |
udi_api = udc_ptr_conf->udi_apis[iface_num]; | |
#if (0!=USB_DEVICE_MAX_EP) | |
if (!udc_update_iface_desc(iface_num, udi_api->getsetting())) | |
return false; | |
// Start at the beginning of interface descriptor | |
{ | |
usb_ep_desc_t UDC_DESC_STORAGE *ep_desc; | |
ep_desc = (UDC_DESC_STORAGE usb_ep_desc_t *) udc_ptr_iface; | |
while (1) { | |
// Search Endpoint descriptor included in global interface descriptor | |
ep_desc = (UDC_DESC_STORAGE usb_ep_desc_t *) | |
udc_next_desc_in_iface((UDC_DESC_STORAGE | |
usb_conf_desc_t *) | |
ep_desc, USB_DT_ENDPOINT); | |
if (NULL == ep_desc) | |
break; | |
// Free the endpoint used by the interface | |
udd_ep_free(ep_desc->bEndpointAddress); | |
} | |
} | |
#endif | |
// Disable interface | |
udi_api->disable(); | |
return true; | |
} | |
/** | |
* \brief Enables an usb device interface (UDI) | |
* This routine calls the UDI corresponding | |
* to the interface and setting number. | |
* | |
* \param iface_num Interface number to enable | |
* \param setting_num Setting number to enable | |
* | |
* \return 1 if it is done or 0 if interface is not found | |
*/ | |
static bool udc_iface_enable(uint8_t iface_num, uint8_t setting_num) | |
{ | |
// Select the interface descriptor | |
if (!udc_update_iface_desc(iface_num, setting_num)) | |
return false; | |
#if (0!=USB_DEVICE_MAX_EP) | |
usb_ep_desc_t UDC_DESC_STORAGE *ep_desc; | |
// Start at the beginning of the global interface descriptor | |
ep_desc = (UDC_DESC_STORAGE usb_ep_desc_t *) udc_ptr_iface; | |
while (1) { | |
// Search Endpoint descriptor included in the global interface descriptor | |
ep_desc = (UDC_DESC_STORAGE usb_ep_desc_t *) | |
udc_next_desc_in_iface((UDC_DESC_STORAGE | |
usb_conf_desc_t *) ep_desc, | |
USB_DT_ENDPOINT); | |
if (NULL == ep_desc) | |
break; | |
// Alloc the endpoint used by the interface | |
if (!udd_ep_alloc(ep_desc->bEndpointAddress, | |
ep_desc->bmAttributes, | |
le16_to_cpu | |
(ep_desc->wMaxPacketSize))) | |
return false; | |
} | |
#endif | |
// Enable the interface | |
return udc_ptr_conf->udi_apis[iface_num]->enable(); | |
} | |
/** | |
* \brief Reset the current configuration of the USB device, | |
* This routines can be called by UDD when a RESET on the USB line occurs. | |
*/ | |
void udc_reset(void) | |
{ | |
uint8_t iface_num; | |
if (udc_num_configuration) { | |
for (iface_num = 0; | |
iface_num < udc_ptr_conf->desc->bNumInterfaces; | |
iface_num++) { | |
udc_iface_disable(iface_num); | |
} | |
} | |
udc_num_configuration = 0; | |
#if (USB_CONFIG_ATTR_REMOTE_WAKEUP \ | |
== (USB_DEVICE_ATTR & USB_CONFIG_ATTR_REMOTE_WAKEUP)) | |
if (0 != (CPU_TO_LE16(USB_DEV_STATUS_REMOTEWAKEUP) & udc_device_status)) { | |
// Remote wakeup is enabled then disable it | |
UDC_REMOTEWAKEUP_DISABLE(); | |
} | |
#endif | |
udc_device_status = | |
#if (USB_DEVICE_ATTR & USB_CONFIG_ATTR_SELF_POWERED) | |
CPU_TO_LE16(USB_DEV_STATUS_SELF_POWERED); | |
#else | |
CPU_TO_LE16(USB_DEV_STATUS_BUS_POWERED); | |
#endif | |
} | |
/** | |
* \brief Standard device request to get device status | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_get_status(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != sizeof(udc_device_status)) | |
return false; | |
udd_set_setup_payload( | |
(uint8_t *) & udc_device_status, | |
sizeof(udc_device_status)); | |
return true; | |
} | |
#if (0!=USB_DEVICE_MAX_EP) | |
/** | |
* \brief Standard endpoint request to get endpoint status | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_ep_get_status(void) | |
{ | |
static le16_t udc_ep_status; | |
if (udd_g_ctrlreq.req.wLength != sizeof(udc_ep_status)) | |
return false; | |
udc_ep_status = udd_ep_is_halted(udd_g_ctrlreq.req. | |
wIndex & 0xFF) ? CPU_TO_LE16(USB_EP_STATUS_HALTED) : 0; | |
udd_set_setup_payload( | |
(uint8_t *) & udc_ep_status, | |
sizeof(udc_ep_status)); | |
return true; | |
} | |
#endif | |
/** | |
* \brief Standard device request to change device status | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_clear_feature(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
if (udd_g_ctrlreq.req.wValue == USB_DEV_FEATURE_REMOTE_WAKEUP) { | |
udc_device_status &= CPU_TO_LE16(~USB_DEV_STATUS_REMOTEWAKEUP); | |
#if (USB_CONFIG_ATTR_REMOTE_WAKEUP \ | |
== (USB_DEVICE_ATTR & USB_CONFIG_ATTR_REMOTE_WAKEUP)) | |
UDC_REMOTEWAKEUP_DISABLE(); | |
#endif | |
return true; | |
} | |
return false; | |
} | |
#if (0!=USB_DEVICE_MAX_EP) | |
/** | |
* \brief Standard endpoint request to clear endpoint feature | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_ep_clear_feature(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
if (udd_g_ctrlreq.req.wValue == USB_EP_FEATURE_HALT) { | |
return udd_ep_clear_halt(udd_g_ctrlreq.req.wIndex & 0xFF); | |
} | |
return false; | |
} | |
#endif | |
/** | |
* \brief Standard device request to set a feature | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_set_feature(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
switch (udd_g_ctrlreq.req.wValue) { | |
case USB_DEV_FEATURE_REMOTE_WAKEUP: | |
#if (USB_CONFIG_ATTR_REMOTE_WAKEUP \ | |
== (USB_DEVICE_ATTR & USB_CONFIG_ATTR_REMOTE_WAKEUP)) | |
udc_device_status |= CPU_TO_LE16(USB_DEV_STATUS_REMOTEWAKEUP); | |
UDC_REMOTEWAKEUP_ENABLE(); | |
return true; | |
#else | |
return false; | |
#endif | |
#ifdef USB_DEVICE_HS_SUPPORT | |
case USB_DEV_FEATURE_TEST_MODE: | |
if (!udd_is_high_speed()) | |
break; | |
if (udd_g_ctrlreq.req.wIndex & 0xff) | |
break; | |
// Unconfigure the device, terminating all ongoing requests | |
udc_reset(); | |
switch ((udd_g_ctrlreq.req.wIndex >> 8) & 0xFF) { | |
case USB_DEV_TEST_MODE_J: | |
udd_g_ctrlreq.callback = udd_test_mode_j; | |
return true; | |
case USB_DEV_TEST_MODE_K: | |
udd_g_ctrlreq.callback = udd_test_mode_k; | |
return true; | |
case USB_DEV_TEST_MODE_SE0_NAK: | |
udd_g_ctrlreq.callback = udd_test_mode_se0_nak; | |
return true; | |
case USB_DEV_TEST_MODE_PACKET: | |
udd_g_ctrlreq.callback = udd_test_mode_packet; | |
return true; | |
case USB_DEV_TEST_MODE_FORCE_ENABLE: // Only for downstream facing hub ports | |
default: | |
break; | |
} | |
break; | |
#endif | |
#ifdef USB_OTG | |
// TODO | |
case USB_DEV_FEATURE_OTG_B_HNP_ENABLE: | |
break; | |
case USB_DEV_FEATURE_OTG_A_HNP_SUPPORT: | |
break; | |
case USB_DEV_FEATURE_OTG_A_ALT_HNP_SUPPORT: | |
break; | |
#endif | |
} | |
return false; | |
} | |
/** | |
* \brief Standard endpoint request to halt an endpoint | |
* | |
* \return true if success | |
*/ | |
#if (0!=USB_DEVICE_MAX_EP) | |
static bool udc_req_std_epset_feature(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
if (udd_g_ctrlreq.req.wValue == USB_EP_FEATURE_HALT) { | |
return udd_ep_set_halt(udd_g_ctrlreq.req.wIndex & 0xFF); | |
} | |
return false; | |
} | |
#endif | |
/** | |
* \brief Change the address of device | |
* Callback called at the end of request set address | |
*/ | |
static void udc_valid_address(void) | |
{ | |
udd_set_address(udd_g_ctrlreq.req.wValue & 0x7F); | |
} | |
/** | |
* \brief Standard device request to set device address | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_set_address(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
// The address must be changed at the end of setup request after the handshake | |
// then we use a callback to change address | |
udd_g_ctrlreq.callback = udc_valid_address; | |
return true; | |
} | |
/** | |
* \brief Standard device request to get device string descriptor | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_get_str_desc(void) | |
{ | |
uint8_t i; | |
uint8_t *str; | |
uint8_t str_lgt=0; | |
// Link payload pointer to the string corresponding at request | |
switch (udd_g_ctrlreq.req.wValue & 0xff) { | |
case 0: | |
udd_set_setup_payload( | |
(uint8_t *) & udc_string_desc_languageid, | |
sizeof(udc_string_desc_languageid)); | |
break; | |
#ifdef USB_DEVICE_MANUFACTURE_NAME | |
case 1: | |
str_lgt = USB_DEVICE_MANUFACTURE_NAME_SIZE; | |
str = udc_string_manufacturer_name; | |
break; | |
#endif | |
#ifdef USB_DEVICE_PRODUCT_NAME | |
case 2: | |
str_lgt = USB_DEVICE_PRODUCT_NAME_SIZE; | |
str = udc_string_product_name; | |
break; | |
#endif | |
#ifdef USB_DEVICE_SERIAL_NAME | |
case 3: | |
str_lgt = USB_DEVICE_SERIAL_NAME_SIZE; | |
str = udc_string_serial_name; | |
break; | |
#endif | |
default: | |
#ifdef UDC_GET_EXTRA_STRING | |
if (UDC_GET_EXTRA_STRING()) | |
break; | |
#endif | |
return false; | |
} | |
if (str_lgt != 0) { | |
for(i = 0; i < str_lgt; i++) { | |
udc_string_desc.string[i] = cpu_to_le16((le16_t)str[i]); | |
} | |
udc_string_desc.header.bLength = 2 + (str_lgt) * 2; | |
udd_set_setup_payload( | |
(uint8_t *) &udc_string_desc, | |
udc_string_desc.header.bLength); | |
} | |
return true; | |
} | |
/** | |
* \brief Standard device request to get descriptors about USB device | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_get_descriptor(void) | |
{ | |
uint8_t conf_num; | |
conf_num = udd_g_ctrlreq.req.wValue & 0xff; | |
// Check descriptor ID | |
switch ((uint8_t) (udd_g_ctrlreq.req.wValue >> 8)) { | |
case USB_DT_DEVICE: | |
// Device descriptor requested | |
#ifdef USB_DEVICE_HS_SUPPORT | |
if (!udd_is_high_speed()) { | |
udd_set_setup_payload( | |
(uint8_t *) udc_config.confdev_hs, | |
udc_config.confdev_hs->bLength); | |
} else | |
#endif | |
{ | |
udd_set_setup_payload( | |
(uint8_t *) udc_config.confdev_lsfs, | |
udc_config.confdev_lsfs->bLength); | |
} | |
break; | |
case USB_DT_CONFIGURATION: | |
// Configuration descriptor requested | |
#ifdef USB_DEVICE_HS_SUPPORT | |
if (udd_is_high_speed()) { | |
// HS descriptor | |
if (conf_num >= udc_config.confdev_hs-> | |
bNumConfigurations) | |
return false; | |
udd_set_setup_payload( | |
(uint8_t *)udc_config.conf_hs[conf_num].desc, | |
le16_to_cpu(udc_config.conf_hs[conf_num].desc->wTotalLength)); | |
} else | |
#endif | |
{ | |
// FS descriptor | |
if (conf_num >= udc_config.confdev_lsfs-> | |
bNumConfigurations) | |
return false; | |
udd_set_setup_payload( | |
(uint8_t *)udc_config.conf_lsfs[conf_num].desc, | |
le16_to_cpu(udc_config.conf_lsfs[conf_num].desc->wTotalLength)); | |
} | |
((usb_conf_desc_t *) udd_g_ctrlreq.payload)->bDescriptorType = | |
USB_DT_CONFIGURATION; | |
break; | |
#ifdef USB_DEVICE_HS_SUPPORT | |
case USB_DT_DEVICE_QUALIFIER: | |
// Device qualifier descriptor requested | |
udd_set_setup_payload( | |
(uint8_t *) udc_config.qualifier, | |
udc_config.qualifier->bLength); | |
break; | |
case USB_DT_OTHER_SPEED_CONFIGURATION: | |
// Other configuration descriptor requested | |
if (!udd_is_high_speed()) { | |
// HS descriptor | |
if (conf_num >= udc_config.confdev_hs-> | |
bNumConfigurations) | |
return false; | |
udd_set_setup_payload( | |
(uint8_t *)udc_config.conf_hs[conf_num].desc, | |
le16_to_cpu(udc_config.conf_hs[conf_num].desc->wTotalLength)); | |
} else { | |
// FS descriptor | |
if (conf_num >= udc_config.confdev_lsfs-> | |
bNumConfigurations) | |
return false; | |
udd_set_setup_payload( | |
(uint8_t *)udc_config.conf_lsfs[conf_num].desc, | |
le16_to_cpu(udc_config.conf_lsfs[conf_num].desc->wTotalLength)); | |
} | |
((usb_conf_desc_t *) udd_g_ctrlreq.payload)->bDescriptorType = | |
USB_DT_OTHER_SPEED_CONFIGURATION; | |
break; | |
#endif | |
case USB_DT_STRING: | |
// String descriptor requested | |
if (!udc_req_std_dev_get_str_desc()) { | |
return false; | |
} | |
break; | |
default: | |
// Unknown descriptor requested | |
return false; | |
} | |
// if the descriptor is larger than length requested, then reduce it | |
if (udd_g_ctrlreq.req.wLength < udd_g_ctrlreq.payload_size) | |
udd_g_ctrlreq.payload_size = udd_g_ctrlreq.req.wLength; | |
return true; | |
} | |
/** | |
* \brief Standard device request to get configuration number | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_get_configuration(void) | |
{ | |
if (udd_g_ctrlreq.req.wLength != 1) | |
return false; | |
udd_set_setup_payload(&udc_num_configuration,1); | |
return true; | |
} | |
/** | |
* \brief Standard device request to enable a configuration | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_dev_set_configuration(void) | |
{ | |
uint8_t iface_num; | |
// Check request length | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
// Authorize configuration only if the address is valid | |
if (!udd_getaddress()) | |
return false; | |
// Check the configuration number requested | |
#ifdef USB_DEVICE_HS_SUPPORT | |
if (udd_is_high_speed()) { | |
// HS descriptor | |
if ((udd_g_ctrlreq.req.wValue & 0xFF) > | |
udc_config.confdev_hs->bNumConfigurations) | |
return false; | |
} else | |
#endif | |
{ | |
// FS descriptor | |
if ((udd_g_ctrlreq.req.wValue & 0xFF) > | |
udc_config.confdev_lsfs->bNumConfigurations) | |
return false; | |
} | |
// Reset current configuration | |
udc_reset(); | |
// Enable new configuration | |
udc_num_configuration = udd_g_ctrlreq.req.wValue & 0xFF; | |
if (udc_num_configuration == 0) { | |
return true; // Default empty configuration requested | |
} | |
// Update pointer of the configuration descriptor | |
#ifdef USB_DEVICE_HS_SUPPORT | |
if (udd_is_high_speed()) { | |
// HS descriptor | |
udc_ptr_conf = &udc_config.conf_hs[udc_num_configuration - 1]; | |
} else | |
#endif | |
{ | |
// FS descriptor | |
udc_ptr_conf = &udc_config.conf_lsfs[udc_num_configuration - 1]; | |
} | |
// Enable all interfaces of the selected configuration | |
for (iface_num = 0; iface_num < udc_ptr_conf->desc->bNumInterfaces; | |
iface_num++) { | |
if (!udc_iface_enable(iface_num, 0)) | |
return false; | |
} | |
return true; | |
} | |
/** | |
* \brief Standard interface request | |
* to get the alternate setting number of an interface | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_iface_get_setting(void) | |
{ | |
static uint8_t udc_iface_setting; | |
uint8_t iface_num; | |
udi_api_t UDC_DESC_STORAGE *udi_api; | |
if (udd_g_ctrlreq.req.wLength != 1) | |
return false; // Error in request | |
if (!udc_num_configuration) | |
return false; // The device is not is configured state yet | |
// Check the interface number included in the request | |
iface_num = udd_g_ctrlreq.req.wIndex & 0xFF; | |
if (iface_num >= udc_ptr_conf->desc->bNumInterfaces) | |
return false; | |
// Select first alternate setting of the interface to update udc_ptr_iface | |
// before call iface->getsetting() | |
if (!udc_update_iface_desc(iface_num, 0)) | |
return false; | |
// Get alternate setting from UDI | |
udi_api = udc_ptr_conf->udi_apis[iface_num]; | |
udc_iface_setting = udi_api->getsetting(); | |
// Link value to payload pointer of request | |
udd_set_setup_payload(&udc_iface_setting,1); | |
return true; | |
} | |
/** | |
* \brief Standard interface request | |
* to set an alternate setting of an interface | |
* | |
* \return true if success | |
*/ | |
static bool udc_req_std_iface_set_setting(void) | |
{ | |
uint8_t iface_num, setting_num; | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; // Error in request | |
if (!udc_num_configuration) | |
return false; // The device is not is configured state yet | |
iface_num = udd_g_ctrlreq.req.wIndex & 0xFF; | |
setting_num = udd_g_ctrlreq.req.wValue & 0xFF; | |
// Disable current setting | |
if (!udc_iface_disable(iface_num)) | |
return false; | |
// Enable new setting | |
return udc_iface_enable(iface_num, setting_num); | |
} | |
/** | |
* \brief Main routine to manage the standard USB SETUP request | |
* | |
* \return true if the request is supported | |
*/ | |
static bool udc_reqstd(void) | |
{ | |
if (Udd_setup_is_in()) { | |
// GET Standard Requests | |
if (udd_g_ctrlreq.req.wLength == 0) | |
return false; // Error for USB host | |
if (USB_REQ_RECIP_DEVICE == Udd_setup_recipient()) { | |
// Standard Get Device request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_GET_STATUS: | |
return udc_req_std_dev_get_status(); | |
case USB_REQ_GET_DESCRIPTOR: | |
return udc_req_std_dev_get_descriptor(); | |
case USB_REQ_GET_CONFIGURATION: | |
return udc_req_std_dev_get_configuration(); | |
} | |
} | |
if (USB_REQ_RECIP_INTERFACE == Udd_setup_recipient()) { | |
// Standard Get Interface request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_GET_INTERFACE: | |
return udc_req_std_iface_get_setting(); | |
} | |
} | |
#if (0!=USB_DEVICE_MAX_EP) | |
if (USB_REQ_RECIP_ENDPOINT == Udd_setup_recipient()) { | |
// Standard Get Endpoint request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_GET_STATUS: | |
return udc_req_std_ep_get_status(); | |
} | |
} | |
#endif | |
} else { | |
// SET Standard Requests | |
if (USB_REQ_RECIP_DEVICE == Udd_setup_recipient()) { | |
// Standard Set Device request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_SET_ADDRESS: | |
return udc_req_std_dev_set_address(); | |
case USB_REQ_CLEAR_FEATURE: | |
return udc_req_std_dev_clear_feature(); | |
case USB_REQ_SET_FEATURE: | |
return udc_req_std_dev_set_feature(); | |
case USB_REQ_SET_CONFIGURATION: | |
return udc_req_std_dev_set_configuration(); | |
case USB_REQ_SET_DESCRIPTOR: | |
/* Not supported (defined as optional by the USB 2.0 spec) */ | |
break; | |
} | |
} | |
if (USB_REQ_RECIP_INTERFACE == Udd_setup_recipient()) { | |
// Standard Set Interface request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_SET_INTERFACE: | |
return udc_req_std_iface_set_setting(); | |
} | |
} | |
#if (0!=USB_DEVICE_MAX_EP) | |
if (USB_REQ_RECIP_ENDPOINT == Udd_setup_recipient()) { | |
// Standard Set Endpoint request | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_CLEAR_FEATURE: | |
return udc_req_std_ep_clear_feature(); | |
case USB_REQ_SET_FEATURE: | |
return udc_req_std_epset_feature(); | |
} | |
} | |
#endif | |
} | |
return false; | |
} | |
/** | |
* \brief Send the SETUP interface request to UDI | |
* | |
* \return true if the request is supported | |
*/ | |
static bool udc_req_iface(void) | |
{ | |
uint8_t iface_num; | |
udi_api_t UDC_DESC_STORAGE *udi_api; | |
if (0 == udc_num_configuration) | |
return false; // The device is not is configured state yet | |
// Check interface number | |
iface_num = udd_g_ctrlreq.req.wIndex & 0xFF; | |
if (iface_num >= udc_ptr_conf->desc->bNumInterfaces) | |
return false; | |
//* To update udc_ptr_iface with the selected interface in request | |
// Select first alternate setting of interface to update udc_ptr_iface | |
// before calling udi_api->getsetting() | |
if (!udc_update_iface_desc(iface_num, 0)) | |
return false; | |
// Select the interface with the current alternate setting | |
udi_api = udc_ptr_conf->udi_apis[iface_num]; | |
if (!udc_update_iface_desc(iface_num, udi_api->getsetting())) | |
return false; | |
// Send the SETUP request to the UDI corresponding to the interface number | |
return udi_api->setup(); | |
} | |
/** | |
* \brief Main routine to manage the USB SETUP request. | |
* | |
* This function parses a USB SETUP request and submits an appropriate | |
* response back to the host or, in the case of SETUP OUT requests | |
* with data, sets up a buffer for receiving the data payload. | |
* | |
* The main standard requests defined by the USB 2.0 standard are handled | |
* internally. The interface requests are sent to UDI, and the specific request | |
* sent to a specific application callback. | |
* | |
* \return true if the request is supported, else the request is stalled by UDD | |
*/ | |
bool udc_process_setup(void) | |
{ | |
// By default no data (receive/send) and no callbacks registered | |
udd_g_ctrlreq.payload_size = 0; | |
udd_g_ctrlreq.callback = NULL; | |
udd_g_ctrlreq.over_under_run = NULL; | |
if (Udd_setup_is_in()) { | |
if (udd_g_ctrlreq.req.wLength == 0) | |
return false; // Error from USB host | |
} | |
// If standard request then try to decode it in UDC | |
if (Udd_setup_type() == USB_REQ_TYPE_STANDARD) { | |
if (udc_reqstd()) | |
return true; | |
} | |
// If interface request then try to decode it in UDI | |
if (Udd_setup_recipient() == USB_REQ_RECIP_INTERFACE) { | |
if (udc_req_iface()) | |
return true; | |
} | |
// Here SETUP request unknown by UDC and UDIs | |
#ifdef USB_DEVICE_SPECIFIC_REQUEST | |
// Try to decode it in specific callback | |
return USB_DEVICE_SPECIFIC_REQUEST(); // Ex: Vendor request,... | |
#else | |
return false; | |
#endif | |
} | |
//! @} |