├── .gitignore ├── CMakeLists.txt ├── README.md ├── components └── usb │ ├── CMakeLists.txt │ ├── hcd.c │ ├── maintainers.md │ ├── private_include │ ├── hcd.h │ └── usb.h │ └── test │ ├── CMakeLists.txt │ ├── component.mk │ └── hcd │ ├── test_hcd_bulk.c │ ├── test_hcd_common.c │ ├── test_hcd_common.h │ ├── test_hcd_ctrl.c │ ├── test_hcd_intr.c │ ├── test_hcd_isoc.c │ └── test_hcd_port.c └── main ├── CMakeLists.txt ├── cdc_class.c ├── cdc_class.h ├── common.h ├── component.mk ├── ctrl_pipe.c ├── ctrl_pipe.h ├── main.c ├── parse_data.c ├── usb_host_port.c └── usb_host_port.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | sdkconfig 4 | sdkconfig.old 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(hello-world-usb-host) 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simple cdc host example 2 | Simple USB host CDC example which should works with any CDC-ACM device. Tested with arduino tinyusb device built using 3 | https://github.com/chegewara/EspTinyUSB library. 4 | 5 | ## Example logs: 6 | 7 | ### Before connecting device 8 | ``` 9 | Hello world USB host! 10 | I (627) : USB host setup properly 11 | I (627) : Port powered ON 12 | Waiting for conenction 13 | ``` 14 | 15 | ### After connecting device 16 | ``` 17 | I (892) : HCD_PORT_EVENT_CONNECTION 18 | I (892) : HCD_PORT_STATE_DISABLED 19 | I (952) : USB device reset 20 | I (952) : HCD_PORT_STATE_ENABLED 21 | ... 22 | I (974) : address set: 1 23 | I (978) : set current configuration: 1 24 | ``` 25 | 26 | ### Example reading device descriptors and strings 27 | - device descriptor on EP0 28 | ``` 29 | Device descriptor: 30 | Length: 18 31 | Descriptor type: 18 32 | USB version: 2.00 33 | Device class: 0xef (Miscellaneous) 34 | Device subclass: 0x02 35 | Device protocol: 0x01 36 | EP0 max packet size: 64 37 | VID: 0x303a 38 | PID: 0x0002 39 | Revision number: 1.00 40 | Manufacturer id: 1 41 | Product id: 2 42 | Serial id: 3 43 | Configurations num: 1 44 | ``` 45 | 46 | - configuration descriptor 47 | ``` 48 | Config: 49 | Number of Interfaces: 2 50 | Attributes: 0xa0 51 | Max power: 500 mA 52 | 53 | Interface: 54 | bInterfaceNumber: 0 55 | bAlternateSetting: 0 56 | bNumEndpoints: 1 57 | bInterfaceClass: 0x02 (CDC) 58 | bInterfaceSubClass: 0x02 59 | bInterfaceProtocol: 0x00 60 | 61 | CS_Interface: 62 | 63 | CS_Interface: 64 | 65 | CS_Interface: 66 | 67 | CS_Interface: 68 | 69 | Endpoint: 70 | bEndpointAddress: 0x81 71 | bmAttributes: 0x03 72 | bDescriptorType: 5 73 | wMaxPacketSize: 8 74 | bInterval: 16 ms 75 | Creating transfer requests 76 | 77 | Interface: 78 | bInterfaceNumber: 1 79 | bAlternateSetting: 0 80 | bNumEndpoints: 2 81 | bInterfaceClass: 0x0a (CDC-data) 82 | bInterfaceSubClass: 0x00 83 | bInterfaceProtocol: 0x00 84 | 85 | Endpoint: 86 | bEndpointAddress: 0x02 87 | bmAttributes: 0x02 88 | bDescriptorType: 5 89 | wMaxPacketSize: 64 90 | bInterval: 0 ms 91 | Creating transfer requests 92 | 93 | Endpoint: 94 | bEndpointAddress: 0x82 95 | bmAttributes: 0x02 96 | bDescriptorType: 5 97 | wMaxPacketSize: 64 98 | bInterval: 0 ms 99 | Creating transfer requests 100 | ``` 101 | 102 | - manufacturer, product and serial strings 103 | ``` 104 | strings: Espressif 105 | strings: ESP32S2 arduino device 106 | strings: 1234-5678 107 | ``` 108 | 109 | - set DTR/RTS lines and bitrate, stop bits, parity and data bits 110 | ``` 111 | I (7306) Set control line state: 112 | I (7311) Set line coding: Bitrate: 115200, stop bits: 0, parity: 0, bits: 5 113 | ``` 114 | 115 | ### Testing 116 | - after connecting we can start sending data from CDC ACM device to host and see received data in logs, 117 | - when we send `?` from device then host should send back `test\n` to device 118 | 119 | 120 | Have a nice play. 121 | -------------------------------------------------------------------------------- /components/usb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_build_get_property(target IDF_TARGET) 2 | 3 | #USB Host is currently only supported on ESP32-S2, ESP32S3 chips 4 | if(NOT "${target}" MATCHES "^esp32s[2-3]") 5 | return() 6 | endif() 7 | 8 | idf_component_register(SRCS "hcd.c" 9 | INCLUDE_DIRS "" 10 | INCLUDE_DIRS "private_include" 11 | PRIV_REQUIRES "hal" 12 | REQUIRES "") 13 | -------------------------------------------------------------------------------- /components/usb/maintainers.md: -------------------------------------------------------------------------------- 1 | # USB Host Stack Maintainers Notes 2 | 3 | This document is intended future maintainers of the ESP-IDF USB Host stack. 4 | 5 | Note: The underlying DWC_OTG controller will sometimes be referred to as the Host Controller in this document. 6 | 7 | The host driver is currently split into the following layers, ordered from the lowest (furthest away from the user) to the highest layer (closest to the user). 8 | 9 | * USB Host lower layer in `usbh_ll.h` 10 | * USB HAL in `usbh_hal.h` and `usbh_hal.c` 11 | * Host Controller Driver in `hcd.c` and `hcd.h` 12 | 13 | # USB Host Lower Layer 14 | 15 | * `usbh_ll.h` abstracts away the basic register operation of the **DWC** OTG controller 16 | * The LL provides register operations of the DWC OTG controller operating under Host Mode using scatter/gather internal DMA. 17 | * For non-host mode specific register operations (i.e. global registers), the functions are prefixed with `usb_ll_...()` 18 | * For host mode specific register operations, the functions are prefixed with `usbh_ll_...()` 19 | 20 | # USB Host HAL 21 | 22 | The HAL layer abstracts the DWC_OTG operating in Host Mode using Internal Scatter/Gather DMA. The HAL presents an abstraction of a single Host Port and multiple channels. 23 | 24 | ## HAL Host Port 25 | 26 | - Models a single USB port where a single device can be attached 27 | - Actions: Port can be powered ON/OFF, reset, suspended 28 | - Events: When an interrupt occurs, call `usbh_hal_decode_intr()` to decoded the port interrupt. 29 | - Port can detect various events such as connection, disconnection, enabled (i.e., connected device successfully reset), overcurrent etc. 30 | 31 | ## HAL Channels 32 | 33 | - Channels are essentially the controllers abstraction of USB pipes. At any one point in time, a channel can be configured to map to a particular endpoint on a particular connected device (i.e., a particular device address). 34 | - Channels have to be allocated and freed. It's possible to change a channel's endpoint characteristics (i.e., EP number, device address, direction, transfer type etc) so long as the channel is halted (i.e., not actively executing transfer descriptors). 35 | - Use `usbh_hal_chan_alloc()` to allocate a channel 36 | - Once allocated, use `usbh_hal_chan_set_ep_char()` to set the Endpoint characteristics of the channel (i.e., the information of the endpoint that the channel is communicating with). There are also some `usbh_hal_chan_set...()` functions to change a particular characteristic. 37 | - Once the channel is no longer needed, call `usbh_hal_chan_free()` to free the channel 38 | - Channels use a list of Queue Transfer Descriptors (QTDs) to executed USB transfers. 39 | - A transfer descriptor list must be filled using `usbh_hal_xfer_desc_fill()` 40 | - Once filled, a channel can be activated using `usbh_hal_chan_activate()` 41 | - Once the channel is done (i.e., a descriptor with the `USBH_HAL_XFER_DESC_FLAG_HOC` is executed), a `USBH_HAL_CHAN_EVENT_CPLT` event is generated. The channel is now halted 42 | - Call `usbh_hal_xfer_desc_parse()` to parse the results of the descriptor list 43 | - If you need to halt the channel early (such as aborting a transfer), call `usbh_hal_chan_request_halt()` 44 | - In case of a channel error event: 45 | - Call `usbh_hal_chan_get_error()` to get the specific channel error that occurred 46 | - You must call `usbh_hal_chan_clear_error()` after an error to clear the error and allow the channel to continue to be used. 47 | 48 | # Host Controller Driver (HCD) 49 | 50 | The HCD (Host Controller Driver) abstracts the DWC_OTG as N number of ports and an arbitrary number of pipes that can be routed through one of the ports to a device. However note that **the underlying hardware controller only has a single port, so technically only one port can ever be enabled**. 51 | 52 | - In other words, the HCD essentially implements a root hub (not fully behavioral compliant) that contains a single port. 53 | - Pipes are "an association between an endpoint on a device and software on the host". IRPs (I/O Request Packets) that each represent a USB transfer can be enqueued into a pipe for transmission, and dequeued from a pipe when completed. 54 | 55 | The HCD currently has the following limitations: 56 | 57 | - HCD **does not** "present the root hub and its behavior according to the hub class definition". We currently don't have a hub driver yet, so the port commands in the driver do not fully represent an interface of a USB hub as described in 10.4 of the USB2.0 spec. 58 | - No more than 8 pipes can be allocated at any one time due to underlying Host Controllers 8 channel limit. In the future, we could make particular pipes share a single Host Controller channel. 59 | - The HCD currently only supports Control and Bulk transfer types. 60 | - If you are connecting to a device with a large MPS requirements (e.g., Isochronous transfers), you may need to call `hcd_port_set_fifo_bias()` to adjust the size of the internal FIFO 61 | 62 | ## HCD Port 63 | 64 | - An HCD port can be as a simplified version of a port on the Root Hub of the host controller. However, the complexity of parsing Hub Requests is discarded in favor of port commands (`hcd_port_cmd_t`) as the current USB Host Stack does not support hubs yet. 65 | - A port must first initialized before it can be used. A port is identified by its handled of type `hcd_port_handle_t` 66 | - The port can be manipulated using commands such as: 67 | - Powering the port ON/OFF 68 | - Issuing reset/resume signals 69 | - The various host port events are represented in the `hcd_port_event_t` enumeration 70 | - When a fatal error (such as a sudden disconnection or a port over current), the port will be put into the HCD_PORT_STATE_RECOVERY state. The port can be deinitialized from there, or recovered using `hcd_port_recover()`. All the pipes routed through the port will be made invalid. 71 | - The FIFO bias of a port can be set using `hcd_port_set_fifo_bias()`. Biasing the FIFO will affect the permissible MPS sizes of pipes. For example, if the connected device has an IN endpoint with large MPS (e.g., 512 bytes), the FIFO should be biased as `HCD_PORT_FIFO_BIAS_RX`. 72 | 73 | ## HCD Pipes 74 | 75 | - Pipes can be opened to a particular endpoint based on a descriptor provided on allocation. If opening a default pipe, a `NULL` descriptor can be provided. 76 | - IRPs can be enqueued into a pipe. Pipes use a linked list internally, so there is in-theory no limit to the number of IRPs that can be enqueued. 77 | - IRPs need to be dequeued once they are completed. 78 | - IRPs need to have the transfer information (such as data buffer, transfer length in bytes) filled before they should be enqueued. 79 | - IRPs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify an IRP object (and the IRP's data buffer) until the IRP is dequeued. 80 | - The IRP is defined in `usb.h` instead of `hcd.h` so that it can be used throughout the entire Host stack. Each layer simply needs to pass the pointer of the IRP to the next layer thus minimizing the amount of copying required. 81 | 82 | ## HCD SW Arch 83 | 84 | The software arch for the HCD has the following properties and intentions: 85 | 86 | - Some static functions need to be called in critical sections whilst others need not. Therefore, static functions prefixed by an underscore ((e.g., `_some_static_func()`) will need to be called in critical sections. 87 | - Some static functions may be blocking. Those functions will have a note in their descriptions. 88 | - The HCD communicates events entirely through callbacks and polling/handling functions. The client can choose what type of data structure they want to use in the callbacks to keep track of port and pipe events. Callbacks don't even need to be used, and the HCD should be able to operate entirely on a polling basis. 89 | - The port and each pipe have to be treated as completely separate entities (wither their own handles and events). This allows client to group these entities however it sees fit. For example, the client can: 90 | - Let a hub driver manage the port, and manipulate the port via its port handle 91 | - Group pipes into interfaces and expose the interface as a single entity 92 | - The HCD will not internally allocate any tasks. It is up to the client to decide how distribute workload (e.g., a single while loop polling a port and all its pipes vs or each pipe having its own task). 93 | - The HCD uses an interrupt process things that require low latency such as processing pipe transfer requests. Where possible, processing is deferred to the `hcd_port_handle_event()` to reduce ISR workload. 94 | 95 | ### Events 96 | 97 | In order to communicate events to the client of the HCD, the HCD does not attempt to allocate any queues to store events that have occurred. Callbacks are used in order to achieve maximum flexibility. Within these callbacks, the client of the HCD is free too use whatever OS primitives they want to store/forward these events. 98 | 99 | There are two types callbacks that the HCD offers: 100 | 101 | - Port callback will run whenever an event a port occurs. `hcd_port_handle_event()` should be called after a port event occurs. 102 | - A pipe callback on each pipe, that will run when a event occurs on a pipe. 103 | 104 | The client of the HCD can also forego callbacks entirely and simply poll for port and pipe events using the `hcd_port_handle_event()` and `hcd_pipe_get_event()` respectively. 105 | 106 | - Some of the port and pipe commands may need to block for a certain condition before the command is executed. For example when suspending the port, the port command must block until all pipes to finish their current transfers. The blocking and unblocking is handled by an internal event mechanism. 107 | 108 | ### Thread Safety 109 | 110 | The HCD API is thread safe however the following limitations should be noted: 111 | 112 | - It is the client's responsibility to ensure that `hcd_install()` is called before any other HCD function is called 113 | - Likewise, it is the client's responsibility to ensure that events and pipes are cleared before calling `hcd_port_deinit()`. 114 | - `hcd_port_command()` is thread safe, but only one port command can be executed at any one time. Therefore HCD internally used a mutex to protect against concurrent commands. 115 | - If multiple threads attempt to execute a command on the sample one, all but one of those threads will return with an invalid state error. 116 | - HCD functions should not be called from critical sections and interrupts, as some functions (e.g., `hcd_port_command()` and `hcd_pipe_command()`) may be blocking. -------------------------------------------------------------------------------- /components/usb/private_include/hcd.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | #include "esp_err.h" 25 | #include "usb.h" 26 | 27 | // ------------------------------------------------- Macros & Types ---------------------------------------------------- 28 | 29 | // ----------------------- States -------------------------- 30 | 31 | /** 32 | * @brief States of the HCD port 33 | * 34 | * @note The port can be thought of as an abstraction of the Root Hub that contains 35 | * a single port. 36 | * @note These states roughly match the port states outlined in 11.5.1 of the 37 | * USB2.0 specification. 38 | */ 39 | typedef enum { 40 | HCD_PORT_STATE_NOT_POWERED, /**< The port is not powered */ 41 | HCD_PORT_STATE_DISCONNECTED, /**< The port is powered but no device is connected */ 42 | HCD_PORT_STATE_DISABLED, /**< A device has connected to the port but has not been reset. SOF/keep alive are not being sent */ 43 | HCD_PORT_STATE_RESETTING, /**< The port is issuing a reset condition */ 44 | HCD_PORT_STATE_SUSPENDED, /**< The port has been suspended. */ 45 | HCD_PORT_STATE_RESUMING, /**< The port is issuing a resume condition */ 46 | HCD_PORT_STATE_ENABLED, /**< The port has been enabled. SOF/keep alive are being sent */ 47 | HCD_PORT_STATE_RECOVERY, /**< Port needs to be recovered from a fatal error (port error, overcurrent, or sudden disconnection) */ 48 | } hcd_port_state_t; 49 | 50 | /** 51 | * @brief States of an HCD pipe 52 | * 53 | * Active: 54 | * - Pipe is able to transmit data. IRPs can be enqueued. 55 | * - Event if pipe has no IRPs enqueued, it can still be in the active state. 56 | * Halted: 57 | * - An error has occurred on the pipe. IRPs will no longer be executed. 58 | * - Halt should be cleared using the clear command 59 | * Invalid: 60 | * - The underlying device that the pipe connects is not longer valid, thus making the pipe invalid. 61 | * - Pending IRPs should be dequeued and the pipe should be freed. 62 | */ 63 | typedef enum { 64 | HCD_PIPE_STATE_ACTIVE, /**< The pipe is active */ 65 | HCD_PIPE_STATE_HALTED, /**< The pipe is halted */ 66 | HCD_PIPE_STATE_INVALID, /**< The pipe no longer exists and should be freed */ 67 | } hcd_pipe_state_t; 68 | 69 | // ----------------------- Events -------------------------- 70 | 71 | /** 72 | * @brief HCD port events 73 | * 74 | * On receiving a port event, hcd_port_handle_event() should be called to handle that event 75 | */ 76 | typedef enum { 77 | HCD_PORT_EVENT_NONE, /**< No event has occurred. Or the previous event is no longer valid */ 78 | HCD_PORT_EVENT_CONNECTION, /**< A device has been connected to the port */ 79 | HCD_PORT_EVENT_DISCONNECTION, /**< A device disconnection has been detected */ 80 | HCD_PORT_EVENT_ERROR, /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY */ 81 | HCD_PORT_EVENT_OVERCURRENT, /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */ 82 | HCD_PORT_EVENT_SUDDEN_DISCONN, /**< The port has suddenly disconnected (i.e., there was an enabled device connected 83 | to the port when the disconnection occurred. Port is now HCD_PORT_STATE_RECOVERY. */ 84 | } hcd_port_event_t; 85 | 86 | /** 87 | * @brief HCD pipe events 88 | * 89 | * @note Pipe error events will put the pipe into the HCD_PIPE_STATE_HALTED state 90 | * @note The HCD_PIPE_EVENT_INVALID will put the pipe in the HCD_PIPE_STATE_INVALID state 91 | */ 92 | typedef enum { 93 | HCD_PIPE_EVENT_NONE, /**< The pipe has no events (used to indicate no events when polling) */ 94 | HCD_PIPE_EVENT_IRP_DONE, /**< The pipe has completed an IRP. The IRP can be dequeued */ 95 | HCD_PIPE_EVENT_INVALID, /**< The pipe is invalid because */ 96 | HCD_PIPE_EVENT_ERROR_XFER, /**< Excessive (three consecutive) transaction errors (e.g., no ACK, bad CRC etc) */ 97 | HCD_PIPE_EVENT_ERROR_IRP_NOT_AVAIL, /**< IRP was not available */ 98 | HCD_PIPE_EVENT_ERROR_OVERFLOW, /**< Received more data than requested. Usually a Packet babble error 99 | (i.e., an IN packet has exceeded the endpoint's MPS) */ 100 | HCD_PIPE_EVENT_ERROR_STALL, /**< Pipe received a STALL response received */ 101 | } hcd_pipe_event_t; 102 | 103 | // ---------------------- Commands ------------------------- 104 | 105 | /** 106 | * @brief HCD port commands 107 | */ 108 | typedef enum { 109 | HCD_PORT_CMD_POWER_ON, /**< Power ON the port */ 110 | HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port */ 111 | HCD_PORT_CMD_RESET, /**< Issue a reset on the port */ 112 | HCD_PORT_CMD_SUSPEND, /**< Suspend the port */ 113 | HCD_PORT_CMD_RESUME, /**< Resume the port */ 114 | HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive) */ 115 | } hcd_port_cmd_t; 116 | 117 | /** 118 | * @brief HCD pipe commands 119 | * 120 | * The pipe commands represent the list of pipe manipulations outlined in 10.5.2.2. of USB2.0 specification. 121 | */ 122 | typedef enum { 123 | HCD_PIPE_CMD_ABORT, /**< Retire all scheduled IRPs. Pipe's state remains unchanged */ 124 | HCD_PIPE_CMD_RESET, /**< Retire all scheduled IRPs. Pipe's state moves to active */ 125 | HCD_PIPE_CMD_CLEAR, /**< Pipe's state moves from halted to active */ 126 | HCD_PIPE_CMD_HALT /**< Pipe's state moves to halted */ 127 | } hcd_pipe_cmd_t; 128 | 129 | // -------------------- Object Types ----------------------- 130 | 131 | /** 132 | * @brief Port handle type 133 | */ 134 | typedef void * hcd_port_handle_t; 135 | 136 | /** 137 | * @brief Pipe handle type 138 | */ 139 | typedef void * hcd_pipe_handle_t; 140 | 141 | /** 142 | * @brief Port event callback type 143 | * 144 | * This callback is run when a port event occurs 145 | */ 146 | typedef bool (*hcd_port_isr_callback_t)(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); 147 | 148 | /** 149 | * @brief Pipe event callback 150 | * 151 | * This callback is run when a pipe event occurs 152 | */ 153 | typedef bool (*hcd_pipe_isr_callback_t)(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); 154 | 155 | typedef enum { 156 | HCD_PORT_FIFO_BIAS_BALANCED, /**< Balanced FIFO sizing for RX, Non-periodic TX, and periodic TX */ 157 | HCD_PORT_FIFO_BIAS_RX, /**< Bias towards a large RX FIFO */ 158 | HCD_PORT_FIFO_BIAS_PTX, /**< Bias towards periodic TX FIFO */ 159 | } hcd_port_fifo_bias_t; 160 | 161 | /** 162 | * @brief HCD configuration structure 163 | */ 164 | typedef struct { 165 | int intr_flags; /**< Interrupt flags for HCD interrupt */ 166 | } hcd_config_t; 167 | 168 | /** 169 | * @brief Port configuration structure 170 | */ 171 | typedef struct { 172 | hcd_port_isr_callback_t callback; /**< HCD port event callback */ 173 | void *callback_arg; /**< User argument for HCD port callback */ 174 | void *context; /**< Context variable used to associate the port with upper layer object */ 175 | } hcd_port_config_t; 176 | 177 | /** 178 | * @brief Pipe configuration structure 179 | * 180 | * @note The callback can be set to NULL if no callback is required (e.g., using HCD in a polling manner). 181 | */ 182 | typedef struct { 183 | hcd_pipe_isr_callback_t callback; /**< HCD pipe event ISR callback */ 184 | void *callback_arg; /**< User argument for HCD pipe callback */ 185 | void *context; /**< Context variable used to associate the pipe with upper layer object */ 186 | const usb_desc_ep_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */ 187 | usb_speed_t dev_speed; /**< Speed of the device */ 188 | uint8_t dev_addr; /**< Device address of the pipe */ 189 | } hcd_pipe_config_t; 190 | 191 | // --------------------------------------------- Host Controller Driver ------------------------------------------------ 192 | 193 | /** 194 | * @brief Installs the Host Controller Driver 195 | * 196 | * - Allocates memory and interrupt resources for the HCD and underlying ports 197 | * - Setups up HCD to use internal PHY 198 | * 199 | * @note This function must be called before any other HCD function is called 200 | * 201 | * @param config HCD configuration 202 | * @retval ESP_OK: HCD successfully installed 203 | * @retval ESP_ERR_NO_MEM: Insufficient memory 204 | * @retval ESP_ERR_INVALID_STATE: HCD is already installed 205 | * @retval ESP_ERR_NOT_FOUND: HCD could not allocate interrupt 206 | * @retval ESP_ERR_INVALID_ARG: Arguments are invalid 207 | */ 208 | esp_err_t hcd_install(const hcd_config_t *config); 209 | 210 | /** 211 | * @brief Uninstalls the HCD 212 | * 213 | * Before uninstalling the HCD, the following conditions should be met: 214 | * - All ports must be uninitialized, all pipes freed 215 | * 216 | * @retval ESP_OK: HCD successfully uninstalled 217 | * @retval ESP_ERR_INVALID_STATE: HCD is not in the right condition to be uninstalled 218 | */ 219 | esp_err_t hcd_uninstall(void); 220 | 221 | // ---------------------------------------------------- HCD Port ------------------------------------------------------- 222 | 223 | /** 224 | * @brief Initialize a particular port of the HCD 225 | * 226 | * After a port is initialized, it will be put into the HCD_PORT_STATE_NOT_POWERED state. 227 | * 228 | * @note The host controller only has one port, thus the only valid port_number is 1 229 | * 230 | * @param[in] port_number Port number 231 | * @param[in] port_config Port configuration 232 | * @param[out] port_hdl Port handle 233 | * @retval ESP_OK: Port enabled 234 | * @retval ESP_ERR_NO_MEM: Insufficient memory 235 | * @retval ESP_ERR_INVALID_STATE: The port is already enabled 236 | * @retval ESP_ERR_NOT_FOUND: Port number not found 237 | * @retval ESP_ERR_INVALID_ARG: Arguments are invalid 238 | */ 239 | esp_err_t hcd_port_init(int port_number, hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl); 240 | 241 | /** 242 | * @brief Deinitialize a particular port 243 | * 244 | * The port must be placed in the HCD_PORT_STATE_NOT_POWERED or HCD_PORT_STATE_RECOVERY state before it can be 245 | * deinitialized. 246 | * 247 | * @param port_hdl Port handle 248 | * @retval ESP_OK: Port disabled 249 | * @retval ESP_ERR_INVALID_STATE: The port is not in a condition to be disabled (not unpowered) 250 | */ 251 | esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl); 252 | 253 | /** 254 | * @brief Execute a port command 255 | * 256 | * Call this function to manipulate a port (e.g., powering it ON, sending a reset etc). The following conditions 257 | * must be met when calling this function: 258 | * - The port is in the correct state for the command (e.g., port must be suspend in order to use the resume command) 259 | * - The port does not have any pending events 260 | * 261 | * @note This function is internally protected by a mutex. If multiple threads call this function, this function will 262 | * can block. 263 | * @note For some of the commands that involve a blocking delay (e.g., reset and resume), if the port's state changes 264 | * unexpectedly (e.g., a disconnect during a resume), this function will return ESP_ERR_INVALID_RESPONSE. 265 | * 266 | * @param port_hdl Port handle 267 | * @param command Command for the HCD port 268 | * @retval ESP_OK: Command executed successfully 269 | * @retval ESP_ERR_INVALID_STATE: Conditions have not been met to call this function 270 | * @retval ESP_ERR_INVALID_RESPONSE: The command is no longer valid due to a change in the port's state 271 | */ 272 | esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command); 273 | 274 | /** 275 | * @brief Get the port's current state 276 | * 277 | * @param port_hdl Port handle 278 | * @return hcd_port_state_t Current port state 279 | */ 280 | hcd_port_state_t hcd_port_get_state(hcd_port_handle_t port_hdl); 281 | 282 | /** 283 | * @brief Get the speed of the port 284 | * 285 | * The speed of the port is determined by the speed of the device connected to it. 286 | * 287 | * @note This function is only valid after a device directly to the port and has been reset 288 | * 289 | * @param[in port_hdl Port handle 290 | * @param[out] speed Speed of the port 291 | * @retval ESP_OK Device speed obtained 292 | * @retval ESP_ERR_INVALID_STATE: No valid device connected to the port 293 | * @retval ESP_ERR_INVALID_ARG: Invalid arguments 294 | */ 295 | esp_err_t hcd_port_get_speed(hcd_port_handle_t port_hdl, usb_speed_t *speed); 296 | 297 | /** 298 | * @brief Handle a ports event 299 | * 300 | * When an port event occurs (as indicated by a callback), this function should be called the handle this event. A 301 | * port's event should always be handled before attempting to execute a port command. Note that is actually handled 302 | * may be different than the event reflected in the callback. 303 | * 304 | * If the port has no events, this function will return HCD_PORT_EVENT_NONE. 305 | * 306 | * @note If callbacks are not used, this function can also be used in a polling manner to repeatedly check for and 307 | * handle a port's events. 308 | * @note This function is internally protected by a mutex. If multiple threads call this function, this function will 309 | * can block. 310 | * 311 | * @param port_hdl Port handle 312 | * @return hcd_port_event_t The port event that was handled 313 | */ 314 | hcd_port_event_t hcd_port_handle_event(hcd_port_handle_t port_hdl); 315 | 316 | /** 317 | * @brief Recover a port after a fatal error has occurred on it 318 | * 319 | * The port must be in the HCD_PORT_STATE_RECOVERY state to be called. Recovering the port will involve issuing a soft 320 | * reset on the underlying USB controller. The port will be returned to the HCD_PORT_STATE_NOT_POWERED state. 321 | * 322 | * @param port_hdl Port handle 323 | * @retval ESP_OK Port recovered successfully 324 | * @retval ESP_ERR_INVALID_STATE Port is not in the HCD_PORT_STATE_RECOVERY state 325 | */ 326 | esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl); 327 | 328 | /** 329 | * @brief Get the context variable of a port 330 | * 331 | * @param port_hdl Port handle 332 | * @return void* Context variable 333 | */ 334 | void *hcd_port_get_context(hcd_port_handle_t port_hdl); 335 | 336 | /** 337 | * @brief Set the bias of the HCD port's internal FIFO 338 | * 339 | * @note This function can only be called when the following conditions are met: 340 | * - Port is initialized 341 | * - Port does not have any pending events 342 | * - Port does not have any allocated pipes 343 | * 344 | * @param port_hdl Port handle 345 | * @param bias Fifo bias 346 | * @retval ESP_OK FIFO sizing successfully set 347 | * @retval ESP_ERR_INVALID_STATE Incorrect state for FIFO sizes to be set 348 | */ 349 | esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_t bias); 350 | 351 | // --------------------------------------------------- HCD Pipes ------------------------------------------------------- 352 | 353 | /** 354 | * @brief Allocate a pipe 355 | * 356 | * When allocating a pipe, the HCD will assess whether there are sufficient resources (i.e., bus time, and controller 357 | * channels). If sufficient, the pipe will be allocated. 358 | * 359 | * @note Currently, Interrupt and Isochronous pipes are not supported yet 360 | * @note The host port must be in the enabled state before a pipe can be allcoated 361 | * 362 | * @param[in] port_hdl Handle of the port this pipe will be routed through 363 | * @param[in] pipe_config Pipe configuration 364 | * @param[out] pipe_hdl Pipe handle 365 | * 366 | * @retval ESP_OK: Pipe successfully allocated 367 | * @retval ESP_ERR_NO_MEM: Insufficient memory 368 | * @retval ESP_ERR_INVALID_ARG: Arguments are invalid 369 | * @retval ESP_ERR_INVALID_STATE: Host port is not in the correct state to allocate a pipe 370 | * @retval ESP_ERR_NOT_SUPPORTED: The pipe's configuration cannot be supported 371 | */ 372 | esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pipe_config, hcd_pipe_handle_t *pipe_hdl); 373 | 374 | /** 375 | * @brief Free a pipe 376 | * 377 | * Frees the resources used by an HCD pipe. The pipe's handle should be discarded after calling this function. The pipe 378 | * must be in following condition before it can be freed: 379 | * - All IRPs have been dequeued 380 | * 381 | * @param pipe_hdl Pipe handle 382 | * 383 | * @retval ESP_OK: Pipe successfully freed 384 | * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be freed 385 | */ 386 | esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl); 387 | 388 | /** 389 | * @brief Update a pipe's maximum packet size 390 | * 391 | * This function is intended to be called on default pipes during enumeration in order to update the pipe's maximum 392 | * packet size. This function can only be called on a pipe that has met the following conditions: 393 | * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state) 394 | * - Pipe is not currently processing a command 395 | * - All IRPs have been dequeued from the pipe 396 | * 397 | * @param pipe_hdl Pipe handle 398 | * @param mps New Maximum Packet Size 399 | * 400 | * @retval ESP_OK: Pipe successfully updated 401 | * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated 402 | */ 403 | esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); 404 | 405 | /** 406 | * @brief Update a pipe's device address 407 | * 408 | * This function is intended to be called on default pipes during enumeration in order to update the pipe's device 409 | * address. This function can only be called on a pipe that has met the following conditions: 410 | * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state) 411 | * - Pipe is not currently processing a command 412 | * - All IRPs have been dequeued from the pipe 413 | * 414 | * @param pipe_hdl Pipe handle 415 | * @param dev_addr New device address 416 | * 417 | * @retval ESP_OK: Pipe successfully updated 418 | * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be updated 419 | */ 420 | esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr); 421 | 422 | /** 423 | * @brief Get the context variable of a pipe from its handle 424 | * 425 | * @param pipe_hdl Pipe handle 426 | * @return void* Context variable 427 | */ 428 | void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl); 429 | 430 | /** 431 | * @brief Get the current sate of the pipe 432 | * 433 | * @param pipe_hdl Pipe handle 434 | * @return hcd_pipe_state_t Current state of the pipe 435 | */ 436 | hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl); 437 | 438 | /** 439 | * @brief Execute a command on a particular pipe 440 | * 441 | * Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all IRPs etc). The following 442 | * conditions must for a pipe command to be issued: 443 | * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID) 444 | * - No other thread/task processing a command on the pipe concurrently (will return) 445 | * 446 | * @note Some pipe commands will block until the pipe's current in-flight IRP is complete. If the pipe's state 447 | * changes unexpectedly, this function will return ESP_ERR_INVALID_RESPONSE 448 | * 449 | * @param pipe_hdl Pipe handle 450 | * @param command Pipe command 451 | * @retval ESP_OK: Command executed successfully 452 | * @retval ESP_ERR_INVALID_STATE: The pipe is not in the correct state/condition too execute the command 453 | * @retval ESP_ERR_INVALID_RESPONSE: The pipe's state changed unexpectedley 454 | */ 455 | esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command); 456 | 457 | /** 458 | * @brief Get the last event that occurred on a pipe 459 | * 460 | * This function allows a pipe to be polled for events (i.e., when callbacks are not used). Once an event has been 461 | * obtained, this function reset the last event of the pipe to HCD_PIPE_EVENT_NONE. 462 | * 463 | * @param pipe_hdl Pipe handle 464 | * @return hcd_pipe_event_t Last pipe event to occur 465 | */ 466 | hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl); 467 | 468 | // ---------------------------------------------------- HCD IRPs ------------------------------------------------------- 469 | 470 | /** 471 | * @brief Enqueue an IRP to a particular pipe 472 | * 473 | * The following conditions must be met before an IRP can be enqueued: 474 | * - The IRP is properly initialized (data buffer and transfer length are set) 475 | * - The IRP must not already be enqueued 476 | * - The pipe must be in the HCD_PIPE_STATE_ACTIVE state 477 | * 478 | * @param pipe_hdl Pipe handle 479 | * @param irp I/O Request Packet to enqueue 480 | * @retval ESP_OK: IRP enqueued successfully 481 | * @retval ESP_ERR_INVALID_STATE: Conditions not met to enqueue IRP 482 | */ 483 | esp_err_t hcd_irp_enqueue(hcd_pipe_handle_t pipe_hdl, usb_irp_t *irp); 484 | 485 | /** 486 | * @brief Dequeue an IRP from a particular pipe 487 | * 488 | * This function should be called on a pipe after a pipe receives a HCD_PIPE_EVENT_IRP_DONE event. If a pipe has 489 | * multiple IRPs that can be dequeued, this function should be called repeatedly until all IRPs are dequeued. If a pipe 490 | * has no more IRPs to dequeue, this function will return NULL. 491 | * 492 | * @param pipe_hdl Pipe handle 493 | * @return usb_irp_t* Dequeued I/O Request Packet, or NULL if no more IRPs to dequeue 494 | */ 495 | usb_irp_t *hcd_irp_dequeue(hcd_pipe_handle_t pipe_hdl); 496 | 497 | /** 498 | * @brief Abort an enqueued IRP 499 | * 500 | * This function will attempt to abort an IRP that is already enqueued. If the IRP has yet to be executed, it will be 501 | * "cancelled" and can then be dequeued. If the IRP is currenty in-flight or has already completed, the IRP will not be 502 | * affected by this function. 503 | * 504 | * @param irp I/O Request Packet to abort 505 | * @retval ESP_OK: IRP successfully aborted, or was not affected by this function 506 | * @retval ESP_ERR_INVALID_STATE: IRP was never enqueued 507 | */ 508 | esp_err_t hcd_irp_abort(usb_irp_t *irp); 509 | 510 | #ifdef __cplusplus 511 | } 512 | #endif 513 | -------------------------------------------------------------------------------- /components/usb/private_include/usb.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation 17 | agnostic. In other words, this header is only meant to be used in the HCD layer and above of the USB Host stack. For 18 | types and macros that are HW implementation specific (i.e., HAL layer and below), add them to the "usb_types.h" header 19 | instead. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | #include 32 | #include 33 | 34 | #define USB_CTRL_REQ_ATTR __attribute__((packed)) 35 | #define USB_DESC_ATTR __attribute__((packed)) 36 | 37 | // ------------------------------------------------ Common USB Types --------------------------------------------------- 38 | 39 | // --------------------- Bus Related ----------------------- 40 | 41 | /** 42 | * @brief USB Standard Speeds 43 | */ 44 | typedef enum { 45 | USB_SPEED_LOW = 0, /**< USB Low Speed (1.5 Mbit/s) */ 46 | USB_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */ 47 | } usb_speed_t; 48 | 49 | // ------------------ Transfer Related --------------------- 50 | 51 | /** 52 | * @brief The type of USB transfer 53 | * 54 | * @note The enum values need to match the bmAttributes field of an EP descriptor 55 | */ 56 | typedef enum { 57 | USB_TRANSFER_TYPE_CTRL = 0, 58 | USB_TRANSFER_TYPE_ISOCHRONOUS, 59 | USB_TRANSFER_TYPE_BULK, 60 | USB_TRANSFER_TYPE_INTR, 61 | } usb_transfer_type_t; 62 | 63 | /** 64 | * @brief The status of a particular transfer 65 | */ 66 | typedef enum { 67 | USB_TRANSFER_STATUS_COMPLETED, /**< The transfer was successful (but may be short) */ 68 | USB_TRANSFER_STATUS_ERROR, /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */ 69 | USB_TRANSFER_STATUS_TIMED_OUT, /**< The transfer failed due to a time out */ 70 | USB_TRANSFER_STATUS_CANCELED, /**< The transfer was canceled */ 71 | USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */ 72 | USB_TRANSFER_STATUS_NO_DEVICE, /**< The transfer failed because the device is no longer valid (e.g., disconnected */ 73 | USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */ 74 | USB_TRANSFER_STATUS_SKIPPED, /**< ISOC only. The packet was skipped due to system latency */ 75 | } usb_transfer_status_t; 76 | 77 | /** 78 | * @brief Isochronous packet descriptor 79 | * 80 | * If the number of bytes in an IRP (I/O Request Packet, see USB2.0 Spec) is 81 | * larger than the MPS of the endpoint, the IRP is split over multiple packets 82 | * (one packet per bInterval of the endpoint). An array of Isochronous packet 83 | * descriptors describes how an IRP should be split over multiple packets. 84 | */ 85 | typedef struct { 86 | int length; /**< Number of bytes to transmit/receive in the packet */ 87 | int actual_length; /**< Actual number of bytes transmitted/received in the packet */ 88 | usb_transfer_status_t status; /**< Status of the packet */ 89 | } usb_iso_packet_desc_t; 90 | 91 | #define USB_IRP_FLAG_ZERO_PACK 0x01 /**< (For bulk OUT only). Indicates that a bulk OUT transfers should always terminate with a short packet, even if it means adding an extra zero length packet */ 92 | 93 | /** 94 | * @brief USB IRP (I/O Request Packet). See USB2.0 Spec 95 | * 96 | * An IRP is used to represent data transfer request form a software client to and endpoint over the USB bus. The same 97 | * IRP object type is used at each layer of the USB stack. This minimizes copying/conversion across the different layers 98 | * of the stack as each layer will pass a pointer to this type of object. 99 | * 100 | * See 10.5.3.1 os USB2.0 specification 101 | * Bulk: Represents a single bulk transfer which a pipe will transparently split into multiple MPS transactions (until 102 | * the last) 103 | * Control: Represents a single control transfer with the setup packet at the first 8 bytes of the buffer. 104 | * Interrupt: Represents a single interrupt transaction 105 | * Isochronous: Represents a buffer of a stream of bytes which the pipe will transparently transfer the stream of bytes 106 | * one or more service periods 107 | * 108 | * @note The tailq_entry and reserved variables are used by the USB Host stack internally. Users should not modify those fields. 109 | * @note Once an IRP is submitted, users should not modify the IRP as the Host stack takes ownership of the IRP. 110 | */ 111 | struct usb_irp_obj { 112 | //Internal members 113 | TAILQ_ENTRY(usb_irp_obj) tailq_entry; /**< TAILQ entry that allows this object to be added to linked lists. Users should NOT modify this field */ 114 | void *reserved_ptr; /**< Reserved pointer variable for internal use in the stack. Users should set this to NULL on allocation and NOT modify this afterwards */ 115 | uint32_t reserved_flags; /**< Reserved variable for flags used internally in the stack. Users should set this to 0 on allocation and NOT modify this afterwards */ 116 | //Public members 117 | uint8_t *data_buffer; /**< Pointer to data buffer. Must be DMA capable memory */ 118 | int num_bytes; /**< Number of bytes in IRP. Control should exclude size of setup. IN should be integer multiple of MPS */ 119 | int actual_num_bytes; /**< Actual number of bytes transmitted/receives in the IRP */ 120 | uint32_t flags; /**< IRP flags */ 121 | usb_transfer_status_t status; /**< Status of the transfer */ 122 | uint32_t timeout; /**< Timeout (in milliseconds) of the packet (currently not supported yet) */ 123 | void *context; /**< Context variable used to associate the IRP object with another object */ 124 | int num_iso_packets; /**< Only relevant to Isochronous. Number of service periods to transfer data buffer over. Set to 0 for non-iso transfers */ 125 | usb_iso_packet_desc_t iso_packet_desc[0]; /**< Descriptors for each ISO packet */ 126 | }; 127 | 128 | typedef struct usb_irp_obj usb_irp_t; 129 | 130 | // ---------------------------------------------------- Chapter 9 ------------------------------------------------------ 131 | 132 | #define USB_B_DESCRIPTOR_TYPE_DEVICE 1 133 | #define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 2 134 | #define USB_B_DESCRIPTOR_TYPE_STRING 3 135 | #define USB_B_DESCRIPTOR_TYPE_INTERFACE 4 136 | #define USB_B_DESCRIPTOR_TYPE_ENDPOINT 5 137 | #define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 6 138 | #define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 7 139 | #define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 8 140 | 141 | // ------------------- Control Request --------------------- 142 | 143 | /** 144 | * @brief Size of a USB control transfer setup packet in bytes 145 | */ 146 | #define USB_CTRL_REQ_SIZE 8 147 | 148 | /** 149 | * @brief Structure representing a USB control transfer setup packet 150 | */ 151 | typedef union { 152 | struct { 153 | uint8_t bRequestType; 154 | uint8_t bRequest; 155 | uint16_t wValue; 156 | uint16_t wIndex; 157 | uint16_t wLength; 158 | } USB_CTRL_REQ_ATTR; 159 | uint8_t val[USB_CTRL_REQ_SIZE]; 160 | } usb_ctrl_req_t; 161 | _Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_req_t incorrect"); 162 | 163 | /** 164 | * @brief Bit masks belonging to the bRequestType field of a setup packet 165 | */ 166 | #define USB_B_REQUEST_TYPE_DIR_OUT (0X00 << 7) 167 | #define USB_B_REQUEST_TYPE_DIR_IN (0x01 << 7) 168 | #define USB_B_REQUEST_TYPE_TYPE_STANDARD (0x00 << 5) 169 | #define USB_B_REQUEST_TYPE_TYPE_CLASS (0x01 << 5) 170 | #define USB_B_REQUEST_TYPE_TYPE_VENDOR (0x02 << 5) 171 | #define USB_B_REQUEST_TYPE_TYPE_RESERVED (0x03 << 5) 172 | #define USB_B_REQUEST_TYPE_TYPE_MASK (0x03 << 5) 173 | #define USB_B_REQUEST_TYPE_RECIP_DEVICE (0x00 << 0) 174 | #define USB_B_REQUEST_TYPE_RECIP_INTERFACE (0x01 << 0) 175 | #define USB_B_REQUEST_TYPE_RECIP_ENDPOINT (0x02 << 0) 176 | #define USB_B_REQUEST_TYPE_RECIP_OTHER (0x03 << 0) 177 | #define USB_B_REQUEST_TYPE_RECIP_MASK (0x1f << 0) 178 | 179 | /** 180 | * @brief Bit masks belonging to the bRequest field of a setup packet 181 | */ 182 | #define USB_B_REQUEST_GET_STATUS 0x00 183 | #define USB_B_REQUEST_CLEAR_FEATURE 0x01 184 | #define USB_B_REQUEST_SET_FEATURE 0x03 185 | #define USB_B_REQUEST_SET_ADDRESS 0x05 186 | #define USB_B_REQUEST_GET_DESCRIPTOR 0x06 187 | #define USB_B_REQUEST_SET_DESCRIPTOR 0x07 188 | #define USB_B_REQUEST_GET_CONFIGURATION 0x08 189 | #define USB_B_REQUEST_SET_CONFIGURATION 0x09 190 | #define USB_B_REQUEST_GET_INTERFACE 0x0A 191 | #define USB_B_REQUEST_SET_INTERFACE 0x0B 192 | #define USB_B_REQUEST_SYNCH_FRAME 0x0C 193 | 194 | /** 195 | * @brief Bit masks belonging to the wValue field of a setup packet 196 | */ 197 | #define USB_W_VALUE_DT_DEVICE 0x01 198 | #define USB_W_VALUE_DT_CONFIG 0x02 199 | #define USB_W_VALUE_DT_STRING 0x03 200 | #define USB_W_VALUE_DT_INTERFACE 0x04 201 | #define USB_W_VALUE_DT_ENDPOINT 0x05 202 | #define USB_W_VALUE_DT_DEVICE_QUALIFIER 0x06 203 | #define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 204 | #define USB_W_VALUE_DT_INTERFACE_POWER 0x08 205 | 206 | /** 207 | * @brief Initializer for a SET_ADDRESS request 208 | * 209 | * Sets the address of a connected device 210 | */ 211 | #define USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req_ptr, addr) ({ \ 212 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD |USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 213 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS; \ 214 | (ctrl_req_ptr)->wValue = (addr); \ 215 | (ctrl_req_ptr)->wIndex = 0; \ 216 | (ctrl_req_ptr)->wLength = 0; \ 217 | }) 218 | 219 | /** 220 | * @brief Initializer for a request to get a device's device descriptor 221 | */ 222 | #define USB_CTRL_REQ_INIT_GET_DEVC_DESC(ctrl_req_ptr) ({ \ 223 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 224 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ 225 | (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_DEVICE << 8); \ 226 | (ctrl_req_ptr)->wIndex = 0; \ 227 | (ctrl_req_ptr)->wLength = 18; \ 228 | }) 229 | 230 | /** 231 | * @brief Initializer for a request to get a device's current configuration number 232 | */ 233 | #define USB_CTRL_REQ_INIT_GET_CONFIG(ctrl_req_ptr) ({ \ 234 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 235 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_CONFIGURATION; \ 236 | (ctrl_req_ptr)->wValue = 0; \ 237 | (ctrl_req_ptr)->wIndex = 0; \ 238 | (ctrl_req_ptr)->wLength = 1; \ 239 | }) 240 | 241 | /** 242 | * @brief Initializer for a request to get one of the device's current configuration descriptor 243 | * 244 | * - desc_index indicates the configuration's index number 245 | * - Number of bytes of the configuration descriptor to get 246 | */ 247 | #define USB_CTRL_REQ_INIT_GET_CFG_DESC(ctrl_req_ptr, desc_index, desc_len) ({ \ 248 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 249 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ 250 | (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_CONFIG << 8) | ((desc_index) & 0xFF); \ 251 | (ctrl_req_ptr)->wIndex = 0; \ 252 | (ctrl_req_ptr)->wLength = (desc_len); \ 253 | }) 254 | 255 | /** 256 | * @brief Initializer for a request to set a device's current configuration number 257 | */ 258 | #define USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req_ptr, config_num) ({ \ 259 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 260 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION; \ 261 | (ctrl_req_ptr)->wValue = (config_num); \ 262 | (ctrl_req_ptr)->wIndex = 0; \ 263 | (ctrl_req_ptr)->wLength = 0; \ 264 | }) 265 | 266 | /** 267 | * @brief Initializer for a request to set an interface's alternate setting 268 | */ 269 | #define USB_CTRL_REQ_INIT_SET_INTERFACE(ctrl_req_ptr, intf_num, alt_setting_num) ({ \ 270 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \ 271 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \ 272 | (ctrl_req_ptr)->wValue = (alt_setting_num); \ 273 | (ctrl_req_ptr)->wIndex = (intf_num); \ 274 | (ctrl_req_ptr)->wLength = 0; \ 275 | }) 276 | 277 | // ------------------ Device Descriptor -------------------- 278 | 279 | /** 280 | * @brief Size of a USB device descriptor in bytes 281 | */ 282 | #define USB_DESC_DEVC_SIZE 18 283 | 284 | /** 285 | * @brief Structure representing a USB device descriptor 286 | */ 287 | typedef union { 288 | struct { 289 | uint8_t bLength; 290 | uint8_t bDescriptorType; 291 | uint16_t bcdUSB; 292 | uint8_t bDeviceClass; 293 | uint8_t bDeviceSubClass; 294 | uint8_t bDeviceProtocol; 295 | uint8_t bMaxPacketSize0; 296 | uint16_t idVendor; 297 | uint16_t idProduct; 298 | uint16_t bcdDevice; 299 | uint8_t iManufacturer; 300 | uint8_t iProduct; 301 | uint8_t iSerialNumber; 302 | uint8_t bNumConfigurations; 303 | } USB_DESC_ATTR; 304 | uint8_t val[USB_DESC_DEVC_SIZE]; 305 | } usb_desc_devc_t; 306 | _Static_assert(sizeof(usb_desc_devc_t) == USB_DESC_DEVC_SIZE, "Size of usb_desc_devc_t incorrect"); 307 | 308 | /** 309 | * @brief Possible base class values of the bDeviceClass field of a USB device descriptor 310 | */ 311 | #define USB_CLASS_PER_INTERFACE 0x00 312 | #define USB_CLASS_AUDIO 0x01 313 | #define USB_CLASS_COMM 0x02 314 | #define USB_CLASS_HID 0x03 315 | #define USB_CLASS_PHYSICAL 0x05 316 | #define USB_CLASS_STILL_IMAGE 0x06 317 | #define USB_CLASS_PRINTER 0x07 318 | #define USB_CLASS_MASS_STORAGE 0x08 319 | #define USB_CLASS_HUB 0x09 320 | #define USB_CLASS_CDC_DATA 0x0a 321 | #define USB_CLASS_CSCID 0x0b 322 | #define USB_CLASS_CONTENT_SEC 0x0d 323 | #define USB_CLASS_VIDEO 0x0e 324 | #define USB_CLASS_WIRELESS_CONTROLLER 0xe0 325 | #define USB_CLASS_PERSONAL_HEALTHCARE 0x0f 326 | #define USB_CLASS_AUDIO_VIDEO 0x10 327 | #define USB_CLASS_BILLBOARD 0x11 328 | #define USB_CLASS_USB_TYPE_C_BRIDGE 0x12 329 | #define USB_CLASS_MISC 0xef 330 | #define USB_CLASS_APP_SPEC 0xfe 331 | #define USB_CLASS_VENDOR_SPEC 0xff 332 | 333 | /** 334 | * @brief Vendor specific subclass code 335 | */ 336 | #define USB_SUBCLASS_VENDOR_SPEC 0xff 337 | 338 | // -------------- Configuration Descriptor ----------------- 339 | 340 | /** 341 | * @brief Size of a short USB configuration descriptor in bytes 342 | * 343 | * @note The size of a full USB configuration includes all the interface and endpoint 344 | * descriptors of that configuration. 345 | */ 346 | #define USB_DESC_CFG_SIZE 9 347 | 348 | /** 349 | * @brief Structure representing a short USB configuration descriptor 350 | * 351 | * @note The full USB configuration includes all the interface and endpoint 352 | * descriptors of that configuration. 353 | */ 354 | typedef union { 355 | struct { 356 | uint8_t bLength; 357 | uint8_t bDescriptorType; 358 | uint16_t wTotalLength; 359 | uint8_t bNumInterfaces; 360 | uint8_t bConfigurationValue; 361 | uint8_t iConfiguration; 362 | uint8_t bmAttributes; 363 | uint8_t bMaxPower; 364 | } USB_DESC_ATTR; 365 | uint8_t val[USB_DESC_CFG_SIZE]; 366 | } usb_desc_cfg_t; 367 | _Static_assert(sizeof(usb_desc_cfg_t) == USB_DESC_CFG_SIZE, "Size of usb_desc_cfg_t incorrect"); 368 | 369 | /** 370 | * @brief Bit masks belonging to the bmAttributes field of a configuration descriptor 371 | */ 372 | #define USB_BM_ATTRIBUTES_ONE (1 << 7) //Must be set 373 | #define USB_BM_ATTRIBUTES_SELFPOWER (1 << 6) //Self powered 374 | #define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up 375 | #define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered 376 | 377 | // ---------------- Interface Descriptor ------------------- 378 | 379 | /** 380 | * @brief Size of a USB interface descriptor in bytes 381 | */ 382 | #define USB_DESC_INTF_SIZE 9 383 | 384 | /** 385 | * @brief Structure representing a USB interface descriptor 386 | */ 387 | typedef union { 388 | struct { 389 | uint8_t bLength; 390 | uint8_t bDescriptorType; 391 | uint8_t bInterfaceNumber; 392 | uint8_t bAlternateSetting; 393 | uint8_t bNumEndpoints; 394 | uint8_t bInterfaceClass; 395 | uint8_t bInterfaceSubClass; 396 | uint8_t bInterfaceProtocol; 397 | uint8_t iInterface; 398 | } USB_DESC_ATTR; 399 | uint8_t val[USB_DESC_INTF_SIZE]; 400 | } usb_desc_intf_t; 401 | _Static_assert(sizeof(usb_desc_intf_t) == USB_DESC_INTF_SIZE, "Size of usb_desc_intf_t incorrect"); 402 | 403 | // ----------------- Endpoint Descriptor ------------------- 404 | 405 | /** 406 | * @brief Size of a USB endpoint descriptor in bytes 407 | */ 408 | #define USB_DESC_EP_SIZE 7 409 | 410 | /** 411 | * @brief Structure representing a USB endpoint descriptor 412 | */ 413 | typedef union { 414 | struct { 415 | uint8_t bLength; 416 | uint8_t bDescriptorType; 417 | uint8_t bEndpointAddress; 418 | uint8_t bmAttributes; 419 | uint16_t wMaxPacketSize; 420 | uint8_t bInterval; 421 | } USB_DESC_ATTR; 422 | uint8_t val[USB_DESC_EP_SIZE]; 423 | } usb_desc_ep_t; 424 | _Static_assert(sizeof(usb_desc_ep_t) == USB_DESC_EP_SIZE, "Size of usb_desc_ep_t incorrect"); 425 | 426 | /** 427 | * @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor 428 | */ 429 | #define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK 0x0f 430 | #define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80 431 | 432 | /** 433 | * @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor 434 | */ 435 | #define USB_BM_ATTRIBUTES_XFERTYPE_MASK 0x03 436 | #define USB_BM_ATTRIBUTES_XFER_CONTROL (0 << 0) 437 | #define USB_BM_ATTRIBUTES_XFER_ISOC (1 << 0) 438 | #define USB_BM_ATTRIBUTES_XFER_BULK (2 << 0) 439 | #define USB_BM_ATTRIBUTES_XFER_INT (3 << 0) 440 | #define USB_BM_ATTRIBUTES_SYNCTYPE_MASK 0x0C /* in bmAttributes */ 441 | #define USB_BM_ATTRIBUTES_SYNC_NONE (0 << 2) 442 | #define USB_BM_ATTRIBUTES_SYNC_ASYNC (1 << 2) 443 | #define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE (2 << 2) 444 | #define USB_BM_ATTRIBUTES_SYNC_SYNC (3 << 2) 445 | #define USB_BM_ATTRIBUTES_USAGETYPE_MASK 0x30 446 | #define USB_BM_ATTRIBUTES_USAGE_DATA (0 << 4) 447 | #define USB_BM_ATTRIBUTES_USAGE_FEEDBACK (1 << 4) 448 | #define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB (2 << 4) 449 | 450 | /** 451 | * @brief Macro helpers to get information about an endpoint from its descriptor 452 | */ 453 | #define USB_DESC_EP_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK)) 454 | #define USB_DESC_EP_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) 455 | #define USB_DESC_EP_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0) 456 | #define USB_DESC_EP_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF) 457 | 458 | // ------------------ String Descriptor -------------------- 459 | 460 | /** 461 | * @brief Size of a short USB string descriptor in bytes 462 | */ 463 | #define USB_DESC_STR_SIZE 4 464 | 465 | /** 466 | * @brief Structure representing a USB string descriptor 467 | */ 468 | typedef union { 469 | struct { 470 | uint8_t bLength; 471 | uint8_t bDescriptorType; 472 | uint16_t wData[1]; /* UTF-16LE encoded */ 473 | } USB_DESC_ATTR; 474 | uint8_t val[USB_DESC_STR_SIZE]; 475 | } usb_desc_str_t; 476 | _Static_assert(sizeof(usb_desc_str_t) == USB_DESC_STR_SIZE, "Size of usb_desc_str_t incorrect"); 477 | 478 | #ifdef __cplusplus 479 | } 480 | #endif 481 | -------------------------------------------------------------------------------- /components/usb/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_build_get_property(target IDF_TARGET) 2 | 3 | #USB Host is currently only supported on ESP32-S2, ESP32S3 chips 4 | if(NOT "${target}" MATCHES "^esp32s[2-3]") 5 | return() 6 | endif() 7 | 8 | idf_component_register(SRC_DIRS "hcd" 9 | PRIV_INCLUDE_DIRS "../private_include" "." "hcd" 10 | PRIV_REQUIRES cmock usb test_utils 11 | ) 12 | -------------------------------------------------------------------------------- /components/usb/test/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Component Makefile (not used for tests, but CI checks test parity between GNU Make & CMake) 3 | # 4 | COMPONENT_CONFIG_ONLY := 1 5 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_bulk.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include "freertos/FreeRTOS.h" 18 | #include "freertos/semphr.h" 19 | #include "unity.h" 20 | #include "test_utils.h" 21 | #include "test_hcd_common.h" 22 | 23 | // ------------------------------------------------- Mock MSC SCSI ----------------------------------------------------- 24 | 25 | /* 26 | Note: The following test requires that USB flash drive be connected. The flash drive should... 27 | 28 | - Be implement the Mass Storage class supporting BULK only transfers using SCSI commands 29 | - It's configuration 1 should have the following endpoints 30 | 31 | Endpoint Descriptor: 32 | bLength 7 33 | bDescriptorType 5 34 | bEndpointAddress 0x01 EP 1 OUT 35 | bmAttributes 2 36 | Transfer Type Bulk 37 | Synch Type None 38 | Usage Type Data 39 | wMaxPacketSize 0x0040 1x 64 bytes 40 | bInterval 1 41 | Endpoint Descriptor: 42 | bLength 7 43 | bDescriptorType 5 44 | bEndpointAddress 0x82 EP 2 IN 45 | bmAttributes 2 46 | Transfer Type Bulk 47 | Synch Type None 48 | Usage Type Data 49 | wMaxPacketSize 0x0040 1x 64 bytes 50 | bInterval 1 51 | 52 | If you're using a flash driver with different endpoints, modify the endpoint descriptors below. 53 | */ 54 | 55 | static const usb_desc_ep_t bulk_out_ep_desc = { 56 | .bLength = sizeof(usb_desc_ep_t), 57 | .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, 58 | .bEndpointAddress = 0x01, //EP 1 OUT 59 | .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, 60 | .wMaxPacketSize = 64, //MPS of 64 bytes 61 | .bInterval = 1, 62 | }; 63 | 64 | static const usb_desc_ep_t bulk_in_ep_desc = { 65 | .bLength = sizeof(usb_desc_ep_t), 66 | .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, 67 | .bEndpointAddress = 0x82, //EP 2 IN 68 | .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, 69 | .wMaxPacketSize = 64, //MPS of 64 bytes 70 | .bInterval = 1, 71 | }; 72 | 73 | #define MOCK_MSC_SCSI_SECTOR_SIZE 512 74 | #define MOCK_MSC_SCSI_LUN 0 75 | #define MSC_SCSI_INTR_NUMBER 0 76 | 77 | #define MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \ 78 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_CLASS | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \ 79 | (ctrl_req_ptr)->bRequest = 0xFF; \ 80 | (ctrl_req_ptr)->wValue = 0; \ 81 | (ctrl_req_ptr)->wIndex = (intf_num); \ 82 | (ctrl_req_ptr)->wLength = 0; \ 83 | }) 84 | 85 | typedef struct __attribute__((packed)) { 86 | uint8_t opcode; //0x28 = read(10), 0x2A=write(10) 87 | uint8_t flags; 88 | uint8_t lba_3; 89 | uint8_t lba_2; 90 | uint8_t lba_1; 91 | uint8_t lba_0; 92 | uint8_t group; 93 | uint8_t len_1; 94 | uint8_t len_0; 95 | uint8_t control; 96 | } mock_scsi_cmd10_t; 97 | 98 | typedef struct __attribute__((packed)) { 99 | uint32_t dCBWSignature; 100 | uint32_t dCBWTag; 101 | uint32_t dCBWDataTransferLength; 102 | uint8_t bmCBWFlags; 103 | uint8_t bCBWLUN; 104 | uint8_t bCBWCBLength; 105 | mock_scsi_cmd10_t CBWCB; 106 | uint8_t padding[6]; 107 | } mock_msc_bulk_cbw_t; 108 | 109 | // USB Bulk Transfer Command Status Wrapper data 110 | typedef struct __attribute__((packed)) { 111 | uint32_t dCSWSignature; 112 | uint32_t dCSWTag; 113 | uint32_t dCSWDataResidue; 114 | uint8_t bCSWStatus; 115 | } mock_msc_bulk_csw_t; 116 | 117 | static void mock_msc_reset_req(hcd_pipe_handle_t default_pipe) 118 | { 119 | //Create IRP 120 | usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT); 121 | TEST_ASSERT_NOT_EQUAL(NULL, irp); 122 | irp->data_buffer = heap_caps_malloc(sizeof(usb_ctrl_req_t), MALLOC_CAP_DMA); 123 | TEST_ASSERT_NOT_EQUAL(NULL, irp->data_buffer); 124 | usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)irp->data_buffer; 125 | MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req, MSC_SCSI_INTR_NUMBER); 126 | irp->num_bytes = 0; 127 | //Enqueue, wait, dequeue, and check IRP 128 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp)); 129 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 130 | TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe)); 131 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 132 | //Free IRP 133 | heap_caps_free(irp->data_buffer); 134 | heap_caps_free(irp); 135 | } 136 | 137 | static void mock_msc_scsi_init_cbw(mock_msc_bulk_cbw_t *cbw, bool is_read, int offset, int num_sectors, uint32_t tag) 138 | { 139 | cbw->dCBWSignature = 0x43425355; //Fixed value 140 | cbw->dCBWTag = tag; //Random value that is echoed back 141 | cbw->dCBWDataTransferLength = num_sectors * MOCK_MSC_SCSI_SECTOR_SIZE; 142 | cbw->bmCBWFlags = (is_read) ? (1 << 7) : 0; //If this is a read, set the direction flag 143 | cbw->bCBWLUN = MOCK_MSC_SCSI_LUN; 144 | cbw->bCBWCBLength = 10; //The length of the SCSI command 145 | //Initialize SCSI CMD as READ10 or WRITE 10 146 | cbw->CBWCB.opcode = (is_read) ? 0x28 : 0x2A; //SCSI CMD READ10 or WRITE10 147 | cbw->CBWCB.flags = 0; 148 | cbw->CBWCB.lba_3 = (offset >> 24); 149 | cbw->CBWCB.lba_2 = (offset >> 16); 150 | cbw->CBWCB.lba_1 = (offset >> 8); 151 | cbw->CBWCB.lba_0 = (offset >> 0); 152 | cbw->CBWCB.group = 0; 153 | cbw->CBWCB.len_1 = (num_sectors >> 8); 154 | cbw->CBWCB.len_0 = (num_sectors >> 0); 155 | cbw->CBWCB.control = 0; 156 | } 157 | 158 | static bool mock_msc_scsi_check_csw(mock_msc_bulk_csw_t *csw, uint32_t tag_expect) 159 | { 160 | bool no_issues = true; 161 | if (csw->dCSWSignature != 0x53425355) { 162 | no_issues = false; 163 | printf("Warning: csw signature corrupt (0x%X)\n", csw->dCSWSignature); 164 | } 165 | if (csw->dCSWTag != tag_expect) { 166 | no_issues = false; 167 | printf("Warning: csw tag unexpected! Expected %d got %d\n", tag_expect, csw->dCSWTag); 168 | } 169 | if (csw->dCSWDataResidue) { 170 | no_issues = false; 171 | printf("Warning: csw indicates data residue of %d bytes!\n", csw->dCSWDataResidue); 172 | } 173 | if (csw->bCSWStatus) { 174 | no_issues = false; 175 | printf("Warning: csw indicates non-good status %d!\n", csw->bCSWStatus); 176 | } 177 | return no_issues; 178 | } 179 | 180 | // --------------------------------------------------- Test Cases ------------------------------------------------------ 181 | 182 | /* 183 | Test HCD bulk pipe IRPs 184 | 185 | Purpose: 186 | - Test that a bulk pipe can be created 187 | - IRPs can be created and enqueued to the bulk pipe pipe 188 | - Bulk pipe returns HCD_PIPE_EVENT_IRP_DONE for completed IRPs 189 | - Test utilizes a bare bones (i.e., mock) MSC class using SCSI commands 190 | 191 | Procedure: 192 | - Setup HCD and wait for connection 193 | - Allocate default pipe and enumerate the device 194 | - Allocate separate IRPS for CBW, Data, and CSW transfers of the MSC class 195 | - Read TEST_NUM_SECTORS number of sectors for the mass storage device 196 | - Expect HCD_PIPE_EVENT_IRP_DONE for each IRP 197 | - Deallocate IRPs 198 | - Teardown 199 | */ 200 | 201 | #define TEST_NUM_SECTORS 10 202 | #define TEST_NUM_SECTORS_PER_ITER 2 203 | 204 | TEST_CASE("Test HCD bulk pipe IRPs", "[hcd][ignore]") 205 | { 206 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 207 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 208 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 209 | 210 | //Enumerate and reset MSC SCSI device 211 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor) 212 | uint8_t dev_addr = test_hcd_enum_devc(default_pipe); 213 | mock_msc_reset_req(default_pipe); 214 | 215 | //Create BULK IN and BULK OUT pipes for SCSI 216 | hcd_pipe_handle_t bulk_out_pipe = test_hcd_pipe_alloc(port_hdl, &bulk_out_ep_desc, dev_addr, port_speed); 217 | hcd_pipe_handle_t bulk_in_pipe = test_hcd_pipe_alloc(port_hdl, &bulk_in_ep_desc, dev_addr, port_speed); 218 | //Create IRPs for CBW, Data, and CSW transport. IN Buffer sizes are rounded up to nearest MPS 219 | usb_irp_t *irp_cbw = test_hcd_alloc_irp(0, sizeof(mock_msc_bulk_cbw_t)); 220 | usb_irp_t *irp_data = test_hcd_alloc_irp(0, TEST_NUM_SECTORS_PER_ITER * MOCK_MSC_SCSI_SECTOR_SIZE); 221 | usb_irp_t *irp_csw = test_hcd_alloc_irp(0, sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize))); 222 | irp_cbw->num_bytes = sizeof(mock_msc_bulk_cbw_t); 223 | irp_data->num_bytes = TEST_NUM_SECTORS_PER_ITER * MOCK_MSC_SCSI_SECTOR_SIZE; 224 | irp_csw->num_bytes = sizeof(mock_msc_bulk_csw_t) + (bulk_in_ep_desc.wMaxPacketSize - (sizeof(mock_msc_bulk_csw_t) % bulk_in_ep_desc.wMaxPacketSize)); 225 | 226 | for (int block_num = 0; block_num < TEST_NUM_SECTORS; block_num += TEST_NUM_SECTORS_PER_ITER) { 227 | //Initialize CBW IRP, then send it on the BULK OUT pipe 228 | mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)irp_cbw->data_buffer, true, block_num, TEST_NUM_SECTORS_PER_ITER, 0xAAAAAAAA); 229 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_out_pipe, irp_cbw)); 230 | test_hcd_expect_pipe_event(bulk_out_pipe, HCD_PIPE_EVENT_IRP_DONE); 231 | TEST_ASSERT_EQUAL(irp_cbw, hcd_irp_dequeue(bulk_out_pipe)); 232 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_cbw->status); 233 | //Read data through BULK IN pipe 234 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_in_pipe, irp_data)); 235 | test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_IRP_DONE); 236 | TEST_ASSERT_EQUAL(irp_data, hcd_irp_dequeue(bulk_in_pipe)); 237 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_data->status); 238 | //Read the CSW through BULK IN pipe 239 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(bulk_in_pipe, irp_csw)); 240 | test_hcd_expect_pipe_event(bulk_in_pipe, HCD_PIPE_EVENT_IRP_DONE); 241 | TEST_ASSERT_EQUAL(irp_csw, hcd_irp_dequeue(bulk_in_pipe)); 242 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp_data->status); 243 | TEST_ASSERT_EQUAL(sizeof(mock_msc_bulk_csw_t), irp_csw->actual_num_bytes); 244 | TEST_ASSERT_EQUAL(true, mock_msc_scsi_check_csw((mock_msc_bulk_csw_t *)irp_csw->data_buffer, 0xAAAAAAAA)); 245 | //Print the read data 246 | printf("Block %d to %d:\n", block_num, block_num + TEST_NUM_SECTORS_PER_ITER); 247 | for (int i = 0; i < irp_data->actual_num_bytes; i++) { 248 | printf("0x%02x,", ((char *)irp_data->data_buffer)[i]); 249 | } 250 | printf("\n\n"); 251 | } 252 | 253 | test_hcd_free_irp(irp_cbw); 254 | test_hcd_free_irp(irp_data); 255 | test_hcd_free_irp(irp_csw); 256 | test_hcd_pipe_free(bulk_out_pipe); 257 | test_hcd_pipe_free(bulk_in_pipe); 258 | test_hcd_pipe_free(default_pipe); 259 | //Cleanup 260 | test_hcd_wait_for_disconn(port_hdl, false); 261 | test_hcd_teardown(port_hdl); 262 | } 263 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_common.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | 17 | 18 | //Todo: Move all the port and PHY to here 19 | //Have a separate test file for INTR (HID), ISOC (UVC), and BULK (SCSI) 20 | //Each test case has a fixed HW device 21 | //Implements bare minimum for a MOCK protocol 22 | 23 | #include 24 | #include 25 | #include "freertos/FreeRTOS.h" 26 | #include "freertos/semphr.h" 27 | #include "test_utils.h" 28 | #include "soc/gpio_pins.h" 29 | #include "soc/gpio_sig_map.h" 30 | #include "esp_intr_alloc.h" 31 | #include "esp_err.h" 32 | #include "esp_attr.h" 33 | #include "esp_rom_gpio.h" 34 | #include "hal/usbh_ll.h" 35 | #include "usb.h" 36 | #include "hcd.h" 37 | 38 | #define PORT_NUM 1 39 | #define EVENT_QUEUE_LEN 5 40 | #define ENUM_ADDR 1 //Device address to use for tests that enumerate the device 41 | #define ENUM_CONFIG 1 //Device configuration number to use for tests that enumerate the device 42 | 43 | typedef struct { 44 | hcd_port_handle_t port_hdl; 45 | hcd_port_event_t port_event; 46 | } port_event_msg_t; 47 | 48 | typedef struct { 49 | hcd_pipe_handle_t pipe_hdl; 50 | hcd_pipe_event_t pipe_event; 51 | } pipe_event_msg_t; 52 | 53 | // ---------------------------------------------------- Private -------------------------------------------------------- 54 | 55 | /** 56 | * @brief HCD port callback. Registered when initializing an HCD port 57 | * 58 | * @param port_hdl Port handle 59 | * @param port_event Port event that triggered the callback 60 | * @param user_arg User argument 61 | * @param in_isr Whether callback was called in an ISR context 62 | * @return true ISR should yield after this callback returns 63 | * @return false No yield required (non-ISR context calls should always return false) 64 | */ 65 | static bool port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) 66 | { 67 | //We store the port's queue handle in the port's context variable 68 | void *port_ctx = hcd_port_get_context(port_hdl); 69 | QueueHandle_t port_evt_queue = (QueueHandle_t)port_ctx; 70 | TEST_ASSERT(in_isr); //Current HCD implementation should never call a port callback in a task context 71 | port_event_msg_t msg = { 72 | .port_hdl = port_hdl, 73 | .port_event = port_event, 74 | }; 75 | BaseType_t xTaskWoken = pdFALSE; 76 | xQueueSendFromISR(port_evt_queue, &msg, &xTaskWoken); 77 | return (xTaskWoken == pdTRUE); 78 | } 79 | 80 | /** 81 | * @brief HCD pipe callback. Registered when allocating a HCD pipe 82 | * 83 | * @param pipe_hdl Pipe handle 84 | * @param pipe_event Pipe event that triggered the callback 85 | * @param user_arg User argument 86 | * @param in_isr Whether the callback was called in an ISR context 87 | * @return true ISR should yield after this callback returns 88 | * @return false No yield required (non-ISR context calls should always return false) 89 | */ 90 | static bool pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) 91 | { 92 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)user_arg; 93 | pipe_event_msg_t msg = { 94 | .pipe_hdl = pipe_hdl, 95 | .pipe_event = pipe_event, 96 | }; 97 | if (in_isr) { 98 | BaseType_t xTaskWoken = pdFALSE; 99 | xQueueSendFromISR(pipe_evt_queue, &msg, &xTaskWoken); 100 | return (xTaskWoken == pdTRUE); 101 | } else { 102 | xQueueSend(pipe_evt_queue, &msg, portMAX_DELAY); 103 | return false; 104 | } 105 | } 106 | 107 | // ------------------------------------------------- HCD Event Test ---------------------------------------------------- 108 | 109 | void test_hcd_expect_port_event(hcd_port_handle_t port_hdl, hcd_port_event_t expected_event) 110 | { 111 | //Get the port event queue from the port's context variable 112 | QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl); 113 | TEST_ASSERT_NOT_EQUAL(NULL, port_evt_queue); 114 | //Wait for port callback to send an event message 115 | port_event_msg_t msg; 116 | xQueueReceive(port_evt_queue, &msg, portMAX_DELAY); 117 | //Check the contents of that event message 118 | TEST_ASSERT_EQUAL(port_hdl, msg.port_hdl); 119 | TEST_ASSERT_EQUAL(expected_event, msg.port_event); 120 | printf("\t-> Port event\n"); 121 | } 122 | 123 | void test_hcd_expect_pipe_event(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t expected_event) 124 | { 125 | //Get the pipe's event queue from the pipe's context variable 126 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl); 127 | TEST_ASSERT_NOT_EQUAL(NULL, pipe_evt_queue); 128 | //Wait for pipe callback to send an event message 129 | pipe_event_msg_t msg; 130 | xQueueReceive(pipe_evt_queue, &msg, portMAX_DELAY); 131 | //Check the contents of that event message 132 | TEST_ASSERT_EQUAL(pipe_hdl, msg.pipe_hdl); 133 | TEST_ASSERT_EQUAL(expected_event, msg.pipe_event); 134 | } 135 | 136 | int test_hcd_get_num_port_events(hcd_port_handle_t port_hdl) 137 | { 138 | //Get the port event queue from the port's context variable 139 | QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl); 140 | TEST_ASSERT_NOT_EQUAL(NULL, port_evt_queue); 141 | return EVENT_QUEUE_LEN - uxQueueSpacesAvailable(port_evt_queue); 142 | } 143 | 144 | int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl) 145 | { 146 | //Get the pipe's event queue from the pipe's context variable 147 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl); 148 | TEST_ASSERT_NOT_EQUAL(NULL, pipe_evt_queue); 149 | return EVENT_QUEUE_LEN - uxQueueSpacesAvailable(pipe_evt_queue); 150 | } 151 | 152 | // ----------------------------------------------- Driver/Port Related ------------------------------------------------- 153 | 154 | void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks) 155 | { 156 | vTaskDelay(delay_ticks); 157 | usb_wrap_dev_t *wrap = &USB_WRAP; 158 | if (connected) { 159 | //Swap back to internal PHY that is connected to a device 160 | wrap->otg_conf.phy_sel = 0; 161 | } else { 162 | //Set external PHY input signals to fixed voltage levels mimicking a disconnected state 163 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VP_IDX, false); 164 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VM_IDX, false); 165 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_EXTPHY_RCV_IDX, false); 166 | //Swap to the external PHY 167 | wrap->otg_conf.phy_sel = 1; 168 | } 169 | } 170 | 171 | hcd_port_handle_t test_hcd_setup(void) 172 | { 173 | //Create a queue for port callback to queue up port events 174 | QueueHandle_t port_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(port_event_msg_t)); 175 | TEST_ASSERT_NOT_EQUAL(NULL, port_evt_queue); 176 | //Install HCD 177 | hcd_config_t hcd_config = { 178 | .intr_flags = ESP_INTR_FLAG_LEVEL1, 179 | }; 180 | TEST_ASSERT_EQUAL(ESP_OK, hcd_install(&hcd_config)); 181 | //Initialize a port 182 | hcd_port_config_t port_config = { 183 | .callback = port_callback, 184 | .callback_arg = (void *)port_evt_queue, 185 | .context = (void *)port_evt_queue, 186 | }; 187 | hcd_port_handle_t port_hdl; 188 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_init(PORT_NUM, &port_config, &port_hdl)); 189 | TEST_ASSERT_NOT_EQUAL(NULL, port_hdl); 190 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl)); 191 | test_hcd_force_conn_state(false, 0); //Force disconnected state on PHY 192 | return port_hdl; 193 | } 194 | 195 | void test_hcd_teardown(hcd_port_handle_t port_hdl) 196 | { 197 | //Get the queue handle from the port's context variable 198 | QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl); 199 | TEST_ASSERT_NOT_EQUAL(NULL, port_evt_queue); 200 | //Deinitialize a port 201 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_deinit(port_hdl)); 202 | //Uninstall the HCD 203 | TEST_ASSERT_EQUAL(ESP_OK, hcd_uninstall()); 204 | vQueueDelete(port_evt_queue); 205 | } 206 | 207 | usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl) 208 | { 209 | //Power ON the port 210 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_ON)); 211 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl)); 212 | //Wait for connection event 213 | printf("Waiting for connection\n"); 214 | test_hcd_force_conn_state(true, pdMS_TO_TICKS(100)); //Allow for connected state on PHY 215 | test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_CONNECTION); 216 | TEST_ASSERT_EQUAL(HCD_PORT_EVENT_CONNECTION, hcd_port_handle_event(port_hdl)); 217 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl)); 218 | //Reset newly connected device 219 | printf("Resetting\n"); 220 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESET)); 221 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); 222 | //Get speed of connected 223 | usb_speed_t port_speed; 224 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_get_speed(port_hdl, &port_speed)); 225 | if (port_speed == USB_SPEED_FULL) { 226 | printf("Full speed enabled\n"); 227 | } else { 228 | printf("Low speed enabled\n"); 229 | } 230 | return port_speed; 231 | } 232 | 233 | void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled) 234 | { 235 | if (!already_disabled) { 236 | //Disable the device 237 | printf("Disabling\n"); 238 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE)); 239 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl)); 240 | } 241 | //Wait for a safe disconnect 242 | printf("Waiting for disconnection\n"); 243 | test_hcd_force_conn_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY 244 | test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); 245 | TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); 246 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl)); 247 | //Power down the port 248 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF)); 249 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl)); 250 | } 251 | 252 | // ---------------------------------------------- Pipe Setup/Tear-down ------------------------------------------------- 253 | 254 | hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_desc_ep_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed) 255 | { 256 | //Create a queue for pipe callback to queue up pipe events 257 | QueueHandle_t pipe_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(pipe_event_msg_t)); 258 | TEST_ASSERT_NOT_EQUAL(NULL, pipe_evt_queue); 259 | printf("Creating pipe\n"); 260 | hcd_pipe_config_t pipe_config = { 261 | .callback = pipe_callback, 262 | .callback_arg = (void *)pipe_evt_queue, 263 | .context = (void *)pipe_evt_queue, 264 | .ep_desc = ep_desc, 265 | .dev_addr = dev_addr, 266 | .dev_speed = dev_speed, 267 | }; 268 | hcd_pipe_handle_t pipe_hdl; 269 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_alloc(port_hdl, &pipe_config, &pipe_hdl)); 270 | TEST_ASSERT_NOT_EQUAL(NULL, pipe_hdl); 271 | return pipe_hdl; 272 | } 273 | 274 | void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl) 275 | { 276 | //Get the pipe's event queue from its context variable 277 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl); 278 | TEST_ASSERT_NOT_EQUAL(NULL, pipe_evt_queue); 279 | //Free the pipe and queue 280 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_free(pipe_hdl)); 281 | vQueueDelete(pipe_evt_queue); 282 | } 283 | 284 | usb_irp_t *test_hcd_alloc_irp(int num_iso_packets, size_t data_buffer_size) 285 | { 286 | //Allocate list of IRPs 287 | usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t) + (num_iso_packets * sizeof(usb_iso_packet_desc_t)), MALLOC_CAP_DEFAULT); 288 | TEST_ASSERT_NOT_EQUAL(NULL, irp); 289 | //Allocate data buffer for each IRP and assign them 290 | uint8_t *data_buffer = heap_caps_malloc(data_buffer_size, MALLOC_CAP_DMA); 291 | TEST_ASSERT_NOT_EQUAL(NULL, data_buffer); 292 | irp->data_buffer = data_buffer; 293 | irp->num_iso_packets = num_iso_packets; 294 | return irp; 295 | } 296 | 297 | void test_hcd_free_irp(usb_irp_t *irp) 298 | { 299 | //Free data buffers of each IRP 300 | heap_caps_free(irp->data_buffer); 301 | //Free the IRP list 302 | heap_caps_free(irp); 303 | } 304 | 305 | uint8_t test_hcd_enum_devc(hcd_pipe_handle_t default_pipe) 306 | { 307 | //We need to create an IRP for the enumeration control transfers 308 | usb_irp_t *irp = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT); 309 | TEST_ASSERT_NOT_EQUAL(NULL, irp); 310 | //We use a single data buffer for all control transfers during enumerations. 256 bytes should be large enough for most descriptors 311 | irp->data_buffer = heap_caps_malloc(sizeof(usb_ctrl_req_t) + 256, MALLOC_CAP_DMA); 312 | TEST_ASSERT_NOT_EQUAL(NULL, irp->data_buffer); 313 | usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)irp->data_buffer; 314 | 315 | //Get the device descriptor (note that device might only return 8 bytes) 316 | USB_CTRL_REQ_INIT_GET_DEVC_DESC(ctrl_req); 317 | irp->num_bytes = sizeof(usb_desc_devc_t); 318 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp)); 319 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 320 | TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe)); 321 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 322 | 323 | //Update the MPS of the default pipe 324 | usb_desc_devc_t *devc_desc = (usb_desc_devc_t *)(irp->data_buffer + sizeof(usb_ctrl_req_t)); 325 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, devc_desc->bMaxPacketSize0)); 326 | 327 | //Send a set address request 328 | USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req, ENUM_ADDR); //We only support one device for now so use address 1 329 | irp->num_bytes = 0; 330 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp)); 331 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 332 | TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe)); 333 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 334 | 335 | //Update address of default pipe 336 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR)); 337 | 338 | //Send a set configuration request 339 | USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req, ENUM_CONFIG); 340 | irp->num_bytes = 0; 341 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp)); 342 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 343 | TEST_ASSERT_EQUAL(irp, hcd_irp_dequeue(default_pipe)); 344 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 345 | 346 | //Free IRP 347 | heap_caps_free(irp->data_buffer); 348 | heap_caps_free(irp); 349 | return ENUM_ADDR; 350 | } 351 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "freertos/FreeRTOS.h" 16 | #include "freertos/semphr.h" 17 | #include "usb.h" 18 | #include "hcd.h" 19 | 20 | #define IRP_CONTEXT_VAL ((void *)0xDEADBEEF) 21 | 22 | // ------------------------------------------------- HCD Event Test ---------------------------------------------------- 23 | 24 | /** 25 | * @brief Expect (wait) for an HCD port event 26 | * 27 | * @param port_hdl Port handle to expect event from 28 | * @param expected_event Port event to expect 29 | */ 30 | void test_hcd_expect_port_event(hcd_port_handle_t port_hdl, hcd_port_event_t expected_event); 31 | 32 | /** 33 | * @brief Expect (wait) for an HCD pipe event 34 | * 35 | * @param pipe_hdl Pipe handle to expect event from 36 | * @param expected_event Pipe event to expect 37 | */ 38 | void test_hcd_expect_pipe_event(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t expected_event); 39 | 40 | /** 41 | * @brief Get the current number of queued port events (dequeued using test_hcd_expect_port_event()) 42 | * 43 | * @param port_hdl Port handle 44 | * @return int Number of port events currently queued 45 | */ 46 | int test_hcd_get_num_port_events(hcd_port_handle_t port_hdl); 47 | 48 | /** 49 | * @brief Get the current number of queued pipe events (dequeued using test_hcd_expect_pipe_event()) 50 | * 51 | * @param pipe_hdl Pipe handle 52 | * @return int Number of pipe events currently queued 53 | */ 54 | int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl); 55 | 56 | // ----------------------------------------------- Driver/Port Related ------------------------------------------------- 57 | 58 | /** 59 | * @brief For the USB PHY into the connected or disconnected state 60 | * 61 | * @param connected For into connected state if true, disconnected if false 62 | * @param delay_ticks Delay in ticks before forcing state 63 | */ 64 | void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks); 65 | 66 | /** 67 | * @brief Sets up the HCD and initializes an HCD port. 68 | * 69 | * @return hcd_port_handle_t Port handle 70 | */ 71 | hcd_port_handle_t test_hcd_setup(void); 72 | 73 | /** 74 | * @brief Frees and HCD port and uninstalls the HCD 75 | * 76 | * @param port_hdl Port handle 77 | */ 78 | void test_hcd_teardown(hcd_port_handle_t port_hdl); 79 | 80 | /** 81 | * @brief Wait for a connection on an HCD port 82 | * 83 | * @note This function will internally call test_hcd_force_conn_state() to allow for a connection 84 | * 85 | * @param port_hdl Port handle 86 | * @return usb_speed_t Speed of the connected device 87 | */ 88 | usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl); 89 | 90 | /** 91 | * @brief Wait for a disconnection on an HCD port 92 | * 93 | * @note This fucntion will internally call test_hcd_force_conn_state() to force a disconnection 94 | * 95 | * @param port_hdl Port handle 96 | * @param already_disabled Whether the HCD port is already in the disabled state 97 | */ 98 | void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled); 99 | 100 | // ------------------------------------------------- Pipe alloc/free --------------------------------------------------- 101 | 102 | /** 103 | * @brief Test the allocation of a pipe 104 | * 105 | * @param port_hdl Port handle 106 | * @param ep_desc Endpoint descriptor 107 | * @param dev_addr Device address of the pipe 108 | * @param dev_speed Device speed of the pipe 109 | * @return hcd_pipe_handle_t Pipe handle 110 | */ 111 | hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_desc_ep_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed); 112 | 113 | /** 114 | * @brief Test the freeing of a pipe 115 | * 116 | * @param pipe_hdl Pipe handle 117 | */ 118 | void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl); 119 | 120 | /** 121 | * @brief Allocate an IRP 122 | * 123 | * @param num_iso_packets Number of isochronous packets 124 | * @param data_buffer_size Size of the data buffer of the IRP 125 | * @return usb_irp_t* IRP 126 | */ 127 | usb_irp_t *test_hcd_alloc_irp(int num_iso_packets, size_t data_buffer_size); 128 | 129 | /** 130 | * @brief Free an IRP 131 | * 132 | * @param irp IRP 133 | */ 134 | void test_hcd_free_irp(usb_irp_t *irp); 135 | 136 | // --------------------------------------------------- Enumeration ----------------------------------------------------- 137 | 138 | /** 139 | * @brief Do some basic enumeration of the device 140 | * 141 | * For tests that need a device to have been enumerated (such as bulk tests). This function will enumerate that device 142 | * using the device's default pipe. The minimal enumeration will include 143 | * 144 | * - Getting the device's descriptor and updating the default pipe's MPS 145 | * - Setting the device's address and updating the default pipe to use that address 146 | * - Setting the device to configuration 1 (i.e., the first configuration found 147 | * 148 | * @param default_pipe The connected device's default pipe 149 | * @return uint8_t The address of the device after enumeration 150 | */ 151 | uint8_t test_hcd_enum_devc(hcd_pipe_handle_t default_pipe); 152 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_ctrl.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "freertos/FreeRTOS.h" 17 | #include "freertos/semphr.h" 18 | #include "unity.h" 19 | #include "test_utils.h" 20 | #include "test_hcd_common.h" 21 | 22 | #define TEST_DEV_ADDR 0 23 | #define NUM_IRPS 3 24 | #define TRANSFER_MAX_BYTES 256 25 | #define IRP_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors 26 | 27 | /* 28 | Test HCD control pipe IRPs (normal completion and early abort) 29 | 30 | Purpose: 31 | - Test that a control pipe can be created 32 | - IRPs can be created and enqueued to the control pipe 33 | - Control pipe returns HCD_PIPE_EVENT_IRP_DONE 34 | - Test that IRPs can be aborted when enqueued 35 | 36 | Procedure: 37 | - Setup HCD and wait for connection 38 | - Setup default pipe and allocate IRPs 39 | - Enqueue IRPs 40 | - Expect HCD_PIPE_EVENT_IRP_DONE 41 | - Requeue IRPs, but abort them immediately 42 | - Expect IRP to be USB_TRANSFER_STATUS_CANCELED or USB_TRANSFER_STATUS_COMPLETED 43 | - Teardown 44 | */ 45 | TEST_CASE("Test HCD control pipe IRPs", "[hcd][ignore]") 46 | { 47 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 48 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 49 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 50 | 51 | //Allocate some IRPs and initialize their data buffers with control transfers 52 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 53 | usb_irp_t *irp_list[NUM_IRPS]; 54 | for (int i = 0; i < NUM_IRPS; i++) { 55 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 56 | //Initialize with a "Get Config Descriptor request" 57 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 58 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 59 | irp_list[i]->context = IRP_CONTEXT_VAL; 60 | } 61 | 62 | //Enqueue IRPs but immediately suspend the port 63 | printf("Enqueuing IRPs\n"); 64 | for (int i = 0; i < NUM_IRPS; i++) { 65 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 66 | } 67 | //Wait for each done event of each IRP 68 | for (int i = 0; i < NUM_IRPS; i++) { 69 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 70 | } 71 | //Dequeue IRPs 72 | for (int i = 0; i < NUM_IRPS; i++) { 73 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 74 | TEST_ASSERT_EQUAL(irp_list[i], irp); 75 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 76 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 77 | } 78 | 79 | //Enqueue IRPs again but abort them short after 80 | for (int i = 0; i < NUM_IRPS; i++) { 81 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 82 | } 83 | for (int i = 0; i < NUM_IRPS; i++) { 84 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_abort(irp_list[i])); 85 | } 86 | vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for any inflight transfers to complete 87 | 88 | //Wait for the IRPs to complete and dequeue them, then check results 89 | //Dequeue IRPs 90 | for (int i = 0; i < NUM_IRPS; i++) { 91 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 92 | //No need to check for IRP pointer address as they may be out of order 93 | TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_CANCELED); 94 | if (irp->status == USB_TRANSFER_STATUS_COMPLETED) { 95 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 96 | } else { 97 | TEST_ASSERT_EQUAL(0, irp->actual_num_bytes); 98 | } 99 | TEST_ASSERT_EQUAL(irp->context, IRP_CONTEXT_VAL); 100 | } 101 | 102 | //Free IRP list and pipe 103 | for (int i = 0; i < NUM_IRPS; i++) { 104 | test_hcd_free_irp(irp_list[i]); 105 | } 106 | test_hcd_pipe_free(default_pipe); 107 | //Cleanup 108 | test_hcd_wait_for_disconn(port_hdl, false); 109 | test_hcd_teardown(port_hdl); 110 | } 111 | 112 | /* 113 | Test HCD control pipe STALL condition, abort, and clear 114 | 115 | Purpose: 116 | - Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_HALTED event) 117 | - The HCD_PIPE_CMD_ABORT can retire all IRPs 118 | - Pipe clear command can return the pipe to being active 119 | 120 | Procedure: 121 | - Setup HCD and wait for connection 122 | - Setup default pipe and allocate IRPs 123 | - Corrupt the first IRP so that it will trigger a STALL, then enqueue all the IRPs 124 | - Check that a HCD_PIPE_EVENT_ERROR_STALL event is triggered 125 | - Check that all IRPs can be retired using HCD_PIPE_CMD_ABORT 126 | - Check that the STALL can be cleared by using HCD_PIPE_CMD_CLEAR 127 | - Fix the corrupt first IRP and retry the IRPs 128 | - Dequeue IRPs 129 | - Teardown 130 | */ 131 | TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]") 132 | { 133 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 134 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 135 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 136 | 137 | //Allocate some IRPs and initialize their data buffers with control transfers 138 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 139 | usb_irp_t *irp_list[NUM_IRPS]; 140 | for (int i = 0; i < NUM_IRPS; i++) { 141 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 142 | //Initialize with a "Get Config Descriptor request" 143 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 144 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 145 | irp_list[i]->context = IRP_CONTEXT_VAL; 146 | } 147 | //Corrupt the first IRP so that it triggers a STALL 148 | ((usb_ctrl_req_t *)irp_list[0]->data_buffer)->bRequest = 0xAA; 149 | 150 | //Enqueue IRPs. A STALL should occur 151 | for (int i = 0; i < NUM_IRPS; i++) { 152 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 153 | } 154 | printf("Expecting STALL\n"); 155 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_ERROR_STALL); 156 | TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); 157 | 158 | //Call the pipe abort command to retire all IRPs then dequeue them all 159 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_ABORT)); 160 | for (int i = 0; i < NUM_IRPS; i++) { 161 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 162 | TEST_ASSERT_EQUAL(irp_list[i], irp); 163 | TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_STALL || irp->status == USB_TRANSFER_STATUS_CANCELED); 164 | if (irp->status == USB_TRANSFER_STATUS_COMPLETED) { 165 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 166 | } else { 167 | TEST_ASSERT_EQUAL(0, irp->actual_num_bytes); 168 | } 169 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 170 | } 171 | 172 | //Call the clear command to un-stall the pipe 173 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); 174 | TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); 175 | 176 | printf("Retrying\n"); 177 | //Correct first IRP then requeue 178 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[0]->data_buffer, 0, TRANSFER_MAX_BYTES); 179 | for (int i = 0; i < NUM_IRPS; i++) { 180 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 181 | } 182 | 183 | //Wait for each IRP to be done, deequeue, and check results 184 | for (int i = 0; i < NUM_IRPS; i++) { 185 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 186 | //expect_pipe_event(pipe_evt_queue, default_pipe, HCD_PIPE_EVENT_IRP_DONE); 187 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 188 | TEST_ASSERT_EQUAL(irp_list[i], irp); 189 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 190 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 191 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 192 | } 193 | 194 | //Free IRP list and pipe 195 | for (int i = 0; i < NUM_IRPS; i++) { 196 | test_hcd_free_irp(irp_list[i]); 197 | } 198 | test_hcd_pipe_free(default_pipe); 199 | //Cleanup 200 | test_hcd_wait_for_disconn(port_hdl, false); 201 | test_hcd_teardown(port_hdl); 202 | } 203 | 204 | /* 205 | Test control pipe run-time halt and clear 206 | 207 | Purpose: 208 | - Test that a control pipe can be halted with HCD_PIPE_CMD_HALT whilst there are ongoing IRPs 209 | - Test that a control pipe can be un-halted with a HCD_PIPE_CMD_CLEAR 210 | - Test that enqueued IRPs are resumed when pipe is un-halted 211 | 212 | Procedure: 213 | - Setup HCD and wait for connection 214 | - Setup default pipe and allocate IRPs 215 | - Enqqueue IRPs but execute a HCD_PIPE_CMD_HALT command immediately after. Halt command should let on 216 | the current going IRP finish before actually halting the pipe. 217 | - Un-halt the pipe a HCD_PIPE_CMD_HALT command. Enqueued IRPs will be resumed 218 | - Check that all IRPs have completed successfully 219 | - Dequeue IRPs and teardown 220 | */ 221 | TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]") 222 | { 223 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 224 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 225 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 226 | 227 | //Allocate some IRPs and initialize their data buffers with control transfers 228 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 229 | usb_irp_t *irp_list[NUM_IRPS]; 230 | for (int i = 0; i < NUM_IRPS; i++) { 231 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 232 | //Initialize with a "Get Config Descriptor request" 233 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 234 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 235 | irp_list[i]->context = IRP_CONTEXT_VAL; 236 | } 237 | 238 | //Enqueue IRPs but immediately halt the pipe 239 | printf("Enqueuing IRPs\n"); 240 | for (int i = 0; i < NUM_IRPS; i++) { 241 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 242 | } 243 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); 244 | TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); 245 | printf("Pipe halted\n"); 246 | 247 | //Un-halt the pipe 248 | TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); 249 | TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); 250 | printf("Pipe cleared\n"); 251 | vTaskDelay(pdMS_TO_TICKS(100)); //Give some time pending for transfers to restart and complete 252 | 253 | //Wait for each IRP to be done, dequeue, and check results 254 | for (int i = 0; i < NUM_IRPS; i++) { 255 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 256 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 257 | TEST_ASSERT_EQUAL(irp_list[i], irp); 258 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 259 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 260 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 261 | } 262 | 263 | //Free IRP list and pipe 264 | for (int i = 0; i < NUM_IRPS; i++) { 265 | test_hcd_free_irp(irp_list[i]); 266 | } 267 | test_hcd_pipe_free(default_pipe); 268 | //Cleanup 269 | test_hcd_wait_for_disconn(port_hdl, false); 270 | test_hcd_teardown(port_hdl); 271 | } 272 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_intr.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "freertos/FreeRTOS.h" 17 | #include "freertos/semphr.h" 18 | #include "unity.h" 19 | #include "test_utils.h" 20 | #include "test_hcd_common.h" 21 | 22 | // ------------------------------------------------- Mock HID Mice ----------------------------------------------------- 23 | 24 | /* 25 | Note: The following test requires that USB low speed mouse be connected. The mouse should... 26 | 27 | - Be implement the HID with standard report format used by mice 28 | - It's configuration 1 should have the following endpoint 29 | 30 | Endpoint Descriptor: 31 | bLength 7 32 | bDescriptorType 5 33 | bEndpointAddress 0x81 EP 1 IN 34 | bmAttributes 3 35 | Transfer Type Interrupt 36 | Synch Type None 37 | Usage Type Data 38 | wMaxPacketSize 0x0004 1x 4 bytes 39 | bInterval 10 40 | 41 | If you're using another mice with different endpoints, modify the endpoint descriptor below 42 | */ 43 | static const usb_desc_ep_t in_ep_desc = { 44 | .bLength = sizeof(usb_desc_ep_t), 45 | .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, 46 | .bEndpointAddress = 0x81, //EP 1 IN 47 | .bmAttributes = USB_BM_ATTRIBUTES_XFER_INT, 48 | .wMaxPacketSize = 4, //MPS of 4 bytes 49 | .bInterval = 10, //Interval of 10ms 50 | }; 51 | 52 | typedef union { 53 | struct { 54 | uint32_t left_button: 1; 55 | uint32_t right_button: 1; 56 | uint32_t middle_button: 1; 57 | uint32_t reserved5: 5; 58 | uint8_t x_movement; 59 | uint8_t y_movement; 60 | } __attribute__((packed)); 61 | uint8_t val[3]; 62 | } mock_hid_mouse_report_t; 63 | _Static_assert(sizeof(mock_hid_mouse_report_t) == 3, "Size of HID mouse report incorrect"); 64 | 65 | static void mock_hid_process_report(mock_hid_mouse_report_t *report, int iter) 66 | { 67 | static int x_pos = 0; 68 | static int y_pos = 0; 69 | //Update X position 70 | if (report->x_movement & 0x80) { //Positive movement 71 | x_pos += report->x_movement & 0x7F; 72 | } else { //Negative movement 73 | x_pos -= report->x_movement & 0x7F; 74 | } 75 | //Update Y position 76 | if (report->y_movement & 0x80) { //Positive movement 77 | y_pos += report->y_movement & 0x7F; 78 | } else { //Negative movement 79 | y_pos -= report->y_movement & 0x7F; 80 | } 81 | printf("\rX:%d\tY:%d\tIter: %d\n", x_pos, y_pos, iter); 82 | } 83 | 84 | // --------------------------------------------------- Test Cases ------------------------------------------------------ 85 | 86 | /* 87 | Test HCD interrupt pipe IRPs 88 | Purpose: 89 | - Test that an interrupt pipe can be created 90 | - IRPs can be created and enqueued to the interrupt pipe 91 | - Interrupt pipe returns HCD_PIPE_EVENT_IRP_DONE 92 | - Test that IRPs can be aborted when enqueued 93 | 94 | Procedure: 95 | - Setup HCD and wait for connection 96 | - Allocate default pipe and enumerate the device 97 | - Setup interrupt pipe and allocate IRPs 98 | - Enqueue IRPs, expect HCD_PIPE_EVENT_IRP_DONE, and requeue 99 | - Stop after fixed number of iterations 100 | - Deallocate IRPs 101 | - Teardown 102 | 103 | Note: Some mice will NAK until it is moved, so try moving the mouse around if this test case gets stuck. 104 | */ 105 | 106 | #define TEST_HID_DEV_SPEED USB_SPEED_LOW 107 | #define NUM_IRPS 3 108 | #define IRP_DATA_BUFF_SIZE 4 //MPS is 4 109 | #define MOCK_HID_NUM_REPORT_PER_IRP 2 110 | #define NUM_IRP_ITERS (NUM_IRPS * 100) 111 | 112 | TEST_CASE("Test HCD interrupt pipe IRPs", "[hcd][ignore]") 113 | { 114 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 115 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 116 | TEST_ASSERT_EQUAL(TEST_HID_DEV_SPEED, TEST_HID_DEV_SPEED); 117 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 118 | 119 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor) 120 | uint8_t dev_addr = test_hcd_enum_devc(default_pipe); 121 | 122 | //Allocate interrupt pipe and IRPS 123 | hcd_pipe_handle_t intr_pipe = test_hcd_pipe_alloc(port_hdl, &in_ep_desc, dev_addr, port_speed); 124 | usb_irp_t *irp_list[NUM_IRPS]; 125 | for (int i = 0; i < NUM_IRPS; i++) { 126 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 127 | irp_list[i]->num_bytes = IRP_DATA_BUFF_SIZE; 128 | irp_list[i]->context = IRP_CONTEXT_VAL; 129 | } 130 | 131 | //Enqueue IRPs 132 | for (int i = 0; i < NUM_IRPS; i++) { 133 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(intr_pipe, irp_list[i])); 134 | } 135 | int iter_count = NUM_IRP_ITERS; 136 | for (iter_count = NUM_IRP_ITERS; iter_count > 0; iter_count--) { 137 | //Wait for an IRP to be done 138 | test_hcd_expect_pipe_event(intr_pipe, HCD_PIPE_EVENT_IRP_DONE); 139 | //Dequeue the IRP and check results 140 | usb_irp_t *irp = hcd_irp_dequeue(intr_pipe); 141 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->status); 142 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 143 | mock_hid_process_report((mock_hid_mouse_report_t *)irp->data_buffer, iter_count); 144 | //Requeue IRP 145 | if (iter_count > NUM_IRPS) { 146 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(intr_pipe, irp)); 147 | } 148 | } 149 | 150 | //Free IRP list and pipe 151 | for (int i = 0; i < NUM_IRPS; i++) { 152 | test_hcd_free_irp(irp_list[i]); 153 | } 154 | test_hcd_pipe_free(intr_pipe); 155 | test_hcd_pipe_free(default_pipe); 156 | //Clearnup 157 | test_hcd_wait_for_disconn(port_hdl, false); 158 | test_hcd_teardown(port_hdl); 159 | } 160 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_isoc.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include "freertos/FreeRTOS.h" 18 | #include "freertos/semphr.h" 19 | #include "unity.h" 20 | #include "test_utils.h" 21 | #include "test_hcd_common.h" 22 | 23 | //We talk to a non-existent device. Since ISOC out requires no ACK, there should be no errors. 24 | #define MOCK_ISOC_EP_NUM 2 25 | #define MOCK_ISOC_EP_MPS 512 26 | 27 | #define NUM_IRPS 3 28 | #define NUM_PACKETS_PER_IRP 3 29 | #define ISOC_PACKET_SIZE MOCK_ISOC_EP_MPS 30 | #define IRP_DATA_BUFF_SIZE (NUM_PACKETS_PER_IRP * ISOC_PACKET_SIZE) 31 | 32 | static const usb_desc_ep_t isoc_out_ep_desc = { 33 | .bLength = sizeof(usb_desc_ep_t), 34 | .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, 35 | .bEndpointAddress = MOCK_ISOC_EP_NUM, 36 | .bmAttributes = USB_BM_ATTRIBUTES_XFER_ISOC, 37 | .wMaxPacketSize = MOCK_ISOC_EP_MPS, //MPS of 512 bytes 38 | .bInterval = 1, //Isoc interval is (2 ^ (bInterval - 1)) which means an interval of 1ms 39 | }; 40 | 41 | /* 42 | Test HCD ISOC pipe IRPs 43 | 44 | Purpose: 45 | - Test that an isochronous pipe can be created 46 | - IRPs can be created and enqueued to the isoc pipe pipe 47 | - isoc pipe returns HCD_PIPE_EVENT_IRP_DONE for completed IRPs 48 | - Test utilizes ISOC OUT transfers and do not require ACKs. So the isoc pipe will target a non existing endpoint 49 | 50 | Procedure: 51 | - Setup HCD and wait for connection 52 | - Allocate default pipe and enumerate the device 53 | - Allocate an isochronous pipe and multiple IRPs. Each IRP should contain multiple packets to test HCD's ability to 54 | schedule an IRP across multiple intervals. 55 | - Enqueue those IRPs 56 | - Expect HCD_PIPE_EVENT_IRP_DONE for each IRP. Verify that data is correct using logic analyzer 57 | - Deallocate IRPs 58 | - Teardown 59 | */ 60 | 61 | TEST_CASE("Test HCD isochronous pipe IRPs", "[hcd][ignore]") 62 | { 63 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 64 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 65 | //The MPS of the ISOC OUT pipe is quite large, so we need to bias the FIFO sizing 66 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_set_fifo_bias(port_hdl, HCD_PORT_FIFO_BIAS_PTX)); 67 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 68 | 69 | //Enumerate and reset device 70 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor) 71 | uint8_t dev_addr = test_hcd_enum_devc(default_pipe); 72 | 73 | //Create ISOC OUT pipe to non-existent device 74 | hcd_pipe_handle_t isoc_out_pipe = test_hcd_pipe_alloc(port_hdl, &isoc_out_ep_desc, dev_addr + 1, port_speed); 75 | //Create IRPs 76 | usb_irp_t *irp_list[NUM_IRPS]; 77 | //Initialize IRPs 78 | for (int irp_idx = 0; irp_idx < NUM_IRPS; irp_idx++) { 79 | irp_list[irp_idx] = test_hcd_alloc_irp(NUM_PACKETS_PER_IRP, IRP_DATA_BUFF_SIZE); 80 | irp_list[irp_idx]->num_bytes = 0; //num_bytes is not used for ISOC 81 | irp_list[irp_idx]->context = IRP_CONTEXT_VAL; 82 | for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_IRP; pkt_idx++) { 83 | irp_list[irp_idx]->iso_packet_desc[pkt_idx].length = ISOC_PACKET_SIZE; 84 | //Each packet will consist of the same byte, but each subsequent packet's byte will increment (i.e., packet 0 transmits all 0x0, packet 1 transmits all 0x1) 85 | memset(&irp_list[irp_idx]->data_buffer[pkt_idx * ISOC_PACKET_SIZE], (irp_idx * NUM_IRPS) + pkt_idx, ISOC_PACKET_SIZE); 86 | } 87 | } 88 | //Enqueue IRPs 89 | for (int i = 0; i < NUM_IRPS; i++) { 90 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(isoc_out_pipe, irp_list[i])); 91 | } 92 | //Wait for each done event from each IRP 93 | for (int i = 0; i < NUM_IRPS; i++) { 94 | test_hcd_expect_pipe_event(isoc_out_pipe, HCD_PIPE_EVENT_IRP_DONE); 95 | } 96 | //Dequeue IRPs 97 | for (int irp_idx = 0; irp_idx < NUM_IRPS; irp_idx++) { 98 | usb_irp_t *irp = hcd_irp_dequeue(isoc_out_pipe); 99 | TEST_ASSERT_EQUAL(irp_list[irp_idx], irp); 100 | TEST_ASSERT_EQUAL(IRP_CONTEXT_VAL, irp->context); 101 | for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_IRP; pkt_idx++) { 102 | TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, irp->iso_packet_desc[pkt_idx].status); 103 | } 104 | } 105 | //Free IRP list and pipe 106 | for (int i = 0; i < NUM_IRPS; i++) { 107 | test_hcd_free_irp(irp_list[i]); 108 | } 109 | test_hcd_pipe_free(isoc_out_pipe); 110 | test_hcd_pipe_free(default_pipe); 111 | //Cleanup 112 | test_hcd_wait_for_disconn(port_hdl, false); 113 | test_hcd_teardown(port_hdl); 114 | } 115 | -------------------------------------------------------------------------------- /components/usb/test/hcd/test_hcd_port.c: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "freertos/FreeRTOS.h" 17 | #include "freertos/semphr.h" 18 | #include "unity.h" 19 | #include "test_utils.h" 20 | #include "test_hcd_common.h" 21 | 22 | #define TEST_DEV_ADDR 0 23 | #define NUM_IRPS 3 24 | #define TRANSFER_MAX_BYTES 256 25 | #define IRP_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors 26 | 27 | /* 28 | Test a port sudden disconnect and port recovery 29 | 30 | Purpose: Test that when sudden disconnection happens on an HCD port, the port will 31 | - Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state 32 | - Ongoing IRPs and pipes are handled correctly 33 | 34 | Procedure: 35 | - Setup the HCD and a port 36 | - Trigger a port connection 37 | - Create a default pipe 38 | - Start transfers but immediately trigger a disconnect 39 | - Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle the event. 40 | - Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue IRPs 41 | - Free default pipe 42 | - Recover the port 43 | - Trigger connection and disconnection again (to make sure the port works post recovery) 44 | - Teardown port and HCD 45 | */ 46 | 47 | TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") 48 | { 49 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 50 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 51 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 52 | 53 | //Allocate some IRPs and initialize their data buffers with control transfers 54 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 55 | usb_irp_t *irp_list[NUM_IRPS]; 56 | for (int i = 0; i < NUM_IRPS; i++) { 57 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 58 | //Initialize with a "Get Config Descriptor request" 59 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 60 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 61 | irp_list[i]->context = (void *)0xDEADBEEF; 62 | } 63 | 64 | //Enqueue IRPs but immediately trigger a disconnect 65 | printf("Enqueuing IRPs\n"); 66 | for (int i = 0; i < NUM_IRPS; i++) { 67 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 68 | } 69 | test_hcd_force_conn_state(false, 0); 70 | //Disconnect event should have occurred. Handle the event 71 | test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN); 72 | TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl)); 73 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); 74 | printf("Sudden disconnect\n"); 75 | 76 | //Pipe should have received (zero or more HCD_PIPE_EVENT_IRP_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR) 77 | int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe); 78 | for (int i = 0; i < num_pipe_events - 1; i++) { 79 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 80 | } 81 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID); 82 | TEST_ASSERT_EQUAL(hcd_pipe_get_state(default_pipe), HCD_PIPE_STATE_INVALID); 83 | 84 | //Dequeue IRPs 85 | for (int i = 0; i < NUM_IRPS; i++) { 86 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 87 | TEST_ASSERT_EQUAL(irp_list[i], irp); 88 | TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE); 89 | if (irp->status == USB_TRANSFER_STATUS_COMPLETED) { 90 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 91 | } else { 92 | TEST_ASSERT_EQUAL(0, irp->actual_num_bytes); 93 | } 94 | TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context); 95 | } 96 | //Free IRP list and pipe 97 | for (int i = 0; i < NUM_IRPS; i++) { 98 | test_hcd_free_irp(irp_list[i]); 99 | } 100 | test_hcd_pipe_free(default_pipe); 101 | 102 | //Recover the port should return to the to NOT POWERED state 103 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_recover(port_hdl)); 104 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl)); 105 | 106 | //Recovered port should be able to connect and disconnect again 107 | test_hcd_wait_for_conn(port_hdl); 108 | test_hcd_wait_for_disconn(port_hdl, false); 109 | test_hcd_teardown(port_hdl); 110 | } 111 | 112 | /* 113 | Test port suspend and resume with active pipes 114 | 115 | Purpose: 116 | - Test port suspend and resume commands work correctly whilst there are active pipes with ongoing transfers 117 | - When suspending, the pipes should be allowed to finish their current ongoing transfer before the bus is suspended. 118 | - When resuming, pipes with pending transfer should be started after the bus is resumed. 119 | 120 | Procedure: 121 | - Setup the HCD and a port 122 | - Trigger a port connection 123 | - Create a default pipe 124 | - Start transfers but suspend the port immediately 125 | - Resume the port 126 | - Check that all the IRPs have completed successfully 127 | - Cleanup IRPs and default pipe 128 | - Trigger disconnection and teardown 129 | */ 130 | TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") 131 | { 132 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 133 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 134 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 135 | 136 | //Allocate some IRPs and initialize their data buffers with control transfers 137 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 138 | usb_irp_t *irp_list[NUM_IRPS]; 139 | for (int i = 0; i < NUM_IRPS; i++) { 140 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 141 | //Initialize with a "Get Config Descriptor request" 142 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 143 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 144 | irp_list[i]->context = (void *)0xDEADBEEF; 145 | } 146 | 147 | //Enqueue IRPs but immediately suspend the port 148 | printf("Enqueuing IRPs\n"); 149 | for (int i = 0; i < NUM_IRPS; i++) { 150 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 151 | } 152 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); 153 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl)); 154 | printf("Suspended\n"); 155 | vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for bus to remain suspended 156 | 157 | //Resume the port 158 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); 159 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); 160 | printf("Resumed\n"); 161 | vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed IRPs to complete 162 | //Dequeue IRPs 163 | for (int i = 0; i < NUM_IRPS; i++) { 164 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 165 | TEST_ASSERT_EQUAL(irp_list[i], irp); 166 | TEST_ASSERT_EQUAL(irp->status, USB_TRANSFER_STATUS_COMPLETED); 167 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 168 | TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context); 169 | } 170 | 171 | //Free IRP list and pipe 172 | for (int i = 0; i < NUM_IRPS; i++) { 173 | test_hcd_free_irp(irp_list[i]); 174 | } 175 | test_hcd_pipe_free(default_pipe); 176 | //Cleanup 177 | test_hcd_wait_for_disconn(port_hdl, false); 178 | test_hcd_teardown(port_hdl); 179 | } 180 | 181 | /* 182 | Test HCD port disable with active pipes 183 | 184 | Purpose: 185 | - Test that the port disable command works correctly with active pipes 186 | - Pipes should be to finish their current ongoing transfer before port is disabled 187 | - After disabling the port, all pipes should become invalid. 188 | 189 | Procedure: 190 | - Setup HCD, a default pipe, and multiple IRPs 191 | - Start transfers but immediately disable the port 192 | - Check pipe received invalid event 193 | - Check that transfer are either done or not executed 194 | - Teardown 195 | */ 196 | TEST_CASE("Test HCD port disable", "[hcd][ignore]") 197 | { 198 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 199 | usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection 200 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 201 | 202 | //Allocate some IRPs and initialize their data buffers with control transfers 203 | hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) 204 | usb_irp_t *irp_list[NUM_IRPS]; 205 | for (int i = 0; i < NUM_IRPS; i++) { 206 | irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE); 207 | //Initialize with a "Get Config Descriptor request" 208 | irp_list[i]->num_bytes = TRANSFER_MAX_BYTES; 209 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES); 210 | irp_list[i]->context = (void *)0xDEADBEEF; 211 | } 212 | 213 | //Enqueue IRPs but immediately disable the port 214 | printf("Enqueuing IRPs\n"); 215 | for (int i = 0; i < NUM_IRPS; i++) { 216 | TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_list[i])); 217 | } 218 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE)); 219 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl)); 220 | printf("Disabled\n"); 221 | 222 | //Pipe should have received (zero or more HCD_PIPE_EVENT_IRP_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR) 223 | int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe); 224 | for (int i = 0; i < num_pipe_events - 1; i++) { 225 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_IRP_DONE); 226 | } 227 | test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID); 228 | 229 | //Dequeue IRPs 230 | for (int i = 0; i < NUM_IRPS; i++) { 231 | usb_irp_t *irp = hcd_irp_dequeue(default_pipe); 232 | TEST_ASSERT_EQUAL(irp_list[i], irp); 233 | TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE); 234 | if (irp->status == USB_TRANSFER_STATUS_COMPLETED) { 235 | TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes); 236 | } else { 237 | TEST_ASSERT_EQUAL(0, irp->actual_num_bytes); 238 | } 239 | TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context); 240 | } 241 | 242 | //Free IRP list and pipe 243 | for (int i = 0; i < NUM_IRPS; i++) { 244 | test_hcd_free_irp(irp_list[i]); 245 | } 246 | test_hcd_pipe_free(default_pipe); 247 | //Cleanup 248 | test_hcd_wait_for_disconn(port_hdl, true); 249 | test_hcd_teardown(port_hdl); 250 | } 251 | 252 | /* 253 | Test HCD port command bailout 254 | 255 | Purpose: 256 | - Test that if the a port's state changes whilst a command is being executed, the port command should return 257 | ESP_ERR_INVALID_RESPONSE 258 | 259 | Procedure: 260 | - Setup HCD and wait for connection 261 | - Suspend the port 262 | - Resume the port but trigger a disconnect from another thread during the resume command 263 | - Check that port command returns ESP_ERR_INVALID_RESPONSE 264 | */ 265 | 266 | static void concurrent_task(void *arg) 267 | { 268 | SemaphoreHandle_t sync_sem = (SemaphoreHandle_t) arg; 269 | xSemaphoreTake(sync_sem, portMAX_DELAY); 270 | vTaskDelay(pdMS_TO_TICKS(10)); //Give a short delay let reset command start in main thread 271 | //Force a disconnection 272 | test_hcd_force_conn_state(false, 0); 273 | vTaskDelay(portMAX_DELAY); //Block forever and wait to be deleted 274 | } 275 | 276 | TEST_CASE("Test HCD port command bailout", "[hcd][ignore]") 277 | { 278 | hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port 279 | test_hcd_wait_for_conn(port_hdl); //Trigger a connection 280 | vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS) 281 | 282 | //Create task to run port commands concurrently 283 | SemaphoreHandle_t sync_sem = xSemaphoreCreateBinary(); 284 | TaskHandle_t task_handle; 285 | TEST_ASSERT_NOT_EQUAL(NULL, sync_sem); 286 | TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(concurrent_task, "tsk", 4096, (void *) sync_sem, UNITY_FREERTOS_PRIORITY + 1, &task_handle, 0)); 287 | 288 | //Suspend the device 289 | printf("Suspending\n"); 290 | TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); 291 | vTaskDelay(pdMS_TO_TICKS(20)); //Short delay for device to enter suspend state 292 | 293 | //Attempt to resume the port. But the concurrent task should override this with a disconnection event 294 | printf("Attempting to resume\n"); 295 | xSemaphoreGive(sync_sem); //Trigger concurrent task 296 | TEST_ASSERT_EQUAL(ESP_ERR_INVALID_RESPONSE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); 297 | 298 | //Check that concurrent task triggered a sudden disconnection 299 | test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN); 300 | TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl)); 301 | TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); 302 | 303 | //Cleanup task and semaphore 304 | vTaskDelay(pdMS_TO_TICKS(10)); //Short delay for concurrent task finish running 305 | vTaskDelete(task_handle); 306 | vSemaphoreDelete(sync_sem); 307 | 308 | test_hcd_teardown(port_hdl); 309 | } 310 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.c" "ctrl_pipe.c" "usb_host_port.c" "parse_data.c" "cdc_class.c" 2 | INCLUDE_DIRS "") 3 | -------------------------------------------------------------------------------- /main/cdc_class.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | #include "cdc_class.h" 5 | #include "hcd.h" 6 | 7 | 8 | #define MAX_NUM_ENDP 3 9 | #define EP1 0 10 | #define EP2 1 11 | #define EP3 2 12 | 13 | 14 | hcd_pipe_handle_t cdc_ep_pipe_hdl[MAX_NUM_ENDP]; 15 | uint8_t *cdc_data_buffers[MAX_NUM_ENDP]; 16 | usb_irp_t *cdc_ep_irps[MAX_NUM_ENDP]; 17 | usb_desc_ep_t endpoints[MAX_NUM_ENDP]; 18 | int bMaxPacketSize0; 19 | extern hcd_pipe_handle_t ctrl_pipe_hdl; 20 | 21 | static QueueHandle_t cdc_pipe_evt_queue; 22 | static ctrl_pipe_cb_t cdc_pipe_cb; 23 | 24 | static bool cdc_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) 25 | { 26 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)user_arg; 27 | pipe_event_msg_t msg = { 28 | .pipe_hdl = pipe_hdl, 29 | .pipe_event = pipe_event, 30 | }; 31 | if (in_isr) { 32 | BaseType_t xTaskWoken = pdFALSE; 33 | xQueueSendFromISR(pipe_evt_queue, &msg, &xTaskWoken); 34 | return (xTaskWoken == pdTRUE); 35 | } else { 36 | xQueueSend(pipe_evt_queue, &msg, portMAX_DELAY); 37 | return false; 38 | } 39 | } 40 | 41 | void cdc_pipe_event_task(void* p) 42 | { 43 | printf("start pipe event task\n"); 44 | pipe_event_msg_t msg; 45 | cdc_pipe_evt_queue = xQueueCreate(10, sizeof(pipe_event_msg_t)); 46 | 47 | while(1){ 48 | xQueueReceive(cdc_pipe_evt_queue, &msg, portMAX_DELAY); 49 | usb_irp_t* irp = hcd_irp_dequeue(msg.pipe_hdl); 50 | hcd_pipe_handle_t pipe_hdl; 51 | if(irp == NULL) continue; 52 | void *context = irp->context; 53 | 54 | if (cdc_pipe_cb != NULL) 55 | { 56 | cdc_pipe_cb(msg, irp, context); 57 | } 58 | } 59 | } 60 | 61 | static void free_pipe_and_xfer_reqs(hcd_pipe_handle_t pipe_hdl, 62 | // hcd_xfer_req_handle_t *req_hdls, 63 | uint8_t **data_buffers, 64 | usb_irp_t **irps, 65 | int num_xfers) 66 | { 67 | //Dequeue transfer requests 68 | do{ 69 | usb_irp_t* irp = hcd_irp_dequeue(pipe_hdl); 70 | if(irp == NULL) break; 71 | }while(1); 72 | 73 | ESP_LOGD("", "Freeing transfer requets\n"); 74 | //Free transfer requests (and their associated objects such as IRPs and data buffers) 75 | for (int i = 0; i < num_xfers; i++) { 76 | heap_caps_free(irps[i]); 77 | heap_caps_free(data_buffers[i]); 78 | } 79 | ESP_LOGD("", "Freeing pipe\n"); 80 | //Delete the pipe 81 | if(ESP_OK != hcd_pipe_free(pipe_hdl)) { 82 | ESP_LOGE("", "err to free pipes"); 83 | } 84 | } 85 | 86 | static void alloc_pipe_and_xfer_reqs_cdc(hcd_port_handle_t port_hdl, 87 | QueueHandle_t pipe_evt_queue, 88 | hcd_pipe_handle_t *pipe_hdl, 89 | uint8_t **data_buffers, 90 | usb_irp_t **irps, 91 | int num_xfers, 92 | usb_desc_ep_t* ep) 93 | { 94 | //We don't support hubs yet. Just get the speed of the port to determine the speed of the device 95 | usb_speed_t port_speed; 96 | if(ESP_OK == hcd_port_get_speed(port_hdl, &port_speed)){} 97 | 98 | hcd_pipe_config_t config = { 99 | .callback = cdc_pipe_callback, 100 | .callback_arg = (void *)pipe_evt_queue, 101 | .context = NULL, 102 | .ep_desc = ep, 103 | .dev_addr = DEVICE_ADDR, // TODO 104 | .dev_speed = port_speed, 105 | }; 106 | if(ESP_OK == hcd_pipe_alloc(port_hdl, &config, pipe_hdl)) {} 107 | if(NULL == pipe_hdl) { 108 | ESP_LOGE("", "NULL == pipe_hdl"); 109 | return; 110 | } 111 | //Create transfer requests (and other required objects such as IRPs and data buffers) 112 | printf("Creating transfer requests\n"); 113 | for (int i = 0; i < num_xfers; i++) { 114 | irps[i] = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT); 115 | if(NULL == irps[i]) ESP_LOGE("", "err 6"); 116 | //Allocate data buffers 117 | data_buffers = heap_caps_calloc(1, sizeof(usb_ctrl_req_t) + TRANSFER_DATA_MAX_BYTES, MALLOC_CAP_DMA); 118 | if(NULL == data_buffers) ESP_LOGE("", "err 5"); 119 | //Initialize IRP and IRP list 120 | irps[i]->data_buffer = data_buffers; 121 | irps[i]->num_iso_packets = 0; 122 | } 123 | } 124 | 125 | void cdc_create_pipe(usb_desc_ep_t* ep) 126 | { 127 | if(cdc_pipe_evt_queue == NULL) 128 | cdc_pipe_evt_queue = xQueueCreate(10, sizeof(pipe_event_msg_t)); 129 | if(USB_DESC_EP_GET_XFERTYPE(ep) == USB_TRANSFER_TYPE_INTR){ 130 | ESP_LOGI("", "create INTR endpoint"); 131 | memcpy(&endpoints[EP1], ep, sizeof(usb_desc_ep_t)); 132 | alloc_pipe_and_xfer_reqs_cdc(port_hdl, cdc_pipe_evt_queue, &cdc_ep_pipe_hdl[EP1], &cdc_data_buffers[EP1], &cdc_ep_irps[EP1], 1, ep); 133 | } else if(USB_DESC_EP_GET_XFERTYPE(ep) == USB_TRANSFER_TYPE_BULK && USB_DESC_EP_GET_EP_DIR(ep)){ 134 | ESP_LOGI("", "create BULK1 endpoint"); 135 | memcpy(&endpoints[EP2], ep, sizeof(usb_desc_ep_t)); 136 | alloc_pipe_and_xfer_reqs_cdc(port_hdl, cdc_pipe_evt_queue, &cdc_ep_pipe_hdl[EP2], &cdc_data_buffers[EP2], &cdc_ep_irps[EP2], 1, ep); 137 | } else { 138 | ESP_LOGI("", "create BULK2 endpoint"); 139 | memcpy(&endpoints[EP3], ep, sizeof(usb_desc_ep_t)); 140 | alloc_pipe_and_xfer_reqs_cdc(port_hdl, cdc_pipe_evt_queue, &cdc_ep_pipe_hdl[EP3], &cdc_data_buffers[EP3], &cdc_ep_irps[EP3], 1, ep); 141 | } 142 | } 143 | 144 | void delete_pipes() 145 | { 146 | for (size_t i = 0; i < MAX_NUM_ENDP; i++) 147 | { 148 | if(cdc_ep_pipe_hdl[i] == NULL) continue; 149 | if (HCD_PIPE_STATE_INVALID == hcd_pipe_get_state(cdc_ep_pipe_hdl[i])) 150 | { 151 | ESP_LOGD("", "pipe state: %d", hcd_pipe_get_state(cdc_ep_pipe_hdl[i])); 152 | free_pipe_and_xfer_reqs( cdc_ep_pipe_hdl[i], &cdc_data_buffers[i], &cdc_ep_irps[i], 1); 153 | cdc_ep_pipe_hdl[i] = NULL; 154 | } 155 | } 156 | } 157 | 158 | void xfer_set_line_coding(uint32_t bitrate, uint8_t cf, uint8_t parity, uint8_t bits) 159 | { 160 | usb_irp_t *irp = allocate_irp(port_hdl, 7); 161 | USB_CTRL_REQ_CDC_SET_LINE_CODING((cdc_ctrl_line_t *) irp->data_buffer, 0, bitrate, cf, parity, bits); 162 | 163 | esp_err_t err; 164 | if(ESP_OK == (err = hcd_irp_enqueue(ctrl_pipe_hdl, irp))) { 165 | ESP_LOGD("xfer", "set line codding"); 166 | } else { 167 | ESP_LOGW("xfer", "set line codding: 0x%x", err); 168 | } 169 | } 170 | 171 | void xfer_get_line_coding() 172 | { 173 | usb_irp_t *irp = allocate_irp(port_hdl, 7); 174 | USB_CTRL_REQ_CDC_GET_LINE_CODING((usb_ctrl_req_t *) irp->data_buffer, 0); 175 | 176 | esp_err_t err; 177 | if(ESP_OK == (err = hcd_irp_enqueue(ctrl_pipe_hdl, irp))) { 178 | ESP_LOGD("xfer", "get line codding"); 179 | } else { 180 | ESP_LOGW("xfer", "get line codding: 0x%x", err); 181 | } 182 | } 183 | 184 | void xfer_set_control_line(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, bool dtr, bool rts) 185 | { 186 | usb_irp_t *irp = allocate_irp(port_hdl, 0); 187 | USB_CTRL_REQ_CDC_SET_CONTROL_LINE_STATE((usb_ctrl_req_t *) irp->data_buffer, 0, dtr, rts); 188 | 189 | esp_err_t err; 190 | if(ESP_OK == (err = hcd_irp_enqueue(handle, irp))) { 191 | ESP_LOGD("xfer", "set control line"); 192 | } else { 193 | ESP_LOGW("xfer", "set control line: 0x%x", err); 194 | } 195 | } 196 | 197 | 198 | // ENDPOINTS 199 | void xfer_intr_data() 200 | { 201 | cdc_ep_irps[EP1]->num_bytes = 8; //1 worst case MPS 202 | cdc_ep_irps[EP1]->data_buffer = cdc_data_buffers[EP1]; 203 | cdc_ep_irps[EP1]->num_iso_packets = 0; 204 | cdc_ep_irps[EP1]->num_bytes = 8; 205 | 206 | esp_err_t err; 207 | if(ESP_OK == (err = hcd_irp_enqueue(cdc_ep_pipe_hdl[EP1], cdc_ep_irps[EP1]))) { 208 | ESP_LOGI("", "INT "); 209 | } else { 210 | ESP_LOGE("", "INT err: 0x%02x", err); 211 | } 212 | } 213 | 214 | void xfer_in_data() 215 | { 216 | ESP_LOGD("", "EP: 0x%02x", USB_DESC_EP_GET_ADDRESS(&endpoints[EP2])); 217 | cdc_ep_irps[EP2]->num_bytes = bMaxPacketSize0; //1 worst case MPS 218 | cdc_ep_irps[EP2]->num_iso_packets = 0; 219 | cdc_ep_irps[EP2]->num_bytes = 64; 220 | 221 | esp_err_t err; 222 | if(ESP_OK != (err = hcd_irp_enqueue(cdc_ep_pipe_hdl[EP2], cdc_ep_irps[EP2]))) { 223 | ESP_LOGW("", "BULK %s, dir: %d, err: 0x%x", "IN", USB_DESC_EP_GET_EP_DIR(&endpoints[EP2]), err); 224 | } 225 | } 226 | 227 | void xfer_out_data(uint8_t* data, size_t len) 228 | { 229 | ESP_LOGD("", "EP: 0x%02x", USB_DESC_EP_GET_ADDRESS(&endpoints[EP3])); 230 | memcpy(cdc_ep_irps[EP3]->data_buffer, data, len); 231 | cdc_ep_irps[EP3]->num_bytes = bMaxPacketSize0; //1 worst case MPS 232 | cdc_ep_irps[EP3]->num_iso_packets = 0; 233 | cdc_ep_irps[EP3]->num_bytes = len; 234 | 235 | esp_err_t err; 236 | if(ESP_OK != (err = hcd_irp_enqueue(cdc_ep_pipe_hdl[EP3], cdc_ep_irps[EP3]))) { 237 | ESP_LOGW("", "BULK %s, dir: %d, err: 0x%x", "OUT", USB_DESC_EP_GET_EP_DIR(&endpoints[EP3]), err); 238 | } 239 | } 240 | 241 | void cdc_class_specific_ctrl_cb(usb_irp_t* irp) 242 | { 243 | if (irp->data_buffer[0] == SET_VALUE && irp->data_buffer[1] == SET_LINE_CODING) // set line coding 244 | { 245 | line_coding_t* data = (line_coding_t*)(irp->data_buffer + sizeof(usb_ctrl_req_t)); 246 | ESP_LOGI("Set line coding", "Bitrate: %d, stop bits: %d, parity: %d, bits: %d", 247 | data->dwDTERate, data->bCharFormat, data->bParityType, data->bDataBits); 248 | } else if (irp->data_buffer[0] == GET_VALUE && irp->data_buffer[1] == GET_LINE_CODING) // get line coding 249 | { 250 | line_coding_t* data = (line_coding_t*)(irp->data_buffer + sizeof(usb_ctrl_req_t)); 251 | ESP_LOGI("Get line coding", "Bitrate: %d, stop bits: %d, parity: %d, bits: %d", 252 | data->dwDTERate, data->bCharFormat, data->bParityType, data->bDataBits); 253 | } else if (irp->data_buffer[0] == SET_VALUE && irp->data_buffer[1] == SET_CONTROL_LINE_STATE) // set line coding 254 | { 255 | line_coding_t* data = (line_coding_t*)(irp->data_buffer + sizeof(usb_ctrl_req_t)); 256 | ESP_LOGI("Set control line state", ""); 257 | xfer_set_line_coding(115200, 0, 0, 5); 258 | } 259 | } 260 | 261 | void register_cdc_pipe_callback(ctrl_pipe_cb_t cb) 262 | { 263 | cdc_pipe_cb = cb; 264 | } 265 | -------------------------------------------------------------------------------- /main/cdc_class.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "hcd.h" 3 | #include "ctrl_pipe.h" 4 | #include "usb_host_port.h" 5 | 6 | // #define XFER_DATA_MAX_LEN 1023 //Just assume that will only IN/OUT 1023 bytes for now, which is max length for full speed 7 | #define DEVICE_ADDR 1 8 | 9 | #define USB_DESC_EP_GET_ADDRESS(desc_ptr) ((desc_ptr)->bEndpointAddress & 0x7F) 10 | 11 | #define SET_VALUE 0x21 12 | #define GET_VALUE 0xA1 13 | 14 | // DTR/RTS control in SET_CONTROL_LINE_STATE 15 | #define ENABLE_DTR(val) (val<<0) 16 | #define ENABLE_RTS(val) (val<<1) 17 | 18 | #define SET_LINE_CODING 0x20 19 | #define GET_LINE_CODING 0x21 20 | #define SET_CONTROL_LINE_STATE 0x22 21 | #define SERIAL_STATE 0x20 22 | 23 | typedef struct{ 24 | uint32_t dwDTERate; 25 | uint8_t bCharFormat; 26 | uint8_t bParityType; 27 | uint8_t bDataBits; 28 | }line_coding_t; 29 | 30 | typedef union { 31 | struct { 32 | uint8_t bRequestType; 33 | uint8_t bRequest; 34 | uint16_t wValue; 35 | uint16_t wIndex; 36 | uint16_t wLength; 37 | line_coding_t data; 38 | } USB_CTRL_REQ_ATTR; 39 | uint8_t val[USB_CTRL_REQ_SIZE + 7]; 40 | } cdc_ctrl_line_t; 41 | 42 | /** 43 | * @brief Initializer for a SET_ADDRESS request 44 | * 45 | * Sets the address of a connected device 46 | */ 47 | #define USB_CTRL_REQ_CDC_SET_LINE_CODING(ctrl_req_ptr, index, bitrate, cf, parity, bits) ({ \ 48 | (ctrl_req_ptr)->bRequestType = SET_VALUE; \ 49 | (ctrl_req_ptr)->bRequest = SET_LINE_CODING; \ 50 | (ctrl_req_ptr)->wValue = 0; \ 51 | (ctrl_req_ptr)->wIndex = (index); \ 52 | (ctrl_req_ptr)->wLength = (7); \ 53 | (ctrl_req_ptr)->data.dwDTERate = (bitrate); \ 54 | (ctrl_req_ptr)->data.bCharFormat = (cf); \ 55 | (ctrl_req_ptr)->data.bParityType = (parity); \ 56 | (ctrl_req_ptr)->data.bDataBits = (bits); \ 57 | }) 58 | 59 | #define USB_CTRL_REQ_CDC_GET_LINE_CODING(ctrl_req_ptr, index) ({ \ 60 | (ctrl_req_ptr)->bRequestType = GET_VALUE; \ 61 | (ctrl_req_ptr)->bRequest = GET_LINE_CODING; \ 62 | (ctrl_req_ptr)->wValue = 0; \ 63 | (ctrl_req_ptr)->wIndex = (index); \ 64 | (ctrl_req_ptr)->wLength = (7); \ 65 | }) 66 | 67 | #define USB_CTRL_REQ_CDC_SET_CONTROL_LINE_STATE(ctrl_req_ptr, index, dtr, rts) ({ \ 68 | (ctrl_req_ptr)->bRequestType = SET_VALUE; \ 69 | (ctrl_req_ptr)->bRequest = SET_CONTROL_LINE_STATE; \ 70 | (ctrl_req_ptr)->wValue = ENABLE_DTR(dtr) | ENABLE_RTS(rts); \ 71 | (ctrl_req_ptr)->wIndex = (index); \ 72 | (ctrl_req_ptr)->wLength = (0); \ 73 | }) 74 | 75 | void xfer_set_line_coding(uint32_t bitrate, uint8_t cf, uint8_t parity, uint8_t bits); 76 | void xfer_set_control_line(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, bool dtr, bool rts); 77 | void xfer_get_line_coding(); 78 | void xfer_intr_data(); 79 | void xfer_in_data(); 80 | void xfer_out_data(uint8_t* data, size_t len); 81 | void delete_pipes(); 82 | void cdc_create_pipe(usb_desc_ep_t* ep); 83 | void register_cdc_pipe_callback(ctrl_pipe_cb_t cb); 84 | void cdc_class_specific_ctrl_cb(usb_irp_t* irp); 85 | -------------------------------------------------------------------------------- /main/common.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/semphr.h" 8 | #include "esp_intr_alloc.h" 9 | #include "esp_err.h" 10 | #include "esp_attr.h" 11 | #include "esp_rom_gpio.h" 12 | #include "soc/gpio_pins.h" 13 | #include "soc/gpio_sig_map.h" 14 | #include "hal/usbh_ll.h" 15 | #include "hcd.h" 16 | #include "esp_log.h" 17 | 18 | #define USB_WEAK __attribute__((weak)) 19 | 20 | 21 | 22 | 23 | USB_WEAK void class_specific_data_cb(usb_irp_t* irp); 24 | USB_WEAK void ep_data_cb(usb_irp_t* irp); 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | -------------------------------------------------------------------------------- /main/ctrl_pipe.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/semphr.h" 5 | #include "esp_intr_alloc.h" 6 | #include "esp_err.h" 7 | #include "esp_attr.h" 8 | #include "esp_rom_gpio.h" 9 | #include "soc/gpio_pins.h" 10 | #include "soc/gpio_sig_map.h" 11 | #include "hal/usbh_ll.h" 12 | #include "hcd.h" 13 | #include "esp_log.h" 14 | #include "hcd.h" 15 | #include "ctrl_pipe.h" 16 | 17 | hcd_pipe_handle_t ctrl_pipe_hdl; 18 | uint8_t *ctrl_data_buffers[NUM_XFER_REQS]; 19 | usb_irp_t *ctrl_irps[NUM_XFER_REQS]; 20 | 21 | static QueueHandle_t ctrl_pipe_evt_queue; 22 | static ctrl_pipe_cb_t ctrl_pipe_cb; 23 | 24 | void register_ctrl_pipe_callback(ctrl_pipe_cb_t cb) 25 | { 26 | ctrl_pipe_cb = cb; 27 | } 28 | 29 | static bool ctrl_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) 30 | { 31 | QueueHandle_t pipe_evt_queue = (QueueHandle_t)user_arg; 32 | pipe_event_msg_t msg = { 33 | .pipe_hdl = pipe_hdl, 34 | .pipe_event = pipe_event, 35 | }; 36 | if (in_isr) 37 | { 38 | BaseType_t xTaskWoken = pdFALSE; 39 | xQueueSendFromISR(pipe_evt_queue, &msg, &xTaskWoken); 40 | return (xTaskWoken == pdTRUE); 41 | } 42 | else 43 | { 44 | xQueueSend(pipe_evt_queue, &msg, portMAX_DELAY); 45 | return false; 46 | } 47 | } 48 | 49 | void free_pipe_and_irp_list(hcd_pipe_handle_t pipe_hdl) 50 | { 51 | printf("Freeing IRPs and IRP list\n"); 52 | //Dequeue transfer requests 53 | do 54 | { 55 | usb_irp_t *irp = hcd_irp_dequeue(pipe_hdl); 56 | if (irp == NULL) 57 | break; 58 | heap_caps_free(irp->data_buffer); 59 | heap_caps_free(irp); 60 | } while (1); 61 | 62 | ESP_LOGD("", "Freeing transfer requets\n"); 63 | //Free transfer requests (and their associated objects such as IRPs and data buffers) 64 | ESP_LOGD("", "Freeing pipe\n"); 65 | //Delete the pipe 66 | if (ESP_OK != hcd_pipe_free(pipe_hdl)) 67 | { 68 | ESP_LOGE("", "err to free pipes"); 69 | } 70 | } 71 | 72 | void allocate_ctrl_pipe(hcd_port_handle_t port_hdl, hcd_pipe_handle_t *handle) 73 | { 74 | //We don't support hubs yet. Just get the speed of the port to determine the speed of the device 75 | usb_speed_t port_speed; 76 | if (ESP_OK == hcd_port_get_speed(port_hdl, &port_speed)) 77 | { 78 | } 79 | 80 | //Create default pipe 81 | ESP_LOGI("", "Creating default pipe\n"); 82 | hcd_pipe_config_t config = { 83 | .callback = ctrl_pipe_callback, 84 | .callback_arg = (void *)ctrl_pipe_evt_queue, 85 | .context = NULL, 86 | .ep_desc = NULL, //NULL EP descriptor to create a default pipe 87 | .dev_addr = 0, 88 | .dev_speed = port_speed, 89 | }; 90 | if (ESP_OK != hcd_pipe_alloc(port_hdl, &config, handle)) 91 | ESP_LOGE("", "cant alloc pipe"); 92 | if (NULL == handle) 93 | { 94 | ESP_LOGE("", "NULL == pipe_hdl"); 95 | } 96 | } 97 | 98 | usb_irp_t *allocate_irp(hcd_port_handle_t port_hdl, size_t size) 99 | { 100 | //Create IRPs and their data buffers 101 | ESP_LOGI("", "Creating new IRP, free memory: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); 102 | usb_irp_t *_irp = heap_caps_calloc(1, sizeof(usb_irp_t), MALLOC_CAP_DEFAULT); 103 | if (NULL == _irp) 104 | ESP_LOGE("", "err to alloc IRP"); 105 | //Allocate data buffer 106 | uint8_t *_data_buffer = heap_caps_calloc(1, sizeof(usb_ctrl_req_t) + size, MALLOC_CAP_DMA); 107 | if (NULL == _data_buffer) 108 | ESP_LOGE("", "err to alloc data buffer"); 109 | //Initialize IRP and IRP list 110 | _irp->data_buffer = _data_buffer; 111 | _irp->num_iso_packets = 0; 112 | _irp->num_bytes = size; 113 | 114 | return _irp; 115 | } 116 | 117 | void ctrl_pipe_event_task(void *p) 118 | { 119 | printf("start pipe event task\n"); 120 | pipe_event_msg_t msg; 121 | ctrl_pipe_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(pipe_event_msg_t)); 122 | 123 | while (1) 124 | { 125 | xQueueReceive(ctrl_pipe_evt_queue, &msg, portMAX_DELAY); 126 | usb_irp_t *irp = hcd_irp_dequeue(msg.pipe_hdl); 127 | 128 | if (irp == NULL) 129 | continue; 130 | void *context = irp->context; 131 | 132 | usb_ctrl_req_t *ctrl = (usb_ctrl_req_t *)irp->data_buffer; 133 | 134 | switch (msg.pipe_event) 135 | { 136 | case HCD_PIPE_EVENT_NONE: 137 | break; 138 | 139 | case HCD_PIPE_EVENT_IRP_DONE: 140 | switch (ctrl->bRequest) 141 | { 142 | case USB_B_REQUEST_GET_STATUS: 143 | break; 144 | 145 | case USB_B_REQUEST_CLEAR_FEATURE: 146 | break; 147 | 148 | case USB_B_REQUEST_SET_FEATURE: 149 | break; 150 | 151 | case USB_B_REQUEST_SET_ADDRESS: 152 | usbh_set_address_cb(ctrl->wValue, context); 153 | break; 154 | 155 | case USB_B_REQUEST_GET_DESCRIPTOR: 156 | switch ((ctrl->wValue >> 8) & 0xff) 157 | { 158 | case USB_W_VALUE_DT_DEVICE: 159 | usbh_get_device_desc_cb(irp->data_buffer + sizeof(usb_ctrl_req_t), irp->actual_num_bytes, context); 160 | break; 161 | 162 | case USB_W_VALUE_DT_CONFIG: 163 | usbh_get_config_desc_cb(irp->data_buffer + sizeof(usb_ctrl_req_t), irp->actual_num_bytes, context); 164 | break; 165 | 166 | case USB_W_VALUE_DT_STRING: 167 | usbh_get_string_cb((irp->data_buffer + sizeof(usb_ctrl_req_t)), irp->actual_num_bytes, context); 168 | break; 169 | 170 | case USB_W_VALUE_DT_INTERFACE: 171 | usbh_get_interface_desc_cb(irp->data_buffer + sizeof(usb_ctrl_req_t), irp->actual_num_bytes, context); 172 | break; 173 | 174 | case USB_W_VALUE_DT_ENDPOINT: 175 | usbh_get_endpoint_desc_cb(irp->data_buffer + sizeof(usb_ctrl_req_t), irp->actual_num_bytes, context); 176 | break; 177 | 178 | case USB_W_VALUE_DT_DEVICE_QUALIFIER: 179 | break; 180 | 181 | case USB_W_VALUE_DT_OTHER_SPEED_CONFIG: 182 | break; 183 | 184 | case USB_W_VALUE_DT_INTERFACE_POWER: 185 | usbh_get_power_desc_cb(irp->data_buffer + sizeof(usb_ctrl_req_t), irp->actual_num_bytes, context); 186 | break; 187 | 188 | default: 189 | break; 190 | } 191 | break; 192 | 193 | case USB_B_REQUEST_GET_CONFIGURATION: 194 | usbh_get_configuration_cb(*(uint8_t*)(irp->data_buffer + sizeof(usb_ctrl_req_t)), context); 195 | break; 196 | 197 | case USB_B_REQUEST_SET_CONFIGURATION: 198 | usbh_set_config_desc_cb(ctrl->wValue, context); 199 | break; 200 | 201 | case USB_B_REQUEST_SET_INTERFACE: 202 | break; 203 | 204 | case USB_B_REQUEST_SYNCH_FRAME: 205 | ESP_LOGI("", "SYNC"); 206 | break; 207 | 208 | default: 209 | usbh_ctrl_pipe_class_specific_cb(msg, irp); 210 | break; 211 | } 212 | break; 213 | 214 | case HCD_PIPE_EVENT_ERROR_XFER: 215 | usbh_ctrl_pipe_error_cb(ctrl); 216 | ESP_LOGW("", "XFER error: %d", irp->status); 217 | hcd_pipe_command(msg.pipe_hdl, HCD_PIPE_CMD_RESET); 218 | break; 219 | 220 | case HCD_PIPE_EVENT_ERROR_STALL: 221 | usbh_ctrl_pipe_stalled_cb(ctrl); 222 | ESP_LOGW("", "Device stalled: %s pipe, state: %d", msg.pipe_hdl == ctrl_pipe_hdl ? "CTRL" : "BULK", hcd_pipe_get_state(msg.pipe_hdl)); 223 | ESP_LOG_BUFFER_HEX_LEVEL("Ctrl data", ctrl, sizeof(usb_ctrl_req_t), ESP_LOG_INFO); 224 | hcd_pipe_command(msg.pipe_hdl, HCD_PIPE_CMD_RESET); 225 | break; 226 | 227 | default: 228 | break; 229 | } 230 | if (ctrl_pipe_cb != NULL) 231 | { 232 | ctrl_pipe_cb(msg, irp, context); 233 | } 234 | 235 | heap_caps_free(irp->data_buffer); 236 | heap_caps_free(irp); 237 | ESP_LOGI("", "Dequeue and delete IRP, free memory: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); 238 | } 239 | } 240 | 241 | void xfer_get_device_desc(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle) 242 | { 243 | usb_irp_t *irp = allocate_irp(port_hdl, 18); 244 | USB_CTRL_REQ_INIT_GET_DEVC_DESC((usb_ctrl_req_t *)irp->data_buffer); 245 | 246 | //Enqueue those transfer requests 247 | esp_err_t err; 248 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 249 | { 250 | ESP_LOGE("", "Get device desc: %d", err); 251 | } 252 | } 253 | 254 | void xfer_set_address(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t addr) 255 | { 256 | usb_irp_t *irp = allocate_irp(port_hdl, 0); 257 | USB_CTRL_REQ_INIT_SET_ADDR((usb_ctrl_req_t *)irp->data_buffer, addr); 258 | 259 | //Enqueue those transfer requests 260 | esp_err_t err; 261 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 262 | { 263 | ESP_LOGE("", "Set address: %d", err); 264 | } 265 | } 266 | 267 | void xfer_get_current_config(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle) 268 | { 269 | usb_irp_t *irp = allocate_irp(port_hdl, 1); 270 | USB_CTRL_REQ_INIT_GET_CONFIG((usb_ctrl_req_t *)irp->data_buffer); 271 | 272 | //Enqueue those transfer requests 273 | esp_err_t err; 274 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 275 | { 276 | ESP_LOGE("", "Get current config: %d", err); 277 | } 278 | } 279 | 280 | void xfer_set_configuration(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t num) 281 | { 282 | usb_irp_t *irp = allocate_irp(port_hdl, 0); 283 | USB_CTRL_REQ_INIT_SET_CONFIG((usb_ctrl_req_t *)irp->data_buffer, num); 284 | 285 | //Enqueue those transfer requests 286 | esp_err_t err; 287 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 288 | { 289 | ESP_LOGE("", "Set current config: %d", err); 290 | } 291 | } 292 | 293 | void xfer_get_desc(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle) 294 | { 295 | usb_irp_t *irp = allocate_irp(port_hdl, TRANSFER_DATA_MAX_BYTES); 296 | USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp->data_buffer, 0, TRANSFER_DATA_MAX_BYTES); 297 | 298 | //Enqueue those transfer requests 299 | esp_err_t err; 300 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 301 | { 302 | ESP_LOGE("", "Get config desc: %d", err); 303 | } 304 | } 305 | 306 | void xfer_get_string(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t num) 307 | { 308 | usb_irp_t *irp = allocate_irp(port_hdl, TRANSFER_DATA_MAX_BYTES); 309 | USB_CTRL_REQ_INIT_GET_STRING((usb_ctrl_req_t *)irp->data_buffer, 0, num, TRANSFER_DATA_MAX_BYTES); 310 | 311 | //Enqueue those transfer requests 312 | esp_err_t err; 313 | if (ESP_OK != (err = hcd_irp_enqueue(handle, irp))) 314 | { 315 | ESP_LOGE("", "Get string: %d, err: %d", num, err); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /main/ctrl_pipe.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "hcd.h" 4 | #include "usb.h" 5 | 6 | #define USBH_WEAK_CB __attribute__((weak)) 7 | 8 | #define EVENT_QUEUE_LEN 10 9 | #define NUM_XFER_REQS 4 10 | #define TRANSFER_DATA_MAX_BYTES 256 //Just assume that will only IN/OUT 64 bytes for now 11 | #define PORT_NUM 1 12 | 13 | #define USB_CTRL_REQ_INIT_GET_STRING(ctrl_req_ptr, lang, desc_index, len) ({ \ 14 | (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ 15 | (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ 16 | (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_STRING << 8) | ((desc_index) & 0xFF); \ 17 | (ctrl_req_ptr)->wIndex = (lang); \ 18 | (ctrl_req_ptr)->wLength = (len); \ 19 | }) 20 | 21 | typedef struct { 22 | hcd_pipe_handle_t pipe_hdl; 23 | hcd_pipe_event_t pipe_event; 24 | } pipe_event_msg_t; 25 | 26 | extern hcd_pipe_handle_t ctrl_pipe_hdl; 27 | extern uint8_t *ctrl_data_buffers[NUM_XFER_REQS]; 28 | extern usb_irp_t *ctrl_irps[NUM_XFER_REQS]; 29 | typedef void (*ctrl_pipe_cb_t)(pipe_event_msg_t msg, usb_irp_t *irp, void *context); 30 | void register_ctrl_pipe_callback(ctrl_pipe_cb_t); 31 | 32 | void ctrl_pipe_event_task(void* p); 33 | void alloc_pipe_and_irp_list(hcd_port_handle_t port_hdl, hcd_pipe_handle_t* handle); 34 | void free_pipe_and_irp_list(hcd_pipe_handle_t pipe_hdl); 35 | 36 | USBH_WEAK_CB void usbh_get_device_desc_cb(uint8_t*, size_t, void*); 37 | USBH_WEAK_CB void usbh_get_config_desc_cb(uint8_t*, size_t, void*); 38 | USBH_WEAK_CB void usbh_get_string_cb(uint8_t*, size_t, void*); 39 | USBH_WEAK_CB void usbh_get_interface_desc_cb(uint8_t*, size_t, void*); 40 | USBH_WEAK_CB void usbh_get_endpoint_desc_cb(uint8_t*, size_t, void*); 41 | USBH_WEAK_CB void usbh_get_power_desc_cb(uint8_t*, size_t, void*); 42 | 43 | USBH_WEAK_CB void usbh_set_address_cb(uint16_t, void*); 44 | USBH_WEAK_CB void usbh_set_config_desc_cb(uint16_t, void*); 45 | USBH_WEAK_CB void usbh_get_configuration_cb(uint8_t, void*); 46 | 47 | USBH_WEAK_CB void usbh_ctrl_pipe_error_cb(usb_ctrl_req_t* ctrl); 48 | USBH_WEAK_CB void usbh_ctrl_pipe_stalled_cb(usb_ctrl_req_t* ctrl); 49 | USBH_WEAK_CB void usbh_ctrl_pipe_class_specific_cb(pipe_event_msg_t msg, usb_irp_t *irp); 50 | 51 | void xfer_get_device_desc(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle); 52 | void xfer_set_address(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t addr); 53 | void xfer_get_current_config(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle); 54 | void xfer_set_configuration(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t); 55 | void xfer_get_desc(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle); 56 | void xfer_get_string(hcd_port_handle_t port_hdl, hcd_pipe_handle_t handle, uint8_t); 57 | void allocate_ctrl_pipe(hcd_port_handle_t port_hdl, hcd_pipe_handle_t* handle); 58 | usb_irp_t* allocate_irp(hcd_port_handle_t port_hdl, size_t size); 59 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | 2 | /* USB CDC host example 3 | 4 | This example code is in the Public Domain (or CC0 licensed, at your option.) 5 | 6 | Unless required by applicable law or agreed to in writing, this 7 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 8 | CONDITIONS OF ANY KIND, either express or implied. 9 | */ 10 | #include 11 | #include "freertos/FreeRTOS.h" 12 | #include "freertos/semphr.h" 13 | #include "esp_intr_alloc.h" 14 | #include "esp_err.h" 15 | #include "esp_attr.h" 16 | #include "esp_rom_gpio.h" 17 | #include "soc/gpio_pins.h" 18 | #include "soc/gpio_sig_map.h" 19 | #include "hal/usbh_ll.h" 20 | #include "hcd.h" 21 | #include "esp_log.h" 22 | #include "ctrl_pipe.h" 23 | #include "usb_host_port.h" 24 | #include "cdc_class.h" 25 | 26 | 27 | uint8_t device_state = 0; 28 | uint8_t conf_num; 29 | 30 | hcd_pipe_handle_t ctrl_pipe_hdl; 31 | static hcd_port_handle_t _port_hdl; 32 | 33 | uint8_t bMaxPacketSize0 = 64; 34 | uint8_t conf_num = 0; 35 | static bool ready = false; 36 | 37 | void parse_cfg_descriptor(uint8_t* data_buffer, usb_transfer_status_t status, uint8_t len, uint8_t* conf_num); 38 | 39 | static void utf16_to_utf8(char* in, char* out, uint8_t len) 40 | { 41 | for (size_t i = 0; i < len; i++) 42 | { 43 | out[i/2] = in[i]; 44 | i++; 45 | } 46 | } 47 | 48 | void usbh_ctrl_pipe_class_specific_cb(pipe_event_msg_t msg, usb_irp_t *irp) 49 | { 50 | cdc_class_specific_ctrl_cb(irp); 51 | ready = true; 52 | } 53 | 54 | static void cdc_pipe_cb(pipe_event_msg_t msg, usb_irp_t *irp, void *context) 55 | { 56 | ESP_LOGD("", "\t-> Pipe [%d] event: %d\n", (uint8_t)context, msg.pipe_event); 57 | 58 | switch (msg.pipe_event) 59 | { 60 | case HCD_PIPE_EVENT_NONE: 61 | break; 62 | 63 | case HCD_PIPE_EVENT_IRP_DONE: 64 | ESP_LOGD("Pipe cdc: ", "XFER status: %d, num bytes: %d, actual bytes: %d", irp->status, irp->num_bytes, irp->actual_num_bytes); 65 | // we are getting only IN data here 66 | if(irp->data_buffer[0] == 0x3f) 67 | xfer_out_data((uint8_t*)"test\n", 5); 68 | else 69 | ESP_LOGI("", "%.*s", irp->actual_num_bytes, irp->data_buffer); 70 | 71 | ready = true; 72 | break; 73 | 74 | case HCD_PIPE_EVENT_ERROR_XFER: 75 | ESP_LOGW("", "XFER error: %d", irp->status); 76 | hcd_pipe_command(msg.pipe_hdl, HCD_PIPE_CMD_RESET); 77 | break; 78 | 79 | case HCD_PIPE_EVENT_ERROR_STALL: 80 | ESP_LOGW("", "Device stalled: %s pipe, state: %d", "BULK", hcd_pipe_get_state(msg.pipe_hdl)); 81 | hcd_pipe_command(msg.pipe_hdl, HCD_PIPE_CMD_RESET); 82 | break; 83 | 84 | default: 85 | ESP_LOGW("", "not handled pipe event: %d", msg.pipe_event); 86 | break; 87 | } 88 | } 89 | 90 | void usbh_get_device_desc_cb(uint8_t* data_buffer, size_t num_bytes, void* context) 91 | { 92 | ESP_LOG_BUFFER_HEX_LEVEL("DEVICE descriptor", data_buffer, num_bytes, ESP_LOG_INFO); 93 | parse_cfg_descriptor(data_buffer, 0, num_bytes, &conf_num); 94 | 95 | usb_desc_devc_t* desc = (usb_desc_devc_t*)data_buffer; 96 | xfer_get_string(port_hdl, ctrl_pipe_hdl, desc->iManufacturer); 97 | xfer_get_string(port_hdl, ctrl_pipe_hdl, desc->iSerialNumber); 98 | xfer_get_string(port_hdl, ctrl_pipe_hdl, desc->iProduct); 99 | 100 | xfer_set_address(port_hdl, ctrl_pipe_hdl, DEVICE_ADDR); 101 | } 102 | 103 | void usbh_set_address_cb(uint16_t addr, void* context) 104 | { 105 | ESP_LOGI("ADDRESS", "%d", addr); 106 | if (ESP_OK != hcd_pipe_update_dev_addr(ctrl_pipe_hdl, DEVICE_ADDR)) 107 | ESP_LOGE("", "failed to update device address"); 108 | xfer_set_configuration(port_hdl, ctrl_pipe_hdl, 1); 109 | } 110 | 111 | void usbh_get_config_desc_cb(uint8_t* data_buffer, size_t num_bytes, void* context) 112 | { 113 | ESP_LOG_BUFFER_HEX_LEVEL("CONFIG descriptor", data_buffer, num_bytes, ESP_LOG_INFO); 114 | parse_cfg_descriptor(data_buffer, 0, num_bytes, &conf_num); 115 | 116 | xfer_get_current_config(port_hdl, ctrl_pipe_hdl); 117 | } 118 | 119 | void usbh_set_config_desc_cb(uint16_t data, void* context) 120 | { 121 | ESP_LOGI("SET CONFIG", "%d", data); 122 | xfer_get_desc(port_hdl, ctrl_pipe_hdl); 123 | } 124 | 125 | void usbh_get_string_cb(uint8_t* data, size_t num_bytes, void* context) 126 | { 127 | char out[64] = {}; 128 | utf16_to_utf8((char*)data, out, num_bytes); 129 | ESP_LOGI("STRING CB", "[%d] %s", num_bytes, out); 130 | parse_cfg_descriptor(data, 0, num_bytes, &conf_num); 131 | } 132 | 133 | void usbh_ctrl_pipe_stalled_cb(usb_ctrl_req_t* ctrl) 134 | { 135 | ESP_LOG_BUFFER_HEX_LEVEL("STALLED", ctrl, 8, ESP_LOG_WARN); 136 | } 137 | 138 | void usbh_ctrl_pipe_error_cb(usb_ctrl_req_t* ctrl) 139 | { 140 | ESP_LOG_BUFFER_HEX_LEVEL("ERROR", ctrl, 8, ESP_LOG_WARN); 141 | } 142 | 143 | void usbh_get_configuration_cb(uint8_t addr, void* context) 144 | { 145 | ESP_LOGI("GET CONFIG", "%d", addr); 146 | xfer_set_control_line(port_hdl, ctrl_pipe_hdl, 1, 1); 147 | } 148 | 149 | void usbh_port_connection_cb(port_event_msg_t msg) 150 | { 151 | hcd_port_state_t state; 152 | ESP_LOGI("", "HCD_PORT_EVENT_CONNECTION"); 153 | if (HCD_PORT_STATE_DISABLED == hcd_port_get_state(msg.port_hdl)) 154 | ESP_LOGI("", "HCD_PORT_STATE_DISABLED"); 155 | if (ESP_OK == hcd_port_command(msg.port_hdl, HCD_PORT_CMD_RESET)) 156 | ESP_LOGI("", "USB device reset"); 157 | else 158 | return; 159 | if (HCD_PORT_STATE_ENABLED == hcd_port_get_state(msg.port_hdl)) 160 | { 161 | ESP_LOGI("", "HCD_PORT_STATE_ENABLED"); 162 | // we are already physically connected and ready, now we can perform software connection steps 163 | allocate_ctrl_pipe(msg.port_hdl, &ctrl_pipe_hdl); 164 | // get device descriptor on EP0, this is first mandatory step 165 | xfer_get_device_desc(msg.port_hdl, ctrl_pipe_hdl); 166 | port_hdl = msg.port_hdl; 167 | ESP_LOGW("", "port handle: %p, %p", _port_hdl, msg.port_hdl); 168 | } 169 | } 170 | 171 | void usbh_port_sudden_disconn_cb(port_event_msg_t msg) 172 | { 173 | hcd_port_state_t state; 174 | if (HCD_PIPE_STATE_INVALID == hcd_pipe_get_state(ctrl_pipe_hdl)) 175 | { 176 | ESP_LOGW("", "pipe state: %d", hcd_pipe_get_state(ctrl_pipe_hdl)); 177 | ready = false; 178 | delete_pipes(); 179 | 180 | free_pipe_and_irp_list(ctrl_pipe_hdl); 181 | ctrl_pipe_hdl = NULL; 182 | 183 | esp_err_t err; 184 | if(HCD_PORT_STATE_RECOVERY == (state = hcd_port_get_state(msg.port_hdl))){ 185 | if(ESP_OK != (err = hcd_port_recover(msg.port_hdl))) ESP_LOGE("recovery", "should be not powered state %d => (%d)", state, err); 186 | } else { 187 | ESP_LOGE("", "hcd_port_state_t: %d", state); 188 | } 189 | if(ESP_OK == hcd_port_command(msg.port_hdl, HCD_PORT_CMD_POWER_ON)) ESP_LOGI("", "Port powered ON"); 190 | } 191 | } 192 | 193 | extern void cdc_pipe_event_task(void* p); 194 | 195 | void app_main(void) 196 | { 197 | xTaskCreate(ctrl_pipe_event_task, "pipe_task", 4*1024, NULL, 10, NULL); 198 | xTaskCreate(cdc_pipe_event_task, "pipe_task", 4*1024, NULL, 10, NULL); 199 | 200 | printf("Hello world USB host!\n"); 201 | if(setup_usb_host()){ 202 | register_cdc_pipe_callback(cdc_pipe_cb); 203 | } 204 | 205 | while(1){ 206 | vTaskDelay(10); 207 | if(ready){ 208 | ready = false; 209 | xfer_in_data(); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /main/parse_data.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/semphr.h" 5 | #include "esp_intr_alloc.h" 6 | #include "esp_err.h" 7 | #include "esp_attr.h" 8 | #include "esp_rom_gpio.h" 9 | #include "soc/gpio_pins.h" 10 | #include "soc/gpio_sig_map.h" 11 | #include "hal/usbh_ll.h" 12 | #include "hcd.h" 13 | #include "esp_log.h" 14 | 15 | #define USB_W_VALUE_DT_HID 0x22 16 | #define USB_W_VALUE_DT_CS_INTERFACE 0x24 17 | 18 | static uint8_t itf = 0; 19 | extern void cdc_create_pipe(usb_desc_ep_t* ep); 20 | 21 | static void create_pipe(usb_desc_ep_t* ep) 22 | { 23 | switch (itf) 24 | { 25 | case 0x02: 26 | case 0x0A: 27 | cdc_create_pipe(ep); 28 | break; 29 | 30 | default: 31 | break; 32 | } 33 | } 34 | 35 | static char* class_to_str(uint8_t class) 36 | { 37 | itf = class; 38 | switch (class) 39 | { 40 | case 0x00: 41 | return ">ifc"; 42 | case 0x01: 43 | return "Audio"; 44 | case 0x02: 45 | return "CDC"; 46 | case 0x03: 47 | return "HID"; 48 | case 0x05: 49 | return "Physical"; 50 | case 0x06: 51 | return "Image"; 52 | case 0x07: 53 | return "Printer"; 54 | case 0x08: 55 | return "Mass Storage"; 56 | case 0x09: 57 | return "Hub"; 58 | case 0x0a: 59 | return "CDC-data"; 60 | case 0x0b: 61 | return "Smart card"; 62 | case 0x0d: 63 | return "Content security"; 64 | case 0x0e: 65 | return "Video"; 66 | case 0x0f: 67 | return "Personal heathcare"; 68 | case 0x10: 69 | return "Audio/Vdeo devices"; 70 | case 0x11: 71 | return "Bilboard"; 72 | case 0x12: 73 | return "USB-C bridge"; 74 | case 0xdc: 75 | return "Diagnostic device"; 76 | case 0xe0: 77 | return "Wireless controller"; 78 | case 0xef: 79 | return "Miscellaneous"; 80 | case 0xfe: 81 | return "Application specific"; 82 | case 0xff: 83 | return "Vendor specific"; 84 | 85 | default: 86 | return "Wrong class type"; 87 | } 88 | } 89 | 90 | static inline int bcd_to_decimal(unsigned char x) { 91 | return x - 6 * (x >> 4); 92 | } 93 | 94 | static void utf16_to_utf8(char* in, char* out, uint8_t len) 95 | { 96 | for (size_t i = 0; i < len; i++) 97 | { 98 | out[i/2] = in[i]; 99 | i++; 100 | } 101 | } 102 | 103 | static void parse_device_descriptor(uint8_t* data_buffer, usb_transfer_status_t status, uint8_t* num) 104 | { 105 | if(status == USB_TRANSFER_STATUS_COMPLETED){ 106 | printf("\nDevice descriptor:\n"); 107 | 108 | usb_desc_devc_t* desc = (usb_desc_devc_t*)data_buffer; 109 | extern uint8_t bMaxPacketSize0; 110 | bMaxPacketSize0 = desc->bMaxPacketSize0; 111 | 112 | printf("Length: %d\n", desc->bLength); 113 | printf("Descriptor type: %d\n", desc->bDescriptorType); 114 | printf("USB version: %d.%02d\n", bcd_to_decimal(desc->bcdUSB >> 8), bcd_to_decimal(desc->bcdUSB & 0xff)); 115 | printf("Device class: 0x%02x (%s)\n", desc->bDeviceClass, class_to_str(desc->bDeviceClass)); 116 | printf("Device subclass: 0x%02x\n", desc->bDeviceSubClass); 117 | printf("Device protocol: 0x%02x\n", desc->bDeviceProtocol); 118 | printf("EP0 max packet size: %d\n", desc->bMaxPacketSize0); 119 | printf("VID: 0x%04x\n", desc->idVendor); 120 | printf("PID: 0x%04x\n", desc->idProduct); 121 | printf("Revision number: %d.%02d\n", bcd_to_decimal(desc->bcdDevice >> 8), bcd_to_decimal(desc->bcdDevice & 0xff)); 122 | printf("Manufacturer id: %d\n", desc->iManufacturer); 123 | printf("Product id: %d\n", desc->iProduct); 124 | printf("Serial id: %d\n", desc->iSerialNumber); 125 | printf("Configurations num: %d\n", desc->bNumConfigurations); 126 | *num = desc->bNumConfigurations; 127 | } else { 128 | ESP_LOGW("", "status: %d", status); 129 | } 130 | } 131 | 132 | void parse_cfg_descriptor(uint8_t* data_buffer, usb_transfer_status_t status, uint8_t len, uint8_t* num) 133 | { 134 | if(!len) return; 135 | if(status == USB_TRANSFER_STATUS_COMPLETED){ 136 | uint8_t offset = 0; 137 | uint8_t type = *(&data_buffer[0] + offset + 1); 138 | do{ 139 | ESP_LOGD("", "type: %d\n", type); 140 | switch (type) 141 | { 142 | case USB_W_VALUE_DT_DEVICE: 143 | parse_device_descriptor(data_buffer, status, num); 144 | offset += len; 145 | break; 146 | 147 | case USB_W_VALUE_DT_CONFIG:{ 148 | printf("\nConfig:\n"); 149 | usb_desc_cfg_t* data = (usb_desc_cfg_t*)(data_buffer + offset); 150 | printf("Number of Interfaces: %d\n", data->bNumInterfaces); 151 | // printf("type: %d\n", data->bConfigurationValue); 152 | // printf("type: %d\n", data->iConfiguration); 153 | printf("Attributes: 0x%02x\n", data->bmAttributes); 154 | printf("Max power: %d mA\n", data->bMaxPower * 2); 155 | offset += data->bLength; 156 | break; 157 | } 158 | case USB_W_VALUE_DT_STRING:{ 159 | usb_desc_str_t* data = (usb_desc_str_t*)(data_buffer + offset); 160 | uint8_t len = 0; 161 | len = data->bLength; 162 | offset += len; 163 | char* str = (char*)calloc(1, len); 164 | utf16_to_utf8((char*)&data->val[2], str, len); 165 | printf("strings: %s\n", str); 166 | free(str); 167 | break; 168 | } 169 | case USB_W_VALUE_DT_INTERFACE:{ 170 | printf("\nInterface:\n"); 171 | usb_desc_intf_t* data = (usb_desc_intf_t*)(data_buffer + offset); 172 | offset += data->bLength; 173 | printf("bInterfaceNumber: %d\n", data->bInterfaceNumber); 174 | printf("bAlternateSetting: %d\n", data->bAlternateSetting); 175 | printf("bNumEndpoints: %d\n", data->bNumEndpoints); 176 | printf("bInterfaceClass: 0x%02x (%s)\n", data->bInterfaceClass, class_to_str(data->bInterfaceClass)); 177 | printf("bInterfaceSubClass: 0x%02x\n", data->bInterfaceSubClass); 178 | printf("bInterfaceProtocol: 0x%02x\n", data->bInterfaceProtocol); 179 | break; 180 | } 181 | case USB_W_VALUE_DT_ENDPOINT:{ 182 | printf("\nEndpoint:\n"); 183 | usb_desc_ep_t* data = (usb_desc_ep_t*)(data_buffer + offset); 184 | offset += data->bLength; 185 | printf("bEndpointAddress: 0x%02x\n", data->bEndpointAddress); 186 | printf("bmAttributes: 0x%02x\n", data->bmAttributes); 187 | printf("bDescriptorType: %d\n", data->bDescriptorType); 188 | printf("wMaxPacketSize: %d\n", data->wMaxPacketSize); 189 | printf("bInterval: %d ms\n", data->bInterval); 190 | create_pipe(data); 191 | break; 192 | } 193 | case USB_W_VALUE_DT_CS_INTERFACE:{ 194 | printf("\nCS_Interface:\n"); 195 | usb_desc_intf_t* data = (usb_desc_intf_t*)(data_buffer + offset); 196 | offset += data->bLength; 197 | 198 | break; 199 | } 200 | default: 201 | ESP_LOGI("", "unknown descriptor: %d", type); 202 | ESP_LOG_BUFFER_HEX_LEVEL("Actual data", data_buffer, len, ESP_LOG_DEBUG); 203 | 204 | offset += *(data_buffer + offset); 205 | break; 206 | } 207 | if(offset >= len) break; 208 | type = *(data_buffer + offset + 1); 209 | }while(1); 210 | } else { 211 | ESP_LOGW("", "status: %d", (uint8_t)status); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /main/usb_host_port.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "freertos/FreeRTOS.h" 3 | #include "freertos/semphr.h" 4 | #include "esp_intr_alloc.h" 5 | #include "esp_err.h" 6 | #include "esp_attr.h" 7 | #include "esp_rom_gpio.h" 8 | #include "soc/gpio_pins.h" 9 | #include "soc/gpio_sig_map.h" 10 | #include "hal/usbh_ll.h" 11 | #include "hcd.h" 12 | #include "esp_log.h" 13 | #include "usb_host_port.h" 14 | 15 | #define EVENT_QUEUE_LEN 10 16 | #define XFER_DATA_MAX_LEN 256 //Just assume that will only IN/OUT 256 bytes for now 17 | #define PORT_NUM 1 // we have only 1 port 18 | 19 | static port_evt_cb_t port_evt_cb; 20 | static QueueHandle_t port_evt_queue; 21 | 22 | // -------------------------------------------------- PHY Control ------------------------------------------------------ 23 | 24 | static void phy_force_conn_state(bool connected, TickType_t delay_ticks) 25 | { 26 | vTaskDelay(delay_ticks); 27 | usb_wrap_dev_t *wrap = &USB_WRAP; 28 | if (connected) { 29 | //Swap back to internal PHY that is connected to a devicee 30 | wrap->otg_conf.phy_sel = 0; 31 | } else { 32 | //Set externa PHY input signals to fixed voltage levels mimicing a disconnected state 33 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VP_IDX, false); 34 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VM_IDX, false); 35 | esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_EXTPHY_RCV_IDX, false); 36 | //Swap to the external PHY 37 | wrap->otg_conf.phy_sel = 1; 38 | } 39 | } 40 | 41 | // ------------------------------------------------ Helper Functions --------------------------------------------------- 42 | 43 | static bool port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) 44 | { 45 | QueueHandle_t port_evt_queue = (QueueHandle_t)user_arg; 46 | port_event_msg_t msg = { 47 | .port_hdl = port_hdl, 48 | .port_event = port_event, 49 | }; 50 | 51 | BaseType_t xTaskWoken = pdFALSE; 52 | xQueueSendFromISR(port_evt_queue, &msg, &xTaskWoken); 53 | return (xTaskWoken == pdTRUE); 54 | } 55 | 56 | static void port_event_task(void* p) 57 | { 58 | port_event_msg_t msg; 59 | while(1){ 60 | xQueueReceive(port_evt_queue, &msg, portMAX_DELAY); 61 | ESP_LOGI("", "port event: %d", msg.port_event); 62 | hcd_port_handle_event(msg.port_hdl); 63 | switch (msg.port_event) 64 | { 65 | case HCD_PORT_EVENT_NONE: 66 | break; 67 | 68 | case HCD_PORT_EVENT_CONNECTION: 69 | usbh_port_connection_cb(msg); 70 | break; 71 | 72 | case HCD_PORT_EVENT_DISCONNECTION: 73 | hcd_port_command(msg.port_hdl, HCD_PORT_CMD_POWER_OFF); 74 | usbh_port_disconnection_cb(msg); 75 | break; 76 | 77 | case HCD_PORT_EVENT_ERROR: 78 | usbh_port_error_cb(msg); 79 | break; 80 | 81 | case HCD_PORT_EVENT_OVERCURRENT: 82 | usbh_port_overcurrent_cb(msg); 83 | break; 84 | 85 | case HCD_PORT_EVENT_SUDDEN_DISCONN: 86 | hcd_port_command(msg.port_hdl, HCD_PORT_CMD_RESET); 87 | usbh_port_sudden_disconn_cb(msg); 88 | break; 89 | } 90 | if(port_evt_cb != NULL) 91 | { 92 | port_evt_cb(msg); 93 | } 94 | } 95 | } 96 | 97 | // ------------------------------------------------ Public Functions --------------------------------------------------- 98 | 99 | /** 100 | * @brief Creates port and pipe event queues. Sets up the HCD, and initializes a port. 101 | */ 102 | bool setup_usb_host() 103 | { 104 | port_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(port_event_msg_t)); 105 | 106 | if(port_evt_queue) xTaskCreate(port_event_task, "port_task", 4*1024, NULL, 10, NULL); 107 | 108 | //Install HCD 109 | hcd_config_t config = { 110 | .intr_flags = ESP_INTR_FLAG_LEVEL1, 111 | }; 112 | if(hcd_install(&config) == ESP_OK) { 113 | //Initialize a port 114 | hcd_port_config_t port_config = { 115 | .callback = port_callback, 116 | .callback_arg = (void *)port_evt_queue, 117 | .context = NULL, 118 | }; 119 | esp_err_t err; 120 | if(ESP_OK == (err = hcd_port_init(PORT_NUM, &port_config, &port_hdl))){ 121 | if(HCD_PORT_STATE_NOT_POWERED == hcd_port_get_state(port_hdl)) ESP_LOGI("", "USB host setup properly"); 122 | 123 | phy_force_conn_state(false, 0); //Force disconnected state on PHY 124 | if(ESP_OK != hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_ON)) return false; 125 | ESP_LOGI("", "Port is power ON now"); 126 | phy_force_conn_state(true, pdMS_TO_TICKS(10)); //Allow for connected state on PHY 127 | return true; 128 | } else { 129 | ESP_LOGE("", "Error to init port: %d!!!", err); 130 | } 131 | } else { 132 | ESP_LOGE("", "Error to install HCD!!!"); 133 | } 134 | return false; 135 | } 136 | 137 | void register_port_callback(port_evt_cb_t cb) 138 | { 139 | port_evt_cb = cb; 140 | } 141 | -------------------------------------------------------------------------------- /main/usb_host_port.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "hcd.h" 3 | 4 | #define USBH_WEAK_CB __attribute__((weak)) 5 | 6 | typedef struct { 7 | hcd_port_handle_t port_hdl; 8 | hcd_port_event_t port_event; 9 | } port_event_msg_t; 10 | 11 | typedef void (*port_evt_cb_t)(port_event_msg_t msg); 12 | 13 | hcd_port_handle_t port_hdl; 14 | 15 | bool setup_usb_host(); 16 | void register_port_callback(port_evt_cb_t cb); 17 | 18 | USBH_WEAK_CB void usbh_port_connection_cb(port_event_msg_t); 19 | USBH_WEAK_CB void usbh_port_disconnection_cb(port_event_msg_t); 20 | USBH_WEAK_CB void usbh_port_error_cb(port_event_msg_t); 21 | USBH_WEAK_CB void usbh_port_overcurrent_cb(port_event_msg_t); 22 | USBH_WEAK_CB void usbh_port_sudden_disconn_cb(port_event_msg_t); 23 | --------------------------------------------------------------------------------