esp-idf/components/usb/ext_port.c

1405 lines
51 KiB
C

/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "usb_private.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_types_ch11.h"
#include "usbh.h"
#include "ext_port.h"
#include "usb/usb_helpers.h"
// Amount of attempts to reset the port with a device in case of a failure
#if (CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS)
#define EXT_PORT_RESET_ATTEMPTS CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS
#else
#define EXT_PORT_RESET_ATTEMPTS 1
#endif
// Delay in ms after sending the SetFeature() class specific request
#define EXT_PORT_RESET_RECOVERY_DELAY_MS CONFIG_USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS
#define EXT_PORT_POWER_ON_CUSTOM_DELAY CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE
#define EXT_PORT_POWER_ON_CUSTOM_DELAY_MS CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS
/**
* @brief External Port driver action flags
*/
typedef enum {
PORT_ACTION_HANDLE = (1 << 0), /**< Port requires handling */
PORT_ACTION_DISABLE = (1 << 1), /**< Disable port */
PORT_ACTION_RECYCLE = (1 << 2), /**< Recycle port */
PORT_ACTION_RESET = (1 << 3), /**< Reset port */
PORT_ACTION_GET_STATUS = (1 << 4), /**< Get status request */
} port_action_t;
/**
* @brief State of the device, attached to the port
*/
typedef enum {
PORT_DEV_NOT_PRESENT = 0,
PORT_DEV_PRESENT
} port_dev_state_t;
/**
* @brief Object representing a single External Port
*/
struct ext_port_s {
/**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
TAILQ_ENTRY(ext_port_s) tailq_entry;
union {
struct {
uint32_t in_pending_list: 1; /**< Port in pending list */
uint32_t status_lock: 1; /**< Ports' status lock. Parent Hub processes receiving new status */
uint32_t status_outdated: 1; /**< Ports' has changed and status is not valid. Get port status is required */
uint32_t is_gone: 1; /**< Ports' parent Hub is gone */
uint32_t has_enum_device: 1; /**< Port has an enumerated device */
uint32_t waiting_recycle: 1; /**< Port is waiting to be recycled */
uint32_t waiting_free: 1; /**< Port is waiting to be freed */
uint32_t reserved25: 25; /**< Reserved */
};
uint32_t val; /**< Ports' flags value */
} flags; /**< Ports' flags */
uint32_t action_flags; /**< Ports' action flags */
usb_hub_port_state_t state; /**< Ports' state */
usb_port_status_t status; /**< Ports' status data */
port_dev_state_t dev_state; /**< Ports' device state */
uint8_t dev_reset_attempts; /**< Ports' device reset failure */
struct {
// Ports' parent info for optimisation and better debug output
usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */
uint8_t parent_dev_addr; /**< Ports' parent device bus address */
// Port related constant members
ext_hub_handle_t ext_hub_hdl; /**< Ports' parent External Hub handle */
uint8_t port_num; /**< Ports' parent External Hub Port number */
int power_on_delay_ms; /**< Ports' Power on time to Power Good, ms */
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
};
/**
* @brief Definition of the type for the struct, representing a single External Port
*/
typedef struct ext_port_s ext_port_t;
/**
* @brief Object representing the External Port Driver
*/
typedef struct {
struct {
TAILQ_HEAD(ext_ports, ext_port_s) pending_tailq; /**< External Ports require handling */
} single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
struct {
ext_port_cb_t proc_req_cb; /**< External Port process callback */
void *proc_req_cb_arg; /**< External Port process callback argument */
ext_port_event_cb_t event_cb; /**< External Port event callback */
void *event_cb_arg; /**< External Port event callback argument */
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} ext_port_driver_t;
static ext_port_driver_t *p_ext_port_driver = NULL;
const char *EXT_PORT_TAG = "EXT_PORT";
// -----------------------------------------------------------------------------
// --------------------------- Helpers & Macros --------------------------------
// -----------------------------------------------------------------------------
#define EXT_PORT_CHECK(cond, ret_val) ({ \
if (!(cond)) { \
return (ret_val); \
} \
})
/**
* @brief Port connection status
*
* @param[in] ext_port Port object
* @return
* - true Port has a connection
* - false Port doesn't have a connection
*/
static inline bool port_has_connection(ext_port_t *ext_port)
{
return ext_port->status.wPortStatus.PORT_CONNECTION ? true : false;
}
/**
* @brief Port power status
*
* @param[in] ext_port Port object
* @return
* - true Port is powered
* - false Port in not powered
*/
static inline bool port_is_powered(ext_port_t *ext_port)
{
return ext_port->status.wPortStatus.PORT_POWER ? true : false;
}
/**
* @brief Port reset status
*
* @param[in] ext_port Port object
* @return
* - true Port is in reset
* - false Port is not in reset
*/
static inline bool port_is_in_reset(ext_port_t *ext_port)
{
return ext_port->status.wPortStatus.PORT_RESET ? true : false;
}
/**
* @brief Port enable status
*
* @param[in] ext_port Port object
* @return
* - true Port is enabled
* - false Port is disabled
*/
static inline bool port_is_enabled(ext_port_t *ext_port)
{
return ext_port->status.wPortStatus.PORT_ENABLE ? true : false;
}
/**
* @brief Port connection status changed
*
* @param[in] ext_port Port object
* @return
* - true Port connection changed
* - false Port connection not changed
*/
static inline bool port_has_changed_connection(ext_port_t * ext_port)
{
return ext_port->status.wPortChange.C_PORT_CONNECTION ? true : false;
}
/**
* @brief Port enabled status changed
*
* @param[in] ext_port Port object
* @return
* - true Port has error state and was disabled
* - false Port operates normally
*/
static inline bool port_has_changed_from_enable(ext_port_t *ext_port)
{
return ext_port->status.wPortChange.C_PORT_ENABLE ? true : false;
}
/**
* @brief Port reset status changed
*
* @param[in] ext_port Port object
* @return
* - true Port has finished the reset
* - false Port hasn't finished the reset
*/
static inline bool port_has_finished_reset(ext_port_t *ext_port)
{
return ext_port->status.wPortChange.C_PORT_RESET ? true : false;
}
// -----------------------------------------------------------------------------
// ------------------------ Parent Hub related logic ---------------------------
// -----------------------------------------------------------------------------
/**
* @brief Request the port status for the port object
*
* @note This call uses the External Hub Driver API
*
* @param[in] ext_port Port object
* @return
* - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed
* - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL
* - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort]
* - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured
* - ESP_OK: Status has been requested
*/
static esp_err_t port_request_status(ext_port_t* ext_port)
{
esp_err_t ret = ext_hub_get_port_status(ext_port->constant.ext_hub_hdl, ext_port->constant.port_num);
if (ret != ESP_OK) {
return ret;
}
// Port is requesting status, lock the status
ext_port->flags.status_lock = 1;
return ret;
}
/**
* @brief Sets the feature to the port
*
* @note This call uses the External Hub Driver API
*
* @param[in] ext_port Port object
* @param[in] feature Port feature to set
* @return
* - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed
* - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL
* - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort]
* - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured
* - ESP_OK: SetPortFeature() has been requested
*/
static esp_err_t port_set_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature)
{
esp_err_t ret = ext_hub_set_port_feature(ext_port->constant.ext_hub_hdl,
ext_port->constant.port_num,
feature);
if (ret != ESP_OK) {
return ret;
}
// Every set feature requires status update
ext_port->flags.status_outdated = 1;
switch (feature) {
case USB_FEATURE_PORT_POWER:
// PowerOn to PowerGood delay for port
vTaskDelay(pdMS_TO_TICKS(ext_port->constant.power_on_delay_ms));
break;
case USB_FEATURE_PORT_RESET:
// Port has reset, give the port some time to recover
vTaskDelay(pdMS_TO_TICKS(EXT_PORT_RESET_RECOVERY_DELAY_MS));
break;
default:
break;
}
return ret;
}
/**
* @brief Clears the feature to the port
*
* @note This call uses the External Hub Driver API
*
* @param[in] ext_port Port object
* @param[in] feature Port feature to set
* @return
* - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed
* - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL
* - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort]
* - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured
* - ESP_OK: ClearPortFeature() has been requested
*/
static esp_err_t port_clear_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature)
{
esp_err_t ret = ext_hub_clear_port_feature(ext_port->constant.ext_hub_hdl,
ext_port->constant.port_num,
feature);
if (ret != ESP_OK) {
return ret;
}
// Every clearing feature requires status update
ext_port->flags.status_outdated = 1;
return ret;
}
// -----------------------------------------------------------------------------
// ---------------------------- Internal logic ---------------------------------
// -----------------------------------------------------------------------------
/**
* @brief Port set actions
*
* @note
* - The External Port Driver is a single threaded driver
* - All calls are made from USB Host related task
* - Does not need critical section
* - Always requires processing
*
* @param[in] ext_port Port object
* @param[in] action_flags Action flags (could not be 0)
*/
static void port_set_actions(ext_port_t *ext_port, uint32_t action_flags)
{
assert(action_flags != 0); // Sanity check
// Check if port is not in the pending list
if (!ext_port->flags.in_pending_list) {
// Add port to pending list
ext_port->flags.in_pending_list = 1;
TAILQ_INSERT_TAIL(&p_ext_port_driver->single_thread.pending_tailq, ext_port, tailq_entry);
}
ext_port->action_flags |= action_flags;
p_ext_port_driver->constant.proc_req_cb(p_ext_port_driver->constant.proc_req_cb_arg);
}
/**
* @brief Stops the handling and remove the port from the pending list
*
* When the ports parent hub device has been removed, the port could be freed immediately
* if it doesn't have an inserted device.
* For this purpose port object should be removed from the pending
* list and the action flags should be dropped.
*
* @note
* - The External Port Driver is a single threaded driver
* - All calls are made from USB Host related task
* - Does not need critical section
* - Does not require processing
*
* @param[in] ext_port Port object
*/
static void port_stop_handling(ext_port_t *ext_port)
{
// Check if port is in the pending list
if (ext_port->flags.in_pending_list) {
// Add port to pending list
ext_port->flags.in_pending_list = 0;
TAILQ_REMOVE(&p_ext_port_driver->single_thread.pending_tailq, ext_port, tailq_entry);
}
if (ext_port->action_flags) {
// Port should be freed but has actions
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port stop handling. Dropped actions 0x%"PRIx32"",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->action_flags);
}
ext_port->action_flags = 0;
}
/**
* @brief Port event propagation
*
* Propagating the following events to the Hub Driver:
* - EXT_PORT_CONNECTED
* - EXT_PORT_RESET_COMPLETED
* - EXT_PORT_DISCONNECTED
*
* Filling all the event data parameters, according to the event.
*
* @param[in] ext_port Port object
* @param[in] event Port event
*/
static void port_event(ext_port_t *ext_port, ext_port_event_t event)
{
ext_port_event_data_t event_data = {
.event = event,
};
switch (event) {
case EXT_PORT_CONNECTED:
event_data.connected.ext_hub_hdl = ext_port->constant.ext_hub_hdl;
event_data.connected.parent_dev_hdl = ext_port->constant.parent_dev_hdl;
event_data.connected.parent_port_num = ext_port->constant.port_num;
break;
case EXT_PORT_RESET_COMPLETED:
event_data.reset_completed.parent_dev_hdl = ext_port->constant.parent_dev_hdl;
event_data.reset_completed.parent_port_num = ext_port->constant.port_num;
break;
case EXT_PORT_DISCONNECTED:
event_data.disconnected.parent_dev_hdl = ext_port->constant.parent_dev_hdl;
event_data.disconnected.parent_port_num = ext_port->constant.port_num;
break;
default:
// Should never occur
abort();
break;
}
p_ext_port_driver->constant.event_cb(&event_data, p_ext_port_driver->constant.event_cb_arg);
}
/**
* @brief Returns first port from the pending list
*
* @return
* - Port object pointer. NULL if no ports in pending list.
*/
static ext_port_t *get_port_from_pending_list(void)
{
ext_port_t *ext_port = NULL;
if (!TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq)) {
ext_port = TAILQ_FIRST(&p_ext_port_driver->single_thread.pending_tailq);
}
return ext_port;
}
/**
* @brief Allocates port object
*
* Allocates new por object with following parameters:
* - Port state: USB_PORT_STATE_NOT_CONFIGURED
* - Port device status: PORT_DEV_NOT_PRESENT
*
* @param[in] ext_hub_hdl Ports' parent hub handle
* @param[in] parent_dev_hdl Ports' parent device handle
* @param[in] parent_port_num Ports' parent port number
* @param[in] port_delay_ms Ports' Power on time to Power Good, ms
* @param[out] port_obj Port objects' pointer
* @return
* - ESP_ERR_INVALID_ARG: Unable to allocate the port object: parent hub handle and parent device handle must be not NULL
* - ESP_ERR_NO_MEM: Unable to allocate the port object: no memory
* - ESP_ERR_NOT_FINISHED: Unable to allocate the port object: parent device is not available
* - ESP_OK: Port object created successfully
*/
static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, uint16_t port_delay_ms, ext_port_t **port_obj)
{
uint8_t parent_dev_addr = 0;
EXT_PORT_CHECK(ext_hub_hdl != NULL && parent_dev_hdl != NULL, ESP_ERR_INVALID_ARG);
// This is the one exception from the requirement to use only the Ext Hub Driver API.
// TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH
EXT_PORT_CHECK(usbh_dev_get_addr(parent_dev_hdl, &parent_dev_addr) == ESP_OK, ESP_ERR_NOT_FINISHED);
ext_port_t *ext_port = heap_caps_calloc(1, sizeof(ext_port_t), MALLOC_CAP_DEFAULT);
if (ext_port == NULL) {
return ESP_ERR_NO_MEM;
}
ext_port->constant.parent_dev_hdl = parent_dev_hdl;
ext_port->constant.parent_dev_addr = parent_dev_addr;
ext_port->constant.ext_hub_hdl = ext_hub_hdl;
ext_port->constant.port_num = parent_port_num;
#if (EXT_PORT_POWER_ON_CUSTOM_DELAY)
ext_port->constant.power_on_delay_ms = EXT_PORT_POWER_ON_CUSTOM_DELAY_MS;
#else
// We don't need any additional delay in case port_delay_ms == 0, because this usually means
// that parent Hub device has no power switches
ext_port->constant.power_on_delay_ms = port_delay_ms;
#endif // EXT_PORT_POWER_ON_CUSTOM_DELAY
ext_port->state = USB_PORT_STATE_NOT_CONFIGURED;
ext_port->dev_state = PORT_DEV_NOT_PRESENT;
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port has been added (PwrOn2PwrGood=%d ms)",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->constant.power_on_delay_ms);
*port_obj = ext_port;
return ESP_OK;
}
/**
* @brief Port object handling complete
*
* @note This is the final stage of port object handling.
* After port being handled, the port object:
* - is removed from the pending list
* - is freed, is the port gone or port object waiting to be freed
*
* The final stage is parent notification about port object handling completion to enable the EP IN
*
* @param[in] ext_port Port object
* @return
* - ESP_OK: Port object handle completed
*/
static esp_err_t handle_complete(ext_port_t *ext_port)
{
bool all_ports_were_handled = true;
bool has_pending_ports = false;
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port is %s",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->flags.is_gone ? "gone" :
(ext_port->dev_state == PORT_DEV_PRESENT) ? "active" : "idle");
assert(ext_port->flags.is_gone == 0); // Handle complete could be called only on port that is still available
assert(ext_port->flags.waiting_free == 0); // Handle completion could be called only on port that should be freed
assert(ext_port->action_flags == 0); // Port should not have any further actions
assert(ext_port->flags.waiting_recycle == 0); // Port should not await to be recycled
assert(ext_port->flags.in_pending_list == 1); // Port should be in pending list
// Remove port from it
port_stop_handling(ext_port);
// Verify, that list has more ports in pending list
if (!TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq)) {
has_pending_ports = true;
}
// Port handling complete
#if ENABLE_MULTIPLE_HUBS
// When multiply Hubs are attached, we can have ports in pending list, but for another parent
ext_port_t *port = NULL;
TAILQ_FOREACH(port, &p_ext_port_driver->single_thread.pending_tailq, tailq_entry) {
if (port->constant.ext_hub_hdl == ext_port->constant.ext_hub_hdl) {
// Port with same parent has been found
all_ports_were_handled = false;
break;
}
}
#else
// When only one hub is supported - all ports have the same parent
// all_port_were_handled should be triggered if no more ports in pending list
all_ports_were_handled = !has_pending_ports;
#endif //
if (all_ports_were_handled) {
// Notify parent to enable Interrupt EP
ext_hub_status_handle_complete(ext_port->constant.ext_hub_hdl);
}
if (has_pending_ports) {
// Re-trigger the processing if there are more ports in pending list
p_ext_port_driver->constant.proc_req_cb(p_ext_port_driver->constant.proc_req_cb_arg);
}
return ESP_OK;
}
/**
* @brief Handles port status
*
* @param[in] ext_port Port object
* @return
* - true: Port has a status, that requires processing
* - false: Port has a status, that doesn't require processing
*/
static bool handle_port_status(ext_port_t *ext_port)
{
bool need_processing = false;
if (port_is_in_reset(ext_port)) {
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port still in reset, wait and repeat get status...",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
port_request_status(ext_port);
need_processing = true;
}
return need_processing;
}
/**
* @brief Handles port connection
*
* @param[in] ext_port Port object
*/
static void handle_port_connection(ext_port_t *ext_port)
{
bool has_device = false;
switch (ext_port->state) {
case USB_PORT_STATE_POWERED_OFF:
if (!port_is_powered(ext_port)) {
ext_port->state = USB_PORT_STATE_DISCONNECTED;
port_set_feature(ext_port, USB_FEATURE_PORT_POWER);
}
break;
case USB_PORT_STATE_DISCONNECTED:
case USB_PORT_STATE_DISABLED:
if (port_has_connection(ext_port)) {
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Mismatch port state and device status",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
has_device = true;
} else {
// New device connected, flush reset attempts
ext_port->dev_reset_attempts = 0;
ext_port->state = USB_PORT_STATE_RESETTING;
// New device has not been enumerated yet, reset the flag
ext_port->flags.has_enum_device = 0;
}
}
break;
case USB_PORT_STATE_RESETTING:
if (!port_has_connection(ext_port)) {
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Failed to issue downstream port reset",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
has_device = true;
} else {
ext_port->state = USB_PORT_STATE_DISCONNECTED;
}
}
break;
case USB_PORT_STATE_ENABLED:
// TODO: IDF-10071 Port debounce mechanism
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ext_port->flags.waiting_recycle = 1;
}
break;
default:
// Should never occur
abort();
break;
}
if (has_device) {
ext_port->flags.waiting_recycle = 1;
ext_port->dev_state = PORT_DEV_NOT_PRESENT;
port_event(ext_port, EXT_PORT_DISCONNECTED);
}
}
/**
* @brief Handles port changes
*
* @param[in] ext_port Port object
* @return
* - true: Port has a change, that requires processing
* - false: Port has no changes and doesn't require processing
*/
static bool handle_port_changes(ext_port_t *ext_port)
{
bool need_processing = false;
if (port_has_changed_connection(ext_port)) {
handle_port_connection(ext_port);
port_clear_feature(ext_port, USB_FEATURE_C_PORT_CONNECTION);
need_processing = true;
} else if (port_has_changed_from_enable(ext_port)) {
// For more information, refer to section 11.8.1 Port Error of usb_2.0 specification
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Error on port (state=%d, dev=%d)",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->state,
ext_port->dev_state == PORT_DEV_PRESENT);
port_clear_feature(ext_port, USB_FEATURE_C_PORT_ENABLE);
need_processing = true;
} else if (port_has_finished_reset(ext_port)) {
if (port_has_connection(ext_port)) {
ext_port->state = USB_PORT_STATE_ENABLED;
}
port_clear_feature(ext_port, USB_FEATURE_C_PORT_RESET);
need_processing = true;
}
return need_processing;
}
/**
* @brief Handles port state
*
* @param[in] ext_port Port object
*/
static void handle_port_state(ext_port_t *ext_port)
{
bool need_handling = false;
usb_hub_port_state_t curr_state = ext_port->state;
usb_hub_port_state_t new_state = ext_port->state;
switch (curr_state) {
case USB_PORT_STATE_NOT_CONFIGURED:
new_state = USB_PORT_STATE_POWERED_OFF;
port_request_status(ext_port);
need_handling = true;
break;
case USB_PORT_STATE_POWERED_OFF:
// Port power state depends on the wHubCharacteristics.power_switching
new_state = USB_PORT_STATE_DISCONNECTED;
port_set_feature(ext_port, USB_FEATURE_PORT_POWER);
need_handling = true;
break;
case USB_PORT_STATE_DISCONNECTED:
if (port_has_connection(ext_port)) {
if (ext_port->dev_reset_attempts < EXT_PORT_RESET_ATTEMPTS) {
// Available, it EXT_PORT_RESET_ATTEMPTS > 1
ext_port->dev_reset_attempts++;
port_set_feature(ext_port, USB_FEATURE_PORT_RESET);
need_handling = true;
} else {
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Unable to reset the device",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
}
}
break;
case USB_PORT_STATE_DISABLED:
if (port_has_connection(ext_port)) {
// This logic does depend on the moment, when we propagate the EXT_PORT_DISCONNECTED event
// during the port disable.
if (!ext_port->flags.has_enum_device && ext_port->flags.waiting_recycle) {
// Port was disabled before enumeration, so the USBH device object was not created.
// Clean the recycle flag and complete port handling with device attached.
ext_port->flags.waiting_recycle = 0;
}
}
break;
case USB_PORT_STATE_RESETTING:
if (port_has_connection(ext_port)) {
// Port in resetting state and has connection
if (ext_port->dev_state == PORT_DEV_NOT_PRESENT) {
assert(ext_port->dev_reset_attempts == 0); // First reset, attempts always should be 0
ext_port->dev_reset_attempts++;
port_set_feature(ext_port, USB_FEATURE_PORT_RESET);
} else {
assert(port_is_enabled(ext_port)); // Port should be enabled
port_event(ext_port, EXT_PORT_RESET_COMPLETED);
new_state = USB_PORT_STATE_ENABLED;
}
need_handling = true;
} else {
// Port in resetting state and doesn't have connection
// Error case, could be, when device was removed during port reset
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Device gone during port reset, recover port",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
new_state = USB_PORT_STATE_DISCONNECTED;
}
break;
case USB_PORT_STATE_ENABLED:
if (port_is_enabled(ext_port)) {
if (port_has_connection(ext_port)) {
if (ext_port->dev_state == PORT_DEV_NOT_PRESENT) {
usb_speed_t dev_speed = USB_SPEED_LOW;
if (ext_port->status.wPortStatus.PORT_LOW_SPEED == 0) {
dev_speed = ext_port->status.wPortStatus.PORT_HIGH_SPEED
? USB_SPEED_HIGH
: USB_SPEED_FULL;
}
ESP_LOGD(EXT_PORT_TAG, "Device speed %s", (char *[]) {
"Low", "Full", "High"
}[dev_speed]);
ext_port->dev_state = PORT_DEV_PRESENT;
port_event(ext_port, EXT_PORT_CONNECTED);
} else {
// Port enabled, device present, reset completed
ext_port->dev_reset_attempts = 0;
port_event(ext_port, EXT_PORT_RESET_COMPLETED);
}
need_handling = true;
} else {
// TODO: IDF-10071 Port debounce mechanism
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Enabled, but doesn't have connection",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
}
} else {
// If port was enabled, there should be an active device
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ext_port->dev_state = PORT_DEV_NOT_PRESENT;
port_event(ext_port, EXT_PORT_DISCONNECTED);
ext_port->flags.waiting_recycle = 1;
} else {
// Error state
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Enabled, but doesn't have a device",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
new_state = USB_PORT_STATE_DISCONNECTED;
}
}
break;
default:
// Should never occur
abort();
break;
}
if (curr_state != new_state) {
ESP_LOGD(EXT_PORT_TAG, "New state: %d", new_state);
ext_port->state = new_state;
}
if (!ext_port->flags.waiting_recycle && !need_handling) {
handle_complete(ext_port);
}
}
/**
* @brief Port object handling action
*
* @note Handles the port in the following order:
* - Port changes (C_CONNECTION, C_ENABLE, C_RESET)
* - Port status (Port still in RESET state)
* - Port state
*
* When ports' handling has been completed, removes the port object from the pending list.
*
* @param[in] ext_port Port object
*/
static void handle_port(ext_port_t *ext_port)
{
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] change=0x%04x, status=0x%04x, state=%d",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->status.wPortChange.val,
ext_port->status.wPortStatus.val,
ext_port->state);
if (handle_port_changes(ext_port)) {
return;
} else if (handle_port_status(ext_port)) {
return;
}
handle_port_state(ext_port);
}
/**
* @brief Port object handling recycle action
*
* @note Port should undergo a recycle procedure when:
* - Device has been detached and freed by all clients (port is present)
* - Parent device been detached (port is gone)
* - Port has run an error during the handling
*
* @param[in] ext_port Port object
*/
static void handle_recycle(ext_port_t *ext_port)
{
assert(ext_port->flags.waiting_recycle == 1);
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port recycle (state=%d, dev=%d)",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->state,
ext_port->dev_state);
ext_port->flags.waiting_recycle = 0;
if (ext_port->flags.status_lock) {
// Port is awaiting the status
return;
}
// Port should not have any changes
assert(ext_port->status.wPortChange.val == 0);
switch (ext_port->state) {
case USB_PORT_STATE_DISABLED:
// We don't need to do anything, as port will be handled after completing USB_FEATURE_PORT_ENABLE
if (ext_port->flags.is_gone) {
handle_complete(ext_port);
}
break;
default:
ext_port->state = USB_PORT_STATE_DISCONNECTED;
if (ext_port->flags.is_gone) {
handle_complete(ext_port);
} else {
handle_port(ext_port);
}
break;
}
}
/**
* @brief Port object handling disable action
*
* @param[in] ext_port Port object
*/
static void handle_disable(ext_port_t *ext_port)
{
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Disable (state=%d, dev=%d)",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->state,
ext_port->dev_state);
assert(ext_port->state != USB_PORT_STATE_POWERED_OFF);
assert(ext_port->state != USB_PORT_STATE_DISCONNECTED);
assert(ext_port->state != USB_PORT_STATE_NOT_CONFIGURED);
if (ext_port->state == USB_PORT_STATE_ENABLED) {
if (port_has_connection(ext_port)) {
ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Port disabled, reset attempts=%d",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->dev_reset_attempts);
// Do not try to reset port anymore
ext_port->dev_reset_attempts = EXT_PORT_RESET_ATTEMPTS;
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ext_port->dev_state = PORT_DEV_NOT_PRESENT;
// Propagate device disconnection if device present
port_event(ext_port, EXT_PORT_DISCONNECTED);
ext_port->flags.waiting_recycle = 1;
}
}
}
ext_port->state = USB_PORT_STATE_DISABLED;
if (!ext_port->flags.is_gone) {
// Port not gone, disable port
port_clear_feature(ext_port, USB_FEATURE_PORT_ENABLE);
}
}
// -----------------------------------------------------------------------------
// ------------------------ External Port API ----------------------------------
// -----------------------------------------------------------------------------
/**
* @brief Creates new port object
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_cfg Port configuration
* @param[out] port_hdl Port object handle
* @return
* - ESP_ERR_INVALID_STATE: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: Unable to create the port, arguments couldn't be NULL
* - ESP_ERR_NO_MEM: Unable to allocate the port object: no memory
* - ESP_ERR_NOT_FINISHED: Unable to allocate the port object: parent device is not available
* - ESP_OK: Port has been created and added to the pending list
*/
static esp_err_t port_new(void *port_cfg, void **port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_INVALID_STATE);
EXT_PORT_CHECK(port_cfg != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *port = NULL;
ext_port_config_t *config = (ext_port_config_t *)port_cfg;
esp_err_t ret = port_alloc(config->ext_hub_hdl,
config->parent_dev_hdl,
config->parent_port_num,
config->port_power_delay_ms,
&port);
if (ret != ESP_OK) {
*port_hdl = NULL;
goto exit;
}
port_set_actions(port, PORT_ACTION_HANDLE);
*port_hdl = (ext_port_hdl_t) port;
exit:
return ret;
}
/**
* @brief Indicates to the External Port driver that a device's port can be recycled
*
* The device connected to the port has been freed. The External Port driver can now
* recycle the port.
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: Unable to recycle the port, port handle couldn't be NULL
* - ESP_OK:
*/
static esp_err_t port_recycle(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
ESP_LOGD(EXT_PORT_TAG, "Port %d request recycle, state=%d", ext_port->constant.port_num, ext_port->state);
port_set_actions(ext_port, PORT_ACTION_RECYCLE);
return ESP_OK;
}
/**
* @brief Indicate to the External Port driver that reset of a device's port is required
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: Port handle couldn't be NULL
* - ESP_ERR_INVALID_STATE: Port doesn't have a connection or not enabled
* - ESP_OK: Port reset requested
*/
static esp_err_t port_reset(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
EXT_PORT_CHECK(port_has_connection(ext_port), ESP_ERR_INVALID_STATE);
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port reset request",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
// Reset can be triggered only when port is enabled
EXT_PORT_CHECK(ext_port->state == USB_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE);
port_set_actions(ext_port, PORT_ACTION_RESET);
return ESP_OK;
}
/**
* @brief Get the speed of the External Port
*
* The speed of the port is determined by the speed of the device connected to it.
*
* @note This function is only when a device connected to the port and has been reset
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @param[out] speed Speed of the port
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL, speed pointer can't be NULL
* - ESP_ERR_INVALID_STATE: No valid device connected to the port
* - ESP_OK: Device speed obtained
*/
esp_err_t port_get_speed(void *port_hdl, usb_speed_t *speed)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL && speed != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
usb_speed_t dev_speed = USB_SPEED_LOW;
EXT_PORT_CHECK(port_has_connection(ext_port), ESP_ERR_INVALID_STATE);
// Full-speed or High-speed device attached to this port (determined by bit PORT_HIGH_SPEED).
if (ext_port->status.wPortStatus.PORT_LOW_SPEED == 0) {
dev_speed = ext_port->status.wPortStatus.PORT_HIGH_SPEED
? USB_SPEED_HIGH
: USB_SPEED_FULL;
}
// Otherwise, Low-speed
*speed = dev_speed;
return ESP_OK;
}
/**
* @brief Indicate to the External Port Driver that port has an active device
*
* The device:
* - has been enumerated
* - still present
* - is in configured state
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_OK: Port has a device and the device completed enumeration process
*/
static esp_err_t port_active(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port has an enumerated device",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
ext_port->flags.has_enum_device = 1;
return handle_complete(ext_port);
}
/**
* @brief Indicate to the External Port Driver that port should be disabled
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_ERR_INVALID_STATE: The port is not enabled
* - ESP_OK: Port disable requested
*/
static esp_err_t port_disable(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Disable",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
EXT_PORT_CHECK(ext_port->state == USB_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE);
port_set_actions(ext_port, PORT_ACTION_DISABLE);
return ESP_OK;
}
/**
* @brief Deletes the port object
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_OK: Port object has been freed and deleted
*/
static esp_err_t port_delete(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *) port_hdl;
// Sanity checks
assert(ext_port->dev_state == PORT_DEV_NOT_PRESENT); // Port should not have a device
assert(ext_port->flags.in_pending_list == 0); // Port should not be in pending list
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Freeing",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num);
heap_caps_free(ext_port);
return ESP_OK;
}
/**
* @brief Marks the Port as gone and initialize process of freeing the Port
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_ERR_NOT_FINISHED: The port has a device and should be recycled
* - ESP_OK: Port object has been marked as gone and could be freed
*/
static esp_err_t port_gone(void *port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *)port_hdl;
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port is gone (state=%d, dev=%d)",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
ext_port->state,
ext_port->dev_state);
bool has_device = false;
ext_port->flags.is_gone = 1;
ext_port->flags.waiting_free = 1;
switch (ext_port->state) {
case USB_PORT_STATE_ENABLED:
case USB_PORT_STATE_DISABLED:
case USB_PORT_STATE_RESETTING:
// Port could have a device
if (ext_port->dev_state == PORT_DEV_PRESENT) {
ext_port->dev_state = PORT_DEV_NOT_PRESENT;
ext_port->flags.waiting_recycle = 1;
port_event(ext_port, EXT_PORT_DISCONNECTED);
has_device = true;
}
break;
case USB_PORT_STATE_NOT_CONFIGURED:
// Port has been added, but the Driver has not started the handling yet
case USB_PORT_STATE_POWERED_OFF:
case USB_PORT_STATE_DISCONNECTED:
// Port has been added, but doesn't have a device
// We can remove the port from pending list and could be freed by the External Hub Driver
break;
default:
// Should never occur
abort();
break;
}
// If the port is in handling list, stop it
if (ext_port->flags.in_pending_list) {
port_stop_handling(ext_port);
}
return (has_device) ? ESP_ERR_NOT_FINISHED : ESP_OK;
}
/**
* @brief Indicate to the External Port Driver that Port request has been finished and the Port requires handling
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_OK: Port action was requested
*/
static esp_err_t port_get_status(void* port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *)port_hdl;
port_set_actions(ext_port, PORT_ACTION_GET_STATUS);
return ESP_OK;
}
/**
* @brief Indicate to the External Port Driver that the Port has new status
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @param[in] port_status New status data
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL, the status data can't be NULL
* - ESP_OK: Port action was requested
*/
static esp_err_t port_set_status(void* port_hdl, const usb_port_status_t *port_status)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL && port_status != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *)port_hdl;
// Update status
ext_port->status.wPortChange.val = port_status->wPortChange.val;
ext_port->status.wPortStatus.val = port_status->wPortStatus.val;
// Status valid
ext_port->flags.status_outdated = 0;
// Remove status lock
ext_port->flags.status_lock = 0;
// Request port handling
port_set_actions(ext_port, PORT_ACTION_HANDLE);
return ESP_OK;
}
/**
* @brief Indicate to the External Port Driver that the Port requires processing
*
* @note This function should only be called from the External Hub Driver
*
* @param[in] port_hdl Port object handle
* @return
* - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed
* - ESP_ERR_INVALID_ARG: The port handle can't be NULL
* - ESP_OK: Port action was requested
*/
static esp_err_t port_req_process(void* port_hdl)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG);
ext_port_t *ext_port = (ext_port_t *)port_hdl;
if (ext_port->flags.status_outdated) {
port_set_actions(ext_port, PORT_ACTION_GET_STATUS);
} else {
port_set_actions(ext_port, PORT_ACTION_HANDLE);
}
return ESP_OK;
}
// -----------------------------------------------------------------------------
// ------------------ External Port Processing Functions -----------------------
// -----------------------------------------------------------------------------
/**
* @brief External Port Driver API
*/
const ext_hub_port_driver_t ext_port_driver = {
.new = port_new,
.reset = port_reset,
.recycle = port_recycle,
.active = port_active,
.disable = port_disable,
.gone = port_gone,
.del = port_delete,
.get_speed = port_get_speed,
.get_status = port_get_status,
.set_status = port_set_status,
.req_process = port_req_process,
};
esp_err_t ext_port_install(const ext_port_driver_config_t *config)
{
EXT_PORT_CHECK(p_ext_port_driver == NULL, ESP_ERR_NOT_ALLOWED);
ext_port_driver_t *ext_port_drv = heap_caps_calloc(1, sizeof(ext_port_driver_t), MALLOC_CAP_DEFAULT);
EXT_PORT_CHECK(ext_port_drv != NULL, ESP_ERR_NO_MEM);
// Save callbacks
ext_port_drv->constant.proc_req_cb = config->proc_req_cb;
ext_port_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg;
ext_port_drv->constant.event_cb = config->event_cb;
ext_port_drv->constant.event_cb_arg = config->event_cb_arg;
TAILQ_INIT(&ext_port_drv->single_thread.pending_tailq);
p_ext_port_driver = ext_port_drv;
ESP_LOGD(EXT_PORT_TAG, "Driver installed");
return ESP_OK;
}
esp_err_t ext_port_uninstall(void)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_PORT_CHECK(TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq), ESP_ERR_INVALID_STATE);
ext_port_driver_t *ext_port_drv = p_ext_port_driver;
p_ext_port_driver = NULL;
heap_caps_free(ext_port_drv);
ESP_LOGD(EXT_PORT_TAG, "Driver uninstalled");
return ESP_OK;
}
esp_err_t ext_port_process(void)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED);
ext_port_t *ext_port = get_port_from_pending_list();
if (ext_port == NULL) {
// No more ports in list to handle
// NOTE:
// This is possible, when an external Hub detached sooner than being
// configured and handled by the Driver. This is not an error case,
// because the processing requests by the HUB_DRIVER_ACTION_EXT_PORT
// flag in the Hub Driver and there is no way to clean it.
ESP_LOGD(EXT_PORT_TAG, "No more ports to handle");
return ESP_OK;
}
uint32_t action_flags = ext_port->action_flags;
ext_port->action_flags = 0;
while (action_flags) {
// Keep processing until all port's action have been handled
ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Processing actions 0x%"PRIx32"",
ext_port->constant.parent_dev_addr,
ext_port->constant.port_num,
action_flags);
if (action_flags & PORT_ACTION_HANDLE) {
handle_port(ext_port);
}
if (action_flags & PORT_ACTION_DISABLE) {
handle_disable(ext_port);
}
if (action_flags & PORT_ACTION_RECYCLE) {
handle_recycle(ext_port);
}
/*
* Feature related actions are mutual exclusive and require:
* - transfer completion callback
* - further request of new port status via get_status(), except PORT_ACTION_GET_STATUS itself
*/
if (action_flags & PORT_ACTION_GET_STATUS) {
port_request_status(ext_port);
} else if (action_flags & PORT_ACTION_RESET) {
if (ext_port->state != USB_PORT_STATE_RESETTING) {
/*
* IMPORTANT NOTE
* This is possible, when the reset is requested via port_reset()
* Port reset is possible only in two states:
* - USB_PORT_STATE_DISCONNECTED (mainly, first reset)
* - USB_PORT_STATE_ENABLED (mainly, second reset)
*/
assert(ext_port->state == USB_PORT_STATE_DISCONNECTED ||
ext_port->state == USB_PORT_STATE_ENABLED);
ext_port->state = USB_PORT_STATE_RESETTING;
}
port_set_feature(ext_port, USB_FEATURE_PORT_RESET);
}
action_flags = ext_port->action_flags;
ext_port->action_flags = 0;
}
return ESP_OK;
}
const ext_hub_port_driver_t *ext_port_get_driver(void)
{
EXT_PORT_CHECK(p_ext_port_driver != NULL, NULL);
return &ext_port_driver;
}