diff --git a/components/hal/include/hal/usb_types_private.h b/components/hal/include/hal/usb_types_private.h index 868fdee589..5cce6b66af 100644 --- a/components/hal/include/hal/usb_types_private.h +++ b/components/hal/include/hal/usb_types_private.h @@ -16,7 +16,7 @@ Note: This header file contains USB2.0 related types and macros that can be used by code specific to the DWC_OTG controller (i.e., the HW specific layers of the USB host stack). Thus, this header is only meant to be used below (and including) the HAL layer. For types and macros that are HW implementation agnostic (i.e., HCD layer and above), add them -to the "usb.h" header instead. +to the "usb/usb_types_ch9.h" header instead. */ #pragma once diff --git a/components/hal/include/hal/usbh_hal.h b/components/hal/include/hal/usbh_hal.h index 1ff721c373..b315f6c0b1 100644 --- a/components/hal/include/hal/usbh_hal.h +++ b/components/hal/include/hal/usbh_hal.h @@ -151,7 +151,7 @@ typedef struct { //Channel control, status, and information union { struct { - uint32_t active: 1; /**< The channel is enabled */ + uint32_t active: 1; /**< Debugging bit to indicate whether channel is enabled */ uint32_t halt_requested: 1; /**< A halt has been requested */ uint32_t error_pending: 1; /**< The channel is waiting for the error to be handled */ uint32_t reserved: 1; @@ -811,6 +811,8 @@ usbh_hal_chan_t *usbh_hal_get_chan_pending_intr(usbh_hal_context_t *hal); * - Returns the corresponding event for that channel * * @param chan_obj Channel object + * @note If the host port has an error (e.g., a sudden disconnect or an port error), any active channels will not + * receive an interrupt. Each active channel must be manually halted. * @return usbh_hal_chan_event_t Channel event */ usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj); diff --git a/components/hal/usbh_hal.c b/components/hal/usbh_hal.c index c85438c61b..5091952de8 100644 --- a/components/hal/usbh_hal.c +++ b/components/hal/usbh_hal.c @@ -291,27 +291,19 @@ void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj) { //Cannot request halt on a channel that is pending error handling - HAL_ASSERT(!chan_obj->flags.error_pending); - if (usbh_ll_chan_is_active(chan_obj->regs) || chan_obj->flags.active) { + HAL_ASSERT(chan_obj->flags.active && !chan_obj->flags.error_pending); + if (usbh_ll_chan_is_active(chan_obj->regs)) { usbh_ll_chan_halt(chan_obj->regs); chan_obj->flags.halt_requested = 1; return false; + } else { + chan_obj->flags.active = 0; + return true; } - return true; } // ------------------------------------------------- Event Handling ---------------------------------------------------- -//When a device on the port is no longer valid (e.g., disconnect, port error). All channels are no longer valid -static void chan_all_halt(usbh_hal_context_t *hal) -{ - for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) { - if (hal->channels.hdls[i] != NULL) { - hal->channels.hdls[i]->flags.active = 0; - } - } -} - usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal) { uint32_t intrs_core = usb_ll_intr_read_and_clear(hal->dev); //Read and clear core interrupts @@ -321,7 +313,7 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal) intrs_port = usbh_ll_hprt_intr_read_and_clear(hal->dev); } //Note: Do not change order of checks. Regressing events (e.g. enable -> disabled, connected -> connected) - //always take precendance. ENABLED < DISABLED < CONN < DISCONN < OVRCUR + //always take precedence. ENABLED < DISABLED < CONN < DISCONN < OVRCUR usbh_hal_port_event_t event = USBH_HAL_PORT_EVENT_NONE; //Check if this is a core or port event @@ -330,13 +322,11 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal) if (intrs_core & USB_LL_INTR_CORE_DISCONNINT) { event = USBH_HAL_PORT_EVENT_DISCONN; debounce_lock_enable(hal); - chan_all_halt(hal); //All channels are halted on a disconnect //Mask the port connection and disconnection interrupts to prevent repeated triggering } else if (intrs_port & USBH_LL_INTR_HPRT_PRTOVRCURRCHNG) { //Check if this is an overcurrent or an overcurrent cleared if (usbh_ll_hprt_get_port_overcur(hal->dev)) { event = USBH_HAL_PORT_EVENT_OVRCUR; - chan_all_halt(hal); //All channels are halted on an overcurrent } else { event = USBH_HAL_PORT_EVENT_OVRCUR_CLR; } @@ -345,14 +335,13 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal) event = USBH_HAL_PORT_EVENT_ENABLED; } else { //Host port has been disabled event = USBH_HAL_PORT_EVENT_DISABLED; - chan_all_halt(hal); //All channels are halted when the port is disabled } } else if (intrs_port & USBH_LL_INTR_HPRT_PRTCONNDET && !hal->flags.dbnc_lock_enabled) { event = USBH_HAL_PORT_EVENT_CONN; debounce_lock_enable(hal); } } - //Port events always take precendance over channel events + //Port events always take precedence over channel events if (event == USBH_HAL_PORT_EVENT_NONE && (intrs_core & USB_LL_INTR_CORE_HCHINT)) { //One or more channels have pending interrupts. Store the mask of those channels hal->channels.chan_pend_intrs_msk = usbh_ll_get_chan_intrs_msk(hal->dev); diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 20d143ea6e..50c5deb399 100644 --- a/components/usb/CMakeLists.txt +++ b/components/usb/CMakeLists.txt @@ -4,7 +4,13 @@ set(priv_include) set(priv_require) if(CONFIG_USB_OTG_SUPPORTED) - list(APPEND srcs "hcd.c") + list(APPEND srcs "hcd.c" + "hub.c" + "usb_host_misc.c" + "usb_host.c" + "usb_private.c" + "usbh.c") + list(APPEND include "include") list(APPEND priv_include "private_include") list(APPEND priv_require "hal" "driver") endif() diff --git a/components/usb/Kconfig b/components/usb/Kconfig index 2be5c9da6b..939a2b1d14 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -6,4 +6,51 @@ menu "USB-OTG" bool default y if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 -endmenu + config USB_HOST_CONTROL_TRANSFER_MAX_SIZE + depends on USB_OTG_SUPPORTED + int "Largest size (in bytes) of transfers to/from default endpoints" + default 256 + help + Each USB device attached is allocated a dedicated buffer for its OUT/IN transfers to/from the device's + control endpoint. The maximum size of that buffer is determined by this option. The limited size of the + transfer buffer have the following implications: + - The maximum length of control transfers is limited + - Device's with configuration descriptors larger than this limit cannot be supported + + choice USB_HOST_HW_BUFFER_BIAS + depends on USB_OTG_SUPPORTED + prompt "Hardware FIFO size biasing" + default USB_HOST_HW_BUFFER_BIAS_BALANCED + help + The underlying hardware has size adjustable FIFOs to cache USB packets on reception (IN) or for + transmission (OUT). The size of these FIFOs will affect the largest MPS (maximum packet size) and the + maximum number of packets that can be cached at any one time. The hardware contains the following + FIFOS: RX (for all IN packets), Non-periodic TX (for Bulk and Control OUT packets), and Periodic TX + (for Interrupt and Isochronous OUT packets). This configuration option allows biasing the FIFO sizes + towards a particular use case, which may be necessary for devices that have endpoints with large MPS. + The MPS limits for each biasing are listed below: + + Balanced: + - IN (all transfer types), 408 bytes + - OUT non-periodic (Bulk/Control), 192 bytes (i.e., 3 x 64 byte packets) + - OUT periodic (Interrupt/Isochronous), 192 bytes + + Bias IN: + - IN (all transfer types), 600 bytes + - OUT non-periodic (Bulk/Control), 64 bytes (i.e., 1 x 64 byte packets) + - OUT periodic (Interrupt/Isochronous), 128 bytes + + Bias Periodic OUT: + - IN (all transfer types), 128 bytes + - OUT non-periodic (Bulk/Control), 64 bytes (i.e., 1 x 64 byte packets) + - OUT periodic (Interrupt/Isochronous), 600 bytes + + config USB_HOST_HW_BUFFER_BIAS_BALANCED + bool "Balanced" + config USB_HOST_HW_BUFFER_BIAS_IN + bool "Bias IN" + config USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT + bool "Periodic OUT" + endchoice + +endmenu #USB-OTG diff --git a/components/usb/hcd.c b/components/usb/hcd.c index 5e94b5921b..87be3a350a 100644 --- a/components/usb/hcd.c +++ b/components/usb/hcd.c @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include @@ -30,7 +22,9 @@ #include "driver/periph_ctrl.h" #include "hcd.h" #include "usb_private.h" -#include "usb.h" +#include "usb/usb_types_ch9.h" + +#include "esp_rom_sys.h" // ----------------------------------------------------- Macros -------------------------------------------------------- @@ -219,13 +213,11 @@ typedef struct { } flags; union { struct { - uint32_t stop_idx: 8; //The descriptor index when the channel was halted uint32_t executing: 1; //The buffer is currently executing - uint32_t error_occurred: 1; //An error occurred - uint32_t cancelled: 1; //The buffer was actively cancelled - uint32_t reserved5: 5; - hcd_pipe_state_t pipe_state: 8; //The pipe's state when the error occurred - hcd_pipe_event_t pipe_event: 8; //The pipe event when the error occurred + uint32_t reserved7: 7; + uint32_t stop_idx: 8; //The descriptor index when the channel was halted + hcd_pipe_event_t pipe_event: 8; //The pipe event when the buffer was done + uint32_t reserved8: 8; }; uint32_t val; } status_flags; //Status flags for the buffer @@ -236,8 +228,8 @@ typedef struct { */ struct pipe_obj { //URB queueing related - TAILQ_HEAD(tailhead_urb_pending, urb_obj) pending_urb_tailq; - TAILQ_HEAD(tailhead_urb_done, urb_obj) done_urb_tailq; + TAILQ_HEAD(tailhead_urb_pending, urb_s) pending_urb_tailq; + TAILQ_HEAD(tailhead_urb_done, urb_s) done_urb_tailq; int num_urb_pending; int num_urb_done; //Multi-buffer control @@ -268,13 +260,12 @@ struct pipe_obj { TaskHandle_t task_waiting_pipe_notif; //Task handle used for internal pipe events union { struct { - uint32_t waiting_xfer_done: 1; - uint32_t paused: 1; + uint32_t waiting_halt: 1; uint32_t pipe_cmd_processing: 1; - uint32_t is_active: 1; + uint32_t has_urb: 1; //Indicates there is at least one URB either pending, inflight, or done uint32_t persist: 1; //indicates that this pipe should persist through a run-time port reset uint32_t reset_lock: 1; //Indicates that this pipe is undergoing a run-time reset - uint32_t reserved26: 26; + uint32_t reserved27: 27; }; uint32_t val; } cs_flags; @@ -305,17 +296,17 @@ struct port_obj { uint32_t event_pending: 1; //The port has an event that needs to be handled uint32_t event_processing: 1; //The port is current processing (handling) an event uint32_t cmd_processing: 1; //Used to indicate command handling is ongoing - uint32_t waiting_all_pipes_pause: 1; //Waiting for all pipes routed through this port to be paused uint32_t disable_requested: 1; uint32_t conn_dev_ena: 1; //Used to indicate the port is connected to a device that has been reset uint32_t periodic_scheduling_enabled: 1; - uint32_t reserved9:9; - uint32_t num_pipes_waiting_pause: 16; + uint32_t reserved26: 26; }; uint32_t val; } flags; bool initialized; - hcd_port_fifo_bias_t fifo_bias; + //FIFO biasing related + const usbh_hal_fifo_config_t *fifo_config; + const fifo_mps_limits_t *fifo_mps_limits; //Port callback and context hcd_port_callback_t callback; void *callback_arg; @@ -400,13 +391,30 @@ static void _buffer_exec(pipe_t *pipe); * @brief Check if a buffer as completed execution * * This should only be called after receiving a USBH_HAL_CHAN_EVENT_CPLT event to check if a buffer is actually - * done. Buffers that aren't complete (such as Control transfers) will be continued automatically. + * done. * * @param pipe Pipe object * @return true Buffer complete * @return false Buffer not complete */ -static bool _buffer_check_done(pipe_t *pipe); +static inline bool _buffer_check_done(pipe_t *pipe) +{ + if (pipe->ep_char.type != USB_PRIV_XFER_TYPE_CTRL) { + return true; + } + //Only control transfers need to be continued + dma_buffer_block_t *buffer_inflight = pipe->buffers[pipe->multi_buffer_control.rd_idx]; + return (buffer_inflight->flags.ctrl.cur_stg == 2); +} + +/** + * @brief Continue execution of a buffer + * + * This should only be called after checking if a buffer has completed execution using _buffer_check_done() + * + * @param pipe Pipe object + */ +static void _buffer_exec_cont(pipe_t *pipe); /** * @brief Marks the last executed buffer as complete @@ -415,40 +423,14 @@ static bool _buffer_check_done(pipe_t *pipe); * * @param pipe Pipe object * @param stop_idx Descriptor index when the buffer stopped execution + * @param pipe_event Pipe event that caused the buffer to be complete */ -static inline void _buffer_done(pipe_t *pipe, int stop_idx) +static inline void _buffer_done(pipe_t *pipe, int stop_idx, hcd_pipe_event_t pipe_event) { - //Store the stop_idx for later parsing + //Store the stop_idx and pipe_event for later parsing dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx]; buffer_done->status_flags.executing = 0; - buffer_done->status_flags.error_occurred = 0; buffer_done->status_flags.stop_idx = stop_idx; - pipe->multi_buffer_control.rd_idx++; - pipe->multi_buffer_control.buffer_num_to_exec--; - pipe->multi_buffer_control.buffer_num_to_parse++; - pipe->multi_buffer_control.buffer_is_executing = 0; -} - -/** - * @brief Marks the last executed buffer as complete due to an error - * - * This should be called on a pipe that has received a USBH_HAL_CHAN_EVENT_ERROR event - * - * @param pipe Pipe object - * @param stop_idx Descriptor index when the buffer stopped execution - * @param pipe_state State of the pipe after the error - * @param pipe_event Error event - * @param cancelled Whether the pipe stopped due to cancellation - */ -static inline void _buffer_done_error(pipe_t *pipe, int stop_idx, hcd_pipe_state_t pipe_state, hcd_pipe_event_t pipe_event, bool cancelled) -{ - //Mark the buffer as erroneous for later parsing - dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx]; - buffer_done->status_flags.executing = 0; - buffer_done->status_flags.error_occurred = 1; - buffer_done->status_flags.cancelled = cancelled; - buffer_done->status_flags.stop_idx = stop_idx; - buffer_done->status_flags.pipe_state = pipe_state; buffer_done->status_flags.pipe_event = pipe_event; pipe->multi_buffer_control.rd_idx++; pipe->multi_buffer_control.buffer_num_to_exec--; @@ -493,50 +475,13 @@ static void _buffer_parse(pipe_t *pipe); * * @param pipe Pipe object * @param cancelled Whether this flush is due to cancellation + * @return true One or more buffers were flushed + * @return false There were no buffers that needed to be flushed */ -static void _buffer_flush_all(pipe_t *pipe, bool cancelled); +static bool _buffer_flush_all(pipe_t *pipe, bool cancelled); // ------------------------ Pipe --------------------------- -/** - * @brief Wait until a pipe's in-flight URB is done - * - * If the pipe has an in-flight URB, this function will block until it is done (via a internal pipe event). - * If the pipe has no in-flight URB, this function do nothing and return immediately. - * If the pipe's state changes unexpectedly, this function will return false. - * - * Also parses all buffers on exit - * - * @note This function is blocking (will exit and re-enter the critical section to do so) - * - * @param pipe Pipe object - * @return true Pipes in-flight URB is done - * @return false Pipes state unexpectedly changed - */ -static bool _pipe_wait_done(pipe_t *pipe); - -/** - * @brief Retires all URBs (those that were previously in-flight or pending) - * - * Retiring all URBs will result in any pending URB being moved to the done tailq. This function will update the IPR - * status of each URB. - * - If the retiring is self-initiated (i.e., due to a pipe command), the URB status will be set to USB_TRANSFER_STATUS_CANCELED. - * - If the retiring is NOT self-initiated (i.e., the pipe is no longer valid), the URB status will be set to USB_TRANSFER_STATUS_NO_DEVICE - * - * Entry: - * - There can be no in-flight URB (must already be parsed and returned to done queue) - * - All buffers must be parsed - * Exit: - * - If there was an in-flight URB, it is parsed and returned to the done queue - * - If there are any pending URBs: - * - They are moved to the done tailq - * - * @param pipe Pipe object - * @param cancelled Are we actively Pipe retire is initialized by the user due to a command, thus URB are - * actively cancelled. - */ -static void _pipe_retire(pipe_t *pipe, bool self_initiated); - /** * @brief Decode a HAL channel error to the corresponding pipe event * @@ -545,59 +490,47 @@ static void _pipe_retire(pipe_t *pipe, bool self_initiated); */ static inline hcd_pipe_event_t pipe_decode_error_event(usbh_hal_chan_error_t chan_error); +/** + * @brief Halt a pipe + * + * - Attempts to halt a pipe. Pipe must be active in order to be halted + * - If the underlying channel has an ongoing transfer, a halt will be requested, then the function will block until the + * channel indicates it is halted + * - If the channel is no on-going transfer, the pipe will simply be marked has halted (thus preventing any further URBs + * from being enqueued) + * + * @note This function can block + * @param pipe Pipe object + * @return esp_err_t + */ +static esp_err_t _pipe_cmd_halt(pipe_t *pipe); + +/** + * @brief Flush a pipe + * + * - Flushing a pipe causes all of its pending URBs to be become done, thus allowing them to be dequeued + * - The pipe must be halted in order to be flushed + * - The pipe callback will be run if one or more URBs become done + * + * @param pipe Pipe object + * @return esp_err_t + */ +static esp_err_t _pipe_cmd_flush(pipe_t *pipe); + +/** + * @brief Clear a pipe from its halt + * + * - Pipe must be halted in order to be cleared + * - Clearing a pipe makes it active again + * - If there are any enqueued URBs, they will executed + * + * @param pipe Pipe object + * @return esp_err_t + */ +static esp_err_t _pipe_cmd_clear(pipe_t *pipe); + // ------------------------ Port --------------------------- -/** - * @brief Invalidates all the pipes routed through a port - * - * This should be called when port or its connected device is no longer valid (e.g., the port is suddenly reset/disabled - * or the device suddenly disconnects) - * - * @note This function may run one or more callbacks, and will exit and enter the critical section to do so - * - * Entry: - * - The port or its connected device is no longer valid. This guarantees that none of the pipes will be transferring - * Exit: - * - Each pipe will have any pending URBs moved to their respective done tailq - * - Each pipe will be put into the invalid state - * - Generate a HCD_PIPE_EVENT_INVALID event on each pipe and run their respective callbacks - * - * @param port Port object - */ -static void _port_invalidate_all_pipes(port_t *port); - -/** - * @brief Pause all pipes routed through a port - * - * Call this before attempting to reset or suspend a port - * - * Entry: - * - The port is in the HCD_PORT_STATE_ENABLED state (i.e., there is a connected device which has been reset) - * Exit: - * - All pipes routed through the port have either paused, or are waiting to complete their in-flight URBs before pausing - * - If waiting for one or more pipes to pause, _internal_port_event_wait() must be called after this function returns - * - * @param port Port object - * @return true All pipes have been paused - * @return false Need to wait for one or more pipes to pause. Call _internal_port_event_wait() afterwards - */ -static bool _port_pause_all_pipes(port_t *port); - -/** - * @brief Un-pause all pipes routed through a port - * - * Call this before after coming out of a port reset or resume. - * - * Entry: - * - The port is in the HCD_PORT_STATE_ENABLED state - * - All pipes are paused - * Exit: - * - All pipes un-paused. If those pipes have pending URBs, they will be started. - * - * @param port Port object - */ -static void _port_unpause_all_pipes(port_t *port); - /** * @brief Prepare persistent pipes for reset * @@ -621,72 +554,13 @@ static bool _port_persist_all_pipes(port_t *port); static void _port_recover_all_pipes(port_t *port); /** - * @brief Send a reset condition on a port's bus - * - * Entry: - * - The port must be in the HCD_PORT_STATE_ENABLED or HCD_PORT_STATE_DISABLED state - * Exit: - * - Reset condition sent on the port's bus - * - * @note This function is blocking (will exit and re-enter the critical section to do so) + * @brief Checks if all pipes are in the halted state * * @param port Port object - * @return true Reset condition successfully sent - * @return false Failed to send reset condition due to unexpected port state + * @return true All pipes are halted + * @return false Not all pipes are halted */ -static bool _port_bus_reset(port_t *port); - -/** - * @brief Send a suspend condition on a port's bus - * - * This function will first pause pipes routed through a port, and then send a suspend condition. - * - * Entry: - * - The port must be in the HCD_PORT_STATE_ENABLED state - * Exit: - * - All pipes paused and the port is put into the suspended state - * - * @note This function is blocking (will exit and re-enter the critical section to do so) - * - * @param port Port object - * @return true Suspend condition successfully sent. Port is now in the HCD_PORT_STATE_SUSPENDED state - * @return false Failed to send a suspend condition due to unexpected port state - */ -static bool _port_bus_suspend(port_t *port); - -/** - * @brief Send a resume condition on a port's bus - * - * This function will send a resume condition, and then un-pause all the pipes routed through a port - * - * Entry: - * - The port must be in the HCD_PORT_STATE_SUSPENDED state - * Exit: - * - The port is put into the enabled state and all pipes un-paused - * - * @note This function is blocking (will exit and re-enter the critical section to do so) - * - * @param port Port object - * @return true Resume condition successfully sent. Port is now in the HCD_PORT_STATE_ENABLED state - * @return false Failed to send a resume condition due to unexpected port state. - */ -static bool _port_bus_resume(port_t *port); - -/** - * @brief Disable a port - * - * Entry: - * - The port must be in the HCD_PORT_STATE_ENABLED or HCD_PORT_STATE_SUSPENDED state - * Exit: - * - All pipes paused (should already be paused if port was suspended), and the port is put into the disabled state. - * - * @note This function is blocking (will exit and re-enter the critical section to do so) - * - * @param port Port object - * @return true Port successfully disabled - * @return false Port to disable port due to unexpected port state - */ -static bool _port_disable(port_t *port); +static bool _port_check_all_pipes_halted(port_t *port); /** * @brief Debounce port after a connection or disconnection event @@ -694,12 +568,77 @@ static bool _port_disable(port_t *port); * This function should be called after a port connection or disconnect event. This function will execute a debounce * delay then check the actual connection/disconnections state. * + * @note This function can block * @param port Port object * @return true A device is connected * @return false No device connected */ static bool _port_debounce(port_t *port); +/** + * @brief Power ON the port + * + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_power_on(port_t *port); + +/** + * @brief Power OFF the port + * + * - If a device is currently connected, this function will cause a disconnect event + * + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_power_off(port_t *port); + +/** + * @brief Reset the port + * + * - This function issues a reset signal using the timings specified by the USB2.0 spec + * + * @note This function can block + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_reset(port_t *port); + +/** + * @brief Suspend the port + * + * - Port must be enabled in order to to be suspended + * - All pipes must be halted for the port to be suspended + * - Suspending the port stops Keep Alive/SOF from being sent to the connected device + * + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_bus_suspend(port_t *port); + +/** + * @brief Resume the port + * + * - Port must be suspended in order to be resumed + * + * @note This function can block + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_bus_resume(port_t *port); + +/** + * @brief Disable the port + * + * - All pipes must be halted for the port to be disabled + * - The port must be enabled or suspended in order to be disabled + * + * @note This function can block + * @param port Port object + * @return esp_err_t + */ +static esp_err_t _port_cmd_disable(port_t *port); + // ----------------------- Events -------------------------- /** @@ -822,14 +761,8 @@ static hcd_port_event_t _intr_hdlr_hprt(port_t *port, usbh_hal_port_event_t hal_ break; } case USBH_HAL_PORT_EVENT_DISCONN: { - if (port->flags.conn_dev_ena) { - //The port was previously enabled, so this is a sudden disconnection - port->state = HCD_PORT_STATE_RECOVERY; - port_event = HCD_PORT_EVENT_SUDDEN_DISCONN; - } else { - //For normal disconnections, don't update state immediately as we still need to debounce. - port_event = HCD_PORT_EVENT_DISCONNECTION; - } + port->state = HCD_PORT_STATE_RECOVERY; + port_event = HCD_PORT_EVENT_DISCONNECTION; port->flags.conn_dev_ena = 0; break; } @@ -892,31 +825,27 @@ static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj, { usbh_hal_chan_event_t chan_event = usbh_hal_chan_decode_intr(chan_obj); hcd_pipe_event_t event = HCD_PIPE_EVENT_NONE; - //Check the the pipe's port still has a connected and enabled device before processing the interrupt - if (!pipe->port->flags.conn_dev_ena) { - return event; //Treat as a no event. - } - bool handle_waiting_xfer_done = false; + switch (chan_event) { case USBH_HAL_CHAN_EVENT_CPLT: { if (!_buffer_check_done(pipe)) { + _buffer_exec_cont(pipe); break; } pipe->last_event = HCD_PIPE_EVENT_URB_DONE; event = pipe->last_event; //Mark the buffer as done int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); - _buffer_done(pipe, stop_idx); - //First check if there is another buffer we can execute - if (_buffer_can_exec(pipe) && !pipe->cs_flags.waiting_xfer_done) { + _buffer_done(pipe, stop_idx, pipe->last_event); + //First check if there is another buffer we can execute. But we only want to execute if there's still a valid device + if (_buffer_can_exec(pipe) && pipe->port->flags.conn_dev_ena) { //If the next buffer is filled and ready to execute, execute it _buffer_exec(pipe); } //Handle the previously done buffer _buffer_parse(pipe); - if (pipe->cs_flags.waiting_xfer_done) { - handle_waiting_xfer_done = true; - } else if (_buffer_can_fill(pipe)) { + //Check to see if we can fill another buffer. But we only want to fill if there is still a valid device + if (_buffer_can_fill(pipe) && pipe->port->flags.conn_dev_ena) { //Now that we've parsed a buffer, see if another URB can be filled in its place _buffer_fill(pipe); } @@ -931,39 +860,33 @@ static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj, pipe->state = HCD_PIPE_STATE_HALTED; //Mark the buffer as done with an error int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); - _buffer_done_error(pipe, stop_idx, pipe->state, pipe->last_event, false); + _buffer_done(pipe, stop_idx, pipe->last_event); //Parse the buffer _buffer_parse(pipe); - if (pipe->cs_flags.waiting_xfer_done) { - handle_waiting_xfer_done = true; - } + break; + } + case USBH_HAL_CHAN_EVENT_HALT_REQ: { + assert(pipe->cs_flags.waiting_halt); + //We've halted a transfer, so we need to trigger the pipe callback + pipe->last_event = HCD_PIPE_EVENT_URB_DONE; + event = pipe->last_event; + //Halt request event is triggered when packet is successful completed. But just treat all halted transfers as errors + pipe->state = HCD_PIPE_STATE_HALTED; + int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); + _buffer_done(pipe, stop_idx, HCD_PIPE_EVENT_NONE); + //Parse the buffer + _buffer_parse(pipe); + //Notify the task waiting for the pipe halt + *yield |= _internal_pipe_event_notify(pipe, true); break; } case USBH_HAL_CHAN_EVENT_NONE: { break; //Nothing to do } - case USBH_HAL_CHAN_EVENT_HALT_REQ: //We currently don't halt request so this event should never occur default: abort(); break; } - if (handle_waiting_xfer_done) { - //A port/pipe command is waiting for this pipe to complete its transfer. So don't load the next transfer - pipe->cs_flags.waiting_xfer_done = 0; - if (pipe->port->flags.waiting_all_pipes_pause) { - //Port command is waiting for all pipes to be paused - pipe->cs_flags.paused = 1; - pipe->port->flags.num_pipes_waiting_pause--; - if (pipe->port->flags.num_pipes_waiting_pause == 0) { - //All pipes have finished pausing, Notify the blocked port command - pipe->port->flags.waiting_all_pipes_pause = 0; - *yield |= _internal_port_event_notify_from_isr(pipe->port); - } - } else { - //Pipe command is waiting for transfer to complete - *yield |= _internal_pipe_event_notify(pipe, true); - } - } return event; } @@ -1138,94 +1061,7 @@ esp_err_t hcd_uninstall(void) // ------------------------------------------------------ Port --------------------------------------------------------- -// ----------------------- Private ------------------------- - -static void _port_invalidate_all_pipes(port_t *port) -{ - //This function should only be called when the port is invalid - assert(!port->flags.conn_dev_ena); - pipe_t *pipe; - //Process all pipes that have queued URBs - TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) { - //Mark the pipe as invalid and set an invalid event - pipe->state = HCD_PIPE_STATE_INVALID; - pipe->last_event = HCD_PIPE_EVENT_INVALID; - //Flush all buffers that are still awaiting exec - _buffer_flush_all(pipe, false); - //Retire any remaining URBs in the pending tailq - _pipe_retire(pipe, false); - if (pipe->task_waiting_pipe_notif != NULL) { - //Unblock the thread/task waiting for a notification from the pipe as the pipe is no longer valid. - _internal_pipe_event_notify(pipe, false); - } - if (pipe->callback != NULL) { - HCD_EXIT_CRITICAL(); - (void) pipe->callback((hcd_pipe_handle_t)pipe, HCD_PIPE_EVENT_INVALID, pipe->callback_arg, false); - HCD_ENTER_CRITICAL(); - } - } - //Process all idle pipes - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - //Mark pipe as invalid and call its callback - pipe->state = HCD_PIPE_STATE_INVALID; - pipe->last_event = HCD_PIPE_EVENT_INVALID; - if (pipe->callback != NULL) { - HCD_EXIT_CRITICAL(); - (void) pipe->callback((hcd_pipe_handle_t)pipe, HCD_PIPE_EVENT_INVALID, pipe->callback_arg, false); - HCD_ENTER_CRITICAL(); - } - } -} - -static bool _port_pause_all_pipes(port_t *port) -{ - assert(port->state == HCD_PORT_STATE_ENABLED); - pipe_t *pipe; - int num_pipes_waiting_done = 0; - //Process all pipes that have queued URBs - TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) { - //Check if pipe is currently executing - if (pipe->multi_buffer_control.buffer_is_executing) { - //Pipe is executing a buffer. Indicate to the pipe we are waiting the buffer's transfer to complete - pipe->cs_flags.waiting_xfer_done = 1; - num_pipes_waiting_done++; - } else { - //No buffer is being executed so need to wait - pipe->cs_flags.paused = 1; - } - } - //Process all idle pipes. They don't have queue transfer so just mark them as paused - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - pipe->cs_flags.paused = 1; - } - if (num_pipes_waiting_done > 0) { - //Indicate we need to wait for one or more pipes to complete their transfers - port->flags.num_pipes_waiting_pause = num_pipes_waiting_done; - port->flags.waiting_all_pipes_pause = 1; - return false; - } - return true; -} - -static void _port_unpause_all_pipes(port_t *port) -{ - assert(port->state == HCD_PORT_STATE_ENABLED); - pipe_t *pipe; - //Process all idle pipes. They don't have queue transfer so just mark them as un-paused - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - pipe->cs_flags.paused = 0; - } - //Process all pipes that have queued URBs - TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) { - pipe->cs_flags.paused = 0; - if (_buffer_can_fill(pipe)) { - _buffer_fill(pipe); - } - if (_buffer_can_exec(pipe)) { - _buffer_exec(pipe); - } - } -} +// ----------------------- Helpers ------------------------- static bool _port_persist_all_pipes(port_t *port) { @@ -1264,108 +1100,23 @@ static void _port_recover_all_pipes(port_t *port) } } -static bool _port_bus_reset(port_t *port) +static bool _port_check_all_pipes_halted(port_t *port) { - assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED); - //Put and hold the bus in the reset state. If the port was previously enabled, a disabled event will occur after this - port->state = HCD_PORT_STATE_RESETTING; - usbh_hal_port_toggle_reset(port->hal, true); - HCD_EXIT_CRITICAL(); - vTaskDelay(pdMS_TO_TICKS(RESET_HOLD_MS)); - HCD_ENTER_CRITICAL(); - if (port->state != HCD_PORT_STATE_RESETTING) { - //The port state has unexpectedly changed - goto bailout; - } - //Return the bus to the idle state and hold it for the required reset recovery time. Port enabled event should occur - usbh_hal_port_toggle_reset(port->hal, false); - HCD_EXIT_CRITICAL(); - vTaskDelay(pdMS_TO_TICKS(RESET_RECOVERY_MS)); - HCD_ENTER_CRITICAL(); - if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_dev_ena) { - //The port state has unexpectedly changed - goto bailout; - } - return true; -bailout: - return false; -} - -static bool _port_bus_suspend(port_t *port) -{ - assert(port->state == HCD_PORT_STATE_ENABLED); - //Pause all pipes before suspending the bus - if (!_port_pause_all_pipes(port)) { - //Need to wait for some pipes to pause. Wait for notification from ISR - _internal_port_event_wait(port); - if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_dev_ena) { - //Port state unexpectedly changed - goto bailout; + bool all_halted = true; + pipe_t *pipe; + TAILQ_FOREACH(pipe, &port->pipes_active_tailq, tailq_entry) { + if (pipe->state != HCD_PIPE_STATE_HALTED) { + all_halted = false; + break; } } - //All pipes are guaranteed paused at this point. Proceed to suspend the port - usbh_hal_port_suspend(port->hal); - port->state = HCD_PORT_STATE_SUSPENDED; - return true; -bailout: - return false; -} - -static bool _port_bus_resume(port_t *port) -{ - assert(port->state == HCD_PORT_STATE_SUSPENDED); - //Put and hold the bus in the K state. - usbh_hal_port_toggle_resume(port->hal, true); - port->state = HCD_PORT_STATE_RESUMING; - HCD_EXIT_CRITICAL(); - vTaskDelay(pdMS_TO_TICKS(RESUME_HOLD_MS)); - HCD_ENTER_CRITICAL(); - //Return and hold the bus to the J state (as port of the LS EOP) - usbh_hal_port_toggle_resume(port->hal, false); - if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_dev_ena) { - //Port state unexpectedly changed - goto bailout; - } - HCD_EXIT_CRITICAL(); - vTaskDelay(pdMS_TO_TICKS(RESUME_RECOVERY_MS)); - HCD_ENTER_CRITICAL(); - if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_dev_ena) { - //Port state unexpectedly changed - goto bailout; - } - port->state = HCD_PORT_STATE_ENABLED; - _port_unpause_all_pipes(port); - return true; -bailout: - return false; -} - -static bool _port_disable(port_t *port) -{ - assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_SUSPENDED); - if (port->state == HCD_PORT_STATE_ENABLED) { - //There may be pipes that are still transferring, so pause them. - if (!_port_pause_all_pipes(port)) { - //Need to wait for some pipes to pause. Wait for notification from ISR - _internal_port_event_wait(port); - if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_dev_ena) { - //Port state unexpectedly changed - goto bailout; - } + TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { + if (pipe->state != HCD_PIPE_STATE_HALTED) { + all_halted = false; + break; } } - //All pipes are guaranteed paused at this point. Proceed to suspend the port. This should trigger an internal event - port->flags.disable_requested = 1; - usbh_hal_port_disable(port->hal); - _internal_port_event_wait(port); - if (port->state != HCD_PORT_STATE_DISABLED) { - //Port state unexpectedly changed - goto bailout; - } - _port_invalidate_all_pipes(port); - return true; -bailout: - return false; + return all_halted; } static bool _port_debounce(port_t *port) @@ -1389,6 +1140,166 @@ static bool _port_debounce(port_t *port) return is_connected; } +// ---------------------- Commands ------------------------- + +static esp_err_t _port_cmd_power_on(port_t *port) +{ + esp_err_t ret; + //Port can only be powered on if it's currently unpowered + if (port->state == HCD_PORT_STATE_NOT_POWERED) { + port->state = HCD_PORT_STATE_DISCONNECTED; + usbh_hal_port_init(port->hal); + usbh_hal_port_toggle_power(port->hal, true); + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + return ret; +} + +static esp_err_t _port_cmd_power_off(port_t *port) +{ + esp_err_t ret; + //Port can only be unpowered if already powered + if (port->state != HCD_PORT_STATE_NOT_POWERED) { + port->state = HCD_PORT_STATE_NOT_POWERED; + usbh_hal_port_deinit(port->hal); + usbh_hal_port_toggle_power(port->hal, false); + //If a device is currently connected, this should trigger a disconnect event + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + return ret; +} + +static esp_err_t _port_cmd_reset(port_t *port) +{ + esp_err_t ret; + //Port can only a reset when it is in the enabled or disabled states (in case of new connection) + if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_DISABLED) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + bool is_runtime_reset = (port->state == HCD_PORT_STATE_ENABLED) ? true : false; + if (is_runtime_reset && !_port_persist_all_pipes(port)) { + //If this is a run time reset, check all pipes that are still allocated can persist the reset + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //All pipes (if any_) are guaranteed to be persistent at this point. Proceed to resetting the bus + port->state = HCD_PORT_STATE_RESETTING; + //Put and hold the bus in the reset state. If the port was previously enabled, a disabled event will occur after this + usbh_hal_port_toggle_reset(port->hal, true); + HCD_EXIT_CRITICAL(); + vTaskDelay(pdMS_TO_TICKS(RESET_HOLD_MS)); + HCD_ENTER_CRITICAL(); + if (port->state != HCD_PORT_STATE_RESETTING) { + //The port state has unexpectedly changed + ret = ESP_ERR_INVALID_RESPONSE; + goto bailout; + } + //Return the bus to the idle state and hold it for the required reset recovery time. Port enabled event should occur + usbh_hal_port_toggle_reset(port->hal, false); + HCD_EXIT_CRITICAL(); + vTaskDelay(pdMS_TO_TICKS(RESET_RECOVERY_MS)); + HCD_ENTER_CRITICAL(); + if (port->state != HCD_PORT_STATE_ENABLED || !port->flags.conn_dev_ena) { + //The port state has unexpectedly changed + ret = ESP_ERR_INVALID_RESPONSE; + goto bailout; + } + //Set FIFO sizes based on the selected biasing + usbh_hal_set_fifo_size(port->hal, port->fifo_config); + //We start periodic scheduling only after a RESET command since SOFs only start after a reset + usbh_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN); + usbh_hal_port_periodic_enable(port->hal); + ret = ESP_OK; +bailout: + if (is_runtime_reset) { + _port_recover_all_pipes(port); + } +exit: + return ret; +} + +static esp_err_t _port_cmd_bus_suspend(port_t *port) +{ + esp_err_t ret; + //Port must have been previously enabled, and all pipes must already be halted + if (port->state == HCD_PORT_STATE_ENABLED && !_port_check_all_pipes_halted(port)) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //All pipes are guaranteed halted at this point. Proceed to suspend the port + usbh_hal_port_suspend(port->hal); + port->state = HCD_PORT_STATE_SUSPENDED; + ret = ESP_OK; +exit: + return ret; +} + +static esp_err_t _port_cmd_bus_resume(port_t *port) +{ + esp_err_t ret; + //Port can only be resumed if it was previously suspended + if (port->state != HCD_PORT_STATE_SUSPENDED) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //Put and hold the bus in the K state. + usbh_hal_port_toggle_resume(port->hal, true); + port->state = HCD_PORT_STATE_RESUMING; + HCD_EXIT_CRITICAL(); + vTaskDelay(pdMS_TO_TICKS(RESUME_HOLD_MS)); + HCD_ENTER_CRITICAL(); + //Return and hold the bus to the J state (as port of the LS EOP) + usbh_hal_port_toggle_resume(port->hal, false); + if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_dev_ena) { + //Port state unexpectedly changed + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + HCD_EXIT_CRITICAL(); + vTaskDelay(pdMS_TO_TICKS(RESUME_RECOVERY_MS)); + HCD_ENTER_CRITICAL(); + if (port->state != HCD_PORT_STATE_RESUMING || !port->flags.conn_dev_ena) { + //Port state unexpectedly changed + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + port->state = HCD_PORT_STATE_ENABLED; + ret = ESP_OK; +exit: + return ret; +} + +static esp_err_t _port_cmd_disable(port_t *port) +{ + esp_err_t ret; + if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_SUSPENDED) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //All pipes must be halted before disabling the port + if (!_port_check_all_pipes_halted(port)){ + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //All pipes are guaranteed to be halted or freed at this point. Proceed to disable the port + port->flags.disable_requested = 1; + usbh_hal_port_disable(port->hal); + _internal_port_event_wait(port); + if (port->state != HCD_PORT_STATE_DISABLED) { + //Port state unexpectedly changed + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + ret = ESP_OK; +exit: + return ret; +} + // ----------------------- Public -------------------------- esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl) @@ -1396,6 +1307,29 @@ esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, h HCD_CHECK(port_number > 0 && port_config != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG); HCD_CHECK(port_number <= NUM_PORTS, ESP_ERR_NOT_FOUND); + //Get a pointer to the correct FIFO bias constant values + const usbh_hal_fifo_config_t *fifo_config; + const fifo_mps_limits_t *mps_limits; + switch (port_config->fifo_bias) { + case HCD_PORT_FIFO_BIAS_BALANCED: + fifo_config = &fifo_config_default; + mps_limits = &mps_limits_default; + break; + case HCD_PORT_FIFO_BIAS_RX: + fifo_config = &fifo_config_bias_rx; + mps_limits = &mps_limits_bias_rx; + break; + case HCD_PORT_FIFO_BIAS_PTX: + fifo_config = &fifo_config_bias_ptx; + mps_limits = &mps_limits_bias_ptx; + break; + default: + fifo_config = NULL; + mps_limits = NULL; + abort(); + break; + } + HCD_ENTER_CRITICAL(); HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && !s_hcd_obj->port_obj->initialized, ESP_ERR_INVALID_STATE); //Port object memory and resources (such as the mutex) already be allocated. Just need to initialize necessary fields only @@ -1404,6 +1338,8 @@ esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, h TAILQ_INIT(&port_obj->pipes_active_tailq); port_obj->state = HCD_PORT_STATE_NOT_POWERED; port_obj->last_event = HCD_PORT_EVENT_NONE; + port_obj->fifo_config = fifo_config; + port_obj->fifo_mps_limits = mps_limits; port_obj->callback = port_config->callback; port_obj->callback_arg = port_config->callback_arg; port_obj->context = port_config->context; @@ -1427,7 +1363,7 @@ esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl) HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && port->initialized && port->num_pipes_idle == 0 && port->num_pipes_queued == 0 && (port->state == HCD_PORT_STATE_NOT_POWERED || port->state == HCD_PORT_STATE_RECOVERY) - && port->flags.val == 0 && port->task_waiting_port_notif == NULL, + && port->task_waiting_port_notif == NULL, ESP_ERR_INVALID_STATE); port->initialized = false; esp_intr_disable(s_hcd_obj->isr_hdl); @@ -1447,74 +1383,27 @@ esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command) port->flags.cmd_processing = 1; switch (command) { case HCD_PORT_CMD_POWER_ON: { - //Port can only be powered on if currently unpowered - if (port->state == HCD_PORT_STATE_NOT_POWERED) { - port->state = HCD_PORT_STATE_DISCONNECTED; - usbh_hal_port_init(port->hal); - usbh_hal_port_toggle_power(port->hal, true); - ret = ESP_OK; - } + ret = _port_cmd_power_on(port); break; } case HCD_PORT_CMD_POWER_OFF: { - //Port can only be unpowered if already powered - if (port->state != HCD_PORT_STATE_NOT_POWERED) { - port->state = HCD_PORT_STATE_NOT_POWERED; - usbh_hal_port_deinit(port->hal); - usbh_hal_port_toggle_power(port->hal, false); - //If a device is currently connected, this should trigger a disconnect event - ret = ESP_OK; - } + ret = _port_cmd_power_off(port); break; } case HCD_PORT_CMD_RESET: { - //Port can only a reset when it is in the enabled or disabled states (in case of new connection) - if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED) { - bool is_runtime_reset = (port->state == HCD_PORT_STATE_ENABLED) ? true : false; - if (is_runtime_reset) { - //Check all pipes that are still allocated are persistent before we execute the reset - if (!_port_persist_all_pipes(port)) { - ret = ESP_ERR_INVALID_STATE; - break; - } - } - if (_port_bus_reset(port)) { - //Set FIFO sizes to default - usbh_hal_set_fifo_size(port->hal, &fifo_config_default); - port->fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED; - //We start periodic scheduling only after a RESET command since SOFs only start after a reset - usbh_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN); - usbh_hal_port_periodic_enable(port->hal); - ret = ESP_OK; - } else { - ret = ESP_ERR_INVALID_RESPONSE; - } - if (is_runtime_reset) { - _port_recover_all_pipes(port); - } - - } + ret = _port_cmd_reset(port); break; } case HCD_PORT_CMD_SUSPEND: { - //Port can only be suspended if already in the enabled state - if (port->state == HCD_PORT_STATE_ENABLED) { - ret = (_port_bus_suspend(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE; - } + ret = _port_cmd_bus_suspend(port); break; } case HCD_PORT_CMD_RESUME: { - //Port can only be resumed if already suspended - if (port->state == HCD_PORT_STATE_SUSPENDED) { - ret = (_port_bus_resume(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE; - } + ret = _port_cmd_bus_resume(port); break; } case HCD_PORT_CMD_DISABLE: { - //Can only disable the port when already enabled or suspended - if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_SUSPENDED) { - ret = (_port_disable(port)) ? ESP_OK : ESP_ERR_INVALID_RESPONSE; - } + ret = _port_cmd_disable(port); break; } } @@ -1570,22 +1459,8 @@ hcd_port_event_t hcd_port_handle_event(hcd_port_handle_t port_hdl) break; } case HCD_PORT_EVENT_DISCONNECTION: - if (_port_debounce(port)) { - //A device is still connected, so it was just a debounce - port->state = HCD_PORT_STATE_DISABLED; - ret = HCD_PORT_EVENT_NONE; - } else { - //No device connected after debounce delay. This is an actual disconnection - if (port->state != HCD_PORT_STATE_NOT_POWERED) { //Don't update state if disconnect was due to power-off - port->state = HCD_PORT_STATE_DISCONNECTED; - } - ret = HCD_PORT_EVENT_DISCONNECTION; - } - break; case HCD_PORT_EVENT_ERROR: - case HCD_PORT_EVENT_OVERCURRENT: - case HCD_PORT_EVENT_SUDDEN_DISCONN: { - _port_invalidate_all_pipes(port); + case HCD_PORT_EVENT_OVERCURRENT: { break; } default: { @@ -1637,28 +1512,37 @@ void *hcd_port_get_context(hcd_port_handle_t port_hdl) esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_t bias) { esp_err_t ret; + //Get a pointer to the correct FIFO bias constant values + const usbh_hal_fifo_config_t *fifo_config; + const fifo_mps_limits_t *mps_limits; + switch (bias) { + case HCD_PORT_FIFO_BIAS_BALANCED: + fifo_config = &fifo_config_default; + mps_limits = &mps_limits_default; + break; + case HCD_PORT_FIFO_BIAS_RX: + fifo_config = &fifo_config_bias_rx; + mps_limits = &mps_limits_bias_rx; + break; + case HCD_PORT_FIFO_BIAS_PTX: + fifo_config = &fifo_config_bias_ptx; + mps_limits = &mps_limits_bias_ptx; + break; + default: + fifo_config = NULL; + mps_limits = NULL; + abort(); + break; + } + //Configure the new FIFO sizes and store the pointers port_t *port = (port_t *)port_hdl; xSemaphoreTake(port->port_mux, portMAX_DELAY); HCD_ENTER_CRITICAL(); //Check that port is in the correct state to update FIFO sizes if (port->initialized && !port->flags.event_pending && port->num_pipes_idle == 0 && port->num_pipes_queued == 0) { - const usbh_hal_fifo_config_t *fifo_config; - switch (bias) { - case HCD_PORT_FIFO_BIAS_BALANCED: - fifo_config = &fifo_config_default; - break; - case HCD_PORT_FIFO_BIAS_RX: - fifo_config = &fifo_config_bias_rx; - break; - case HCD_PORT_FIFO_BIAS_PTX: - fifo_config = &fifo_config_bias_ptx; - break; - default: - fifo_config = NULL; - abort(); - } usbh_hal_set_fifo_size(port->hal, fifo_config); - port->fifo_bias = bias; + port->fifo_config = fifo_config; + port->fifo_mps_limits = mps_limits; ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; @@ -1672,45 +1556,6 @@ esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_ // ----------------------- Private ------------------------- -static bool _pipe_wait_done(pipe_t *pipe) -{ - //Check if the pipe has a currently executing buffer - if (pipe->multi_buffer_control.buffer_is_executing) { - //Wait for pipe to complete its transfer - pipe->cs_flags.waiting_xfer_done = 1; - _internal_pipe_event_wait(pipe); - if (pipe->state == HCD_PIPE_STATE_INVALID) { - //The pipe become invalid whilst waiting for its internal event - pipe->cs_flags.waiting_xfer_done = 0; //Need to manually reset this bit in this case - return false; - } - bool chan_halted = usbh_hal_chan_request_halt(pipe->chan_obj); - assert(chan_halted); - (void) chan_halted; - } - return true; -} - -static void _pipe_retire(pipe_t *pipe, bool self_initiated) -{ - //Cannot have a currently executing buffer - assert(!pipe->multi_buffer_control.buffer_is_executing); - if (pipe->num_urb_pending > 0) { - //Process all remaining pending URBs - urb_t *urb; - TAILQ_FOREACH(urb, &pipe->pending_urb_tailq, tailq_entry) { - //Update the URB's current state - urb->hcd_var = URB_HCD_STATE_DONE; - //If we are initiating the retire, mark the URB as canceled - urb->transfer.status = (self_initiated) ? USB_TRANSFER_STATUS_CANCELED : USB_TRANSFER_STATUS_NO_DEVICE; - } - //Concatenated pending tailq to the done tailq - TAILQ_CONCAT(&pipe->done_urb_tailq, &pipe->pending_urb_tailq, tailq_entry); - pipe->num_urb_done += pipe->num_urb_pending; - pipe->num_urb_pending = 0; - } -} - static inline hcd_pipe_event_t pipe_decode_error_event(usbh_hal_chan_error_t chan_error) { hcd_pipe_event_t event = HCD_PIPE_EVENT_NONE; @@ -1768,7 +1613,7 @@ static void buffer_block_free(dma_buffer_block_t *buffer) free(buffer); } -static bool pipe_alloc_check_args(const hcd_pipe_config_t *pipe_config, usb_speed_t port_speed, hcd_port_fifo_bias_t fifo_bias, usb_transfer_type_t type, bool is_default_pipe) +static bool pipe_alloc_check_args(const hcd_pipe_config_t *pipe_config, usb_speed_t port_speed, const fifo_mps_limits_t *mps_limits, usb_transfer_type_t type, bool is_default_pipe) { //Check if pipe can be supported if (port_speed == USB_SPEED_LOW && pipe_config->dev_speed == USB_SPEED_FULL) { @@ -1790,25 +1635,12 @@ static bool pipe_alloc_check_args(const hcd_pipe_config_t *pipe_config, usb_spee //Interval not supported for isochronous pipe (where 0 < 2^(bInterval - 1) <= 32) return false; } - if (is_default_pipe) { return true; } - //Check if MPS is within FIFO limits - const fifo_mps_limits_t *mps_limits; - switch (fifo_bias) { - case HCD_PORT_FIFO_BIAS_BALANCED: - mps_limits = &mps_limits_default; - break; - case HCD_PORT_FIFO_BIAS_RX: - mps_limits = &mps_limits_bias_rx; - break; - default: //HCD_PORT_FIFO_BIAS_PTX - mps_limits = &mps_limits_bias_ptx; - break; - } + int limit; - if (USB_DESC_EP_GET_EP_DIR(pipe_config->ep_desc)) { //IN + if (USB_EP_DESC_GET_EP_DIR(pipe_config->ep_desc)) { //IN limit = mps_limits->in_mps; } else { //OUT if (type == USB_TRANSFER_TYPE_CTRL || type == USB_TRANSFER_TYPE_BULK) { @@ -1880,6 +1712,102 @@ static void pipe_set_ep_char(const hcd_pipe_config_t *pipe_config, usb_transfer_ } } +// ---------------------- Commands ------------------------- + +static esp_err_t _pipe_cmd_halt(pipe_t *pipe) +{ + esp_err_t ret; + //Pipe must be in the active state in order to be halted + if (pipe->state != HCD_PIPE_STATE_ACTIVE) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //Request that the channel halts + if (!usbh_hal_chan_request_halt(pipe->chan_obj)) { + //We need to wait for channel to be halted. State will be updated in the ISR + pipe->cs_flags.waiting_halt = 1; + _internal_pipe_event_wait(pipe); + } else { + pipe->state = HCD_PIPE_STATE_HALTED; + } + ret = ESP_OK; +exit: + return ret; +} + +static esp_err_t _pipe_cmd_flush(pipe_t *pipe) +{ + esp_err_t ret; + //The pipe must be halted in order to be flushed + if (pipe->state != HCD_PIPE_STATE_HALTED) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //Cannot have a currently executing buffer + assert(!pipe->multi_buffer_control.buffer_is_executing); + bool call_pipe_cb; + //Flush any filled buffers + call_pipe_cb = _buffer_flush_all(pipe, true); + //Move all URBs from the pending tailq to the done tailq + if (pipe->num_urb_pending > 0) { + //Process all remaining pending URBs + urb_t *urb; + TAILQ_FOREACH(urb, &pipe->pending_urb_tailq, tailq_entry) { + //Update the URB's current state + urb->hcd_var = URB_HCD_STATE_DONE; + //We are canceling the URB. Update its actual_num_bytes and status + urb->transfer.actual_num_bytes = 0; + urb->transfer.status = USB_TRANSFER_STATUS_CANCELED; + if (pipe->ep_char.type == USB_PRIV_XFER_TYPE_ISOCHRONOUS) { + //Update the URB's isoc packet descriptors as well + for (int pkt_idx = 0; pkt_idx < urb->transfer.num_isoc_packets; pkt_idx++) { + urb->transfer.isoc_packet_desc[pkt_idx].actual_num_bytes = 0; + urb->transfer.isoc_packet_desc[pkt_idx].status = USB_TRANSFER_STATUS_CANCELED; + } + } + } + //Concatenated pending tailq to the done tailq + TAILQ_CONCAT(&pipe->done_urb_tailq, &pipe->pending_urb_tailq, tailq_entry); + pipe->num_urb_done += pipe->num_urb_pending; + pipe->num_urb_pending = 0; + call_pipe_cb = true; + } + if (call_pipe_cb) { + //One or more URBs can be dequeued as a result of the flush. We need to call the callback + HCD_EXIT_CRITICAL(); + pipe->callback((hcd_pipe_handle_t)pipe, HCD_PIPE_EVENT_URB_DONE, pipe->callback_arg, false); + HCD_ENTER_CRITICAL(); + } + ret = ESP_OK; +exit: + return ret; +} + +static esp_err_t _pipe_cmd_clear(pipe_t *pipe) +{ + esp_err_t ret; + //Pipe must be in the halted state in order to be made active, and there must be an enabled device on the port + if (pipe->state != HCD_PIPE_STATE_HALTED || !pipe->port->flags.conn_dev_ena) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //Update the pipe's state + pipe->state = HCD_PIPE_STATE_ACTIVE; + if (pipe->num_urb_pending > 0) { + //Fill as many buffers as possible + while (_buffer_can_fill(pipe)) { + _buffer_fill(pipe); + } + } + //Execute any filled buffers + if (_buffer_can_exec(pipe)) { + _buffer_exec(pipe); + } + ret = ESP_OK; +exit: + return ret; +} + // ----------------------- Public -------------------------- 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) @@ -1890,7 +1818,7 @@ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pi //Can only allocate a pipe if the target port is initialized and connected to an enabled device HCD_CHECK_FROM_CRIT(port->initialized && port->flags.conn_dev_ena, ESP_ERR_INVALID_STATE); usb_speed_t port_speed = port->speed; - hcd_port_fifo_bias_t port_fifo_bias = port->fifo_bias; + const fifo_mps_limits_t *mps_limits = port->fifo_mps_limits; int pipe_idx = port->num_pipes_idle + port->num_pipes_queued; HCD_EXIT_CRITICAL(); @@ -1900,11 +1828,11 @@ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pi type = USB_TRANSFER_TYPE_CTRL; is_default = true; } else { - type = USB_DESC_EP_GET_XFERTYPE(pipe_config->ep_desc); + type = USB_EP_DESC_GET_XFERTYPE(pipe_config->ep_desc); is_default = false; } //Check if pipe configuration can be supported - if (!pipe_alloc_check_args(pipe_config, port_speed, port_fifo_bias, type, is_default)) { + if (!pipe_alloc_check_args(pipe_config, port_speed, mps_limits, type, is_default)) { return ESP_ERR_NOT_SUPPORTED; } @@ -1979,10 +1907,7 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl) HCD_ENTER_CRITICAL(); //Check that all URBs have been removed and pipe has no pending events HCD_CHECK_FROM_CRIT(!pipe->multi_buffer_control.buffer_is_executing - && pipe->multi_buffer_control.buffer_num_to_parse == 0 - && pipe->multi_buffer_control.buffer_num_to_exec == 0 - && pipe->num_urb_pending == 0 - && pipe->num_urb_done == 0 + && !pipe->cs_flags.has_urb && !pipe->cs_flags.reset_lock, ESP_ERR_INVALID_STATE); //Remove pipe from the list of idle pipes (it must be in the idle list because it should have no queued URBs) @@ -2005,11 +1930,9 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps) pipe_t *pipe = (pipe_t *)pipe_hdl; HCD_ENTER_CRITICAL(); //Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID - && !pipe->cs_flags.pipe_cmd_processing - && pipe->num_urb_pending == 0 - && pipe->num_urb_done == 0 - && !pipe->cs_flags.reset_lock, + HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && + !pipe->cs_flags.has_urb && + !pipe->cs_flags.reset_lock, ESP_ERR_INVALID_STATE); pipe->ep_char.mps = mps; //Update the underlying channel's registers @@ -2023,11 +1946,9 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) pipe_t *pipe = (pipe_t *)pipe_hdl; HCD_ENTER_CRITICAL(); //Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID - && !pipe->cs_flags.pipe_cmd_processing - && pipe->num_urb_pending == 0 - && pipe->num_urb_done == 0 - && !pipe->cs_flags.reset_lock, + HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && + !pipe->cs_flags.has_urb && + !pipe->cs_flags.reset_lock, ESP_ERR_INVALID_STATE); pipe->ep_char.dev_addr = dev_addr; //Update the underlying channel's registers @@ -2036,16 +1957,29 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) return ESP_OK; } -esp_err_t hcd_pipe_persist_reset(hcd_pipe_handle_t pipe_hdl) +esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg) { pipe_t *pipe = (pipe_t *)pipe_hdl; HCD_ENTER_CRITICAL(); //Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID - && !pipe->cs_flags.pipe_cmd_processing - && pipe->num_urb_pending == 0 - && pipe->num_urb_done == 0 - && !pipe->cs_flags.reset_lock, + HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && + !pipe->cs_flags.has_urb && + !pipe->cs_flags.reset_lock, + ESP_ERR_INVALID_STATE); + pipe->callback = callback; + pipe->callback_arg = user_arg; + HCD_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl) +{ + pipe_t *pipe = (pipe_t *)pipe_hdl; + HCD_ENTER_CRITICAL(); + //Check if pipe is in the correct state to be updated + HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && + !pipe->cs_flags.has_urb && + !pipe->cs_flags.reset_lock, ESP_ERR_INVALID_STATE); pipe->cs_flags.persist = 1; HCD_EXIT_CRITICAL(); @@ -2067,14 +2001,7 @@ hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl) hcd_pipe_state_t ret; pipe_t *pipe = (pipe_t *)pipe_hdl; HCD_ENTER_CRITICAL(); - //If there is no enabled device, all existing pipes are invalid. - if (pipe->port->state != HCD_PORT_STATE_ENABLED - && pipe->port->state != HCD_PORT_STATE_SUSPENDED - && pipe->port->state != HCD_PORT_STATE_RESUMING) { - ret = HCD_PIPE_STATE_INVALID; - } else { - ret = pipe->state; - } + ret = pipe->state; HCD_EXIT_CRITICAL(); return ret; } @@ -2084,60 +2011,31 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command) pipe_t *pipe = (pipe_t *)pipe_hdl; esp_err_t ret = ESP_OK; + xSemaphoreTake(pipe->port->port_mux, portMAX_DELAY); HCD_ENTER_CRITICAL(); //Cannot execute pipe commands the pipe is already executing a command, or if the pipe or its port are no longer valid - if (pipe->cs_flags.pipe_cmd_processing || pipe->cs_flags.reset_lock || !pipe->port->flags.conn_dev_ena || pipe->state == HCD_PIPE_STATE_INVALID) { + if (pipe->cs_flags.reset_lock) { ret = ESP_ERR_INVALID_STATE; } else { pipe->cs_flags.pipe_cmd_processing = 1; switch (command) { - case HCD_PIPE_CMD_ABORT: { - //Retire all scheduled URBs. Pipe's state remains unchanged - if (!_pipe_wait_done(pipe)) { //Stop any on going transfers - ret = ESP_ERR_INVALID_RESPONSE; - } - _buffer_flush_all(pipe, true); //Some buffers might still be filled. Flush them - _pipe_retire(pipe, true); //Retire any pending transfers - break; - } - case HCD_PIPE_CMD_RESET: { - //Retire all scheduled URBs. Pipe's state moves to active - if (!_pipe_wait_done(pipe)) { //Stop any on going transfers - ret = ESP_ERR_INVALID_RESPONSE; - break; - } - _buffer_flush_all(pipe, true); //Some buffers might still be filled. Flush them - _pipe_retire(pipe, true); //Retire any pending transfers - pipe->state = HCD_PIPE_STATE_ACTIVE; - break; - } - case HCD_PIPE_CMD_CLEAR: { //Can only do this if port is still active - //Pipe's state moves from halted to active - if (pipe->state == HCD_PIPE_STATE_HALTED) { - pipe->state = HCD_PIPE_STATE_ACTIVE; - //Start the next pending transfer if it exists - if (_buffer_can_fill(pipe)) { - _buffer_fill(pipe); - } - if (_buffer_can_exec(pipe)) { - _buffer_exec(pipe); - } - } - break; - } case HCD_PIPE_CMD_HALT: { - //Pipe's state moves to halted - if (!_pipe_wait_done(pipe)) { //Stop any on going transfers - ret = ESP_ERR_INVALID_RESPONSE; - break; - } - pipe->state = HCD_PIPE_STATE_HALTED; + ret = _pipe_cmd_halt(pipe); + break; + } + case HCD_PIPE_CMD_FLUSH: { + ret = _pipe_cmd_flush(pipe); + break; + } + case HCD_PIPE_CMD_CLEAR: { + ret = _pipe_cmd_clear(pipe); break; } } pipe->cs_flags.pipe_cmd_processing = 0; } HCD_EXIT_CRITICAL(); + xSemaphoreGive(pipe->port->port_mux); return ret; } @@ -2157,11 +2055,11 @@ hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl) static inline void _buffer_fill_ctrl(dma_buffer_block_t *buffer, usb_transfer_t *transfer) { //Get information about the control transfer by analyzing the setup packet (the first 8 bytes of the URB's data) - usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)transfer->data_buffer; - bool data_stg_in = (ctrl_req->bRequestType & USB_B_REQUEST_TYPE_DIR_IN); - bool data_stg_skip = (transfer->num_bytes == 0); + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; + bool data_stg_in = (setup_pkt->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN); + bool data_stg_skip = (setup_pkt->wLength == 0); //Fill setup stage - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, sizeof(usb_ctrl_req_t), + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, sizeof(usb_setup_packet_t), USBH_HAL_XFER_DESC_FLAG_SETUP | USBH_HAL_XFER_DESC_FLAG_HOC); //Fill data stage if (data_stg_skip) { @@ -2169,7 +2067,7 @@ static inline void _buffer_fill_ctrl(dma_buffer_block_t *buffer, usb_transfer_t usbh_hal_xfer_desc_clear(buffer->xfer_desc_list, 1); } else { //Fill data stage - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, transfer->data_buffer + sizeof(usb_ctrl_req_t), transfer->num_bytes, + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, transfer->data_buffer + sizeof(usb_setup_packet_t), setup_pkt->wLength, ((data_stg_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0) | USBH_HAL_XFER_DESC_FLAG_HOC); } //Fill status stage (i.e., a zero length packet). If data stage is skipped, the status stage is always IN. @@ -2370,15 +2268,14 @@ static void _buffer_exec(pipe_t *pipe) usbh_hal_chan_activate(pipe->chan_obj, buffer_to_exec->xfer_desc_list, desc_list_len, start_idx); } -static bool _buffer_check_done(pipe_t *pipe) +static void _buffer_exec_cont(pipe_t *pipe) { - if (pipe->ep_char.type != USB_PRIV_XFER_TYPE_CTRL) { - return true; - } - //Only control transfers need to be continued + //This should only ever be called on control transfers + assert(pipe->ep_char.type == USB_PRIV_XFER_TYPE_CTRL); dma_buffer_block_t *buffer_inflight = pipe->buffers[pipe->multi_buffer_control.rd_idx]; bool next_dir_is_in; int next_pid; + assert(buffer_inflight->flags.ctrl.cur_stg != 2); if (buffer_inflight->flags.ctrl.cur_stg == 0) { //Just finished control stage if (buffer_inflight->flags.ctrl.data_stg_skip) { //Skipping data stage. Go straight to status stage @@ -2391,18 +2288,15 @@ static bool _buffer_check_done(pipe_t *pipe) next_pid = 1; //Data stage always starts with a PID of DATA1 buffer_inflight->flags.ctrl.cur_stg = 1; } - } else if (buffer_inflight->flags.ctrl.cur_stg == 1) { //Just finished data stage. Go to status stage + } else { //cur_stg == 1. //Just finished data stage. Go to status stage next_dir_is_in = !buffer_inflight->flags.ctrl.data_stg_in; //Status stage is always the opposite direction of data stage next_pid = 1; //Status stage always has a PID of DATA1 buffer_inflight->flags.ctrl.cur_stg = 2; - } else { //Just finished status stage. Transfer is complete - return true; } //Continue the control transfer usbh_hal_chan_set_dir(pipe->chan_obj, next_dir_is_in); usbh_hal_chan_set_pid(pipe->chan_obj, next_pid); usbh_hal_chan_activate(pipe->chan_obj, buffer_inflight->xfer_desc_list, XFER_LIST_LEN_CTRL, buffer_inflight->flags.ctrl.cur_stg); - return false; } static inline void _buffer_parse_ctrl(dma_buffer_block_t *buffer) @@ -2410,15 +2304,15 @@ static inline void _buffer_parse_ctrl(dma_buffer_block_t *buffer) usb_transfer_t *transfer = &buffer->urb->transfer; //Update URB's actual number of bytes if (buffer->flags.ctrl.data_stg_skip) { - //There was no data stage. Just set the actual length to zero - transfer->actual_num_bytes = 0; + //There was no data stage. Just set the actual length to the size of the setup packet + transfer->actual_num_bytes = sizeof(usb_setup_packet_t); } else { //Parse the data stage for the remaining length int rem_len; int desc_status; usbh_hal_xfer_desc_parse(buffer->xfer_desc_list, 1, &rem_len, &desc_status); assert(desc_status == USBH_HAL_XFER_DESC_STS_SUCCESS); - assert(rem_len <= transfer->num_bytes); + assert(rem_len <= (transfer->num_bytes - sizeof(usb_setup_packet_t))); transfer->actual_num_bytes = transfer->num_bytes - rem_len; } //Update URB status @@ -2497,6 +2391,7 @@ static inline void _buffer_parse_isoc(dma_buffer_block_t *buffer, bool is_in) { usb_transfer_t *transfer = &buffer->urb->transfer; int desc_idx = buffer->flags.isoc.start_idx; //Descriptor index tracks which descriptor in the QTD list + int total_actual_num_bytes = 0; for (int pkt_idx = 0; pkt_idx < transfer->num_isoc_packets; pkt_idx++) { //Clear the filled descriptor int rem_len; @@ -2508,6 +2403,7 @@ static inline void _buffer_parse_isoc(dma_buffer_block_t *buffer, bool is_in) assert(rem_len <= transfer->isoc_packet_desc[pkt_idx].num_bytes); //Check for DMA errata //Update ISO packet actual length and status transfer->isoc_packet_desc[pkt_idx].actual_num_bytes = transfer->isoc_packet_desc[pkt_idx].num_bytes - rem_len; + total_actual_num_bytes += transfer->isoc_packet_desc[pkt_idx].actual_num_bytes; transfer->isoc_packet_desc[pkt_idx].status = (desc_status == USBH_HAL_XFER_DESC_STS_NOT_EXECUTED) ? USB_TRANSFER_STATUS_SKIPPED : USB_TRANSFER_STATUS_COMPLETED; //A descriptor is also allocated for unscheduled frames. We need to skip over them desc_idx += buffer->flags.isoc.interval; @@ -2515,43 +2411,39 @@ static inline void _buffer_parse_isoc(dma_buffer_block_t *buffer, bool is_in) desc_idx -= XFER_LIST_LEN_INTR; } } + //Write back the actual_num_bytes and statue of entire transfer + assert(total_actual_num_bytes <= transfer->num_bytes); + transfer->actual_num_bytes = total_actual_num_bytes; + transfer->status = USB_TRANSFER_STATUS_COMPLETED; } static inline void _buffer_parse_error(dma_buffer_block_t *buffer) { - //The URB had an error, so we consider that NO bytes were transferred + //The URB had an error, so we consider that NO bytes were transferred. Set actual_num_bytes to zero usb_transfer_t *transfer = &buffer->urb->transfer; transfer->actual_num_bytes = 0; for (int i = 0; i < transfer->num_isoc_packets; i++) { transfer->isoc_packet_desc[i].actual_num_bytes = 0; } - //Update status of URB - if (buffer->status_flags.cancelled) { - transfer->status = USB_TRANSFER_STATUS_CANCELED; - } else if (buffer->status_flags.pipe_state == HCD_PIPE_STATE_INVALID) { - transfer->status = USB_TRANSFER_STATUS_NO_DEVICE; - } else { - switch (buffer->status_flags.pipe_event) { - case HCD_PIPE_EVENT_ERROR_XFER: //Excessive transaction error - transfer->status = USB_TRANSFER_STATUS_ERROR; - break; - case HCD_PIPE_EVENT_ERROR_OVERFLOW: - transfer->status = USB_TRANSFER_STATUS_OVERFLOW; - break; - case HCD_PIPE_EVENT_ERROR_STALL: - transfer->status = USB_TRANSFER_STATUS_STALL; - break; - case HCD_PIPE_EVENT_URB_DONE: //Special case where we are cancelling an URB due to pipe_retire - transfer->status = USB_TRANSFER_STATUS_CANCELED; - break; - default: - //HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL should never occur - abort(); - break; - } + //Update status of URB. Status will depend on the pipe_event + switch (buffer->status_flags.pipe_event) { + case HCD_PIPE_EVENT_NONE: + transfer->status = USB_TRANSFER_STATUS_CANCELED; + break; + case HCD_PIPE_EVENT_ERROR_XFER: + transfer->status = USB_TRANSFER_STATUS_ERROR; + break; + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + transfer->status = USB_TRANSFER_STATUS_OVERFLOW; + break; + case HCD_PIPE_EVENT_ERROR_STALL: + transfer->status = USB_TRANSFER_STATUS_STALL; + break; + default: + //HCD_PIPE_EVENT_URB_DONE and HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL should not occur here + abort(); + break; } - //Clear error flags - buffer->status_flags.val = 0; } static void _buffer_parse(pipe_t *pipe) @@ -2563,9 +2455,8 @@ static void _buffer_parse(pipe_t *pipe) int mps = pipe->ep_char.mps; //Parsing the buffer will update the buffer's corresponding URB - if (buffer_to_parse->status_flags.error_occurred) { - _buffer_parse_error(buffer_to_parse); - } else { + if (buffer_to_parse->status_flags.pipe_event == HCD_PIPE_EVENT_URB_DONE) { + //URB was successful switch (pipe->ep_char.type) { case USB_PRIV_XFER_TYPE_CTRL: { _buffer_parse_ctrl(buffer_to_parse); @@ -2588,6 +2479,9 @@ static void _buffer_parse(pipe_t *pipe) break; } } + } else { + //URB failed + _buffer_parse_error(buffer_to_parse); } urb_t *urb = buffer_to_parse->urb; urb->hcd_var = URB_HCD_STATE_DONE; @@ -2602,18 +2496,19 @@ static void _buffer_parse(pipe_t *pipe) pipe->multi_buffer_control.buffer_num_to_fill++; } -static void _buffer_flush_all(pipe_t *pipe, bool cancelled) +static bool _buffer_flush_all(pipe_t *pipe, bool cancelled) { - int cur_num_to_mark_done = pipe->multi_buffer_control.buffer_num_to_exec; + int cur_num_to_mark_done = pipe->multi_buffer_control.buffer_num_to_exec; for (int i = 0; i < cur_num_to_mark_done; i++) { //Mark any filled buffers as done - _buffer_done_error(pipe, 0, pipe->state, pipe->last_event, cancelled); + _buffer_done(pipe, 0, HCD_PIPE_EVENT_NONE); } int cur_num_to_parse = pipe->multi_buffer_control.buffer_num_to_parse; for (int i = 0; i < cur_num_to_parse; i++) { _buffer_parse(pipe); } //At this point, there should be no more filled buffers. Only URBs in the pending or done tailq + return (cur_num_to_parse > 0); } // ---------------------------------------------- HCD Transfer Descriptors --------------------------------------------- @@ -2646,13 +2541,13 @@ esp_err_t hcd_urb_enqueue(hcd_pipe_handle_t pipe_hdl, urb_t *urb) if (_buffer_can_exec(pipe)) { _buffer_exec(pipe); } - if (!pipe->cs_flags.is_active) { + if (!pipe->cs_flags.has_urb) { //This is the first URB to be enqueued into the pipe. Move the pipe to the list of active pipes TAILQ_REMOVE(&pipe->port->pipes_idle_tailq, pipe, tailq_entry); TAILQ_INSERT_TAIL(&pipe->port->pipes_active_tailq, pipe, tailq_entry); pipe->port->num_pipes_idle--; pipe->port->num_pipes_queued++; - pipe->cs_flags.is_active = 1; + pipe->cs_flags.has_urb = 1; } HCD_EXIT_CRITICAL(); return ESP_OK; @@ -2672,7 +2567,7 @@ urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl) assert(urb->hcd_ptr == (void *)pipe && urb->hcd_var == URB_HCD_STATE_DONE); //The URB's reserved field should have been set to this pipe urb->hcd_ptr = NULL; urb->hcd_var = URB_HCD_STATE_IDLE; - if (pipe->cs_flags.is_active + if (pipe->cs_flags.has_urb && pipe->num_urb_pending == 0 && pipe->num_urb_done == 0 && pipe->multi_buffer_control.buffer_num_to_exec == 0 && pipe->multi_buffer_control.buffer_num_to_parse == 0) { //This pipe has no more enqueued URBs. Move the pipe to the list of idle pipes @@ -2680,7 +2575,7 @@ urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl) TAILQ_INSERT_TAIL(&pipe->port->pipes_idle_tailq, pipe, tailq_entry); pipe->port->num_pipes_idle++; pipe->port->num_pipes_queued--; - pipe->cs_flags.is_active = 0; + pipe->cs_flags.has_urb = 0; } } else { //No more URBs to dequeue from this pipe diff --git a/components/usb/hub.c b/components/usb/hub.c new file mode 100644 index 0000000000..f970c41b3f --- /dev/null +++ b/components/usb/hub.c @@ -0,0 +1,668 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "usb_private.h" +#include "hcd.h" +#include "hub.h" + +/* +Implementation of the HUB driver that only supports the root hub with a single port. Therefore, we currently don't +implement the bare minimum to control the root HCD port. +*/ + +#define HUB_ROOT_PORT_NUM 1 //HCD only supports one port +#ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN +#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_RX +#elif CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT +#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_PTX +#else //CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED +#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED +#endif + +#define ENUM_CTRL_TRANSFER_MAX_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE +#define ENUM_DEV_ADDR 1 //Device address used in enumeration +#define ENUM_CONFIG_INDEX 0 //Index of the first configuration of the device +#define ENUM_DEV_DESC_REQ_SIZE 64 //Worst case size for device descriptor request +#define ENUM_LOW_SPEED_MPS 8 //Worst case MPS for the default endpoint of a low-speed device +#define ENUM_FULL_SPEED_MPS 64 //Worst case MPS for the default endpoint of a full-speed device + +#define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 +#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x02 +#define HUB_DRIVER_FLAG_ACTION_PORT_RECOVER 0x04 + +typedef enum { + HUB_DRIVER_STATE_INSTALLED, + HUB_DRIVER_STATE_ROOT_POWERD, + HUB_DRIVER_STATE_ROOT_ENUMERATING, + HUB_DRIVER_STATE_ROOT_ACTIVE, + HUB_DRIVER_STATE_ROOT_RECOVERY, +} hub_driver_state_t; + +typedef enum { + ENUM_STAGE_NONE, /**< There is no device awaiting enumeration */ + ENUM_STAGE_START, /**< Start of enumeration. Allocates device object */ + ENUM_STAGE_GET_SHORT_DEV_DESC, /**< Getting short device descriptor */ + ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK, /**< Check that short device descriptor was obtained */ + ENUM_STAGE_SECOND_RESET, /**< Doing second reset */ + ENUM_STAGE_SET_ADDR, /**< Setting address */ + ENUM_STAGE_SET_ADDR_CHECK, /**< Check that address was set successful */ + ENUM_STAGE_GET_FULL_DEV_DESC, /**< Getting full device descriptor */ + ENUM_STAGE_GET_FULL_DEV_DESC_CHECK, /**< Check that full device descriptor was obtained */ + ENUM_STAGE_GET_CONFIG_DESC, /**< Getting full configuration descriptor */ + ENUM_STAGE_GET_CONFIG_DESC_CHECK, /**< Check that full configuration descriptor was obtained */ + ENUM_STAGE_SET_CONFIG, /**< Set configuration number */ + ENUM_STAGE_SET_CONFIG_CHECK, /**< Check that configuration number was set */ + ENUM_STAGE_CLEANUP, /**< Clean up successful enumeration. Adds enumerated device to USBH */ + ENUM_STAGE_CLEANUP_FAILED, /**< Cleanup failed enumeration. Free device resources */ +} enum_stage_t; + +typedef struct { + //Dynamic members require a critical section + struct { + union { + struct { + uint32_t actions: 8; + uint32_t reserved24: 24; + }; + uint32_t val; + } flags; + hub_driver_state_t driver_state; + usb_device_handle_t root_dev_hdl; //Indicates if an enumerated device is connected to the root port + } dynamic; + //Single thread members don't require a critical section so long as they are never accessed from multiple threads + struct { + enum_stage_t enum_stage; + urb_t *enum_urb; + usb_device_handle_t enum_dev_hdl; //Handle of the device being enumerated. Moves to root_dev_hdl on enumeration completion + hcd_pipe_handle_t enum_dflt_pipe_hdl; + } single_thread; + //Constant members do no change after installation thus do not require a critical section + struct { + hcd_port_handle_t root_port_hdl; + usb_notif_cb_t notif_cb; + void *notif_cb_arg; + } constant; +} hub_driver_t; + +static hub_driver_t *p_hub_driver_obj = NULL; +static portMUX_TYPE hub_driver_lock = portMUX_INITIALIZER_UNLOCKED; + +const char *HUB_DRIVER_TAG = "HUB"; + +#define HUB_DRIVER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&hub_driver_lock) +#define HUB_DRIVER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&hub_driver_lock) +#define HUB_DRIVER_ENTER_CRITICAL() portENTER_CRITICAL(&hub_driver_lock) +#define HUB_DRIVER_EXIT_CRITICAL() portEXIT_CRITICAL(&hub_driver_lock) +#define HUB_DRIVER_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&hub_driver_lock) +#define HUB_DRIVER_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&hub_driver_lock) + +#define HUB_DRIVER_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define HUB_DRIVER_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + HUB_DRIVER_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// ------------------------------------------------- Event Handling ---------------------------------------------------- + +// ---------------------- Callbacks ------------------------ + +static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) +{ + HUB_DRIVER_ENTER_CRITICAL_SAFE(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; + HUB_DRIVER_EXIT_CRITICAL_SAFE(); + bool yield; + if (in_isr) { + p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg); + } else { + yield = false; + } + return yield; +} + +static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +{ + //Note: This callback may have triggered when a failed enumeration is already cleaned up (e.g., due to a failed port reset) + HUB_DRIVER_ENTER_CRITICAL_SAFE(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + HUB_DRIVER_EXIT_CRITICAL_SAFE(); + return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg); +} + +static void usbh_hub_callback(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg) +{ + //We currently only support the root port + assert(port_hdl == p_hub_driver_obj->constant.root_port_hdl); + HUB_DRIVER_ENTER_CRITICAL(); + //Any hub_event results in whatever device connected to the root port to no longer be valid. We clear root_dev_hdl here. + usb_device_handle_t dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl; + p_hub_driver_obj->dynamic.root_dev_hdl = NULL; + assert(dev_hdl); + bool call_port_disable = false; + switch (hub_event) { + case USBH_HUB_EVENT_CLEANUP_PORT: + //After USBH has cleaned up gone device. The port can be recovered. + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + break; + case USBH_HUB_EVENT_DISABLE_PORT: + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; + call_port_disable = true; + break; + default: + abort(); //Should never occur + break; + } + HUB_DRIVER_EXIT_CRITICAL(); + if (call_port_disable) { + ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); + hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE); + ESP_ERROR_CHECK(usbh_hub_dev_port_disabled(dev_hdl)); + } +} + +// ---------------------- Handlers ------------------------- + +static void root_port_handle_events(hcd_port_handle_t root_port_hdl) +{ + hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl); + switch (port_event) { + case HCD_PORT_EVENT_NONE: + //Nothing to do + break; + case HCD_PORT_EVENT_CONNECTION: { + if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) == ESP_OK) { + ESP_LOGD(HUB_DRIVER_TAG, "Root port reset"); + //Start enumeration + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUMERATING; + HUB_DRIVER_EXIT_CRITICAL(); + p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_START; + } else { + ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed"); + } + break; + } + case HCD_PORT_EVENT_DISCONNECTION: + case HCD_PORT_EVENT_ERROR: + case HCD_PORT_EVENT_OVERCURRENT: { + usb_device_handle_t dev_hdl = NULL; + HUB_DRIVER_ENTER_CRITICAL(); + switch (p_hub_driver_obj->dynamic.driver_state) { + case HUB_DRIVER_STATE_ROOT_POWERD: + //This occurred before enumeration. Therefore, there's no device and we can go straight to port recovery + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + break; + case HUB_DRIVER_STATE_ROOT_ENUMERATING: + //This occurred during enumeration. Therefore, we need to recover the failed enumeration + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_CLEANUP_FAILED; + break; + case HUB_DRIVER_STATE_ROOT_ACTIVE: + //There was an enumerated device. We need to indicate to USBH that the device is gone + dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl; + break; + default: + abort(); //Should never occur + break; + } + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_RECOVERY; + HUB_DRIVER_EXIT_CRITICAL(); + if (dev_hdl) { + ESP_ERROR_CHECK(usbh_hub_mark_dev_gone(dev_hdl)); + } + break; + } + default: + abort(); //Should never occur + break; + } +} + +// ------------------------------------------------- Enum Functions ---------------------------------------------------- + +static enum_stage_t enum_stage_start(void) +{ + usb_speed_t speed; + if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { + return ENUM_STAGE_NONE; + } + usb_device_handle_t enum_dev_hdl; + hcd_pipe_handle_t enum_dflt_pipe_hdl; + //We use NULL as the parent device to indicate the root hub port 1. We currently only support a single device + if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) { + return ENUM_STAGE_NONE; + } + //Set our own default pipe callback + ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL)); + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->single_thread.enum_dev_hdl = enum_dev_hdl; + p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = enum_dflt_pipe_hdl; + HUB_DRIVER_EXIT_CRITICAL(); + ESP_LOGD(HUB_DRIVER_TAG, "Enumeration starting"); + return ENUM_STAGE_GET_SHORT_DEV_DESC; +} + +static enum_stage_t enum_stage_get_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer); + enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE; + if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to get short device descriptor"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Getting short device descriptor"); + return ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK; +} + +static enum_stage_t enum_stage_check_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); + assert(dequeued_enum_urb == enum_urb); + if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor transfer failed"); + return ENUM_STAGE_CLEANUP_FAILED; + } + usb_device_desc_t *device_desc = (usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + 8) { //Device must return at least 8 bytes in its data stage + ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor too short"); + return ENUM_STAGE_CLEANUP_FAILED; + } + //Update the MPS of the default pipe + if (hcd_pipe_update_mps(pipe_hdl, device_desc->bMaxPacketSize0) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to update default pipe MPS"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Short device descriptor obtained"); + return ENUM_STAGE_SECOND_RESET; +} + +static enum_stage_t enum_stage_second_reset(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(pipe_hdl)); //Persist the default pipe through the reset + if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Second reset done"); + return ENUM_STAGE_SET_ADDR; +} + +static enum_stage_t enum_stage_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_DEV_ADDR); + enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage + if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to set address"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Setting address %d", ENUM_DEV_ADDR); + return ENUM_STAGE_SET_ADDR_CHECK; +} + +static enum_stage_t enum_stage_check_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); + assert(dequeued_enum_urb == enum_urb); + if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Set address transfer failed"); + return ENUM_STAGE_CLEANUP_FAILED; + } + //Update the pipe and device's address, and fill the address into the device object + ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(pipe_hdl, ENUM_DEV_ADDR)); + ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(dev_hdl, ENUM_DEV_ADDR)); + ESP_LOGD(HUB_DRIVER_TAG, "Address set successfully"); + return ENUM_STAGE_GET_FULL_DEV_DESC; +} + +static enum_stage_t enum_stage_get_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer); + enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE; + if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to get full device descriptor"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Getting full device descriptor"); + return ENUM_STAGE_GET_FULL_DEV_DESC_CHECK; +} + +static enum_stage_t enum_stage_check_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); + assert(dequeued_enum_urb == enum_urb); + if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor transfer failed"); + return ENUM_STAGE_CLEANUP_FAILED; + } + if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t)) { + ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor too short"); + return ENUM_STAGE_CLEANUP_FAILED; + } + //Fill device descriptor into device + const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(dev_hdl, device_desc)); + ESP_LOGD(HUB_DRIVER_TAG, "Full device descriptor obtained"); + return ENUM_STAGE_GET_CONFIG_DESC; +} + +static enum_stage_t enum_stage_get_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + //Get the configuration descriptor at index 0 + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX, ENUM_CTRL_TRANSFER_MAX_LEN); + enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN; + if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to get configuration descriptor"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Getting configuration descriptor"); + return ENUM_STAGE_GET_CONFIG_DESC_CHECK; +} + +static enum_stage_t enum_stage_check_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); + assert(dequeued_enum_urb == enum_urb); + if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor transfer failed"); + return ENUM_STAGE_CLEANUP_FAILED; + } + usb_config_desc_t *config_desc = (usb_config_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_config_desc_t)) { + ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor too small"); + return ENUM_STAGE_CLEANUP_FAILED; + } + if (config_desc->wTotalLength > ENUM_CTRL_TRANSFER_MAX_LEN) { + ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor larger than control transfer max length"); + return ENUM_STAGE_CLEANUP_FAILED; + } + //Fill configuration descriptor into device + ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(dev_hdl, config_desc)); + return ENUM_STAGE_SET_CONFIG; +} + +static enum_stage_t enum_stage_set_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX + 1); + enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage + if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to set configuration"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Setting configuration"); + return ENUM_STAGE_SET_CONFIG_CHECK; +} + +static enum_stage_t enum_stage_check_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); + assert(dequeued_enum_urb == enum_urb); + if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Set configuration transfer failed"); + return ENUM_STAGE_CLEANUP_FAILED; + } + ESP_LOGD(HUB_DRIVER_TAG, "Configuration set successfully"); + return ENUM_STAGE_CLEANUP; +} + +static enum_stage_t enum_stage_cleanup(void) +{ + //We currently only support a single device connected to the root port. Move the device handle from enum to root + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.root_dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ACTIVE; + HUB_DRIVER_EXIT_CRITICAL(); + usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; + p_hub_driver_obj->single_thread.enum_dev_hdl = NULL; + p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL; + //Update device object after enumeration is done + ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl)); + return ENUM_STAGE_NONE; +} + +static enum_stage_t enum_stage_cleanup_failed(void) +{ + //Enumeration failed. Clear the enum device handle and pipe + usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; + if (p_hub_driver_obj->single_thread.enum_dev_hdl) { + //Halt, flush, and dequeue enum default pipe + ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_HALT)); + ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_FLUSH)); + urb_t *dequeued_enum_urb = hcd_urb_dequeue(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl); + assert(dequeued_enum_urb == p_hub_driver_obj->single_thread.enum_urb); + ESP_ERROR_CHECK(usbh_hub_enum_failed(dev_hdl)); //Free the underlying device object first before recovering the port + } + p_hub_driver_obj->single_thread.enum_dev_hdl = NULL; + p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL; + HUB_DRIVER_ENTER_CRITICAL(); + //Enum could have failed due to a port error. If so, we need to trigger a port recovery + if (p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_ROOT_RECOVERY) { + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + } + HUB_DRIVER_EXIT_CRITICAL(); + return ENUM_STAGE_NONE; +} + +static void enum_handle_events(void) +{ + enum_stage_t cur_stage = p_hub_driver_obj->single_thread.enum_stage; + hcd_pipe_handle_t enum_dflt_pipe_hdl = p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl; + urb_t *enum_urb = p_hub_driver_obj->single_thread.enum_urb; + usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; + enum_stage_t next_stage; + switch (cur_stage) { + case ENUM_STAGE_START: + next_stage = enum_stage_start(); + break; + case ENUM_STAGE_GET_SHORT_DEV_DESC: + next_stage = enum_stage_get_short_dev_desc(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK: + next_stage = enum_stage_check_short_dev_desc(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_SECOND_RESET: + next_stage = enum_stage_second_reset(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_SET_ADDR: + next_stage = enum_stage_set_addr(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_SET_ADDR_CHECK: + next_stage = enum_stage_check_set_addr(enum_dflt_pipe_hdl, enum_urb, dev_hdl); + break; + case ENUM_STAGE_GET_FULL_DEV_DESC: + next_stage = enum_stage_get_full_dev_desc(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_GET_FULL_DEV_DESC_CHECK: + next_stage = enum_stage_check_full_dev_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl); + break; + case ENUM_STAGE_GET_CONFIG_DESC: + next_stage = enum_stage_get_config_desc(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_GET_CONFIG_DESC_CHECK: + next_stage = enum_stage_check_config_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl); + break; + case ENUM_STAGE_SET_CONFIG: + next_stage = enum_stage_set_config(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_SET_CONFIG_CHECK: + next_stage = enum_stage_check_config(enum_dflt_pipe_hdl, enum_urb); + break; + case ENUM_STAGE_CLEANUP: + next_stage = enum_stage_cleanup(); + break; + case ENUM_STAGE_CLEANUP_FAILED: + next_stage = enum_stage_cleanup_failed(); + break; + default: + //Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup + next_stage = ENUM_STAGE_NONE; + break; + } + p_hub_driver_obj->single_thread.enum_stage = next_stage; + HUB_DRIVER_ENTER_CRITICAL(); + if (next_stage == ENUM_STAGE_GET_SHORT_DEV_DESC || + next_stage == ENUM_STAGE_SECOND_RESET || + next_stage == ENUM_STAGE_SET_ADDR || + next_stage == ENUM_STAGE_GET_FULL_DEV_DESC || + next_stage == ENUM_STAGE_GET_CONFIG_DESC || + next_stage == ENUM_STAGE_SET_CONFIG || + next_stage == ENUM_STAGE_CLEANUP || + next_stage == ENUM_STAGE_CLEANUP_FAILED) { + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + } + HUB_DRIVER_EXIT_CRITICAL(); +} + +// ---------------------------------------------- Hub Driver Functions ------------------------------------------------- + +esp_err_t hub_install(hub_config_t *hub_config) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + //Allocate Hub driver object + hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT); + urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN, 0, 0); + if (hub_driver_obj == NULL || enum_urb == NULL) { + return ESP_ERR_NO_MEM; + } + esp_err_t ret; + //Install HCD port + hcd_port_config_t port_config = { + .fifo_bias = HUB_ROOT_HCD_PORT_FIFO_BIAS, + .callback = root_port_callback, + .callback_arg = NULL, + .context = NULL, + }; + hcd_port_handle_t port_hdl; + ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &port_hdl); + if (ret != ESP_OK) { + goto err; + } + //Initialize hub driver object + hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED; + hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_NONE; + hub_driver_obj->single_thread.enum_urb = enum_urb; + hub_driver_obj->constant.root_port_hdl = port_hdl; + hub_driver_obj->constant.notif_cb = hub_config->notif_cb; + hub_driver_obj->constant.notif_cb_arg = hub_config->notif_cb_arg; + HUB_DRIVER_ENTER_CRITICAL(); + if (p_hub_driver_obj != NULL) { + HUB_DRIVER_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + p_hub_driver_obj = hub_driver_obj; + HUB_DRIVER_EXIT_CRITICAL(); + //Indicate to USBH that the hub is installed + ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_callback, NULL)); + ret = ESP_OK; + return ret; + +assign_err: + ESP_ERROR_CHECK(hcd_port_deinit(port_hdl)); +err: + urb_free(enum_urb); + heap_caps_free(hub_driver_obj); + return ret; +} + +esp_err_t hub_uninstall(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + hub_driver_t *hub_driver_obj = p_hub_driver_obj; + p_hub_driver_obj = NULL; + HUB_DRIVER_EXIT_CRITICAL(); + + ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl)); + //Free Hub driver resources + urb_free(hub_driver_obj->single_thread.enum_urb); + heap_caps_free(hub_driver_obj); + return ESP_OK; +} + +esp_err_t hub_root_start(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + //Power ON the root port + esp_err_t ret; + ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON); + if (ret == ESP_OK) { + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; + HUB_DRIVER_EXIT_CRITICAL(); + } + return ret; +} + +esp_err_t hub_root_stop(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state != HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + esp_err_t ret; + ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_OFF); + if (ret == ESP_OK) { + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED; + HUB_DRIVER_EXIT_CRITICAL(); + } + return ret; +} + +esp_err_t hub_process(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + uint32_t action_flags = p_hub_driver_obj->dynamic.flags.actions; + p_hub_driver_obj->dynamic.flags.actions = 0; + HUB_DRIVER_EXIT_CRITICAL(); + + while (action_flags) { + if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { + root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); + } + if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { + enum_handle_events(); + } + if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_RECOVER) { + ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); + ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); + ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; + HUB_DRIVER_EXIT_CRITICAL(); + } + HUB_DRIVER_ENTER_CRITICAL(); + action_flags = p_hub_driver_obj->dynamic.flags.actions; + p_hub_driver_obj->dynamic.flags.actions = 0; + HUB_DRIVER_EXIT_CRITICAL(); + } + return ESP_OK; +} diff --git a/components/usb/include/usb/usb_host.h b/components/usb/include/usb/usb_host.h new file mode 100644 index 0000000000..dbc38bd385 --- /dev/null +++ b/components/usb/include/usb/usb_host.h @@ -0,0 +1,408 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "esp_intr_alloc.h" +//Include the other USB Host Library headers as well +#include "usb/usb_host_misc.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +// ----------------------- Handles ------------------------- + +typedef void * usb_host_client_handle_t; /**< Handle to a client using the USB Host Library */ + +// ----------------------- Events -------------------------- + +#define USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS 0x01 /**< All clients have been deregistered from the USB Host Library */ +#define USB_HOST_LIB_EVENT_FLAGS_ALL_FREE 0x02 /**< The USB Host Library has freed all devices */ + +/** + * @brief The type event in a client event message + */ +typedef enum { + USB_HOST_CLIENT_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the USB Host Library */ + USB_HOST_CLIENT_EVENT_DEV_GONE, /**< A device opened by the client is now gone */ +} usb_host_client_event_t; + +/** + * @brief Client event message + * + * Client event messages are sent to each client of the USB Host Library in order to notify them of various + * USB Host Library events such as: + * - Addition of new devices + * - Removal of existing devices + * + * @note The event message structure has a union with members corresponding to each particular event. Based on the event + * type, only the relevant member field should be accessed. + */ +typedef struct { + usb_host_client_event_t event; /**< Type of event */ + union { + struct { + uint8_t address; /**< New device's address */ + } new_dev; + struct { + usb_device_handle_t dev_hdl; /**< The handle of the device that was gone */ + } dev_gone; + }; +} usb_host_client_event_msg_t; + +// ---------------------- Callbacks ------------------------ + +/** + * @brief Client event callback + * + * - Each client of the USB Host Library must register an event callback to receive event messages from the USB Host + * Library. + * - The client event callback is run from the context of the clients usb_host_client_handle_events() function + */ +typedef void (*usb_host_client_event_cb_t)(const usb_host_client_event_msg_t *event_msg, void *arg); + +// -------------------- Configurations --------------------- + +/** + * @brief USB Host Library configuration + * + * Configuration structure of the USB Host Library. Provided in the usb_host_install() function + */ +typedef struct { + int intr_flags; /**< Interrupt flags for the underlying ISR used by the USB Host stack */ +} usb_host_config_t; + +/** + * @brief USB Host Library Client configuration + * + * Configuration structure for a USB Host Library client. Provided in usb_host_client_register() + */ +typedef struct { + usb_host_client_event_cb_t client_event_callback; /**< Client's event callback function */ + void *callback_arg; /**< Event callback function argument */ + int max_num_event_msg; /**< Maximum number of event messages that can be stored (e.g., 3) */ +} usb_host_client_config_t; + +// ------------------------------------------------ Library Functions -------------------------------------------------- + +/** + * @brief Install the USB Host Library + * + * - This function should only once to install the USB Host Library + * - This function should be called before any other USB Host Library functions are called + * + * @param[in] config USB Host Library configuration + * @return esp_err_t + */ +esp_err_t usb_host_install(const usb_host_config_t *config); + +/** + * @brief Uninstall the USB Host Library + * + * - This function should be called to uninstall the USB Host Library, thereby freeing its resources + * - All clients must have been deregistered before calling this function + * - All devices must have been freed by calling usb_host_device_free_all() and receiving the + * USB_HOST_LIB_EVENT_FLAGS_ALL_FREE event flag + * + * @return esp_err_t + */ +esp_err_t usb_host_uninstall(void); + +/** + * @brief Handle USB Host Library events + * + * - This function handles all of the USB Host Library's processing and should be called repeatedly in a loop + * - Check event_flags_ret to see if an flags are set indicating particular USB Host Library events + * + * @param[in] timeout_ticks Timeout in ticks to wait for an event to occur + * @param[out] event_flags_ret Event flags that indicate what USB Host Library event occurred + * @return esp_err_t + */ +esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret); + +// ------------------------------------------------ Client Functions --------------------------------------------------- + +/** + * @brief Register a client of the USB Host Library + * + * - This function registers a client of the USB Host Library + * - Once a client is registered, its processing function usb_host_client_handle_events() should be called repeatedly + * + * @param[in] client_config Client configuration + * @param[out] client_hdl_ret Client handle + * @return esp_err_t + */ +esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret); + +/** + * @brief Deregister a USB Host Library client + * + * - This function deregisters a client of the USB Host Library + * - The client must have closed all previously opened devices before attempting to deregister + * + * @param[in] client_hdl Client handle + * @return esp_err_t + */ +esp_err_t usb_host_client_deregister(usb_host_client_handle_t client_hdl); + +/** + * @brief USB Host Library client processing function + * + * - This function handles all of a client's processing and should be called repeatedly in a loop + * + * @param[in] client_hdl Client handle + * @param[in] timeout_ticks Timeout in ticks to wait for an event to occur + * @return esp_err_t + */ +esp_err_t usb_host_client_handle_events(usb_host_client_handle_t client_hdl, TickType_t timeout_ticks); + +/** + * @brief Unblock a client + * + * - This function simply unblocks a client if it is blocked on the usb_host_client_handle_events() function. + * - This function is useful when need to unblock a client in order to deregister it. + * + * @param[in] client_hdl Client handle + * @return esp_err_t + */ +esp_err_t usb_host_client_unblock(usb_host_client_handle_t client_hdl); + +// ------------------------------------------------- Device Handling --------------------------------------------------- + +/** + * @brief Open a device + * + * - This function allows a client to open a device + * - A client must open a device first before attempting to use it (e.g., sending transfers, device requests etc.) + * + * @param[in] client_hdl Client handle + * @param[in] dev_addr Device's address + * @param[out] dev_hdl_ret Device's handle + * @return esp_err_t + */ +esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_addr, usb_device_handle_t *dev_hdl_ret); + +/** + * @brief Close a device + * + * - This function allows a client to close a device + * - A client must close a device after it has finished using the device + * - A client must close all devices it has opened before deregistering + * + * @param[in] client_hdl Client handle + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl); + +/** + * @brief Indicate that all devices can be freed when possible + * + * - This function marks all devices as waiting to be freed + * - If a device is not opened by any clients, it will be freed immediately + * - If a device is opened by at least one client, the device will be free when the last client closes that device. + * - Wait for the USB_HOST_LIB_EVENT_FLAGS_ALL_FREE flag to be set by usb_host_lib_handle_events() in order to know + * when all devices have been freed + * - This function is useful when cleaning up devices before uninstalling the USB Host Library + * + * @return esp_err_t + */ +esp_err_t usb_host_device_free_all(void); + +// ------------------------------------------------- Device Requests --------------------------------------------------- + +// ------------------- Cached Requests --------------------- + +/** + * @brief Get a device's information + * + * - This function gets some basic information of a device + * - The device must be opened first before attempting to get its information + * + * @param[in] dev_hdl Device handle + * @param[out] dev_info Device information + * @return esp_err_t + */ +esp_err_t usb_host_device_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info); + +// ----------------------------------------------- Descriptor Requests ------------------------------------------------- + +// ----------------- Cached Descriptors -------------------- + +/** + * @brief Get a device's device descriptor + * + * - A client must call usb_host_device_open() first + * - No control transfer is sent. The device's descriptor is cached on enumeration + * - This function simple returns a pointer to the cached descriptor + * + * @note No control transfer is sent. The device's descriptor is cached on enumeration + * @param[in] dev_hdl Device handle + * @param[out] device_desc Device descriptor + * @return esp_err_t + */ +esp_err_t usb_host_get_device_descriptor(usb_device_handle_t dev_hdl, const usb_device_desc_t **device_desc); + +/** + * @brief Get a device's active configuration descriptor + * + * - A client must call usb_host_device_open() first + * - No control transfer is sent. The device's active configuration descriptor is cached on enumeration + * - This function simple returns a pointer to the cached descriptor + * + * @note No control transfer is sent. A device's active configuration descriptor is cached on enumeration + * @param[in] dev_hdl Device handle + * @param[out] config_desc Configuration descriptor + * @return esp_err_t + */ +esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc); + +// ----------------------------------------------- Interface Functions ------------------------------------------------- + +/** + * @brief Function for a client to claim an device's interface + * + * - A client must claim a device's interface before attempting to communicate with any of its endpoints + * - Once an interface is claimed by a client, it cannot be claimed by any other client. + * + * @param[in] client_hdl Client handle + * @param[in] dev_hdl Device handle + * @param[in] bInterfaceNumber Interface number + * @param[in] bAlternateSetting Interface alternate setting number + * @return esp_err_t + */ +esp_err_t usb_host_interface_claim(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber, uint8_t bAlternateSetting); + +/** + * @brief Function for a client to release a device's interface + * + * - A client should release a device's interface after it no longer needs to communicate with the interface + * - A client must release all of its interfaces of a device it has claimed before being able to close the device + * + * @param[in] client_hdl Client handle + * @param[in] dev_hdl Device handle + * @param[in] bInterfaceNumber Interface number + * @return esp_err_t + */ +esp_err_t usb_host_interface_release(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber); + +/** + * @brief Halt a particular endpoint + * + * - The device must have been opened by a client + * - The endpoint must be part of an interface claimed by a client + * - Once halted, the endpoint must be cleared using usb_host_endpoint_clear() before it can communicate again + * + * @param dev_hdl Device handle + * @param bEndpointAddress Endpoint address + * @return esp_err_t + */ +esp_err_t usb_host_endpoint_halt(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); + +/** + * @brief Flush a particular endpoint + * + * - The device must have been opened by a client + * - The endpoint must be part of an interface claimed by a client + * - The endpoint must have been halted (either through a transfer error, or usb_host_endpoint_halt()) + * - Flushing an endpoint will caused an queued up transfers to be canceled + * + * @param dev_hdl Device handle + * @param bEndpointAddress Endpoint address + * @return esp_err_t + */ +esp_err_t usb_host_endpoint_flush(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); + +/** + * @brief Clear a halt on a particular endpoint + * + * - The device must have been opened by a client + * - The endpoint must be part of an interface claimed by a client + * - The endpoint must have been halted (either through a transfer error, or usb_host_endpoint_halt()) + * - If the endpoint has any queued up transfers, clearing a halt will resume their execution + * + * @param dev_hdl Device handle + * @param bEndpointAddress Endpoint address + * @return esp_err_t + */ +esp_err_t usb_host_endpoint_clear(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); + +// ------------------------------------------------ Asynchronous I/O --------------------------------------------------- + +/** + * @brief Allocate a transfer object + * + * - This function allocates a transfer object + * - Each transfer object has a fixed sized buffer specified on allocation + * - A transfer object can be re-used indefinitely + * - A transfer can be submitted using usb_host_transfer_submit() or usb_host_transfer_submit_control() + * + * @param[in] data_buffer_size Size of the transfer's data buffer + * @param[in] num_isoc_packets Number of isochronous packets in transfer (set to 0 for non-isochronous transfers) + * @param[out] transfer Transfer object + * @return esp_err_t + */ +esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer); + +/** + * @brief Free a transfer object + * + * - Free a transfer object previously allocated using usb_host_transfer_alloc() + * - The transfer must not be in-flight when attempting to free it + * + * @param[in] transfer Transfer object + * @return esp_err_t + */ +esp_err_t usb_host_transfer_free(usb_transfer_t *transfer); + +/** + * @brief Submit a non-control transfer + * + * - Submit a transfer to a particular endpoint. The device and endpoint number is specified inside the transfer + * - The transfer must be properly initialized before submitting + * - On completion, the transfer's callback will be called from the client's usb_host_client_handle_events() function. + * + * @param[in] transfer Initialized transfer object + * @return esp_err_t + */ +esp_err_t usb_host_transfer_submit(usb_transfer_t *transfer); + +/** + * @brief Submit a control transfer + * + * - Submit a control transfer to a particular device. The client must have opened the device first + * - The transfer must be properly initialized before submitting. The first 8 bytes of the transfer's data buffer should + * contain the control transfer setup packet + * - On completion, the transfer's callback will be called from the client's usb_host_client_handle_events() function. + * + * @param[in] client_hdl Client handle + * @param[in] transfer Initialized transfer object + * @return esp_err_t + */ +esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, usb_transfer_t *transfer); + +/** + * @brief Cancel a submitted transfer + * + * - Cancel a previously submitted transfer + * - In its current implementation, any transfer that is already in-flight will not be canceled + * + * @param transfer Transfer object + * @return esp_err_t + */ +esp_err_t usb_host_transfer_cancel(usb_transfer_t *transfer); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/include/usb/usb_host_misc.h b/components/usb/include/usb/usb_host_misc.h new file mode 100644 index 0000000000..ab04b471e2 --- /dev/null +++ b/components/usb/include/usb/usb_host_misc.h @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "usb/usb_types_ch9.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------------------------------- Configuration Descriptor Parsing ------------------------------------------- + +/** + * @brief Get the next descriptor + * + * Given a particular descriptor within a full configuration descriptor, get the next descriptor within the + * configuration descriptor. This is a convenience function that can be used to walk each individual descriptor within + * a full configuration descriptor. + * + * @param[in] cur_desc Current descriptor + * @param[in] wTotalLength Total length of the configuration descriptor + * @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset of + * the current descriptor. On output, it is the offset of the returned descriptor. + * @return usb_standard_desc_t* Next descriptor, NULL if end of configuration descriptor reached + */ +const usb_standard_desc_t *usb_host_parse_next_descriptor(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, int *offset); + +/** + * @brief Get the next descriptor of a particular type + * + * Given a particular descriptor within a full configuration descriptor, get the next descriptor of a particular type + * (i.e., using the bDescriptorType value) within the configuration descriptor. + * + * @param[in] cur_desc Current descriptor + * @param[in] wTotalLength Total length of the configuration descriptor + * @param[in] bDescriptorType Type of the next descriptor to get + * @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset of + * the current descriptor. On output, it is the offset of the returned descriptor. + * @return usb_standard_desc_t* Next descriptor, NULL if end descriptor is not found or configuration descriptor reached + */ +const usb_standard_desc_t *usb_host_parse_next_descriptor_of_type(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, uint8_t bDescriptorType, int *offset); + +/** + * @brief Get the number of alternate settings for a bInterfaceNumber + * + * Given a particular configuration descriptor, for a particular bInterfaceNumber, get the number of alternate settings + * available for that interface (i.e., the max possible value of bAlternateSetting for that bInterfaceNumber). + * + * @param[in] config_desc Pointer to the start of a full configuration descriptor + * @param[in] bInterfaceNumber Interface number + * @return int The number of alternate settings that the interface has, -1 if bInterfaceNumber not found + */ +int usb_host_parse_interface_number_of_alternate(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber); + +/** + * @brief Get a particular interface descriptor (using bInterfaceNumber and bAlternateSetting) + * + * Given a full configuration descriptor, get a particular interface descriptor. + * + * @note To get the number of alternate settings for a particular bInterfaceNumber, call + * usb_host_parse_interface_number_of_alternate() + * + * @param[in] config_desc Pointer to the start of a full configuration descriptor + * @param[in] bInterfaceNumber Interface number + * @param[in] bAlternateSetting Alternate setting number + * @param[out] offset Byte offset of the interface descriptor relative to the start of the configuration descriptor. Can be NULL. + * @return const usb_intf_desc_t* Pointer to interface descriptor, NULL if not found. + */ +const usb_intf_desc_t *usb_host_parse_interface(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, int *offset); + +/** + * @brief Get an endpoint descriptor within an interface descriptor + * + * Given an interface descriptor, get the Nth endpoint descriptor of the interface. The number of endpoints in an + * interface is indicated by the bNumEndpoints field of the interface descriptor. + * + * @note If bNumEndpoints is 0, it means the interface uses the default endpoint only + * + * @param[in] intf_desc Pointer to thee start of an interface descriptor + * @param[in] index Endpoint index + * @param[in] wTotalLength Total length of the containing configuration descriptor + * @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset + * of the interface descriptor. On output, it is the offset of the endpoint descriptor. + * @return const usb_ep_desc_t* Pointer to endpoint descriptor, NULL if not found. + */ +const usb_ep_desc_t *usb_host_parse_endpoint_by_index(const usb_intf_desc_t *intf_desc, int index, uint16_t wTotalLength, int *offset); + +/** + * @brief Get an endpoint descriptor based on the endpoint's address + * + * Given a configuration descriptor, get an endpoint descriptor based on it's bEndpointAddress, bAlternateSetting, and + * bInterfaceNumber. + * + * @param[in] config_desc Pointer to the start of a full configuration descriptor + * @param[in] bInterfaceNumber Interface number + * @param[in] bAlternateSetting Alternate setting number + * @param[in] bEndpointAddress Endpoint address + * @param[out] offset Byte offset of the endpoint descriptor relative to the start of the configuration descriptor. Can be NULL + * @return const usb_ep_desc_t* Pointer to endpoint descriptor, NULL if not found. + */ +const usb_ep_desc_t *usb_host_parse_endpoint_by_address(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, uint8_t bEndpointAddress, int *offset); + +// ------------------------------------------------------ Misc --------------------------------------------------------- + +/** + * @brief Round up to an integer multiple of an endpoint's MPS + * + * This is a convenience function to round up a size/length to an endpoint's MPS (Maximum packet size). This is useful + * when calculating transfer or buffer lengths of IN endpoints. + * + * @param[in] num_bytes Number of bytes + * @param[in] mps MPS + * @return int Round up integer multiple of MPS + */ +static inline int usb_host_round_up_to_mps(int num_bytes, int mps) +{ + if (num_bytes < 0 || mps < 0) { + return 0; + } + return ((num_bytes + mps - 1) / mps) * mps; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h new file mode 100644 index 0000000000..4fe31b342f --- /dev/null +++ b/components/usb/include/usb/usb_types_ch9.h @@ -0,0 +1,464 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* +Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation +and mode (i.e., Host or Device) agnostic. + +- On the USB Host Stack, this header is only meant to be used on the HCD layer and above. For types and macros on the + Host stack that are HW implementation specific (i.e., HAL layer and below), add them to the "hal/usb_types_private.h" + header instead. +*/ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define USB_DESC_ATTR __attribute__((packed)) + +// ---------------------------------------------------- Chapter 9 ------------------------------------------------------ + +/** + * @brief USB2.0 device states + */ +typedef enum { + USB_DEVICE_STATE_NOT_ATTACHED, //Not in the USB spec, but is a catch all state for devices that need to be cleaned up after a sudden disconnection or port error + USB_DEVICE_STATE_ATTACHED, + USB_DEVICE_STATE_POWERED, + USB_DEVICE_STATE_DEFAULT, + USB_DEVICE_STATE_ADDRESS, + USB_DEVICE_STATE_CONFIGURED, + USB_DEVICE_STATE_SUSPENDED, +} usb_device_state_t; + +/** + * @brief Descriptor types from USB2.0 specification table 9.5 + */ +#define USB_B_DESCRIPTOR_TYPE_DEVICE 0x01 +#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 0x02 +#define USB_B_DESCRIPTOR_TYPE_STRING 0x03 +#define USB_B_DESCRIPTOR_TYPE_INTERFACE 0x04 +#define USB_B_DESCRIPTOR_TYPE_ENDPOINT 0x05 +#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 0x06 +#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 0x07 +#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 0x08 + +/** + * @brief Descriptor types from USB 2.0 ECN + */ +#define USB_B_DESCRIPTOR_TYPE_OTG 0x09 +#define USB_B_DESCRIPTOR_TYPE_DEBUG 0x0a +#define USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION 0x0b + +/** + * @brief Descriptor types from Wireless USB spec + */ +#define USB_B_DESCRIPTOR_TYPE_SECURITY 0x0c +#define USB_B_DESCRIPTOR_TYPE_KEY 0x0d +#define USB_B_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 0x0e +#define USB_B_DESCRIPTOR_TYPE_BOS 0x0f +#define USB_B_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 0x10 +#define USB_B_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMP 0x11 +#define USB_B_DESCRIPTOR_TYPE_WIRE_ADAPTER 0x21 +#define USB_B_DESCRIPTOR_TYPE_RPIPE 0x22 +#define USB_B_DESCRIPTOR_TYPE_CS_RADIO_CONTROL 0x23 + +/** + * @brief Descriptor types from UAS specification + */ +#define USB_B_DESCRIPTOR_TYPE_PIPE_USAGE 0x24 + +// -------------------- Setup Packet ----------------------- + +/** + * @brief Size of a USB control transfer setup packet in bytes + */ +#define USB_SETUP_PACKET_SIZE 8 + +/** + * @brief Structure representing a USB control transfer setup packet + */ +typedef union { + struct { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + } __attribute__((packed)); + uint8_t val[USB_SETUP_PACKET_SIZE]; +} usb_setup_packet_t; +_Static_assert(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb_setup_packet_t incorrect"); + +/** + * @brief Bit masks belonging to the bmRequestType field of a setup packet + */ +#define USB_BM_REQUEST_TYPE_DIR_OUT (0X00 << 7) +#define USB_BM_REQUEST_TYPE_DIR_IN (0x01 << 7) +#define USB_BM_REQUEST_TYPE_TYPE_STANDARD (0x00 << 5) +#define USB_BM_REQUEST_TYPE_TYPE_CLASS (0x01 << 5) +#define USB_BM_REQUEST_TYPE_TYPE_VENDOR (0x02 << 5) +#define USB_BM_REQUEST_TYPE_TYPE_RESERVED (0x03 << 5) +#define USB_BM_REQUEST_TYPE_TYPE_MASK (0x03 << 5) +#define USB_BM_REQUEST_TYPE_RECIP_DEVICE (0x00 << 0) +#define USB_BM_REQUEST_TYPE_RECIP_INTERFACE (0x01 << 0) +#define USB_BM_REQUEST_TYPE_RECIP_ENDPOINT (0x02 << 0) +#define USB_BM_REQUEST_TYPE_RECIP_OTHER (0x03 << 0) +#define USB_BM_REQUEST_TYPE_RECIP_MASK (0x1f << 0) + +/** + * @brief Bit masks belonging to the bRequest field of a setup packet + */ +#define USB_B_REQUEST_GET_STATUS 0x00 +#define USB_B_REQUEST_CLEAR_FEATURE 0x01 +#define USB_B_REQUEST_SET_FEATURE 0x03 +#define USB_B_REQUEST_SET_ADDRESS 0x05 +#define USB_B_REQUEST_GET_DESCRIPTOR 0x06 +#define USB_B_REQUEST_SET_DESCRIPTOR 0x07 +#define USB_B_REQUEST_GET_CONFIGURATION 0x08 +#define USB_B_REQUEST_SET_CONFIGURATION 0x09 +#define USB_B_REQUEST_GET_INTERFACE 0x0A +#define USB_B_REQUEST_SET_INTERFACE 0x0B +#define USB_B_REQUEST_SYNCH_FRAME 0x0C + +/** + * @brief Bit masks belonging to the wValue field of a setup packet + */ +#define USB_W_VALUE_DT_DEVICE 0x01 +#define USB_W_VALUE_DT_CONFIG 0x02 +#define USB_W_VALUE_DT_STRING 0x03 +#define USB_W_VALUE_DT_INTERFACE 0x04 +#define USB_W_VALUE_DT_ENDPOINT 0x05 +#define USB_W_VALUE_DT_DEVICE_QUALIFIER 0x06 +#define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 +#define USB_W_VALUE_DT_INTERFACE_POWER 0x08 + +/** + * @brief Initializer for a SET_ADDRESS request + * + * Sets the address of a connected device + */ +#define USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt_ptr, addr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD |USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS; \ + (setup_pkt_ptr)->wValue = (addr); \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +/** + * @brief Initializer for a request to get a device's device descriptor + */ +#define USB_SETUP_PACKET_INIT_GET_DEVICE_DESC(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ + (setup_pkt_ptr)->wValue = (USB_W_VALUE_DT_DEVICE << 8); \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 18; \ +}) + +/** + * @brief Initializer for a request to get a device's current configuration number + */ +#define USB_SETUP_PACKET_INIT_GET_CONFIG(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_CONFIGURATION; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 1; \ +}) + +/** + * @brief Initializer for a request to get one of the device's current configuration descriptor + * + * - desc_index indicates the configuration's index number + * - Number of bytes of the configuration descriptor to get + */ +#define USB_SETUP_PACKET_INIT_GET_CONFIG_DESC(setup_pkt_ptr, desc_index, desc_len) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ + (setup_pkt_ptr)->wValue = (USB_W_VALUE_DT_CONFIG << 8) | ((desc_index) & 0xFF); \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = (desc_len); \ +}) + +/** + * @brief Initializer for a request to set a device's current configuration number + */ +#define USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt_ptr, config_num) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION; \ + (setup_pkt_ptr)->wValue = (config_num); \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +/** + * @brief Initializer for a request to set an interface's alternate setting + */ +#define USB_SETUP_PACKET_INIT_SET_INTERFACE(setup_pkt_ptr, intf_num, alt_setting_num) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \ + (setup_pkt_ptr)->wValue = (alt_setting_num); \ + (setup_pkt_ptr)->wIndex = (intf_num); \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +// ---------------- Standard Descriptor -------------------- + +/** + * @brief Size of dummy USB standard descriptor + */ +#define USB_STANDARD_DESC_SIZE 2 + +/** + * @brief Dummy USB standard descriptor + * + * All USB standard descriptors start with these two bytes. Use this type traversing over descriptors + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + } USB_DESC_ATTR; + uint8_t val[USB_STANDARD_DESC_SIZE]; +} usb_standard_desc_t; +_Static_assert(sizeof(usb_standard_desc_t) == USB_STANDARD_DESC_SIZE, "Size of usb_standard_desc_t incorrect"); + +// ------------------ Device Descriptor -------------------- + +/** + * @brief Size of a USB device descriptor in bytes + */ +#define USB_DEVICE_DESC_SIZE 18 + +/** + * @brief Structure representing a USB device descriptor + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } USB_DESC_ATTR; + uint8_t val[USB_DEVICE_DESC_SIZE]; +} usb_device_desc_t; +_Static_assert(sizeof(usb_device_desc_t) == USB_DEVICE_DESC_SIZE, "Size of usb_device_desc_t incorrect"); + +/** + * @brief Possible base class values of the bDeviceClass field of a USB device descriptor + */ +#define USB_CLASS_PER_INTERFACE 0x00 +#define USB_CLASS_AUDIO 0x01 +#define USB_CLASS_COMM 0x02 +#define USB_CLASS_HID 0x03 +#define USB_CLASS_PHYSICAL 0x05 +#define USB_CLASS_STILL_IMAGE 0x06 +#define USB_CLASS_PRINTER 0x07 +#define USB_CLASS_MASS_STORAGE 0x08 +#define USB_CLASS_HUB 0x09 +#define USB_CLASS_CDC_DATA 0x0a +#define USB_CLASS_CSCID 0x0b +#define USB_CLASS_CONTENT_SEC 0x0d +#define USB_CLASS_VIDEO 0x0e +#define USB_CLASS_WIRELESS_CONTROLLER 0xe0 +#define USB_CLASS_PERSONAL_HEALTHCARE 0x0f +#define USB_CLASS_AUDIO_VIDEO 0x10 +#define USB_CLASS_BILLBOARD 0x11 +#define USB_CLASS_USB_TYPE_C_BRIDGE 0x12 +#define USB_CLASS_MISC 0xef +#define USB_CLASS_APP_SPEC 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +/** + * @brief Vendor specific subclass code + */ +#define USB_SUBCLASS_VENDOR_SPEC 0xff + +// -------------- Configuration Descriptor ----------------- + +/** + * @brief Size of a short USB configuration descriptor in bytes + * + * @note The size of a full USB configuration includes all the interface and endpoint + * descriptors of that configuration. + */ +#define USB_CONFIG_DESC_SIZE 9 + +/** + * @brief Structure representing a short USB configuration descriptor + * + * @note The full USB configuration includes all the interface and endpoint + * descriptors of that configuration. + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; + } USB_DESC_ATTR; + uint8_t val[USB_CONFIG_DESC_SIZE]; +} usb_config_desc_t; +_Static_assert(sizeof(usb_config_desc_t) == USB_CONFIG_DESC_SIZE, "Size of usb_config_desc_t incorrect"); + +/** + * @brief Bit masks belonging to the bmAttributes field of a configuration descriptor + */ +#define USB_BM_ATTRIBUTES_ONE (1 << 7) //Must be set +#define USB_BM_ATTRIBUTES_SELFPOWER (1 << 6) //Self powered +#define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up +#define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered + +// ---------- Interface Association Descriptor ------------- + +/** + * @brief Size of a USB interface association descriptor in bytes + */ +#define USB_IAD_DESC_SIZE 9 + +/** + * @brief Structure representing a USB interface association descriptor + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; + } USB_DESC_ATTR; + uint8_t val[USB_IAD_DESC_SIZE]; +} usb_iad_desc_t; +_Static_assert(sizeof(usb_iad_desc_t) == USB_IAD_DESC_SIZE, "Size of usb_iad_desc_t incorrect"); + +// ---------------- Interface Descriptor ------------------- + +/** + * @brief Size of a USB interface descriptor in bytes + */ +#define USB_INTF_DESC_SIZE 9 + +/** + * @brief Structure representing a USB interface descriptor + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } USB_DESC_ATTR; + uint8_t val[USB_INTF_DESC_SIZE]; +} usb_intf_desc_t; +_Static_assert(sizeof(usb_intf_desc_t) == USB_INTF_DESC_SIZE, "Size of usb_intf_desc_t incorrect"); + +// ----------------- Endpoint Descriptor ------------------- + +/** + * @brief Size of a USB endpoint descriptor in bytes + */ +#define USB_EP_DESC_SIZE 7 + +/** + * @brief Structure representing a USB endpoint descriptor + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + } USB_DESC_ATTR; + uint8_t val[USB_EP_DESC_SIZE]; +} usb_ep_desc_t; +_Static_assert(sizeof(usb_ep_desc_t) == USB_EP_DESC_SIZE, "Size of usb_ep_desc_t incorrect"); + +/** + * @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor + */ +#define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK 0x0f +#define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80 + +/** + * @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor + */ +#define USB_BM_ATTRIBUTES_XFERTYPE_MASK 0x03 +#define USB_BM_ATTRIBUTES_XFER_CONTROL (0 << 0) +#define USB_BM_ATTRIBUTES_XFER_ISOC (1 << 0) +#define USB_BM_ATTRIBUTES_XFER_BULK (2 << 0) +#define USB_BM_ATTRIBUTES_XFER_INT (3 << 0) +#define USB_BM_ATTRIBUTES_SYNCTYPE_MASK 0x0C /* in bmAttributes */ +#define USB_BM_ATTRIBUTES_SYNC_NONE (0 << 2) +#define USB_BM_ATTRIBUTES_SYNC_ASYNC (1 << 2) +#define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE (2 << 2) +#define USB_BM_ATTRIBUTES_SYNC_SYNC (3 << 2) +#define USB_BM_ATTRIBUTES_USAGETYPE_MASK 0x30 +#define USB_BM_ATTRIBUTES_USAGE_DATA (0 << 4) +#define USB_BM_ATTRIBUTES_USAGE_FEEDBACK (1 << 4) +#define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB (2 << 4) + +/** + * @brief Macro helpers to get information about an endpoint from its descriptor + */ +#define USB_EP_DESC_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK)) +#define USB_EP_DESC_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) +#define USB_EP_DESC_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0) +#define USB_EP_DESC_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF) + +// ------------------ String Descriptor -------------------- + +/** + * @brief Size of a short USB string descriptor in bytes + */ +#define USB_STR_DESC_SIZE 4 + +/** + * @brief Structure representing a USB string descriptor + */ +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wData[1]; /* UTF-16LE encoded */ + } USB_DESC_ATTR; + uint8_t val[USB_STR_DESC_SIZE]; +} usb_str_desc_t; +_Static_assert(sizeof(usb_str_desc_t) == USB_STR_DESC_SIZE, "Size of usb_str_desc_t incorrect"); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/include/usb/usb_types_stack.h b/components/usb/include/usb/usb_types_stack.h new file mode 100644 index 0000000000..0db75d3788 --- /dev/null +++ b/components/usb/include/usb/usb_types_stack.h @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "usb/usb_types_ch9.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------ Protocol Standard -------------------------------------------------- + +/** + * @brief USB Standard Speeds + */ +typedef enum { + USB_SPEED_LOW = 0, /**< USB Low Speed (1.5 Mbit/s) */ + USB_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */ +} usb_speed_t; + +/** + * @brief The type of USB transfer + * + * @note The enum values need to match the bmAttributes field of an EP descriptor + */ +typedef enum { + USB_TRANSFER_TYPE_CTRL = 0, + USB_TRANSFER_TYPE_ISOCHRONOUS, + USB_TRANSFER_TYPE_BULK, + USB_TRANSFER_TYPE_INTR, +} usb_transfer_type_t; + +// ------------------------------------------------- Device Related ---------------------------------------------------- + +/** + * @brief Handle of a USB Device connected to a USB Host + */ +typedef void * usb_device_handle_t; + +/** + * @brief Basic information of an enumerated device + */ +typedef struct { + usb_speed_t speed; /**< Device's speed */ + uint8_t dev_addr; /**< Device's address */ + uint8_t bMaxPacketSize0; /**< The maximum packet size of the device's default endpoint */ + uint8_t bConfigurationValue; /**< Device's current configuration number */ +} usb_device_info_t; + +// ------------------------------------------------ Transfer Related --------------------------------------------------- + +/** + * @brief The status of a particular transfer + */ +typedef enum { + USB_TRANSFER_STATUS_COMPLETED, /**< The transfer was successful (but may be short) */ + USB_TRANSFER_STATUS_ERROR, /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */ + USB_TRANSFER_STATUS_TIMED_OUT, /**< The transfer failed due to a time out */ + USB_TRANSFER_STATUS_CANCELED, /**< The transfer was canceled */ + USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */ + USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */ + USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */ +} usb_transfer_status_t; + +/** + * @brief Isochronous packet descriptor + * + * If the number of bytes in an Isochronous transfer is larger than the MPS of the endpoint, the transfer is split + * into multiple packets transmitted at the endpoint's specified interval. An array of Isochronous packet descriptors + * describes how an Isochronous transfer should be split into multiple packets. + */ +typedef struct { + int num_bytes; /**< Number of bytes to transmit/receive in the packet. IN packets should be integer multiple of MPS */ + int actual_num_bytes; /**< Actual number of bytes transmitted/received in the packet */ + usb_transfer_status_t status; /**< Status of the packet */ +} usb_isoc_packet_desc_t; + +/** + * @brief USB transfer structure + * + * This structure is used to represent a transfer from a software client to an endpoint over the USB bus. Some of the + * fields are made const on purpose as they are fixed on allocation. Users should call the appropriate USB Host Library + * function to allocate a USB transfer structure instead of allocating this structure themselves. + * + * The transfer type is inferred from the endpoint this transfer is sent to. Depending on the transfer type, users + * should note the following: + * + * - Bulk: This structure represents a single bulk transfer. If the number of bytes exceeds the endpoint's MPS, the + * transfer will be split into multiple MPS sized packets followed by a short packet. + * - Control: This structure represents a single control transfer. This first 8 bytes of the data_buffer must be filled + * with the setup packet (see usb_setup_packet_t). The num_bytes field should be the total size of the + * transfer (i.e., size of setup packet + wLength). + * - Interrupt: Represents an interrupt transfer. If num_bytes exceeds the MPS of the endpoint, the transfer will be + * split into multiple packets, and each packet is transferred at the endpoint's specified interval. + * - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer + * is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval + * of the endpoint. + * + * @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS + * @note This structure should be allocated via __insert_func_name__() + * @note Once the transfer has be submitted, users should not modify the structure until the transfer has completed + */ +typedef struct usb_transfer_s usb_transfer_t; + +/** + * @brief USB transfer completion callback + */ +typedef void (*usb_transfer_cb_t)(usb_transfer_t *transfer); + +struct usb_transfer_s{ + uint8_t *const data_buffer; /**< Pointer to data buffer */ + const size_t data_buffer_size; /**< Size of the data buffer in bytes */ + int num_bytes; /**< Number of bytes to transfer. + Control transfers should include the size of the setup packet. + Isochronous transfer should be the total transfer size of all packets. + For non-control IN transfers, num_bytes should be an integer multiple of MPS. */ + int actual_num_bytes; /**< Actual number of bytes transferred */ + uint32_t flags; /**< Transfer flags */ + usb_device_handle_t device_handle; /**< Device handle */ + uint8_t bEndpointAddress; /**< Endpoint Address */ + usb_transfer_status_t status; /**< Status of the transfer */ + uint32_t timeout; /**< Timeout (in milliseconds) of the packet (currently not supported yet) */ + usb_transfer_cb_t callback; /**< Transfer callback */ + void *context; /**< Context variable for transfer to associate transfer with something */ + const int num_isoc_packets; /**< Only relevant to Isochronous. Number of service periods (i.e., intervals) to transfer data buffer over. */ + usb_isoc_packet_desc_t isoc_packet_desc[0]; /**< Descriptors for each Isochronous packet */ +}; + +#define USB_TRANSFER_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 */ + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/maintainers.md b/components/usb/maintainers.md index d31fb84d25..81e2d00dec 100644 --- a/components/usb/maintainers.md +++ b/components/usb/maintainers.md @@ -1,18 +1,41 @@ # USB Host Stack Maintainers Notes -This document is intended future maintainers of the ESP-IDF USB Host stack. +This document is intended for future maintainers of the ESP-IDF USB Host stack. Note: The underlying DWC_OTG controller will sometimes be referred to as the Host Controller in this document. +Note: Some static functions within the stack need to be called within critical sections. Therefore, the USB Host stack will prefix all such function names with an underscore (e.g., `_some_static_func()`) will need to be called in critical sections) + 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). * USB Host lower layer in `usbh_ll.h` * USB HAL in `usbh_hal.h` and `usbh_hal.c` * Host Controller Driver in `hcd.c` and `hcd.h` +* USB Host Driver in `usb_host.h` and `usb_host.c` + +# DWC_OTG + +Details regarding the DWC_OTG peripheral are covered in the data book and programming guide. However, this section adds some extra notes regarding the hardware's behavior that are not specified / cannot be easily found in the data book or programming guide. + +## Implicit interrupt on short packet on INTERRUPT transfer. + +- An interrupt channel will trigger an interrupt one of its QTDs is a short packet (event if the QTD did not set the interrupt on complete bit). This implicit interrupt is used to notify the software that it should halt the channel to +stop the remainder of the interrupt transfer. + +## Channel interrupt on port errors + +- If there are one or more channels active, and a port error interrupt occurs (such as disconnection, over-current), the active channels will not have an interrupt. Each need to be manually disabled to obtain an interrupt. + +## Reset + +- When resetting, if a disconnect occurs while a reset condition is being held. The disconnect is not detected until the reset condition is released. Once released, a `USBH_HAL_PORT_EVENT_DISCONN` is generated. +- During a second reset (i.e., there is already an enabled device). + - Once the reset condition is asserted, a `USBH_HAL_PORT_EVENT_DISABLED` is generated + - Once released, a `USBH_HAL_PORT_EVENT_ENABLED` is generated again. # USB Host Lower Layer -* `usbh_ll.h` abstracts away the basic register operation of the **DWC** OTG controller +* `usbh_ll.h` abstracts away the basic register operation of the DWC_OTG controller * The LL provides register operations of the DWC OTG controller operating under Host Mode using scatter/gather internal DMA. * For non-host mode specific register operations (i.e. global registers), the functions are prefixed with `usb_ll_...()` * For host mode specific register operations, the functions are prefixed with `usbh_ll_...()` @@ -47,49 +70,52 @@ The HAL layer abstracts the DWC_OTG operating in Host Mode using Internal Scatte # Host Controller Driver (HCD) -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**. +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**. - In other words, the HCD essentially implements a root hub (not fully behavioral compliant) that contains a single port. - Pipes are "an association between an endpoint on a device and software on the host". URBs (USB Request Block) represent a USB transfer that can be enqueued into a pipe for transmission, and dequeued from a pipe when completed. The HCD currently has the following limitations: -- 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. +- 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. - 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. -- The HCD currently only supports Control and Bulk transfer types. -- 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 ## HCD Port -- 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. +- An HCD port can be thought of 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. - A port must first initialized before it can be used. A port is identified by its handled of type `hcd_port_handle_t` - The port can be manipulated using commands such as: - Powering the port ON/OFF - Issuing reset/resume signals - The various host port events are represented in the `hcd_port_event_t` enumeration -- 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. -- 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`. +- When the following port events occur, 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 must be freed before the port can be recovered. + - `HCD_PORT_EVENT_DISCONNECTION` + - `HCD_PORT_EVENT_ERROR` + - `HCD_PORT_EVENT_OVERCURRENT` +- The port's internal FIFOs (RX, Periodic TX, Non-periodic TX) must be after each port reset (a port reset seems to clear the FIFO sizing registers). The sizing of these FIFOs will affect the largest supported MPS of endpoints using that FIFO. For convenience, the HCD provides the `hcd_port_fifo_bias_t` enum that will set the FIFO sizes for you but biased towards a particular use case. 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`. + - The FIFO sizes will be set on port initialization (supplied in `hcd_port_config_t`) + - FIFOs can be resized after port reset using `hcd_port_set_fifo_bias()` but some restrictions apply (see API description). ## HCD Pipes -- URBs can be enqueued into a pipe. Pipes use a linked list internally, so there is in-theory no limit to the number of URBs that can be enqueued. +- 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. +- URBs can be enqueued into a pipe. Pipes use a linked list internally, so there is (in theory) no limit to the number of URBs that can be enqueued. - URBs need to be dequeued once they are completed. - URBs need to have the transfer information (such as data buffer, transfer length in bytes) filled before they should be enqueued. -- URBs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify an URB object (and the URB's data buffer) until the URB is dequeued. +- URBs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify a URB object (and the URB's data buffer) until the URB is dequeued. - The URB is defined in `usb_private.h` instead of `hcd.h` so that the same structure can shared throughout the entire Host stack. Each layer simply needs to pass the pointer of the URB to the next layer thus minimizing the amount of copying required. ## HCD SW Arch The software arch for the HCD has the following properties and intentions: -- 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. - Some static functions may be blocking. Those functions will have a note in their descriptions. - 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. -- 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: +- The port and each pipe have to be treated as completely separate entities (with their own handles and events). This allows clients to group these entities however it sees fit. For example, the client can: - Let a hub driver manage the port, and manipulate the port via its port handle - Group pipes into interfaces and expose the interface as a single entity -- 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). -- 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. +- The HCD will not internally allocate any tasks. It is up to the client to decide how to distribute workload (e.g., a single while loop polling a port and all its pipes vs or each pipe having its own task). +- The HCD uses an interrupt to 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. ### Events @@ -112,4 +138,4 @@ The HCD API is thread safe however the following limitations should be noted: - Likewise, it is the client's responsibility to ensure that events and pipes are cleared before calling `hcd_port_deinit()`. - `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. - 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. -- 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. \ No newline at end of file +- Blocking HCD functions should not be called from critical sections and interrupts (e.g., `hcd_port_command()` and `hcd_pipe_command()`). \ No newline at end of file diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index d5f25c0e92..b1e24ae530 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -23,7 +15,7 @@ extern "C" { #include #include "esp_err.h" #include "usb_private.h" -#include "usb.h" +#include "usb/usb_types_ch9.h" // ------------------------------------------------- Macros & Types ---------------------------------------------------- @@ -53,18 +45,14 @@ typedef enum { * * Active: * - Pipe is able to transmit data. URBs can be enqueued. - * - Event if pipe has no URBs enqueued, it can still be in the active state. + * - Even if pipe has no URBs enqueued, it can still be in the active state. * Halted: * - An error has occurred on the pipe. URBs will no longer be executed. - * - Halt should be cleared using the clear command - * Invalid: - * - The underlying device that the pipe connects is not longer valid, thus making the pipe invalid. - * - Pending URBs should be dequeued and the pipe should be freed. + * - Halt should be cleared using the HCD_PIPE_CMD_CLEAR command */ typedef enum { HCD_PIPE_STATE_ACTIVE, /**< The pipe is active */ HCD_PIPE_STATE_HALTED, /**< The pipe is halted */ - HCD_PIPE_STATE_INVALID, /**< The pipe no longer exists and should be freed */ } hcd_pipe_state_t; // ----------------------- Events -------------------------- @@ -80,20 +68,16 @@ typedef enum { HCD_PORT_EVENT_DISCONNECTION, /**< A device disconnection has been detected */ HCD_PORT_EVENT_ERROR, /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY */ HCD_PORT_EVENT_OVERCURRENT, /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */ - HCD_PORT_EVENT_SUDDEN_DISCONN, /**< The port has suddenly disconnected (i.e., there was an enabled device connected - to the port when the disconnection occurred. Port is now HCD_PORT_STATE_RECOVERY. */ } hcd_port_event_t; /** * @brief HCD pipe events * * @note Pipe error events will put the pipe into the HCD_PIPE_STATE_HALTED state - * @note The HCD_PIPE_EVENT_INVALID will put the pipe in the HCD_PIPE_STATE_INVALID state */ typedef enum { HCD_PIPE_EVENT_NONE, /**< The pipe has no events (used to indicate no events when polling) */ HCD_PIPE_EVENT_URB_DONE, /**< The pipe has completed an URB. The URB can be dequeued */ - HCD_PIPE_EVENT_INVALID, /**< The pipe is invalid because the underlying device is no longer valid */ HCD_PIPE_EVENT_ERROR_XFER, /**< Excessive (three consecutive) transaction errors (e.g., no ACK, bad CRC etc) */ HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL, /**< URB was not available */ HCD_PIPE_EVENT_ERROR_OVERFLOW, /**< Received more data than requested. Usually a Packet babble error @@ -111,9 +95,9 @@ typedef enum { HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port. If the port is enabled, this will cause a HCD_PORT_EVENT_SUDDEN_DISCONN event. If the port is disabled, this will cause a HCD_PORT_EVENT_DISCONNECTION event. */ HCD_PORT_CMD_RESET, /**< Issue a reset on the port */ - HCD_PORT_CMD_SUSPEND, /**< Suspend the port */ + HCD_PORT_CMD_SUSPEND, /**< Suspend the port. All pipes must be halted */ HCD_PORT_CMD_RESUME, /**< Resume the port */ - HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive). Any created pipes will receive a HCD_PIPE_EVENT_INVALID event */ + HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive). All pipes must be halted. */ } hcd_port_cmd_t; /** @@ -122,10 +106,9 @@ typedef enum { * The pipe commands represent the list of pipe manipulations outlined in 10.5.2.2. of USB2.0 specification. */ typedef enum { - HCD_PIPE_CMD_ABORT, /**< Retire all scheduled URBs. Pipe's state remains unchanged */ - HCD_PIPE_CMD_RESET, /**< Retire all scheduled URBs. Pipe's state moves to active */ - HCD_PIPE_CMD_CLEAR, /**< Pipe's state moves from halted to active */ - HCD_PIPE_CMD_HALT /**< Pipe's state moves to halted */ + HCD_PIPE_CMD_HALT, /**< Halt an active pipe. The currently executing URB will be canceled. Enqueued URBs are left untouched */ + HCD_PIPE_CMD_FLUSH, /**< Can only be called when halted. Will cause all enqueued URBs to be canceled */ + HCD_PIPE_CMD_CLEAR, /**< Causes a halted pipe to become active again. Any enqueued URBs will being executing.*/ } hcd_pipe_cmd_t; // -------------------- Object Types ----------------------- @@ -171,6 +154,7 @@ typedef struct { * @brief Port configuration structure */ typedef struct { + hcd_port_fifo_bias_t fifo_bias; /**< HCD port internal FIFO biasing */ hcd_port_callback_t callback; /**< HCD port event callback */ void *callback_arg; /**< User argument for HCD port callback */ void *context; /**< Context variable used to associate the port with upper layer object */ @@ -185,7 +169,7 @@ typedef struct { hcd_pipe_callback_t callback; /**< HCD pipe event ISR callback */ void *callback_arg; /**< User argument for HCD pipe callback */ void *context; /**< Context variable used to associate the pipe with upper layer object */ - const usb_desc_ep_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */ + const usb_ep_desc_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */ usb_speed_t dev_speed; /**< Speed of the device */ uint8_t dev_addr; /**< Device address of the pipe */ } hcd_pipe_config_t; @@ -257,11 +241,12 @@ esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl); * * Call this function to manipulate a port (e.g., powering it ON, sending a reset etc). The following conditions * must be met when calling this function: - * - The port is in the correct state for the command (e.g., port must be suspend in order to use the resume command) + * - The port is in the correct state for the command (e.g., port must be suspended in order to use the resume command) * - The port does not have any pending events * * @note This function is internally protected by a mutex. If multiple threads call this function, this function will * can block. + * @note The function can block * @note For some of the commands that involve a blocking delay (e.g., reset and resume), if the port's state changes * unexpectedly (e.g., a disconnect during a resume), this function will return ESP_ERR_INVALID_RESPONSE. * @@ -358,8 +343,7 @@ esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_ * When allocating a pipe, the HCD will assess whether there are sufficient resources (i.e., bus time, and controller * channels). If sufficient, the pipe will be allocated. * - * @note Currently, Interrupt and Isochronous pipes are not supported yet - * @note The host port must be in the enabled state before a pipe can be allcoated + * @note The host port must be in the enabled state before a pipe can be allocated * * @param[in] port_hdl Handle of the port this pipe will be routed through * @param[in] pipe_config Pipe configuration @@ -392,9 +376,9 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl); * * This function is intended to be called on default pipes during enumeration in order to update the pipe's maximum * packet size. This function can only be called on a pipe that has met the following conditions: - * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state) - * - Pipe is not currently processing a command - * - All URBs have been dequeued from the pipe + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting * * @param pipe_hdl Pipe handle * @param mps New Maximum Packet Size @@ -409,9 +393,9 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); * * This function is intended to be called on default pipes during enumeration in order to update the pipe's device * address. This function can only be called on a pipe that has met the following conditions: - * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state) - * - Pipe is not currently processing a command - * - All URBs have been dequeued from the pipe + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting * * @param pipe_hdl Pipe handle * @param dev_addr New device address @@ -421,6 +405,22 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); */ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr); +/** + * @brief Update a pipe's callback + * + * This function is intended to be called on default pipes at the end of enumeration to switch to a callback that + * handles the completion of regular control transfer. + * - Pipe is not current processing a command + * - Pipe does not have any enqueued URBs + * - Port cannot be resetting + * + * @param pipe_hdl Pipe handle + * @param callback Callback + * @param user_arg Callback argument + * @return esp_err_t + */ +esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg); + /** * @brief Make a pipe persist through a run time reset * @@ -454,19 +454,14 @@ hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl); /** * @brief Execute a command on a particular pipe * - * Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs etc). The following - * conditions must for a pipe command to be issued: - * - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID) - * - No other thread/task processing a command on the pipe concurrently (will return) + * Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs etc) * - * @note Some pipe commands will block until the pipe's current in-flight URB is complete. If the pipe's state - * changes unexpectedly, this function will return ESP_ERR_INVALID_RESPONSE + * @note This function can block * * @param pipe_hdl Pipe handle * @param command Pipe command * @retval ESP_OK: Command executed successfully * @retval ESP_ERR_INVALID_STATE: The pipe is not in the correct state/condition too execute the command - * @retval ESP_ERR_INVALID_RESPONSE: The pipe's state changed unexpectedly */ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command); @@ -490,6 +485,7 @@ hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl); * - The URB is properly initialized (data buffer and transfer length are set) * - The URB must not already be enqueued * - The pipe must be in the HCD_PIPE_STATE_ACTIVE state + * - The pipe cannot be executing a command * * @param pipe_hdl Pipe handle * @param urb URB to enqueue @@ -514,7 +510,7 @@ urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl); * @brief Abort an enqueued URB * * This function will attempt to abort an URB that is already enqueued. If the URB has yet to be executed, it will be - * "cancelled" and can then be dequeued. If the URB is currenty in-flight or has already completed, the URB will not be + * "canceled" and can then be dequeued. If the URB is currently in-flight or has already completed, the URB will not be * affected by this function. * * @param urb URB to abort diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h new file mode 100644 index 0000000000..fad62817b8 --- /dev/null +++ b/components/usb/private_include/hub.h @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "usb_private.h" +#include "usbh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ Types -------------------------------------------------------- + +/** + * @brief Hub driver configuration + */ +typedef struct { + usb_notif_cb_t notif_cb; /**< Notification callback */ + void *notif_cb_arg; /**< Notification callback argument */ +} hub_config_t; + +// ---------------------------------------------- Hub Driver Functions ------------------------------------------------- + +/** + * @brief Install Hub driver + * + * Entry: + * - USBH must already be installed + * Exit: + * - Install Hub driver memory resources + * - Initializes the HCD root port + * + * @param[in] hub_config Hub driver configuration + * @return esp_err_t + */ +esp_err_t hub_install(hub_config_t *hub_config); + +/** + * @brief Uninstall Hub driver + * + * This must be called before uninstalling the USBH + * Entry: + * - Must have stopped the root port + * Exit: + * - HCD root port deinitialized + * + * @return esp_err_t + */ +esp_err_t hub_uninstall(void); + +/** + * @brief Start the Hub driver's root port + * + * This will power the root port ON + * + * @return esp_err_t + */ +esp_err_t hub_root_start(void); + +/** + * @brief Stops the Hub driver's root port + * + * This will power OFF the root port + * + * @return esp_err_t + */ +esp_err_t hub_root_stop(void); + +/** + * @brief Hub driver's processing function + * + * Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the + * caller can block on the notification callback of source USB_NOTIF_SOURCE_HUB to run this function. + * + * @return esp_err_t + */ +esp_err_t hub_process(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/usb.h b/components/usb/private_include/usb.h deleted file mode 100644 index 7c4287ec6e..0000000000 --- a/components/usb/private_include/usb.h +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation -agnostic. In other words, this header is only meant to be used in the HCD layer and above of the USB Host stack. For -types and macros that are HW implementation specific (i.e., HAL layer and below), add them to the -"hal/usb_types_private.h" header instead. -*/ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define USB_CTRL_REQ_ATTR __attribute__((packed)) -#define USB_DESC_ATTR __attribute__((packed)) - -// ------------------------------------------------ Common USB Types --------------------------------------------------- - -// --------------------- Bus Related ----------------------- - -/** - * @brief USB Standard Speeds - */ -typedef enum { - USB_SPEED_LOW = 0, /**< USB Low Speed (1.5 Mbit/s) */ - USB_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */ -} usb_speed_t; - -// ------------------ Transfer Related --------------------- - -/** - * @brief The type of USB transfer - * - * @note The enum values need to match the bmAttributes field of an EP descriptor - */ -typedef enum { - USB_TRANSFER_TYPE_CTRL = 0, - USB_TRANSFER_TYPE_ISOCHRONOUS, - USB_TRANSFER_TYPE_BULK, - USB_TRANSFER_TYPE_INTR, -} usb_transfer_type_t; - -/** - * @brief The status of a particular transfer - */ -typedef enum { - USB_TRANSFER_STATUS_COMPLETED, /**< The transfer was successful (but may be short) */ - USB_TRANSFER_STATUS_ERROR, /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */ - USB_TRANSFER_STATUS_TIMED_OUT, /**< The transfer failed due to a time out */ - USB_TRANSFER_STATUS_CANCELED, /**< The transfer was canceled */ - USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */ - USB_TRANSFER_STATUS_NO_DEVICE, /**< The transfer failed because the device is no longer valid (e.g., disconnected */ - USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */ - USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */ -} usb_transfer_status_t; - - -#define USB_TRANSFER_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 */ - -/** - * @brief Isochronous packet descriptor - * - * If the number of bytes in an Isochronous transfer is larger than the MPS of the endpoint, the transfer is split - * into multiple packets transmitted at the endpoint's specified interval. An array of Isochronous packet descriptors - * describes how an Isochronous transfer should be split into multiple packets. - */ -typedef struct { - int num_bytes; /**< Number of bytes to transmit/receive in the packet */ - int actual_num_bytes; /**< Actual number of bytes transmitted/received in the packet */ - usb_transfer_status_t status; /**< Status of the packet */ -} usb_isoc_packet_desc_t; - -/** - * @brief USB transfer structure - * - * This structure is used to represent a transfer from a software client to an endopint over the USB bus. Some of the - * fields are made const on purpose as they are fixed on allocation. Users should call the appropriate USB Host Driver - * function to allocate a USB transfer structure instead of allocating this structure themselves. - * - * The transfer type is inferred from the endpoint this transfer is sent to. Depending on the transfer type, users - * should note the following: - * - * - Bulk: This structure represents a single bulk transfer. If the number of bytes exceeds the endpoint's MPS, the - * transfer will be split into multiple MPS sized packets followed by a short packet. - * - Control: This structure represents a single control transfer. This first 8 bytes of the data_buffer must be filled - * with the setup packet. The num_bytes field should exclude the size of the setup packet (i.e., set to 0 if - * the control transfer has no data stage). - * - Interrupt: Represents an interrupt transfer. If num_bytes exceeds the MPS of the endpoint, the transfer will be - * split into multiple packets, and each packet is transferred at the endpoint's specified interval. - * - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer - * is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval - * of the endpoint. - * - * @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS - * @note This structure should be allocated via __insert_func_name__() - * @note Once the transfer has be submitted, users should not modify the structure until the transfer has completed - */ -typedef struct usb_transfer_obj usb_transfer_t; - -/** - * @brief USB transfer completion callback - */ -typedef void (*usb_transfer_cb_t)(usb_transfer_t *transfer); - -struct usb_transfer_obj{ - uint8_t *const data_buffer; /**< Pointer to data buffer */ - const size_t data_buffer_size; /**< Size of the data buffer in bytes */ - int num_bytes; /**< Number of bytes to transfer. Control transfers should exclude size of setup packet. IN transfers should be integer multiple of MPS */ - int actual_num_bytes; /**< Actual number of bytes transferred */ - uint32_t flags; /**< Transfer flags */ - usb_transfer_status_t status; /**< Status of the transfer */ - uint32_t timeout; /**< Timeout (in milliseconds) of the packet (currently not supported yet) */ - usb_transfer_cb_t callback; /**< Transfer callback */ - void *context; /**< Context variable for transfer to associate transfer with something */ - const int num_isoc_packets; /**< Only relevant to Isochronous. Number of service periods (i.e., intervals) to transfer data buffer over. */ - usb_isoc_packet_desc_t isoc_packet_desc[0]; /**< Descriptors for each Isochronous packet */ -}; - - -// ---------------------------------------------------- Chapter 9 ------------------------------------------------------ - -/** - * @brief Descriptor types from USB2.0 specification table 9.5 - */ -#define USB_B_DESCRIPTOR_TYPE_DEVICE 0x01 -#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 0x02 -#define USB_B_DESCRIPTOR_TYPE_STRING 0x03 -#define USB_B_DESCRIPTOR_TYPE_INTERFACE 0x04 -#define USB_B_DESCRIPTOR_TYPE_ENDPOINT 0x05 -#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 0x06 -#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 0x07 -#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 0x08 - -/** - * @brief Descriptor types from USB 2.0 ECN - */ -#define USB_B_DESCRIPTOR_TYPE_OTG 0x09 -#define USB_B_DESCRIPTOR_TYPE_DEBUG 0x0a -#define USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION 0x0b - -/** - * @brief Descriptor types from Wireless USB spec - */ -#define USB_B_DESCRIPTOR_TYPE_SECURITY 0x0c -#define USB_B_DESCRIPTOR_TYPE_KEY 0x0d -#define USB_B_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 0x0e -#define USB_B_DESCRIPTOR_TYPE_BOS 0x0f -#define USB_B_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 0x10 -#define USB_B_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMP 0x11 -#define USB_B_DESCRIPTOR_TYPE_WIRE_ADAPTER 0x21 -#define USB_B_DESCRIPTOR_TYPE_RPIPE 0x22 -#define USB_B_DESCRIPTOR_TYPE_CS_RADIO_CONTROL 0x23 - -/** - * @brief Descriptor types from UAS specification - */ -#define USB_B_DESCRIPTOR_TYPE_PIPE_USAGE 0x24 - -// ------------------- Control Request --------------------- - -/** - * @brief Size of a USB control transfer setup packet in bytes - */ -#define USB_CTRL_REQ_SIZE 8 - -/** - * @brief Structure representing a USB control transfer setup packet - */ -typedef union { - struct { - uint8_t bRequestType; - uint8_t bRequest; - uint16_t wValue; - uint16_t wIndex; - uint16_t wLength; - } USB_CTRL_REQ_ATTR; - uint8_t val[USB_CTRL_REQ_SIZE]; -} usb_ctrl_req_t; -_Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_req_t incorrect"); - -/** - * @brief Bit masks belonging to the bRequestType field of a setup packet - */ -#define USB_B_REQUEST_TYPE_DIR_OUT (0X00 << 7) -#define USB_B_REQUEST_TYPE_DIR_IN (0x01 << 7) -#define USB_B_REQUEST_TYPE_TYPE_STANDARD (0x00 << 5) -#define USB_B_REQUEST_TYPE_TYPE_CLASS (0x01 << 5) -#define USB_B_REQUEST_TYPE_TYPE_VENDOR (0x02 << 5) -#define USB_B_REQUEST_TYPE_TYPE_RESERVED (0x03 << 5) -#define USB_B_REQUEST_TYPE_TYPE_MASK (0x03 << 5) -#define USB_B_REQUEST_TYPE_RECIP_DEVICE (0x00 << 0) -#define USB_B_REQUEST_TYPE_RECIP_INTERFACE (0x01 << 0) -#define USB_B_REQUEST_TYPE_RECIP_ENDPOINT (0x02 << 0) -#define USB_B_REQUEST_TYPE_RECIP_OTHER (0x03 << 0) -#define USB_B_REQUEST_TYPE_RECIP_MASK (0x1f << 0) - -/** - * @brief Bit masks belonging to the bRequest field of a setup packet - */ -#define USB_B_REQUEST_GET_STATUS 0x00 -#define USB_B_REQUEST_CLEAR_FEATURE 0x01 -#define USB_B_REQUEST_SET_FEATURE 0x03 -#define USB_B_REQUEST_SET_ADDRESS 0x05 -#define USB_B_REQUEST_GET_DESCRIPTOR 0x06 -#define USB_B_REQUEST_SET_DESCRIPTOR 0x07 -#define USB_B_REQUEST_GET_CONFIGURATION 0x08 -#define USB_B_REQUEST_SET_CONFIGURATION 0x09 -#define USB_B_REQUEST_GET_INTERFACE 0x0A -#define USB_B_REQUEST_SET_INTERFACE 0x0B -#define USB_B_REQUEST_SYNCH_FRAME 0x0C - -/** - * @brief Bit masks belonging to the wValue field of a setup packet - */ -#define USB_W_VALUE_DT_DEVICE 0x01 -#define USB_W_VALUE_DT_CONFIG 0x02 -#define USB_W_VALUE_DT_STRING 0x03 -#define USB_W_VALUE_DT_INTERFACE 0x04 -#define USB_W_VALUE_DT_ENDPOINT 0x05 -#define USB_W_VALUE_DT_DEVICE_QUALIFIER 0x06 -#define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 -#define USB_W_VALUE_DT_INTERFACE_POWER 0x08 - -/** - * @brief Initializer for a SET_ADDRESS request - * - * Sets the address of a connected device - */ -#define USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req_ptr, addr) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD |USB_B_REQUEST_TYPE_RECIP_DEVICE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS; \ - (ctrl_req_ptr)->wValue = (addr); \ - (ctrl_req_ptr)->wIndex = 0; \ - (ctrl_req_ptr)->wLength = 0; \ -}) - -/** - * @brief Initializer for a request to get a device's device descriptor - */ -#define USB_CTRL_REQ_INIT_GET_DEVICE_DESC(ctrl_req_ptr) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ - (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_DEVICE << 8); \ - (ctrl_req_ptr)->wIndex = 0; \ - (ctrl_req_ptr)->wLength = 18; \ -}) - -/** - * @brief Initializer for a request to get a device's current configuration number - */ -#define USB_CTRL_REQ_INIT_GET_CONFIG(ctrl_req_ptr) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_CONFIGURATION; \ - (ctrl_req_ptr)->wValue = 0; \ - (ctrl_req_ptr)->wIndex = 0; \ - (ctrl_req_ptr)->wLength = 1; \ -}) - -/** - * @brief Initializer for a request to get one of the device's current configuration descriptor - * - * - desc_index indicates the configuration's index number - * - Number of bytes of the configuration descriptor to get - */ -#define USB_CTRL_REQ_INIT_GET_CONFIG_DESC(ctrl_req_ptr, desc_index, desc_len) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ - (ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_CONFIG << 8) | ((desc_index) & 0xFF); \ - (ctrl_req_ptr)->wIndex = 0; \ - (ctrl_req_ptr)->wLength = (desc_len); \ -}) - -/** - * @brief Initializer for a request to set a device's current configuration number - */ -#define USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req_ptr, config_num) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION; \ - (ctrl_req_ptr)->wValue = (config_num); \ - (ctrl_req_ptr)->wIndex = 0; \ - (ctrl_req_ptr)->wLength = 0; \ -}) - -/** - * @brief Initializer for a request to set an interface's alternate setting - */ -#define USB_CTRL_REQ_INIT_SET_INTERFACE(ctrl_req_ptr, intf_num, alt_setting_num) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \ - (ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \ - (ctrl_req_ptr)->wValue = (alt_setting_num); \ - (ctrl_req_ptr)->wIndex = (intf_num); \ - (ctrl_req_ptr)->wLength = 0; \ -}) - -// ---------------- Standard Descriptor -------------------- - -/** - * @brief Size of dummy USB standard descriptor - */ -#define USB_DESC_STANDARD_SIZE 2 - -/** - * @brief Dummy USB standard descriptor - * - * All USB standard descriptors start with these two bytes. Use this type traversing over descriptors - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_STANDARD_SIZE]; -} usb_desc_standard_t; -_Static_assert(sizeof(usb_desc_standard_t) == USB_DESC_STANDARD_SIZE, "Size of usb_desc_standard_t incorrect"); - -// ------------------ Device Descriptor -------------------- - -/** - * @brief Size of a USB device descriptor in bytes - */ -#define USB_DESC_DEVICE_SIZE 18 - -/** - * @brief Structure representing a USB device descriptor - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t bcdUSB; - uint8_t bDeviceClass; - uint8_t bDeviceSubClass; - uint8_t bDeviceProtocol; - uint8_t bMaxPacketSize0; - uint16_t idVendor; - uint16_t idProduct; - uint16_t bcdDevice; - uint8_t iManufacturer; - uint8_t iProduct; - uint8_t iSerialNumber; - uint8_t bNumConfigurations; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_DEVICE_SIZE]; -} usb_desc_device_t; -_Static_assert(sizeof(usb_desc_device_t) == USB_DESC_DEVICE_SIZE, "Size of usb_desc_device_t incorrect"); - -/** - * @brief Possible base class values of the bDeviceClass field of a USB device descriptor - */ -#define USB_CLASS_PER_INTERFACE 0x00 -#define USB_CLASS_AUDIO 0x01 -#define USB_CLASS_COMM 0x02 -#define USB_CLASS_HID 0x03 -#define USB_CLASS_PHYSICAL 0x05 -#define USB_CLASS_STILL_IMAGE 0x06 -#define USB_CLASS_PRINTER 0x07 -#define USB_CLASS_MASS_STORAGE 0x08 -#define USB_CLASS_HUB 0x09 -#define USB_CLASS_CDC_DATA 0x0a -#define USB_CLASS_CSCID 0x0b -#define USB_CLASS_CONTENT_SEC 0x0d -#define USB_CLASS_VIDEO 0x0e -#define USB_CLASS_WIRELESS_CONTROLLER 0xe0 -#define USB_CLASS_PERSONAL_HEALTHCARE 0x0f -#define USB_CLASS_AUDIO_VIDEO 0x10 -#define USB_CLASS_BILLBOARD 0x11 -#define USB_CLASS_USB_TYPE_C_BRIDGE 0x12 -#define USB_CLASS_MISC 0xef -#define USB_CLASS_APP_SPEC 0xfe -#define USB_CLASS_VENDOR_SPEC 0xff - -/** - * @brief Vendor specific subclass code - */ -#define USB_SUBCLASS_VENDOR_SPEC 0xff - -// -------------- Configuration Descriptor ----------------- - -/** - * @brief Size of a short USB configuration descriptor in bytes - * - * @note The size of a full USB configuration includes all the interface and endpoint - * descriptors of that configuration. - */ -#define USB_DESC_CONFIG_SIZE 9 - -/** - * @brief Structure representing a short USB configuration descriptor - * - * @note The full USB configuration includes all the interface and endpoint - * descriptors of that configuration. - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t wTotalLength; - uint8_t bNumInterfaces; - uint8_t bConfigurationValue; - uint8_t iConfiguration; - uint8_t bmAttributes; - uint8_t bMaxPower; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_CONFIG_SIZE]; -} usb_desc_config_t; -_Static_assert(sizeof(usb_desc_config_t) == USB_DESC_CONFIG_SIZE, "Size of usb_desc_config_t incorrect"); - -/** - * @brief Bit masks belonging to the bmAttributes field of a configuration descriptor - */ -#define USB_BM_ATTRIBUTES_ONE (1 << 7) //Must be set -#define USB_BM_ATTRIBUTES_SELFPOWER (1 << 6) //Self powered -#define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up -#define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered - -// ---------- Interface Association Descriptor ------------- - -/** - * @brief Size of a USB interface association descriptor in bytes - */ -#define USB_DESC_INTF_ASSOC_SIZE 9 - -/** - * @brief Structure representing a USB interface association descriptor - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bFirstInterface; - uint8_t bInterfaceCount; - uint8_t bFunctionClass; - uint8_t bFunctionSubClass; - uint8_t bFunctionProtocol; - uint8_t iFunction; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_INTF_ASSOC_SIZE]; -} usb_desc_iad_t; -_Static_assert(sizeof(usb_desc_iad_t) == USB_DESC_INTF_ASSOC_SIZE, "Size of usb_desc_iad_t incorrect"); - -// ---------------- Interface Descriptor ------------------- - -/** - * @brief Size of a USB interface descriptor in bytes - */ -#define USB_DESC_INTF_SIZE 9 - -/** - * @brief Structure representing a USB interface descriptor - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bInterfaceNumber; - uint8_t bAlternateSetting; - uint8_t bNumEndpoints; - uint8_t bInterfaceClass; - uint8_t bInterfaceSubClass; - uint8_t bInterfaceProtocol; - uint8_t iInterface; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_INTF_SIZE]; -} usb_desc_intf_t; -_Static_assert(sizeof(usb_desc_intf_t) == USB_DESC_INTF_SIZE, "Size of usb_desc_intf_t incorrect"); - -// ----------------- Endpoint Descriptor ------------------- - -/** - * @brief Size of a USB endpoint descriptor in bytes - */ -#define USB_DESC_EP_SIZE 7 - -/** - * @brief Structure representing a USB endpoint descriptor - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint8_t bEndpointAddress; - uint8_t bmAttributes; - uint16_t wMaxPacketSize; - uint8_t bInterval; - } USB_DESC_ATTR; - uint8_t val[USB_DESC_EP_SIZE]; -} usb_desc_ep_t; -_Static_assert(sizeof(usb_desc_ep_t) == USB_DESC_EP_SIZE, "Size of usb_desc_ep_t incorrect"); - -/** - * @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor - */ -#define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK 0x0f -#define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80 - -/** - * @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor - */ -#define USB_BM_ATTRIBUTES_XFERTYPE_MASK 0x03 -#define USB_BM_ATTRIBUTES_XFER_CONTROL (0 << 0) -#define USB_BM_ATTRIBUTES_XFER_ISOC (1 << 0) -#define USB_BM_ATTRIBUTES_XFER_BULK (2 << 0) -#define USB_BM_ATTRIBUTES_XFER_INT (3 << 0) -#define USB_BM_ATTRIBUTES_SYNCTYPE_MASK 0x0C /* in bmAttributes */ -#define USB_BM_ATTRIBUTES_SYNC_NONE (0 << 2) -#define USB_BM_ATTRIBUTES_SYNC_ASYNC (1 << 2) -#define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE (2 << 2) -#define USB_BM_ATTRIBUTES_SYNC_SYNC (3 << 2) -#define USB_BM_ATTRIBUTES_USAGETYPE_MASK 0x30 -#define USB_BM_ATTRIBUTES_USAGE_DATA (0 << 4) -#define USB_BM_ATTRIBUTES_USAGE_FEEDBACK (1 << 4) -#define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB (2 << 4) - -/** - * @brief Macro helpers to get information about an endpoint from its descriptor - */ -#define USB_DESC_EP_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK)) -#define USB_DESC_EP_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) -#define USB_DESC_EP_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0) -#define USB_DESC_EP_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF) - -// ------------------ String Descriptor -------------------- - -/** - * @brief Size of a short USB string descriptor in bytes - */ -#define USB_DESC_STR_SIZE 4 - -/** - * @brief Structure representing a USB string descriptor - */ -typedef union { - struct { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t wData[1]; /* UTF-16LE encoded */ - } USB_DESC_ATTR; - uint8_t val[USB_DESC_STR_SIZE]; -} usb_desc_str_t; -_Static_assert(sizeof(usb_desc_str_t) == USB_DESC_STR_SIZE, "Size of usb_desc_str_t incorrect"); - -#ifdef __cplusplus -} -#endif diff --git a/components/usb/private_include/usb_private.h b/components/usb/private_include/usb_private.h index f3b0eaac38..b245970b54 100644 --- a/components/usb/private_include/usb_private.h +++ b/components/usb/private_include/usb_private.h @@ -1,34 +1,32 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once #include +#include #include #include -#include "usb.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" #ifdef __cplusplus extern "C" { #endif +// ------------------------------------------------------ Types -------------------------------------------------------- + typedef struct { uint8_t *data_buffer; size_t data_buffer_size; int num_bytes; int actual_num_bytes; uint32_t flags; + usb_device_handle_t device_handle; + uint8_t bEndpointAddress; usb_transfer_status_t status; uint32_t timeout; usb_transfer_cb_t callback; @@ -38,17 +36,49 @@ typedef struct { } usb_transfer_dummy_t; _Static_assert(sizeof(usb_transfer_dummy_t) == sizeof(usb_transfer_t), "usb_transfer_dummy_t does not match usb_transfer_t"); -struct urb_obj{ - TAILQ_ENTRY(urb_obj) tailq_entry; - //HCD context pointer and variables. Must be initialized to NULL and 0 respectively +struct urb_s{ + TAILQ_ENTRY(urb_s) tailq_entry; + //HCD handler pointer and variables. Must be initialized to NULL and 0 respectively void *hcd_ptr; uint32_t hcd_var; - //Host Driver layer will add its fields here. + //Host Driver layer handler + void *usb_host_client; //Currently only used when submitted to shared pipes (i.e., Device default pipes) + size_t usb_host_header_size; //USB Host may need the data buffer to have a transparent header //Public transfer structure. Must be last due to variable length array usb_transfer_t transfer; }; +typedef struct urb_s urb_t; -typedef struct urb_obj urb_t; +typedef enum { + USB_NOTIF_SOURCE_USBH = 0x01, + USB_NOTIF_SOURCE_HUB = 0x02, +} usb_notif_source_t; + +typedef bool (*usb_notif_cb_t)(usb_notif_source_t source, bool in_isr, void *context); + +// --------------------------------------------------- Allocation ------------------------------------------------------ + +/** + * @brief Allocate a URB + * + * - Data buffer is allocated in DMA capable memory + * - The constant fields of the URB are also set + * - The data_buffer field of the URB is set to point to start of the allocated data buffer AFTER the header. To access + * the header, users need a negative offset from data_buffer. + * + * @param data_buffer_size Size of the URB's data buffer + * @param header_size Size of header to put in front of URB's data buffer + * @param num_isoc_packets Number of isochronous packet descriptors + * @return urb_t* URB object + */ +urb_t *urb_alloc(size_t data_buffer_size, size_t header_size, int num_isoc_packets); + +/** + * @brief Free a URB + * + * @param urb URB object + */ +void urb_free(urb_t *urb); #ifdef __cplusplus } diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h new file mode 100644 index 0000000000..924646fc48 --- /dev/null +++ b/components/usb/private_include/usbh.h @@ -0,0 +1,383 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hcd.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ Types -------------------------------------------------------- + +// ----------------------- Events -------------------------- + +typedef enum { + USBH_EVENT_DEV_NEW, /**< A new device has been enumerated and added to the device pool */ + USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ + USBH_EVENT_DEV_ALL_FREE, /**< All devices have been freed */ +} usbh_event_t; + +typedef enum { + USBH_HUB_EVENT_CLEANUP_PORT, /**< Indicate to the Hub driver that it should clean up the port of a device (occurs after a gone device has been freed) */ + USBH_HUB_EVENT_DISABLE_PORT, /**< Indicate to the Hub driver that it should disable the port of a device (occurs after a device has been freed) */ +} usbh_hub_event_t; + +// ---------------------- Callbacks ------------------------ + +/** + * @brief Callback used to indicate completion of control transfers submitted usbh_dev_submit_ctrl_urb() + * @note This callback is called from within usbh_process() + */ +typedef void (*usbh_ctrl_xfer_cb_t)(usb_device_handle_t dev_hdl, urb_t *urb, void *arg); + +/** + * @brief Callback used to indicate that the USBH has an event + * + * @note This callback is called from within usbh_process() + * @note On a USBH_EVENT_DEV_ALL_FREE event, the dev_hdl argument is set to NULL + */ +typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg); + +/** + * @brief Callback used by the USBH to request actions from the Hub driver + * @note This callback is called from within usbh_process() + */ +typedef void (*usbh_hub_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg); + +// ----------------------- Objects ------------------------- + +/** + * @brief Configuration for an endpoint being allocated using usbh_ep_alloc() + */ +typedef struct { + const usb_ep_desc_t *ep_desc; /**< Endpoint descriptor */ + hcd_pipe_callback_t pipe_cb; /**< Endpoint's pipe callback */ + void *pipe_cb_arg; /**< Pipe callback argument */ + void *context; /**< Pipe context */ +} usbh_ep_config_t; + +/** + * @brief USBH configuration used in usbh_install() + */ +typedef struct { + usb_notif_cb_t notif_cb; /**< Notification callback */ + void *notif_cb_arg; /**< Notification callback argument */ + usbh_ctrl_xfer_cb_t ctrl_xfer_cb; /**< Control transfer callback */ + void *ctrl_xfer_cb_arg; /**< Control transfer callback argument */ + usbh_event_cb_t event_cb; /**< USBH event callback */ + void *event_cb_arg; /**< USBH event callback argument */ + hcd_config_t hcd_config; /**< HCD configuration */ +} usbh_config_t; + +// ------------------------------------------------- USBH Functions ---------------------------------------------------- + +/** + * @brief Installs the USBH driver + * + * - This function will internally install the HCD + * - This must be called before calling any Hub driver functions + * + * @param usbh_config USBH driver configuration + * @return esp_err_t + */ +esp_err_t usbh_install(const usbh_config_t *usbh_config); + +/** + * @brief Uninstall the USBH driver + * + * - This function will uninstall the HCD + * - The Hub driver must be uninstalled before calling this function + * + * @return esp_err_t + */ +esp_err_t usbh_uninstall(void); + +/** + * @brief USBH processing function + * + * - USBH processing function that must be called repeatedly to process USBH events + * - If blocking, the caller can block until a USB_NOTIF_SOURCE_USBH notification is received before running this + * function + * + * @return esp_err_t + */ +esp_err_t usbh_process(void); + +// ------------------------------------------------ Device Functions --------------------------------------------------- + +// --------------------- Device Pool ----------------------- + +/** + * @brief Open a device by address + * + * A device must be opened before it can be used + * + * @param[in] dev_addr Device address + * @param[out] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); + +/** + * @brief CLose a device + * + * Device can be opened by calling usbh_dev_open() + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl); + +/** + * @brief Mark that all devices should be freed at the next possible opportunity + * + * A device marked as free will not be freed until the last client using the device has called usbh_dev_close() + * + * @return esp_err_t + */ +esp_err_t usbh_dev_mark_all_free(void); + +// ------------------- Single Device ---------------------- + +/** + * @brief Get a device's address + * + * @note Can be called without opening the device + * + * @param[in] dev_hdl Device handle + * @param[out] dev_addr Device's address + * @return esp_err_t + */ +esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr); + +/** + * @brief Get a device's information + * + * @param[in] dev_hdl Device handle + * @param[out] dev_info Device information + * @return esp_err_t + */ +esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info); + +/** + * @brief Get a device's device descriptor + * + * - The device descriptor is cached when the device is created by the Hub driver + * + * @param[in] dev_hdl Device handle + * @param[out] dev_desc_ret Device descriptor + * @return esp_err_t + */ +esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret); + +/** + * @brief Get a device's active configuration descriptor + * + * Simply returns a reference to the internally cached configuration descriptor + * + * @param[in] dev_hdl Device handle + * @param config_desc_ret + * @return esp_err_t + */ +esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); + +/** + * @brief Submit a control transfer (URB) to a device + * + * @param[in] dev_hdl Device handle + * @param[in] urb URB + * @return esp_err_t + */ +esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb); + +// ----------------------------------------------- Endpoint Functions ------------------------------------------------- + +/** + * @brief Allocate an endpoint on a device + * + * Clients that have opened a device must call this function to allocate all endpoints in an interface that is claimed. + * The pipe handle of the endpoint is returned so that clients can use and control the pipe directly. + * + * @note Default pipes are owned by the USBH. For control transfers, use usbh_dev_submit_ctrl_urb() instead + * @note Device must be opened by the client first + * + * @param[in] dev_hdl Device handle + * @param[in] ep_config + * @param[out] pipe_hdl_ret Pipe handle + * @return esp_err_t + */ +esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret); + +/** + * @brief Free and endpoint on a device + * + * Free an endpoint previously opened by usbh_ep_alloc() + * + * @param[in] dev_hdl Device handle + * @param[in] bEndpointAddress Endpoint's address + * @return esp_err_t + */ +esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress); + +/** + * @brief Get the context of an endpoint + * + * Get the context variable assigned to and endpoint on allocation. + * + * @param[in] dev_hdl Device handle + * @param[in] bEndpointAddress Endpoint's address + * @param[out] context_ret Context variable + * @return esp_err_t + */ +esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret); + +// -------------------------------------------------- Hub Functions ---------------------------------------------------- + +// ------------------- Device Related ---------------------- + +/** + * @brief Indicates to USBH that the Hub driver is installed + * + * - The Hub driver must call this function in its installation to indicate the the USBH that it has been installed. + * - This should only be called after the USBH has already be installed + * + * @note Hub Driver only + * @param[in] hub_callback Hub callback + * @param[in] callback_arg Callback argument + * @return esp_err_t + */ +esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg); + +/** + * @brief Indicates to USBH the start of enumeration for a device + * + * - The Hub driver calls this function before it starts enumerating a new device. + * - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration + * functions. + * - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration. + * + * @note Hub Driver only + * @param[in] port_hdl Handle of the port that the device is connected to + * @param[in] dev_speed Device's speed + * @param[out] new_dev_hdl Device's handle + * @param[out] default_pipe_hdl Device's default pipe handle + * @return esp_err_t + */ +esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); + +/** + * @brief Indicate that a device is gone + * + * This Hub driver must call this function to indicate that a device is gone. A device is gone when: + * - It suddenly disconnects + * - Its upstream port or device has an error or is also gone. + * Marking a device as gone will: + * - Trigger a USBH_EVENT_DEV_GONE + * - Prevent further transfers to the device + * - Trigger the device's cleanup if it is already closed + * - When the last client closes the device via usbh_dev_close(), the device's resources will be cleaned up + * + * @note Hub Driver only + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_mark_dev_gone(usb_device_handle_t dev_hdl); + +/** + * @brief Indicate that a device's port has been disabled + * + * - The Hub driver must call this function once it has disabled the port of a particular device + * - The Hub driver disables a device's port when requested by the USBH via the USBH_HUB_EVENT_DISABLE_PORT + * - This function will trigger the device's cleanup. + * + * @note Hub Driver only + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_dev_port_disabled(usb_device_handle_t dev_hdl); + +// ----------------- Enumeration Related ------------------- + +/** + * @brief Fill the enumerating device's descriptor + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param device_desc + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); + +/** + * @brief Assign the enumerating device's address + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param dev_addr + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); + +/** + * @brief Fill the enumerating device's active configuration descriptor + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param config_desc_full + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); + +/** + * @brief Assign the enumerating device's active configuration number + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param bConfigurationValue + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_config_num(usb_device_handle_t dev_hdl, uint8_t bConfigurationValue); + +/** + * @brief Indicate the device enumeration is completed + * + * This will all the device to be opened by clients, and also trigger a USBH_EVENT_DEV_NEW event. + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl); + +/** + * @brief Indicate that device enumeration has failed + * + * This will cause the enumerating device's resources to be cleaned up + * The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued. + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/test/CMakeLists.txt b/components/usb/test/CMakeLists.txt index b2b31a2bec..7857c7ee66 100644 --- a/components/usb/test/CMakeLists.txt +++ b/components/usb/test/CMakeLists.txt @@ -5,7 +5,8 @@ if(NOT "${target}" MATCHES "^esp32s[2-3]") return() endif() -idf_component_register(SRC_DIRS "common" "hcd" - PRIV_INCLUDE_DIRS "../private_include" "common" "hcd" +idf_component_register( + SRC_DIRS "common" "hcd" "usb_host" + PRIV_INCLUDE_DIRS "../private_include" "common" "hcd" "usb_host" PRIV_REQUIRES cmock usb test_utils ) diff --git a/components/usb/test/common/test_usb_mock_classes.c b/components/usb/test/common/test_usb_mock_classes.c index cadf33d9a8..f3d7d42d8e 100644 --- a/components/usb/test/common/test_usb_mock_classes.c +++ b/components/usb/test/common/test_usb_mock_classes.c @@ -15,7 +15,7 @@ #include #include #include -#include "usb.h" +#include "usb/usb_types_ch9.h" #include "test_usb_mock_classes.h" // ---------------------------------------------------- MSC SCSI ------------------------------------------------------- diff --git a/components/usb/test/common/test_usb_mock_classes.h b/components/usb/test/common/test_usb_mock_classes.h index 77352b3bcf..6c80b076c1 100644 --- a/components/usb/test/common/test_usb_mock_classes.h +++ b/components/usb/test/common/test_usb_mock_classes.h @@ -21,7 +21,7 @@ Host stack. #include #include -#include "usb.h" +#include "usb/usb_types_ch9.h" #ifdef __cplusplus extern "C" { @@ -94,8 +94,8 @@ Configuration Descriptor: If you're using a flash driver with different endpoints, modify the endpoint descriptors below. */ -static const usb_desc_ep_t mock_msc_scsi_bulk_out_ep_desc = { - .bLength = sizeof(usb_desc_ep_t), +static const usb_ep_desc_t mock_msc_scsi_bulk_out_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, .bEndpointAddress = 0x01, //EP 1 OUT .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, @@ -103,8 +103,8 @@ static const usb_desc_ep_t mock_msc_scsi_bulk_out_ep_desc = { .bInterval = 1, }; -static const usb_desc_ep_t mock_msc_scsi_bulk_in_ep_desc = { - .bLength = sizeof(usb_desc_ep_t), +static const usb_ep_desc_t mock_msc_scsi_bulk_in_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, .bEndpointAddress = 0x82, //EP 2 IN .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, @@ -123,12 +123,12 @@ static const usb_desc_ep_t mock_msc_scsi_bulk_in_ep_desc = { #define MOCK_MSC_SCSI_BULK_IN_EP_ADDR 0x82 #define MOCK_MSC_SCSI_BULK_EP_MPS 64 -#define MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \ - (ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_CLASS | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \ - (ctrl_req_ptr)->bRequest = 0xFF; \ - (ctrl_req_ptr)->wValue = 0; \ - (ctrl_req_ptr)->wIndex = (intf_num); \ - (ctrl_req_ptr)->wLength = 0; \ +#define MOCK_MSC_SCSI_REQ_INIT_RESET(setup_pkt_ptr, intf_num) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = 0xFF; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = (intf_num); \ + (setup_pkt_ptr)->wLength = 0; \ }) typedef struct __attribute__((packed)) { @@ -193,21 +193,67 @@ Note: The mock HID mouse tests require that USB low speed mouse be connected. Th - Be implement the HID with standard report format used by mice - It's configuration 1 should have the following endpoint -Endpoint Descriptor: - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x81 EP 1 IN - bmAttributes 3 - Transfer Type Interrupt - Synch Type None - Usage Type Data - wMaxPacketSize 0x0004 1x 4 bytes - bInterval 10 +Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 2.00 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x413c Dell Computer Corp. + idProduct 0x301a + bcdDevice 1.00 + iManufacturer 1 + iProduct 2 + iSerial 0 + bNumConfigurations 1 + Configuration Descriptor: + bLength 9 + bDescriptorType 2 + wTotalLength 0x0022 + bNumInterfaces 1 + bConfigurationValue 1 + iConfiguration 0 + bmAttributes 0xa0 + (Bus Powered) + Remote Wakeup + MaxPower 100mA + Interface Descriptor: + bLength 9 + bDescriptorType 4 + bInterfaceNumber 0 + bAlternateSetting 0 + bNumEndpoints 1 + bInterfaceClass 3 Human Interface Device + bInterfaceSubClass 1 Boot Interface Subclass + bInterfaceProtocol 2 Mouse + iInterface 0 + HID Device Descriptor: + bLength 9 + bDescriptorType 33 + bcdHID 1.11 + bCountryCode 0 Not supported + bNumDescriptors 1 + bDescriptorType 34 Report + wDescriptorLength 46 + Report Descriptors: + ** UNAVAILABLE ** + Endpoint Descriptor: + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x81 EP 1 IN + bmAttributes 3 + Transfer Type Interrupt + Synch Type None + Usage Type Data + wMaxPacketSize 0x0004 1x 4 bytes + bInterval 10 If you're using another mice with different endpoints, modify the endpoint descriptor below */ -static const usb_desc_ep_t mock_hid_mouse_in_ep_desc = { - .bLength = sizeof(usb_desc_ep_t), +static const usb_ep_desc_t mock_hid_mouse_in_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, .bEndpointAddress = 0x81, //EP 1 IN .bmAttributes = USB_BM_ATTRIBUTES_XFER_INT, @@ -215,6 +261,14 @@ static const usb_desc_ep_t mock_hid_mouse_in_ep_desc = { .bInterval = 10, //Interval of 10ms }; +#define MOCK_HID_MOUSE_DEV_ID_VENDOR 0x413C +#define MOCK_HID_MOUSE_DEV_ID_PRODUCT 0x301A +#define MOCK_HID_MOUSE_DEV_DFLT_EP_MPS 8 +#define MOCK_HID_MOUSE_INTF_NUMBER 0 +#define MOCK_HID_MOUSE_INTF_ALT_SETTING 0 +#define MOCK_HID_MOUSE_INTR_IN_EP_ADDR 0x81 +#define MOCK_HID_MOUSE_INTR_IN_MPS 0x04 + typedef union { struct { uint32_t left_button: 1; @@ -241,8 +295,8 @@ ISOC, transferring to a non-existent endpoint should work. The non-existent endp #define MOCK_ISOC_EP_MPS 512 -static const usb_desc_ep_t mock_isoc_out_ep_desc = { - .bLength = sizeof(usb_desc_ep_t), +static const usb_ep_desc_t mock_isoc_out_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, .bEndpointAddress = MOCK_ISOC_EP_NUM, .bmAttributes = USB_BM_ATTRIBUTES_XFER_ISOC, diff --git a/components/usb/test/hcd/test_hcd_bulk.c b/components/usb/test/hcd/test_hcd_bulk.c index aef531a046..e1bd189c27 100644 --- a/components/usb/test/hcd/test_hcd_bulk.c +++ b/components/usb/test/hcd/test_hcd_bulk.c @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include @@ -26,10 +18,10 @@ static void mock_msc_reset_req(hcd_pipe_handle_t default_pipe) { //Create URB - urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t)); - usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer; - MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req, MOCK_MSC_SCSI_INTF_NUMBER); - urb->transfer.num_bytes = 0; + urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t)); + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)urb->transfer.data_buffer; + MOCK_MSC_SCSI_REQ_INIT_RESET(setup_pkt, MOCK_MSC_SCSI_INTF_NUMBER); + urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //Enqueue, wait, dequeue, and check URB TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb)); test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); diff --git a/components/usb/test/hcd/test_hcd_common.c b/components/usb/test/hcd/test_hcd_common.c index 2bf815b47b..93a22d8fd9 100644 --- a/components/usb/test/hcd/test_hcd_common.c +++ b/components/usb/test/hcd/test_hcd_common.c @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include @@ -26,7 +18,7 @@ #include "soc/usb_wrap_struct.h" #include "hcd.h" #include "usb_private.h" -#include "usb.h" +#include "usb/usb_types_ch9.h" #include "test_hcd_common.h" #define PORT_NUM 1 @@ -174,6 +166,7 @@ hcd_port_handle_t test_hcd_setup(void) TEST_ASSERT_EQUAL(ESP_OK, hcd_install(&hcd_config)); //Initialize a port hcd_port_config_t port_config = { + .fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED, .callback = port_callback, .callback_arg = (void *)port_evt_queue, .context = (void *)port_evt_queue, @@ -237,7 +230,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled test_hcd_force_conn_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); - TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl)); + TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); //Power down the port TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl)); @@ -245,7 +238,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled // ---------------------------------------------- Pipe Setup/Tear-down ------------------------------------------------- -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) +hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed) { //Create a queue for pipe callback to queue up pipe events QueueHandle_t pipe_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(pipe_event_msg_t)); @@ -300,24 +293,24 @@ void test_hcd_free_urb(urb_t *urb) uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe) { //We need to create a URB for the enumeration control transfers - urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t) + 256); - usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer; + urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t) + 256); + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)urb->transfer.data_buffer; //Get the device descriptor (note that device might only return 8 bytes) - USB_CTRL_REQ_INIT_GET_DEVICE_DESC(ctrl_req); - urb->transfer.num_bytes = sizeof(usb_desc_device_t); + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC(setup_pkt); + urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t); TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb)); test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe)); TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status); //Update the MPS of the default pipe - usb_desc_device_t *device_desc = (usb_desc_device_t *)(urb->transfer.data_buffer + sizeof(usb_ctrl_req_t)); + usb_device_desc_t *device_desc = (usb_device_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, device_desc->bMaxPacketSize0)); //Send a set address request - USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req, ENUM_ADDR); //We only support one device for now so use address 1 - urb->transfer.num_bytes = 0; + USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt, ENUM_ADDR); //We only support one device for now so use address 1 + urb->transfer.num_bytes = sizeof(usb_setup_packet_t); TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb)); test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe)); @@ -327,8 +320,8 @@ uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe) TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR)); //Send a set configuration request - USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req, ENUM_CONFIG); - urb->transfer.num_bytes = 0; + USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt, ENUM_CONFIG); + urb->transfer.num_bytes = sizeof(usb_setup_packet_t); TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb)); test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe)); diff --git a/components/usb/test/hcd/test_hcd_common.h b/components/usb/test/hcd/test_hcd_common.h index 1d9dac14a2..493b95dc24 100644 --- a/components/usb/test/hcd/test_hcd_common.h +++ b/components/usb/test/hcd/test_hcd_common.h @@ -1,22 +1,14 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "hcd.h" #include "usb_private.h" -#include "usb.h" +#include "usb/usb_types_ch9.h" #define URB_CONTEXT_VAL ((void *)0xDEADBEEF) @@ -109,7 +101,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled * @param dev_speed Device speed of the pipe * @return hcd_pipe_handle_t Pipe handle */ -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); +hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed); /** * @brief Test the freeing of a pipe diff --git a/components/usb/test/hcd/test_hcd_ctrl.c b/components/usb/test/hcd/test_hcd_ctrl.c index fe29db4374..678de453ca 100644 --- a/components/usb/test/hcd/test_hcd_ctrl.c +++ b/components/usb/test/hcd/test_hcd_ctrl.c @@ -22,7 +22,7 @@ #define TEST_DEV_ADDR 0 #define NUM_URBS 3 #define TRANSFER_MAX_BYTES 256 -#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors +#define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors /* Test HCD control pipe URBs (normal completion and early abort) @@ -54,8 +54,8 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = URB_CONTEXT_VAL; } @@ -68,20 +68,20 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); } - //Dequeue URBs + //Dequeue URBs, check, and print for (int i = 0; i < NUM_URBS; i++) { urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status); TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType); + printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength); } - //Print config desc - for (int i = 0; i < urb_list[0]->transfer.actual_num_bytes; i++) { - printf("%d\t0x%x\n", i, urb_list[0]->transfer.data_buffer[sizeof(usb_ctrl_req_t) + i]); - } - - //Enqueue URBs again but abort them short after for (int i = 0; i < NUM_URBS; i++) { TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); @@ -98,8 +98,11 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]") //No need to check for URB pointer address as they may be out of order TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); } else { + //A failed transfer should 0 actual number of bytes transmitted TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); } TEST_ASSERT_EQUAL(urb->transfer.context, URB_CONTEXT_VAL); @@ -119,8 +122,8 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]") Test HCD control pipe STALL condition, abort, and clear Purpose: - - Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_HALTED event) - - The HCD_PIPE_CMD_ABORT can retire all URBs + - Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_ERROR_STALL event) + - The HCD_PIPE_CMD_FLUSH can retire all URBs - Pipe clear command can return the pipe to being active Procedure: @@ -128,7 +131,7 @@ Procedure: - Setup default pipe and allocate URBs - Corrupt the first URB so that it will trigger a STALL, then enqueue all the URBs - Check that a HCD_PIPE_EVENT_ERROR_STALL event is triggered - - Check that all URBs can be retired using HCD_PIPE_CMD_ABORT + - Check that all URBs can be retired using HCD_PIPE_CMD_FLUSH, a HCD_PIPE_EVENT_URB_DONE event should be generated - Check that the STALL can be cleared by using HCD_PIPE_CMD_CLEAR - Fix the corrupt first URB and retry the URBs - Dequeue URBs @@ -146,12 +149,12 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = URB_CONTEXT_VAL; } //Corrupt the first URB so that it triggers a STALL - ((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer)->bRequest = 0xAA; + ((usb_setup_packet_t *)urb_list[0]->transfer.data_buffer)->bRequest = 0xAA; //Enqueue URBs. A STALL should occur int num_enqueued = 0; @@ -168,14 +171,18 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]") TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); //Call the pipe abort command to retire all URBs then dequeue them all - TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_ABORT)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); for (int i = 0; i < num_enqueued; i++) { urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_STALL || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); } else { + //A failed transfer should 0 actual number of bytes transmitted TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); } TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context); @@ -187,7 +194,7 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]") printf("Retrying\n"); //Correct first URB then requeue - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[0]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); for (int i = 0; i < NUM_URBS; i++) { TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); } @@ -199,8 +206,13 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]") urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status); - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType); + printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength); } //Free URB list and pipe @@ -224,9 +236,10 @@ Purpose: Procedure: - Setup HCD and wait for connection - Setup default pipe and allocate URBs - - Enqqueue URBs but execute a HCD_PIPE_CMD_HALT command immediately after. Halt command should let on - the current going URB finish before actually halting the pipe. - - Un-halt the pipe a HCD_PIPE_CMD_HALT command. Enqueued URBs will be resumed + - Enqqueue URBs but execute a HCD_PIPE_CMD_HALT command immediately after. + - Halt command should immediately halt the current URB and generate a HCD_PIPE_EVENT_URB_DONE + - Other pending URBs should be untouched. + - Un-halt the pipe using a HCD_PIPE_CMD_CLEAR command. Enqueued URBs will be resumed - Check that all URBs have completed successfully - Dequeue URBs and teardown */ @@ -242,8 +255,8 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = URB_CONTEXT_VAL; } @@ -253,6 +266,7 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]") TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); } TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); printf("Pipe halted\n"); @@ -264,11 +278,20 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]") //Wait for each URB to be done, dequeue, and check results for (int i = 0; i < NUM_URBS; i++) { - test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); - TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status); - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); + if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType); + printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength); + } else { + //A failed transfer should 0 actual number of bytes transmitted + TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); + } TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context); } diff --git a/components/usb/test/hcd/test_hcd_intr.c b/components/usb/test/hcd/test_hcd_intr.c index b4de2042c5..1e6f01cbd4 100644 --- a/components/usb/test/hcd/test_hcd_intr.c +++ b/components/usb/test/hcd/test_hcd_intr.c @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "freertos/FreeRTOS.h" diff --git a/components/usb/test/hcd/test_hcd_isoc.c b/components/usb/test/hcd/test_hcd_isoc.c index d421d926ee..cb9dbec971 100644 --- a/components/usb/test/hcd/test_hcd_isoc.c +++ b/components/usb/test/hcd/test_hcd_isoc.c @@ -1,16 +1,8 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include @@ -65,7 +57,7 @@ TEST_CASE("Test HCD isochronous pipe URBs", "[hcd][ignore]") //Initialize URBs for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) { urb_list[urb_idx] = test_hcd_alloc_urb(NUM_PACKETS_PER_URB, URB_DATA_BUFF_SIZE); - urb_list[urb_idx]->transfer.num_bytes = 0; //num_bytes is not used for ISOC + urb_list[urb_idx]->transfer.num_bytes = URB_DATA_BUFF_SIZE; urb_list[urb_idx]->transfer.context = URB_CONTEXT_VAL; for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) { urb_list[urb_idx]->transfer.isoc_packet_desc[pkt_idx].num_bytes = ISOC_PACKET_SIZE; diff --git a/components/usb/test/hcd/test_hcd_port.c b/components/usb/test/hcd/test_hcd_port.c index 5d2c56f8ab..e7267de5f1 100644 --- a/components/usb/test/hcd/test_hcd_port.c +++ b/components/usb/test/hcd/test_hcd_port.c @@ -1,43 +1,40 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "unity.h" +#include "esp_rom_sys.h" #include "test_utils.h" #include "test_hcd_common.h" #define TEST_DEV_ADDR 0 #define NUM_URBS 3 #define TRANSFER_MAX_BYTES 256 -#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors +#define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors +#define POST_ENQUEUE_DELAY_US 10 /* Test a port sudden disconnect and port recovery Purpose: Test that when sudden disconnection happens on an HCD port, the port will - Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state - - Ongoing URBs and pipes are handled correctly + - Pipes can be halted and flushed after a port error Procedure: - Setup the HCD and a port - Trigger a port connection - Create a default pipe - - Start transfers but immediately trigger a disconnect - - Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle the event. - - Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue URBs + - Start transfers but trigger a disconnect after a short delay + - Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event. + - Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error. + - Check that the default pipe can be halted, a HCD_PIPE_EVENT_URB_DONE event should be generated + - Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated + - Check that all URBs can be dequeued. - Free default pipe - Recover the port - Trigger connection and disconnection again (to make sure the port works post recovery) @@ -56,8 +53,8 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = (void *)0xDEADBEEF; } @@ -66,29 +63,35 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); } + //Add a short delay to let the transfers run for a bit + esp_rom_delay_us(POST_ENQUEUE_DELAY_US); test_hcd_force_conn_state(false, 0); //Disconnect event should have occurred. Handle the event - test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN); - TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl)); + test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); + TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); printf("Sudden disconnect\n"); - //Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR) - int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe); - for (int i = 0; i < num_pipe_events - 1; i++) { - test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); - } - test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID); - TEST_ASSERT_EQUAL(hcd_pipe_get_state(default_pipe), HCD_PIPE_STATE_INVALID); + //We should be able to halt then flush the pipe + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); + printf("Pipe halted and flushed\n"); //Dequeue URBs for (int i = 0; i < NUM_URBS; i++) { urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); - TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE); + TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); } else { + //A failed transfer should 0 actual number of bytes transmitted TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); } TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context); @@ -113,17 +116,20 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") Test port suspend and resume with active pipes Purpose: - - Test port suspend and resume commands work correctly whilst there are active pipes with ongoing transfers - - When suspending, the pipes should be allowed to finish their current ongoing transfer before the bus is suspended. - - When resuming, pipes with pending transfer should be started after the bus is resumed. + - Test port suspend and resume procedure + - When suspending, the pipes should be halted before suspending the port. Any pending transfers should remain pending + - When resuming, the pipes should remain in the halted state + - Pipes on being cleared of the halt should resume transferring the pending transfers Procedure: - Setup the HCD and a port - Trigger a port connection - Create a default pipe - - Start transfers but suspend the port immediately + - Start transfers + - Halt the default pipe after a short delay + - Suspend the port - Resume the port - - Check that all the URBs have completed successfully + - Check that all the URBs have either completed successfully or been canceled by the pipe halt - Cleanup URBs and default pipe - Trigger disconnection and teardown */ @@ -139,8 +145,8 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = (void *)0xDEADBEEF; } @@ -149,6 +155,13 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); } + //Add a short delay to let the transfers run for a bit + esp_rom_delay_us(POST_ENQUEUE_DELAY_US); + //Halt the default pipe before suspending + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + //Suspend the port TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl)); printf("Suspended\n"); @@ -158,13 +171,23 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); printf("Resumed\n"); + //Clear the default pipe's halt + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed URBs to complete //Dequeue URBs for (int i = 0; i < NUM_URBS; i++) { urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); - TEST_ASSERT_EQUAL(urb->transfer.status, USB_TRANSFER_STATUS_COMPLETED); - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); + if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + } else { + //A failed transfer should 0 actual number of bytes transmitted + TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); + } TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context); } @@ -179,18 +202,20 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") } /* -Test HCD port disable with active pipes +Test HCD port disable and disconnection Purpose: - - Test that the port disable command works correctly with active pipes - - Pipes should be to finish their current ongoing transfer before port is disabled - - After disabling the port, all pipes should become invalid. + - Test that the port disable command works correctly + - Check that port can only be disabled when pipes have been halted + - Check that a disconnection after port disable still triggers a HCD_PORT_EVENT_DISCONNECTION event Procedure: - Setup HCD, a default pipe, and multiple URBs - - Start transfers but immediately disable the port - - Check pipe received invalid event - - Check that transfer are either done or not executed + - Start transfers + - Halt the default pipe after a short delay + - Check that port can be disabled + - Flush the default pipe and cleanup the default pipe + - Check that a disconnection still works after disable - Teardown */ TEST_CASE("Test HCD port disable", "[hcd][ignore]") @@ -205,8 +230,8 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = TRANSFER_MAX_BYTES; - USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); urb_list[i]->transfer.context = (void *)0xDEADBEEF; } @@ -215,25 +240,32 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]") for (int i = 0; i < NUM_URBS; i++) { TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); } + //Add a short delay to let the transfers run for a bit + esp_rom_delay_us(POST_ENQUEUE_DELAY_US); + //Halt the default pipe before suspending + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + + + //Check that port can be disabled TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl)); printf("Disabled\n"); - //Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR) - int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe); - for (int i = 0; i < num_pipe_events - 1; i++) { - test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); - } - test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID); - + //Flush pipe + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH)); //Dequeue URBs for (int i = 0; i < NUM_URBS; i++) { urb_t *urb = hcd_urb_dequeue(default_pipe); TEST_ASSERT_EQUAL(urb_list[i], urb); - TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE); + TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { - TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes); + //We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); } else { + //A failed transfer should 0 actual number of bytes transmitted TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); } TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context); @@ -244,7 +276,8 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]") test_hcd_free_urb(urb_list[i]); } test_hcd_pipe_free(default_pipe); - //Cleanup + + //Trigger a disconnection and cleanup test_hcd_wait_for_disconn(port_hdl, true); test_hcd_teardown(port_hdl); } @@ -296,8 +329,8 @@ TEST_CASE("Test HCD port command bailout", "[hcd][ignore]") TEST_ASSERT_EQUAL(ESP_ERR_INVALID_RESPONSE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); //Check that concurrent task triggered a sudden disconnection - test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN); - TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl)); + test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); + TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); //Cleanup task and semaphore diff --git a/components/usb/test/usb_host/ctrl_client.h b/components/usb/test/usb_host/ctrl_client.h new file mode 100644 index 0000000000..4cfeccc53d --- /dev/null +++ b/components/usb/test/usb_host/ctrl_client.h @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +typedef struct { + int num_ctrl_xfer_to_send; + uint16_t idVendor; + uint16_t idProduct; +} ctrl_client_test_param_t; + +void ctrl_client_async_seq_task(void *arg); diff --git a/components/usb/test/usb_host/ctrl_client_async_seq.c b/components/usb/test/usb_host/ctrl_client_async_seq.c new file mode 100644 index 0000000000..85751adeb6 --- /dev/null +++ b/components/usb/test/usb_host/ctrl_client_async_seq.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "ctrl_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +/* +Implementation of a control transfer client used for USB Host Tests. + +- Implemented using sequential call patterns, meaning: + - The entire client is contained within a single task + - All API calls and callbacks are run sequentially + - No critical sections required since everything is sequential +- The control transfer client will: + - Register itself as a client + - Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device + - Allocate multiple transfer objects + - Submit a number of control transfers (get configuration descriptor requests) + - Free transfer objects + - Close the device + - Deregister control client +*/ + +#define MAX(x,y) (((x) >= (y)) ? (x) : (y)) +#define CTRL_CLIENT_MAX_EVENT_MSGS 5 +#define NUM_TRANSFER_OBJ 3 +#define MAX_TRANSFER_BYTES 256 + +const char *CTRL_CLIENT_TAG = "Ctrl Client"; + +typedef enum { + TEST_STAGE_WAIT_CONN, + TEST_STAGE_DEV_OPEN, + TEST_STAGE_CTRL_XFER, + TEST_STAGE_CTRL_XFER_WAIT, + TEST_STAGE_DEV_CLOSE, +} test_stage_t; + +typedef struct { + ctrl_client_test_param_t test_param; + test_stage_t cur_stage; + test_stage_t next_stage; + uint8_t num_xfer_done; + uint8_t num_xfer_sent; + uint8_t dev_addr_to_open; + usb_host_client_handle_t client_hdl; + usb_device_handle_t dev_hdl; + const usb_config_desc_t *config_desc_cached; +} ctrl_client_obj_t; + +static void ctrl_transfer_cb(usb_transfer_t *transfer) +{ + ctrl_client_obj_t *ctrl_obj = (ctrl_client_obj_t *)transfer->context; + //Check the completed control transfer + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(ctrl_obj->config_desc_cached->wTotalLength, transfer->actual_num_bytes - sizeof(usb_setup_packet_t)); + ctrl_obj->num_xfer_done++; + if (ctrl_obj->num_xfer_sent < ctrl_obj->test_param.num_ctrl_xfer_to_send) { + ctrl_obj->next_stage = TEST_STAGE_CTRL_XFER; + } else if (ctrl_obj->num_xfer_done == ctrl_obj->test_param.num_ctrl_xfer_to_send) { + ctrl_obj->next_stage = TEST_STAGE_DEV_CLOSE; + } +} + +static void ctrl_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + ctrl_client_obj_t *ctrl_obj = (ctrl_client_obj_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, ctrl_obj->cur_stage); + ctrl_obj->next_stage = TEST_STAGE_DEV_OPEN; + ctrl_obj->dev_addr_to_open = event_msg->new_dev.address; + break; + default: + abort(); //Should never occur in this test + break; + } +} + +void ctrl_client_async_seq_task(void *arg) +{ + ctrl_client_obj_t ctrl_obj = {0}; + memcpy(&ctrl_obj.test_param, arg, sizeof(ctrl_client_test_param_t)); + ctrl_obj.cur_stage = TEST_STAGE_WAIT_CONN; + ctrl_obj.next_stage = TEST_STAGE_WAIT_CONN; + + //Register client + usb_host_client_config_t client_config = { + .client_event_callback = ctrl_client_event_cb, + .callback_arg = (void *)&ctrl_obj, + .max_num_event_msg = CTRL_CLIENT_MAX_EVENT_MSGS, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &ctrl_obj.client_hdl)); + + //Allocate transfers + usb_transfer_t *ctrl_xfer[NUM_TRANSFER_OBJ] = {NULL}; + for (int i = 0; i < NUM_TRANSFER_OBJ; i++) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(sizeof(usb_setup_packet_t) + MAX_TRANSFER_BYTES, 0, &ctrl_xfer[i])); + ctrl_xfer[i]->callback = ctrl_transfer_cb; + ctrl_xfer[i]->context = (void *)&ctrl_obj; + } + + //Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ESP_LOGD(CTRL_CLIENT_TAG, "Starting"); + + bool exit_loop = false; + bool skip_event_handling = false; + while (!exit_loop) { + if (!skip_event_handling) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(ctrl_obj.client_hdl, portMAX_DELAY)); + } + skip_event_handling = false; + if (ctrl_obj.cur_stage == ctrl_obj.next_stage) { + continue; + } + ctrl_obj.cur_stage = ctrl_obj.next_stage; + + switch (ctrl_obj.next_stage) { + case TEST_STAGE_DEV_OPEN: { + ESP_LOGD(CTRL_CLIENT_TAG, "Open"); + //Open the device + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(ctrl_obj.client_hdl, ctrl_obj.dev_addr_to_open, &ctrl_obj.dev_hdl)); + //Target our transfers to the device + for (int i = 0; i < NUM_TRANSFER_OBJ; i++) { + ctrl_xfer[i]->device_handle = ctrl_obj.dev_hdl; + } + //Check the VID/PID of the opened device + const usb_device_desc_t *device_desc; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(ctrl_obj.dev_hdl, &device_desc)); + TEST_ASSERT_EQUAL(ctrl_obj.test_param.idVendor, device_desc->idVendor); + TEST_ASSERT_EQUAL(ctrl_obj.test_param.idProduct, device_desc->idProduct); + //Cache the active configuration descriptor for later comparison + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_active_config_descriptor(ctrl_obj.dev_hdl, &ctrl_obj.config_desc_cached)); + ctrl_obj.next_stage = TEST_STAGE_CTRL_XFER; + skip_event_handling = true; + break; + } + case TEST_STAGE_CTRL_XFER: { + ESP_LOGD(CTRL_CLIENT_TAG, "Transfer"); + //Send a control transfer to get the device's configuration descriptor + usb_transfer_t *transfer = ctrl_xfer[ctrl_obj.num_xfer_sent % NUM_TRANSFER_OBJ]; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, 0, MAX_TRANSFER_BYTES); + transfer->num_bytes = sizeof(usb_setup_packet_t) + MAX_TRANSFER_BYTES; + transfer->bEndpointAddress = 0; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit_control(ctrl_obj.client_hdl, transfer)); + ctrl_obj.num_xfer_sent++; + ctrl_obj.next_stage = TEST_STAGE_CTRL_XFER_WAIT; + skip_event_handling = true; + break; + } + case TEST_STAGE_CTRL_XFER_WAIT: { + //Nothing to do but wait + break; + } + case TEST_STAGE_DEV_CLOSE: { + ESP_LOGD(CTRL_CLIENT_TAG, "Close"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(ctrl_obj.client_hdl, ctrl_obj.dev_hdl)); + exit_loop = true; + break; + } + default: + abort(); + break; + } + } + //Free transfers and deregister client + for (int i = 0; i < NUM_TRANSFER_OBJ; i++) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(ctrl_xfer[i])); + } + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(ctrl_obj.client_hdl)); + ESP_LOGD(CTRL_CLIENT_TAG, "Done"); + vTaskDelete(NULL); +} diff --git a/components/usb/test/usb_host/msc_client.h b/components/usb/test/usb_host/msc_client.h new file mode 100644 index 0000000000..b1809b949c --- /dev/null +++ b/components/usb/test/usb_host/msc_client.h @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +typedef struct { + int num_sectors_to_read; + int num_sectors_per_xfer; + uint32_t msc_scsi_xfer_tag; + uint16_t idVendor; + uint16_t idProduct; +} msc_client_test_param_t; + +void msc_client_async_seq_task(void *arg); diff --git a/components/usb/test/usb_host/msc_client_async_seq.c b/components/usb/test/usb_host/msc_client_async_seq.c new file mode 100644 index 0000000000..6880e96ec7 --- /dev/null +++ b/components/usb/test/usb_host/msc_client_async_seq.c @@ -0,0 +1,242 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "test_usb_mock_classes.h" +#include "msc_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +/* +Implementation of an MSC client used for USB Host Tests + +- Implemented using sequential call patterns, meaning: + - The entire client is contained within a single task + - All API calls and callbacks are run sequentially + - No critical sections required since everything is sequential +- The MSC client will: + - Register itself as a client + - Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device + - Allocate IN and OUT transfer objects for MSC SCSI transfers + - Iterate through multiple MSC SCSI block reads + - Free transfer objects + - Close device + - Deregister MSC client +*/ + +#define MAX(x,y) (((x) >= (y)) ? (x) : (y)) +#define MSC_CLIENT_MAX_EVENT_MSGS 5 + +const char *MSC_CLIENT_TAG = "MSC Client"; + +typedef enum { + TEST_STAGE_WAIT_CONN, + TEST_STAGE_DEV_OPEN, + TEST_STAGE_MSC_RESET, + TEST_STAGE_MSC_CBW, + TEST_STAGE_MSC_DATA, + TEST_STAGE_MSC_CSW, + TEST_STAGE_DEV_CLOSE, +} test_stage_t; + +typedef struct { + msc_client_test_param_t test_param; + test_stage_t cur_stage; + test_stage_t next_stage; + uint8_t dev_addr_to_open; + usb_host_client_handle_t client_hdl; + usb_device_handle_t dev_hdl; + int num_sectors_read; +} msc_client_obj_t; + +static void msc_transfer_cb(usb_transfer_t *transfer) +{ + msc_client_obj_t *msc_obj = (msc_client_obj_t *)transfer->context; + switch (msc_obj->cur_stage) { + case TEST_STAGE_MSC_RESET: { + //Check MSC SCSI interface reset + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(transfer->num_bytes, transfer->actual_num_bytes); + msc_obj->next_stage = TEST_STAGE_MSC_CBW; + break; + } + case TEST_STAGE_MSC_CBW: { + //Check MSC SCSI CBW transfer + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(sizeof(mock_msc_bulk_cbw_t), transfer->actual_num_bytes); + msc_obj->next_stage = TEST_STAGE_MSC_DATA; + break; + } + case TEST_STAGE_MSC_DATA: { + //Check MSC SCSI data IN transfer + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj->test_param.num_sectors_per_xfer, transfer->actual_num_bytes); + msc_obj->next_stage = TEST_STAGE_MSC_CSW; + break; + } + case TEST_STAGE_MSC_CSW: { + //Check MSC SCSI CSW transfer + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(true, mock_msc_scsi_check_csw((mock_msc_bulk_csw_t *)transfer->data_buffer, msc_obj->test_param.msc_scsi_xfer_tag)); + msc_obj->num_sectors_read += msc_obj->test_param.num_sectors_per_xfer; + if (msc_obj->num_sectors_read < msc_obj->test_param.num_sectors_to_read) { + msc_obj->next_stage = TEST_STAGE_MSC_CBW; + } else { + msc_obj->next_stage = TEST_STAGE_DEV_CLOSE; + } + break; + } + default: { + abort(); + break; + } + } +} + +static void msc_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + msc_client_obj_t *msc_obj = (msc_client_obj_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, msc_obj->cur_stage); + msc_obj->next_stage = TEST_STAGE_DEV_OPEN; + msc_obj->dev_addr_to_open = event_msg->new_dev.address; + break; + default: + abort(); //Should never occur in this test + break; + + } +} + +void msc_client_async_seq_task(void *arg) +{ + msc_client_obj_t msc_obj; + memcpy(&msc_obj.test_param, arg, sizeof(msc_client_test_param_t)); + msc_obj.cur_stage = TEST_STAGE_WAIT_CONN; + msc_obj.next_stage = TEST_STAGE_WAIT_CONN; + msc_obj.client_hdl = NULL; + msc_obj.dev_addr_to_open = 0; + msc_obj.dev_hdl = NULL; + msc_obj.num_sectors_read = 0; + + //Register client + usb_host_client_config_t client_config = { + .client_event_callback = msc_client_event_cb, + .callback_arg = (void *)&msc_obj, + .max_num_event_msg = MSC_CLIENT_MAX_EVENT_MSGS, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &msc_obj.client_hdl)); + + //Allocate transfers + usb_transfer_t *xfer_out = NULL; //Must be large enough to contain CBW and MSC reset control transfer + usb_transfer_t *xfer_in = NULL; //Must be large enough to contain CSW and Data + size_t out_worst_case_size = MAX(sizeof(mock_msc_bulk_cbw_t), sizeof(usb_setup_packet_t)); + size_t in_worst_case_size = usb_host_round_up_to_mps(MAX(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj.test_param.num_sectors_per_xfer, sizeof(mock_msc_bulk_csw_t)), MOCK_MSC_SCSI_BULK_EP_MPS); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(out_worst_case_size, 0, &xfer_out)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(in_worst_case_size, 0, &xfer_in)); + xfer_out->callback = msc_transfer_cb; + xfer_in->callback = msc_transfer_cb; + xfer_out->context = (void *)&msc_obj; + xfer_in->context = (void *)&msc_obj; + + //Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ESP_LOGD(MSC_CLIENT_TAG, "Starting"); + + bool exit_loop = false; + bool skip_event_handling = false; + while (!exit_loop) { + if (!skip_event_handling) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(msc_obj.client_hdl, portMAX_DELAY)); + } + skip_event_handling = false; + if (msc_obj.cur_stage == msc_obj.next_stage) { + continue; + } + msc_obj.cur_stage = msc_obj.next_stage; + + switch (msc_obj.cur_stage) { + case TEST_STAGE_DEV_OPEN: { + ESP_LOGD(MSC_CLIENT_TAG, "Open"); + //Open the device + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(msc_obj.client_hdl, msc_obj.dev_addr_to_open, &msc_obj.dev_hdl)); + //Target our transfers to the device + xfer_out->device_handle = msc_obj.dev_hdl; + xfer_in->device_handle = msc_obj.dev_hdl; + //Check the VID/PID of the opened device + const usb_device_desc_t *device_desc; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(msc_obj.dev_hdl, &device_desc)); + TEST_ASSERT_EQUAL(msc_obj.test_param.idVendor, device_desc->idVendor); + TEST_ASSERT_EQUAL(msc_obj.test_param.idProduct, device_desc->idProduct); + //Claim the MSC interface + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_claim(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING)); + msc_obj.next_stage = TEST_STAGE_MSC_RESET; + skip_event_handling = true; //Need to execute TEST_STAGE_MSC_RESET + break; + } + case TEST_STAGE_MSC_RESET: { + ESP_LOGD(MSC_CLIENT_TAG, "MSC Reset"); + //Send an MSC SCSI interface reset + MOCK_MSC_SCSI_REQ_INIT_RESET((usb_setup_packet_t *)xfer_out->data_buffer, MOCK_MSC_SCSI_INTF_NUMBER); + xfer_out->num_bytes = sizeof(usb_setup_packet_t); + xfer_out->bEndpointAddress = 0; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit_control(msc_obj.client_hdl, xfer_out)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_MSC_CBW: { + ESP_LOGD(MSC_CLIENT_TAG, "CBW"); + mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)xfer_out->data_buffer, true, msc_obj.next_stage, msc_obj.test_param.num_sectors_per_xfer, msc_obj.test_param.msc_scsi_xfer_tag); + xfer_out->num_bytes = sizeof(mock_msc_bulk_cbw_t); + xfer_out->bEndpointAddress = MOCK_MSC_SCSI_BULK_OUT_EP_ADDR; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_out)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_MSC_DATA: { + ESP_LOGD(MSC_CLIENT_TAG, "Data"); + xfer_in->num_bytes = usb_host_round_up_to_mps(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj.test_param.num_sectors_per_xfer, MOCK_MSC_SCSI_BULK_EP_MPS); + xfer_in->bEndpointAddress = MOCK_MSC_SCSI_BULK_IN_EP_ADDR; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_MSC_CSW: { + ESP_LOGD(MSC_CLIENT_TAG, "CSW"); + xfer_in->num_bytes = usb_host_round_up_to_mps(sizeof(mock_msc_bulk_csw_t), MOCK_MSC_SCSI_BULK_EP_MPS); + xfer_in->bEndpointAddress = MOCK_MSC_SCSI_BULK_IN_EP_ADDR; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_DEV_CLOSE: { + ESP_LOGD(MSC_CLIENT_TAG, "Close"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_release(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(msc_obj.client_hdl, msc_obj.dev_hdl)); + exit_loop = true; + break; + } + default: + abort(); + break; + } + } + //Free transfers and deregister the client + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_out)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_in)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(msc_obj.client_hdl)); + ESP_LOGD(MSC_CLIENT_TAG, "Done"); + vTaskDelete(NULL); +} diff --git a/components/usb/test/usb_host/test_usb_host_async.c b/components/usb/test/usb_host/test_usb_host_async.c new file mode 100644 index 0000000000..4c7f792e7c --- /dev/null +++ b/components/usb/test/usb_host/test_usb_host_async.c @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_intr_alloc.h" +#include "test_usb_mock_classes.h" +#include "msc_client.h" +#include "ctrl_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +#define TEST_MSC_NUM_SECTORS_TOTAL 10 +#define TEST_MSC_NUM_SECTORS_PER_XFER 2 +#define TEST_MSC_SCSI_TAG 0xDEADBEEF +#define TEST_CTRL_NUM_TRANSFERS 30 + +// --------------------------------------------------- Test Cases ------------------------------------------------------ + +/* +Test USB Host Asynchronous API single client + +Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class) + +Purpose: + - Test that USB Host Asynchronous API works correctly with a single client + - Test that a client can be created + - Test that client can operate concurrently in a separate thread + - Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) + +Procedure: + - Install USB Host Library + - Create a task to run an MSC client + - Start the MSC client task. It will execute a bunch of MSC SCSI sector reads + - Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event + - Free all devices + - Uninstall USB Host Library +*/ + +TEST_CASE("Test USB Host async (single client)", "[usb_host][ignore]") +{ + //Install USB Host + usb_host_config_t host_config = { + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Create task to run client that communicates with MSC SCSI interface + msc_client_test_param_t params = { + .num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL, + .num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER, + .msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG, + .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR, + .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT, + }; + TaskHandle_t task_hdl; + xTaskCreatePinnedToCore(msc_client_async_seq_task, "async", 4096, (void *)¶ms, 2, &task_hdl, 0); + //Start the task + xTaskNotifyGive(task_hdl); + + while (1) { + //Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_free_all()); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + break; + } + } + + //Short delay to allow task to be cleaned up + vTaskDelay(10); + //Clean up USB Host + ESP_ERROR_CHECK(usb_host_uninstall()); +} + +/* +Test USB Host Asynchronous API with multiple clients + +Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class) + +Purpose: + - Test the USB Host Asynchronous API works correctly with multiple clients + - Test that multiple clients can be created + - Test that multiple clients can operate concurrently in separate threads + - Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) + +Procedure: + - Install USB Host Library + - Create separate tasks to run an MSC client and Ctrl Client + - MSC Client will execute a bunch of MSC SCSI sector reads + - Ctrl Client will execute a bunch of control transfers + - Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event + - Free all devices + - Uninstall USB Host Library +*/ +TEST_CASE("Test USB Host async (multi client)", "[usb_host][ignore]") +{ + //Install USB Host + usb_host_config_t host_config = { + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Create task to run the MSC client + msc_client_test_param_t msc_params = { + .num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL, + .num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER, + .msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG, + .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR, + .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT, + }; + TaskHandle_t msc_task_hdl; + xTaskCreatePinnedToCore(msc_client_async_seq_task, "msc", 4096, (void *)&msc_params, 2, &msc_task_hdl, 0); + + //Create task a control transfer client + ctrl_client_test_param_t ctrl_params = { + .num_ctrl_xfer_to_send = TEST_CTRL_NUM_TRANSFERS, + .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR, + .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT, + }; + TaskHandle_t ctrl_task_hdl; + xTaskCreatePinnedToCore(ctrl_client_async_seq_task, "ctrl", 4096, (void *)&ctrl_params, 2, &ctrl_task_hdl, 0); + + //Start both tasks + xTaskNotifyGive(msc_task_hdl); + xTaskNotifyGive(ctrl_task_hdl); + + while (1) { + //Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_free_all()); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + break; + } + } + + //Short delay to allow task to be cleaned up + vTaskDelay(10); + //Clean up USB Host + ESP_ERROR_CHECK(usb_host_uninstall()); +} diff --git a/components/usb/test/usb_host/test_usb_host_misc.c b/components/usb/test/usb_host/test_usb_host_misc.c new file mode 100644 index 0000000000..465e7ca915 --- /dev/null +++ b/components/usb/test/usb_host/test_usb_host_misc.c @@ -0,0 +1,439 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "unity.h" +#include "test_utils.h" +#include "usb/usb_host.h" + +/* +Tests that check the configuration descriptor parsing functions provided in usb_host.h work by parsing a fixed +configuration descriptor. The fixed configuration descriptor used in this test is provided below (both in textual and +byte format), and is of a UVC device. Thus the configuration descriptor has a good set of scenarios that can be tested +(such as multiple interfaces, alternate settings, class specific descriptors, default endpoint only interfaces etc). +*/ + +/* +Configuration Descriptor: + bLength 9 + bDescriptorType 2 + wTotalLength 0x0185 + bNumInterfaces 2 + bConfigurationValue 1 + iConfiguration 0 + bmAttributes 0x80 + (Bus Powered) + MaxPower 500mA + Interface Association: + bLength 8 + bDescriptorType 11 + bFirstInterface 0 + bInterfaceCount 2 + bFunctionClass 14 Video + bFunctionSubClass 3 Video Interface Collection + bFunctionProtocol 0 + iFunction 5 + Interface Descriptor: + bLength 9 + bDescriptorType 4 + bInterfaceNumber 0 + bAlternateSetting 0 + bNumEndpoints 1 + bInterfaceClass 14 Video + bInterfaceSubClass 1 Video Control + bInterfaceProtocol 0 + iInterface 5 + VideoControl Interface Descriptor: + bLength 13 + bDescriptorType 36 + bDescriptorSubtype 1 (HEADER) + bcdUVC 1.00 + wTotalLength 0x004f + dwClockFrequency 15.000000MHz + bInCollection 1 + baInterfaceNr( 0) 1 + VideoControl Interface Descriptor: + bLength 9 + bDescriptorType 36 + bDescriptorSubtype 3 (OUTPUT_TERMINAL) + bTerminalID 4 + wTerminalType 0x0101 USB Streaming + bAssocTerminal 0 + bSourceID 3 + iTerminal 0 + VideoControl Interface Descriptor: + bLength 28 + bDescriptorType 36 + bDescriptorSubtype 6 (EXTENSION_UNIT) + bUnitID 3 + guidExtensionCode {4cf18db6-abd0-495c-9876-1fa3942ff9fa} + bNumControl 24 + bNrPins 1 + baSourceID( 0) 2 + bControlSize 3 + bmControls( 0) 0xff + bmControls( 1) 0xff + bmControls( 2) 0xff + iExtension 0 + VideoControl Interface Descriptor: + bLength 18 + bDescriptorType 36 + bDescriptorSubtype 2 (INPUT_TERMINAL) + bTerminalID 1 + wTerminalType 0x0201 Camera Sensor + bAssocTerminal 0 + iTerminal 0 + wObjectiveFocalLengthMin 0 + wObjectiveFocalLengthMax 0 + wOcularFocalLength 0 + bControlSize 3 + bmControls 0x0000000e + Auto-Exposure Mode + Auto-Exposure Priority + Exposure Time (Absolute) + VideoControl Interface Descriptor: + bLength 11 + bDescriptorType 36 + bDescriptorSubtype 5 (PROCESSING_UNIT) + Warning: Descriptor too short + bUnitID 2 + bSourceID 1 + wMaxMultiplier 0 + bControlSize 2 + bmControls 0x0000177f + Brightness + Contrast + Hue + Saturation + Sharpness + Gamma + White Balance Temperature + Backlight Compensation + Gain + Power Line Frequency + White Balance Temperature, Auto + iProcessing 0 + bmVideoStandards 0x62 + NTSC - 525/60 + PAL - 525/60 + Endpoint Descriptor: + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x83 EP 3 IN + bmAttributes 3 + Transfer Type Interrupt + Synch Type None + Usage Type Data + wMaxPacketSize 0x0010 1x 16 bytes + bInterval 6 + Interface Descriptor: + bLength 9 + bDescriptorType 4 + bInterfaceNumber 1 + bAlternateSetting 0 + bNumEndpoints 0 + bInterfaceClass 14 Video + bInterfaceSubClass 2 Video Streaming + bInterfaceProtocol 0 + iInterface 0 + VideoStreaming Interface Descriptor: + bLength 15 + bDescriptorType 36 + bDescriptorSubtype 1 (INPUT_HEADER) + bNumFormats 2 + wTotalLength 0x00f7 + bEndPointAddress 129 + bmInfo 0 + bTerminalLink 4 + bStillCaptureMethod 0 + bTriggerSupport 0 + bTriggerUsage 0 + bControlSize 1 + bmaControls( 0) 0 + bmaControls( 1) 0 + VideoStreaming Interface Descriptor: + bLength 27 + bDescriptorType 36 + bDescriptorSubtype 4 (FORMAT_UNCOMPRESSED) + bFormatIndex 1 + bNumFrameDescriptors 2 + guidFormat {85f6cc1d-0c9f-44f5-9ce0-97c7dd8c98ab} + bBitsPerPixel 16 + bDefaultFrameIndex 1 + bAspectRatioX 0 + bAspectRatioY 0 + bmInterlaceFlags 0x00 + Interlaced stream or variable: No + Fields per frame: 2 fields + Field 1 first: No + Field pattern: Field 1 only + bCopyProtect 0 + VideoStreaming Interface Descriptor: + bLength 30 + bDescriptorType 36 + bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) + bFrameIndex 1 + bmCapabilities 0x00 + Still image unsupported + wWidth 480 + wHeight 320 + dwMinBitRate 12288000 + dwMaxBitRate 12288000 + dwMaxVideoFrameBufferSize 307200 + dwDefaultFrameInterval 2000000 + bFrameIntervalType 1 + dwFrameInterval( 0) 2000000 + VideoStreaming Interface Descriptor: + bLength 30 + bDescriptorType 36 + bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) + bFrameIndex 2 + bmCapabilities 0x00 + Still image unsupported + wWidth 640 + wHeight 480 + dwMinBitRate 73728000 + dwMaxBitRate 73728000 + dwMaxVideoFrameBufferSize 614400 + dwDefaultFrameInterval 666666 + bFrameIntervalType 1 + dwFrameInterval( 0) 666666 + VideoStreaming Interface Descriptor: + bLength 11 + bDescriptorType 36 + bDescriptorSubtype 6 (FORMAT_MJPEG) + bFormatIndex 2 + bNumFrameDescriptors 4 + bFlags 0 + Fixed-size samples: No + bDefaultFrameIndex 1 + bAspectRatioX 0 + bAspectRatioY 0 + bmInterlaceFlags 0x00 + Interlaced stream or variable: No + Fields per frame: 1 fields + Field 1 first: No + Field pattern: Field 1 only + bCopyProtect 0 + VideoStreaming Interface Descriptor: + bLength 30 + bDescriptorType 36 + bDescriptorSubtype 7 (FRAME_MJPEG) + bFrameIndex 1 + bmCapabilities 0x00 + Still image unsupported + wWidth 640 + wHeight 480 + dwMinBitRate 36864000 + dwMaxBitRate 36864000 + dwMaxVideoFrameBufferSize 307789 + dwDefaultFrameInterval 666666 + bFrameIntervalType 1 + dwFrameInterval( 0) 666666 + VideoStreaming Interface Descriptor: + bLength 38 + bDescriptorType 36 + bDescriptorSubtype 7 (FRAME_MJPEG) + bFrameIndex 2 + bmCapabilities 0x00 + Still image unsupported + wWidth 480 + wHeight 320 + dwMinBitRate 6144000 + dwMaxBitRate 18432000 + dwMaxVideoFrameBufferSize 154189 + dwDefaultFrameInterval 666666 + bFrameIntervalType 3 + dwFrameInterval( 0) 666666 + dwFrameInterval( 1) 1000000 + dwFrameInterval( 2) 2000000 + VideoStreaming Interface Descriptor: + bLength 30 + bDescriptorType 36 + bDescriptorSubtype 7 (FRAME_MJPEG) + bFrameIndex 3 + bmCapabilities 0x00 + Still image unsupported + wWidth 352 + wHeight 288 + dwMinBitRate 12165120 + dwMaxBitRate 12165120 + dwMaxVideoFrameBufferSize 101965 + dwDefaultFrameInterval 666666 + bFrameIntervalType 1 + dwFrameInterval( 0) 666666 + VideoStreaming Interface Descriptor: + bLength 30 + bDescriptorType 36 + bDescriptorSubtype 7 (FRAME_MJPEG) + bFrameIndex 4 + bmCapabilities 0x00 + Still image unsupported + wWidth 320 + wHeight 240 + dwMinBitRate 9216000 + dwMaxBitRate 9216000 + dwMaxVideoFrameBufferSize 77389 + dwDefaultFrameInterval 666666 + bFrameIntervalType 1 + dwFrameInterval( 0) 666666 + VideoStreaming Interface Descriptor: + bLength 6 + bDescriptorType 36 + bDescriptorSubtype 13 (COLORFORMAT) + bColorPrimaries 1 (BT.709,sRGB) + bTransferCharacteristics 1 (BT.709) + bMatrixCoefficients 4 (SMPTE 170M (BT.601)) + Interface Descriptor: + bLength 9 + bDescriptorType 4 + bInterfaceNumber 1 + bAlternateSetting 1 + bNumEndpoints 1 + bInterfaceClass 14 Video + bInterfaceSubClass 2 Video Streaming + bInterfaceProtocol 0 + iInterface 0 + Endpoint Descriptor: + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x81 EP 1 IN + bmAttributes 5 + Transfer Type Isochronous + Synch Type Asynchronous + Usage Type Data + wMaxPacketSize 0x03bc 1x 956 bytes + bInterval 1 +*/ + +static uint8_t config_desc_bytes [] = { + 0x09, 0x02, 0x85, 0x01, 0x02, 0x01, 0x00, 0x80, 0xFA, 0x08, 0x0B, 0x00, 0x02, 0x0E, 0x03, 0x00, 0x05, 0x09, 0x04, + 0x00, 0x00, 0x01, 0x0E, 0x01, 0x00, 0x05, 0x0D, 0x24, 0x01, 0x00, 0x01, 0x4F, 0x00, 0xC0, 0xE1, 0xE4, 0x00, 0x01, + 0x01, 0x09, 0x24, 0x03, 0x04, 0x01, 0x01, 0x00, 0x03, 0x00, 0x1C, 0x24, 0x06, 0x03, 0xB6, 0x8D, 0xF1, 0x4C, 0xD0, + 0xAB, 0x5C, 0x49, 0x76, 0x98, 0xFA, 0xF9, 0x2F, 0x94, 0xA3, 0x1F, 0x18, 0x01, 0x02, 0x03, 0xFF, 0xFF, 0xFF, 0x00, + 0x12, 0x24, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0B, + 0x24, 0x05, 0x02, 0x01, 0x00, 0x00, 0x02, 0x7F, 0x17, 0x00, 0x07, 0x05, 0x83, 0x03, 0x10, 0x00, 0x06, 0x05, 0x25, + 0x03, 0x80, 0x00, 0x09, 0x04, 0x01, 0x00, 0x00, 0x0E, 0x02, 0x00, 0x00, 0x0F, 0x24, 0x01, 0x02, 0xF7, 0x00, 0x81, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x1B, 0x24, 0x04, 0x01, 0x02, 0x1D, 0xCC, 0xF6, 0x85, 0x9F, 0x0C, + 0xF5, 0x44, 0xE0, 0x9C, 0xAB, 0x98, 0x8C, 0xDD, 0xC7, 0x97, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x24, 0x05, + 0x01, 0x00, 0xE0, 0x01, 0x40, 0x01, 0x00, 0x80, 0xBB, 0x00, 0x00, 0x80, 0xBB, 0x00, 0x00, 0xB0, 0x04, 0x00, 0x80, + 0x84, 0x1E, 0x00, 0x01, 0x80, 0x84, 0x1E, 0x00, 0x1E, 0x24, 0x05, 0x02, 0x00, 0x80, 0x02, 0xE0, 0x01, 0x00, 0x00, + 0x65, 0x04, 0x00, 0x00, 0x65, 0x04, 0x00, 0x60, 0x09, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00, + 0x0B, 0x24, 0x06, 0x02, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x24, 0x07, 0x01, 0x00, 0x80, 0x02, 0xE0, + 0x01, 0x00, 0x80, 0x32, 0x02, 0x00, 0x80, 0x32, 0x02, 0x4D, 0xB2, 0x04, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A, + 0x2C, 0x0A, 0x00, 0x26, 0x24, 0x07, 0x02, 0x00, 0xE0, 0x01, 0x40, 0x01, 0x00, 0xC0, 0x5D, 0x00, 0x00, 0x40, 0x19, + 0x01, 0x4D, 0x5A, 0x02, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x03, 0x2A, 0x2C, 0x0A, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x80, + 0x84, 0x1E, 0x00, 0x1E, 0x24, 0x07, 0x03, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0xA0, 0xB9, 0x00, 0x00, 0xA0, 0xB9, + 0x00, 0x4D, 0x8E, 0x01, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00, 0x1E, 0x24, 0x07, 0x04, 0x00, + 0x40, 0x01, 0xF0, 0x00, 0x00, 0xA0, 0x8C, 0x00, 0x00, 0xA0, 0x8C, 0x00, 0x4D, 0x2E, 0x01, 0x00, 0x2A, 0x2C, 0x0A, + 0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00, 0x06, 0x24, 0x0D, 0x01, 0x01, 0x04, 0x09, 0x04, 0x01, 0x01, 0x01, 0x0E, 0x02, + 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0xBC, 0x03, 0x01, +}; +_Static_assert(sizeof(config_desc_bytes) == 0x0185, "Configuration Descriptor size does not match"); + +#define TEST_NUM_INTF_DESC 3 //Total number of interface descriptors (including alternate) + +// --------------------- Sub-Test 1 ------------------------ + +/* +Test if we can walk the configuration descriptor to find each interface descriptor +*/ +static void test_walk_desc(const usb_config_desc_t *config_desc) +{ + int offset = 0; + const usb_standard_desc_t *cur_desc = (usb_standard_desc_t *)config_desc; + for (int i = 0; i < TEST_NUM_INTF_DESC; i++) { + cur_desc = usb_host_parse_next_descriptor_of_type(cur_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset); + TEST_ASSERT_NOT_EQUAL(NULL, cur_desc); + } + //Attempting to look for another interface descriptor should result in NULL + cur_desc = usb_host_parse_next_descriptor_of_type(cur_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset); + TEST_ASSERT_EQUAL(NULL, cur_desc); +} + +/* +Test if the count of number of alternate descriptors is correct +*/ +static void test_alt_intf_desc_count(const usb_config_desc_t *config_desc) +{ + //bInterface 0 has no alternate interfaces + TEST_ASSERT_EQUAL(0, usb_host_parse_interface_number_of_alternate(config_desc, 0)); + //bInterface 1 has 1 alternate interface + TEST_ASSERT_EQUAL(1, usb_host_parse_interface_number_of_alternate(config_desc, 1)); + //Non existent bInterface 2 should return -1 + TEST_ASSERT_EQUAL(-1, usb_host_parse_interface_number_of_alternate(config_desc, 2)); +} + +static void test_parse_intf_and_ep(const usb_config_desc_t *config_desc) +{ + int offset_intf = 0; + + //Get bInterfaceNumber 0 (index 0) + const usb_intf_desc_t *intf_desc = usb_host_parse_interface(config_desc, 0, 0, &offset_intf); + TEST_ASSERT_NOT_EQUAL(NULL, intf_desc); + //Should only have one endpoint + int offset_ep = offset_intf; + const usb_ep_desc_t *ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep); + TEST_ASSERT_NOT_EQUAL(NULL, ep_desc); + TEST_ASSERT_EQUAL(0x83, ep_desc->bEndpointAddress); + offset_ep = offset_intf; + ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 1, config_desc->wTotalLength, &offset_ep); + TEST_ASSERT_EQUAL(NULL, ep_desc); + + //Get bInterfaceNumber 1 alternate setting 0 + offset_intf = 0; + intf_desc = usb_host_parse_interface(config_desc, 1, 0, &offset_intf); + TEST_ASSERT_NOT_EQUAL(NULL, intf_desc); + //Should have no endpoints + offset_ep = offset_intf; + ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep); + TEST_ASSERT_EQUAL(NULL, ep_desc); + + //Get bInterfaceNumber 1 alternate setting 1 + offset_intf = 0; + intf_desc = usb_host_parse_interface(config_desc, 1, 1, &offset_intf); + TEST_ASSERT_NOT_EQUAL(NULL, intf_desc); + //Should only have one endpoint + offset_ep = offset_intf; + ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep); + TEST_ASSERT_NOT_EQUAL(NULL, ep_desc); + TEST_ASSERT_EQUAL(0x81, ep_desc->bEndpointAddress); + offset_ep = offset_intf; + ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 1, config_desc->wTotalLength, &offset_ep); + TEST_ASSERT_EQUAL(NULL, ep_desc); +} + +static void test_parse_ep_by_address(const usb_config_desc_t *config_desc) +{ + int offset_ep = 0; + //Get bInterface 0 bAlternateSetting 0 EP 0x83 + const usb_ep_desc_t *ep_desc = usb_host_parse_endpoint_by_address(config_desc, 0, 0, 0x83, &offset_ep); + TEST_ASSERT_NOT_EQUAL(NULL, ep_desc); + TEST_ASSERT_EQUAL(0x83, ep_desc->bEndpointAddress); + //Getting same EP address under different interface should return NULL + offset_ep = 0; + ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 0, 0x83, &offset_ep); + TEST_ASSERT_EQUAL(NULL, ep_desc); + + //Get bInterface 1 bAlternateSetting 1 EP 0x81 + offset_ep = 0; + ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 1, 0x81, &offset_ep); + TEST_ASSERT_NOT_EQUAL(NULL, ep_desc); + TEST_ASSERT_EQUAL(0x81, ep_desc->bEndpointAddress); + //Getting same EP address under different interface should return NULL + offset_ep = 0; + ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 0, 0x81, &offset_ep); + TEST_ASSERT_EQUAL(NULL, ep_desc); +} + +TEST_CASE("Test USB Host descriptor parsing", "[usb_host][ignore]") +{ + const usb_config_desc_t *config_desc = (const usb_config_desc_t *)config_desc_bytes; + test_walk_desc(config_desc); + test_alt_intf_desc_count(config_desc); + test_parse_intf_and_ep(config_desc); + test_parse_ep_by_address(config_desc); +} diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c new file mode 100644 index 0000000000..f745001b22 --- /dev/null +++ b/components/usb/usb_host.c @@ -0,0 +1,1270 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "hub.h" +#include "usbh.h" +#include "usb/usb_host.h" + +static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED; + +#define HOST_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&host_lock) +#define HOST_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&host_lock) +#define HOST_ENTER_CRITICAL() portENTER_CRITICAL(&host_lock) +#define HOST_EXIT_CRITICAL() portEXIT_CRITICAL(&host_lock) +#define HOST_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&host_lock) +#define HOST_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&host_lock) + +#define HOST_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define HOST_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + HOST_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +#define PROCESS_PENDING_FLAG_USBH 0x01 +#define PROCESS_PENDING_FLAG_HUB 0x02 +#define PROCESS_PENDING_FLAG_EVENT 0x04 + +typedef struct endpoint_s endpoint_t; +typedef struct interface_s interface_t; +typedef struct client_s client_t; + +struct endpoint_s { + //Dynamic members require a critical section + struct { + TAILQ_ENTRY(endpoint_s) tailq_entry; + union { + struct { + uint32_t pending: 1; + uint32_t reserved31:31; + }; + } flags; + uint32_t num_urb_inflight; + hcd_pipe_event_t last_event; + } dynamic; + //Constant members do no change after claiming the interface thus do not require a critical section + struct { + hcd_pipe_handle_t pipe_hdl; + const usb_ep_desc_t *ep_desc; + interface_t *intf_obj; + } constant; +}; + +struct interface_s { + //Dynamic members require a critical section + struct { + TAILQ_ENTRY(interface_s) tailq_entry; + } mux_protected; + //Constant members do no change after claiming the interface thus do not require a critical section + struct { + const usb_intf_desc_t *intf_desc; + usb_device_handle_t dev_hdl; + client_t *client_obj; + endpoint_t *endpoints[0]; + } constant; +}; + +struct client_s { + //Dynamic members require a critical section + struct { + TAILQ_ENTRY(client_s) tailq_entry; + TAILQ_HEAD(tailhead_pending_ep, endpoint_s) pending_ep_tailq; + TAILQ_HEAD(tailhead_idle_ep, endpoint_s) idle_ep_tailq; + TAILQ_HEAD(tailhead_done_ctrl_xfers, urb_s) done_ctrl_xfer_tailq; + union { + struct { + uint32_t events_pending: 1; + uint32_t handling_events: 1; + uint32_t blocked: 1; + uint32_t taking_mux: 1; + uint32_t reserved4: 4; + uint32_t num_intf_claimed: 8; + uint32_t reserved16: 16; + }; + uint32_t val; + } flags; + uint32_t num_done_ctrl_xfer; + uint32_t opened_dev_addr_map; + } dynamic; + //Mux protected members must be protected by host library the mux_lock when accessed + struct { + TAILQ_HEAD(tailhead_interfaces, interface_s) interface_tailq; + } mux_protected; + //Constant members do no change after registration thus do not require a critical section + struct { + SemaphoreHandle_t event_sem; + usb_host_client_event_cb_t event_callback; + void *callback_arg; + QueueHandle_t event_msg_queue; + } constant; +}; + +typedef struct { + //Dynamic members require a critical section + struct { + //Access to these should be done in a critical section + uint32_t process_pending_flags; + uint32_t lib_event_flags; + union { + struct { + uint32_t process_pending: 1; + uint32_t handling_events: 1; + uint32_t blocked: 1; + uint32_t reserved5: 5; + uint32_t num_clients: 8; + uint32_t reserved16: 16; + }; + uint32_t val; + } flags; + } dynamic; + //Mux protected members must be protected by host library the mux_lock when accessed + struct { + TAILQ_HEAD(tailhead_clients, client_s) client_tailq; //List of all clients registered + } mux_protected; + //Constant members do no change after installation thus do not require a critical section + struct { + SemaphoreHandle_t event_sem; + SemaphoreHandle_t mux_lock; + } constant; +} host_lib_t; + +static host_lib_t *p_host_lib_obj = NULL; + +const char *USB_HOST_TAG = "USB HOST"; + +// ----------------------------------------------------- Helpers ------------------------------------------------------- + +static inline void _record_client_opened_device(client_t *client_obj, uint8_t dev_addr) +{ + assert(dev_addr != 0); + client_obj->dynamic.opened_dev_addr_map |= (1 << (dev_addr - 1)); +} + +static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev_addr) +{ + assert(dev_addr != 0); + client_obj->dynamic.opened_dev_addr_map &= ~(1 << (dev_addr - 1)); +} + +static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr) +{ + assert(dev_addr != 0); + return (client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1))); +} + +static bool _unblock_client(client_t *client_obj, bool in_isr) +{ + bool send_sem; + if (!client_obj->dynamic.flags.events_pending && !client_obj->dynamic.flags.handling_events) { + client_obj->dynamic.flags.events_pending = 1; + send_sem = true; + } else { + send_sem = false; + } + + HOST_EXIT_CRITICAL_SAFE(); + bool yield = false; + if (send_sem) { + if (in_isr) { + BaseType_t xTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(client_obj->constant.event_sem, &xTaskWoken); + yield = (xTaskWoken == pdTRUE); + } else { + xSemaphoreGive(client_obj->constant.event_sem); + } + } + HOST_ENTER_CRITICAL_SAFE(); + + return yield; +} + +static bool _unblock_lib(bool in_isr) +{ + bool send_sem; + if (!p_host_lib_obj->dynamic.flags.process_pending && !p_host_lib_obj->dynamic.flags.handling_events) { + p_host_lib_obj->dynamic.flags.process_pending = 1; + send_sem = true; + } else { + send_sem = false; + } + + HOST_EXIT_CRITICAL_SAFE(); + bool yield = false; + if (send_sem) { + if (in_isr) { + BaseType_t xTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(p_host_lib_obj->constant.event_sem, &xTaskWoken); + yield = (xTaskWoken == pdTRUE); + } else { + xSemaphoreGive(p_host_lib_obj->constant.event_sem); + } + } + HOST_ENTER_CRITICAL_SAFE(); + + return yield; +} + +static void send_event_msg_to_clients(const usb_host_client_event_msg_t *event_msg, bool send_to_all, uint8_t opened_dev_addr) +{ + //Lock client list + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + //Send event message to relevant or all clients + client_t *client_obj; + TAILQ_FOREACH(client_obj, &p_host_lib_obj->mux_protected.client_tailq, dynamic.tailq_entry) { + if (!send_to_all) { + //Check if client opened the device + HOST_ENTER_CRITICAL(); + bool send = _check_client_opened_device(client_obj, opened_dev_addr); + HOST_EXIT_CRITICAL(); + if (!send) { + continue; + } + } + //Send the event message + if (xQueueSend(client_obj->constant.event_msg_queue, event_msg, 0) == pdTRUE) { + HOST_ENTER_CRITICAL(); + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); + } else { + ESP_LOGE(USB_HOST_TAG, "Client event message queue full"); + } + } + //Unlock client list + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); +} + +// ---------------------------------------------------- Callbacks ------------------------------------------------------ + +// ------------------- Library Related --------------------- + +static bool notif_callback(usb_notif_source_t source, bool in_isr, void *arg) +{ + HOST_ENTER_CRITICAL_SAFE(); + //Store notification source + switch (source) { + case USB_NOTIF_SOURCE_USBH: + p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_PENDING_FLAG_USBH; + break; + case USB_NOTIF_SOURCE_HUB: + p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_PENDING_FLAG_HUB; + break; + } + bool yield = _unblock_lib(in_isr); + HOST_EXIT_CRITICAL_SAFE(); + + return yield; +} + +static void ctrl_xfer_callback(usb_device_handle_t dev_hdl, urb_t *urb, void *arg) +{ + assert(urb->usb_host_client != NULL); + //Redistribute done control transfer to the clients that submitted them + client_t *client_obj = (client_t *)urb->usb_host_client; + + HOST_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, urb, tailq_entry); + client_obj->dynamic.num_done_ctrl_xfer++; + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); +} + +static void dev_event_callback(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg) +{ + //Check usbh_event. The data type of event_arg depends on the type of event + switch (usbh_event) { + case USBH_EVENT_DEV_NEW: { + //Prepare a NEW_DEV client event message, the send it to all clients + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + usb_host_client_event_msg_t event_msg = { + .event = USB_HOST_CLIENT_EVENT_NEW_DEV, + .new_dev.address = dev_addr, + }; + send_event_msg_to_clients(&event_msg, true, 0); + break; + } + case USBH_EVENT_DEV_GONE: { + //Prepare event msg, send only to clients that have opened the device + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + usb_host_client_event_msg_t event_msg = { + .event = USB_HOST_CLIENT_EVENT_DEV_GONE, + .dev_gone.dev_hdl = dev_hdl, + }; + send_event_msg_to_clients(&event_msg, false, dev_addr); + break; + } + case USBH_EVENT_DEV_ALL_FREE: { + //Notify the lib handler that all devices are free + HOST_ENTER_CRITICAL(); + p_host_lib_obj->dynamic.lib_event_flags |= USB_HOST_LIB_EVENT_FLAGS_ALL_FREE; + _unblock_lib(false); + HOST_EXIT_CRITICAL(); + break; + } + default: + abort(); //Should never occur + break; + } +} + +// ------------------- Client Related ---------------------- + +static bool pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +{ + endpoint_t *ep_obj = (endpoint_t *)user_arg; + client_t *client_obj = (client_t *)ep_obj->constant.intf_obj->constant.client_obj; + + HOST_ENTER_CRITICAL_SAFE(); + //Store the event to be handled later. Note that we allow overwriting of events because more severe will halt the pipe prevent any further events. + ep_obj->dynamic.last_event = pipe_event; + //Add the EP to the client's pending list if it's not in the list already + if (!ep_obj->dynamic.flags.pending) { + ep_obj->dynamic.flags.pending = 1; + TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, ep_obj, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&client_obj->dynamic.pending_ep_tailq, ep_obj, dynamic.tailq_entry); + } + bool yield = _unblock_client(client_obj, in_isr); + HOST_EXIT_CRITICAL_SAFE(); + + return yield; +} + +// ------------------------------------------------ Library Functions -------------------------------------------------- + +// ----------------------- Public -------------------------- + +esp_err_t usb_host_install(const usb_host_config_t *config) +{ + HOST_CHECK(config != NULL, ESP_ERR_INVALID_ARG); + HOST_ENTER_CRITICAL(); + HOST_CHECK_FROM_CRIT(p_host_lib_obj == NULL, ESP_ERR_INVALID_STATE); + HOST_EXIT_CRITICAL(); + + esp_err_t ret; + host_lib_t *host_lib_obj = heap_caps_calloc(1, sizeof(host_lib_t), MALLOC_CAP_DEFAULT); + SemaphoreHandle_t event_sem = xSemaphoreCreateBinary(); + SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); + if (host_lib_obj == NULL || event_sem == NULL || mux_lock == NULL) { + ret = ESP_ERR_NO_MEM; + goto alloc_err; + } + //Initialize host library object + TAILQ_INIT(&host_lib_obj->mux_protected.client_tailq); + host_lib_obj->constant.event_sem = event_sem; + host_lib_obj->constant.mux_lock = mux_lock; + //Install USBH + usbh_config_t usbh_config = { + .notif_cb = notif_callback, + .notif_cb_arg = NULL, + .ctrl_xfer_cb = ctrl_xfer_callback, + .ctrl_xfer_cb_arg = NULL, + .event_cb = dev_event_callback, + .event_cb_arg = NULL, + .hcd_config = { + .intr_flags = config->intr_flags, + }, + }; + ret = usbh_install(&usbh_config); + if (ret != ESP_OK) { + goto usbh_err; + } + //Install Hub + hub_config_t hub_config = { + .notif_cb = notif_callback, + .notif_cb_arg = NULL, + }; + ret = hub_install(&hub_config); + if (ret != ESP_OK) { + goto hub_err; + } + + //Assign host library object + HOST_ENTER_CRITICAL(); + if (p_host_lib_obj != NULL) { + HOST_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + p_host_lib_obj = host_lib_obj; + HOST_EXIT_CRITICAL(); + + //Start the root hub + ESP_ERROR_CHECK(hub_root_start()); + ret = ESP_OK; + return ret; + +assign_err: + ESP_ERROR_CHECK(hub_uninstall()); +hub_err: + ESP_ERROR_CHECK(usbh_uninstall()); +usbh_err: +alloc_err: + if (mux_lock) { + vSemaphoreDelete(mux_lock); + } + if (event_sem) { + vSemaphoreDelete(event_sem); + } + heap_caps_free(host_lib_obj); + return ret; +} + +esp_err_t usb_host_uninstall(void) +{ + //All devices must have been freed at this point + HOST_ENTER_CRITICAL(); + HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE); + HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.process_pending_flags == 0 && + p_host_lib_obj->dynamic.lib_event_flags == 0 && + p_host_lib_obj->dynamic.flags.val == 0, + ESP_ERR_INVALID_STATE); + HOST_EXIT_CRITICAL(); + + //Stop the root hub + ESP_ERROR_CHECK(hub_root_stop()); + + //Uninstall Hub and USBH + ESP_ERROR_CHECK(hub_uninstall()); + ESP_ERROR_CHECK(usbh_uninstall()); + + HOST_ENTER_CRITICAL(); + host_lib_t *host_lib_obj = p_host_lib_obj; + p_host_lib_obj = NULL; + HOST_EXIT_CRITICAL(); + + //Free memory objects + vSemaphoreDelete(host_lib_obj->constant.mux_lock); + vSemaphoreDelete(host_lib_obj->constant.event_sem); + heap_caps_free(host_lib_obj); + return ESP_OK; +} + +esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret) +{ + esp_err_t ret; + uint32_t event_flags = 0; + + HOST_ENTER_CRITICAL(); + if (!p_host_lib_obj->dynamic.flags.process_pending) { + //There is currently processing that needs to be done. Wait for some processing + HOST_EXIT_CRITICAL(); + BaseType_t sem_ret = xSemaphoreTake(p_host_lib_obj->constant.event_sem, timeout_ticks); + if (sem_ret == pdFALSE) { + ret = ESP_ERR_TIMEOUT; + goto exit; + } + HOST_ENTER_CRITICAL(); + } + //Read and clear process pending flags + uint32_t process_pending_flags = p_host_lib_obj->dynamic.process_pending_flags; + p_host_lib_obj->dynamic.process_pending_flags = 0; + p_host_lib_obj->dynamic.flags.handling_events = 1; + while (process_pending_flags) { + HOST_EXIT_CRITICAL(); + if (process_pending_flags & PROCESS_PENDING_FLAG_USBH) { + ESP_ERROR_CHECK(usbh_process()); + } + if (process_pending_flags & PROCESS_PENDING_FLAG_HUB) { + ESP_ERROR_CHECK(hub_process()); + } + HOST_ENTER_CRITICAL(); + //Read and clear process pending flags again, and loop back if there is more to process + process_pending_flags = p_host_lib_obj->dynamic.process_pending_flags; + p_host_lib_obj->dynamic.process_pending_flags = 0; + } + p_host_lib_obj->dynamic.flags.process_pending = 0; + p_host_lib_obj->dynamic.flags.handling_events = 0; + event_flags = p_host_lib_obj->dynamic.lib_event_flags; + p_host_lib_obj->dynamic.lib_event_flags = 0; + HOST_EXIT_CRITICAL(); + + ret = ESP_OK; +exit: + if (event_flags_ret != NULL) { + *event_flags_ret = event_flags; + } + return ret; +} + +// ------------------------------------------------ Client Functions --------------------------------------------------- + +// ----------------------- Private ------------------------- + +static void _handle_pending_ep(client_t *client_obj) +{ + //Handle each EP on the pending list + while (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq)) { + //Get the next pending EP. + endpoint_t *ep_obj = TAILQ_FIRST(&client_obj->dynamic.pending_ep_tailq); + TAILQ_REMOVE(&client_obj->dynamic.pending_ep_tailq, ep_obj, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, ep_obj, dynamic.tailq_entry); + ep_obj->dynamic.flags.pending = 0; + hcd_pipe_event_t last_event = ep_obj->dynamic.last_event; + uint32_t num_urb_dequeued = 0; + + HOST_EXIT_CRITICAL(); + //Handle pipe event + switch (last_event) { + case HCD_PIPE_EVENT_ERROR_XFER: + case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + case HCD_PIPE_EVENT_ERROR_STALL: + //The pipe is now stalled. Flush all pending URBs + ESP_ERROR_CHECK(hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH)); + //All URBs in this pipe are now retired waiting to be dequeued. Fall through to dequeue them + __attribute__((fallthrough)); + case HCD_PIPE_EVENT_URB_DONE: { + //Dequeue all URBs and run their transfer callback + urb_t *urb = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); + while (urb != NULL) { + urb->transfer.callback(&urb->transfer); + num_urb_dequeued++; + urb = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); + } + break; + } + default: + abort(); //Should never occur + break; + } + HOST_ENTER_CRITICAL(); + + //Update the endpoint's number of URB's inflight + assert(num_urb_dequeued <= ep_obj->dynamic.num_urb_inflight); + ep_obj->dynamic.num_urb_inflight -= num_urb_dequeued; + } +} + +// ----------------------- Public -------------------------- + +esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret) +{ + HOST_CHECK(client_config != NULL && client_hdl_ret != NULL, ESP_ERR_INVALID_ARG); + HOST_CHECK(client_config->client_event_callback != NULL && client_config->max_num_event_msg > 0, ESP_ERR_INVALID_ARG); + + esp_err_t ret; + //Create client object + client_t *client_obj = heap_caps_calloc(1, sizeof(client_t), MALLOC_CAP_DEFAULT); + SemaphoreHandle_t event_sem = xSemaphoreCreateBinary(); + QueueHandle_t event_msg_queue = xQueueCreate(client_config->max_num_event_msg, sizeof(usb_host_client_event_msg_t)); + if (client_obj == NULL || event_sem == NULL || event_msg_queue == NULL) { + ret = ESP_ERR_NO_MEM; + goto alloc_err; + } + //Initialize client object + TAILQ_INIT(&client_obj->dynamic.pending_ep_tailq); + TAILQ_INIT(&client_obj->dynamic.idle_ep_tailq); + TAILQ_INIT(&client_obj->mux_protected.interface_tailq); + TAILQ_INIT(&client_obj->dynamic.done_ctrl_xfer_tailq); + client_obj->constant.event_sem = event_sem; + client_obj->constant.event_callback = client_config->client_event_callback; + client_obj->constant.callback_arg = client_config->callback_arg; + client_obj->constant.event_msg_queue = event_msg_queue; + + //Add client to the host library's list of clients + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + HOST_ENTER_CRITICAL(); + p_host_lib_obj->dynamic.flags.num_clients++; + HOST_EXIT_CRITICAL(); + TAILQ_INSERT_TAIL(&p_host_lib_obj->mux_protected.client_tailq, client_obj, dynamic.tailq_entry); + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); + + //Write back client handle + *client_hdl_ret = (usb_host_client_handle_t)client_obj; + ret = ESP_OK; + return ret; + +alloc_err: + if (event_msg_queue) { + vQueueDelete(event_msg_queue); + } + if (event_sem) { + vSemaphoreDelete(event_sem); + } + heap_caps_free(client_obj); + return ESP_OK; +} + +esp_err_t usb_host_client_deregister(usb_host_client_handle_t client_hdl) +{ + HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + esp_err_t ret; + + //We take the mux_lock because we need to access the host library's client_tailq + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + HOST_ENTER_CRITICAL(); + //Check that client can currently deregistered + bool can_deregister; + if (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq) || + !TAILQ_EMPTY(&client_obj->dynamic.idle_ep_tailq) || + !TAILQ_EMPTY(&client_obj->dynamic.done_ctrl_xfer_tailq) || + client_obj->dynamic.flags.handling_events || + client_obj->dynamic.flags.blocked || + client_obj->dynamic.flags.taking_mux || + client_obj->dynamic.flags.num_intf_claimed != 0 || + client_obj->dynamic.num_done_ctrl_xfer != 0 || + client_obj->dynamic.opened_dev_addr_map != 0) { + can_deregister = false; + } else { + can_deregister = true; + } + HOST_EXIT_CRITICAL(); + if (!can_deregister) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + + //Remove client object from the library's list of clients + TAILQ_REMOVE(&p_host_lib_obj->mux_protected.client_tailq, client_obj, dynamic.tailq_entry); + HOST_ENTER_CRITICAL(); + p_host_lib_obj->dynamic.flags.num_clients--; + if (p_host_lib_obj->dynamic.flags.num_clients == 0) { + //This is the last client being deregistered. Notify the lib handler + p_host_lib_obj->dynamic.lib_event_flags |= USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS; + _unblock_lib(false); + } + HOST_EXIT_CRITICAL(); + //Free client object + vQueueDelete(client_obj->constant.event_msg_queue); + vSemaphoreDelete(client_obj->constant.event_sem); + heap_caps_free(client_obj); + ret = ESP_OK; +exit: + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); + return ret; +} + +esp_err_t usb_host_client_handle_events(usb_host_client_handle_t client_hdl, TickType_t timeout_ticks) +{ + HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + client_t *client_obj = (client_t *)client_hdl; + + HOST_ENTER_CRITICAL(); + if (!client_obj->dynamic.flags.events_pending) { + //There are currently no events, wait for one to occur + client_obj->dynamic.flags.blocked = 1; + HOST_EXIT_CRITICAL(); + BaseType_t sem_ret = xSemaphoreTake(client_obj->constant.event_sem, timeout_ticks); + HOST_ENTER_CRITICAL(); + client_obj->dynamic.flags.blocked = 0; + if (sem_ret == pdFALSE) { + HOST_EXIT_CRITICAL(); + //Timed out waiting for semaphore + ret = ESP_ERR_TIMEOUT; + goto exit; + } + } + //Mark that we're processing events + client_obj->dynamic.flags.handling_events = 1; + while (client_obj->dynamic.flags.handling_events) { + //Handle pending endpoints + if (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq)) { + _handle_pending_ep(client_obj); + } + //Handle any done control transfers + while (client_obj->dynamic.num_done_ctrl_xfer > 0) { + urb_t *urb = TAILQ_FIRST(&client_obj->dynamic.done_ctrl_xfer_tailq); + TAILQ_REMOVE(&client_obj->dynamic.done_ctrl_xfer_tailq, urb, tailq_entry); + client_obj->dynamic.num_done_ctrl_xfer--; + HOST_EXIT_CRITICAL(); + //Call the transfer's callback + urb->transfer.callback(&urb->transfer); + HOST_ENTER_CRITICAL(); + } + //Handle event messages + while (uxQueueMessagesWaiting(client_obj->constant.event_msg_queue) > 0) { + HOST_EXIT_CRITICAL(); + //Dequeue the event message and call the client event callback + usb_host_client_event_msg_t event_msg; + BaseType_t queue_ret = xQueueReceive(client_obj->constant.event_msg_queue, &event_msg, 0); + assert(queue_ret == pdTRUE); + client_obj->constant.event_callback(&event_msg, client_obj->constant.callback_arg); + HOST_ENTER_CRITICAL(); + } + //Check each event again to see any new events occurred + if (TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq) && + client_obj->dynamic.num_done_ctrl_xfer == 0 && + uxQueueMessagesWaiting(client_obj->constant.event_msg_queue) == 0) { + //All pending endpoints and event messages handled + client_obj->dynamic.flags.events_pending = 0; + client_obj->dynamic.flags.handling_events = 0; + } + } + HOST_EXIT_CRITICAL(); + + ret = ESP_OK; +exit: + return ret; +} + +esp_err_t usb_host_client_unblock(usb_host_client_handle_t client_hdl) +{ + HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + + HOST_ENTER_CRITICAL(); + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); + + return ESP_OK; +} + +// ------------------------------------------------- Device Handling --------------------------------------------------- + +esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_addr, usb_device_handle_t *dev_hdl_ret) +{ + HOST_CHECK(dev_addr > 0 && client_hdl != NULL && dev_hdl_ret != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + + esp_err_t ret; + usb_device_handle_t dev_hdl; + ret = usbh_dev_open(dev_addr, &dev_hdl); + if (ret != ESP_OK) { + goto exit; + } + + HOST_ENTER_CRITICAL(); + if (_check_client_opened_device(client_obj, dev_addr)) { + //Client has already opened the device. Close it and return an error + ret = ESP_ERR_INVALID_STATE; + HOST_EXIT_CRITICAL(); + goto already_opened; + } + //Record in client object that we have opened the device of this address + _record_client_opened_device(client_obj, dev_addr); + HOST_EXIT_CRITICAL(); + + *dev_hdl_ret = dev_hdl; + ret = ESP_OK; + return ret; + +already_opened: + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); +exit: + return ret; +} + +esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl) +{ + HOST_CHECK(dev_hdl != NULL && client_hdl != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + + //We take the lock because we need to walk the interface list + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + esp_err_t ret; + //Check that all interfaces claimed by this client do not belong to this device + bool all_released = true; + interface_t *intf_obj; + TAILQ_FOREACH(intf_obj, &client_obj->mux_protected.interface_tailq, mux_protected.tailq_entry) { + if (intf_obj->constant.dev_hdl == dev_hdl) { + all_released = false; + break; + } + } + if (!all_released) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + + //Check that client actually opened the device in the first place + HOST_ENTER_CRITICAL(); + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_NOT_FOUND); + if (!_check_client_opened_device(client_obj, dev_addr)) { + //Client never opened this device + ret = ESP_ERR_INVALID_STATE; + HOST_EXIT_CRITICAL(); + goto exit; + } + //Proceed to clear the record of the device form the client + _clear_client_opened_device(client_obj, dev_addr); + HOST_EXIT_CRITICAL(); + + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + ret = ESP_OK; +exit: + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); + return ret; +} + +esp_err_t usb_host_device_free_all(void) +{ + HOST_ENTER_CRITICAL(); + HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.flags.num_clients == 0, ESP_ERR_INVALID_STATE); //All clients must have been deregistered + HOST_EXIT_CRITICAL(); + esp_err_t ret; + ret = usbh_dev_mark_all_free(); + //Wait for USB_HOST_LIB_EVENT_FLAGS_ALL_FREE to confirm all devices free + return ret; +} + +// ------------------------------------------------- Device Requests --------------------------------------------------- + +// ------------------- Cached Requests --------------------- + +esp_err_t usb_host_device_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info) +{ + HOST_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG); + return usbh_dev_get_info(dev_hdl, dev_info); +} + +// ----------------------------------------------- Descriptor Requests ------------------------------------------------- + +// ----------------- Cached Descriptors -------------------- + +esp_err_t usb_host_get_device_descriptor(usb_device_handle_t dev_hdl, const usb_device_desc_t **device_desc) +{ + HOST_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); + return usbh_dev_get_desc(dev_hdl, device_desc); +} + +esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc) +{ + HOST_CHECK(dev_hdl != NULL && config_desc != NULL, ESP_ERR_INVALID_ARG); + return usbh_dev_get_config_desc(dev_hdl, config_desc); +} + +// ----------------------------------------------- Interface Functions ------------------------------------------------- + +// ----------------------- Private ------------------------- + +static esp_err_t endpoint_alloc(usb_device_handle_t dev_hdl, const usb_ep_desc_t *ep_desc, interface_t *intf_obj, endpoint_t **ep_obj_ret) +{ + endpoint_t *ep_obj = heap_caps_calloc(1, sizeof(endpoint_t), MALLOC_CAP_DEFAULT); + if (ep_obj == NULL) { + return ESP_ERR_NO_MEM; + } + esp_err_t ret; + usbh_ep_config_t ep_config = { + .ep_desc = ep_desc, + .pipe_cb = pipe_callback, + .pipe_cb_arg = (void *)ep_obj, + .context = (void *)ep_obj, + }; + hcd_pipe_handle_t pipe_hdl; + ret = usbh_ep_alloc(dev_hdl, &ep_config, &pipe_hdl); + if (ret != ESP_OK) { + goto ep_alloc_err; + } + //Initialize endpoint object + ep_obj->constant.pipe_hdl = pipe_hdl; + ep_obj->constant.ep_desc = ep_desc; + ep_obj->constant.intf_obj = intf_obj; + //Write back result + *ep_obj_ret = ep_obj; + ret = ESP_OK; + return ret; + +ep_alloc_err: + heap_caps_free(ep_obj); + return ret; +} + +static void endpoint_free(usb_device_handle_t dev_hdl, endpoint_t *ep_obj) +{ + if (ep_obj == NULL) { + return; + } + //Free the underlying endpoint + ESP_ERROR_CHECK(usbh_ep_free(dev_hdl, ep_obj->constant.ep_desc->bEndpointAddress)); + //Free the endpoint object + heap_caps_free(ep_obj); +} + +static interface_t *interface_alloc(client_t *client_obj, usb_device_handle_t dev_hdl, const usb_intf_desc_t *intf_desc) +{ + interface_t *intf_obj = heap_caps_calloc(1, sizeof(interface_t) + (sizeof(endpoint_t *) * intf_desc->bNumEndpoints), MALLOC_CAP_DEFAULT); + if (intf_obj == NULL) { + return NULL; + } + intf_obj->constant.intf_desc = intf_desc; + intf_obj->constant.client_obj = client_obj; + intf_obj->constant.dev_hdl = dev_hdl; + return intf_obj; +} + +static void interface_free(interface_t *intf_obj) +{ + if (intf_obj == NULL) { + return; + } + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + assert(intf_obj->constant.endpoints[i] == NULL); + } + heap_caps_free(intf_obj); +} + +static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, interface_t **intf_obj_ret) +{ + esp_err_t ret; + //We need to walk to configuration descriptor to find the correct interface descriptor, and each of its constituent endpoint descriptors + //Find the interface descriptor and allocate the interface object + int offset_intf; + const usb_intf_desc_t *intf_desc = usb_host_parse_interface(config_desc, bInterfaceNumber, bAlternateSetting, &offset_intf); + if (intf_desc == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto exit; + } + //Allocate interface object + interface_t *intf_obj = interface_alloc(client_obj, dev_hdl, intf_desc); + if (intf_obj == NULL) { + ret = ESP_ERR_NO_MEM; + goto exit; + } + //Find each endpoint descriptor in the interface by index, and allocate those endpoints + for (int i = 0; i < intf_desc->bNumEndpoints; i++) { + int offset_ep = offset_intf; + const usb_ep_desc_t *ep_desc = usb_host_parse_endpoint_by_index(intf_desc, i, config_desc->wTotalLength, &offset_ep); + if (ep_desc == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto ep_alloc_err; + } + //Allocate the endpoint + endpoint_t *ep_obj; + ret = endpoint_alloc(dev_hdl, ep_desc, intf_obj, &ep_obj); + if (ret != ESP_OK) { + goto ep_alloc_err; + } + //Store endpoint object into interface object + intf_obj->constant.endpoints[i] = ep_obj; + } + //Add interface object to client (safe because we have already taken the mutex) + TAILQ_INSERT_TAIL(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry); + //Add each endpoint to the client's endpoint list + HOST_ENTER_CRITICAL(); + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry); + } + HOST_EXIT_CRITICAL(); + //Write back result + *intf_obj_ret = intf_obj; + ret = ESP_OK; + return ret; + +ep_alloc_err: + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + endpoint_free(dev_hdl, intf_obj->constant.endpoints[i]); + intf_obj->constant.endpoints[i] = NULL; + } + interface_free(intf_obj); +exit: + return ret; +} + +static esp_err_t interface_release(client_t *client_obj, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber) +{ + esp_err_t ret; + //Find the interface object + interface_t *intf_obj_iter; + interface_t *intf_obj = NULL; + TAILQ_FOREACH(intf_obj_iter, &client_obj->mux_protected.interface_tailq, mux_protected.tailq_entry) { + if (intf_obj_iter->constant.dev_hdl == dev_hdl && intf_obj_iter->constant.intf_desc->bInterfaceNumber == bInterfaceNumber) { + intf_obj = intf_obj_iter; + break; + } + } + if (intf_obj == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto exit; + } + + //Check that all endpoints in the interface are in a state to be freed + HOST_ENTER_CRITICAL(); + bool can_free = true; + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + endpoint_t *ep_obj = intf_obj->constant.endpoints[i]; + //Endpoint must not be on the pending list and must not have inflight URBs + if (ep_obj->dynamic.num_urb_inflight != 0 || ep_obj->dynamic.flags.pending) { + can_free = false; + break; + } + } + if (!can_free) { + HOST_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + //Proceed to remove all endpoint objects from list + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry); + } + HOST_EXIT_CRITICAL(); + + //Remove the interface object from the list (safe because we have already taken the mutex) + TAILQ_REMOVE(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry); + + //Free each endpoint in the interface + for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + endpoint_free(dev_hdl, intf_obj->constant.endpoints[i]); + intf_obj->constant.endpoints[i] = NULL; + } + //Free the interface object itself + interface_free(intf_obj); + ret = ESP_OK; +exit: + return ret; +} + +// ----------------------- Public -------------------------- + +esp_err_t usb_host_interface_claim(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber, uint8_t bAlternateSetting) +{ + HOST_CHECK(client_hdl != NULL && dev_hdl != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + + HOST_ENTER_CRITICAL(); + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + //Check if client actually opened device + HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_INVALID_STATE); + client_obj->dynamic.flags.taking_mux = 1; + HOST_EXIT_CRITICAL(); + + //Take mux lock. This protects the client being released or other clients from claiming interfaces + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + esp_err_t ret; + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK(usbh_dev_get_config_desc(dev_hdl, &config_desc)); + interface_t *intf_obj; + //Claim interface + ret = interface_claim(client_obj, dev_hdl, config_desc, bInterfaceNumber, bAlternateSetting, &intf_obj); + if (ret != ESP_OK) { + goto exit; + } + ret = ESP_OK; +exit: + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); + + HOST_ENTER_CRITICAL(); + if (ret == ESP_OK) { + client_obj->dynamic.flags.num_intf_claimed++; + } + client_obj->dynamic.flags.taking_mux = 0; + HOST_EXIT_CRITICAL(); + return ret; +} + +esp_err_t usb_host_interface_release(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber) +{ + HOST_CHECK(client_hdl != NULL && dev_hdl != NULL, ESP_ERR_INVALID_ARG); + client_t *client_obj = (client_t *)client_hdl; + + HOST_ENTER_CRITICAL(); + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + //Check if client actually opened device + HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_INVALID_STATE); + client_obj->dynamic.flags.taking_mux = 1; + HOST_EXIT_CRITICAL(); + + //Take mux lock. This protects the client being released or other clients from claiming interfaces + xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY); + esp_err_t ret = interface_release(client_obj, dev_hdl, bInterfaceNumber); + xSemaphoreGive(p_host_lib_obj->constant.mux_lock); + + HOST_ENTER_CRITICAL(); + if (ret == ESP_OK) { + client_obj->dynamic.flags.num_intf_claimed--; + } + client_obj->dynamic.flags.taking_mux = 0; + HOST_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usb_host_endpoint_halt(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) +{ + esp_err_t ret; + endpoint_t *ep_obj = NULL; + ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + if (ret != ESP_OK) { + goto exit; + } + assert(ep_obj != NULL); + ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_HALT); +exit: + return ret; +} + +esp_err_t usb_host_endpoint_flush(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) +{ + esp_err_t ret; + endpoint_t *ep_obj = NULL; + ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + if (ret != ESP_OK) { + goto exit; + } + assert(ep_obj != NULL); + ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH); +exit: + return ret; +} + +esp_err_t usb_host_endpoint_clear(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) +{ + esp_err_t ret; + endpoint_t *ep_obj = NULL; + ret = usbh_ep_get_context(dev_hdl, bEndpointAddress, (void **)&ep_obj); + if (ret != ESP_OK) { + goto exit; + } + assert(ep_obj != NULL); + ret = hcd_pipe_command(ep_obj->constant.pipe_hdl, HCD_PIPE_CMD_CLEAR); +exit: + return ret; +} + +// ------------------------------------------------ Asynchronous I/O --------------------------------------------------- + +// ----------------------- Private ------------------------- + +static bool transfer_check(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in) +{ + if (transfer->callback == NULL) { + ESP_LOGE(USB_HOST_TAG, "Transfer callback is NULL"); + return false; + } + //Check that the total transfer length does not exceed data buffer size + if (transfer->num_bytes > transfer->data_buffer_size) { + ESP_LOGE(USB_HOST_TAG, "Transfer num_bytes > data_buffer_size"); + return false; + } + if (type == USB_TRANSFER_TYPE_CTRL) { + //Check that num_bytes and wLength are set correctly + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; + if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) { + ESP_LOGE(USB_HOST_TAG, "Control transfer num_bytes wLength mismatch"); + return false; + } + } else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) { + //Check that there is at least one isochronous packet descriptor + if (transfer->num_isoc_packets <= 0) { + ESP_LOGE(USB_HOST_TAG, "ISOC transfer has 0 packet descriptors"); + return false; + } + //Check that sum of all packet lengths add up to transfer length + //If IN, check that each packet length is integer multiple of MPS + int total_num_bytes = 0; + bool mod_mps_all_zero = true; + for (int i = 0; i < transfer->num_isoc_packets; i++) { + total_num_bytes += transfer->isoc_packet_desc[i].num_bytes; + if (transfer->isoc_packet_desc[i].num_bytes % mps != 0) { + mod_mps_all_zero = false; + } + } + if (transfer->num_bytes != total_num_bytes) { + ESP_LOGE(USB_HOST_TAG, "ISOC transfer num_bytes not equal to total num_bytes of all packets"); + return false; + } + if (is_in && !mod_mps_all_zero) { + ESP_LOGE(USB_HOST_TAG, "ISOC IN transfer all packets num_bytes must be integer multiple of MPS"); + return false; + } + } else { + //Check that IN transfers are integer multiple of MPS + if (is_in && (transfer->num_bytes % mps != 0)) { + ESP_LOGE(USB_HOST_TAG, "IN transfer num_bytes must be integer multiple of MPS"); + return false; + } + } + return true; +} + +// ----------------------- Public -------------------------- + +esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer) +{ + urb_t *urb = urb_alloc(data_buffer_size, 0, num_isoc_packets); + if (urb == NULL) { + return ESP_ERR_NO_MEM; + } + *transfer = &urb->transfer; + return ESP_OK; +} + +esp_err_t usb_host_transfer_free(usb_transfer_t *transfer) +{ + HOST_CHECK(transfer != NULL, ESP_ERR_INVALID_ARG); + urb_t *urb = __containerof(transfer, urb_t, transfer); + urb_free(urb); + return ESP_OK; +} + +esp_err_t usb_host_transfer_submit(usb_transfer_t *transfer) +{ + HOST_CHECK(transfer != NULL, ESP_ERR_INVALID_ARG); + //Check that transfer and target endpoint are valid + HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); //Target device must be set + HOST_CHECK((transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) != 0, ESP_ERR_INVALID_ARG); + endpoint_t *ep_obj = NULL; + urb_t *urb_obj = __containerof(transfer, urb_t, transfer); + esp_err_t ret; + ret = usbh_ep_get_context(transfer->device_handle, transfer->bEndpointAddress, (void **)&ep_obj); + if (ret != ESP_OK) { + goto err; + } + assert(ep_obj != NULL); + HOST_CHECK(transfer_check(transfer, + USB_EP_DESC_GET_XFERTYPE(ep_obj->constant.ep_desc), + USB_EP_DESC_GET_MPS(ep_obj->constant.ep_desc), + transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK), ESP_ERR_INVALID_ARG); + HOST_ENTER_CRITICAL(); + ep_obj->dynamic.num_urb_inflight++; + HOST_EXIT_CRITICAL(); + //Check if pipe is in a state to enqueue URBs + if (hcd_pipe_get_state(ep_obj->constant.pipe_hdl) != HCD_PIPE_STATE_ACTIVE) { + ret = ESP_ERR_INVALID_STATE; + goto hcd_err; + } + ret = hcd_urb_enqueue(ep_obj->constant.pipe_hdl, urb_obj); + if (ret != ESP_OK) { + goto hcd_err; + } + ret = ESP_OK; + return ret; + +hcd_err: + HOST_ENTER_CRITICAL(); + ep_obj->dynamic.num_urb_inflight--; + HOST_EXIT_CRITICAL(); +err: + return ret; +} + +esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, usb_transfer_t *transfer) +{ + HOST_CHECK(client_hdl != NULL && transfer != NULL, ESP_ERR_INVALID_ARG); + //Check that control transfer is valid + HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); //Target device must be set + usb_device_handle_t dev_hdl = transfer->device_handle; + bool xfer_is_in = ((usb_setup_packet_t *)transfer->data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_OUT; + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(dev_hdl, &dev_info)); + HOST_CHECK(transfer_check(transfer, USB_TRANSFER_TYPE_CTRL, dev_info.bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); + HOST_CHECK(transfer->bEndpointAddress == 0, ESP_ERR_INVALID_ARG); + //Save client handle into URB + urb_t *urb_obj = __containerof(transfer, urb_t, transfer); + urb_obj->usb_host_client = (void *)client_hdl; + return usbh_dev_submit_ctrl_urb(dev_hdl, urb_obj); +} diff --git a/components/usb/usb_host_misc.c b/components/usb/usb_host_misc.c new file mode 100644 index 0000000000..072b547e31 --- /dev/null +++ b/components/usb/usb_host_misc.c @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "usb/usb_host_misc.h" +#include "usb/usb_types_ch9.h" + +// ---------------------------------------- Configuration Descriptor Parsing ------------------------------------------- + +const usb_standard_desc_t *usb_host_parse_next_descriptor(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, int *offset) +{ + assert(cur_desc != NULL && offset != NULL); + if (*offset >= wTotalLength) { + return NULL; //We have traversed the entire configuration descriptor + } + if (*offset + cur_desc->bLength >= wTotalLength) { + return NULL; //Next descriptor is out of bounds + } + //Return the next descriptor, update offset + const usb_standard_desc_t *ret_desc = (const usb_standard_desc_t *)(((uint32_t)cur_desc) + cur_desc->bLength); + *offset += cur_desc->bLength; + return ret_desc; +} + +const usb_standard_desc_t *usb_host_parse_next_descriptor_of_type(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, uint8_t bDescriptorType, int *offset) +{ + assert(cur_desc != NULL && offset != NULL); + int offset_temp = *offset; //We only want to update offset if we've actually found a descriptor + //Keep stepping over descriptors until we find one of bDescriptorType or until we go out of bounds + const usb_standard_desc_t *ret_desc = usb_host_parse_next_descriptor(cur_desc, wTotalLength, &offset_temp); + while (ret_desc != NULL) { + if (ret_desc->bDescriptorType == bDescriptorType) { + break; + } + ret_desc = usb_host_parse_next_descriptor(ret_desc, wTotalLength, &offset_temp); + } + if (ret_desc != NULL) { + //We've found a descriptor. Update the offset + *offset = offset_temp; + } + return ret_desc; +} + +int usb_host_parse_interface_number_of_alternate(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber) +{ + assert(config_desc != NULL); + int offset = 0; + //Find the first interface descriptor of bInterfaceNumber + const usb_intf_desc_t *first_intf_desc = usb_host_parse_interface(config_desc, bInterfaceNumber, 0, &offset); + if (first_intf_desc == NULL) { + return -1; //bInterfaceNumber not found + } + + int num_alt_setting = 0; + const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)first_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset); + while (next_intf_desc != NULL) { + if (next_intf_desc->bInterfaceNumber != bInterfaceNumber) { + break; + } + num_alt_setting++; + next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset); + } + return num_alt_setting; +} + +const usb_intf_desc_t *usb_host_parse_interface(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, int *offset) +{ + assert(config_desc != NULL); + if (bInterfaceNumber >= config_desc->bNumInterfaces) { + return NULL; //bInterfaceNumber is out of range + } + + //Walk to first interface descriptor of bInterfaceNumber + int offset_temp = 0; + const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)config_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp); + while (next_intf_desc != NULL) { + if (next_intf_desc->bInterfaceNumber == bInterfaceNumber) { + break; //We found the first interface descriptor with matching bInterfaceNumber + } + next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp); + } + if (next_intf_desc == NULL) { + return NULL; //Couldn't find a interface with bInterfaceNumber + } + + //Keep walking until an interface descriptor matching bInterfaceNumber and bAlternateSetting is found + while (next_intf_desc != NULL) { + if (next_intf_desc->bInterfaceNumber == bInterfaceNumber + 1) { + //We've walked past our target bInterfaceNumber + next_intf_desc = NULL; + break; + } + if (next_intf_desc->bAlternateSetting == bAlternateSetting) { + //We've found our target interface descriptor + break; + } + //Get the next interface descriptor + next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp); + } + if (next_intf_desc != NULL && offset != NULL) { + *offset = offset_temp; + } + return next_intf_desc; +} + +const usb_ep_desc_t *usb_host_parse_endpoint_by_index(const usb_intf_desc_t *intf_desc, int index, uint16_t wTotalLength, int *offset) +{ + assert(intf_desc != NULL && offset != NULL); + if (index >= intf_desc->bNumEndpoints) { + return NULL; //Index is out of range + } + //Walk to the Nth endpoint descriptor we find + int offset_temp = *offset; + bool ep_found = true; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)intf_desc; + for (int i = 0; i <= index; i++) { + next_desc = usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_desc, wTotalLength, USB_B_DESCRIPTOR_TYPE_ENDPOINT, &offset_temp); + if (next_desc == NULL) { + ep_found = false; + break; + } + } + if (ep_found) { + *offset = offset_temp; + return (const usb_ep_desc_t *)next_desc; + } + return NULL; +} + +const usb_ep_desc_t *usb_host_parse_endpoint_by_address(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, uint8_t bEndpointAddress, int *offset) +{ + assert(config_desc != NULL); + + //Find the interface descriptor + int offset_intf; + const usb_intf_desc_t *intf_desc = usb_host_parse_interface(config_desc, bInterfaceNumber, bAlternateSetting, &offset_intf); + if (intf_desc == NULL) { + return NULL; + } + + //Walk endpoint descriptors until one matching bEndpointAddress is found + int offset_ep; + bool ep_found = false; + const usb_ep_desc_t *ep_desc = NULL; + for (int index = 0; index < intf_desc->bNumEndpoints; index++) { + offset_ep = offset_intf; + ep_desc = usb_host_parse_endpoint_by_index(intf_desc, index, config_desc->wTotalLength, &offset_ep); + if (ep_desc == NULL) { + break; + } + if (ep_desc->bEndpointAddress == bEndpointAddress) { + ep_found = true; + break; + } + } + if (ep_found && offset != NULL) { + *offset = offset_ep; + } + return ep_desc; +} + +// ------------------------------------------------------ Misc --------------------------------------------------------- diff --git a/components/usb/usb_private.c b/components/usb/usb_private.c new file mode 100644 index 0000000000..1ab0448a6f --- /dev/null +++ b/components/usb/usb_private.c @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_heap_caps.h" +#include "usb_private.h" +#include "usb/usb_types_ch9.h" + +urb_t *urb_alloc(size_t data_buffer_size, size_t header_size, int num_isoc_packets) +{ + urb_t *urb = heap_caps_calloc(1, sizeof(urb_t) + (sizeof(usb_isoc_packet_desc_t) * num_isoc_packets), MALLOC_CAP_DEFAULT); + uint8_t *data_buffer = heap_caps_malloc(data_buffer_size + header_size, MALLOC_CAP_DMA); + if (urb == NULL || data_buffer == NULL) { + goto err; + } + urb->usb_host_header_size = header_size; //Indicate that this URB's data_buffer has a header in front of it. + //Case as dummy transfer to write to initialize const fields + usb_transfer_dummy_t *dummy_transfer = (usb_transfer_dummy_t *)&urb->transfer; + dummy_transfer->data_buffer = (uint8_t *)(data_buffer + header_size); + dummy_transfer->data_buffer_size = data_buffer_size; + dummy_transfer->num_isoc_packets = num_isoc_packets; + return urb; +err: + heap_caps_free(urb); + heap_caps_free(data_buffer); + return NULL; +} + +void urb_free(urb_t *urb) +{ + if (urb == NULL) { + return; + } + heap_caps_free((uint8_t *)(urb->transfer.data_buffer - urb->usb_host_header_size)); + heap_caps_free(urb); +} diff --git a/components/usb/usbh.c b/components/usb/usbh.c new file mode 100644 index 0000000000..d05cf6a000 --- /dev/null +++ b/components/usb/usbh.c @@ -0,0 +1,909 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "hcd.h" +#include "usbh.h" +#include "usb/usb_host_misc.h" +#include "usb/usb_types_ch9.h" + +//Device action flags. Listed in the order they should handled in. Some actions are mutually exclusive +#define DEV_FLAG_ACTION_SEND_GONE_EVENT 0x01 //Send a USB_HOST_CLIENT_EVENT_DEV_GONE event +#define DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH 0x02 //Retire all URBS in the default pipe +#define DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE 0x04 //Dequeue all URBs from default pipe +#define DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR 0x08 //Move the default pipe to the active state +#define DEV_FLAG_ACTION_FREE 0x10 //Free the device object +#define DEV_FLAG_ACTION_PORT_DISABLE 0x20 +#define DEV_FLAG_ACTION_SEND_NEW 0x40 //Send a new device event + +#define DEV_ENUM_TODO_FLAG_DEV_ADDR 0x01 +#define DEV_ENUM_TODO_FLAG_DEV_DESC 0x02 +#define DEV_ENUM_TODO_FLAG_CONFIG_DESC 0x04 + +#define EP_NUM_MIN 1 +#define EP_NUM_MAX 16 + +typedef struct device_s device_t; +struct device_s { + //Dynamic members require a critical section + struct { + TAILQ_ENTRY(device_s) tailq_entry; + union { + struct { + uint32_t actions: 8; + uint32_t in_pending_list: 1; + uint32_t is_gone: 1; + uint32_t waiting_close: 1; + uint32_t waiting_port_disable: 1; + uint32_t waiting_free: 1; + uint32_t reserved19: 19; + }; + uint32_t val; + } flags; + int num_ctrl_xfers_inflight; + usb_device_state_t state; + uint32_t ref_count; + usb_config_desc_t *config_desc; + hcd_pipe_handle_t ep_in[EP_NUM_MAX - 1]; //IN EP owner contexts. -1 to exclude the default endpoint + hcd_pipe_handle_t ep_out[EP_NUM_MAX - 1]; //OUT EP owner contexts. -1 to exclude the default endpoint + } dynamic; + //Constant members do no change after device allocation and enumeration thus do not require a critical section + struct { + hcd_pipe_handle_t default_pipe; + hcd_port_handle_t port_hdl; + uint8_t address; + usb_speed_t speed; + const usb_device_desc_t *desc; + uint32_t enum_todo_flags; + } constant; +}; + +typedef struct { + //Dynamic members require a critical section + struct { + TAILQ_HEAD(tailhead_devs, device_s) devs_idle_tailq; //Tailq of all enum and configured devices + TAILQ_HEAD(tailhead_devs_cb, device_s) devs_pending_tailq; //Tailq of devices that need to have their cb called + uint8_t num_device; //Number of enumerated devices + } dynamic; + //Constant members do no change after installation thus do not require a critical section + struct { + usb_notif_cb_t notif_cb; + void *notif_cb_arg; + usbh_hub_cb_t hub_cb; + void *hub_cb_arg; + usbh_event_cb_t event_cb; + void *event_cb_arg; + usbh_ctrl_xfer_cb_t ctrl_xfer_cb; + void *ctrl_xfer_cb_arg; + } constant; +} usbh_t; + +static usbh_t *p_usbh_obj = NULL; + +static portMUX_TYPE usbh_lock = portMUX_INITIALIZER_UNLOCKED; + +const char *USBH_TAG = "USBH"; + +#define USBH_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&usbh_lock) +#define USBH_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&usbh_lock) +#define USBH_ENTER_CRITICAL() portENTER_CRITICAL(&usbh_lock) +#define USBH_EXIT_CRITICAL() portEXIT_CRITICAL(&usbh_lock) +#define USBH_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&usbh_lock) +#define USBH_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&usbh_lock) + +#define USBH_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define USBH_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + USBH_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// --------------------------------------------------- Allocation ------------------------------------------------------ + +static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, device_t **dev_obj_ret) +{ + esp_err_t ret; + device_t *dev_obj = heap_caps_calloc(1, sizeof(device_t), MALLOC_CAP_DEFAULT); + usb_device_desc_t *dev_desc = heap_caps_calloc(1, sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT); + if (dev_obj == NULL || dev_desc == NULL) { + ret = ESP_ERR_NO_MEM; + goto err; + } + //Allocate default pipe. We set the pipe callback to NULL for now + hcd_pipe_config_t pipe_config = { + .callback = NULL, + .callback_arg = NULL, + .context = (void *)dev_obj, + .ep_desc = NULL, //No endpoint descriptor means we're allocating a default pipe + .dev_speed = speed, + .dev_addr = 0, + }; + hcd_pipe_handle_t default_pipe_hdl; + ret = hcd_pipe_alloc(port_hdl, &pipe_config, &default_pipe_hdl); + if (ret != ESP_OK) { + goto err; + } + //Initialize device object + dev_obj->dynamic.state = USB_DEVICE_STATE_DEFAULT; + dev_obj->constant.default_pipe = default_pipe_hdl; + dev_obj->constant.port_hdl = port_hdl; + //Note: dev_obj->constant.address is assigned later during enumeration + dev_obj->constant.speed = speed; + dev_obj->constant.desc = dev_desc; + dev_obj->constant.enum_todo_flags = (DEV_ENUM_TODO_FLAG_DEV_ADDR | DEV_ENUM_TODO_FLAG_DEV_DESC | DEV_ENUM_TODO_FLAG_CONFIG_DESC); + *dev_obj_ret = dev_obj; + ret = ESP_OK; + return ret; + +err: + heap_caps_free(dev_desc); + heap_caps_free(dev_obj); + return ret; +} + +static void device_free(device_t *dev_obj) +{ + if (dev_obj == NULL) { + return; + } + //Configuration must be freed + assert(dev_obj->dynamic.config_desc == NULL); + ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe)); + heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc); + heap_caps_free(dev_obj); +} + +// -------------------------------------------------- Event Related ---------------------------------------------------- + +static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) +{ + if (action_flags == 0) { + return false; + } + bool call_notif_cb; + //Check if device is already on the callback list + if (!dev_obj->dynamic.flags.in_pending_list) { + //Move device form idle device list to callback device list + TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); + dev_obj->dynamic.flags.actions |= action_flags; + dev_obj->dynamic.flags.in_pending_list = 1; + call_notif_cb = true; + } else { + call_notif_cb = false; + } + return call_notif_cb; +} + +static bool default_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +{ + uint32_t action_flags; + device_t *dev_obj = (device_t *)user_arg; + switch (pipe_event) { + case HCD_PIPE_EVENT_URB_DONE: + //A control transfer completed on the default pipe. We need to dequeue it + action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE; + break; + case HCD_PIPE_EVENT_ERROR_XFER: + case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL: + case HCD_PIPE_EVENT_ERROR_OVERFLOW: + //The default pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again + action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | + DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | + DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 Error", dev_obj->constant.address); + } else { + ESP_LOGE(USBH_TAG, "Dev %d EP 0 Error", dev_obj->constant.address); + } + break; + case HCD_PIPE_EVENT_ERROR_STALL: + //The default pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again + action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 STALL", dev_obj->constant.address); + } else { + ESP_LOGE(USBH_TAG, "Dev %d EP 0 STALL", dev_obj->constant.address); + } + break; + default: + action_flags = 0; + break; + } + + USBH_ENTER_CRITICAL_SAFE(); + bool call_notif_cb = _dev_set_actions(dev_obj, action_flags); + USBH_EXIT_CRITICAL_SAFE(); + + bool yield = false; + if (call_notif_cb) { + yield = p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, in_isr, p_usbh_obj->constant.notif_cb_arg); + } + return yield; +} + +static bool handle_dev_free(device_t *dev_obj) +{ + USBH_ENTER_CRITICAL(); + //Remove the device object for it's containing list + if (dev_obj->dynamic.flags.in_pending_list) { + dev_obj->dynamic.flags.in_pending_list = 0; + TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); + } else { + TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + } + p_usbh_obj->dynamic.num_device--; + bool all_free = (p_usbh_obj->dynamic.num_device == 0); + USBH_EXIT_CRITICAL(); + + heap_caps_free(dev_obj->dynamic.config_desc); + dev_obj->dynamic.config_desc = NULL; + device_free(dev_obj); + return all_free; +} + +// ------------------------------------------------- USBH Functions ---------------------------------------------------- + +esp_err_t usbh_install(const usbh_config_t *usbh_config) +{ + USBH_CHECK(usbh_config != NULL, ESP_ERR_INVALID_ARG); + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(p_usbh_obj == NULL, ESP_ERR_INVALID_STATE); + USBH_EXIT_CRITICAL(); + + usbh_t *usbh_obj = heap_caps_calloc(1, sizeof(usbh_t), MALLOC_CAP_DEFAULT); + if (usbh_obj == NULL) { + return ESP_ERR_NO_MEM; + } + esp_err_t ret; + //Install HCD + ret = hcd_install(&usbh_config->hcd_config); + if (ret != ESP_OK) { + goto hcd_install_err; + } + //Initialize usbh object + TAILQ_INIT(&usbh_obj->dynamic.devs_idle_tailq); + TAILQ_INIT(&usbh_obj->dynamic.devs_pending_tailq); + usbh_obj->constant.notif_cb = usbh_config->notif_cb; + usbh_obj->constant.notif_cb_arg = usbh_config->notif_cb_arg; + usbh_obj->constant.event_cb = usbh_config->event_cb; + usbh_obj->constant.event_cb_arg = usbh_config->event_cb_arg; + usbh_obj->constant.ctrl_xfer_cb = usbh_config->ctrl_xfer_cb; + usbh_obj->constant.ctrl_xfer_cb_arg = usbh_config->ctrl_xfer_cb_arg; + + //Assign usbh object pointer + USBH_ENTER_CRITICAL(); + if (p_usbh_obj != NULL) { + USBH_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + p_usbh_obj = usbh_obj; + USBH_EXIT_CRITICAL(); + + ret = ESP_OK; + return ret; + +assign_err: + ESP_ERROR_CHECK(hcd_uninstall()); +hcd_install_err: + heap_caps_free(usbh_obj); + return ret; +} + +esp_err_t usbh_uninstall(void) +{ + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); + //Check that USBH is in a state to be uninstalled + USBH_CHECK_FROM_CRIT(p_usbh_obj->dynamic.num_device == 0, ESP_ERR_INVALID_STATE); + usbh_t *usbh_obj = p_usbh_obj; + p_usbh_obj = NULL; + USBH_EXIT_CRITICAL(); + + //Uninstall HCD + ESP_ERROR_CHECK(hcd_uninstall()); + heap_caps_free(usbh_obj); + return ESP_OK; +} + +esp_err_t usbh_process(void) +{ + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); + //Keep clearing devices with events + while (!TAILQ_EMPTY(&p_usbh_obj->dynamic.devs_pending_tailq)){ + //Move the device back into the idle device list, + device_t *dev_obj = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq); + TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + //Clear the device's flags + uint32_t action_flags = dev_obj->dynamic.flags.actions; + dev_obj->dynamic.flags.actions = 0; + dev_obj->dynamic.flags.in_pending_list = 0; + + USBH_EXIT_CRITICAL(); + ESP_LOGD(USBH_TAG, "Processing actions 0x%x", action_flags); + //Sanity check. If the device is being freed, there must not be any other action flags set + assert(!(action_flags & DEV_FLAG_ACTION_FREE) || action_flags == DEV_FLAG_ACTION_FREE); + if (action_flags & DEV_FLAG_ACTION_SEND_GONE_EVENT) { + //Flush the default pipe. Then do an event gone + ESP_LOGE(USBH_TAG, "Device %d gone", dev_obj->constant.address); + p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_GONE, p_usbh_obj->constant.event_cb_arg); + } + if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH) { + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_HALT)); + ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_FLUSH)); + } + if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE) { + //Empty URBs from default pipe and trigger a control transfer callback + ESP_LOGD(USBH_TAG, "Default pipe device %d", dev_obj->constant.address); + int num_urbs = 0; + urb_t *urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); + while (urb != NULL) { + num_urbs++; + p_usbh_obj->constant.ctrl_xfer_cb((usb_device_handle_t)dev_obj, urb, p_usbh_obj->constant.ctrl_xfer_cb_arg); + urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); + } + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.num_ctrl_xfers_inflight -= num_urbs; + USBH_EXIT_CRITICAL(); + } + if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR) { + //We allow the pipe command to fail just in case the pipe becomes invalid mid command + hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_CLEAR); + } + /* + Note: We make these action flags mutually exclusive in case they happen in rapid succession. They are handled + in the order of precedence + For example + - New device event is requested followed immediately by a disconnection + - Port disable requested followed immediately by a disconnection + */ + if (action_flags & DEV_FLAG_ACTION_FREE) { + ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); + if (handle_dev_free(dev_obj)) { + ESP_LOGD(USBH_TAG, "Device all free"); + p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg); + } + } else if (action_flags & DEV_FLAG_ACTION_PORT_DISABLE) { + //Request that the HUB disables this device's port + ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); + p_usbh_obj->constant.hub_cb(dev_obj->constant.port_hdl, USBH_HUB_EVENT_DISABLE_PORT, p_usbh_obj->constant.hub_cb_arg); + } else if (action_flags & DEV_FLAG_ACTION_SEND_NEW) { + ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); + p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_NEW, p_usbh_obj->constant.event_cb_arg); + } + USBH_ENTER_CRITICAL(); + + } + USBH_EXIT_CRITICAL(); + return ESP_OK; +} + +// ------------------------------------------------ Device Functions --------------------------------------------------- + +// --------------------- Device Pool ----------------------- + +esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + + USBH_ENTER_CRITICAL(); + //Go through the device lists to find the device with the specified address + device_t *found_dev_obj = NULL; + device_t *dev_obj; + TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_obj->constant.address == dev_addr) { + found_dev_obj = dev_obj; + goto exit; + } + } + TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_obj->constant.address == dev_addr) { + found_dev_obj = dev_obj; + goto exit; + } + } +exit: + if (found_dev_obj != NULL) { + //The device is not in a state to be referenced + if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_port_disable || dev_obj->dynamic.flags.waiting_free) { + ret = ESP_ERR_INVALID_STATE; + } else { + dev_obj->dynamic.ref_count++; + *dev_hdl = (usb_device_handle_t)found_dev_obj; + ret = ESP_OK; + } + } else { + ret = ESP_ERR_NOT_FOUND; + } + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.num_ctrl_xfers_inflight == 0, ESP_ERR_INVALID_STATE); + dev_obj->dynamic.ref_count--; + bool call_notif_cb = false; + if (dev_obj->dynamic.ref_count == 0) { + //Sanity check. This can only be set when ref count reaches 0 + assert(!dev_obj->dynamic.flags.waiting_free); + if (dev_obj->dynamic.flags.is_gone) { + //Device is already gone so it's port is already disabled. Trigger the USBH process to free the device + dev_obj->dynamic.flags.waiting_free = 1; + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + } else if (dev_obj->dynamic.flags.waiting_close) { + //Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled + dev_obj->dynamic.flags.waiting_port_disable = 1; + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PORT_DISABLE); + } + //Else, there's nothing to do. Leave the device allocated + } + USBH_EXIT_CRITICAL(); + + if (call_notif_cb) { + p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + } + return ESP_OK; +} + +esp_err_t usbh_dev_mark_all_free(void) +{ + USBH_ENTER_CRITICAL(); + /* + Go through the device list and mark each device as waiting to be closed. If the device is not opened at all, we can + disable it immediately. + Note: We manually traverse the list because we need to add/remove items while traversing + */ + bool call_notif_cb = false; + for (int i = 0; i < 2; i++) { + device_t *dev_obj_cur; + device_t *dev_obj_next; + //Go through pending list first as it's more efficient + if (i == 0) { + dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq); + } else { + dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq); + } + while (dev_obj_cur != NULL) { + assert(!dev_obj_cur->dynamic.flags.waiting_close); //Sanity check + //Keep a copy of the next item first in case we remove the current item + dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); + if (dev_obj_cur->dynamic.ref_count == 0 && !dev_obj_cur->dynamic.flags.is_gone) { + //Device is not opened as is not gone, so we can disable it now + dev_obj_cur->dynamic.flags.waiting_port_disable = 1; + call_notif_cb |= _dev_set_actions(dev_obj_cur, DEV_FLAG_ACTION_PORT_DISABLE); + } else { + //Device is still opened. Just mark it as waiting to be closed + dev_obj_cur->dynamic.flags.waiting_close = 1; + } + dev_obj_cur = dev_obj_next; + } + } + USBH_EXIT_CRITICAL(); + + if (call_notif_cb) { + p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + } + return ESP_OK; +} + +// ------------------- Single Device ---------------------- + +esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr) +{ + USBH_CHECK(dev_hdl != NULL && dev_addr != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->constant.address > 0, ESP_ERR_INVALID_STATE); + *dev_addr = dev_obj->constant.address; + USBH_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info) +{ + USBH_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED, ESP_ERR_INVALID_STATE); + dev_info->speed = dev_obj->constant.speed; + dev_info->dev_addr = dev_obj->constant.address; + dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; + if (dev_obj->dynamic.config_desc == NULL) { + dev_info->bConfigurationValue = 0; + } else { + dev_info->bConfigurationValue = dev_obj->dynamic.config_desc->bConfigurationValue; + } + USBH_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret) +{ + USBH_CHECK(dev_hdl != NULL && dev_desc_ret != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + USBH_EXIT_CRITICAL(); + + *dev_desc_ret = dev_obj->constant.desc; + return ESP_OK; +} + +esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret) +{ + USBH_CHECK(dev_hdl != NULL && config_desc_ret != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + *config_desc_ret = dev_obj->dynamic.config_desc; + USBH_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) +{ + USBH_CHECK(dev_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + //Increment the control transfer count first + dev_obj->dynamic.num_ctrl_xfers_inflight++; + USBH_EXIT_CRITICAL(); + + esp_err_t ret; + if (hcd_pipe_get_state(dev_obj->constant.default_pipe) != HCD_PIPE_STATE_ACTIVE) { + ret = ESP_ERR_INVALID_STATE; + goto hcd_err; + } + ret = hcd_urb_enqueue(dev_obj->constant.default_pipe, urb); + if (ret != ESP_OK) { + goto hcd_err; + } + ret = ESP_OK; + return ret; + +hcd_err: + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.num_ctrl_xfers_inflight--; + USBH_EXIT_CRITICAL(); + return ret; +} + +// ----------------------------------------------- Interface Functions ------------------------------------------------- + +esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret) +{ + USBH_CHECK(dev_hdl != NULL && ep_config != NULL && pipe_hdl_ret != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + dev_obj->dynamic.ref_count++; //Increase the ref_count to keep the device alive while allocating the endpoint + USBH_EXIT_CRITICAL(); + + esp_err_t ret; + //Allocate HCD pipe + hcd_pipe_config_t pipe_config = { + .callback = ep_config->pipe_cb, + .callback_arg = ep_config->pipe_cb_arg, + .context = ep_config->context, + .ep_desc = ep_config->ep_desc, + .dev_speed = dev_obj->constant.speed, + .dev_addr = dev_obj->constant.address, + }; + hcd_pipe_handle_t pipe_hdl; + ret = hcd_pipe_alloc(dev_obj->constant.port_hdl, &pipe_config, &pipe_hdl); + if (ret != ESP_OK) { + goto pipe_alloc_err; + } + + USBH_ENTER_CRITICAL(); + //Check that endpoint has not be allocated yet + bool is_in = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; + uint8_t addr = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; + //Assign the pipe handle + bool assigned = false; + if (is_in && dev_obj->dynamic.ep_in[addr - 1] == NULL) { //Is an IN EP + dev_obj->dynamic.ep_in[addr - 1] = pipe_hdl; + assigned = true; + } else { + dev_obj->dynamic.ep_out[addr - 1] = pipe_hdl; + assigned = true; + } + dev_obj->dynamic.ref_count--; //Restore ref_count + USBH_EXIT_CRITICAL(); + + if (!assigned) { + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + //Write back output + *pipe_hdl_ret = pipe_hdl; + ret = ESP_OK; + return ret; + +assign_err: + ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl)); +pipe_alloc_err: + return ret; +} + +esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + //Un-assign the pipe handle from the endpoint + bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; + uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; + hcd_pipe_handle_t pipe_hdl; + if (is_in) { + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.ep_in[addr - 1] != NULL, ESP_ERR_INVALID_STATE); + pipe_hdl = dev_obj->dynamic.ep_in[addr - 1]; + dev_obj->dynamic.ep_in[addr - 1] = NULL; + } else { + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.ep_out[addr - 1] != NULL, ESP_ERR_INVALID_STATE); + pipe_hdl = dev_obj->dynamic.ep_out[addr - 1]; + dev_obj->dynamic.ep_out[addr - 1] = NULL; + } + USBH_EXIT_CRITICAL(); + + ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl)); + return ESP_OK; +} + +esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret) +{ + bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; + uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK; + USBH_CHECK(dev_hdl != NULL && + addr >= EP_NUM_MIN && //Control endpoints are owned by the USBH + addr <= EP_NUM_MAX && + context_ret != NULL, + ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + //Get the endpoint's corresponding pipe + hcd_pipe_handle_t pipe_hdl; + if (is_in) { + pipe_hdl = dev_obj->dynamic.ep_in[addr - 1]; + } else { + pipe_hdl = dev_obj->dynamic.ep_out[addr - 1]; + } + esp_err_t ret; + if (pipe_hdl == NULL) { + USBH_EXIT_CRITICAL(); + ret = ESP_ERR_NOT_FOUND; + goto exit; + } + //Return the context of the pipe + void *context = hcd_pipe_get_context(pipe_hdl); + *context_ret = context; + USBH_EXIT_CRITICAL(); + + ret = ESP_OK; +exit: + return ret; +} + +// -------------------------------------------------- Hub Functions ---------------------------------------------------- + +// ------------------- Device Related ---------------------- + +esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg) +{ + USBH_CHECK(hub_callback != NULL, ESP_ERR_INVALID_ARG); + + USBH_ENTER_CRITICAL(); + //Check that USBH is already installed + USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); + //Check that Hub has not be installed yet + USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_cb == NULL, ESP_ERR_INVALID_STATE); + p_usbh_obj->constant.hub_cb = hub_callback; + p_usbh_obj->constant.hub_cb_arg = callback_arg; + USBH_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl) +{ + //Note: Parent device handle can be NULL if it's connected to the root hub + USBH_CHECK(new_dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj; + ret = device_alloc(port_hdl, dev_speed, &dev_obj); + if (ret != ESP_OK) { + return ret; + } + //Write-back device handle + *new_dev_hdl = (usb_device_handle_t)dev_obj; + *default_pipe_hdl = dev_obj->constant.default_pipe; + ret = ESP_OK; + return ret; +} + +esp_err_t usbh_hub_mark_dev_gone(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.flags.is_gone = 1; + bool call_notif_cb; + //Check if the device can be freed now + if (dev_obj->dynamic.ref_count == 0) { + dev_obj->dynamic.flags.waiting_free = 1; + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + } else { + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_SEND_GONE_EVENT | + DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | + DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE); + } + USBH_EXIT_CRITICAL(); + + if (call_notif_cb) { + p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + } + return ESP_OK; +} + +esp_err_t usbh_hub_dev_port_disabled(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + assert(dev_obj->dynamic.ref_count == 0); //At this stage, the device should have been closed by all users + dev_obj->dynamic.flags.waiting_free = 1; + bool call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + USBH_EXIT_CRITICAL(); + + if (call_notif_cb) { + ESP_LOGD(USBH_TAG, "Notif free"); + p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + } + return ESP_OK; +} + +// ----------------- Enumeration Related ------------------- + +esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_DEV_ADDR, ESP_ERR_INVALID_STATE); + dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; + USBH_EXIT_CRITICAL(); + + //We can modify the info members outside the critical section + dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_DEV_ADDR; + dev_obj->constant.address = dev_addr; + return ESP_OK; +} + +esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc) +{ + USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + //We can modify the info members outside the critical section + USBH_CHECK(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_DEV_DESC, ESP_ERR_INVALID_STATE); + dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_DEV_DESC; + memcpy((usb_device_desc_t *)dev_obj->constant.desc, device_desc, sizeof(usb_device_desc_t)); + return ESP_OK; +} + +esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full) +{ + USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + esp_err_t ret; + //Allocate memory to store the configuration descriptor + usb_config_desc_t *config_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); //Buffer to copy over full configuration descriptor (wTotalLength) + if (config_desc == NULL) { + ret = ESP_ERR_NO_MEM; + goto err; + } + //Copy the configuration descriptor + memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength); + //Assign the config object to the device object + if (!(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_CONFIG_DESC)) { + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + + USBH_ENTER_CRITICAL(); + assert(dev_obj->dynamic.config_desc == NULL); + dev_obj->dynamic.config_desc = config_desc; + USBH_EXIT_CRITICAL(); + + //We can modify the info members outside the critical section + dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_CONFIG_DESC; + ret = ESP_OK; + return ret; + +assign_err: + heap_caps_free(config_desc); +err: + return ret; +} + +esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + USBH_CHECK(dev_obj->constant.enum_todo_flags == 0, ESP_ERR_INVALID_STATE); //All enumeration stages to be done + + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; + //Add the device to list of devices, then trigger a device event + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); //Add it to the idle device list first + p_usbh_obj->dynamic.num_device++; + bool call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_SEND_NEW); + USBH_EXIT_CRITICAL(); + + //Update the default pipe callback + ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, default_pipe_callback, (void *)dev_obj)); + //Call the notification callback + if (call_notif_cb) { + p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); + } + return ESP_OK; +} + +esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + usb_config_desc_t *config_desc = dev_obj->dynamic.config_desc; + dev_obj->dynamic.config_desc = NULL; + USBH_EXIT_CRITICAL(); + + if (config_desc) { + heap_caps_free(config_desc); + } + device_free(dev_obj); + return ESP_OK; +}