From 284b978cb409f41af431e22909eb8a80c6511b8b Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 8 May 2024 19:52:09 +0800 Subject: [PATCH] refactor(hub): Updated HUB api for ENUM driver --- components/usb/hub.c | 144 ++++++++++++++++++++------- components/usb/private_include/hub.h | 54 +++++++++- components/usb/usb_host.c | 25 ++++- 3 files changed, 181 insertions(+), 42 deletions(-) diff --git a/components/usb/hub.c b/components/usb/hub.c index a90b1f15c3..4e74ebce8c 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -23,7 +23,9 @@ Implementation of the HUB driver that only supports the Root Hub with a single p implement the bare minimum to control the root HCD port. */ -#define HUB_ROOT_PORT_NUM 1 // HCD only supports one port +#define HUB_ROOT_PORT_NUM 1 // HCD only supports one port +#define HUB_ROOT_DEV_UID 1 // Unique device ID + #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 @@ -40,7 +42,6 @@ implement the bare minimum to control the root HCD port. #define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE #define ENUM_DEV_ADDR 1 // Device address used in enumeration -#define ENUM_DEV_UID 1 // Unique ID for device connected to root port #define ENUM_CONFIG_INDEX_DEFAULT 0 // Index used to get the first configuration descriptor of the device #define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) #define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device @@ -199,6 +200,8 @@ typedef struct { hcd_port_handle_t root_port_hdl; usb_proc_req_cb_t proc_req_cb; void *proc_req_cb_arg; + hub_event_cb_t event_cb; + void *event_cb_arg; } constant; } hub_driver_t; @@ -274,7 +277,8 @@ static bool enum_stage_start(enum_ctrl_t *enum_ctrl) static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl) { - if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { + // Hub Driver currently support only one root port, so the second reset always in root port + if (hub_port_reset(NULL, 0) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); return false; } @@ -646,7 +650,7 @@ static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); ESP_ERROR_CHECK(usbh_dev_close(enum_ctrl->dev_hdl)); // We allow this to fail in case the device object was already freed - usbh_devs_remove(ENUM_DEV_UID); + usbh_devs_remove(HUB_ROOT_DEV_UID); } // Clear values in enum_ctrl enum_ctrl->dev_hdl = NULL; @@ -777,6 +781,8 @@ static void enum_transfer_callback(usb_transfer_t *transfer) 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); + hub_event_data_t event_data = { 0 }; + switch (port_event) { case HCD_PORT_EVENT_NONE: // Nothing to do @@ -792,9 +798,9 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) goto new_dev_err; } - // Allocate a new device. We use a fixed ENUM_DEV_UID for now since we only support a single device + // Allocate a new device. We use a fixed HUB_ROOT_DEV_UID for now since we only support a single device usbh_dev_params_t params = { - .uid = ENUM_DEV_UID, + .uid = HUB_ROOT_DEV_UID, .speed = speed, .root_port_hdl = p_hub_driver_obj->constant.root_port_hdl, }; @@ -803,13 +809,18 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device"); goto new_dev_err; } - p_hub_driver_obj->single_thread.root_dev_uid = ENUM_DEV_UID; + p_hub_driver_obj->single_thread.root_dev_uid = HUB_ROOT_DEV_UID; + // Start enumeration HUB_DRIVER_ENTER_CRITICAL(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED; HUB_DRIVER_EXIT_CRITICAL(); p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; + + 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; new_dev_err: // We allow this to fail in case a disconnect/port error happens while disabling. @@ -820,7 +831,7 @@ reset_err: case HCD_PORT_EVENT_DISCONNECTION: case HCD_PORT_EVENT_ERROR: case HCD_PORT_EVENT_OVERCURRENT: { - bool pass_event_to_usbh = false; + 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 @@ -831,7 +842,7 @@ reset_err: break; case ROOT_PORT_STATE_ENABLED: // There is an enabled (active) device. We need to indicate to USBH that the device is gone - pass_event_to_usbh = true; + port_has_device = true; break; default: abort(); // Should never occur @@ -839,11 +850,13 @@ reset_err: } p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); - if (pass_event_to_usbh) { + if (port_has_device) { // The port must have a device object assert(p_hub_driver_obj->single_thread.root_dev_uid != 0); - // We allow this to fail in case the device object was already freed - usbh_devs_remove(p_hub_driver_obj->single_thread.root_dev_uid); + + event_data.event = HUB_EVENT_DISCONNECTED; + event_data.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); } break; } @@ -954,6 +967,32 @@ static void enum_handle_events(void) enum_set_next_stage(enum_ctrl, stage_pass); } +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); + p_hub_driver_obj->single_thread.root_dev_uid = 0; + 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_FLAG_ACTION_PORT_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) @@ -979,8 +1018,8 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) .callback_arg = NULL, .context = NULL, }; - hcd_port_handle_t port_hdl; - ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &port_hdl); + hcd_port_handle_t root_port_hdl; + ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &root_port_hdl); if (ret != ESP_OK) { goto err; } @@ -991,9 +1030,11 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) #ifdef ENABLE_ENUM_FILTER_CALLBACK hub_driver_obj->single_thread.enum_ctrl.enum_filter_cb = hub_config->enum_filter_cb; #endif // ENABLE_ENUM_FILTER_CALLBACK - hub_driver_obj->constant.root_port_hdl = port_hdl; + 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; HUB_DRIVER_ENTER_CRITICAL(); hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; @@ -1012,7 +1053,7 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) return ret; assign_err: - ESP_ERROR_CHECK(hcd_port_deinit(port_hdl)); + ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl)); err: urb_free(enum_urb); heap_caps_free(hub_driver_obj); @@ -1068,32 +1109,59 @@ esp_err_t hub_root_stop(void) return ret; } -esp_err_t hub_port_recycle(unsigned int dev_uid) +esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid) { - if (dev_uid == p_hub_driver_obj->single_thread.root_dev_uid) { - // 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); - p_hub_driver_obj->single_thread.root_dev_uid = 0; - 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_FLAG_ACTION_PORT_REQ; - HUB_DRIVER_EXIT_CRITICAL(); + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + 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); + esp_err_t ret = ESP_FAIL; + + 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(); + } else { + ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port has not been implemented yet"); + return ESP_ERR_NOT_SUPPORTED; } - return ESP_OK; + 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 = ESP_FAIL; + + 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); + if (ret != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset"); + } + + 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 { + ESP_LOGW(HUB_DRIVER_TAG, "Reset External Port has not been implemented yet"); + return ESP_ERR_NOT_SUPPORTED; + } + + return ret; } esp_err_t hub_process(void) diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index cf85ad861d..62649d0d2e 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -17,6 +17,39 @@ extern "C" { #endif +// ----------------------------- Events ---------------------------------------- +typedef enum { + HUB_EVENT_CONNECTED, /**< Device has been connected */ + HUB_EVENT_RESET_COMPLETED, /**< Device reset completed */ + HUB_EVENT_DISCONNECTED, /**< Device has been disconnected */ +} hub_event_t; + +typedef struct { + hub_event_t event; /**< HUB event ID */ + union { + struct { + unsigned int uid; /**< Unique device ID */ + } connected; /**< HUB_EVENT_DEV_CONNECTED specific data */ + + struct { + unsigned int uid; /**< Unique device ID */ + } reset_completed; /**< HUB_EVENT_RESET_COMPLETED specific data */ + + struct { + unsigned int uid; /**< Unique device ID */ + } disconnected; /**< HUB_EVENT_DEV_DISCONNECTED specific data */ + }; +} hub_event_data_t; + +// ---------------------- Callbacks ------------------------ + +/** + * @brief Callback used to indicate that the USBH has an event + * + * @note This callback is called from within usbh_process() + */ +typedef void (*hub_event_cb_t)(hub_event_data_t *event_data, void *arg); + // ------------------------------------------------------ Types -------------------------------------------------------- /** @@ -25,6 +58,8 @@ extern "C" { typedef struct { usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ void *proc_req_cb_arg; /**< Processing request callback argument */ + hub_event_cb_t event_cb; /**< Hub event callback */ + void *event_cb_arg; /**< Hub event callback argument */ #ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ #endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK @@ -84,16 +119,29 @@ esp_err_t hub_root_stop(void); * The device connected to the port has been freed. The Hub driver can now * recycled the port. * - * @param dev_uid Device's unique ID + * @param[in] parent_dev_hdl + * @param[in] parent_port_num + * @param[in] dev_uid Device's unique ID * @return * - ESP_OK: Success */ -esp_err_t hub_port_recycle(unsigned int dev_uid); +esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid); + +/** + * @brief Reset the port + * + * + * @param[in] parent_dev_hdl + * @param[in] parent_port_num + * @return + * - ESP_OK: Success + */ +esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); /** * @brief Hub driver's processing function * - * Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the + * Hub driver handling function that must be called repeatedly to process the Hub driver's events. If blocking, the * caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB to run this function. * * @return esp_err_t diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 47c312ccf4..8a9c1949ce 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -314,7 +314,9 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) } case USBH_EVENT_DEV_FREE: { // Let the Hub driver know that the device is free and its port can be recycled - ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.dev_uid)); + ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.parent_dev_hdl, + event_data->dev_free_data.port_num, + event_data->dev_free_data.dev_uid)); break; } case USBH_EVENT_ALL_FREE: { @@ -331,6 +333,25 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) } } +static void hub_event_callback(hub_event_data_t *event_data, void *arg) +{ + switch (event_data->event) { + case HUB_EVENT_CONNECTED: + // Nothing to do, because enumeration still holding in Hub Driver + break; + case HUB_EVENT_RESET_COMPLETED: + // Nothing to do, because enumeration still holding in Hub Driver + break; + case HUB_EVENT_DISCONNECTED: + // We allow this to fail in case the device object was already freed + usbh_devs_remove(event_data->disconnected.uid); + break; + default: + abort(); // Should never occur + break; + } +} + // ------------------- Client Related ---------------------- static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) @@ -438,6 +459,8 @@ esp_err_t usb_host_install(const usb_host_config_t *config) hub_config_t hub_config = { .proc_req_cb = proc_req_callback, .proc_req_cb_arg = NULL, + .event_cb = hub_event_callback, + .event_cb_arg = NULL, #ifdef ENABLE_ENUM_FILTER_CALLBACK .enum_filter_cb = config->enum_filter_cb, #endif // ENABLE_ENUM_FILTER_CALLBACK