feat(hub): Added device tree nodes list and uid calculation logic

This commit is contained in:
Roman Leonov
2024-06-12 15:30:27 +08:00
parent 9eb4ea32fe
commit 71708508d6
2 changed files with 203 additions and 60 deletions

View File

@@ -8,6 +8,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "esp_err.h" #include "esp_err.h"
@@ -43,7 +44,6 @@ implement the bare minimum to control the root HCD port.
/** /**
* @brief Root port states * @brief Root port states
*
*/ */
typedef enum { typedef enum {
ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */ ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */
@@ -53,31 +53,46 @@ typedef enum {
ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */ ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */
} root_port_state_t; } 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 { typedef struct {
// Dynamic members require a critical section
struct { struct {
union { union {
struct { struct {
uint32_t actions: 8; uint32_t actions: 8;
uint32_t reserved24: 24; uint32_t reserved24: 24;
}; };
uint32_t val; uint32_t val; /**< Root port flag value */
} flags; } flags; /**< Root port flags */
root_port_state_t root_port_state; root_port_state_t root_port_state; /**< Root port state */
unsigned int port_reqs; unsigned int port_reqs; /**< Root port request flag */
} dynamic; } dynamic; /**< Dynamic members. Require a critical section */
// Single thread members don't require a critical section so long as they are never accessed from multiple threads
struct { struct {
unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected TAILQ_HEAD(tailhead_devs, dev_tree_node_s) dev_nodes_tailq; /**< Tailq of attached devices */
} single_thread; uint8_t next_uid; /**< Unique ID for next upcoming device */
// Constant members do no change after installation thus do not require a critical section } single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
struct { struct {
hcd_port_handle_t root_port_hdl; hcd_port_handle_t root_port_hdl; /**< Root port HCD handle */
usb_proc_req_cb_t proc_req_cb; usb_proc_req_cb_t proc_req_cb; /**< Process request callback */
void *proc_req_cb_arg; void *proc_req_cb_arg; /**< Process request callback argument */
hub_event_cb_t event_cb; hub_event_cb_t event_cb; /**< Hub Driver event callback */
void *event_cb_arg; void *event_cb_arg; /**< Event callback argument */
} constant; } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} hub_driver_t; } hub_driver_t;
static hub_driver_t *p_hub_driver_obj = NULL; static hub_driver_t *p_hub_driver_obj = NULL;
@@ -121,6 +136,158 @@ const char *HUB_DRIVER_TAG = "HUB";
*/ */
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); 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 new_dev_tree_node(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed)
{
esp_err_t ret = ESP_FAIL;
unsigned int node_uid = p_hub_driver_obj->single_thread.next_uid;
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;
}
// Allocate a new USBH device
usbh_dev_params_t params = {
.uid = 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 responsibility of parent-child tree building to Hub Driver instead of USBH
.parent_dev_hdl = parent_dev_hdl,
.parent_port_num = parent_port_num,
};
ret = usbh_devs_add(&params);
if (ret != ESP_OK) {
goto fail;
}
dev_tree_node->uid = node_uid;
dev_tree_node->parent_dev_hdl = parent_dev_hdl;
dev_tree_node->parent_port_num = parent_port_num;
TAILQ_INSERT_TAIL(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry);
p_hub_driver_obj->single_thread.next_uid++;
if (p_hub_driver_obj->single_thread.next_uid == 0) {
ESP_LOGW(HUB_DRIVER_TAG, "Counter overflowed, possibility of uid collisions");
p_hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID;
}
// Verify presence of a device with newly prepared uid in USBH
// TODO: IDF-10022 Provide a mechanism to request presence status of a device with uid in USBH device object list
// Return if device uid is not in USBH device object list, repeat until uid will be founded
ESP_LOGD(HUB_DRIVER_TAG, "New device tree node (uid=%d)", node_uid);
hub_event_data_t event_data = {
.event = HUB_EVENT_CONNECTED,
.connected = {
.uid = 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_LOGE(HUB_DRIVER_TAG, "Device tree node with 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_LOGE(HUB_DRIVER_TAG, "Device tree node with port=%d not found", parent_port_num);
return ESP_ERR_NOT_FOUND;
}
ESP_LOGD(HUB_DRIVER_TAG, "Device tree node freeing (uid=%d)", 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 ------------------------ // ---------------------- Callbacks ------------------------
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
@@ -133,12 +300,9 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port
} }
// ---------------------- Handlers ------------------------- // ---------------------- Handlers -------------------------
static void root_port_handle_events(hcd_port_handle_t root_port_hdl) 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); hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl);
hub_event_data_t event_data = { 0 };
switch (port_event) { switch (port_event) {
case HCD_PORT_EVENT_NONE: case HCD_PORT_EVENT_NONE:
// Nothing to do // Nothing to do
@@ -154,27 +318,15 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
goto new_dev_err; goto new_dev_err;
} }
// Allocate a new device. We use a fixed HUB_ROOT_DEV_UID for now since we only support a single device if (new_dev_tree_node(NULL, 0, speed) != ESP_OK) {
usbh_dev_params_t params = { ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new device");
.uid = HUB_ROOT_DEV_UID,
.speed = speed,
.root_port_hdl = p_hub_driver_obj->constant.root_port_hdl,
};
if (usbh_devs_add(&params) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device");
goto new_dev_err; goto new_dev_err;
} }
// Save uid to Port
p_hub_driver_obj->single_thread.root_dev_uid = HUB_ROOT_DEV_UID;
// Change Port state // Change Port state
HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED; p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED;
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
event_data.event = HUB_EVENT_CONNECTED;
event_data.connected.uid = p_hub_driver_obj->single_thread.root_dev_uid;
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
break; break;
new_dev_err: new_dev_err:
// We allow this to fail in case a disconnect/port error happens while disabling. // We allow this to fail in case a disconnect/port error happens while disabling.
@@ -203,14 +355,11 @@ reset_err:
break; break;
} }
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
if (port_has_device) {
// The port must have a device object
assert(p_hub_driver_obj->single_thread.root_dev_uid != 0);
event_data.event = HUB_EVENT_DISCONNECTED; if (port_has_device) {
event_data.disconnected.uid = p_hub_driver_obj->single_thread.root_dev_uid; ESP_ERROR_CHECK(dev_tree_node_dev_gone(NULL, 0));
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
} }
break; break;
} }
default: default:
@@ -263,6 +412,8 @@ static esp_err_t root_port_recycle(void)
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ;
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
ESP_ERROR_CHECK(dev_tree_node_remove_by_parent(NULL, 0));
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); 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; return ESP_OK;
@@ -304,9 +455,12 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; 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 = hub_config->event_cb;
hub_driver_obj->constant.event_cb_arg = hub_config->event_cb_arg; hub_driver_obj->constant.event_cb_arg = hub_config->event_cb_arg;
hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID;
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(); HUB_DRIVER_ENTER_CRITICAL();
hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED;
if (p_hub_driver_obj != NULL) { if (p_hub_driver_obj != NULL) {
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE; ret = ESP_ERR_INVALID_STATE;
@@ -381,11 +535,6 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po
esp_err_t ret = ESP_FAIL; esp_err_t ret = ESP_FAIL;
if (parent_port_num == 0) { if (parent_port_num == 0) {
if (p_hub_driver_obj->single_thread.root_dev_uid) {
// If root port has a device, it should be with correct uid
assert(dev_uid == p_hub_driver_obj->single_thread.root_dev_uid);
p_hub_driver_obj->single_thread.root_dev_uid = 0;
}
ret = root_port_recycle(); ret = root_port_recycle();
} else { } else {
ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port has not been implemented yet"); ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port has not been implemented yet");
@@ -404,21 +553,11 @@ esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port
esp_err_t ret = ESP_FAIL; esp_err_t ret = ESP_FAIL;
if (parent_port_num == 0) { if (parent_port_num == 0) {
// The port must have a device object
assert(p_hub_driver_obj->single_thread.root_dev_uid != 0);
ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET); ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset"); ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset");
} }
ret = dev_tree_node_reset_completed(NULL, 0);
hub_event_data_t event_data = {
.event = HUB_EVENT_RESET_COMPLETED,
.disconnected = {
.uid = p_hub_driver_obj->single_thread.root_dev_uid,
},
};
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
} else { } else {
ESP_LOGW(HUB_DRIVER_TAG, "Reset External Port has not been implemented yet"); ESP_LOGW(HUB_DRIVER_TAG, "Reset External Port has not been implemented yet");
return ESP_ERR_NOT_SUPPORTED; return ESP_ERR_NOT_SUPPORTED;

View File

@@ -96,6 +96,8 @@ esp_err_t hub_uninstall(void);
* *
* This will power the root port ON * This will power the root port ON
* *
* @note This function should only be called from the Host Library task
*
* @return esp_err_t * @return esp_err_t
*/ */
esp_err_t hub_root_start(void); esp_err_t hub_root_start(void);
@@ -112,8 +114,9 @@ esp_err_t hub_root_stop(void);
/** /**
* @brief Indicate to the Hub driver that a device's port can be recycled * @brief Indicate to the Hub driver that a device's port can be recycled
* *
* The device connected to the port has been freed. The Hub driver can now * The device connected to the port has been freed. The Hub driver can now recycled the port
* recycled the port. *
* @note This function should only be called from the Host Library task
* *
* @param[in] parent_dev_hdl * @param[in] parent_dev_hdl
* @param[in] parent_port_num * @param[in] parent_port_num
@@ -126,6 +129,7 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po
/** /**
* @brief Reset the port * @brief Reset the port
* *
* @note This function should only be called from the Host Library task
* *
* @param[in] parent_dev_hdl * @param[in] parent_dev_hdl
* @param[in] parent_port_num * @param[in] parent_port_num