/** * \file * * \brief USB Device Controller (UDC) * * Copyright (c) 2009-2015 Atmel Corporation. All rights reserved. * * \asf_license_start * * \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 microcontroller 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. * * \asf_license_stop * */ /* * Support and FAQ: visit Atmel Support */ #ifdef ARDUINO_ARCH_SAM #include "conf_usb.h" #include "usb_protocol.h" #include "udd.h" #include "udc_desc.h" #include "udi.h" #include "udc.h" /** * \ingroup udc_group * \defgroup udc_group_interne Implementation of UDC * * Internal implementation * @{ */ //! \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; COMPILER_WORD_ALIGNED //! Device interface setting value static uint8_t udc_iface_setting = 0; //! Device Configuration number selected by the USB host COMPILER_WORD_ALIGNED 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 Get USB device serial number * * Use the define USB_DEVICE_SERIAL_NAME to set static serial number. * * For dynamic serial number set the define USB_DEVICE_GET_SERIAL_NAME_POINTER * to a suitable pointer. This will also require the serial number length * define USB_DEVICE_GET_SERIAL_NAME_LENGTH. */ #if defined USB_DEVICE_GET_SERIAL_NAME_POINTER static const uint8_t *udc_get_string_serial_name(void) { return (const uint8_t *)USB_DEVICE_GET_SERIAL_NAME_POINTER; } # define USB_DEVICE_SERIAL_NAME_SIZE \ USB_DEVICE_GET_SERIAL_NAME_LENGTH #elif defined USB_DEVICE_SERIAL_NAME static const uint8_t *udc_get_string_serial_name(void) { return (const uint8_t *)USB_DEVICE_SERIAL_NAME; } # define USB_DEVICE_SERIAL_NAME_SIZE \ (sizeof(USB_DEVICE_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 Start the USB Device stack */ void udc_start(void) { udd_enable(); } /*! \brief Stop the USB Device stack */ void udc_stop(void) { udd_disable(); udc_reset(); } /** * \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 (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 } void udc_sof_notify(void) { uint8_t iface_num; if (udc_num_configuration) { for (iface_num = 0; iface_num < udc_ptr_conf->desc->bNumInterfaces; iface_num++) { if (udc_ptr_conf->udi_apis[iface_num]->sof_notify != NULL) { udc_ptr_conf->udi_apis[iface_num]->sof_notify(); } } } } /** * \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) { return false; } if (udd_g_ctrlreq.req.wValue == USB_DEV_FEATURE_REMOTE_WAKEUP) { udc_device_status &= CPU_TO_LE16(~(uint32_t)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) { 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) { 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 default: break; } 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_ep_set_feature(void) { if (udd_g_ctrlreq.req.wLength) { return false; } if (udd_g_ctrlreq.req.wValue == USB_EP_FEATURE_HALT) { udd_ep_abort(udd_g_ctrlreq.req.wIndex & 0xFF); 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) { 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; const uint8_t *str; uint8_t str_length = 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_length = USB_DEVICE_MANUFACTURE_NAME_SIZE; str = udc_string_manufacturer_name; break; #endif #ifdef USB_DEVICE_PRODUCT_NAME case 2: str_length = USB_DEVICE_PRODUCT_NAME_SIZE; str = udc_string_product_name; break; #endif #if defined USB_DEVICE_SERIAL_NAME || defined USB_DEVICE_GET_SERIAL_NAME_POINTER case 3: str_length = USB_DEVICE_SERIAL_NAME_SIZE; str = udc_get_string_serial_name(); break; #endif default: #ifdef UDC_GET_EXTRA_STRING if (UDC_GET_EXTRA_STRING()) { break; } #endif return false; } if (str_length) { for(i = 0; i < str_length; i++) { udc_string_desc.string[i] = cpu_to_le16((le16_t)str[i]); } udc_string_desc.header.bLength = 2 + (str_length) * 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_BOS: // Device BOS descriptor requested if (udc_config.conf_bos == NULL) { return false; } udd_set_setup_payload( (uint8_t *) udc_config.conf_bos, udc_config.conf_bos->wTotalLength); break; 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) { 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) { 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) { 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(); default: break; } } 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(); default: break; } } #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(); default: break; } } #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; default: 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(); default: break; } } #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_ep_set_feature(); default: break; } } #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 Send the SETUP interface request to UDI * * \return true if the request is supported */ static bool udc_req_ep(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 } // Send this request on all enabled interfaces iface_num = udd_g_ctrlreq.req.wIndex & 0xFF; for (iface_num = 0; iface_num < udc_ptr_conf->desc->bNumInterfaces; iface_num++) { // 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 if (udi_api->setup()) { return true; } } return false; } /** * \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; } } // If endpoint request then try to decode it in UDI if (Udd_setup_recipient() == USB_REQ_RECIP_ENDPOINT) { if (udc_req_ep()) { 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 } //! @} #endif // ARDUINO_ARCH_SAM