/*
 * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "sdkconfig.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/queue.h>
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_bit_defs.h"
#include "esp_private/critical_section.h"
#include "esp_log.h"
#include "usb_private.h"
#include "hcd.h"
#include "hub.h"
#include "usb/usb_helpers.h"

#if ENABLE_USB_HUBS
#include "ext_hub.h"
#endif // ENABLE_USB_HUBS

/*
Implementation of the HUB driver that only supports the Root Hub with a single port. Therefore, we currently don't
implement the bare minimum to control the root HCD port.
*/

#ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN
#define HUB_ROOT_HCD_PORT_FIFO_BIAS                 HCD_PORT_FIFO_BIAS_RX
#elif CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT
#define HUB_ROOT_HCD_PORT_FIFO_BIAS                 HCD_PORT_FIFO_BIAS_PTX
#else   // CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED
#define HUB_ROOT_HCD_PORT_FIFO_BIAS                 HCD_PORT_FIFO_BIAS_BALANCED
#endif

#define PORT_REQ_DISABLE                            0x01
#define PORT_REQ_RECOVER                            0x02

/**
 * @brief Hub driver action flags
 */
typedef enum {
    HUB_DRIVER_ACTION_ROOT_EVENT          = (1 << 0),
    HUB_DRIVER_ACTION_ROOT_REQ            = (1 << 1),
#if ENABLE_USB_HUBS
    HUB_DRIVER_ACTION_EXT_HUB             = (1 << 6),
    HUB_DRIVER_ACTION_EXT_PORT            = (1 << 7)
#endif // ENABLE_USB_HUBS
} hub_flag_action_t;

/**
 * @brief Root port states
 */
typedef enum {
    ROOT_PORT_STATE_NOT_POWERED,    /**< Root port initialized and/or not powered */
    ROOT_PORT_STATE_POWERED,        /**< Root port is powered, device is not connected */
    ROOT_PORT_STATE_DISABLED,       /**< A device is connected but is disabled (i.e., not reset, no SOFs are sent) */
    ROOT_PORT_STATE_ENABLED,        /**< A device is connected, port has been reset, SOFs are sent */
} root_port_state_t;

/**
 * @brief Hub device tree node
 *
 * Object type of a node in the USB device tree that is maintained by the Hub driver
 */
struct dev_tree_node_s {
    TAILQ_ENTRY(dev_tree_node_s) tailq_entry;   /**< Entry for the device tree node object tailq */
    unsigned int uid;                       /**< Device's unique ID */
    usb_device_handle_t parent_dev_hdl;     /**< Device's parent handle */
    uint8_t parent_port_num;                /**< Device's parent port number */
};

typedef struct dev_tree_node_s dev_tree_node_t;

typedef struct {
    struct {
        union {
            struct {
                hub_flag_action_t actions: 8;       /**< Hub actions */
                uint32_t reserved24: 24;            /**< Reserved */
            };
            uint32_t val;                           /**< Hub flag action value */
        } flags;                                    /**< Hub flags */
        root_port_state_t root_port_state;          /**< Root port state */
        unsigned int port_reqs;                     /**< Root port request flag */
    } dynamic;                                      /**< Dynamic members. Require a critical section */

    struct {
        TAILQ_HEAD(tailhead_devs, dev_tree_node_s) dev_nodes_tailq;     /**< Tailq of attached devices */
    } single_thread;                                /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */

    struct {
        hcd_port_handle_t root_port_hdl;            /**< Root port HCD handle */
        usb_proc_req_cb_t proc_req_cb;              /**< Process request callback */
        void *proc_req_cb_arg;                      /**< Process request callback argument */
        hub_event_cb_t event_cb;                    /**< Hub Driver event callback */
        void *event_cb_arg;                         /**< Event callback argument */
    } constant;                                     /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} hub_driver_t;

static hub_driver_t *p_hub_driver_obj = NULL;

const char *HUB_DRIVER_TAG = "HUB";

DEFINE_CRIT_SECTION_LOCK_STATIC(hub_driver_lock);
#define HUB_DRIVER_ENTER_CRITICAL_ISR()       esp_os_enter_critical_isr(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL_ISR()        esp_os_exit_critical_isr(&hub_driver_lock)
#define HUB_DRIVER_ENTER_CRITICAL()           esp_os_enter_critical(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL()            esp_os_exit_critical(&hub_driver_lock)
#define HUB_DRIVER_ENTER_CRITICAL_SAFE()      esp_os_enter_critical_safe(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL_SAFE()       esp_os_exit_critical_safe(&hub_driver_lock)

