/* * 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 packetised 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 "usb_device.h" #define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_LEVEL #define SYS_LOG_NO_NEWLINE #include #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_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 /* Default USB control EP, always 0 and 0x80 */ #define USB_CONTROL_OUT_EP0 0 #define USB_CONTROL_IN_EP0 0x80 static struct usb_dev_priv { /** Setup packet */ struct usb_setup_packet setup; /** Pointer to data buffer */ uint8_t *data_buf; /** Eemaining bytes in buffer */ int32_t data_buf_residue; /** Total length of control transfer */ int32_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 uint8_t *descriptors; /** Array of installed request handler callbacks */ usb_request_handler req_handlers[MAX_NUM_REQ_HANDLERS]; /** Array of installed request data pointers */ uint8_t *data_store[MAX_NUM_REQ_HANDLERS]; /* Buffer used for storing standard usb request data */ uint8_t std_req_data[MAX_STD_REQ_MSG_SIZE]; /** Variable to check whether the usb has been enabled */ bool enabled; /** Currently selected configuration */ uint8_t configuration; } 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 SYS_LOG_DBG is not defined */ setup = setup; SYS_LOG_DBG("SETUP\n"); SYS_LOG_DBG("%x %x %x %x %x\n", setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex, 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, int32_t *len, uint8_t **data) { uint32_t type = REQTYPE_GET_TYPE(setup->bmRequestType); usb_request_handler handler = usb_dev.req_handlers[type]; SYS_LOG_DBG("** %d **\n", type); if (type >= MAX_NUM_REQ_HANDLERS) { SYS_LOG_DBG("Error Incorrect iType %d\n", type); return false; } if (handler == NULL) { SYS_LOG_DBG("No handler for reqtype %d\n", type); return false; } if ((*handler)(setup, len, data) < 0) { SYS_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) { uint32_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(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status) { uint32_t chunk = 0; uint32_t type = 0; struct usb_setup_packet *setup = &usb_dev.setup; SYS_LOG_DBG("usb_handle_control_transfer ep %x, status %x\n", ep, ep_status); if (ep == USB_CONTROL_OUT_EP0 && ep_status == USB_DC_EP_SETUP) { /* * OUT transfer, Setup packet, * reset request message state machine */ if (usb_dc_ep_read(ep, (uint8_t *)setup, sizeof(*setup), NULL) < 0) { SYS_LOG_DBG("Read Setup Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } /* Defaults for data pointer and residue */ type = REQTYPE_GET_TYPE(setup->bmRequestType); usb_dev.data_buf = usb_dev.data_store[type]; usb_dev.data_buf_residue = setup->wLength; usb_dev.data_buf_len = setup->wLength; if (!(setup->wLength == 0) && !(REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST)) { return; } /* Ask installed handler to process request */ if (!usb_handle_request(setup, &usb_dev.data_buf_len, &usb_dev.data_buf)) { SYS_LOG_DBG("usb_handle_request failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } /* Send smallest of requested and offered length */ usb_dev.data_buf_residue = min(usb_dev.data_buf_len, setup->wLength); /* 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) { SYS_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) { SYS_LOG_DBG("Read DATA Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_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 */ type = REQTYPE_GET_TYPE(setup->bmRequestType); usb_dev.data_buf = usb_dev.data_store[type]; if (!usb_handle_request(setup, &usb_dev.data_buf_len, &usb_dev.data_buf)) { SYS_LOG_DBG("usb_handle_request1 failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } /*Send status to host*/ SYS_LOG_DBG(">> usb_data_to_host(2)\n"); usb_data_to_host(); } } else if (ep == USB_CONTROL_IN_EP0) { /* 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 * @param [in] data_store Data storage area for this type of request * * @return N/A */ static void usb_register_request_handler(int32_t type, usb_request_handler handler, uint8_t *data_store) { usb_dev.req_handlers[type] = handler; usb_dev.data_store[type] = data_store; } /* * @brief register a pointer to a descriptor block * * This function registers a pointer to a descriptor block containing all * descriptors for the device. * * @param [in] usb_descriptors The descriptor byte array */ static void usb_register_descriptors(const uint8_t *usb_descriptors) { usb_dev.descriptors = usb_descriptors; } /* * @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(uint16_t type_index, uint16_t lang_id, int32_t *len, uint8_t **data) { uint8_t type = 0; uint8_t index = 0; uint8_t *p = NULL; int32_t cur_index = 0; bool found = false; __ASSERT_NO_MSG(usb_descriptors != NULL); /*Avoid compiler warning until this is used for something*/ lang_id = lang_id; type = GET_DESC_TYPE(type_index); index = GET_DESC_INDEX(type_index); p = (uint8_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 */ SYS_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(uint8_t config_index, uint8_t alt_setting) { uint8_t *p = NULL; uint8_t cur_config = 0; uint8_t cur_alt_setting = 0; __ASSERT_NO_MSG(usb_descriptors != NULL); if (config_index == 0) { /* unconfigure device */ SYS_LOG_DBG("Device not configured - invalid configuration " "offset\n"); return true; } /* configure endpoints for this configuration/altsetting */ p = (uint8_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]; } if (usb_dev.status_callback) usb_dev.status_callback(USB_DC_CONFIGURED); 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, int32_t *len, uint8_t **data_buf) { bool ret = true; uint8_t *data = *data_buf; switch (setup->bRequest) { case REQ_GET_STATUS: SYS_LOG_DBG("REQ_GET_STATUS\n"); /* bit 0: self-powered */ /* bit 1: remote wakeup = not supported */ data[0] = 0; data[1] = 0; *len = 2; break; case REQ_SET_ADDRESS: SYS_LOG_DBG("REQ_SET_ADDRESS\n"); usb_dc_set_address(setup->wValue); break; case REQ_GET_DESCRIPTOR: SYS_LOG_DBG("REQ_GET_DESCRIPTOR\n"); ret = usb_get_descriptor(setup->wValue, setup->wIndex, len, data_buf); break; case REQ_GET_CONFIGURATION: SYS_LOG_DBG("REQ_GET_CONFIGURATION\n"); /* indicate if we are configured */ data[0] = usb_dev.configuration; *len = 1; break; case REQ_SET_CONFIGURATION: SYS_LOG_DBG("REQ_SET_CONFIGURATION\n"); if (!usb_set_configuration(setup->wValue & 0xFF, 0)) { SYS_LOG_DBG("USBSetConfiguration failed!\n"); ret = false; } else { /* configuration successful, * update current configuration */ usb_dev.configuration = setup->wValue & 0xFF; } break; case REQ_CLEAR_FEATURE: SYS_LOG_DBG("REQ_CLEAR_FEATURE\n"); break; case REQ_SET_FEATURE: SYS_LOG_DBG("REQ_SET_FEATURE\n"); if (setup->wValue == FEA_REMOTE_WAKEUP) { /* put DEVICE_REMOTE_WAKEUP code here */ } if (setup->wValue == FEA_TEST_MODE) { /* put TEST_MODE code here */ } ret = false; break; case REQ_SET_DESCRIPTOR: SYS_LOG_DBG("Device req %x not implemented\n", setup->bRequest); ret = false; break; default: SYS_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, int32_t *len, uint8_t **data_buf) { uint8_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: /* there is only one interface, return n-1 (= 0) */ data[0] = 0; *len = 1; break; case REQ_SET_INTERFACE: SYS_LOG_DBG("REQ_SET_INTERFACE\n"); *len = 0; break; default: SYS_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, int32_t *len, uint8_t **data_buf) { uint8_t *data = *data_buf; switch (setup->bRequest) { case REQ_GET_STATUS: /* bit 0 = endpointed halted or not */ usb_dc_ep_is_stalled(setup->wIndex, &data[0]); data[1] = 0; *len = 2; break; case REQ_CLEAR_FEATURE: if (setup->wValue == FEA_ENDPOINT_HALT) { /* clear HALT by unstalling */ usb_dc_ep_clear_stall(setup->wIndex); break; } /* only ENDPOINT_HALT defined for endpoints */ return false; case REQ_SET_FEATURE: if (setup->wValue == FEA_ENDPOINT_HALT) { /* set HALT by stalling */ usb_dc_ep_set_stall(setup->wIndex); break; } /* only ENDPOINT_HALT defined for endpoints */ return false; case REQ_SYNCH_FRAME: SYS_LOG_DBG("EP req %d not implemented\n", setup->bRequest); return false; default: SYS_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, int32_t *len, uint8_t **data_buf) { int rc = 0; /* try the custom request handler first */ if ((usb_dev.custom_req_handler != NULL) && (!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) { SYS_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; } int usb_set_config(struct usb_cfg_data *config) { if (!config) return -EINVAL; /* register descriptors */ usb_register_descriptors(config->usb_device_description); /* register standard request handler */ usb_register_request_handler(REQTYPE_TYPE_STANDARD, &(usb_handle_standard_request), usb_dev.std_req_data); /* register class request handlers for each interface*/ if (config->interface.class_handler != NULL) { usb_register_request_handler(REQTYPE_TYPE_CLASS, config->interface.class_handler, config->interface.payload_data); } /* register class request handlers for each interface*/ if (config->interface.custom_handler != NULL) { usb_register_custom_req_handler( config->interface.custom_handler); } /* register status callback */ if (config->cb_usb_status != NULL) { usb_register_status_callback(config->cb_usb_status); } return 0; } int usb_deconfig(void) { /* unregister descriptors */ usb_register_descriptors(NULL); /* unegister standard request handler */ usb_register_request_handler(REQTYPE_TYPE_STANDARD, NULL, NULL); /* unregister class request handlers for each interface*/ usb_register_request_handler(REQTYPE_TYPE_CLASS, NULL, 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(struct usb_cfg_data *config) { int ret; uint32_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; ret = usb_dc_set_status_callback(config->cb_usb_status); 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; } /* 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.enabled = true; return 0; } int usb_disable(void) { int ret; if (true != usb_dev.enabled) { /*Already disabled*/ return 0; } 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(uint8_t ep, const uint8_t *data, uint32_t data_len, uint32_t *bytes_ret) { return usb_dc_ep_write(ep, data, data_len, bytes_ret); } int usb_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *ret_bytes) { return usb_dc_ep_read(ep, data, max_data_len, ret_bytes); }