| /** |
| * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA |
| * |
| * All rights reserved. |
| * |
| * 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, except as embedded into a Nordic |
| * Semiconductor ASA integrated circuit in a product or a software update for |
| * such product, 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. Neither the name of Nordic Semiconductor ASA nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * 4. This software, with or without modification, must only be used with a |
| * Nordic Semiconductor ASA integrated circuit. |
| * |
| * 5. Any software provided in binary form under this license must not be reverse |
| * engineered, decompiled, modified and/or disassembled. |
| * |
| * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS |
| * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS 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 "sdk_common.h" |
| #if NRF_MODULE_ENABLED(APP_USBD) |
| |
| #include "app_usbd_core.h" |
| #include "app_usbd.h" |
| #include "app_usbd_request.h" |
| #include "app_usbd_string_desc.h" |
| #include "nrf.h" |
| #include "nrf_atomic.h" |
| #include "app_util_platform.h" |
| #include "app_usbd.h" |
| #include "app_usbd_class_base.h" |
| |
| #define NRF_LOG_MODULE_NAME app_usbd_core |
| |
| #if APP_USBD_CONFIG_LOG_ENABLED |
| #define NRF_LOG_LEVEL APP_USBD_CONFIG_LOG_LEVEL |
| #define NRF_LOG_INFO_COLOR APP_USBD_CONFIG_INFO_COLOR |
| #define NRF_LOG_DEBUG_COLOR APP_USBD_CONFIG_DEBUG_COLOR |
| #else //APP_USBD_CONFIG_LOG_ENABLED |
| #define NRF_LOG_LEVEL 0 |
| #endif //APP_USBD_CONFIG_LOG_ENABLED |
| #include "nrf_log.h" |
| NRF_LOG_MODULE_REGISTER(); |
| |
| /* Test if VID was configured */ |
| #ifndef APP_USBD_VID |
| #error APP_USBD_VID not properly defined. |
| #endif |
| |
| |
| /* Device version checking */ |
| #if defined(APP_USBD_DEVICE_VER_MAJOR) && defined(APP_USBD_DEVICE_VER_MINOR) |
| #if ((APP_USBD_DEVICE_VER_MAJOR)) > 99 || ((APP_USBD_DEVICE_VER_MINOR) > 99) |
| #error Major and minor device version value have to be limited to 99. |
| #endif |
| #else |
| #error The definition of a pair APP_USBD_DEVICE_VER_MAJOR and APP_USBD_DEVICE_VER_MINOR required. |
| #endif |
| |
| /** |
| * @internal |
| * @defgroup app_usbd_core_internals USB Device high level library core module internals |
| * @ingroup app_usbd_core |
| * |
| * Internal variables, auxiliary macros and functions of USBD high level core module. |
| * @{ |
| */ |
| |
| /** @brief Make USB power value */ |
| #define APP_USBD_POWER_MAKE(ma) (((ma) + 1) / 2) |
| |
| /** |
| @brief Default device descriptor initializer @ref app_usbd_descriptor_device_t |
| * */ |
| #define APP_USBD_CORE_DEVICE_DESCRIPTOR { \ |
| .bLength = sizeof(app_usbd_descriptor_device_t), /* descriptor size */ \ |
| .bDescriptorType = APP_USBD_DESCRIPTOR_DEVICE, /* descriptor type */ \ |
| .bcdUSB = APP_USBD_BCD_VER_MAKE(2,0,0), /* USB BCD version: 2.0 */ \ |
| .bDeviceClass = 0, /* device class: 0 - specified by interface */ \ |
| .bDeviceSubClass = 0, /* device subclass: 0 - specified by interface */ \ |
| .bDeviceProtocol = 0, /* device protocol: 0 - specified by interface */ \ |
| .bMaxPacketSize0 = NRF_DRV_USBD_EPSIZE, /* endpoint size: fixed to: NRF_DRV_USBD_EPSIZE*/ \ |
| .idVendor = APP_USBD_VID, /* Vendor ID*/ \ |
| .idProduct = APP_USBD_PID, /* Product ID*/ \ |
| .bcdDevice = APP_USBD_BCD_VER_MAKE( /* Device version BCD */ \ |
| APP_USBD_DEVICE_VER_MAJOR, \ |
| APP_USBD_DEVICE_VER_MINOR, \ |
| APP_USBD_DEVICE_VER_SUB), \ |
| .iManufacturer = APP_USBD_STRING_ID_MANUFACTURER, /* String ID: manufacturer */ \ |
| .iProduct = APP_USBD_STRING_ID_PRODUCT, /* String ID: product */ \ |
| .iSerialNumber = APP_USBD_STRING_ID_SERIAL, /* String ID: serial */ \ |
| .bNumConfigurations = 1 /* Fixed value: only one configuration supported*/\ |
| } |
| |
| |
| #define APP_USBD_CORE_CONFIGURATION_DESCRIPTOR { \ |
| .bLength = sizeof(app_usbd_descriptor_configuration_t), \ |
| .bDescriptorType = APP_USBD_DESCRIPTOR_CONFIGURATION, \ |
| .wTotalLength = 0, /*Calculated dynamically*/ \ |
| .bNumInterfaces = 0, /*Calculated dynamically*/ \ |
| .bConfigurationValue = 1, /*Value passed to set configuration*/ \ |
| .iConfiguration = APP_USBD_STRING_ID_CONFIGURATION, /*Configuration ID*/ \ |
| .bmAttributes = APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_ALWAYS_SET_MASK | \ |
| ((APP_USBD_CONFIG_SELF_POWERED) ? \ |
| APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_SELF_POWERED_MASK \ |
| : \ |
| 0), \ |
| .bMaxPower = APP_USBD_POWER_MAKE(APP_USBD_CONFIG_MAX_POWER), \ |
| } |
| |
| /** |
| * @brief Device descriptor instance. |
| * |
| * @note |
| * Constant part of the device descriptor. |
| * Values that must be calculated are updated directly in the buffer |
| * just before the transmission. |
| */ |
| static const app_usbd_descriptor_device_t m_device_dsc = |
| APP_USBD_CORE_DEVICE_DESCRIPTOR; |
| |
| /** |
| * @brief Configuration descriptor instance. |
| * |
| * @note |
| * Constant part of the device descriptor. |
| * Values that must be calculated are updated directly in the buffer |
| * just before the transmission. |
| */ |
| static const app_usbd_descriptor_configuration_t m_configuration_dsc = |
| APP_USBD_CORE_CONFIGURATION_DESCRIPTOR; |
| |
| /* Required early declaration of event handler function */ |
| static ret_code_t app_usbd_core_event_handler(app_usbd_class_inst_t const * const p_inst, |
| app_usbd_complex_evt_t const * const p_event); |
| |
| /** |
| * @brief Current USB device state. |
| * |
| * This variable is updated automatically by core library. |
| */ |
| static app_usbd_state_t m_app_usbd_state = APP_USBD_STATE_Disabled; |
| |
| /** |
| * @brief Active device features. |
| * |
| * @note Only @ref APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP is supported for device. |
| */ |
| static uint8_t m_device_features_state; |
| |
| /** |
| * @brief Remote wake-up pending flag. |
| */ |
| static nrf_atomic_flag_t m_rwu_pending; |
| |
| |
| /** |
| * @brief Core class methods. |
| * |
| * Base methods interface for core class. |
| * This is quite specific class - it would be only connected into endpoint 0. |
| * Not connected into instances list. |
| */ |
| static const app_usbd_class_methods_t m_core_methods = { |
| .event_handler = app_usbd_core_event_handler, |
| .feed_descriptors = NULL, |
| }; |
| |
| /** |
| * @brief Setup transfer buffer. |
| */ |
| static uint8_t m_setup_transfer_buff[NRF_DRV_USBD_EPSIZE]; |
| |
| |
| /** |
| * @brief Handler for outgoing setup data. |
| * |
| */ |
| static app_usbd_core_setup_data_handler_desc_t m_ep0_handler_desc; |
| |
| #define APP_USBD_CORE_CLASS_INSTANCE_CONFIG () |
| |
| |
| /*lint -u -save -e26 -e40 -e64 -e123 -e505 -e651*/ |
| |
| /** |
| * @brief Core instance. |
| * |
| * Create instance that would be connected into endpoints in USBD library. |
| */ |
| APP_USBD_CLASS_INST_GLOBAL_DEF( |
| app_usbd_core_inst, |
| app_usbd_core, |
| &m_core_methods, |
| APP_USBD_CORE_CLASS_CONFIGURATION, |
| () ); |
| /*lint -restore*/ |
| |
| /** |
| * @brief Set the new USB state. |
| * |
| * Function changes the internal status of the bus. |
| * If the bus status is different than the one configured, an event is passed to all |
| * the instances. |
| * |
| * @param state New state to be set. |
| * |
| * @sa usbd_core_state_get |
| */ |
| static void usbd_core_state_set(app_usbd_state_t state) |
| { |
| if (m_app_usbd_state != state) |
| { |
| m_app_usbd_state = state; |
| if(state != APP_USBD_STATE_Configured) |
| { |
| CLR_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP); |
| } |
| static const app_usbd_evt_t evt_data = { |
| .type = APP_USBD_EVT_STATE_CHANGED |
| }; |
| app_usbd_event_execute((app_usbd_internal_evt_t const *)&evt_data); |
| } |
| } |
| |
| /** |
| * @brief Get the current USB state - internal function. |
| * |
| * This is just a wrapper for @ref app_usbd_core_state_get |
| * to make symmetrical function to the internal @ref usbd_core_state_set. |
| * |
| * @return Current USB state. |
| * |
| * @sa usbd_core_state_set |
| * @sa app_usbd_core_state_get |
| */ |
| static inline app_usbd_state_t usbd_core_state_get(void) |
| { |
| return m_app_usbd_state; |
| } |
| |
| /** |
| * @brief Check current USBD power connection status. |
| * |
| */ |
| static inline bool usbd_core_power_is_detected(void) |
| { |
| return 0 != ( (NRF_POWER->USBREGSTATUS) & POWER_USBREGSTATUS_VBUSDETECT_Msk); |
| } |
| |
| /** |
| * @brief Clear current EP0 handler. |
| * |
| * Function just clears the EP0 handler without calling it. |
| */ |
| static inline void usbd_core_ep0_handler_clear(void) |
| { |
| m_ep0_handler_desc.handler = NULL; |
| } |
| |
| /** |
| * @brief Safely call EP0 handler. |
| * |
| * Function calls EP0 handler only if its pointer is non-zero. |
| * |
| * @param status Status to send as a handler parameter. |
| */ |
| static inline ret_code_t usbd_core_ep0_handler_call_and_clear(nrf_drv_usbd_ep_status_t status) |
| { |
| app_usbd_core_setup_data_handler_t handler = m_ep0_handler_desc.handler; |
| if (NULL != handler) |
| { |
| usbd_core_ep0_handler_clear(); |
| return handler(status, m_ep0_handler_desc.p_context); |
| } |
| |
| return NRF_ERROR_NULL; |
| } |
| |
| /** |
| * @brief Check if EP0 handler is configured. |
| * |
| * EP0 handler is configured is any instance that has processed SETUP command |
| * expects some incoming / outgoing data. |
| * |
| * EP0 handler should be cleared automatically just before it is called |
| * (see @ref usbd_core_ep0_handler_call_and_clear). |
| * If instance requires more data - it has to setup EP0 handler once more time |
| * (see @ref app_usbd_core_setup_data_handler_set). |
| * |
| * This function adds small layer of abstraction for checking if EP0 handler |
| * is already configured. |
| * |
| * @retval true EP0 handler is set. |
| * @retval false EP0 handler is cleared. |
| */ |
| static inline bool usb_core_ep0_handler_check(void) |
| { |
| return (NULL != m_ep0_handler_desc.handler); |
| } |
| |
| /** |
| * @brief Empty data handler. |
| * |
| * Data handler used only to mark that there is requested data during SETUP. |
| * |
| * @return Always NRF_SUCCESS |
| * @sa setup_empty_data_handler_desc |
| */ |
| static ret_code_t setup_data_handler_empty(nrf_drv_usbd_ep_status_t status, void * p_contex) |
| { |
| UNUSED_PARAMETER(status); |
| UNUSED_PARAMETER(p_contex); |
| return NRF_SUCCESS; |
| } |
| |
| /** |
| * @brief |
| * |
| * Empty EP0 transfer transfer handler. |
| */ |
| static app_usbd_core_setup_data_handler_desc_t const m_setup_data_handler_empty_desc = |
| { |
| .handler = setup_data_handler_empty, |
| .p_context = NULL |
| }; |
| |
| /** |
| * @brief Structure used as a context for descriptor feeder. |
| * |
| * Structure with all the data required to process instances to generate descriptor |
| * data chunk. |
| */ |
| typedef struct |
| { |
| app_usbd_class_inst_t const * p_cinst; //!< The class instance that is to be processed next. |
| const uint8_t * p_desc; //!< Pointer at current descriptor or NULL if finished. |
| /**< |
| * If the value passed by @ref p_desc is NULL on transfer function enter it means that ZLP is required. |
| * Or it is time to finish the transfer (depending on @c total_left). |
| */ |
| size_t desc_left; //!< Number of bytes left in the current class descriptor to send |
| size_t total_left; //!< Number of bytes left that was requested by the host |
| app_usbd_class_descriptor_ctx_t feed_thread; //!< Class descriptor context |
| } app_usbd_core_descriptor_conf_feed_data_t; |
| |
| /** |
| * @brief Default data used by the feeder. |
| * |
| * |
| */ |
| static app_usbd_core_descriptor_conf_feed_data_t m_descriptor_conf_feed_data; |
| |
| /** |
| * @brief Descriptor feeder. |
| * |
| * Descriptor feeder is used as an callback function when descriptors are |
| * transfered and buffer is ready for next data. |
| * It prepares next chunk of data to be sent. |
| * |
| * @param p_next See @ref nrf_drv_usbd_next_transfer_handler_t documentation. |
| * @param p_context Pointer to @ref app_usbd_core_descriptor_feed_data_t data type. |
| * @param ep_size The size of the endpoint. |
| * |
| * @return See @ref nrf_drv_usbd_next_transfer_handler_t documentation. |
| */ |
| static bool usbd_descriptor_conf_feeder( |
| nrf_drv_usbd_ep_transfer_t * p_next, |
| void * p_context, |
| size_t ep_size) |
| { |
| bool continue_req = true; |
| |
| app_usbd_core_descriptor_conf_feed_data_t * p_data = p_context; |
| |
| |
| if ((p_data->p_desc == NULL) && (app_usbd_class_next_get(p_data->p_cinst) == NULL) |
| && (p_data->desc_left == 0)) |
| { |
| /* ZLP */ |
| continue_req = false; |
| p_next->p_data.tx = NULL; |
| p_next->size = 0; |
| } |
| else |
| { |
| ASSERT(ep_size <= NRF_DRV_USBD_FEEDER_BUFFER_SIZE); |
| uint8_t * p_tx_buff; |
| size_t size = 0; /* Currently added number of bytes */ |
| size_t tx_size; /* Number of bytes to send right now */ |
| bool feeding = false; |
| |
| /* Feeder function can use the USBD driver internal buffer */ |
| p_tx_buff = nrf_drv_usbd_feeder_buffer_get(); |
| |
| tx_size = MIN(ep_size, p_data->total_left); |
| |
| while (0 != tx_size) |
| { |
| size_t to_copy = MIN(tx_size, p_data->desc_left); |
| |
| /* First transfer */ |
| if (p_data->p_desc != NULL) |
| { |
| memcpy(p_tx_buff + size, p_data->p_desc, to_copy); |
| p_data->p_desc = NULL; |
| } |
| /* Starting with second transfer */ |
| else if (0 < p_data->desc_left) |
| { |
| UNUSED_RETURN_VALUE(p_data->p_cinst->p_class_methods->feed_descriptors( |
| &p_data->feed_thread, p_data->p_cinst, |
| (uint8_t *)p_tx_buff + size, to_copy)); |
| feeding = true; |
| } |
| else |
| { |
| ; |
| } |
| |
| p_data->desc_left -= to_copy; |
| p_data->total_left -= to_copy; |
| tx_size -= to_copy; |
| size += to_copy; |
| |
| /* Switch to next class if no descriptor left and first feeding was done */ |
| if(p_data->desc_left == 0 && feeding) |
| { |
| p_data->p_cinst = app_usbd_class_next_get(p_data->p_cinst); |
| } |
| |
| if (0 == p_data->total_left) |
| { |
| continue_req = false; |
| } |
| else if (0 == p_data->desc_left) |
| { |
| if (NULL == p_data->p_cinst) |
| { |
| p_data->p_desc = NULL; |
| /* No more data - check if ZLP is required */ |
| if (size > 0) |
| { |
| if (size < ep_size) |
| { |
| continue_req = false; |
| } |
| } |
| break; |
| } |
| else |
| { |
| /* New class - count descriptor size and initialize feeding thread */ |
| app_usbd_class_descriptor_ctx_t desiz; |
| APP_USBD_CLASS_DESCRIPTOR_INIT(&desiz); |
| while(p_data->p_cinst->p_class_methods->feed_descriptors( |
| &desiz, p_data->p_cinst, NULL, sizeof(uint8_t))) |
| { |
| p_data->desc_left++; |
| } |
| APP_USBD_CLASS_DESCRIPTOR_INIT(&p_data->feed_thread); |
| } |
| } |
| else |
| { |
| /* Nothing to do */ |
| } |
| } |
| p_next->p_data.tx = p_tx_buff; |
| p_next->size = size; |
| } |
| return continue_req; |
| } |
| |
| /** |
| * @brief Standard endpoint request handle. |
| * |
| * @param[in] p_setup_ev Setup event. |
| * |
| * @return Standard error code. |
| */ |
| static ret_code_t setup_endpoint_req_std(app_usbd_setup_evt_t const * p_setup_ev) |
| { |
| if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType)) |
| { |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| nrf_drv_usbd_ep_t ep_addr = (nrf_drv_usbd_ep_t)(p_setup_ev->setup.wIndex.lb); |
| app_usbd_state_t usb_state = usbd_core_state_get(); |
| |
| switch (p_setup_ev->setup.bRequest) |
| { |
| case APP_USBD_SETUP_STDREQ_GET_STATUS: |
| { |
| if ((usb_state == APP_USBD_STATE_Configured) || (NRF_USBD_EP_NR_GET(ep_addr) == 0)) |
| { |
| size_t tx_size; |
| uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size); |
| |
| p_tx_buff[0] = nrf_drv_usbd_ep_stall_check(ep_addr) ? 1 : 0; |
| return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t)); |
| } |
| else |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| } |
| case APP_USBD_SETUP_STDREQ_SET_FEATURE: |
| { |
| if ((!NRF_USBD_EPISO_CHECK(ep_addr)) && |
| (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT)) |
| { |
| if ((usb_state == APP_USBD_STATE_Configured) || (NRF_USBD_EP_NR_GET(ep_addr) == 0)) |
| { |
| nrf_drv_usbd_ep_stall(ep_addr); |
| return NRF_SUCCESS; |
| } |
| else |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| } |
| break; |
| } |
| case APP_USBD_SETUP_STDREQ_CLEAR_FEATURE: |
| { |
| if ((!NRF_USBD_EPISO_CHECK(ep_addr)) && |
| (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_ENDPOINT_HALT)) |
| { |
| if ((usb_state == APP_USBD_STATE_Configured) || (NRF_USBD_EP_NR_GET(ep_addr) == 0)) |
| { |
| nrf_drv_usbd_ep_dtoggle_clear(ep_addr); |
| nrf_drv_usbd_ep_stall_clear(ep_addr); |
| return NRF_SUCCESS; |
| } |
| else |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| /** |
| * @brief Standard interface request handle. |
| * |
| * @param[in,out] p_class_inst Class instance that holds selected interface. |
| * @param[in] iface_idx Index of the interface in class structure. |
| * @param[in] p_event Event structure to be processed. |
| * |
| * @return Operation status. |
| */ |
| static ret_code_t setup_interface_req_std_handle( |
| app_usbd_class_inst_t const * const p_class_inst, |
| uint8_t iface_idx, |
| app_usbd_setup_evt_t const * p_setup_ev) |
| { |
| if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType)) |
| { |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| app_usbd_state_t usb_state = usbd_core_state_get(); |
| |
| if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN) |
| { |
| switch (p_setup_ev->setup.bRequest) |
| { |
| case APP_USBD_SETUP_STDREQ_GET_STATUS: |
| { |
| if (!(usb_state == APP_USBD_STATE_Configured)) |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| size_t tx_size; |
| uint16_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size); |
| p_tx_buff[0] = 0; |
| return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint16_t)); |
| } |
| case APP_USBD_SETUP_STDREQ_GET_INTERFACE: |
| { |
| if (!(usb_state == APP_USBD_STATE_Configured)) |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| size_t tx_size; |
| uint8_t * p_tx_buff = app_usbd_core_setup_transfer_buff_get(&tx_size); |
| p_tx_buff[0] = app_usbd_iface_selection_get(p_class_inst, iface_idx); |
| return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_tx_buff, sizeof(uint8_t)); |
| } |
| } |
| } |
| else /* APP_USBD_SETUP_REQDIR_OUT */ |
| { |
| switch (p_setup_ev->setup.bRequest) |
| { |
| case APP_USBD_SETUP_STDREQ_SET_INTERFACE: |
| { |
| if (!(usb_state == APP_USBD_STATE_Configured)) |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| |
| if(p_setup_ev->setup.wValue.w > UINT8_MAX) |
| { |
| return NRF_ERROR_INVALID_PARAM; |
| } |
| uint8_t alt = p_setup_ev->setup.wValue.lb; |
| return app_usbd_iface_select(p_class_inst, iface_idx, alt); |
| } |
| default: |
| break; |
| } |
| } |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| /** |
| * @brief |
| * |
| * Descriptors feeder handle structure. |
| */ |
| static const nrf_drv_usbd_handler_desc_t usbd_descriptor_feeder_desc = |
| { |
| .handler = { .feeder = usbd_descriptor_conf_feeder }, |
| .p_context = &m_descriptor_conf_feed_data |
| }; |
| |
| static ret_code_t setup_device_req_get_status( |
| app_usbd_class_inst_t const * const p_inst, |
| app_usbd_setup_evt_t const * const p_setup_ev) |
| { |
| size_t max_size; |
| uint8_t * p_trans_buff = app_usbd_core_setup_transfer_buff_get(&max_size); |
| ASSERT(sizeof(uint16_t) <= max_size); |
| |
| memset(p_trans_buff, 0, sizeof(uint16_t)); |
| if (m_configuration_dsc.bmAttributes & |
| APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_SELF_POWERED_MASK) |
| { |
| SET_BIT(p_trans_buff[0], 0); |
| } |
| if (IS_SET(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP)) |
| { |
| SET_BIT(p_trans_buff[0], 1); |
| } |
| return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_trans_buff, sizeof(uint16_t)); |
| } |
| |
| static ret_code_t setup_device_req_get_descriptor(app_usbd_class_inst_t const * const p_inst, |
| app_usbd_setup_evt_t const * const p_setup_ev) |
| { |
| switch (p_setup_ev->setup.wValue.hb) |
| { |
| case APP_USBD_DESCRIPTOR_DEVICE: |
| { |
| if(p_setup_ev->setup.wLength.w == 0) |
| { |
| return NRF_SUCCESS; |
| } |
| |
| return app_usbd_core_setup_rsp(&(p_setup_ev->setup), |
| &m_device_dsc, |
| sizeof(m_device_dsc)); |
| } |
| case APP_USBD_DESCRIPTOR_CONFIGURATION: |
| { |
| if(p_setup_ev->setup.wLength.w == 0) |
| { |
| return NRF_SUCCESS; |
| } |
| |
| /* The size equals the size of configuration descriptor and all classes descriptors */ |
| const size_t size = MIN( |
| sizeof(app_usbd_descriptor_configuration_t), |
| p_setup_ev->setup.wLength.w); |
| size_t total_length = sizeof(app_usbd_descriptor_configuration_t); |
| uint8_t iface_count = 0; |
| |
| /* Iterate over all registered classes count descriptors and total size */ |
| app_usbd_class_inst_t const * p_class; |
| for (p_class = app_usbd_class_first_get(); p_class != NULL; |
| p_class = app_usbd_class_next_get(p_class)) |
| { |
| ASSERT(NULL != (p_class->p_class_methods)); |
| ASSERT(NULL != (p_class->p_class_methods->feed_descriptors)); |
| size_t dsc_size = 0; |
| app_usbd_class_descriptor_ctx_t siz_desc; |
| APP_USBD_CLASS_DESCRIPTOR_INIT(&siz_desc); |
| while(p_class->p_class_methods->feed_descriptors(&siz_desc, |
| p_class, |
| NULL, |
| sizeof(uint8_t)) |
| ) |
| { |
| dsc_size++; |
| } |
| total_length += dsc_size; |
| iface_count += app_usbd_class_iface_count_get(p_class); |
| } |
| |
| /* Access transmission buffer */ |
| size_t max_size; |
| app_usbd_descriptor_configuration_t * p_trans_buff = |
| app_usbd_core_setup_transfer_buff_get(&max_size); |
| /* Copy the configuration descriptor and update the fields that require it */ |
| ASSERT(size <= max_size); |
| memcpy(p_trans_buff, &m_configuration_dsc, size); |
| |
| p_trans_buff->bNumInterfaces = iface_count; |
| p_trans_buff->wTotalLength = total_length; |
| if (app_usbd_class_rwu_enabled_check()) |
| { |
| p_trans_buff->bmAttributes |= |
| APP_USBD_DESCRIPTOR_CONFIGURATION_ATTRIBUTE_REMOTE_WAKEUP_MASK; |
| } |
| |
| |
| m_descriptor_conf_feed_data.p_cinst = app_usbd_class_first_get(); |
| m_descriptor_conf_feed_data.p_desc = (void *)p_trans_buff; |
| m_descriptor_conf_feed_data.desc_left = size; |
| m_descriptor_conf_feed_data.total_left = p_setup_ev->setup.wLength.w; |
| |
| /* Start first transfer */ |
| ret_code_t ret; |
| CRITICAL_REGION_ENTER(); |
| |
| ret = app_usbd_ep_handled_transfer( |
| NRF_DRV_USBD_EPIN0, |
| &usbd_descriptor_feeder_desc); |
| |
| if (NRF_SUCCESS == ret) |
| { |
| ret = app_usbd_core_setup_data_handler_set( |
| NRF_DRV_USBD_EPIN0, |
| &m_setup_data_handler_empty_desc); |
| } |
| CRITICAL_REGION_EXIT(); |
| |
| return ret; |
| } |
| case APP_USBD_DESCRIPTOR_STRING: |
| { |
| if(p_setup_ev->setup.wLength.w == 0) |
| { |
| return NRF_SUCCESS; |
| } |
| |
| app_usbd_string_desc_idx_t id = |
| (app_usbd_string_desc_idx_t)(p_setup_ev->setup.wValue.lb); |
| uint16_t langid = p_setup_ev->setup.wIndex.w; |
| uint16_t const * p_string_dsc = app_usbd_string_desc_get(id, langid); |
| if (p_string_dsc == NULL) |
| { |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| return app_usbd_core_setup_rsp( |
| &p_setup_ev->setup, |
| p_string_dsc, |
| app_usbd_string_desc_length(p_string_dsc)); |
| } |
| default: |
| break; |
| } |
| |
| |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| static ret_code_t setup_device_req_get_configuration( |
| app_usbd_class_inst_t const * const p_inst, |
| app_usbd_setup_evt_t const * const p_setup_ev) |
| { |
| size_t max_size; |
| uint8_t * p_trans_buff = app_usbd_core_setup_transfer_buff_get(&max_size); |
| app_usbd_state_t usb_state = usbd_core_state_get(); |
| if (usb_state == APP_USBD_STATE_Configured) |
| { |
| p_trans_buff[0] = 1; |
| } |
| else if (usb_state == APP_USBD_STATE_Addressed) |
| { |
| p_trans_buff[0] = 0; |
| } |
| else |
| { |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| return app_usbd_core_setup_rsp(&p_setup_ev->setup, p_trans_buff, sizeof(p_trans_buff[0])); |
| } |
| |
| static ret_code_t setup_device_req_set_configuration( |
| app_usbd_class_inst_t const * const p_inst, |
| app_usbd_setup_evt_t const * const p_setup_ev) |
| { |
| app_usbd_state_t usb_state = usbd_core_state_get(); |
| if (!((usb_state == APP_USBD_STATE_Configured) || |
| (usb_state == APP_USBD_STATE_Addressed))) |
| { |
| return NRF_ERROR_INVALID_STATE; |
| } |
| |
| if (p_setup_ev->setup.wValue.lb == 0) |
| { |
| app_usbd_all_iface_deselect(); |
| usbd_core_state_set(APP_USBD_STATE_Addressed); |
| } |
| else if (p_setup_ev->setup.wValue.lb == 1) |
| { |
| /*Clear all bulk/interrupt endpoint status and set toggle to DATA0*/ |
| app_usbd_all_iface_select_0(); |
| usbd_core_state_set(APP_USBD_STATE_Configured); |
| } |
| else |
| { |
| /*In this driver only one configuration is supported.*/ |
| return NRF_ERROR_INVALID_PARAM; |
| } |
| |
| return NRF_SUCCESS; |
| } |
| |
| /** |
| * @brief Internal SETUP event handler. |
| * @param[in] p_inst Instance of the class. |
| * @param[in] p_setup_ev Setup request. |
| * @return Standard error code. |
| * @retval NRF_SUCCESS Request handled correctly. |
| * @retval NRF_ERROR_NOT_SUPPORTED Request is not supported. |
| */ |
| static ret_code_t setup_device_req_std_handler(app_usbd_class_inst_t const * const p_inst, |
| app_usbd_setup_evt_t const * const p_setup_ev) |
| { |
| ASSERT(p_inst != NULL); |
| ASSERT(p_setup_ev != NULL); |
| |
| if (APP_USBD_SETUP_REQTYPE_STD != app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType)) |
| { |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| |
| if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN) |
| { |
| switch (p_setup_ev->setup.bRequest) |
| { |
| case APP_USBD_SETUP_STDREQ_GET_STATUS: |
| { |
| return setup_device_req_get_status(p_inst, p_setup_ev); |
| } |
| case APP_USBD_SETUP_STDREQ_GET_DESCRIPTOR: |
| { |
| return setup_device_req_get_descriptor(p_inst, p_setup_ev); |
| } |
| case APP_USBD_SETUP_STDREQ_GET_CONFIGURATION: |
| { |
| return setup_device_req_get_configuration(p_inst, p_setup_ev); |
| } |
| default: |
| /*Not supported*/ |
| break; |
| } |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| else /* APP_USBD_SETUP_REQDIR_OUT */ |
| { |
| switch (p_setup_ev->setup.bRequest) |
| { |
| case APP_USBD_SETUP_STDREQ_SET_ADDRESS: |
| { |
| ASSERT(0); /* should never reach this point */ |
| break; |
| } |
| case APP_USBD_SETUP_STDREQ_SET_FEATURE: |
| { |
| if (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP) |
| { |
| if (!app_usbd_class_rwu_enabled_check()) |
| { |
| return NRF_ERROR_FORBIDDEN; |
| } |
| SET_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP); |
| return NRF_SUCCESS; |
| } |
| break; |
| } |
| case APP_USBD_SETUP_STDREQ_CLEAR_FEATURE: |
| { |
| if (p_setup_ev->setup.wValue.w == APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP) |
| { |
| if (!app_usbd_class_rwu_enabled_check()) |
| { |
| return NRF_ERROR_FORBIDDEN; |
| } |
| CLR_BIT(m_device_features_state, APP_USBD_SETUP_STDFEATURE_DEVICE_REMOTE_WAKEUP); |
| return NRF_SUCCESS; |
| } |
| break; |
| } |
| case APP_USBD_SETUP_STDREQ_SET_CONFIGURATION: |
| { |
| return setup_device_req_set_configuration(p_inst, p_setup_ev); |
| } |
| case APP_USBD_SETUP_STDREQ_SET_DESCRIPTOR: |
| { |
| /*Not supported yet.*/ |
| break; |
| } |
| default: |
| /*Not supported*/ |
| break; |
| } |
| return NRF_ERROR_NOT_SUPPORTED; |
| } |
| } |
| |
| /** |
| * @brief Process SETUP command. |
| * |
| * Auxiliary function for SETUP command processing. |
| */ |
| static inline ret_code_t app_usbd_core_setup_req_handler(app_usbd_class_inst_t const * const p_inst) |
| { |
| app_usbd_setup_evt_t setup_ev; |
| ret_code_t ret = NRF_ERROR_NOT_SUPPORTED; /* Final result of request processing function */ |
| |
| setup_ev.type = APP_USBD_EVT_DRV_SETUP; |
| nrf_drv_usbd_setup_get((nrf_drv_usbd_setup_t *)&(setup_ev.setup)); |
| |
| NRF_LOG_DEBUG("SETUP: t: 0x%.2x r: 0x%.2x", |
| setup_ev.setup.bmRequestType, |
| setup_ev.setup.bRequest); |
| if (usb_core_ep0_handler_check()) |
| { |
| NRF_LOG_WARNING("Previous setup not finished!"); |
| } |
| /* Clear EP0 handler if there is anything in progress */ |
| usbd_core_ep0_handler_clear(); |
| |
| switch (app_usbd_setup_req_rec(setup_ev.setup.bmRequestType)) |
| { |
| case APP_USBD_SETUP_REQREC_DEVICE: |
| { |
| /* Endpoint 0 has core instance (that process device requests) connected */ |
| ret = setup_device_req_std_handler(p_inst, &setup_ev); |
| if (ret == NRF_ERROR_NOT_SUPPORTED) |
| { |
| ret = app_usbd_all_until_served_call((app_usbd_complex_evt_t const *)&setup_ev); |
| } |
| break; |
| } |
| case APP_USBD_SETUP_REQREC_INTERFACE: |
| { |
| uint8_t const iface_number = setup_ev.setup.wIndex.lb; |
| uint8_t iface_idx; |
| app_usbd_class_inst_t const * p_inst_found = app_usbd_iface_find( |
| iface_number, |
| &iface_idx); |
| if (p_inst_found == NULL) |
| { |
| ret = NRF_ERROR_INVALID_ADDR; |
| } |
| else |
| { |
| ret = app_usbd_iface_call( |
| p_inst_found, iface_idx, |
| (app_usbd_complex_evt_t const *)&setup_ev); |
| if (ret == NRF_ERROR_NOT_SUPPORTED) |
| { |
| ret = setup_interface_req_std_handle(p_inst_found, iface_idx, &setup_ev); |
| } |
| } |
| break; |
| } |
| case APP_USBD_SETUP_REQREC_ENDPOINT: |
| { |
| ret = NRF_ERROR_NOT_SUPPORTED; |
| nrf_drv_usbd_ep_t ep = (nrf_drv_usbd_ep_t)setup_ev.setup.wIndex.lb; |
| if ((NRF_USBD_EP_NR_GET(ep) != 0)) /* For EP0 we would call this function again! */ |
| { |
| ret = app_usbd_ep_call(ep, (app_usbd_complex_evt_t const *)&setup_ev); |
| } |
| if (ret == NRF_ERROR_NOT_SUPPORTED) |
| { |
| ret = setup_endpoint_req_std(&setup_ev); |
| } |
| break; |
| } |
| case APP_USBD_SETUP_REQREC_OTHER: |
| { |
| /* Try to process via every instance */ |
| ret = app_usbd_all_until_served_call((app_usbd_complex_evt_t const *)&setup_ev); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| /* Processing result */ |
| if (ret == NRF_SUCCESS) |
| { |
| if (usb_core_ep0_handler_check()) |
| { |
| if (NRF_DRV_USBD_EPOUT0 == nrf_drv_usbd_last_setup_dir_get()) |
| { |
| /* Request processed successfully and requires SETUP data */ |
| nrf_drv_usbd_setup_data_clear(); |
| } |
| } |
| else |
| { |
| /* Request processed successfully */ |
| nrf_drv_usbd_setup_clear(); |
| } |
| } |
| else |
| { |
| /* Request finished with error */ |
| nrf_drv_usbd_setup_stall(); |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief Event handler for core module. |
| * |
| * The event handler that would process all events directed to device. |
| * |
| */ |
| static ret_code_t app_usbd_core_event_handler(app_usbd_class_inst_t const * const p_inst, |
| app_usbd_complex_evt_t const * const p_event) |
| { |
| ret_code_t ret = NRF_ERROR_NOT_SUPPORTED; |
| switch (p_event->type) |
| { |
| case APP_USBD_EVT_DRV_RESET: |
| { |
| usbd_core_state_set(APP_USBD_STATE_Default); |
| break; |
| } |
| case APP_USBD_EVT_DRV_SUSPEND: |
| { |
| ret = NRF_SUCCESS; |
| break; |
| } |
| case APP_USBD_EVT_DRV_RESUME: |
| { |
| if (nrf_atomic_flag_clear_fetch(&m_rwu_pending) != 0) |
| { |
| nrf_usbd_task_trigger(NRF_USBD_TASK_NODRIVEDPDM); |
| } |
| |
| ASSERT(usbd_core_state_get() >= APP_USBD_STATE_Unattached); |
| ret = NRF_SUCCESS; |
| break; |
| } |
| case APP_USBD_EVT_SETUP_SETADDRESS: |
| { |
| app_usbd_state_t usb_state = usbd_core_state_get(); |
| if (usb_state == APP_USBD_STATE_Default) |
| { |
| usbd_core_state_set(APP_USBD_STATE_Addressed); |
| } |
| break; |
| } |
| case APP_USBD_EVT_DRV_SETUP: |
| { |
| ret = app_usbd_core_setup_req_handler(p_inst); |
| break; |
| } |
| case APP_USBD_EVT_INST_APPEND: |
| { |
| ASSERT(usbd_core_state_get() == APP_USBD_STATE_Disabled); |
| ret = NRF_SUCCESS; |
| break; |
| } |
| case APP_USBD_EVT_INST_REMOVE: |
| { |
| ASSERT(usbd_core_state_get() == APP_USBD_STATE_Unattached); |
| usbd_core_state_set(APP_USBD_STATE_Disabled); |
| ret = NRF_SUCCESS; |
| break; |
| } |
| case APP_USBD_EVT_STARTED: |
| { |
| if (usbd_core_power_is_detected()) |
| { |
| usbd_core_state_set(APP_USBD_STATE_Powered); |
| } |
| else |
| { |
| usbd_core_state_set(APP_USBD_STATE_Unattached); |
| } |
| ret = NRF_SUCCESS; |
| break; |
| } |
| case APP_USBD_EVT_STOPPED: |
| { |
| ASSERT(usbd_core_state_get() >= APP_USBD_STATE_Powered); |
| usbd_core_state_set(APP_USBD_STATE_Unattached); |
| ret = NRF_SUCCESS; |
| break; |
| } |
| /* Data transfer on endpoint 0 */ |
| case APP_USBD_EVT_DRV_EPTRANSFER: |
| { |
| if (p_event->drv_evt.data.eptransfer.status == NRF_USBD_EP_ABORTED) |
| { |
| /* Just ignore aborting */ |
| break; |
| } |
| /* This EPTRANSFER event has to be called only for EP0 */ |
| ASSERT((p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPOUT0) || |
| (p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPIN0)); |
| ret = usbd_core_ep0_handler_call_and_clear(p_event->drv_evt.data.eptransfer.status); |
| /* Processing result */ |
| if (ret == NRF_SUCCESS) |
| { |
| if (usb_core_ep0_handler_check()) |
| { |
| if (p_event->drv_evt.data.eptransfer.ep == NRF_DRV_USBD_EPOUT0) |
| { |
| /* Request processed successfully and requires SETUP data */ |
| nrf_drv_usbd_setup_data_clear(); |
| } |
| } |
| else |
| { |
| if (!nrf_drv_usbd_errata_154()) |
| { |
| nrf_drv_usbd_setup_clear(); |
| } |
| } |
| } |
| else |
| { |
| /* Request finished with error */ |
| nrf_drv_usbd_setup_stall(); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /** @} */ |
| |
| void app_usbd_core_ep0_enable(void) |
| { |
| app_usbd_ep_enable(NRF_DRV_USBD_EPOUT0); |
| app_usbd_ep_enable(NRF_DRV_USBD_EPIN0); |
| } |
| |
| void app_usbd_core_ep0_disable(void) |
| { |
| app_usbd_ep_disable(NRF_DRV_USBD_EPOUT0); |
| app_usbd_ep_disable(NRF_DRV_USBD_EPIN0); |
| } |
| |
| |
| ret_code_t app_usbd_core_setup_rsp(app_usbd_setup_t const * p_setup, |
| void const * p_data, |
| size_t size) |
| { |
| size_t req_size = p_setup->wLength.w; |
| size_t tx_size = MIN(req_size, size); |
| bool zlp_required = (size < req_size) && |
| (0 == (size % nrf_drv_usbd_ep_max_packet_size_get(NRF_DRV_USBD_EPIN0))); |
| |
| NRF_DRV_USBD_TRANSFER_IN_FLAGS( |
| transfer, |
| p_data, |
| tx_size, |
| zlp_required ? NRF_DRV_USBD_TRANSFER_ZLP_FLAG : 0); |
| |
| ret_code_t ret; |
| CRITICAL_REGION_ENTER(); |
| ret = app_usbd_ep_transfer(NRF_DRV_USBD_EPIN0, &transfer); |
| if (NRF_SUCCESS == ret) |
| { |
| ret = app_usbd_core_setup_data_handler_set(NRF_DRV_USBD_EPIN0, |
| &m_setup_data_handler_empty_desc); |
| } |
| CRITICAL_REGION_EXIT(); |
| |
| return ret; |
| } |
| |
| ret_code_t app_usbd_core_setup_data_handler_set( |
| nrf_drv_usbd_ep_t ep, |
| app_usbd_core_setup_data_handler_desc_t const * const p_handler_desc) |
| { |
| if (nrf_drv_usbd_last_setup_dir_get() != ep) |
| { |
| return NRF_ERROR_INVALID_ADDR; |
| } |
| |
| m_ep0_handler_desc = *p_handler_desc; |
| return NRF_SUCCESS; |
| } |
| |
| void * app_usbd_core_setup_transfer_buff_get(size_t * p_size) |
| { |
| if (p_size != NULL) |
| *p_size = sizeof(m_setup_transfer_buff); |
| |
| return m_setup_transfer_buff; |
| } |
| |
| app_usbd_state_t app_usbd_core_state_get(void) |
| { |
| return usbd_core_state_get(); |
| } |
| |
| |
| bool app_usbd_core_feature_state_get(app_usbd_setup_stdfeature_t feature) |
| { |
| return IS_SET(m_device_features_state, feature) ? true : false; |
| } |
| |
| #endif //NRF_MODULE_ENABLED(APP_USBD) |