#define HUB_DRIVER_CHECK(cond, ret_val) ({              \
            if (!(cond)) {                              \
                return (ret_val);                       \
            }                                           \
})
#define HUB_DRIVER_CHECK_FROM_CRIT(cond, ret_val) ({    \
            if (!(cond)) {                              \
                HUB_DRIVER_EXIT_CRITICAL();             \
                return ret_val;                         \
            }                                           \
})

// ------------------------------------------------- Forward Declare ---------------------------------------------------

/**
 * @brief HCD port callback for the root port
 *
 * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process()
 * - Under the current HCD implementation, this callback should only be ever be called in an ISR
 * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run
 *
 * @param port_hdl HCD port handle
 * @param port_event HCD port event
 * @param user_arg Callback argument
 * @param in_isr Whether callback is in an ISR context
 * @return Whether a yield is required
 */
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr);

// ---------------------- Internal Logic ------------------------

/**
 * @brief Creates new device tree node and propagate HUB_EVENT_CONNECTED event
 *
 * @return esp_err_t
 */
static esp_err_t dev_tree_node_new(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed)
{
    esp_err_t ret;
    // Allocate memory for a new device tree node
    dev_tree_node_t *dev_tree_node = heap_caps_calloc(1, sizeof(dev_tree_node_t), MALLOC_CAP_DEFAULT);
    if (dev_tree_node == NULL) {
        return ESP_ERR_NO_MEM;
    }
    // Assign initial UID based on the current number of registered devices
    int device_num = 0;
    ESP_ERROR_CHECK(usbh_devs_num(&device_num));
    dev_tree_node->uid = device_num + 1;
    // Ensure the UID is unique
    while (usbh_devs_is_uid_in_use(dev_tree_node->uid)) {
        dev_tree_node->uid++;
        assert(dev_tree_node->uid != 0); // No overflow possible
    }

    dev_tree_node->parent_dev_hdl = parent_dev_hdl;
    dev_tree_node->parent_port_num = parent_port_num;

    // Initialize and register a new USBH Device with the assigned UID
    usbh_dev_params_t params = {
        .uid = dev_tree_node->uid,
        .speed = speed,
        .root_port_hdl = p_hub_driver_obj->constant.root_port_hdl, // Always the same for all devices
        // TODO: IDF-10023 Move parent-child tree management responsibility to Hub Driver
        .parent_dev_hdl = parent_dev_hdl,
        .parent_port_num = parent_port_num,
    };

    ret = usbh_devs_add(&params);
    if (ret != ESP_OK) {
        // Device registration may fail if there are no available HCD channels.
        // TODO: IDF-10044 Implement hub recovery mechanism for running out of HCD channels.
        goto fail;
    }

    TAILQ_INSERT_TAIL(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry);

    ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): new", dev_tree_node->uid);

    hub_event_data_t event_data = {
        .event = HUB_EVENT_CONNECTED,
        .connected = {
            .uid = dev_tree_node->uid,
        },
    };
    p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);

    return ret;

fail:
    heap_caps_free(dev_tree_node);
    return ret;
}

static esp_err_t dev_tree_node_reset_completed(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    dev_tree_node_t *dev_tree_node = NULL;
    dev_tree_node_t *dev_tree_iter;
    // Search the device tree nodes list for a device node with the specified parent
    TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
        if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
                dev_tree_iter->parent_port_num == parent_port_num) {
            dev_tree_node = dev_tree_iter;
            break;
        }
    }

    if (dev_tree_node == NULL) {
        ESP_LOGE(HUB_DRIVER_TAG, "Reset completed, but device tree node with port=%d not found", parent_port_num);
        return ESP_ERR_NOT_FOUND;
    }

    hub_event_data_t event_data = {
        .event = HUB_EVENT_RESET_COMPLETED,
        .reset_completed = {
            .uid = dev_tree_node->uid,
        },
    };
    p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
    return ESP_OK;
}

static esp_err_t dev_tree_node_dev_gone(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    dev_tree_node_t *dev_tree_node = NULL;
    dev_tree_node_t *dev_tree_iter;
    // Search the device tree nodes list for a device node with the specified parent
    TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
        if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
                dev_tree_iter->parent_port_num == parent_port_num) {
            dev_tree_node = dev_tree_iter;
            break;
        }
    }

    if (dev_tree_node == NULL) {
        ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num);
        return ESP_ERR_NOT_FOUND;
    }

    ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): device gone", dev_tree_node->uid);

    hub_event_data_t event_data = {
        .event = HUB_EVENT_DISCONNECTED,
        .disconnected = {
            .uid = dev_tree_node->uid,
        },
    };
    p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);

    return ESP_OK;
}

