/* * LPCUSB, an USB device driver for LPC microcontrollers * Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) * Copyright (c) 2016 Intel Corporation * * 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 the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR 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. */ /** * @file * @brief USB device core layer * * This module handles control transfer handler, standard request handler and * USB Interface for customer application. * * Control transfers handler is normally installed on the * endpoint 0 callback. * * Control transfers can be of the following type: * 0 Standard; * 1 Class; * 2 Vendor; * 3 Reserved. * * A callback can be installed for each of these control transfers using * usb_register_request_handler. * When an OUT request arrives, data is collected in the data store provided * with the usb_register_request_handler call. When the transfer is done, the * callback is called. * When an IN request arrives, the callback is called immediately to either * put the control transfer data in the data store, or to get a pointer to * control transfer data. The data is then packetized and sent to the host. * * Standard request handler handles the 'chapter 9' processing, specifically * the standard device requests in table 9-3 from the universal serial bus * specification revision 2.0 */ #include #include #include #include #include #if defined(USB_VUSB_EN_GPIO) #include #endif #include #include #include #include #include #include #include #include #define LOG_LEVEL CONFIG_SYS_LOG_USB_DEVICE_LEVEL LOG_MODULE_REGISTER(usb_device); #define MAX_DESC_HANDLERS 4 /** Device, interface, endpoint, other */ /* general descriptor field offsets */ #define DESC_bLength 0 /** Length offset */ #define DESC_bDescriptorType 1 /** Descriptor type offset */ /* config descriptor field offsets */ #define CONF_DESC_wTotalLength 2 /** Total length offset */ #define CONF_DESC_bConfigurationValue 5 /** Configuration value offset */ #define CONF_DESC_bmAttributes 7 /** configuration characteristics */ /* interface descriptor field offsets */ #define INTF_DESC_bInterfaceNumber 2 /** Interface number offset */ #define INTF_DESC_bAlternateSetting 3 /** Alternate setting offset */ /* endpoint descriptor field offsets */ #define ENDP_DESC_bEndpointAddress 2 /** Endpoint address offset */ #define ENDP_DESC_bmAttributes 3 /** Bulk or interrupt? */ #define ENDP_DESC_wMaxPacketSize 4 /** Maximum packet size offset */ #define MAX_NUM_REQ_HANDLERS 4 #define MAX_STD_REQ_MSG_SIZE 8 #define MAX_NUM_TRANSFERS 4 /** Max number of parallel transfers */ /* Default USB control EP, always 0 and 0x80 */ #define USB_CONTROL_OUT_EP0 0 #define USB_CONTROL_IN_EP0 0x80 /* bound the USB descriptor structs */ static const struct usb_cfg_data *usb_cfg_data_buf[CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM]; struct usb_transfer_data { /** endpoint associated to the transfer */ u8_t ep; /** Transfer status */ int status; /** Transfer read/write buffer */ u8_t *buffer; /** Transfer buffer size */ size_t bsize; /** Transferred size */ size_t tsize; /** Transfer callback */ usb_transfer_callback cb; /** Transfer caller private data */ void *priv; /** Transfer synchronization semaphore */ struct k_sem sem; /** Transfer read/write work */ struct k_work work; /** Transfer flags */ unsigned int flags; }; #ifdef CONFIG_USB_DEVICE_TRANSFER static void usb_transfer_work(struct k_work *item); #endif static struct usb_dev_priv { /** Setup packet */ struct usb_setup_packet setup; /** Pointer to data buffer */ u8_t *data_buf; /** Eemaining bytes in buffer */ s32_t data_buf_residue; /** Total length of control transfer */ s32_t data_buf_len; /** Installed custom request handler */ usb_request_handler custom_req_handler; /** USB stack status clalback */ usb_status_callback status_callback; /** Pointer to registered descriptors */ const u8_t *descriptors; /** Pointer to registered full-speed descriptors */ const u8_t *fs_descriptors; /** Pointer to registered high-speed descriptors */ const u8_t *hs_descriptors; /** Array of installed request handler callbacks */ usb_request_handler req_handlers[MAX_NUM_REQ_HANDLERS]; /* Buffer used for storing standard, class and vendor request data */ u8_t req_data[CONFIG_USB_REQUEST_BUFFER_SIZE]; /** Variable to check whether the usb has been enabled */ bool enabled; /** Currently selected configuration */ u8_t configuration; /** Remote wakeup feature status */ bool remote_wakeup; /** Zero-length packet for control-transfer */ bool zero; /* * HACK: Add to let the upper layer take care of control transfer * to support extend function. * * Restriction: * 1. data length = setup->wLength * 2. data length <= MAX_PACKET_SIZE0. */ bool upper_ctrl; #ifdef CONFIG_USB_DEVICE_TRANSFER /** Transfer list */ struct usb_transfer_data transfer[MAX_NUM_TRANSFERS]; #endif u8_t unconfigured; /* 1: if set configuration 0 */ } usb_dev; /* * @brief print the contents of a setup packet * * @param [in] setup The setup packet * */ static void usb_print_setup(struct usb_setup_packet *setup) { /* avoid compiler warning if LOG_DBG is not defined */ ARG_UNUSED(setup); LOG_DBG("SETUP\n"); LOG_DBG("%x %x %x %x %x\n", setup->bmRequestType, setup->bRequest, sys_le16_to_cpu(setup->wValue), sys_le16_to_cpu(setup->wIndex), sys_le16_to_cpu(setup->wLength)); } /* * @brief handle a request by calling one of the installed request handlers * * Local function to handle a request by calling one of the installed request * handlers. In case of data going from host to device, the data is at *ppbData. * In case of data going from device to host, the handler can either choose to * write its data at *ppbData or update the data pointer. * * @param [in] setup The setup packet * @param [in,out] len Pointer to data length * @param [in,out] data Data buffer * * @return true if the request was handles successfully */ static bool usb_handle_request(struct usb_setup_packet *setup, s32_t *len, u8_t **data) { u32_t type = REQTYPE_GET_TYPE(setup->bmRequestType); usb_request_handler handler = usb_dev.req_handlers[type]; LOG_DBG("** %d **\n", type); if (type >= MAX_NUM_REQ_HANDLERS) { LOG_DBG("Error Incorrect iType %d\n", type); return false; } if (handler == NULL) { LOG_DBG("No handler for reqtype %d\n", type); return false; } if ((*handler)(setup, len, data) < 0) { LOG_DBG("Handler Error %d\n", type); usb_print_setup(setup); return false; } return true; } /* * @brief send next chunk of data (possibly 0 bytes) to host * * @return N/A */ static void usb_data_to_host(void) { u32_t chunk = MIN(MAX_PACKET_SIZE0, usb_dev.data_buf_residue); /*Always EP0 for control*/ usb_dc_ep_write(0x80, usb_dev.data_buf, chunk, &chunk); usb_dev.data_buf += chunk; usb_dev.data_buf_residue -= chunk; } /* * @brief handle IN/OUT transfers on EP0 * * @param [in] ep Endpoint address * @param [in] ep_status Endpoint status * * @return N/A */ static void usb_handle_control_transfer(u8_t ep, enum usb_dc_ep_cb_status_code ep_status) { u32_t chunk = 0; struct usb_setup_packet *setup = &usb_dev.setup; LOG_DBG("ep %x, status %x\n", ep, ep_status); if (ep == USB_CONTROL_OUT_EP0 && ep_status == USB_DC_EP_SETUP) { u16_t length; /* * OUT transfer, Setup packet, * reset request message state machine */ if (usb_dc_ep_read(ep, (u8_t *)setup, sizeof(*setup), NULL) < 0) { LOG_DBG("Read Setup Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } length = sys_le16_to_cpu(setup->wLength); if (length > CONFIG_USB_REQUEST_BUFFER_SIZE) { if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST) { LOG_DBG("length 0x%x(0x%x) too small\n", length, CONFIG_USB_REQUEST_BUFFER_SIZE); } else { LOG_ERR("Request buffer too small\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); usb_dc_ep_set_stall(USB_CONTROL_OUT_EP0); return; } } usb_dev.data_buf = usb_dev.req_data; usb_dev.data_buf_residue = length; usb_dev.data_buf_len = length; if (length && REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_DEVICE) { return; } /* Ask installed handler to process request */ if (!usb_handle_request(setup, &usb_dev.data_buf_len, &usb_dev.data_buf)) { LOG_DBG("usb_handle_request failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } if ((usb_dev.data_buf_len == 0) && (length != 0) && (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST)) { usb_dev.upper_ctrl = true; return; } /* Send smallest of requested and offered length */ usb_dev.data_buf_residue = MIN(usb_dev.data_buf_len, length); /* Check for zero-length packet */ if ((usb_dev.data_buf_residue < length) && (usb_dev.data_buf_residue % MAX_PACKET_SIZE0 == 0)) { usb_dev.zero = true; } else { usb_dev.zero = false; } /* Send first part (possibly a zero-length status message) */ usb_data_to_host(); } else if (ep == USB_CONTROL_OUT_EP0) { /* OUT transfer, data or status packets */ if (usb_dev.data_buf_residue <= 0) { /* absorb zero-length status message */ if (usb_dc_ep_read(USB_CONTROL_OUT_EP0, usb_dev.data_buf, 0, &chunk) < 0) { LOG_DBG("Read DATA Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); } return; } if (usb_dc_ep_read(USB_CONTROL_OUT_EP0, usb_dev.data_buf, usb_dev.data_buf_residue, &chunk) < 0) { LOG_DBG("Read DATA Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); usb_dc_ep_set_stall(USB_CONTROL_OUT_EP0); return; } usb_dev.data_buf += chunk; usb_dev.data_buf_residue -= chunk; if (usb_dev.data_buf_residue == 0) { /* Received all, send data to handler */ usb_dev.data_buf = usb_dev.req_data; if (!usb_handle_request(setup, &usb_dev.data_buf_len, &usb_dev.data_buf)) { LOG_DBG("usb_handle_request1 failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } /*Send status to host*/ LOG_DBG(">> usb_data_to_host(2)\n"); usb_data_to_host(); } } else if (ep == USB_CONTROL_IN_EP0) { /* Avoid redundant data transfer */ if (usb_dev.upper_ctrl) { usb_dev.upper_ctrl = false; usb_dev.data_buf_residue = 0; return; } if (usb_dev.zero && usb_dev.data_buf_residue == 0) { LOG_DBG("send zero-length packet\n"); usb_data_to_host(); usb_dev.zero = false; } /* Send more data if available */ if (usb_dev.data_buf_residue != 0) { usb_data_to_host(); } } else { __ASSERT_NO_MSG(false); } } /* * @brief register a callback for handling requests * * @param [in] type Type of request, e.g. REQTYPE_TYPE_STANDARD * @param [in] handler Callback function pointer * * @return N/A */ static void usb_register_request_handler(s32_t type, usb_request_handler handler) { usb_dev.req_handlers[type] = handler; } /* * @brief register full-speed/high-speed USB descriptors * * @return N/A */ void usb_device_register_descriptors(const u8_t *usb_fs_descriptors, const u8_t *usb_hs_descriptors) { usb_dev.fs_descriptors = usb_fs_descriptors; usb_dev.hs_descriptors = usb_hs_descriptors; } #define STRING_LENGTH(s) (strlen(s) * 2) static u8_t str_desc_buf[CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN*2 + 2]; static u8_t manufacturer_str_ptr[CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN]; static u8_t product_str_ptr[CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN]; static u8_t dev_sn_str_ptr[CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN]; int usb_device_register_string_descriptor(enum usb_device_str_desc type, u8_t *str_dat, u8_t str_len) { u8_t *str_array[3] = {"manufacture_str", "product_str", "dev_sn_str"}; if (str_len > CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN) { int index = type - 1; if(index < 0){ index = 0; } LOG_ERR("%s : %s is more than %d bytes!\n", str_array[index], str_dat, CONFIG_USB_DEVICE_STRING_DESC_MAX_LEN); return -EINVAL; } switch (type) { case MANUFACTURE_STR_DESC: memcpy(manufacturer_str_ptr, str_dat, strlen(str_dat)); break; case PRODUCT_STR_DESC: memcpy(product_str_ptr, str_dat, strlen(str_dat)); break; case DEV_SN_DESC: memcpy(dev_sn_str_ptr, str_dat, strlen(str_dat)); break; default: LOG_ERR("Unknown_Type\n"); return -EINVAL; } return 0; } static void change_strdesc_to_unicode(u8_t *str_desc, u8_t *unicode_buf) { for (u8_t i = 2, j = 0; i < unicode_buf[0]; i++) { if (i%2 == 0) { unicode_buf[i] = str_desc[j++]; } else { unicode_buf[i] = 0; } } } static void usb_process_str_des(u8_t *des_ptr) { memset(str_desc_buf, 0, sizeof(str_desc_buf)); str_desc_buf[0] = STRING_LENGTH(des_ptr) + 2; str_desc_buf[1] = USB_STRING_DESC; if(str_desc_buf[0] > sizeof(str_desc_buf)) { str_desc_buf[0] = sizeof(str_desc_buf); } change_strdesc_to_unicode(des_ptr, str_desc_buf); } static inline bool device_qual(u8_t **data) { struct usb_qualifier_descriptor *qual = (struct usb_qualifier_descriptor *)*data; struct usb_device_descriptor *desc = (struct usb_device_descriptor *)(usb_dev.descriptors); if ((usb_dc_maxspeed() < USB_SPEED_HIGH) || (usb_dc_speed() >= USB_SPEED_SUPER)) { return false; } qual->bLength = sizeof(*qual); qual->bDescriptorType = DESC_DEVICE_QUALIFIER; /* POLICY: same bcdUSB and device type info at both speeds */ qual->bcdUSB = sys_cpu_to_le16(USB_2_0), qual->bDeviceClass = desc->bDeviceClass; qual->bDeviceSubClass = desc->bDeviceSubClass; qual->bDeviceProtocol = desc->bDeviceProtocol; /* ASSUME same EP0 fifo size at both speeds */ qual->bMaxPacketSize0 = MAX_PACKET_SIZE0; qual->bNumConfigurations = 1; qual->bRESERVED = 0; return true; } static inline bool other_speed(u8_t **data, s32_t *len, u8_t index) { u8_t *p = NULL; enum usb_device_speed speed = usb_dc_speed(); if (usb_dc_maxspeed() < USB_SPEED_HIGH) { return false; } switch (speed) { case USB_SPEED_HIGH: p = (u8_t *)usb_dev.hs_descriptors; break; case USB_SPEED_FULL: p = (u8_t *)usb_dev.fs_descriptors; break; default: return false; } /* FIXME: not implemented yet */ return false; } /* * @brief get specified USB descriptor * * This function parses the list of installed USB descriptors and attempts * to find the specified USB descriptor. * * @param [in] type_index Type and index of the descriptor * @param [in] lang_id Language ID of the descriptor (currently unused) * @param [out] len Descriptor length * @param [out] data Descriptor data * * @return true if the descriptor was found, false otherwise */ static bool usb_get_descriptor(u16_t type_index, u16_t lang_id, s32_t *len, u8_t **data) { u8_t type = 0; u8_t index = 0; u8_t *p = NULL; s32_t cur_index = 0; bool found = false; /*Avoid compiler warning until this is used for something*/ ARG_UNUSED(lang_id); type = GET_DESC_TYPE(type_index); index = GET_DESC_INDEX(type_index); if (type == DESC_STRING) { switch (index) { case LANGUAGE_ID_STR: memset(str_desc_buf, 0, sizeof(str_desc_buf)); str_desc_buf[0] = 0x04; str_desc_buf[1] = 0x03; str_desc_buf[2] = 0x09; str_desc_buf[3] = 0x04; *data = str_desc_buf; *len = 4; break; case MANUFACTURE_STR_DESC: usb_process_str_des(manufacturer_str_ptr); *data = str_desc_buf; *len = str_desc_buf[0]; break; case PRODUCT_STR_DESC: usb_process_str_des(product_str_ptr); *data = str_desc_buf; *len = str_desc_buf[0]; break; case DEV_SN_DESC: usb_process_str_des(dev_sn_str_ptr); *data = str_desc_buf; *len = str_desc_buf[0]; break; default: break; } return true; } /* * Invalid types of descriptors, * see USB Spec. Revision 2.0, 9.4.3 Get Descriptor */ if ((type == DESC_INTERFACE) || (type == DESC_ENDPOINT) || (type > DESC_OTHER_SPEED)) { return false; } switch (type) { case DESC_DEVICE_QUALIFIER: *len = sizeof(struct usb_qualifier_descriptor); return device_qual(data); case DESC_OTHER_SPEED: return other_speed(data, len, index); default: break; } p = (u8_t *)usb_dev.descriptors; cur_index = 0; while (p[DESC_bLength] != 0) { if (p[DESC_bDescriptorType] == type) { if (cur_index == index) { found = true; break; } cur_index++; } /* skip to next descriptor */ p += p[DESC_bLength]; } if (found) { /* set data pointer */ *data = p; /* get length from structure */ if (type == DESC_CONFIGURATION) { /* configuration descriptor is an * exception, length is at offset * 2 and 3 */ *len = (p[CONF_DESC_wTotalLength]) | (p[CONF_DESC_wTotalLength + 1] << 8); } else { /* normally length is at offset 0 */ *len = p[DESC_bLength]; } } else { /* nothing found */ LOG_DBG("Desc %x not found!\n", type_index); } return found; } /* * @brief set USB configuration * * This function configures the device according to the specified configuration * index and alternate setting by parsing the installed USB descriptor list. * A configuration index of 0 unconfigures the device. * * @param [in] config_index Configuration index * @param [in] alt_setting Alternate setting number * * @return true if successfully configured false if error or unconfigured */ static bool usb_set_configuration(u8_t config_index, u8_t alt_setting) { u8_t *p = NULL; u8_t cur_config = 0; u8_t cur_alt_setting = 0; if (config_index == 0) { /* unconfigure device */ if (usb_dev.configuration) { usb_dev.unconfigured = 1; } LOG_DBG("Device not configured - invalid configuration " "offset\n"); return true; } /* configure endpoints for this configuration/altsetting */ p = (u8_t *)usb_dev.descriptors; cur_config = 0xFF; cur_alt_setting = 0xFF; while (p[DESC_bLength] != 0) { switch (p[DESC_bDescriptorType]) { case DESC_CONFIGURATION: /* remember current configuration index */ cur_config = p[CONF_DESC_bConfigurationValue]; break; case DESC_INTERFACE: /* remember current alternate setting */ cur_alt_setting = p[INTF_DESC_bAlternateSetting]; break; case DESC_ENDPOINT: if ((cur_config == config_index) && (cur_alt_setting == alt_setting)) { struct usb_dc_ep_cfg_data ep_cfg; /* endpoint found for desired config * and alternate setting */ ep_cfg.ep_type = p[ENDP_DESC_bmAttributes]; ep_cfg.ep_mps = (p[ENDP_DESC_wMaxPacketSize]) | (p[ENDP_DESC_wMaxPacketSize + 1] << 8); ep_cfg.ep_addr = p[ENDP_DESC_bEndpointAddress]; usb_dc_ep_configure(&ep_cfg); usb_dc_ep_enable(ep_cfg.ep_addr); } break; default: break; } /* skip to next descriptor */ p += p[DESC_bLength]; } usb_dev.unconfigured = 0; if (usb_dev.status_callback) { usb_dev.status_callback(USB_DC_CONFIGURED, &config_index); } return true; } /* * @brief get USB interface altsetting * * @param [in] iface Interface index * * @return current altsetting */ static inline u8_t usb_get_interface(u8_t iface) { u16_t intf_info; /* * In order to let upper layer know what the interface number * and retrun the current alternate setting. * * iface: the higher byte, alt_setting: the lower byte */ intf_info = (iface << 8) + 0; LOG_DBG("iface %u\n", iface); if (usb_dev.status_callback) { usb_dev.status_callback(USB_DC_ALTSETTING, (u8_t *)&intf_info); } return intf_info & 0xFF; } /* * @brief set USB interface * * @param [in] iface Interface index * @param [in] alt_setting Alternate setting number * * @return true if successfully configured false if error or unconfigured */ static bool usb_set_interface(u8_t iface, u8_t alt_setting) { const u8_t *p = usb_dev.descriptors; u8_t cur_iface = 0xFF; u8_t cur_alt_setting = 0xFF; struct usb_dc_ep_cfg_data ep_cfg; u16_t intf_info; /* * In order to let upper layer know what the interface number * and the alternate setting of "Set Interface" transfer is. * * iface: the higher byte, alt_setting: the lower byte */ intf_info = (iface << 8) + alt_setting; LOG_DBG("iface %u alt_setting %u\n", iface, alt_setting); while (p[DESC_bLength] != 0) { switch (p[DESC_bDescriptorType]) { case DESC_INTERFACE: /* remember current alternate setting */ cur_alt_setting = p[INTF_DESC_bAlternateSetting]; cur_iface = p[INTF_DESC_bInterfaceNumber]; break; case DESC_ENDPOINT: if ((cur_iface != iface) || (cur_alt_setting != alt_setting)) { break; } /* Endpoint is found for desired interface and * alternate setting */ ep_cfg.ep_type = p[ENDP_DESC_bmAttributes] & USB_DC_EP_TYPE_MASK; ep_cfg.ep_mps = (p[ENDP_DESC_wMaxPacketSize]) | (p[ENDP_DESC_wMaxPacketSize + 1] << 8); ep_cfg.ep_addr = p[ENDP_DESC_bEndpointAddress]; usb_dc_ep_configure(&ep_cfg); usb_dc_ep_enable(ep_cfg.ep_addr); LOG_DBG("Found: ep_addr 0x%x\n", ep_cfg.ep_addr); break; default: break; } /* skip to next descriptor */ p += p[DESC_bLength]; LOG_DBG("p %p\n", p); } if (usb_dev.status_callback) { usb_dev.status_callback(USB_DC_INTERFACE, (u8_t *)&intf_info); } return true; } /* * @brief handle a standard device request * * @param [in] setup The setup packet * @param [in,out] len Pointer to data length * @param [in,out] data_buf Data buffer * * @return true if the request was handled successfully */ static bool usb_handle_std_device_req(struct usb_setup_packet *setup, s32_t *len, u8_t **data_buf) { u16_t value = sys_le16_to_cpu(setup->wValue); u16_t index = sys_le16_to_cpu(setup->wIndex); bool ret = true; u8_t *data = *data_buf; switch (setup->bRequest) { case REQ_GET_STATUS: LOG_DBG("REQ_GET_STATUS\n"); /* bit 0: self-powered */ /* bit 1: remote wakeup = not supported */ data[0] = 0; data[1] = 0; #ifdef CONFIG_USB_DEVICE_SELF_POWERED data[0] |= BIT(USB_DEVICE_SELF_POWERED); #endif #ifdef CONFIG_USB_DEVICE_REMOTE_WAKEUP data[0] |= (usb_dev.remote_wakeup ? BIT(USB_DEVICE_REMOTE_WAKEUP) : 0); #endif *len = 2; break; case REQ_SET_ADDRESS: LOG_DBG("REQ_SET_ADDRESS, addr 0x%x\n", value); usb_dc_set_address(value); break; case REQ_GET_DESCRIPTOR: LOG_DBG("REQ_GET_DESCRIPTOR\n"); ret = usb_get_descriptor(value, index, len, data_buf); break; case REQ_GET_CONFIGURATION: LOG_DBG("REQ_GET_CONFIGURATION\n"); /* indicate if we are configured */ data[0] = usb_dev.configuration; *len = 1; break; case REQ_SET_CONFIGURATION: value &= 0xFF; LOG_DBG("REQ_SET_CONFIGURATION, conf 0x%x\n", value); if (!usb_set_configuration(value, 0)) { LOG_DBG("USBSetConfiguration failed!\n"); ret = false; } else { /* configuration successful, * update current configuration */ usb_dev.configuration = value; } break; case REQ_CLEAR_FEATURE: LOG_DBG("REQ_CLEAR_FEATURE\n"); ret = false; #ifdef CONFIG_USB_DEVICE_REMOTE_WAKEUP if (value == FEA_REMOTE_WAKEUP) { usb_dev.remote_wakeup = false; ret = true; } #endif break; case REQ_SET_FEATURE: LOG_DBG("REQ_SET_FEATURE\n"); ret = false; #ifdef CONFIG_USB_DEVICE_REMOTE_WAKEUP if (value == FEA_REMOTE_WAKEUP) { usb_dev.remote_wakeup = true; ret = true; } #endif if (value == FEA_TEST_MODE) { /* put TEST_MODE code here */ } break; case REQ_SET_DESCRIPTOR: LOG_DBG("Device req %x not implemented\n", setup->bRequest); ret = false; break; default: LOG_DBG("Illegal device req %x\n", setup->bRequest); ret = false; break; } return ret; } /* * @brief handle a standard interface request * * @param [in] setup The setup packet * @param [in,out] len Pointer to data length * @param [in] data_buf Data buffer * * @return true if the request was handled successfully */ static bool usb_handle_std_interface_req(struct usb_setup_packet *setup, s32_t *len, u8_t **data_buf) { u16_t value = sys_le16_to_cpu(setup->wValue); u16_t index = sys_le16_to_cpu(setup->wIndex); u8_t *data = *data_buf; switch (setup->bRequest) { case REQ_GET_STATUS: /* no bits specified */ data[0] = 0; data[1] = 0; *len = 2; break; case REQ_CLEAR_FEATURE: case REQ_SET_FEATURE: /* not defined for interface */ return false; case REQ_GET_INTERFACE: data[0] = usb_get_interface(index); *len = 1; break; case REQ_SET_INTERFACE: LOG_DBG("REQ_SET_INTERFACE\n"); usb_set_interface(index, value); *len = 0; break; default: LOG_DBG("Illegal interface req %d\n", setup->bRequest); return false; } return true; } /* * @brief handle a standard endpoint request * * @param [in] setup The setup packet * @param [in,out] len Pointer to data length * @param [in] data_buf Data buffer * * @return true if the request was handled successfully */ static bool usb_handle_std_endpoint_req(struct usb_setup_packet *setup, s32_t *len, u8_t **data_buf) { u16_t value = sys_le16_to_cpu(setup->wValue); u8_t ep = sys_le16_to_cpu(setup->wIndex); u8_t *data = *data_buf; switch (setup->bRequest) { case REQ_GET_STATUS: /* bit 0 = endpointed halted or not */ usb_dc_ep_is_stalled(ep, &data[0]); data[1] = 0; *len = 2; break; case REQ_CLEAR_FEATURE: if (value == FEA_ENDPOINT_HALT) { /* clear HALT by unstalling */ LOG_DBG("... EP clear halt %x\n", ep); usb_dc_ep_clear_stall(ep); break; } /* only ENDPOINT_HALT defined for endpoints */ return false; case REQ_SET_FEATURE: if (value == FEA_ENDPOINT_HALT) { /* set HALT by stalling */ LOG_DBG("--- EP SET halt %x\n", ep); usb_dc_ep_set_stall(ep); break; } /* only ENDPOINT_HALT defined for endpoints */ return false; case REQ_SYNCH_FRAME: LOG_DBG("EP req %d not implemented\n", setup->bRequest); return false; default: LOG_DBG("Illegal EP req %d\n", setup->bRequest); return false; } return true; } /* * @brief default handler for standard ('chapter 9') requests * * If a custom request handler was installed, this handler is called first. * * @param [in] setup The setup packet * @param [in,out] len Pointer to data length * @param [in] data_buf Data buffer * * @return true if the request was handled successfully */ static int usb_handle_standard_request(struct usb_setup_packet *setup, s32_t *len, u8_t **data_buf) { int rc = 0; if (!usb_handle_bos(setup, len, data_buf)) { return 0; } /* try the custom request handler first */ if (usb_dev.custom_req_handler && !usb_dev.custom_req_handler(setup, len, data_buf)) { return 0; } switch (REQTYPE_GET_RECIP(setup->bmRequestType)) { case REQTYPE_RECIP_DEVICE: if (usb_handle_std_device_req(setup, len, data_buf) == false) rc = -EINVAL; break; case REQTYPE_RECIP_INTERFACE: if (usb_handle_std_interface_req(setup, len, data_buf) == false) rc = -EINVAL; break; case REQTYPE_RECIP_ENDPOINT: if (usb_handle_std_endpoint_req(setup, len, data_buf) == false) rc = -EINVAL; break; default: rc = -EINVAL; } return rc; } /* * @brief Registers a callback for custom device requests * * In usb_register_custom_req_handler, the custom request handler gets a first * chance at handling the request before it is handed over to the 'chapter 9' * request handler. * * This can be used for example in HID devices, where a REQ_GET_DESCRIPTOR * request is sent to an interface, which is not covered by the 'chapter 9' * specification. * * @param [in] handler Callback function pointer */ static void usb_register_custom_req_handler(usb_request_handler handler) { usb_dev.custom_req_handler = handler; } /* * @brief register a callback for device status * * This function registers a callback for device status. The registered callback * is used to report changes in the status of the device controller. * * @param [in] cb Callback function pointer */ static void usb_register_status_callback(usb_status_callback cb) { usb_dev.status_callback = cb; } /** * @brief turn on/off USB VBUS voltage * * @param on Set to false to turn off and to true to turn on VBUS * * @return 0 on success, negative errno code on fail */ static int usb_vbus_set(bool on) { #if defined(USB_VUSB_EN_GPIO) int ret = 0; struct device *gpio_dev = device_get_binding(USB_GPIO_DRV_NAME); if (!gpio_dev) { LOG_DBG("USB requires GPIO. Cannot find %s!\n", USB_GPIO_DRV_NAME); return -ENODEV; } /* Enable USB IO */ ret = gpio_pin_configure(gpio_dev, USB_VUSB_EN_GPIO, GPIO_DIR_OUT); if (ret) return ret; ret = gpio_pin_write(gpio_dev, USB_VUSB_EN_GPIO, on == true ? 1 : 0); if (ret) return ret; #endif return 0; } static void status_cb(enum usb_dc_status_code status, u8_t *param) { if (status == USB_DC_HIGHSPEED) { LOG_DBG("High-speed status cb\n"); usb_dev.descriptors = usb_dev.hs_descriptors; } if (usb_dev.status_callback != NULL) { usb_dev.status_callback(status, param); } } bool usb_remote_wakeup_enabled(void) { return usb_dev.remote_wakeup; } int usb_remote_wakeup(void) { #ifdef CONFIG_USB_DEVICE_REMOTE_WAKEUP if (usb_dev.remote_wakeup) { return usb_dc_do_remote_wakeup(); } return -EACCES; #else return -ENOTSUP; #endif } int usb_set_config(const struct usb_cfg_data *config) { if (!config) { return -EINVAL; } /* register descriptors */ usb_dev.descriptors = usb_dev.fs_descriptors; /* register standard request handler */ usb_register_request_handler(REQTYPE_TYPE_STANDARD, usb_handle_standard_request); /* register class request handlers for each interface*/ if (config->interface.class_handler != NULL) { usb_register_request_handler(REQTYPE_TYPE_CLASS, config->interface.class_handler); } /* register vendor request handlers */ if (config->interface.vendor_handler) { usb_register_request_handler(REQTYPE_TYPE_VENDOR, config->interface.vendor_handler); } /* register class request handlers for each interface*/ if (config->interface.custom_handler != NULL) { usb_register_custom_req_handler( config->interface.custom_handler); } return 0; } static u8_t class_num; int usb_decice_composite_set_config(const struct usb_cfg_data *config) { if (class_num >= CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM) { LOG_ERR("%d\n", class_num); class_num = 0; return -EINVAL; } usb_cfg_data_buf[class_num] = config; class_num++; return 0; } int usb_deconfig(void) { class_num = 0; /* unegister standard request handler */ usb_register_request_handler(REQTYPE_TYPE_STANDARD, NULL); /* unregister class request handlers for each interface*/ usb_register_request_handler(REQTYPE_TYPE_CLASS, NULL); /* unregister class request handlers for each interface*/ usb_register_custom_req_handler(NULL); /* unregister status callback */ usb_register_status_callback(NULL); /* Reset USB controller */ usb_dc_reset(); return 0; } int usb_enable(const struct usb_cfg_data *config) { int ret; u32_t i; struct usb_dc_ep_cfg_data ep0_cfg; if (true == usb_dev.enabled) { return 0; } /* Enable VBUS if needed */ ret = usb_vbus_set(true); if (ret < 0) return ret; /* register status callback */ if (config->cb_usb_status != NULL) { usb_register_status_callback(config->cb_usb_status); } ret = usb_dc_set_status_callback(status_cb); if (ret < 0) return ret; ret = usb_dc_attach(); if (ret < 0) return ret; /* Configure control EP */ ep0_cfg.ep_mps = MAX_PACKET_SIZE0; ep0_cfg.ep_type = USB_DC_EP_CONTROL; ep0_cfg.ep_addr = USB_CONTROL_OUT_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) return ret; ep0_cfg.ep_addr = USB_CONTROL_IN_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) return ret; /*register endpoint 0 handlers*/ ret = usb_dc_ep_set_callback(USB_CONTROL_OUT_EP0, usb_handle_control_transfer); if (ret < 0) return ret; ret = usb_dc_ep_set_callback(USB_CONTROL_IN_EP0, usb_handle_control_transfer); if (ret < 0) return ret; /*register endpoint handlers*/ for (i = 0; i < config->num_endpoints; i++) { ret = usb_dc_ep_set_callback(config->endpoint[i].ep_addr, config->endpoint[i].ep_cb); if (ret < 0) return ret; } #ifdef CONFIG_USB_DEVICE_TRANSFER /* init transfer slots */ for (i = 0; i < MAX_NUM_TRANSFERS; i++) { k_work_init(&usb_dev.transfer[i].work, usb_transfer_work); k_sem_init(&usb_dev.transfer[i].sem, 1, 1); } #endif /* enable control EP */ ret = usb_dc_ep_enable(USB_CONTROL_OUT_EP0); if (ret < 0) return ret; ret = usb_dc_ep_enable(USB_CONTROL_IN_EP0); if (ret < 0) return ret; usb_dev.unconfigured = 0; usb_dev.enabled = true; return 0; } int usb_disable(void) { int ret; if (true != usb_dev.enabled) { /* Already disabled: go ahead! */ LOG_DBG("already\n"); } ret = usb_dc_detach(); if (ret < 0) { return ret; } /* Disable VBUS if needed */ usb_vbus_set(false); usb_dev.enabled = false; return 0; } int usb_write(u8_t ep, const u8_t *data, u32_t data_len, u32_t *bytes_ret) { return usb_dc_ep_write(ep, data, data_len, bytes_ret); } int usb_read(u8_t ep, u8_t *data, u32_t max_data_len, u32_t *ret_bytes) { return usb_dc_ep_read(ep, data, max_data_len, ret_bytes); } int usb_read_async(u8_t ep, u8_t *data, u32_t max_data_len, u32_t *ret_bytes) { return usb_dc_ep_read_async(ep, data, max_data_len, ret_bytes); } int usb_read_actual(u8_t ep, u32_t *ret_bytes) { return usb_dc_ep_read_actual(ep, ret_bytes); } int usb_ep_set_stall(u8_t ep) { return usb_dc_ep_set_stall(ep); } int usb_ep_clear_stall(u8_t ep) { return usb_dc_ep_clear_stall(ep); } int usb_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len, u32_t *ret_bytes) { return usb_dc_ep_read_wait(ep, data, max_data_len, ret_bytes); } int usb_ep_read_continue(u8_t ep) { return usb_dc_ep_read_continue(ep); } #ifdef CONFIG_USB_DEVICE_TRANSFER /* Transfer management */ static struct usb_transfer_data *usb_ep_get_transfer(u8_t ep) { int i; for (i = 0; i < ARRAY_SIZE(usb_dev.transfer); i++) { if (usb_dev.transfer[i].ep == ep) { return &usb_dev.transfer[i]; } } return NULL; } static void usb_transfer_work(struct k_work *item) { struct usb_transfer_data *trans; int ret = 0, bytes; u8_t ep; trans = CONTAINER_OF(item, struct usb_transfer_data, work); ep = trans->ep; if (trans->status != -EBUSY) { /* transfer cancelled or already completed */ goto done; } if (trans->flags & USB_TRANS_WRITE) { if (!trans->bsize) { if (!(trans->flags & USB_TRANS_NO_ZLP)) { usb_dc_ep_write(ep, NULL, 0, NULL); } trans->status = 0; goto done; } ret = usb_dc_ep_write(ep, trans->buffer, trans->bsize, &bytes); if (ret) { /* transfer error */ trans->status = -EINVAL; goto done; } trans->buffer += bytes; trans->bsize -= bytes; trans->tsize += bytes; } else { ret = usb_dc_ep_read_wait(ep, trans->buffer, trans->bsize, &bytes); if (ret) { /* transfer error */ trans->status = -EINVAL; goto done; } trans->buffer += bytes; trans->bsize -= bytes; trans->tsize += bytes; /* ZLP, short-pkt or buffer full */ if (!bytes || (bytes % usb_dc_ep_mps(ep)) || !trans->bsize) { /* transfer complete */ trans->status = 0; goto done; } /* we expect mote data, clear NAK */ usb_dc_ep_read_continue(ep); } done: if (trans->status != -EBUSY && trans->cb) { /* Transfer complete */ usb_transfer_callback cb = trans->cb; int tsize = trans->tsize; void *priv = trans->priv; if (k_is_in_isr()) { /* reschedule completion in thread context */ k_work_submit(&trans->work); return; } LOG_DBG("transfer done, ep=%02x, status=%d, size=%zu\n", trans->ep, trans->status, trans->tsize); trans->cb = NULL; k_sem_give(&trans->sem); /* Transfer completion callback */ cb(ep, tsize, priv); } } void usb_transfer_ep_callback(u8_t ep, enum usb_dc_ep_cb_status_code status) { struct usb_transfer_data *trans = usb_ep_get_transfer(ep); if (status != USB_DC_EP_DATA_IN && status != USB_DC_EP_DATA_OUT) { return; } if (!trans) { if (status == USB_DC_EP_DATA_IN) { u32_t bytes; /* In the unlikely case we receive data while no * transfer is ongoing, we have to consume the data * anyway. This is to prevent stucking reception on * other endpoints (e.g dw driver has only one rx-fifo, * so drain it). */ do { u8_t data; usb_dc_ep_read_wait(ep, &data, 1, &bytes); } while (bytes); LOG_ERR("RX data lost, no transfer\n"); } return; } if (!k_is_in_isr() || (status == USB_DC_EP_DATA_OUT)) { /* If we are not in IRQ context, no need to defer work */ /* Read (out) needs to be done from ep_callback */ usb_transfer_work(&trans->work); } else { k_work_submit(&trans->work); } } int usb_transfer(u8_t ep, u8_t *data, size_t dlen, unsigned int flags, usb_transfer_callback cb, void *cb_data) { struct usb_transfer_data *trans = NULL; int i, key, ret = 0; LOG_DBG("transfer start, ep=%02x, data=%p, dlen=%zd\n", ep, data, dlen); key = irq_lock(); for (i = 0; i < MAX_NUM_TRANSFERS; i++) { if (!k_sem_take(&usb_dev.transfer[i].sem, K_NO_WAIT)) { trans = &usb_dev.transfer[i]; break; } } if (!trans) { LOG_ERR("no transfer slot available\n"); ret = -ENOMEM; goto done; } if (trans->status == -EBUSY) { /* A transfer is already ongoing and not completed */ k_sem_give(&trans->sem); ret = -EBUSY; goto done; } /* Configure new transfer */ trans->ep = ep; trans->buffer = data; trans->bsize = dlen; trans->tsize = 0; trans->cb = cb; trans->flags = flags; trans->priv = cb_data; trans->status = -EBUSY; if (usb_dc_ep_mps(ep) && (dlen % usb_dc_ep_mps(ep))) { /* no need to send ZLP since last packet will be a short one */ trans->flags |= USB_TRANS_NO_ZLP; } if (flags & USB_TRANS_WRITE) { /* start writing first chunk */ k_work_submit(&trans->work); } else { /* ready to read, clear NAK */ ret = usb_dc_ep_read_continue(ep); } done: irq_unlock(key); return ret; } void usb_cancel_transfer(u8_t ep) { struct usb_transfer_data *trans; int key; key = irq_lock(); trans = usb_ep_get_transfer(ep); if (!trans) { goto done; } if (trans->status != -EBUSY) { goto done; } trans->status = -ECANCELED; k_work_submit(&trans->work); done: irq_unlock(key); } struct usb_transfer_sync_priv { int tsize; struct k_sem sem; }; static void usb_transfer_sync_cb(u8_t ep, int size, void *priv) { struct usb_transfer_sync_priv *pdata = priv; pdata->tsize = size; k_sem_give(&pdata->sem); } int usb_transfer_sync(u8_t ep, u8_t *data, size_t dlen, unsigned int flags) { struct usb_transfer_sync_priv pdata; int ret; k_sem_init(&pdata.sem, 0, 1); ret = usb_transfer(ep, data, dlen, flags, usb_transfer_sync_cb, &pdata); if (ret) { return ret; } /* Semaphore will be released by the transfer completion callback */ k_sem_take(&pdata.sem, K_FOREVER); return pdata.tsize; } #endif static void forward_status_cb(enum usb_dc_status_code status, u8_t *param) { size_t size = CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM; if (status == USB_DC_HIGHSPEED) { usb_dev.descriptors = usb_dev.hs_descriptors; } for (size_t i = 0; i < size; i++) { if (usb_cfg_data_buf[i]->cb_usb_status) { usb_cfg_data_buf[i]->cb_usb_status(status, param); } } } /* * The functions class_handler(), custom_handler() and vendor_handler() * go through the interfaces one after the other and compare the * bInterfaceNumber with the wIndex and and then call the appropriate * callback of the USB function. * Note, a USB function can have more than one interface and the * request does not have to be directed to the first interface (unlikely). * These functions can be simplified and moved to usb_handle_request() * when legacy initialization throgh the usb_set_config() and * usb_enable() is no longer needed. */ static int class_handler(struct usb_setup_packet *pSetup, s32_t *len, u8_t **data) { size_t size = CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM; u16_t index = sys_le16_to_cpu(pSetup->wIndex); const struct usb_if_descriptor *if_descr; const struct usb_interface_cfg_data *iface; const struct usb_ep_cfg_data *ep_data; LOG_DBG("bRequest 0x%x, wIndex 0x%x\n", pSetup->bRequest, index); for (size_t i = 0; i < size; i++) { iface = &(usb_cfg_data_buf[i]->interface); if_descr = usb_cfg_data_buf[i]->interface_descriptor; if (!iface->class_handler) { continue; } /* interface */ if (if_descr->bInterfaceNumber == (index & 0xFF)) { return iface->class_handler(pSetup, len, data); } /* endpoint */ for (size_t j = 0; j < usb_cfg_data_buf[i]->num_endpoints; j++) { ep_data = usb_cfg_data_buf[i]->endpoint; if (ep_data[j].ep_addr == index) { return iface->class_handler(pSetup, len, data); } } } return -ENOTSUP; } static int custom_handler(struct usb_setup_packet *pSetup, s32_t *len, u8_t **data) { size_t size = CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM; u16_t index = sys_le16_to_cpu(pSetup->wIndex); const struct usb_if_descriptor *if_descr; const struct usb_interface_cfg_data *iface; LOG_DBG("bRequest 0x%x, wIndex 0x%x\n", pSetup->bRequest, index); for (size_t i = 0; i < size; i++) { iface = &(usb_cfg_data_buf[i]->interface); if_descr = usb_cfg_data_buf[i]->interface_descriptor; if ((iface->custom_handler) && (if_descr->bInterfaceNumber == (index & 0xFF))) { return iface->custom_handler(pSetup, len, data); } } return -ENOTSUP; } static int vendor_handler(struct usb_setup_packet *pSetup, s32_t *len, u8_t **data) { size_t size = CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM; u16_t index = sys_le16_to_cpu(pSetup->wIndex); const struct usb_if_descriptor *if_descr; const struct usb_interface_cfg_data *iface; LOG_DBG("bRequest 0x%x, wIndex 0x%x\n", pSetup->bRequest, index); for (size_t i = 0; i < size; i++) { iface = &(usb_cfg_data_buf[i]->interface); if_descr = usb_cfg_data_buf[i]->interface_descriptor; if ((iface->vendor_handler) && (if_descr->bInterfaceNumber == (index & 0xFF))) { return iface->vendor_handler(pSetup, len, data); } } return -ENOTSUP; } static int composite_setup_ep_cb(void) { size_t size = CONFIG_USB_COMPOSITE_DEVICE_CLASS_NUM; const struct usb_ep_cfg_data *ep_data; for (size_t i = 0; i < size; i++) { ep_data = usb_cfg_data_buf[i]->endpoint; for (u8_t n = 0; n < usb_cfg_data_buf[i]->num_endpoints; n++) { LOG_INF("set cb, ep: 0x%x\n", ep_data[n].ep_addr); if (usb_dc_ep_set_callback(ep_data[n].ep_addr, ep_data[n].ep_cb)) { return -1; } } } return 0; } /* * API: initialize USB composite device */ int usb_device_composite_init(struct device *dev) { int ret; struct usb_dc_ep_cfg_data ep0_cfg; if (true == usb_dev.enabled) { return 0; } /* register composite device descriptors */ usb_dev.descriptors = usb_dev.fs_descriptors; /* register standard request handler */ usb_register_request_handler(REQTYPE_TYPE_STANDARD, usb_handle_standard_request); /* register class request handlers for each interface*/ usb_register_request_handler(REQTYPE_TYPE_CLASS, class_handler); /* register vendor request handlers */ usb_register_request_handler(REQTYPE_TYPE_VENDOR, vendor_handler); /* register class request handlers for each interface*/ usb_register_custom_req_handler(custom_handler); usb_register_status_callback(forward_status_cb); ret = usb_dc_set_status_callback(forward_status_cb); if (ret < 0) { return ret; } /* Enable VBUS if needed */ ret = usb_vbus_set(true); if (ret < 0) { return ret; } ret = usb_dc_attach(); if (ret < 0) { return ret; } /* Configure control EP */ ep0_cfg.ep_mps = MAX_PACKET_SIZE0; ep0_cfg.ep_type = USB_DC_EP_CONTROL; ep0_cfg.ep_addr = USB_CONTROL_OUT_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) { return ret; } ep0_cfg.ep_addr = USB_CONTROL_IN_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) { return ret; } /*register endpoint 0 handlers*/ ret = usb_dc_ep_set_callback(USB_CONTROL_OUT_EP0, usb_handle_control_transfer); if (ret < 0) return ret; ret = usb_dc_ep_set_callback(USB_CONTROL_IN_EP0, usb_handle_control_transfer); if (ret < 0) return ret; if (composite_setup_ep_cb()) { return -1; } #ifdef CONFIG_USB_DEVICE_TRANSFER /* init transfer slots */ for (int i = 0; i < MAX_NUM_TRANSFERS; i++) { k_work_init(&usb_dev.transfer[i].work, usb_transfer_work); k_sem_init(&usb_dev.transfer[i].sem, 1, 1); } #endif /* enable control EP */ ret = usb_dc_ep_enable(USB_CONTROL_OUT_EP0); if (ret < 0) { return ret; } ret = usb_dc_ep_enable(USB_CONTROL_IN_EP0); if (ret < 0) { return ret; } usb_dev.unconfigured = 0; usb_dev.enabled = true; return 0; } /* * API: deinitialize USB composite device */ int usb_device_composite_exit(void) { int ret; ret = usb_disable(); if (ret) { LOG_ERR("Failed to disable USB: %d\n", ret); return ret; } usb_deconfig(); LOG_INF("done\n"); return ret; } enum usb_device_speed usb_device_speed(void) { return usb_dc_speed(); } bool usb_device_unconfigured(void) { return usb_dev.unconfigured == 1; }