2024-04-02 08:25:11 -04:00
/*
* SPDX - FileCopyrightText : 2024 Espressif Systems ( Shanghai ) CO LTD
*
* SPDX - License - Identifier : Apache - 2.0
*/
# include <string.h>
# include <stdint.h>
# include "esp_err.h"
# include "esp_log.h"
# include "esp_heap_caps.h"
# include "freertos/FreeRTOS.h"
# include "freertos/task.h"
2024-06-21 06:57:26 -04:00
# include "freertos/semphr.h"
2024-04-02 08:25:11 -04:00
# include "usb_private.h"
# include "ext_hub.h"
# include "usb/usb_helpers.h"
typedef struct ext_port_s * ext_port_hdl_t ; /* This will be implemented during ext_port driver implementation */
2024-06-21 06:57:26 -04:00
# define EXT_HUB_MAX_STATUS_BYTES_SIZE (sizeof(uint32_t))
2024-04-02 08:25:11 -04:00
# define EXT_HUB_STATUS_CHANGE_FLAG (1 << 0)
# define EXT_HUB_STATUS_PORT1_CHANGE_FLAG (1 << 1)
# define EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
/**
* @ brief Device state
*
* Global state of the Device
*/
typedef enum {
EXT_HUB_STATE_ATTACHED , /**< Device attached, but not state is unknown (no: Hub Descriptor, Device status and Hub status) */
EXT_HUB_STATE_CONFIGURED , /**< Device attached and configured (has Hub Descriptor, Device status and Hub status were requested )*/
EXT_HUB_STATE_SUSPENDED , /**< Device suspended */
EXT_HUB_STATE_FAILED /**< Device has internal error */
} ext_hub_state_t ;
/**
* @ brief Device stages
*
* During the lifecycle , Hub requires different actions . To implement interaction with external Hub the FSM , based on these stages is using .
*
* Entry :
* - Every new attached external Hub should start from EXT_HUB_STAGE_GET_HUB_DESCRIPTOR . Without Fetching Hub Descriptor , device is not configured and doesn ' t have any ports .
* - After handling the response during EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR , the External Hub Driver configures the Device according to the data from Hub Descriptor .
* - After completion of any stage , the Device does back to the IDLE stage and waits for another request . Source of the request could be : EP1 INT callback ( the Hub or Ports changes ) or external call .
* - Stages , that don ' t required response handling could not end up with fail .
*/
typedef enum {
// Device in IDLE state
EXT_HUB_STAGE_IDLE = 0 , /**< Device in idle state and do not fulfill any actions */
// Stages, required response handling
EXT_HUB_STAGE_GET_DEVICE_STATUS , /**< Device requests Device Status. For more details, refer to 9.4.5 Get Status of usb_20 */
EXT_HUB_STAGE_CHECK_DEVICE_STATUS , /**< Device received the Device Status and required its' handling */
EXT_HUB_STAGE_GET_HUB_DESCRIPTOR , /**< Device requests Hub Descriptor. For more details, refer to 11.24.2.5 Get Hub Descriptor of usb_20 */
EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR , /**< Device received the Hub Descriptor and requires its' handling */
EXT_HUB_STAGE_GET_HUB_STATUS , /**< Device requests Hub Status. For more details, refer to 11.24.2.6 Get Hub Status of usb_20 */
EXT_HUB_STAGE_CHECK_HUB_STATUS , /**< Device received the Hub Status and requires its' handling */
// Stages, don't required response handling
EXT_HUB_STAGE_PORT_FEATURE , /**< Device completed the Port Feature class-specific request (Set Feature or Clear Feature). For more details, refer to 11.24.2 Class-specific Requests of usb_20 */
EXT_HUB_STAGE_PORT_STATUS_REQUEST , /**< Device completed the Port Get Status class-specific request. For more details, refer to 11.24.2 Class-specific Requests of usb_20 */
EXT_HUB_STAGE_FAILURE /**< Device has internal error and requires handling */
} ext_hub_stage_t ;
const char * const ext_hub_stage_strings [ ] = {
" IDLE " ,
" GET_DEVICE_STATUS " ,
" CHECK_DEVICE_STATUS " ,
" GET_HUB_DESCRIPTOR " ,
" CHECK_HUB_DESCRIPTOR " ,
" GET_HUB_STATUS " ,
" CHECK_HUB_STATUS " ,
" PORT_FEATURE " ,
" PORT_STATUS_REQUEST " ,
" FAILURE "
} ;
/**
* @ brief Device action flags
*/
typedef enum {
DEV_ACTION_EP0_COMPLETE = ( 1 < < 1 ) , /**< Device complete one of stages, requires handling */
DEV_ACTION_EP1_FLUSH = ( 1 < < 2 ) , /**< Device's Interrupt EP needs to be flushed */
DEV_ACTION_EP1_DEQUEUE = ( 1 < < 3 ) , /**< Device's Interrupt EP needs to be dequeued */
DEV_ACTION_EP1_CLEAR = ( 1 < < 4 ) , /**< Device's Interrupt EP needs to be cleared */
DEV_ACTION_REQ = ( 1 < < 5 ) , /**< Device has new actions and required handling */
DEV_ACTION_ERROR = ( 1 < < 6 ) , /**< Device encounters an error */
DEV_ACTION_GONE = ( 1 < < 7 ) , /**< Device was gone */
DEV_ACTION_RELEASE = ( 1 < < 8 ) , /**< Device was released */
DEV_ACTION_FREE = ( 1 < < 9 ) , /**< Device should be freed */
} dev_action_t ;
typedef struct ext_hub_s ext_hub_dev_t ;
/**
* @ brief External Hub device configuration parameters for device allocation
*/
typedef struct {
usb_device_handle_t dev_hdl ; /**< Device's handle */
uint8_t dev_addr ; /**< Device's bus address */
const usb_intf_desc_t * iface_desc ; /**< Device's Interface Descriptor pointer */
const usb_ep_desc_t * ep_in_desc ; /**< Device's IN Endpoint Descriptor pointer */
} device_config_t ;
struct ext_hub_s {
struct {
TAILQ_ENTRY ( ext_hub_s ) tailq_entry ;
union {
struct {
uint32_t in_pending_list : 1 ; /**< Device is in pending list */
2024-06-21 06:57:26 -04:00
uint32_t waiting_release : 1 ; /**< Device waiting to be released */
uint32_t waiting_children : 1 ; /**< Device is waiting children to be freed */
2024-04-02 08:25:11 -04:00
uint32_t waiting_free : 1 ; /**< Device waiting to be freed */
uint32_t is_gone : 1 ; /**< Device is gone */
2024-06-21 06:57:26 -04:00
uint32_t reserved27 : 27 ; /**< Reserved */
2024-04-02 08:25:11 -04:00
} ;
uint32_t val ; /**< Device's flags value */
} flags ;
uint32_t action_flags ; /**< Device's action flags */
} dynamic ; /**< Dynamic members require a critical section */
2024-06-21 06:57:26 -04:00
struct {
ext_hub_stage_t stage ; /**< Device's stage */
uint8_t maxchild ; /**< Amount of allocated ports. Could be 0 for some Hubs. Is increased when new port is added and decreased when port has been freed. */
ext_hub_state_t state ; /**< Device's state */
} single_thread ; /**< Single thread members don't require a critical section, as long as they are never accessed from multiple threads */
2024-04-02 08:25:11 -04:00
struct {
// For optimisation & debug only
uint8_t iface_num ; /**< Device's bInterfaceNum */
// Driver purpose
uint8_t dev_addr ; /**< Device's bus address */
usb_device_handle_t dev_hdl ; /**< Device's handle */
urb_t * ctrl_urb ; /**< Device's Control pipe transfer URB */
urb_t * in_urb ; /**< Device's Interrupt pipe URB */
usbh_ep_handle_t ep_in_hdl ; /**< Device's Interrupt EP handle */
usb_hub_descriptor_t * hub_desc ; /**< Device's Hub descriptor pointer. Could be NULL when not requested */
ext_port_hdl_t * ports ; /**< Flexible array of Ports. Could be NULL, when maxchild is 0 */
} constant ; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} ;
typedef struct {
struct {
TAILQ_HEAD ( ext_hubs , ext_hub_s ) ext_hubs_tailq ; /**< Idle tailq */
TAILQ_HEAD ( ext_hubs_cb , ext_hub_s ) ext_hubs_pending_tailq ; /**< Pending tailq */
} dynamic ; /**< Dynamic members require a critical section */
struct {
ext_hub_cb_t proc_req_cb ; /**< Process callback */
void * proc_req_cb_arg ; /**< Process callback argument */
const ext_hub_port_driver_t * port_driver ; /**< External Port Driver */
} constant ; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} ext_hub_driver_t ;
static ext_hub_driver_t * p_ext_hub_driver = NULL ;
static portMUX_TYPE ext_hub_driver_lock = portMUX_INITIALIZER_UNLOCKED ;
const char * EXT_HUB_TAG = " EXT_HUB " ;
// -----------------------------------------------------------------------------
// ------------------------------- Helpers -------------------------------------
// -----------------------------------------------------------------------------
# define EXT_HUB_ENTER_CRITICAL() portENTER_CRITICAL(&ext_hub_driver_lock)
# define EXT_HUB_EXIT_CRITICAL() portEXIT_CRITICAL(&ext_hub_driver_lock)
# define EXT_HUB_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&ext_hub_driver_lock)
# define EXT_HUB_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&ext_hub_driver_lock)
# define EXT_HUB_CHECK(cond, ret_val) ({ \
if ( ! ( cond ) ) { \
return ( ret_val ) ; \
} \
} )
# define EXT_HUB_CHECK_FROM_CRIT(cond, ret_val) ({ \
if ( ! ( cond ) ) { \
EXT_HUB_EXIT_CRITICAL ( ) ; \
return ret_val ; \
} \
} )
// -----------------------------------------------------------------------------
// ----------------------- Forward declaration ---------------------------------
// -----------------------------------------------------------------------------
static bool _device_set_actions ( ext_hub_dev_t * ext_hub_dev , uint32_t action_flags ) ;
static void device_error ( ext_hub_dev_t * ext_hub_dev ) ;
static void device_status_change_handle ( ext_hub_dev_t * ext_hub_dev , const uint8_t * data , const int length ) ;
// -----------------------------------------------------------------------------
// ---------------------- Callbacks (implementation) ---------------------------
// -----------------------------------------------------------------------------
static bool interrupt_pipe_cb ( usbh_ep_handle_t ep_hdl , usbh_ep_event_t ep_event , void * user_arg , bool in_isr )
{
uint32_t action_flags ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) user_arg ;
switch ( ep_event ) {
case USBH_EP_EVENT_URB_DONE : {
// A interrupt transfer completed on EP1's pipe . We need to dequeue it
action_flags = DEV_ACTION_EP1_DEQUEUE ;
break ;
case USBH_EP_EVENT_ERROR_XFER :
case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL :
case USBH_EP_EVENT_ERROR_OVERFLOW :
// EP1's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again
action_flags = DEV_ACTION_EP1_FLUSH |
DEV_ACTION_EP1_DEQUEUE |
DEV_ACTION_EP1_CLEAR ;
if ( in_isr ) {
ESP_EARLY_LOGE ( EXT_HUB_TAG , " Device %d EP1 Error " , ext_hub_dev - > constant . dev_addr ) ;
} else {
ESP_LOGE ( EXT_HUB_TAG , " Device %d EP1 Error " , ext_hub_dev - > constant . dev_addr ) ;
}
break ;
case USBH_EP_EVENT_ERROR_STALL :
// EP1's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again
action_flags = DEV_ACTION_EP1_DEQUEUE | DEV_ACTION_EP1_CLEAR ;
if ( in_isr ) {
ESP_EARLY_LOGE ( EXT_HUB_TAG , " Device %d EP1 STALL " , ext_hub_dev - > constant . dev_addr ) ;
} else {
ESP_LOGE ( EXT_HUB_TAG , " Device %d EP1 STALL " , ext_hub_dev - > constant . dev_addr ) ;
}
break ;
}
default :
action_flags = 0 ;
break ;
}
EXT_HUB_ENTER_CRITICAL_SAFE ( ) ;
bool call_proc_req_cb = _device_set_actions ( ext_hub_dev , action_flags ) ;
EXT_HUB_EXIT_CRITICAL_SAFE ( ) ;
bool yield = false ;
if ( call_proc_req_cb ) {
yield = p_ext_hub_driver - > constant . proc_req_cb ( in_isr , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
return yield ;
}
/**
* @ brief Control transfer completion callback
*
* Is called by lower logic when transfer is completed with or without error
*
* @ param [ in ] ctrl_xfer Pointer to a transfer buffer
*/
static void control_transfer_complete_cb ( usb_transfer_t * ctrl_xfer )
{
bool call_proc_req_cb = false ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ctrl_xfer - > context ;
EXT_HUB_ENTER_CRITICAL ( ) ;
call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_EP0_COMPLETE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
}
static void interrupt_transfer_complete_cb ( usb_transfer_t * intr_xfer )
{
assert ( intr_xfer ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) intr_xfer - > context ;
assert ( ext_hub_dev ) ;
switch ( intr_xfer - > status ) {
case USB_TRANSFER_STATUS_COMPLETED :
ESP_LOG_BUFFER_HEXDUMP ( EXT_HUB_TAG , intr_xfer - > data_buffer , intr_xfer - > actual_num_bytes , ESP_LOG_VERBOSE ) ;
device_status_change_handle ( ext_hub_dev , intr_xfer - > data_buffer , intr_xfer - > actual_num_bytes ) ;
break ;
case USB_TRANSFER_STATUS_NO_DEVICE :
// Device was removed, nothing to do
break ;
case USB_TRANSFER_STATUS_CANCELED :
// Cancellation due to USB Host uninstall routine
break ;
default :
// Any other error
ESP_LOGE ( EXT_HUB_TAG , " [%d] Interrupt transfer failed, status %d " , ext_hub_dev - > constant . dev_addr , intr_xfer - > status ) ;
device_error ( ext_hub_dev ) ;
break ;
}
}
// -----------------------------------------------------------------------------
// --------------------------- Internal Logic ---------------------------------
// -----------------------------------------------------------------------------
static bool _device_set_actions ( ext_hub_dev_t * ext_hub_dev , uint32_t action_flags )
{
/*
THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION
*/
if ( action_flags = = 0 ) {
return false ;
}
bool call_proc_req_cb ;
// Check if device is already on the callback list
if ( ! ext_hub_dev - > dynamic . flags . in_pending_list ) {
// Move device form idle device list to callback device list
TAILQ_REMOVE ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq , ext_hub_dev , dynamic . tailq_entry ) ;
TAILQ_INSERT_TAIL ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq , ext_hub_dev , dynamic . tailq_entry ) ;
ext_hub_dev - > dynamic . action_flags | = action_flags ;
ext_hub_dev - > dynamic . flags . in_pending_list = 1 ;
call_proc_req_cb = true ;
} else {
// The device is already on the callback list, thus a processing request is already pending.
ext_hub_dev - > dynamic . action_flags | = action_flags ;
call_proc_req_cb = false ;
}
return call_proc_req_cb ;
}
static esp_err_t device_enable_int_ep ( ext_hub_dev_t * ext_hub_dev )
{
2024-06-21 06:57:26 -04:00
ESP_LOGD ( EXT_HUB_TAG , " [%d] Enable EP IN " , ext_hub_dev - > constant . dev_addr ) ;
esp_err_t ret = usbh_ep_enqueue_urb ( ext_hub_dev - > constant . ep_in_hdl , ext_hub_dev - > constant . in_urb ) ;
2024-04-02 08:25:11 -04:00
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Failed to submit in urb: %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
}
return ret ;
}
static void device_has_changed ( ext_hub_dev_t * ext_hub_dev )
{
// TODO: IDF-10053 Hub status change handling
// After getting the IRQ about Hub status change we need to request status
// device_get_status(ext_hub_dev);
ESP_LOGW ( EXT_HUB_TAG , " Hub status change has not been implemented yet " ) ;
device_enable_int_ep ( ext_hub_dev ) ;
}
2024-06-21 06:57:26 -04:00
//
2024-04-02 08:25:11 -04:00
// Figure 11-22. Hub and Port Status Change Bitmap
2024-06-21 06:57:26 -04:00
// Bit | N | ... | 3 | 2 | 1 | 0 |
// Value |1/0| ... |1/0|1/0|1/0|1/0|
// | | | | |
// | | | | +------ Hub change detected
// | | | +---------- Port 1 change detected
// | | +-------------- Port 2 change detected
// | +------------------ Port 3 change detected
// | ...
// +---------------------------- Port N change detected
//
2024-04-02 08:25:11 -04:00
static void device_status_change_handle ( ext_hub_dev_t * ext_hub_dev , const uint8_t * data , const int length )
{
2024-06-21 06:57:26 -04:00
uint32_t device_status = 0 ;
// Driver does not support Hubs with EP IN wMaxPacketSize > 4
assert ( length < = EXT_HUB_MAX_STATUS_BYTES_SIZE ) ;
for ( uint32_t i = 0 ; i < length ; i + + ) {
device_status | = ( uint32_t ) ( data [ i ] < < i ) ;
}
if ( device_status ) {
// If device has a status change, we will re-trigger back the transfer
// after all handling will be done
if ( device_status & EXT_HUB_STATUS_CHANGE_FLAG ) {
// Check device status
device_has_changed ( ext_hub_dev ) ;
}
// HINTs:
// - every byte of Data IN has 8 bits of possible port statuses bits: (length * 8)
// - very first bit of status is used for Hub status: (length * 8) - 1
for ( uint8_t i = 0 ; i < ( length * 8 ) - 1 ; i + + ) {
// Check ports statuses
if ( device_status & ( EXT_HUB_STATUS_PORT1_CHANGE_FLAG < < i ) ) {
assert ( i < ext_hub_dev - > single_thread . maxchild ) ; // Port should be in range
assert ( p_ext_hub_driver - > constant . port_driver ) ; // Port driver call should be valid
// Request Port status to handle changes
p_ext_hub_driver - > constant . port_driver - > get_status ( ext_hub_dev - > constant . ports [ i ] ) ;
2024-04-02 08:25:11 -04:00
}
}
2024-06-21 06:57:26 -04:00
} else {
// Device status is 0. Could happen when HS Hub with HS device is connected to HS Hub, connected to FS root port
// Re-trigger transfer right now
device_enable_int_ep ( ext_hub_dev ) ;
2024-04-02 08:25:11 -04:00
}
}
2024-06-21 06:57:26 -04:00
static void device_error ( ext_hub_dev_t * ext_hub_dev )
2024-04-02 08:25:11 -04:00
{
bool call_proc_req_cb = false ;
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_ERROR ) ;
2024-04-02 08:25:11 -04:00
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
}
2024-06-21 06:57:26 -04:00
static esp_err_t device_port_new ( ext_hub_dev_t * ext_hub_dev , uint8_t port_idx )
2024-04-02 08:25:11 -04:00
{
2024-06-21 06:57:26 -04:00
assert ( p_ext_hub_driver - > constant . port_driver ) ;
esp_err_t ret = p_ext_hub_driver - > constant . port_driver - > new ( NULL , ( void * * ) & ext_hub_dev - > constant . ports [ port_idx ] ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Port allocation error: %s " , ext_hub_dev - > constant . dev_addr , port_idx + 1 , esp_err_to_name ( ret ) ) ;
goto fail ;
}
ext_hub_dev - > single_thread . maxchild + + ;
fail :
return ret ;
}
static esp_err_t device_port_free ( ext_hub_dev_t * ext_hub_dev , uint8_t port_idx )
{
EXT_HUB_CHECK ( ext_hub_dev - > constant . ports [ port_idx ] ! = NULL , ESP_ERR_INVALID_STATE ) ;
2024-04-02 08:25:11 -04:00
bool call_proc_req_cb = false ;
2024-06-21 06:57:26 -04:00
bool all_ports_freed = false ;
assert ( ext_hub_dev - > single_thread . maxchild ! = 0 ) ;
assert ( p_ext_hub_driver - > constant . port_driver ) ;
esp_err_t ret = p_ext_hub_driver - > constant . port_driver - > free ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Unable to free port: %s " , ext_hub_dev - > constant . dev_addr , port_idx + 1 , esp_err_to_name ( ret ) ) ;
goto exit ;
}
ext_hub_dev - > constant . ports [ port_idx ] = NULL ;
ext_hub_dev - > single_thread . maxchild - - ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
if ( ext_hub_dev - > dynamic . flags . is_gone ) {
all_ports_freed = ( ext_hub_dev - > single_thread . maxchild = = 0 ) ;
if ( all_ports_freed ) {
ext_hub_dev - > dynamic . flags . waiting_children = 0 ;
ext_hub_dev - > dynamic . flags . waiting_free = 1 ;
call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_FREE ) ;
} else {
ext_hub_dev - > dynamic . flags . waiting_children = 1 ;
}
}
2024-04-02 08:25:11 -04:00
EXT_HUB_EXIT_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
// Close the device if all children were freed
if ( all_ports_freed ) {
ESP_LOGD ( EXT_HUB_TAG , " [%d] Release USBH device object " , ext_hub_dev - > constant . dev_addr ) ;
ESP_ERROR_CHECK ( usbh_dev_close ( ext_hub_dev - > constant . dev_hdl ) ) ;
}
2024-04-02 08:25:11 -04:00
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
2024-06-21 06:57:26 -04:00
ret = ESP_OK ;
exit :
return ret ;
2024-04-02 08:25:11 -04:00
}
static void device_release ( ext_hub_dev_t * ext_hub_dev )
{
2024-06-21 06:57:26 -04:00
esp_err_t ret ;
2024-04-02 08:25:11 -04:00
ESP_LOGD ( EXT_HUB_TAG , " [%d] Device release " , ext_hub_dev - > constant . dev_addr ) ;
// Release IN EP
ESP_ERROR_CHECK ( usbh_ep_command ( ext_hub_dev - > constant . ep_in_hdl , USBH_EP_CMD_HALT ) ) ;
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > dynamic . flags . is_gone = 1 ;
ext_hub_dev - > dynamic . flags . waiting_release = 0 ;
2024-04-02 08:25:11 -04:00
EXT_HUB_EXIT_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
if ( ext_hub_dev - > single_thread . state > = EXT_HUB_STATE_CONFIGURED ) {
// Hub device was configured and has a descriptor
assert ( ext_hub_dev - > constant . hub_desc ! = NULL ) ;
assert ( p_ext_hub_driver - > constant . port_driver ) ;
// Mark all ports as gone
for ( uint8_t i = 0 ; i < ext_hub_dev - > constant . hub_desc - > bNbrPorts ; i + + ) {
if ( ext_hub_dev - > constant . ports [ i ] ) {
ret = p_ext_hub_driver - > constant . port_driver - > gone ( ext_hub_dev - > constant . ports [ i ] ) ;
if ( ret = = ESP_OK ) {
// Port doesn't have a device and can be recycled right now
ret = device_port_free ( ext_hub_dev , i ) ;
if ( ret ! = ESP_OK ) {
// Hub runs into an error state
// TODO: IDF-10057 Hub handling error
}
} else if ( ret = = ESP_ERR_NOT_FINISHED ) {
// Port has a device and will be recycled after USBH device will be released by all clients and freed
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Port is gone " , ext_hub_dev - > constant . dev_addr , i + 1 ) ;
} else {
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Unable to mark port as gone: %s " ,
ext_hub_dev - > constant . dev_addr , i + 1 , esp_err_to_name ( ret ) ) ;
}
}
}
2024-04-02 08:25:11 -04:00
}
}
static esp_err_t device_alloc_desc ( ext_hub_dev_t * ext_hub_hdl , const usb_hub_descriptor_t * hub_desc )
{
// Allocate memory to store the configuration descriptor
usb_hub_descriptor_t * desc = heap_caps_malloc ( hub_desc - > bDescLength , MALLOC_CAP_DEFAULT ) ; // Buffer to copy over full configuration descriptor (wTotalLength)
if ( desc = = NULL ) {
return ESP_ERR_NO_MEM ;
}
// Copy the hub descriptor
memcpy ( desc , hub_desc , hub_desc - > bDescLength ) ;
// Assign the hub descriptor to the device object
assert ( ext_hub_hdl - > constant . hub_desc = = NULL ) ;
ext_hub_hdl - > constant . hub_desc = desc ;
return ESP_OK ;
}
static esp_err_t device_alloc ( device_config_t * config , ext_hub_dev_t * * ext_hub_dev )
{
esp_err_t ret ;
urb_t * ctrl_urb = NULL ;
urb_t * in_urb = NULL ;
# if !ENABLE_MULTIPLE_HUBS
usb_device_info_t dev_info ;
ESP_ERROR_CHECK ( usbh_dev_get_info ( config - > dev_hdl , & dev_info ) ) ;
if ( dev_info . parent . dev_hdl ) {
ESP_LOGW ( EXT_HUB_TAG , " Multiple Hubs not supported, use menuconfig to enable feature " ) ;
ret = ESP_ERR_NOT_SUPPORTED ;
goto fail ;
}
# endif // ENABLE_MULTIPLE_HUBS
ext_hub_dev_t * hub_dev = heap_caps_calloc ( 1 , sizeof ( ext_hub_dev_t ) , MALLOC_CAP_DEFAULT ) ;
if ( hub_dev = = NULL ) {
ESP_LOGE ( EXT_HUB_TAG , " Unable to allocate device " ) ;
ret = ESP_ERR_NO_MEM ;
goto fail ;
}
// Allocate Control transfer URB
ctrl_urb = urb_alloc ( sizeof ( usb_setup_packet_t ) + EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN , 0 ) ;
if ( ctrl_urb = = NULL ) {
ESP_LOGE ( EXT_HUB_TAG , " Unable to allocate Control URB " ) ;
ret = ESP_ERR_NO_MEM ;
goto ctrl_urb_fail ;
}
2024-06-21 06:57:26 -04:00
if ( config - > ep_in_desc - > wMaxPacketSize > EXT_HUB_MAX_STATUS_BYTES_SIZE ) {
ESP_LOGE ( EXT_HUB_TAG , " wMaxPacketSize=%d is not supported " , config - > ep_in_desc - > wMaxPacketSize ) ;
ret = ESP_ERR_NOT_SUPPORTED ;
goto in_urb_fail ;
}
2024-04-02 08:25:11 -04:00
in_urb = urb_alloc ( config - > ep_in_desc - > wMaxPacketSize , 0 ) ;
// Allocate Interrupt transfer URB
if ( in_urb = = NULL ) {
ESP_LOGE ( EXT_HUB_TAG , " Unable to allocate Interrupt URB " ) ;
ret = ESP_ERR_NO_MEM ;
goto in_urb_fail ;
}
usbh_ep_handle_t ep_hdl ;
usbh_ep_config_t ep_config = {
. bInterfaceNumber = config - > iface_desc - > bInterfaceNumber ,
. bAlternateSetting = config - > iface_desc - > bAlternateSetting ,
. bEndpointAddress = config - > ep_in_desc - > bEndpointAddress ,
. ep_cb = interrupt_pipe_cb ,
. ep_cb_arg = ( void * ) hub_dev ,
. context = ( void * ) hub_dev ,
} ;
ret = usbh_ep_alloc ( config - > dev_hdl , & ep_config , & ep_hdl ) ;
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Endpoint allocation failure: %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
goto ep_fail ;
}
// Configure Control transfer URB
ctrl_urb - > usb_host_client = ( void * ) p_ext_hub_driver ;
ctrl_urb - > transfer . callback = control_transfer_complete_cb ;
ctrl_urb - > transfer . context = ( void * ) hub_dev ;
// Client is a memory address of the p_ext_hub_driver driver object
in_urb - > usb_host_client = ( void * ) p_ext_hub_driver ;
in_urb - > transfer . callback = interrupt_transfer_complete_cb ;
in_urb - > transfer . context = ( void * ) hub_dev ;
in_urb - > transfer . num_bytes = config - > ep_in_desc - > wMaxPacketSize ;
2024-06-21 06:57:26 -04:00
hub_dev - > dynamic . flags . val = 0 ;
hub_dev - > dynamic . action_flags = 0 ;
// Mutex protected
hub_dev - > single_thread . state = EXT_HUB_STATE_ATTACHED ;
// Constant members
2024-04-02 08:25:11 -04:00
hub_dev - > constant . ep_in_hdl = ep_hdl ;
hub_dev - > constant . ctrl_urb = ctrl_urb ;
hub_dev - > constant . in_urb = in_urb ;
hub_dev - > constant . dev_hdl = config - > dev_hdl ;
hub_dev - > constant . dev_addr = config - > dev_addr ;
hub_dev - > constant . iface_num = config - > iface_desc - > bInterfaceNumber ;
2024-06-21 06:57:26 -04:00
// Single thread members
hub_dev - > single_thread . stage = EXT_HUB_STAGE_IDLE ;
2024-04-02 08:25:11 -04:00
// We will update number of ports during Hub Descriptor handling stage
2024-06-21 06:57:26 -04:00
hub_dev - > single_thread . maxchild = 0 ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
TAILQ_INSERT_TAIL ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq , hub_dev , dynamic . tailq_entry ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
ESP_LOGD ( EXT_HUB_TAG , " [%d] New device (iface %d) " , config - > dev_addr , hub_dev - > constant . iface_num ) ;
* ext_hub_dev = hub_dev ;
return ret ;
ep_fail :
urb_free ( in_urb ) ;
in_urb_fail :
urb_free ( ctrl_urb ) ;
ctrl_urb_fail :
heap_caps_free ( hub_dev ) ;
fail :
return ret ;
}
static esp_err_t device_configure ( ext_hub_dev_t * ext_hub_dev )
{
2024-06-21 06:57:26 -04:00
esp_err_t ret ;
2024-04-02 08:25:11 -04:00
EXT_HUB_CHECK ( ext_hub_dev - > constant . hub_desc ! = NULL , ESP_ERR_INVALID_STATE ) ;
usb_hub_descriptor_t * hub_desc = ext_hub_dev - > constant . hub_desc ;
ESP_LOGD ( EXT_HUB_TAG , " [%d] Device configure (iface %d) " ,
ext_hub_dev - > constant . dev_addr ,
ext_hub_dev - > constant . iface_num ) ;
if ( hub_desc - > wHubCharacteristics . compound ) {
ESP_LOGD ( EXT_HUB_TAG , " \t Compound device " ) ;
} else {
ESP_LOGD ( EXT_HUB_TAG , " \t Standalone HUB " ) ;
}
ESP_LOGD ( EXT_HUB_TAG , " \t %d external port%s " ,
ext_hub_dev - > constant . hub_desc - > bNbrPorts ,
( ext_hub_dev - > constant . hub_desc - > bNbrPorts = = 1 ) ? " " : " s " ) ;
switch ( hub_desc - > wHubCharacteristics . power_switching ) {
case USB_W_HUB_CHARS_PORT_PWR_CTRL_NO :
ESP_LOGD ( EXT_HUB_TAG , " \t No power switching (usb 1.0) " ) ;
break ;
case USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV :
ESP_LOGD ( EXT_HUB_TAG , " \t Individual port power switching " ) ;
break ;
default :
// USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL
ESP_LOGD ( EXT_HUB_TAG , " \t All ports power at once " ) ;
break ;
}
switch ( hub_desc - > wHubCharacteristics . ovr_current_protect ) {
case USB_W_HUB_CHARS_PORT_OVER_CURR_NO :
ESP_LOGD ( EXT_HUB_TAG , " \t No over-current protection " ) ;
break ;
case USB_W_HUB_CHARS_PORT_OVER_CURR_INDV :
ESP_LOGD ( EXT_HUB_TAG , " \t Individual port over-current protection " ) ;
break ;
default :
// USB_W_HUB_CHARS_PORT_OVER_CURR_ALL
ESP_LOGD ( EXT_HUB_TAG , " \t Global over-current protection " ) ;
break ;
}
if ( hub_desc - > wHubCharacteristics . indicator_support ) {
ESP_LOGD ( EXT_HUB_TAG , " \t Port indicators are supported " ) ;
}
ESP_LOGD ( EXT_HUB_TAG , " \t Power on to power good time: %dms " , hub_desc - > bPwrOn2PwrGood * 2 ) ;
ESP_LOGD ( EXT_HUB_TAG , " \t Maximum current: %d mA " , hub_desc - > bHubContrCurrent ) ;
// Create External Port flexible array
ext_hub_dev - > constant . ports = heap_caps_calloc ( ext_hub_dev - > constant . hub_desc - > bNbrPorts , sizeof ( ext_port_hdl_t ) , MALLOC_CAP_DEFAULT ) ;
if ( ext_hub_dev - > constant . ports = = NULL ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Ports list allocation error " ) ;
ret = ESP_ERR_NO_MEM ;
goto fail ;
2024-04-02 08:25:11 -04:00
}
// Create port and add it to pending list
2024-06-21 06:57:26 -04:00
for ( uint8_t i = 0 ; i < ext_hub_dev - > constant . hub_desc - > bNbrPorts ; i + + ) {
ext_hub_dev - > constant . ports [ i ] = NULL ;
ret = device_port_new ( ext_hub_dev , i ) ;
if ( ret ! = ESP_OK ) {
goto fail ;
2024-04-02 08:25:11 -04:00
}
}
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . state = EXT_HUB_STATE_CONFIGURED ;
2024-04-02 08:25:11 -04:00
return ESP_OK ;
2024-06-21 06:57:26 -04:00
fail :
// Free allocated resources
for ( uint8_t i = 0 ; i < ext_hub_dev - > constant . hub_desc - > bNbrPorts ; i + + ) {
if ( ext_hub_dev - > constant . ports [ i ] ) {
device_port_free ( ext_hub_dev , i ) ;
}
}
if ( ext_hub_dev - > constant . ports ) {
assert ( ext_hub_dev - > single_thread . maxchild = = 0 ) ;
heap_caps_free ( ext_hub_dev - > constant . ports ) ;
}
return ret ;
2024-04-02 08:25:11 -04:00
}
static void device_free ( ext_hub_dev_t * ext_hub_dev )
{
2024-06-21 06:57:26 -04:00
assert ( ext_hub_dev - > single_thread . maxchild = = 0 ) ; // All ports should be freed by now
2024-04-02 08:25:11 -04:00
ESP_LOGD ( EXT_HUB_TAG , " [%d] Freeing device " , ext_hub_dev - > constant . dev_addr ) ;
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
assert ( ext_hub_dev - > dynamic . flags . waiting_free = = 1 ) ; // Hub should await being freed
2024-04-02 08:25:11 -04:00
ext_hub_dev - > dynamic . flags . waiting_free = 0 ;
TAILQ_REMOVE ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq , ext_hub_dev , dynamic . tailq_entry ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( ext_hub_dev - > constant . hub_desc ) {
heap_caps_free ( ext_hub_dev - > constant . hub_desc ) ;
}
if ( ext_hub_dev - > constant . ports ) {
heap_caps_free ( ext_hub_dev - > constant . ports ) ;
}
ESP_ERROR_CHECK ( usbh_ep_free ( ext_hub_dev - > constant . ep_in_hdl ) ) ;
urb_free ( ext_hub_dev - > constant . ctrl_urb ) ;
urb_free ( ext_hub_dev - > constant . in_urb ) ;
heap_caps_free ( ext_hub_dev ) ;
}
static esp_err_t get_dev_by_hdl ( usb_device_handle_t dev_hdl , ext_hub_dev_t * * ext_hub_hdl )
{
esp_err_t ret = ESP_OK ;
// Go through the Hubs lists to find the hub with the specified device address
ext_hub_dev_t * found_hub = NULL ;
ext_hub_dev_t * hub = NULL ;
EXT_HUB_ENTER_CRITICAL ( ) ;
TAILQ_FOREACH ( hub , & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq , dynamic . tailq_entry ) {
if ( hub - > constant . dev_hdl = = dev_hdl ) {
found_hub = hub ;
goto exit ;
}
}
TAILQ_FOREACH ( hub , & p_ext_hub_driver - > dynamic . ext_hubs_tailq , dynamic . tailq_entry ) {
if ( hub - > constant . dev_hdl = = dev_hdl ) {
found_hub = hub ;
goto exit ;
}
}
exit :
if ( found_hub = = NULL ) {
ret = ESP_ERR_NOT_FOUND ;
}
EXT_HUB_EXIT_CRITICAL ( ) ;
* ext_hub_hdl = found_hub ;
return ret ;
}
static esp_err_t get_dev_by_addr ( uint8_t dev_addr , ext_hub_dev_t * * ext_hub_hdl )
{
esp_err_t ret = ESP_OK ;
// Go through the Hubs lists to find the Hub with the specified device address
ext_hub_dev_t * found_hub = NULL ;
ext_hub_dev_t * hub = NULL ;
EXT_HUB_ENTER_CRITICAL ( ) ;
TAILQ_FOREACH ( hub , & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq , dynamic . tailq_entry ) {
if ( hub - > constant . dev_addr = = dev_addr ) {
found_hub = hub ;
goto exit ;
}
}
TAILQ_FOREACH ( hub , & p_ext_hub_driver - > dynamic . ext_hubs_tailq , dynamic . tailq_entry ) {
if ( hub - > constant . dev_addr = = dev_addr ) {
found_hub = hub ;
goto exit ;
}
}
exit :
if ( found_hub = = NULL ) {
ret = ESP_ERR_NOT_FOUND ;
}
EXT_HUB_EXIT_CRITICAL ( ) ;
* ext_hub_hdl = found_hub ;
return ret ;
}
// -----------------------------------------------------------------------------
// -------------------------- Device handling ---------------------------------
// -----------------------------------------------------------------------------
static bool handle_hub_descriptor ( ext_hub_dev_t * ext_hub_dev )
{
esp_err_t ret ;
bool pass ;
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
const usb_hub_descriptor_t * hub_desc = ( const usb_hub_descriptor_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
if ( ctrl_xfer - > status ! = USB_TRANSFER_STATUS_COMPLETED ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Bad transfer status %d: stage=%d " , ctrl_xfer - > status , ext_hub_dev - > single_thread . stage ) ;
2024-04-02 08:25:11 -04:00
return false ;
}
ESP_LOG_BUFFER_HEXDUMP ( EXT_HUB_TAG , ctrl_xfer - > data_buffer , ctrl_xfer - > actual_num_bytes , ESP_LOG_VERBOSE ) ;
ret = device_alloc_desc ( ext_hub_dev , hub_desc ) ;
if ( ret ! = ESP_OK ) {
pass = false ;
goto exit ;
}
ret = device_configure ( ext_hub_dev ) ;
if ( ret ! = ESP_OK ) {
pass = false ;
goto exit ;
}
pass = true ;
exit :
return pass ;
}
static bool handle_device_status ( ext_hub_dev_t * ext_hub_dev )
{
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
const usb_device_status_t * dev_status = ( const usb_device_status_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
ESP_LOGD ( EXT_HUB_TAG , " [%d] Device status: " , ext_hub_dev - > constant . dev_addr ) ;
ESP_LOGD ( EXT_HUB_TAG , " \t Power: %s " , dev_status - > self_powered ? " self-powered " : " bus-powered " ) ;
ESP_LOGD ( EXT_HUB_TAG , " \t RemoteWakeup: %s " , dev_status - > remote_wakeup ? " yes " : " no " ) ;
if ( dev_status - > remote_wakeup ) {
// Device in remote_wakeup, we need send command Clear Device Feature: USB_W_VALUE_FEATURE_DEVICE_REMOTE_WAKEUP
// HEX codes of command: 00 01 01 00 00 00 00 00
// TODO: IDF-10055 Hub Support remote_wakeup feature
ESP_LOGW ( EXT_HUB_TAG , " Remote Wakeup feature has not been implemented yet " ) ;
}
return true ;
}
static bool handle_hub_status ( ext_hub_dev_t * ext_hub_dev )
{
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
const usb_hub_status_t * hub_status = ( const usb_hub_status_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
ESP_LOGD ( EXT_HUB_TAG , " [%d] Hub status: " , ext_hub_dev - > constant . dev_addr ) ;
ESP_LOGD ( EXT_HUB_TAG , " \t External power supply: %s " , hub_status - > wHubStatus . HUB_LOCAL_POWER ? " yes " : " no " ) ;
ESP_LOGD ( EXT_HUB_TAG , " \t Overcurrent: %s " , hub_status - > wHubStatus . HUB_OVER_CURRENT ? " yes " : " no " ) ;
if ( hub_status - > wHubStatus . HUB_OVER_CURRENT ) {
ESP_LOGE ( EXT_HUB_TAG , " Device has overcurrent! " ) ;
// Hub has an overcurrent, we need to disable all port and/or disable parent port
// TODO: IDF-10056 Hubs overcurrent handling
ESP_LOGW ( EXT_HUB_TAG , " Feature has not been implemented yet " ) ;
return false ;
}
return true ;
}
2024-06-21 06:57:26 -04:00
static void handle_port_feature ( ext_hub_dev_t * ext_hub_dev )
{
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
uint8_t port_num = USB_SETUP_PACKET_GET_PORT ( ( usb_setup_packet_t * ) ctrl_xfer - > data_buffer ) ;
uint8_t port_idx = port_num - 1 ;
assert ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts ) ;
assert ( p_ext_hub_driver - > constant . port_driver ) ;
p_ext_hub_driver - > constant . port_driver - > req_process ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
}
static void handle_port_status ( ext_hub_dev_t * ext_hub_dev )
{
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
uint8_t port_num = USB_SETUP_PACKET_GET_PORT ( ( usb_setup_packet_t * ) ctrl_xfer - > data_buffer ) ;
uint8_t port_idx = port_num - 1 ;
const usb_port_status_t * new_status = ( const usb_port_status_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
assert ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts ) ;
assert ( p_ext_hub_driver - > constant . port_driver ) ;
p_ext_hub_driver - > constant . port_driver - > set_status ( ext_hub_dev - > constant . ports [ port_idx ] , new_status ) ;
}
2024-04-02 08:25:11 -04:00
static bool device_control_request ( ext_hub_dev_t * ext_hub_dev )
{
esp_err_t ret ;
usb_transfer_t * transfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
2024-06-21 06:57:26 -04:00
switch ( ext_hub_dev - > single_thread . stage ) {
2024-04-02 08:25:11 -04:00
case EXT_HUB_STAGE_GET_DEVICE_STATUS :
USB_SETUP_PACKET_INIT_GET_STATUS ( ( usb_setup_packet_t * ) transfer - > data_buffer ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_device_status_t ) ;
break ;
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR :
USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR ( ( usb_setup_packet_t * ) transfer - > data_buffer ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_hub_descriptor_t ) ;
break ;
case EXT_HUB_STAGE_GET_HUB_STATUS :
USB_SETUP_PACKET_INIT_GET_HUB_STATUS ( ( usb_setup_packet_t * ) transfer - > data_buffer ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_hub_status_t ) ;
break ;
default :
// Should never occur
abort ( ) ;
break ;
}
ret = usbh_dev_submit_ctrl_urb ( ext_hub_dev - > constant . dev_hdl , ext_hub_dev - > constant . ctrl_urb ) ;
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Failed to submit ctrl urb: %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
return false ;
}
return true ;
}
static bool device_control_response_handling ( ext_hub_dev_t * ext_hub_dev )
{
bool stage_pass = false ;
2024-06-21 06:57:26 -04:00
usb_transfer_t * ctrl_xfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
// Check transfer status
if ( ctrl_xfer - > status ! = USB_TRANSFER_STATUS_COMPLETED ) {
ESP_LOGE ( EXT_HUB_TAG , " Control request bad transfer status %d " ,
ctrl_xfer - > status ) ;
return stage_pass ;
}
2024-04-02 08:25:11 -04:00
2024-06-21 06:57:26 -04:00
// Transfer completed, verbose data in ESP_LOG_VERBOSE is set
ESP_LOG_BUFFER_HEXDUMP ( EXT_HUB_TAG , ctrl_xfer - > data_buffer , ctrl_xfer - > actual_num_bytes , ESP_LOG_VERBOSE ) ;
switch ( ext_hub_dev - > single_thread . stage ) {
2024-04-02 08:25:11 -04:00
case EXT_HUB_STAGE_CHECK_DEVICE_STATUS :
stage_pass = handle_device_status ( ext_hub_dev ) ;
break ;
case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR :
stage_pass = handle_hub_descriptor ( ext_hub_dev ) ;
break ;
case EXT_HUB_STAGE_CHECK_HUB_STATUS :
stage_pass = handle_hub_status ( ext_hub_dev ) ;
break ;
2024-06-21 06:57:26 -04:00
case EXT_HUB_STAGE_PORT_FEATURE :
handle_port_feature ( ext_hub_dev ) ;
stage_pass = true ;
break ;
case EXT_HUB_STAGE_PORT_STATUS_REQUEST :
handle_port_status ( ext_hub_dev ) ;
stage_pass = true ;
break ;
2024-04-02 08:25:11 -04:00
default :
// Should never occur
abort ( ) ;
break ;
}
return stage_pass ;
}
static bool stage_need_process ( ext_hub_stage_t stage )
{
bool need_process_cb = false ;
switch ( stage ) {
// Stages, required control transfer
case EXT_HUB_STAGE_GET_DEVICE_STATUS :
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR :
case EXT_HUB_STAGE_GET_HUB_STATUS :
// Error stage
case EXT_HUB_STAGE_FAILURE :
need_process_cb = true ;
break ;
default :
break ;
}
return need_process_cb ;
}
// return
// true - next stage requires the processing
// false - terminal stage
static bool device_set_next_stage ( ext_hub_dev_t * ext_hub_dev , bool last_stage_pass )
{
2024-06-21 06:57:26 -04:00
ext_hub_stage_t last_stage = ext_hub_dev - > single_thread . stage ;
2024-04-02 08:25:11 -04:00
ext_hub_stage_t next_stage ;
if ( last_stage_pass ) {
ESP_LOGD ( EXT_HUB_TAG , " Stage %s OK " , ext_hub_stage_strings [ last_stage ] ) ;
if ( last_stage = = EXT_HUB_STAGE_GET_DEVICE_STATUS | |
last_stage = = EXT_HUB_STAGE_GET_HUB_DESCRIPTOR | |
last_stage = = EXT_HUB_STAGE_GET_HUB_STATUS ) {
// Simply increment to get the next stage
next_stage = last_stage + 1 ;
} else {
// Terminal stages, move to IDLE
next_stage = EXT_HUB_STAGE_IDLE ;
}
} else {
ESP_LOGE ( EXT_HUB_TAG , " Stage %s FAILED " , ext_hub_stage_strings [ last_stage ] ) ;
// These stages cannot fail
assert ( last_stage ! = EXT_HUB_STAGE_PORT_FEATURE | |
last_stage ! = EXT_HUB_STAGE_PORT_STATUS_REQUEST ) ;
next_stage = EXT_HUB_STAGE_FAILURE ;
}
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = next_stage ;
return stage_need_process ( next_stage ) ;
2024-04-02 08:25:11 -04:00
}
2024-06-21 06:57:26 -04:00
static void handle_error ( ext_hub_dev_t * ext_hub_dev )
2024-04-02 08:25:11 -04:00
{
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . state = EXT_HUB_STATE_FAILED ;
// TODO: IDF-10057 Hub handling error
ESP_LOGW ( EXT_HUB_TAG , " %s has not been implemented yet " , __FUNCTION__ ) ;
2024-04-02 08:25:11 -04:00
}
static void handle_device ( ext_hub_dev_t * ext_hub_dev )
{
bool call_proc_req_cb ;
bool stage_pass = false ;
// FSM for external Hub
2024-06-21 06:57:26 -04:00
switch ( ext_hub_dev - > single_thread . stage ) {
2024-04-02 08:25:11 -04:00
case EXT_HUB_STAGE_IDLE :
break ;
case EXT_HUB_STAGE_GET_DEVICE_STATUS :
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR :
case EXT_HUB_STAGE_GET_HUB_STATUS :
stage_pass = device_control_request ( ext_hub_dev ) ;
break ;
2024-06-21 06:57:26 -04:00
case EXT_HUB_STAGE_CHECK_DEVICE_STATUS :
2024-04-02 08:25:11 -04:00
case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR :
2024-06-21 06:57:26 -04:00
case EXT_HUB_STAGE_CHECK_HUB_STATUS :
2024-04-02 08:25:11 -04:00
case EXT_HUB_STAGE_PORT_FEATURE :
case EXT_HUB_STAGE_PORT_STATUS_REQUEST :
2024-06-21 06:57:26 -04:00
stage_pass = device_control_response_handling ( ext_hub_dev ) ;
2024-04-02 08:25:11 -04:00
break ;
case EXT_HUB_STAGE_FAILURE :
2024-06-21 06:57:26 -04:00
handle_error ( ext_hub_dev ) ;
stage_pass = true ;
2024-04-02 08:25:11 -04:00
break ;
default :
// Should never occur
abort ( ) ;
break ;
}
call_proc_req_cb = device_set_next_stage ( ext_hub_dev , stage_pass ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
}
static void handle_ep1_flush ( ext_hub_dev_t * ext_hub_dev )
{
ESP_ERROR_CHECK ( usbh_ep_command ( ext_hub_dev - > constant . ep_in_hdl , USBH_EP_CMD_HALT ) ) ;
ESP_ERROR_CHECK ( usbh_ep_command ( ext_hub_dev - > constant . ep_in_hdl , USBH_EP_CMD_FLUSH ) ) ;
}
static void handle_ep1_dequeue ( ext_hub_dev_t * ext_hub_dev )
{
// Dequeue all URBs and run their transfer callback
ESP_LOGD ( EXT_HUB_TAG , " [%d] Interrupt dequeue " , ext_hub_dev - > constant . dev_addr ) ;
2024-06-21 06:57:26 -04:00
2024-04-02 08:25:11 -04:00
urb_t * urb ;
usbh_ep_dequeue_urb ( ext_hub_dev - > constant . ep_in_hdl , & urb ) ;
while ( urb ! = NULL ) {
// Clear the transfer's in-flight flag to indicate the transfer is no longer in-flight
urb - > usb_host_inflight = false ;
urb - > transfer . callback ( & urb - > transfer ) ;
usbh_ep_dequeue_urb ( ext_hub_dev - > constant . ep_in_hdl , & urb ) ;
}
}
static void handle_ep1_clear ( ext_hub_dev_t * ext_hub_dev )
{
// We allow the pipe command to fail just in case the pipe becomes invalid mid command
usbh_ep_command ( ext_hub_dev - > constant . ep_in_hdl , USBH_EP_CMD_CLEAR ) ;
}
static void handle_gone ( ext_hub_dev_t * ext_hub_dev )
{
bool call_proc_req_cb = false ;
// Set the flags
EXT_HUB_ENTER_CRITICAL ( ) ;
ext_hub_dev - > dynamic . flags . waiting_free = 1 ;
call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_FREE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
}
// -----------------------------------------------------------------------------
// ------------------------------ Driver ---------------------------------------
// -----------------------------------------------------------------------------
esp_err_t ext_hub_install ( const ext_hub_config_t * config )
{
esp_err_t ret ;
ext_hub_driver_t * ext_hub_drv = heap_caps_calloc ( 1 , sizeof ( ext_hub_driver_t ) , MALLOC_CAP_DEFAULT ) ;
2024-06-21 06:57:26 -04:00
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex ( ) ;
if ( ext_hub_drv = = NULL | | mux_lock = = NULL ) {
ret = ESP_ERR_NO_MEM ;
goto err ;
}
2024-04-02 08:25:11 -04:00
// Save callbacks
ext_hub_drv - > constant . proc_req_cb = config - > proc_req_cb ;
ext_hub_drv - > constant . proc_req_cb_arg = config - > proc_req_cb_arg ;
// Copy Port driver pointer
ext_hub_drv - > constant . port_driver = config - > port_driver ;
if ( ext_hub_drv - > constant . port_driver = = NULL ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Port Driver has not been implemented yet " ) ;
ret = ESP_ERR_NOT_FINISHED ;
goto err ;
2024-04-02 08:25:11 -04:00
}
TAILQ_INIT ( & ext_hub_drv - > dynamic . ext_hubs_tailq ) ;
TAILQ_INIT ( & ext_hub_drv - > dynamic . ext_hubs_pending_tailq ) ;
EXT_HUB_ENTER_CRITICAL ( ) ;
if ( p_ext_hub_driver ! = NULL ) {
EXT_HUB_EXIT_CRITICAL ( ) ;
ret = ESP_ERR_INVALID_STATE ;
2024-06-21 06:57:26 -04:00
goto err ;
2024-04-02 08:25:11 -04:00
}
p_ext_hub_driver = ext_hub_drv ;
EXT_HUB_EXIT_CRITICAL ( ) ;
ESP_LOGD ( EXT_HUB_TAG , " Driver installed " ) ;
return ESP_OK ;
2024-06-21 06:57:26 -04:00
err :
if ( mux_lock ! = NULL ) {
vSemaphoreDelete ( mux_lock ) ;
}
2024-04-02 08:25:11 -04:00
heap_caps_free ( ext_hub_drv ) ;
return ret ;
}
esp_err_t ext_hub_uninstall ( void )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_CHECK_FROM_CRIT ( TAILQ_EMPTY ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq ) , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_CHECK_FROM_CRIT ( TAILQ_EMPTY ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq ) , ESP_ERR_INVALID_STATE ) ;
ext_hub_driver_t * ext_hub_drv = p_ext_hub_driver ;
p_ext_hub_driver = NULL ;
EXT_HUB_EXIT_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
// Free resources
2024-04-02 08:25:11 -04:00
heap_caps_free ( ext_hub_drv ) ;
ESP_LOGD ( EXT_HUB_TAG , " Driver uninstalled " ) ;
return ESP_OK ;
}
void * ext_hub_get_client ( void )
{
bool driver_installed = false ;
EXT_HUB_ENTER_CRITICAL ( ) ;
driver_installed = ( p_ext_hub_driver ! = NULL ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
return ( driver_installed ) ? ( void * ) p_ext_hub_driver : NULL ;
}
// -----------------------------------------------------------------------------
// -------------------------- External Hub API ---------------------------------
// -----------------------------------------------------------------------------
esp_err_t ext_hub_get_handle ( usb_device_handle_t dev_hdl , ext_hub_handle_t * ext_hub_hdl )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
return get_dev_by_hdl ( dev_hdl , ext_hub_hdl ) ;
}
static esp_err_t find_first_intf_desc ( const usb_config_desc_t * config_desc , device_config_t * hub_config )
{
bool iface_found = false ;
const usb_ep_desc_t * ep_in_desc = NULL ;
int offset = 0 ;
const usb_intf_desc_t * next_intf_desc = ( const usb_intf_desc_t * ) usb_parse_next_descriptor_of_type (
( const usb_standard_desc_t * ) config_desc ,
config_desc - > wTotalLength ,
USB_B_DESCRIPTOR_TYPE_INTERFACE ,
& offset ) ;
while ( next_intf_desc ! = NULL ) {
if ( iface_found ) {
// TODO: IDF-10058 Hubs support interface selection (HS)
ESP_LOGW ( EXT_HUB_TAG , " Device has several Interfaces, selection has not been implemented yet. Using first. " ) ;
break ;
}
// Parse all interfaces
if ( USB_CLASS_HUB = = next_intf_desc - > bInterfaceClass ) {
// We have found the first interface descriptor with matching bInterfaceNumber
if ( next_intf_desc - > bInterfaceProtocol ! = USB_B_DEV_PROTOCOL_HUB_FS ) {
2024-06-21 06:57:26 -04:00
// TODO: IDF-10059 Hubs support multiple TT (HS)
if ( next_intf_desc - > bInterfaceProtocol ! = USB_B_DEV_PROTOCOL_HUB_HS_NO_TT ) {
ESP_LOGW ( EXT_HUB_TAG , " Transaction Translator has not been implemented yet " ) ;
}
switch ( next_intf_desc - > bInterfaceProtocol ) {
case USB_B_DEV_PROTOCOL_HUB_HS_NO_TT :
ESP_LOGD ( EXT_HUB_TAG , " \t No TT " ) ;
break ;
case USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT :
ESP_LOGD ( EXT_HUB_TAG , " \t Single TT " ) ;
break ;
case USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT :
ESP_LOGD ( EXT_HUB_TAG , " \t Multi TT " ) ;
break ;
default :
ESP_LOGE ( EXT_HUB_TAG , " \t Interface Protocol (%#x) not supported " , next_intf_desc - > bInterfaceProtocol ) ;
goto next_iface ;
}
2024-04-02 08:25:11 -04:00
}
// Hub Interface always should have only one Interrupt endpoint
if ( next_intf_desc - > bNumEndpoints ! = 1 ) {
ESP_LOGE ( EXT_HUB_TAG , " Unexpected number of endpoints (%d) " , next_intf_desc - > bNumEndpoints ) ;
goto next_iface ;
}
// Get related IN EP
ep_in_desc = usb_parse_endpoint_descriptor_by_index ( next_intf_desc , 0 , config_desc - > wTotalLength , & offset ) ;
if ( ep_in_desc = = NULL ) {
ESP_LOGE ( EXT_HUB_TAG , " EP descriptor not found (iface=%d) " , next_intf_desc - > bInterfaceNumber ) ;
goto next_iface ;
}
if ( ! USB_EP_DESC_GET_EP_DIR ( ep_in_desc ) | |
( USB_EP_DESC_GET_XFERTYPE ( ep_in_desc ) ! = USB_TRANSFER_TYPE_INTR ) ) {
ESP_LOGE ( EXT_HUB_TAG , " Interrupt EP not found (iface=%d) " , next_intf_desc - > bInterfaceNumber ) ;
goto next_iface ;
}
// Interface found, fill the config
hub_config - > iface_desc = next_intf_desc ;
hub_config - > ep_in_desc = ep_in_desc ;
iface_found = true ;
}
next_iface :
next_intf_desc = ( const usb_intf_desc_t * ) usb_parse_next_descriptor_of_type (
( const usb_standard_desc_t * ) next_intf_desc ,
config_desc - > wTotalLength ,
USB_B_DESCRIPTOR_TYPE_INTERFACE ,
& offset ) ;
}
return ( iface_found ) ? ESP_OK : ESP_ERR_NOT_FOUND ;
}
esp_err_t ext_hub_new_dev ( uint8_t dev_addr )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
esp_err_t ret ;
ext_hub_dev_t * hub_dev = NULL ;
usb_device_handle_t dev_hdl = NULL ;
const usb_config_desc_t * config_desc = NULL ;
bool call_proc_req_cb = false ;
// Open device
ret = usbh_devs_open ( dev_addr , & dev_hdl ) ;
if ( ret ! = ESP_OK ) {
2024-07-12 07:32:18 -04:00
ESP_LOGE ( EXT_HUB_TAG , " USBH device opening error: %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
return ret ;
}
// Get Configuration Descriptor
ret = usbh_dev_get_config_desc ( dev_hdl , & config_desc ) ;
if ( ret ! = ESP_OK ) {
2024-07-12 07:32:18 -04:00
ESP_LOGE ( EXT_HUB_TAG , " Getting config desc error %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
goto exit ;
}
// Find related Hub Interface descriptor
device_config_t hub_config = {
. dev_hdl = dev_hdl ,
. dev_addr = dev_addr ,
. iface_desc = NULL ,
. ep_in_desc = NULL ,
} ;
ret = find_first_intf_desc ( config_desc , & hub_config ) ;
if ( ret ! = ESP_OK ) {
goto exit ;
}
// Create External Hub device
ret = device_alloc ( & hub_config , & hub_dev ) ;
if ( ret ! = ESP_OK ) {
2024-07-12 07:32:18 -04:00
ESP_LOGE ( EXT_HUB_TAG , " External HUB device alloc error %s " , esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
goto exit ;
}
2024-06-21 06:57:26 -04:00
hub_dev - > single_thread . stage = EXT_HUB_STAGE_GET_HUB_DESCRIPTOR ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
call_proc_req_cb = _device_set_actions ( hub_dev , DEV_ACTION_REQ ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
return ret ;
exit :
ESP_ERROR_CHECK ( usbh_dev_close ( dev_hdl ) ) ;
return ret ;
}
esp_err_t ext_hub_dev_gone ( uint8_t dev_addr )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
esp_err_t ret ;
ext_hub_dev_t * ext_hub_dev = NULL ;
2024-06-21 06:57:26 -04:00
bool in_pending = false ;
2024-04-02 08:25:11 -04:00
EXT_HUB_CHECK ( dev_addr ! = 0 , ESP_ERR_INVALID_ARG ) ;
// Find device with dev_addr in the devices TAILQ
// TODO: IDF-10058
// Release all devices by dev_addr
ret = get_dev_by_addr ( dev_addr , & ext_hub_dev ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGD ( EXT_HUB_TAG , " No device with address %d was found " , dev_addr ) ;
2024-06-21 06:57:26 -04:00
goto exit ;
2024-04-02 08:25:11 -04:00
}
2024-06-21 06:57:26 -04:00
ESP_LOGD ( EXT_HUB_TAG , " [%d] Device gone " , ext_hub_dev - > constant . dev_addr ) ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
if ( ext_hub_dev - > dynamic . flags . waiting_release | |
ext_hub_dev - > dynamic . flags . waiting_children ) {
// External Hub was already released or waiting children to be freed
EXT_HUB_EXIT_CRITICAL ( ) ;
ret = ESP_ERR_INVALID_STATE ;
goto exit ;
}
if ( ext_hub_dev - > dynamic . flags . in_pending_list ) {
in_pending = true ;
// TODO: IDF-10490
// test case:
// - detach the external Hub device right before the Hub Descriptor request.
_device_set_actions ( ext_hub_dev , DEV_ACTION_RELEASE ) ;
}
2024-04-02 08:25:11 -04:00
EXT_HUB_EXIT_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
// Device not in pending, can be released immediately
if ( ! in_pending ) {
device_release ( ext_hub_dev ) ;
2024-04-02 08:25:11 -04:00
}
2024-06-21 06:57:26 -04:00
ret = ESP_OK ;
exit :
2024-04-02 08:25:11 -04:00
return ret ;
}
esp_err_t ext_hub_all_free ( void )
{
bool call_proc_req_cb = false ;
2024-06-21 06:57:26 -04:00
bool wait_for_free = false ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
2024-06-21 06:57:26 -04:00
for ( int i = 0 ; i < 2 ; i + + ) {
ext_hub_dev_t * hub_curr ;
ext_hub_dev_t * hub_next ;
// Go through pending list first
if ( i = = 0 ) {
hub_curr = TAILQ_FIRST ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq ) ;
} else {
hub_curr = TAILQ_FIRST ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq ) ;
}
while ( hub_curr ! = NULL ) {
hub_next = TAILQ_NEXT ( hub_curr , dynamic . tailq_entry ) ;
hub_curr - > dynamic . flags . waiting_release = 1 ;
call_proc_req_cb = _device_set_actions ( hub_curr , DEV_ACTION_RELEASE ) ;
// At least one hub should be released
wait_for_free = true ;
hub_curr = hub_next ;
}
2024-04-02 08:25:11 -04:00
}
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
2024-06-21 06:57:26 -04:00
return ( wait_for_free ) ? ESP_ERR_NOT_FINISHED : ESP_OK ;
2024-04-02 08:25:11 -04:00
}
esp_err_t ext_hub_status_handle_complete ( ext_hub_handle_t ext_hub_hdl )
{
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( ext_hub_dev - > single_thread . state = = EXT_HUB_STATE_CONFIGURED , ESP_ERR_INVALID_STATE ) ;
2024-04-02 08:25:11 -04:00
return device_enable_int_ep ( ext_hub_dev ) ;
}
esp_err_t ext_hub_process ( void )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
// Keep processing until all device's with pending events have been handled
while ( ! TAILQ_EMPTY ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq ) ) {
// Move the device back into the idle device list,
ext_hub_dev_t * ext_hub_dev = TAILQ_FIRST ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq ) ;
TAILQ_REMOVE ( & p_ext_hub_driver - > dynamic . ext_hubs_pending_tailq , ext_hub_dev , dynamic . tailq_entry ) ;
TAILQ_INSERT_TAIL ( & p_ext_hub_driver - > dynamic . ext_hubs_tailq , ext_hub_dev , dynamic . tailq_entry ) ;
// Clear the device's flags
uint32_t action_flags = ext_hub_dev - > dynamic . action_flags ;
ext_hub_dev - > dynamic . action_flags = 0 ;
ext_hub_dev - > dynamic . flags . in_pending_list = 0 ;
/* ---------------------------------------------------------------------
Exit critical section to handle device action flags in their listed order
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
EXT_HUB_EXIT_CRITICAL ( ) ;
ESP_LOGD ( EXT_HUB_TAG , " [%d] Processing actions 0x% " PRIx32 " " , ext_hub_dev - > constant . dev_addr , action_flags ) ;
if ( action_flags & DEV_ACTION_REQ | |
action_flags & DEV_ACTION_EP0_COMPLETE ) {
handle_device ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_EP1_FLUSH ) {
handle_ep1_flush ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_EP1_DEQUEUE ) {
handle_ep1_dequeue ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_EP1_CLEAR ) {
handle_ep1_clear ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_ERROR ) {
handle_error ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_GONE ) {
handle_gone ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_RELEASE ) {
device_release ( ext_hub_dev ) ;
}
if ( action_flags & DEV_ACTION_FREE ) {
device_free ( ext_hub_dev ) ;
}
EXT_HUB_ENTER_CRITICAL ( ) ;
/* ---------------------------------------------------------------------
Re - enter critical sections . All device action flags should have been handled .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
}
EXT_HUB_EXIT_CRITICAL ( ) ;
return ESP_OK ;
}
// -----------------------------------------------------------------------------
// --------------------- External Hub - Device related -------------------------
// -----------------------------------------------------------------------------
esp_err_t ext_hub_get_hub_status ( ext_hub_handle_t ext_hub_hdl )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = EXT_HUB_STAGE_GET_HUB_STATUS ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
bool call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_REQ ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
return ESP_OK ;
}
esp_err_t ext_hub_get_status ( ext_hub_handle_t ext_hub_hdl )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = EXT_HUB_STAGE_GET_DEVICE_STATUS ;
2024-04-02 08:25:11 -04:00
EXT_HUB_ENTER_CRITICAL ( ) ;
bool call_proc_req_cb = _device_set_actions ( ext_hub_dev , DEV_ACTION_REQ ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( call_proc_req_cb ) {
p_ext_hub_driver - > constant . proc_req_cb ( false , p_ext_hub_driver - > constant . proc_req_cb_arg ) ;
}
return ESP_OK ;
}
// -----------------------------------------------------------------------------
// --------------------- External Hub - Port related ---------------------------
// -----------------------------------------------------------------------------
esp_err_t ext_hub_port_recycle ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
2024-06-21 06:57:26 -04:00
esp_err_t ret ;
2024-04-02 08:25:11 -04:00
uint8_t port_idx = port_num - 1 ;
2024-06-21 06:57:26 -04:00
bool free_port = false ;
bool release_port = false ;
EXT_HUB_CHECK ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( p_ext_hub_driver - > constant . port_driver ! = NULL , ESP_ERR_NOT_SUPPORTED ) ;
EXT_HUB_CHECK ( ext_hub_dev - > single_thread . state = = EXT_HUB_STATE_CONFIGURED , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_ENTER_CRITICAL ( ) ;
if ( ext_hub_dev - > dynamic . flags . waiting_release ) {
release_port = true ;
} else if ( ext_hub_dev - > dynamic . flags . waiting_children ) {
assert ( ext_hub_dev - > dynamic . flags . waiting_release = = 0 ) ; // Device should be already released
assert ( ext_hub_dev - > dynamic . flags . is_gone = = 1 ) ; // Device should be gone by now
free_port = true ;
}
EXT_HUB_EXIT_CRITICAL ( ) ;
if ( ! release_port & & ! free_port ) {
// Parent still present, recycle the port
2024-04-02 08:25:11 -04:00
ret = p_ext_hub_driver - > constant . port_driver - > recycle ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
2024-06-21 06:57:26 -04:00
if ( ret ! = ESP_OK ) {
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Unable to recycle the port: %s " , ext_hub_dev - > constant . dev_addr , port_num , esp_err_to_name ( ret ) ) ;
goto exit ;
}
2024-04-02 08:25:11 -04:00
} else {
2024-06-21 06:57:26 -04:00
if ( release_port ) {
// Notify the port that parent is not available anymore and port should be recycled then freed
ret = p_ext_hub_driver - > constant . port_driver - > gone ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
if ( ret = = ESP_OK ) {
ESP_LOGD ( EXT_HUB_TAG , " [%d:%d] Port doesn't have a device and can be freed right now " ,
ext_hub_dev - > constant . dev_addr ,
port_num ) ;
assert ( free_port = = false ) ;
free_port = true ;
} else if ( ret = = ESP_ERR_NOT_FINISHED ) {
// Port has a device and will be recycled after USBH device will be released by all clients and freed
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Port is gone " ,
ext_hub_dev - > constant . dev_addr ,
port_num ) ;
// Logically, recycling logic are finished for the Hub Driver, return ESP_OK to free the node
assert ( free_port = = false ) ;
} else {
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Unable to mark port as gone: %s " ,
ext_hub_dev - > constant . dev_addr , port_num , esp_err_to_name ( ret ) ) ;
}
}
if ( free_port ) {
ret = device_port_free ( ext_hub_dev , port_idx ) ;
if ( ret ! = ESP_OK ) {
goto exit ;
}
}
2024-04-02 08:25:11 -04:00
}
2024-06-21 06:57:26 -04:00
ret = ESP_OK ;
exit :
2024-04-02 08:25:11 -04:00
return ret ;
}
esp_err_t ext_hub_port_reset ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
uint8_t port_idx = port_num - 1 ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( p_ext_hub_driver - > constant . port_driver ! = NULL , ESP_ERR_NOT_SUPPORTED ) ;
return p_ext_hub_driver - > constant . port_driver - > reset ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
2024-04-02 08:25:11 -04:00
}
esp_err_t ext_hub_port_active ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
uint8_t port_idx = port_num - 1 ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( p_ext_hub_driver - > constant . port_driver ! = NULL , ESP_ERR_NOT_SUPPORTED ) ;
return p_ext_hub_driver - > constant . port_driver - > active ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
2024-04-02 08:25:11 -04:00
}
esp_err_t ext_hub_port_disable ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
uint8_t port_idx = port_num - 1 ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( p_ext_hub_driver - > constant . port_driver ! = NULL , ESP_ERR_NOT_SUPPORTED ) ;
return p_ext_hub_driver - > constant . port_driver - > disable ( ext_hub_dev - > constant . ports [ port_idx ] ) ;
2024-04-02 08:25:11 -04:00
}
esp_err_t ext_hub_port_get_speed ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num , usb_speed_t * speed )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
uint8_t port_idx = port_num - 1 ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_idx < ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( p_ext_hub_driver - > constant . port_driver ! = NULL , ESP_ERR_NOT_SUPPORTED ) ;
return p_ext_hub_driver - > constant . port_driver - > get_speed ( ext_hub_dev - > constant . ports [ port_idx ] , speed ) ;
2024-04-02 08:25:11 -04:00
}
// -----------------------------------------------------------------------------
// --------------------------- USB Chapter 11 ----------------------------------
// -----------------------------------------------------------------------------
esp_err_t ext_hub_set_port_feature ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num , uint8_t feature )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
esp_err_t ret ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
usb_transfer_t * transfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_num ! = 0 & & port_num < = ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( ext_hub_dev - > single_thread . state = = EXT_HUB_STATE_CONFIGURED , ESP_ERR_INVALID_STATE ) ;
2024-04-02 08:25:11 -04:00
USB_SETUP_PACKET_INIT_SET_PORT_FEATURE ( ( usb_setup_packet_t * ) transfer - > data_buffer , port_num , feature ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = EXT_HUB_STAGE_PORT_FEATURE ;
2024-04-02 08:25:11 -04:00
ret = usbh_dev_submit_ctrl_urb ( ext_hub_dev - > constant . dev_hdl , ext_hub_dev - > constant . ctrl_urb ) ;
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Set port feature %#x, failed to submit ctrl urb: %s " ,
ext_hub_dev - > constant . dev_addr ,
port_num ,
feature ,
esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
}
return ret ;
}
esp_err_t ext_hub_clear_port_feature ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num , uint8_t feature )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
esp_err_t ret ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
usb_transfer_t * transfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_num ! = 0 & & port_num < = ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( ext_hub_dev - > single_thread . state = = EXT_HUB_STATE_CONFIGURED , ESP_ERR_INVALID_STATE ) ;
2024-04-02 08:25:11 -04:00
USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE ( ( usb_setup_packet_t * ) transfer - > data_buffer , port_num , feature ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = EXT_HUB_STAGE_PORT_FEATURE ;
2024-04-02 08:25:11 -04:00
ret = usbh_dev_submit_ctrl_urb ( ext_hub_dev - > constant . dev_hdl , ext_hub_dev - > constant . ctrl_urb ) ;
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Clear port feature %#x, failed to submit ctrl urb: %s " ,
ext_hub_dev - > constant . dev_addr ,
port_num ,
feature ,
esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
}
return ret ;
}
esp_err_t ext_hub_get_port_status ( ext_hub_handle_t ext_hub_hdl , uint8_t port_num )
{
EXT_HUB_ENTER_CRITICAL ( ) ;
EXT_HUB_CHECK_FROM_CRIT ( p_ext_hub_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
EXT_HUB_EXIT_CRITICAL ( ) ;
esp_err_t ret ;
EXT_HUB_CHECK ( ext_hub_hdl ! = NULL , ESP_ERR_INVALID_ARG ) ;
ext_hub_dev_t * ext_hub_dev = ( ext_hub_dev_t * ) ext_hub_hdl ;
usb_transfer_t * transfer = & ext_hub_dev - > constant . ctrl_urb - > transfer ;
2024-06-21 06:57:26 -04:00
EXT_HUB_CHECK ( port_num ! = 0 & & port_num < = ext_hub_dev - > constant . hub_desc - > bNbrPorts , ESP_ERR_INVALID_SIZE ) ;
EXT_HUB_CHECK ( ext_hub_dev - > single_thread . state = = EXT_HUB_STATE_CONFIGURED , ESP_ERR_INVALID_STATE ) ;
2024-04-02 08:25:11 -04:00
USB_SETUP_PACKET_INIT_GET_PORT_STATUS ( ( usb_setup_packet_t * ) transfer - > data_buffer , port_num ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_port_status_t ) ;
2024-06-21 06:57:26 -04:00
ext_hub_dev - > single_thread . stage = EXT_HUB_STAGE_PORT_STATUS_REQUEST ;
2024-04-02 08:25:11 -04:00
ret = usbh_dev_submit_ctrl_urb ( ext_hub_dev - > constant . dev_hdl , ext_hub_dev - > constant . ctrl_urb ) ;
if ( ret ! = ESP_OK ) {
2024-06-21 06:57:26 -04:00
ESP_LOGE ( EXT_HUB_TAG , " [%d:%d] Get port status, failed to submit ctrl urb: %s " ,
ext_hub_dev - > constant . dev_addr ,
port_num ,
esp_err_to_name ( ret ) ) ;
2024-04-02 08:25:11 -04:00
}
return ret ;
}