/**
 * @brief Frees device tree node
 *
 * @return esp_err_t
 */
static esp_err_t dev_tree_node_remove_by_parent(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    dev_tree_node_t *dev_tree_node = NULL;
    dev_tree_node_t *dev_tree_iter;
    // Search the device tree nodes list for a device node with the specified parent
    TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
        if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
                dev_tree_iter->parent_port_num == parent_port_num) {
            dev_tree_node = dev_tree_iter;
            break;
        }
    }

    if (dev_tree_node == NULL) {
        ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num);
        return ESP_ERR_NOT_FOUND;
    }

    ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): freeing", dev_tree_node->uid);

    TAILQ_REMOVE(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry);
    heap_caps_free(dev_tree_node);
    return ESP_OK;
}

// ---------------------- Callbacks ------------------------

static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
{
    HUB_DRIVER_ENTER_CRITICAL_SAFE();
    p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_EVENT;
    HUB_DRIVER_EXIT_CRITICAL_SAFE();
    assert(in_isr); // Currently, this callback should only ever be called from an ISR context
    return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
}

#ifdef ENABLE_USB_HUBS
static bool ext_hub_callback(bool in_isr, void *user_arg)
{
    HUB_DRIVER_ENTER_CRITICAL_SAFE();
    p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_HUB;
    HUB_DRIVER_EXIT_CRITICAL_SAFE();
    return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
}

static void ext_port_callback(void *user_arg)
{
    HUB_DRIVER_ENTER_CRITICAL();
    p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_PORT;
    HUB_DRIVER_EXIT_CRITICAL();
    p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
}

static void ext_port_event_callback(ext_port_hdl_t port_hdl, ext_port_event_data_t *event_data, void *arg)
{
    ext_hub_handle_t ext_hub_hdl = (ext_hub_handle_t) ext_port_get_context(port_hdl);

    switch (event_data->event) {
    case EXT_PORT_CONNECTED:
        // First reset is done by ext_port logic
        usb_speed_t port_speed;

        if (ext_hub_port_get_speed(ext_hub_hdl,
                                   event_data->connected.parent_port_num,
                                   &port_speed) != ESP_OK) {
            goto new_ds_dev_err;
        }

        // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH
        usb_device_info_t parent_dev_info;
        ESP_ERROR_CHECK(usbh_dev_get_info(event_data->connected.parent_dev_hdl, &parent_dev_info));
        if (parent_dev_info.speed == USB_SPEED_HIGH) {
            if (port_speed != parent_dev_info.speed) {
                ESP_LOGE(HUB_DRIVER_TAG, "Connected device is %s, transaction translator (TT) is not supported",
                (char *[]) {
                    "LS", "FS", "HS"
                }[port_speed]);
                goto new_ds_dev_err;
            }
        }

        if (dev_tree_node_new(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new downstream device");
            goto new_ds_dev_err;
        }
        break;
new_ds_dev_err:
        ext_hub_port_disable(ext_hub_hdl, event_data->connected.parent_port_num);
        break;
    case EXT_PORT_RESET_COMPLETED:
        ESP_ERROR_CHECK(dev_tree_node_reset_completed(event_data->reset_completed.parent_dev_hdl, event_data->reset_completed.parent_port_num));
        break;
    case EXT_PORT_DISCONNECTED:
        // The node could be freed by now, no need to verify the result here
        dev_tree_node_dev_gone(event_data->disconnected.parent_dev_hdl, event_data->disconnected.parent_port_num);
        break;
    default:
        // Should never occur
        abort();
        break;
    }
}
#endif // ENABLE_USB_HUBS

// ---------------------- Handlers -------------------------
static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
{
    hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl);
    switch (port_event) {
    case HCD_PORT_EVENT_NONE:
        // Nothing to do
        break;
    case HCD_PORT_EVENT_CONNECTION: {
        if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed");
            goto reset_err;
        }
        ESP_LOGD(HUB_DRIVER_TAG, "Root port reset");
        usb_speed_t speed;
        if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) {
            goto new_dev_err;
        }

        if (dev_tree_node_new(NULL, 0, speed) != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new device");
            goto new_dev_err;
        }

        // Change Port state
        HUB_DRIVER_ENTER_CRITICAL();
        p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED;
        HUB_DRIVER_EXIT_CRITICAL();
        break;
new_dev_err:
        // We allow this to fail in case a disconnect/port error happens while disabling.
        hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
reset_err:
        break;
    }
    case HCD_PORT_EVENT_DISCONNECTION:
    case HCD_PORT_EVENT_ERROR:
    case HCD_PORT_EVENT_OVERCURRENT: {
        bool port_has_device = false;
        HUB_DRIVER_ENTER_CRITICAL();
        switch (p_hub_driver_obj->dynamic.root_port_state) {
        case ROOT_PORT_STATE_POWERED: // This occurred before enumeration
        case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled
            // Therefore, there's no device object to clean up, and we can go straight to port recovery
            p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
            p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
            break;
        case ROOT_PORT_STATE_NOT_POWERED: // The user turned off ports' power. Indicate to USBH that the device is gone
        case ROOT_PORT_STATE_ENABLED: // There is an enabled (active) device. Indicate to USBH that the device is gone
            port_has_device = true;
            break;
        default:
            abort();    // Should never occur
            break;
        }
        HUB_DRIVER_EXIT_CRITICAL();

        if (port_has_device) {
            ESP_ERROR_CHECK(dev_tree_node_dev_gone(NULL, 0));
        }

        break;
    }
    default:
        abort();    // Should never occur
        break;
    }
}

