| /* |
| * LPCUSB, an USB device driver for LPC microcontrollers |
| * Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl) |
| * Copyright (c) 2016 Intel Corporation |
| * Copyright (c) 2020 PHYTEC Messtechnik GmbH |
| * |
| * 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 <errno.h> |
| #include <stddef.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/usb/usb_device.h> |
| #include <usb_descriptor.h> |
| #include <zephyr/usb/class/usb_audio.h> |
| |
| #define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usb_device); |
| |
| #include <zephyr/usb/bos.h> |
| #include <os_desc.h> |
| #include "usb_transfer.h" |
| |
| #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 2U /** Endpoint address offset */ |
| #define ENDP_DESC_bmAttributes 3U /** Bulk or interrupt? */ |
| #define ENDP_DESC_wMaxPacketSize 4U /** Maximum packet size offset */ |
| |
| #define MAX_NUM_REQ_HANDLERS 4U |
| #define MAX_STD_REQ_MSG_SIZE 8U |
| |
| K_MUTEX_DEFINE(usb_enable_lock); |
| |
| static struct usb_dev_priv { |
| /** Setup packet */ |
| struct usb_setup_packet setup; |
| /** Pointer to data buffer */ |
| uint8_t *data_buf; |
| /** Remaining bytes in buffer */ |
| int32_t data_buf_residue; |
| /** Total length of control transfer */ |
| int32_t data_buf_len; |
| /** Zero length packet flag of control transfer */ |
| bool zlp_flag; |
| /** Installed custom request handler */ |
| usb_request_handler custom_req_handler; |
| /** USB stack status callback */ |
| usb_dc_status_callback status_callback; |
| /** USB user status callback */ |
| usb_dc_status_callback user_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]; |
| /* Buffer used for storing standard, class and vendor request data */ |
| uint8_t req_data[CONFIG_USB_REQUEST_BUFFER_SIZE]; |
| |
| /** Variable to check whether the usb has been enabled */ |
| bool enabled; |
| /** Variable to check whether the usb has been configured */ |
| bool configured; |
| /** Currently selected configuration */ |
| uint8_t configuration; |
| /** Currently selected alternate setting */ |
| uint8_t alt_setting[CONFIG_USB_MAX_ALT_SETTING]; |
| /** Remote wakeup feature status */ |
| bool remote_wakeup; |
| } usb_dev; |
| |
| /* Setup packet definition used to read raw data from USB line */ |
| struct usb_setup_packet_packed { |
| uint8_t bmRequestType; |
| uint8_t bRequest; |
| uint16_t wValue; |
| uint16_t wIndex; |
| uint16_t wLength; |
| } __packed; |
| |
| /* |
| * @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: " |
| "bmRT 0x%02x, bR 0x%02x, wV 0x%04x, wI 0x%04x, wL 0x%04x", |
| setup->bmRequestType, |
| setup->bRequest, |
| setup->wValue, |
| setup->wIndex, |
| setup->wLength); |
| } |
| |
| static void usb_reset_alt_setting(void) |
| { |
| memset(usb_dev.alt_setting, 0, ARRAY_SIZE(usb_dev.alt_setting)); |
| } |
| |
| static bool usb_set_alt_setting(uint8_t iface, uint8_t alt_setting) |
| { |
| if (iface < ARRAY_SIZE(usb_dev.alt_setting)) { |
| usb_dev.alt_setting[iface] = alt_setting; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static uint8_t usb_get_alt_setting(uint8_t iface) |
| { |
| if (iface < ARRAY_SIZE(usb_dev.alt_setting)) { |
| return usb_dev.alt_setting[iface]; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * @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 = setup->RequestType.type; |
| usb_request_handler handler; |
| |
| if (type >= MAX_NUM_REQ_HANDLERS) { |
| LOG_DBG("Error Incorrect iType %d", type); |
| return false; |
| } |
| |
| handler = usb_dev.req_handlers[type]; |
| if (handler == NULL) { |
| LOG_DBG("No handler for reqtype %d", type); |
| return false; |
| } |
| |
| if ((*handler)(setup, len, data) < 0) { |
| LOG_DBG("Handler Error %d", type); |
| usb_print_setup(setup); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * @brief send next chunk of data (possibly 0 bytes) to host |
| */ |
| static void usb_data_to_host(void) |
| { |
| if (usb_dev.zlp_flag == false) { |
| uint32_t chunk = usb_dev.data_buf_residue; |
| |
| /*Always EP0 for control*/ |
| usb_write(USB_CONTROL_EP_IN, usb_dev.data_buf, |
| usb_dev.data_buf_residue, &chunk); |
| usb_dev.data_buf += chunk; |
| usb_dev.data_buf_residue -= chunk; |
| |
| /* |
| * Set ZLP flag when host asks for a bigger length and the |
| * last chunk is wMaxPacketSize long, to indicate the last |
| * packet. |
| */ |
| if (!usb_dev.data_buf_residue && chunk && |
| usb_dev.setup.wLength > usb_dev.data_buf_len) { |
| /* Send less data as requested during the Setup stage */ |
| if (!(usb_dev.data_buf_len % USB_MAX_CTRL_MPS)) { |
| /* Transfers a zero-length packet */ |
| LOG_DBG("ZLP, requested %u , length %u ", |
| usb_dev.setup.wLength, |
| usb_dev.data_buf_len); |
| usb_dev.zlp_flag = true; |
| } |
| } |
| |
| } else { |
| usb_dev.zlp_flag = false; |
| usb_dc_ep_write(USB_CONTROL_EP_IN, NULL, 0, NULL); |
| } |
| } |
| |
| /* |
| * @brief handle IN/OUT transfers on EP0 |
| * |
| * @param [in] ep Endpoint address |
| * @param [in] ep_status Endpoint status |
| */ |
| static void usb_handle_control_transfer(uint8_t ep, |
| enum usb_dc_ep_cb_status_code ep_status) |
| { |
| uint32_t chunk = 0U; |
| struct usb_setup_packet *setup = &usb_dev.setup; |
| struct usb_setup_packet_packed setup_raw; |
| |
| LOG_DBG("ep 0x%02x, status 0x%02x", ep, ep_status); |
| |
| if (ep == USB_CONTROL_EP_OUT && ep_status == USB_DC_EP_SETUP) { |
| /* |
| * OUT transfer, Setup packet, |
| * reset request message state machine |
| */ |
| if (usb_dc_ep_read(ep, (uint8_t *)&setup_raw, |
| sizeof(setup_raw), NULL) < 0) { |
| LOG_DBG("Read Setup Packet failed"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| return; |
| } |
| |
| /* Take care of endianness */ |
| setup->bmRequestType = setup_raw.bmRequestType; |
| setup->bRequest = setup_raw.bRequest; |
| setup->wValue = sys_le16_to_cpu(setup_raw.wValue); |
| setup->wIndex = sys_le16_to_cpu(setup_raw.wIndex); |
| setup->wLength = sys_le16_to_cpu(setup_raw.wLength); |
| |
| usb_dev.data_buf = usb_dev.req_data; |
| usb_dev.zlp_flag = false; |
| /* |
| * Set length to 0 as a precaution so that no trouble |
| * happens if control request handler does not check the |
| * request values sufficiently. |
| */ |
| usb_dev.data_buf_len = 0; |
| usb_dev.data_buf_residue = 0; |
| |
| if (usb_reqtype_is_to_device(setup)) { |
| if (setup->wLength > CONFIG_USB_REQUEST_BUFFER_SIZE) { |
| LOG_ERR("Request buffer too small"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_OUT); |
| return; |
| } |
| |
| if (setup->wLength) { |
| /* Continue with data OUT stage */ |
| usb_dev.data_buf_len = setup->wLength; |
| usb_dev.data_buf_residue = setup->wLength; |
| 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"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| 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_EP_OUT) { |
| /* 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_EP_OUT, |
| usb_dev.data_buf, 0, &chunk) < 0) { |
| LOG_DBG("Read DATA Packet failed"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| } |
| return; |
| } |
| |
| if (usb_dc_ep_read(USB_CONTROL_EP_OUT, |
| usb_dev.data_buf, |
| usb_dev.data_buf_residue, &chunk) < 0) { |
| LOG_DBG("Read DATA Packet failed"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_OUT); |
| 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"); |
| usb_dc_ep_set_stall(USB_CONTROL_EP_IN); |
| return; |
| } |
| |
| /*Send status to host*/ |
| LOG_DBG(">> usb_data_to_host(2)"); |
| usb_data_to_host(); |
| } |
| } else if (ep == USB_CONTROL_EP_IN) { |
| /* Send more data if available */ |
| if (usb_dev.data_buf_residue != 0 || usb_dev.zlp_flag == true) { |
| usb_data_to_host(); |
| } |
| } else { |
| __ASSERT_NO_MSG(false); |
| } |
| } |
| |
| /* |
| * @brief register a callback for handling requests |
| * |
| * @param [in] type Type of request, e.g. USB_REQTYPE_TYPE_STANDARD |
| * @param [in] handler Callback function pointer |
| */ |
| static void usb_register_request_handler(int32_t type, |
| usb_request_handler handler) |
| { |
| usb_dev.req_handlers[type] = handler; |
| } |
| |
| /* |
| * @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; |
| } |
| |
| static bool usb_get_status(struct usb_setup_packet *setup, |
| int32_t *len, uint8_t **data_buf) |
| { |
| uint8_t *data = *data_buf; |
| |
| LOG_DBG("Get Status request"); |
| data[0] = 0U; |
| data[1] = 0U; |
| |
| if (IS_ENABLED(CONFIG_USB_SELF_POWERED)) { |
| data[0] |= USB_GET_STATUS_SELF_POWERED; |
| } |
| |
| if (IS_ENABLED(CONFIG_USB_DEVICE_REMOTE_WAKEUP)) { |
| data[0] |= (usb_dev.remote_wakeup ? |
| USB_GET_STATUS_REMOTE_WAKEUP : 0); |
| } |
| |
| *len = 2; |
| |
| return true; |
| } |
| |
| /* |
| * @brief get specified USB descriptor |
| * |
| * This function parses the list of installed USB descriptors and attempts |
| * to find the specified USB descriptor. |
| * |
| * @param [in] setup The setup packet |
| * @param [out] len Descriptor length |
| * @param [out] data Descriptor data |
| * |
| * @return true if the descriptor was found, false otherwise |
| */ |
| static bool usb_get_descriptor(struct usb_setup_packet *setup, |
| int32_t *len, uint8_t **data) |
| { |
| uint8_t type = 0U; |
| uint8_t index = 0U; |
| uint8_t *p = NULL; |
| uint32_t cur_index = 0U; |
| bool found = false; |
| |
| LOG_DBG("Get Descriptor request"); |
| type = USB_GET_DESCRIPTOR_TYPE(setup->wValue); |
| index = USB_GET_DESCRIPTOR_INDEX(setup->wValue); |
| |
| /* |
| * Invalid types of descriptors, |
| * see USB Spec. Revision 2.0, 9.4.3 Get Descriptor |
| */ |
| if ((type == USB_DESC_INTERFACE) || (type == USB_DESC_ENDPOINT) || |
| (type > USB_DESC_OTHER_SPEED)) { |
| return false; |
| } |
| |
| p = (uint8_t *)usb_dev.descriptors; |
| cur_index = 0U; |
| |
| while (p[DESC_bLength] != 0U) { |
| 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 == USB_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!", setup->wValue); |
| } |
| return found; |
| } |
| |
| /* |
| * @brief configure and enable endpoint |
| * |
| * This function sets endpoint configuration according to one specified in USB |
| * endpoint descriptor and then enables it for data transfers. |
| * |
| * @param [in] ep_desc Endpoint descriptor byte array |
| * |
| * @return true if successfully configured and enabled |
| */ |
| static bool set_endpoint(const struct usb_ep_descriptor *ep_desc) |
| { |
| struct usb_dc_ep_cfg_data ep_cfg; |
| int ret; |
| |
| ep_cfg.ep_addr = ep_desc->bEndpointAddress; |
| ep_cfg.ep_mps = sys_le16_to_cpu(ep_desc->wMaxPacketSize); |
| ep_cfg.ep_type = ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK; |
| |
| LOG_DBG("Set endpoint 0x%x type %u MPS %u", |
| ep_cfg.ep_addr, ep_cfg.ep_type, ep_cfg.ep_mps); |
| |
| ret = usb_dc_ep_configure(&ep_cfg); |
| if (ret == -EALREADY) { |
| LOG_WRN("Endpoint 0x%02x already configured", ep_cfg.ep_addr); |
| } else if (ret) { |
| LOG_ERR("Failed to configure endpoint 0x%02x", ep_cfg.ep_addr); |
| return false; |
| } else { |
| ; |
| } |
| |
| ret = usb_dc_ep_enable(ep_cfg.ep_addr); |
| if (ret == -EALREADY) { |
| LOG_WRN("Endpoint 0x%02x already enabled", ep_cfg.ep_addr); |
| } else if (ret) { |
| LOG_ERR("Failed to enable endpoint 0x%02x", ep_cfg.ep_addr); |
| return false; |
| } else { |
| ; |
| } |
| |
| usb_dev.configured = true; |
| |
| return true; |
| } |
| |
| /* |
| * @brief Disable endpoint for transferring data |
| * |
| * This function cancels transfers that are associated with endpoint and |
| * disabled endpoint itself. |
| * |
| * @param [in] ep_desc Endpoint descriptor byte array |
| * |
| * @return true if successfully deconfigured and disabled |
| */ |
| static bool reset_endpoint(const struct usb_ep_descriptor *ep_desc) |
| { |
| struct usb_dc_ep_cfg_data ep_cfg; |
| int ret; |
| |
| ep_cfg.ep_addr = ep_desc->bEndpointAddress; |
| ep_cfg.ep_type = ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK; |
| |
| LOG_DBG("Reset endpoint 0x%02x type %u", |
| ep_cfg.ep_addr, ep_cfg.ep_type); |
| |
| usb_cancel_transfer(ep_cfg.ep_addr); |
| |
| ret = usb_dc_ep_disable(ep_cfg.ep_addr); |
| if (ret == -EALREADY) { |
| LOG_WRN("Endpoint 0x%02x already disabled", ep_cfg.ep_addr); |
| } else if (ret) { |
| LOG_ERR("Failed to disable endpoint 0x%02x", ep_cfg.ep_addr); |
| return false; |
| } else { |
| ; |
| } |
| |
| return true; |
| } |
| |
| static bool usb_eps_reconfigure(struct usb_ep_descriptor *ep_desc, |
| uint8_t cur_alt_setting, |
| uint8_t alt_setting) |
| { |
| bool ret; |
| |
| if (cur_alt_setting != alt_setting) { |
| LOG_DBG("Disable endpoint 0x%02x", ep_desc->bEndpointAddress); |
| ret = reset_endpoint(ep_desc); |
| } else { |
| LOG_DBG("Enable endpoint 0x%02x", ep_desc->bEndpointAddress); |
| ret = set_endpoint(ep_desc); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * @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] setup The setup packet |
| * |
| * @return true if successfully configured false if error or unconfigured |
| */ |
| static bool usb_set_configuration(struct usb_setup_packet *setup) |
| { |
| uint8_t *p = (uint8_t *)usb_dev.descriptors; |
| uint8_t cur_alt_setting = 0xFF; |
| uint8_t cur_config = 0xFF; |
| bool found = false; |
| |
| LOG_DBG("Set Configuration %u request", setup->wValue); |
| |
| if (setup->wValue == 0U) { |
| usb_reset_alt_setting(); |
| usb_dev.configuration = setup->wValue; |
| if (usb_dev.status_callback) { |
| usb_dev.status_callback(USB_DC_CONFIGURED, |
| &usb_dev.configuration); |
| } |
| |
| return true; |
| } |
| |
| /* configure endpoints for this configuration/altsetting */ |
| while (p[DESC_bLength] != 0U) { |
| switch (p[DESC_bDescriptorType]) { |
| case USB_DESC_CONFIGURATION: |
| /* remember current configuration index */ |
| cur_config = p[CONF_DESC_bConfigurationValue]; |
| if (cur_config == setup->wValue) { |
| found = true; |
| } |
| |
| break; |
| |
| case USB_DESC_INTERFACE: |
| /* remember current alternate setting */ |
| cur_alt_setting = |
| p[INTF_DESC_bAlternateSetting]; |
| break; |
| |
| case USB_DESC_ENDPOINT: |
| if ((cur_config != setup->wValue) || |
| (cur_alt_setting != 0)) { |
| break; |
| } |
| |
| found = set_endpoint((struct usb_ep_descriptor *)p); |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* skip to next descriptor */ |
| p += p[DESC_bLength]; |
| } |
| |
| if (found) { |
| usb_reset_alt_setting(); |
| usb_dev.configuration = setup->wValue; |
| if (usb_dev.status_callback) { |
| usb_dev.status_callback(USB_DC_CONFIGURED, |
| &usb_dev.configuration); |
| } |
| } else { |
| LOG_DBG("Set Configuration %u failed", setup->wValue); |
| } |
| |
| return found; |
| } |
| |
| /* |
| * @brief set USB interface |
| * |
| * @param [in] setup The setup packet |
| * |
| * @return true if successfully configured false if error or unconfigured |
| */ |
| static bool usb_set_interface(struct usb_setup_packet *setup) |
| { |
| const uint8_t *p = usb_dev.descriptors; |
| const uint8_t *if_desc = NULL; |
| struct usb_ep_descriptor *ep; |
| uint8_t cur_alt_setting = 0xFF; |
| uint8_t cur_iface = 0xFF; |
| bool ret = false; |
| |
| LOG_DBG("Set Interface %u alternate %u", setup->wIndex, setup->wValue); |
| |
| while (p[DESC_bLength] != 0U) { |
| switch (p[DESC_bDescriptorType]) { |
| case USB_DESC_INTERFACE: |
| /* remember current alternate setting */ |
| cur_alt_setting = p[INTF_DESC_bAlternateSetting]; |
| cur_iface = p[INTF_DESC_bInterfaceNumber]; |
| |
| if (cur_iface == setup->wIndex && |
| cur_alt_setting == setup->wValue) { |
| ret = usb_set_alt_setting(setup->wIndex, |
| setup->wValue); |
| if_desc = (void *)p; |
| } |
| |
| LOG_DBG("Current iface %u alt setting %u", |
| cur_iface, cur_alt_setting); |
| break; |
| case USB_DESC_ENDPOINT: |
| if (cur_iface == setup->wIndex) { |
| ep = (struct usb_ep_descriptor *)p; |
| ret = usb_eps_reconfigure(ep, cur_alt_setting, |
| setup->wValue); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| /* skip to next descriptor */ |
| p += p[DESC_bLength]; |
| } |
| |
| if (usb_dev.status_callback) { |
| usb_dev.status_callback(USB_DC_INTERFACE, if_desc); |
| } |
| |
| return ret; |
| } |
| |
| static bool usb_get_interface(struct usb_setup_packet *setup, |
| int32_t *len, uint8_t **data_buf) |
| { |
| const uint8_t *p = usb_dev.descriptors; |
| uint8_t *data = *data_buf; |
| uint8_t cur_iface; |
| |
| while (p[DESC_bLength] != 0U) { |
| if (p[DESC_bDescriptorType] == USB_DESC_INTERFACE) { |
| cur_iface = p[INTF_DESC_bInterfaceNumber]; |
| if (cur_iface == setup->wIndex) { |
| data[0] = usb_get_alt_setting(cur_iface); |
| LOG_DBG("Current iface %u alt setting %u", |
| setup->wIndex, data[0]); |
| *len = 1; |
| return true; |
| } |
| } |
| |
| /* skip to next descriptor */ |
| p += p[DESC_bLength]; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @brief Check if the device is in Configured state |
| * |
| * @return true if Configured, false otherwise. |
| */ |
| static bool is_device_configured(void) |
| { |
| return (usb_dev.configuration != 0); |
| } |
| |
| /* |
| * @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) |
| { |
| uint8_t *data = *data_buf; |
| |
| if (usb_reqtype_is_to_host(setup)) { |
| switch (setup->bRequest) { |
| case USB_SREQ_GET_STATUS: |
| return usb_get_status(setup, len, data_buf); |
| |
| case USB_SREQ_GET_DESCRIPTOR: |
| return usb_get_descriptor(setup, len, data_buf); |
| |
| case USB_SREQ_GET_CONFIGURATION: |
| LOG_DBG("Get Configuration request"); |
| /* indicate if we are configured */ |
| data[0] = usb_dev.configuration; |
| *len = 1; |
| return true; |
| default: |
| break; |
| } |
| } else { |
| switch (setup->bRequest) { |
| case USB_SREQ_SET_ADDRESS: |
| LOG_DBG("Set Address %u request", setup->wValue); |
| return !usb_dc_set_address(setup->wValue); |
| |
| case USB_SREQ_SET_CONFIGURATION: |
| return usb_set_configuration(setup); |
| |
| case USB_SREQ_CLEAR_FEATURE: |
| LOG_DBG("Clear Feature request"); |
| |
| if (IS_ENABLED(CONFIG_USB_DEVICE_REMOTE_WAKEUP)) { |
| if (setup->wValue == USB_SFS_REMOTE_WAKEUP) { |
| usb_dev.remote_wakeup = false; |
| return true; |
| } |
| } |
| break; |
| |
| case USB_SREQ_SET_FEATURE: |
| LOG_DBG("Set Feature request"); |
| |
| if (IS_ENABLED(CONFIG_USB_DEVICE_REMOTE_WAKEUP)) { |
| if (setup->wValue == USB_SFS_REMOTE_WAKEUP) { |
| usb_dev.remote_wakeup = true; |
| return true; |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| LOG_DBG("Unsupported bmRequestType 0x%02x bRequest 0x%02x", |
| setup->bmRequestType, setup->bRequest); |
| return false; |
| } |
| |
| /** |
| * @brief Check if the interface of given number is valid |
| * |
| * @param [in] interface Number of the addressed interface |
| * |
| * This function searches through descriptor and checks |
| * is the Host has addressed valid interface. |
| * |
| * @return true if interface exists - valid |
| */ |
| static bool is_interface_valid(uint8_t interface) |
| { |
| const uint8_t *p = (uint8_t *)usb_dev.descriptors; |
| const struct usb_cfg_descriptor *cfg_descr; |
| |
| /* Search through descriptor for matching interface */ |
| while (p[DESC_bLength] != 0U) { |
| if (p[DESC_bDescriptorType] == USB_DESC_CONFIGURATION) { |
| cfg_descr = (const struct usb_cfg_descriptor *)p; |
| if (interface < cfg_descr->bNumInterfaces) { |
| return true; |
| } |
| } |
| p += p[DESC_bLength]; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * @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; |
| |
| /** The device must be configured to accept standard interface |
| * requests and the addressed Interface must be valid. |
| */ |
| if (!is_device_configured() || |
| (!is_interface_valid((uint8_t)setup->wIndex))) { |
| return false; |
| } |
| |
| if (usb_reqtype_is_to_host(setup)) { |
| switch (setup->bRequest) { |
| case USB_SREQ_GET_STATUS: |
| /* no bits specified */ |
| data[0] = 0U; |
| data[1] = 0U; |
| *len = 2; |
| return true; |
| |
| case USB_SREQ_GET_INTERFACE: |
| return usb_get_interface(setup, len, data_buf); |
| |
| default: |
| break; |
| } |
| } else { |
| if (setup->bRequest == USB_SREQ_SET_INTERFACE) { |
| return usb_set_interface(setup); |
| } |
| |
| } |
| |
| LOG_DBG("Unsupported bmRequestType 0x%02x bRequest 0x%02x", |
| setup->bmRequestType, setup->bRequest); |
| return false; |
| } |
| |
| /** |
| * @brief Check if the endpoint of given address is valid |
| * |
| * @param [in] ep Address of the Endpoint |
| * |
| * This function checks if the Endpoint of given address |
| * is valid for the configured device. Valid Endpoint is |
| * either Control Endpoint or one used by the device. |
| * |
| * @return true if endpoint exists - valid |
| */ |
| static bool is_ep_valid(uint8_t ep) |
| { |
| const struct usb_ep_cfg_data *ep_data; |
| |
| /* Check if its Endpoint 0 */ |
| if (USB_EP_GET_IDX(ep) == 0) { |
| return true; |
| } |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| ep_data = cfg_data->endpoint; |
| |
| for (uint8_t n = 0; n < cfg_data->num_endpoints; n++) { |
| if (ep_data[n].ep_addr == ep) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool usb_get_status_endpoint(struct usb_setup_packet *setup, |
| int32_t *len, uint8_t **data_buf) |
| { |
| uint8_t ep = setup->wIndex; |
| uint8_t *data = *data_buf; |
| |
| /* Check if request addresses valid Endpoint */ |
| if (!is_ep_valid(ep)) { |
| return false; |
| } |
| |
| /* This request is valid for Control Endpoints when |
| * the device is not yet configured. For other |
| * Endpoints the device must be configured. |
| * Firstly check if addressed ep is Control Endpoint. |
| * If no then the device must be in Configured state |
| * to accept the request. |
| */ |
| if ((USB_EP_GET_IDX(ep) == 0) || is_device_configured()) { |
| /* bit 0 - Endpoint halted or not */ |
| usb_dc_ep_is_stalled(ep, &data[0]); |
| data[1] = 0U; |
| *len = 2; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| static bool usb_halt_endpoint_req(struct usb_setup_packet *setup, bool halt) |
| { |
| uint8_t ep = setup->wIndex; |
| |
| /* Check if request addresses valid Endpoint */ |
| if (!is_ep_valid(ep)) { |
| return false; |
| } |
| |
| /* This request is valid for Control Endpoints when |
| * the device is not yet configured. For other |
| * Endpoints the device must be configured. |
| * Firstly check if addressed ep is Control Endpoint. |
| * If no then the device must be in Configured state |
| * to accept the request. |
| */ |
| if ((USB_EP_GET_IDX(ep) == 0) || is_device_configured()) { |
| if (halt) { |
| LOG_INF("Set halt ep 0x%02x", ep); |
| usb_dc_ep_set_stall(ep); |
| if (usb_dev.status_callback) { |
| usb_dev.status_callback(USB_DC_SET_HALT, &ep); |
| } |
| } else { |
| LOG_INF("Clear halt ep 0x%02x", ep); |
| usb_dc_ep_clear_stall(ep); |
| if (usb_dev.status_callback) { |
| usb_dev.status_callback(USB_DC_CLEAR_HALT, &ep); |
| } |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * @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) |
| { |
| |
| if (usb_reqtype_is_to_host(setup)) { |
| if (setup->bRequest == USB_SREQ_GET_STATUS) { |
| return usb_get_status_endpoint(setup, len, data_buf); |
| } |
| } else { |
| switch (setup->bRequest) { |
| case USB_SREQ_CLEAR_FEATURE: |
| if (setup->wValue == USB_SFS_ENDPOINT_HALT) { |
| return usb_halt_endpoint_req(setup, false); |
| } |
| break; |
| case USB_SREQ_SET_FEATURE: |
| if (setup->wValue == USB_SFS_ENDPOINT_HALT) { |
| return usb_halt_endpoint_req(setup, true); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| LOG_DBG("Unsupported bmRequestType 0x%02x bRequest 0x%02x", |
| setup->bmRequestType, setup->bRequest); |
| return false; |
| } |
| |
| /* |
| * @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; |
| |
| if (!usb_handle_bos(setup, len, data_buf)) { |
| return 0; |
| } |
| |
| if (!usb_handle_os_desc(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 (setup->RequestType.recipient) { |
| case USB_REQTYPE_RECIPIENT_DEVICE: |
| if (usb_handle_std_device_req(setup, len, data_buf) == false) { |
| rc = -EINVAL; |
| } |
| break; |
| case USB_REQTYPE_RECIPIENT_INTERFACE: |
| if (usb_handle_std_interface_req(setup, len, data_buf) == false) { |
| rc = -EINVAL; |
| } |
| break; |
| case USB_REQTYPE_RECIPIENT_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_dc_status_callback cb) |
| { |
| usb_dev.status_callback = cb; |
| } |
| |
| static int foreach_ep(int (* endpoint_callback)(const struct usb_ep_cfg_data *)) |
| { |
| struct usb_ep_cfg_data *ep_data; |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| ep_data = cfg_data->endpoint; |
| |
| for (uint8_t n = 0; n < cfg_data->num_endpoints; n++) { |
| int ret; |
| |
| ret = endpoint_callback(&ep_data[n]); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int disable_interface_ep(const struct usb_ep_cfg_data *ep_data) |
| { |
| return usb_dc_ep_disable(ep_data->ep_addr); |
| } |
| |
| static void forward_status_cb(enum usb_dc_status_code status, const uint8_t *param) |
| { |
| if (status == USB_DC_DISCONNECTED) { |
| usb_reset_alt_setting(); |
| } |
| |
| if (status == USB_DC_DISCONNECTED || status == USB_DC_SUSPEND || status == USB_DC_RESET) { |
| if (usb_dev.configured) { |
| usb_cancel_transfers(); |
| if (status == USB_DC_DISCONNECTED || status == USB_DC_RESET) { |
| foreach_ep(disable_interface_ep); |
| usb_dev.configured = false; |
| } |
| } |
| } |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| if (cfg_data->cb_usb_status) { |
| cfg_data->cb_usb_status(cfg_data, status, param); |
| } |
| } |
| |
| if (usb_dev.user_status_callback) { |
| usb_dev.user_status_callback(status, param); |
| } |
| } |
| |
| /** |
| * @brief turn on/off USB VBUS voltage |
| * |
| * To utilize this in the devicetree the chosen node should have a |
| * zephyr,usb-device property that points to the usb device controller node. |
| * Additionally the usb device controller node should have a vbus-gpios |
| * property that has the GPIO details. |
| * |
| * Something like: |
| * |
| * chosen { |
| * zephyr,usb-device = &usbd; |
| * }; |
| * |
| * usbd: usbd { |
| * vbus-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; |
| * }; |
| * |
| * @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) |
| { |
| #define USB_DEV_NODE DT_CHOSEN(zephyr_usb_device) |
| #if DT_NODE_HAS_STATUS(USB_DEV_NODE, okay) && \ |
| DT_NODE_HAS_PROP(USB_DEV_NODE, vbus_gpios) |
| int ret = 0; |
| struct gpio_dt_spec gpio_dev = GPIO_DT_SPEC_GET(USB_DEV_NODE, vbus_gpios); |
| |
| if (!device_is_ready(gpio_dev.port)) { |
| LOG_DBG("USB requires GPIO. Device %s is not ready!", gpio_dev.port->name); |
| return -ENODEV; |
| } |
| |
| /* Enable USB IO */ |
| ret = gpio_pin_configure_dt(&gpio_dev, GPIO_OUTPUT); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = gpio_pin_set_dt(&gpio_dev, on == true ? 1 : 0); |
| if (ret) { |
| return ret; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| int usb_deconfig(void) |
| { |
| /* unregister descriptors */ |
| usb_register_descriptors(NULL); |
| |
| /* unregister standard request handler */ |
| usb_register_request_handler(USB_REQTYPE_TYPE_STANDARD, NULL); |
| |
| /* unregister class request handlers for each interface*/ |
| usb_register_request_handler(USB_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); |
| |
| /* unregister user status callback */ |
| usb_dev.user_status_callback = NULL; |
| |
| /* Reset USB controller */ |
| usb_dc_reset(); |
| |
| return 0; |
| } |
| |
| int usb_disable(void) |
| { |
| int ret; |
| |
| if (usb_dev.enabled != true) { |
| /*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) |
| { |
| int tries = CONFIG_USB_NUMOF_EP_WRITE_RETRIES; |
| int ret; |
| |
| do { |
| ret = usb_dc_ep_write(ep, data, data_len, bytes_ret); |
| if (ret == -EAGAIN) { |
| LOG_WRN("Failed to write endpoint buffer 0x%02x", ep); |
| k_yield(); |
| } |
| |
| } while (ret == -EAGAIN && tries--); |
| |
| return 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); |
| } |
| |
| int usb_ep_set_stall(uint8_t ep) |
| { |
| return usb_dc_ep_set_stall(ep); |
| } |
| |
| int usb_ep_clear_stall(uint8_t ep) |
| { |
| return usb_dc_ep_clear_stall(ep); |
| } |
| |
| int usb_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *ret_bytes) |
| { |
| return usb_dc_ep_read_wait(ep, data, max_data_len, ret_bytes); |
| } |
| |
| int usb_ep_read_continue(uint8_t ep) |
| { |
| return usb_dc_ep_read_continue(ep); |
| } |
| |
| bool usb_get_remote_wakeup_status(void) |
| { |
| return usb_dev.remote_wakeup; |
| } |
| |
| int usb_wakeup_request(void) |
| { |
| if (IS_ENABLED(CONFIG_USB_DEVICE_REMOTE_WAKEUP)) { |
| if (usb_get_remote_wakeup_status()) { |
| return usb_dc_wakeup_request(); |
| } |
| return -EACCES; |
| } else { |
| return -ENOTSUP; |
| } |
| } |
| |
| /* |
| * 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 through the usb_set_config() and |
| * usb_enable() is no longer needed. |
| */ |
| |
| static int class_handler(struct usb_setup_packet *pSetup, |
| int32_t *len, uint8_t **data) |
| { |
| const struct usb_if_descriptor *if_descr; |
| struct usb_interface_cfg_data *iface; |
| |
| LOG_DBG("bRequest 0x%02x, wIndex 0x%04x", |
| pSetup->bRequest, pSetup->wIndex); |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| iface = &cfg_data->interface; |
| if_descr = cfg_data->interface_descriptor; |
| /* |
| * Wind forward until it is within the range |
| * of the current descriptor. |
| */ |
| if ((uint8_t *)if_descr < usb_dev.descriptors) { |
| continue; |
| } |
| |
| if (iface->class_handler && |
| if_descr->bInterfaceNumber == (pSetup->wIndex & 0xFF)) { |
| return iface->class_handler(pSetup, len, data); |
| } |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int custom_handler(struct usb_setup_packet *pSetup, |
| int32_t *len, uint8_t **data) |
| { |
| const struct usb_if_descriptor *if_descr; |
| struct usb_interface_cfg_data *iface; |
| |
| LOG_DBG("bRequest 0x%02x, wIndex 0x%04x", |
| pSetup->bRequest, pSetup->wIndex); |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| iface = &cfg_data->interface; |
| if_descr = cfg_data->interface_descriptor; |
| /* |
| * Wind forward until it is within the range |
| * of the current descriptor. |
| */ |
| if ((uint8_t *)if_descr < usb_dev.descriptors) { |
| continue; |
| } |
| |
| if (iface->custom_handler == NULL) { |
| continue; |
| } |
| |
| if (if_descr->bInterfaceNumber == (pSetup->wIndex & 0xFF)) { |
| return iface->custom_handler(pSetup, len, data); |
| } else { |
| /* |
| * Audio has several interfaces. if_descr points to |
| * the first interface, but the request may be for |
| * subsequent ones, so forward each request to audio. |
| * The class does not actively engage in request |
| * handling and therefore we can ignore return value. |
| */ |
| if (if_descr->bInterfaceClass == USB_BCC_AUDIO) { |
| (void)iface->custom_handler(pSetup, len, data); |
| } |
| } |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int vendor_handler(struct usb_setup_packet *pSetup, |
| int32_t *len, uint8_t **data) |
| { |
| struct usb_interface_cfg_data *iface; |
| |
| LOG_DBG("bRequest 0x%02x, wIndex 0x%04x", |
| pSetup->bRequest, pSetup->wIndex); |
| |
| if (usb_os_desc_enabled()) { |
| if (!usb_handle_os_desc_feature(pSetup, len, data)) { |
| return 0; |
| } |
| } |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| iface = &cfg_data->interface; |
| if (iface->vendor_handler) { |
| if (!iface->vendor_handler(pSetup, len, data)) { |
| return 0; |
| } |
| } |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int composite_setup_ep_cb(void) |
| { |
| struct usb_ep_cfg_data *ep_data; |
| |
| STRUCT_SECTION_FOREACH(usb_cfg_data, cfg_data) { |
| ep_data = cfg_data->endpoint; |
| for (uint8_t n = 0; n < cfg_data->num_endpoints; n++) { |
| LOG_DBG("set cb, ep: 0x%x", 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; |
| } |
| |
| int usb_set_config(const uint8_t *device_descriptor) |
| { |
| /* register descriptors */ |
| usb_register_descriptors(device_descriptor); |
| |
| /* register standard request handler */ |
| usb_register_request_handler(USB_REQTYPE_TYPE_STANDARD, |
| usb_handle_standard_request); |
| |
| /* register class request handlers for each interface*/ |
| usb_register_request_handler(USB_REQTYPE_TYPE_CLASS, class_handler); |
| |
| /* register vendor request handler */ |
| usb_register_request_handler(USB_REQTYPE_TYPE_VENDOR, vendor_handler); |
| |
| /* register class request handlers for each interface*/ |
| usb_register_custom_req_handler(custom_handler); |
| |
| return 0; |
| } |
| |
| int usb_enable(usb_dc_status_callback status_cb) |
| { |
| int ret; |
| struct usb_dc_ep_cfg_data ep0_cfg; |
| |
| /* Prevent from calling usb_enable form different context. |
| * This should only be called once. |
| */ |
| LOG_DBG("lock usb_enable_lock mutex"); |
| k_mutex_lock(&usb_enable_lock, K_FOREVER); |
| |
| if (usb_dev.enabled == true) { |
| LOG_WRN("USB device support already enabled"); |
| ret = -EALREADY; |
| goto out; |
| } |
| |
| /* Enable VBUS if needed */ |
| ret = usb_vbus_set(true); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| usb_dev.user_status_callback = status_cb; |
| usb_register_status_callback(forward_status_cb); |
| usb_dc_set_status_callback(forward_status_cb); |
| |
| ret = usb_dc_attach(); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| ret = usb_transfer_init(); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| /* Configure control EP */ |
| ep0_cfg.ep_mps = USB_MAX_CTRL_MPS; |
| ep0_cfg.ep_type = USB_DC_EP_CONTROL; |
| |
| ep0_cfg.ep_addr = USB_CONTROL_EP_OUT; |
| ret = usb_dc_ep_configure(&ep0_cfg); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| ep0_cfg.ep_addr = USB_CONTROL_EP_IN; |
| ret = usb_dc_ep_configure(&ep0_cfg); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| /* Register endpoint 0 handlers*/ |
| ret = usb_dc_ep_set_callback(USB_CONTROL_EP_OUT, |
| usb_handle_control_transfer); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| ret = usb_dc_ep_set_callback(USB_CONTROL_EP_IN, |
| usb_handle_control_transfer); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| /* Register endpoint handlers*/ |
| ret = composite_setup_ep_cb(); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| /* Enable control EP */ |
| ret = usb_dc_ep_enable(USB_CONTROL_EP_OUT); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| ret = usb_dc_ep_enable(USB_CONTROL_EP_IN); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| usb_dev.enabled = true; |
| ret = 0; |
| out: |
| LOG_DBG("unlock usb_enable_lock mutex"); |
| k_mutex_unlock(&usb_enable_lock); |
| return ret; |
| } |
| |
| /* |
| * This function configures the USB device stack based on USB descriptor and |
| * usb_cfg_data. |
| */ |
| static int usb_device_init(const struct device *dev) |
| { |
| uint8_t *device_descriptor; |
| |
| if (usb_dev.enabled == true) { |
| return -EALREADY; |
| } |
| |
| /* register device descriptor */ |
| device_descriptor = usb_get_device_descriptor(); |
| if (!device_descriptor) { |
| LOG_ERR("Failed to configure USB device stack"); |
| return -1; |
| } |
| |
| usb_set_config(device_descriptor); |
| |
| if (IS_ENABLED(CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT)) { |
| return usb_enable(NULL); |
| } |
| |
| return 0; |
| } |
| |
| SYS_INIT(usb_device_init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY); |