static void root_port_req(hcd_port_handle_t root_port_hdl)
{
    unsigned int port_reqs;

    HUB_DRIVER_ENTER_CRITICAL();
    port_reqs = p_hub_driver_obj->dynamic.port_reqs;
    p_hub_driver_obj->dynamic.port_reqs = 0;
    HUB_DRIVER_EXIT_CRITICAL();

    if (port_reqs & PORT_REQ_DISABLE) {
        ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port");
        // We allow this to fail in case a disconnect/port error happens while disabling.
        hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
    }
    if (port_reqs & PORT_REQ_RECOVER) {
        ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port");
        ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl));

        // In case the port's power was turned off with usb_host_lib_set_root_port_power(false)
        // we will not turn on the power during port recovery
        HUB_DRIVER_ENTER_CRITICAL();
        const root_port_state_t root_state = p_hub_driver_obj->dynamic.root_port_state;
        HUB_DRIVER_EXIT_CRITICAL();

        if (root_state != ROOT_PORT_STATE_NOT_POWERED) {
            ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON));
            HUB_DRIVER_ENTER_CRITICAL();
            p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
            HUB_DRIVER_EXIT_CRITICAL();
        }
    }
}

static esp_err_t root_port_recycle(void)
{
    // Device is free, we can now request its port be recycled
    hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
    HUB_DRIVER_ENTER_CRITICAL();
    // How the port is recycled will depend on the port's state
    switch (port_state) {
    case HCD_PORT_STATE_ENABLED:
        p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE;
        break;
    case HCD_PORT_STATE_RECOVERY:
        p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
        break;
    default:
        abort();    // Should never occur
        break;
    }
    p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
    HUB_DRIVER_EXIT_CRITICAL();

    p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);

    return ESP_OK;
}

static esp_err_t root_port_disable(void)
{
    hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
    HUB_DRIVER_CHECK(port_state == HCD_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE);

    HUB_DRIVER_ENTER_CRITICAL();
    p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE;
    p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
    HUB_DRIVER_EXIT_CRITICAL();

    p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
    return ESP_OK;
}

// ---------------------------------------------- Hub Driver Functions -------------------------------------------------

esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();
    esp_err_t ret;

    // Allocate Hub driver object
    hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT);
    if (hub_driver_obj == NULL) {
        return ESP_ERR_NO_MEM;
    }

#if ENABLE_USB_HUBS
    // Install External Port driver
    ext_port_driver_config_t ext_port_config = {
        .proc_req_cb = ext_port_callback,
        .event_cb = ext_port_event_callback,
        .hub_request_cb = ext_hub_request,
    };
    ret = ext_port_install(&ext_port_config);
    if (ret != ESP_OK) {
        goto err_ext_port;
    }

    // Install External HUB driver
    ext_hub_config_t ext_hub_config = {
        .proc_req_cb = ext_hub_callback,
        .port_driver = ext_port_get_driver(),
    };
    ret = ext_hub_install(&ext_hub_config);
    if (ret != ESP_OK) {
        ESP_LOGE(HUB_DRIVER_TAG, "Ext hub install error: %s", esp_err_to_name(ret));
        goto err_ext_hub;
    }
    *client_ret = ext_hub_get_client();
#else
    *client_ret = NULL;
#endif // ENABLE_USB_HUBS

    // Install HCD port
    hcd_port_config_t port_config = {
        .callback = root_port_callback,
        .callback_arg = NULL,
        .context = NULL,
    };
    hcd_port_handle_t root_port_hdl;

    // Right now we support only one root port, can be extended in future
    int root_port_index = 0;
    if (hub_config->port_map & BIT1) {
        root_port_index = 1;
    }

    ret = hcd_port_init(root_port_index, &port_config, &root_port_hdl);
    if (ret != ESP_OK) {
        ESP_LOGE(HUB_DRIVER_TAG, "HCD Port init error: %s", esp_err_to_name(ret));
        goto err;
    }

    // Initialize Hub driver object
    hub_driver_obj->constant.root_port_hdl = root_port_hdl;
    hub_driver_obj->constant.proc_req_cb = hub_config->proc_req_cb;
    hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg;
    hub_driver_obj->constant.event_cb = hub_config->event_cb;
    hub_driver_obj->constant.event_cb_arg = hub_config->event_cb_arg;
    TAILQ_INIT(&hub_driver_obj->single_thread.dev_nodes_tailq);
    // Driver is not installed, we can modify dynamic section outside of the critical section
    hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED;

    HUB_DRIVER_ENTER_CRITICAL();
    if (p_hub_driver_obj != NULL) {
        HUB_DRIVER_EXIT_CRITICAL();
        ret = ESP_ERR_INVALID_STATE;
        goto assign_err;
    }
    p_hub_driver_obj = hub_driver_obj;
    HUB_DRIVER_EXIT_CRITICAL();

    return ret;

assign_err:
    ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl));
err:
#if ENABLE_USB_HUBS
    ext_hub_uninstall();
err_ext_hub:
    ext_port_uninstall();
err_ext_port:
#endif // ENABLE_USB_HUBS
    heap_caps_free(hub_driver_obj);
    return ret;
}

esp_err_t hub_uninstall(void)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE);
    hub_driver_t *hub_driver_obj = p_hub_driver_obj;
    p_hub_driver_obj = NULL;
    HUB_DRIVER_EXIT_CRITICAL();

#if ENABLE_USB_HUBS
    ESP_ERROR_CHECK(ext_hub_uninstall());
    ESP_ERROR_CHECK(ext_port_uninstall());
#endif // ENABLE_USB_HUBS

    ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl));
    // Free Hub driver resources
    heap_caps_free(hub_driver_obj);
    return ESP_OK;
}

esp_err_t hub_root_start(void)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();
    // Power ON the root port
    esp_err_t ret;
    ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON);
    if (ret == ESP_OK) {
        HUB_DRIVER_ENTER_CRITICAL();
        p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
        HUB_DRIVER_EXIT_CRITICAL();
    }
    return ret;
}

esp_err_t hub_root_stop(void)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED) {
        // The HUB was already stopped by usb_host_lib_set_root_port_power(false)
        HUB_DRIVER_EXIT_CRITICAL();
        return ESP_OK;
    }
    p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED;
    HUB_DRIVER_EXIT_CRITICAL();

    // HCD_PORT_CMD_POWER_OFF will only fail if the port is already powered_off
    // This should never happen, so we assert ret == ESP_OK
    const esp_err_t ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_OFF);
    assert(ret == ESP_OK);
    return ret;
}

esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();
    esp_err_t ret;

    if (parent_port_num == 0) {
        ret = root_port_recycle();
    } else {
#if ENABLE_USB_HUBS
        ext_hub_handle_t ext_hub_hdl = NULL;
        ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
        ret = ext_hub_port_recycle(ext_hub_hdl, parent_port_num);
        if (ret != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port recycle error: %s", esp_err_to_name(ret));
        }
#else
        ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port is not available (External Hub support disabled)");
        ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    }

    if (ret == ESP_OK) {
        ESP_ERROR_CHECK(dev_tree_node_remove_by_parent(parent_dev_hdl, parent_port_num));
    }

    return ret;
}

esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();
    esp_err_t ret;

    if (parent_port_num == 0) {
        ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET);
        if (ret != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset");
        } else {
            ret = dev_tree_node_reset_completed(NULL, 0);
        }
    } else {
#if ENABLE_USB_HUBS
        ext_hub_handle_t ext_hub_hdl = NULL;
        ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
        ret = ext_hub_port_reset(ext_hub_hdl, parent_port_num);
        if (ret != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port reset error: %s", esp_err_to_name(ret));
        }
#else
        ESP_LOGW(HUB_DRIVER_TAG, "Resetting External Port is not available (External Hub support disabled)");
        ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    }
    return ret;
}

esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    esp_err_t ret;

    if (parent_port_num == 0) {
        // Root port no need to be activated
        ret = ESP_OK;
    } else {
#if ENABLE_USB_HUBS
        // External Hub port
        ext_hub_handle_t ext_hub_hdl = NULL;
        ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
        ret = ext_hub_port_active(ext_hub_hdl, parent_port_num);
        if (ret != ESP_OK) {
            ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port activation error: %s", esp_err_to_name(ret));
        }
#else
        ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)");
        ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    }
    return ret;
}

esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
{
    esp_err_t ret;

    if (parent_port_num == 0) {
        ret = root_port_disable();
    } else {
#if ENABLE_USB_HUBS
        // External Hub port
        ext_hub_handle_t ext_hub_hdl = NULL;
        ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
        ret = ext_hub_port_disable(ext_hub_hdl, parent_port_num);
#else
        ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)");
        ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    }
    return ret;
}

esp_err_t hub_dev_new(uint8_t dev_addr)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();

    esp_err_t ret;
#if ENABLE_USB_HUBS
    ret = ext_hub_new_dev(dev_addr);
#else
    // Verify the device descriptor and if the bDeviceClass is a Hub class,
    // show the warning message, that Hub support feature is not enabled
    usb_device_handle_t dev_hdl = NULL;
    const usb_device_desc_t *device_desc = NULL;
    // Open device
    if (usbh_devs_open(dev_addr, &dev_hdl) == ESP_OK) {
        // Get Device Descriptor
        if (usbh_dev_get_desc(dev_hdl, &device_desc) == ESP_OK) {
            if (device_desc->bDeviceClass == USB_CLASS_HUB) {
                ESP_LOGW(HUB_DRIVER_TAG, "External Hubs support disabled, Hub device was not initialized");
            }
        }
        // Close device
        ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
    }
    // Nothing to do, while Hubs support is not enabled
    ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    return ret;
}

esp_err_t hub_dev_gone(uint8_t dev_addr)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();

    esp_err_t ret;
#if ENABLE_USB_HUBS
    ret = ext_hub_dev_gone(dev_addr);
#else
    // Nothing to do, while Hubs support is not enabled
    ret = ESP_ERR_NOT_SUPPORTED;
#endif // ENABLE_USB_HUBS
    return ret;
}

#if (ENABLE_USB_HUBS)
esp_err_t hub_notify_all_free(void)
{
    HUB_DRIVER_ENTER_CRITICAL();
    HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
    HUB_DRIVER_EXIT_CRITICAL();

    ext_hub_mark_all_free();

    return ESP_OK;
}
#endif // ENABLE_USB_HUBS

esp_err_t hub_process(void)
{
    HUB_DRIVER_ENTER_CRITICAL();
    uint32_t action_flags = p_hub_driver_obj->dynamic.flags.actions;
    p_hub_driver_obj->dynamic.flags.actions = 0;
    HUB_DRIVER_EXIT_CRITICAL();

    while (action_flags) {
#if ENABLE_USB_HUBS
        if (action_flags & HUB_DRIVER_ACTION_EXT_PORT) {
            ESP_ERROR_CHECK(ext_port_process());
        }
        if (action_flags & HUB_DRIVER_ACTION_EXT_HUB) {
            ESP_ERROR_CHECK(ext_hub_process());
        }
#endif // ENABLE_USB_HUBS
        if (action_flags & HUB_DRIVER_ACTION_ROOT_EVENT) {
            root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl);
        }
        if (action_flags & HUB_DRIVER_ACTION_ROOT_REQ) {
            root_port_req(p_hub_driver_obj->constant.root_port_hdl);
        }

        HUB_DRIVER_ENTER_CRITICAL();
        action_flags = p_hub_driver_obj->dynamic.flags.actions;
        p_hub_driver_obj->dynamic.flags.actions = 0;
        HUB_DRIVER_EXIT_CRITICAL();
    }

    return ESP_OK;
}
