diff --git a/components/usb/hub.c b/components/usb/hub.c index 3cc456546b..52aa84e00a 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -50,9 +50,8 @@ implement the bare minimum to control the root HCD port. // Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive #define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 -#define HUB_DRIVER_FLAG_ACTION_PORT_DISABLE 0x02 -#define HUB_DRIVER_FLAG_ACTION_PORT_RECOVER 0x04 -#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x08 +#define HUB_DRIVER_FLAG_ACTION_PORT 0x02 +#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04 /** * @brief Root port states @@ -255,18 +254,6 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port */ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); -/** - * @brief USBH Hub driver request callback - * - * - This callback is called from the context of the USBH, so so any event handling should be deferred to hub_process() - * - 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 hub_req Hub driver request - * @param arg Callback argument - */ -static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); - // ------------------------------------------------- Enum Functions ---------------------------------------------------- static bool enum_stage_start(enum_ctrl_t *enum_ctrl) @@ -693,7 +680,7 @@ static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) HUB_DRIVER_ENTER_CRITICAL(); // Enum could have failed due to a port error. If so, we need to trigger a port recovery if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) { - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; } else { // Otherwise, we move to the enum failed state and wait for the device to disconnect p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED; @@ -809,7 +796,7 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_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);; + 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 bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) @@ -821,29 +808,6 @@ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t 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 usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg) -{ - // We currently only support the root port, so the port_hdl should match the root port - assert(port_hdl == p_hub_driver_obj->constant.root_port_hdl); - - HUB_DRIVER_ENTER_CRITICAL(); - switch (hub_req) { - case USBH_HUB_REQ_PORT_DISABLE: - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_DISABLE; - break; - case USBH_HUB_REQ_PORT_RECOVER: - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; - break; - default: - // Should never occur - abort(); - break; - } - 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); -} - // ---------------------- Handlers ------------------------- static void root_port_handle_events(hcd_port_handle_t root_port_hdl) @@ -876,10 +840,10 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) case ROOT_PORT_STATE_POWERED: // This occurred before enumeration case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration. // Therefore, there's no device and we can go straight to port recovery - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; break; case ROOT_PORT_STATE_ENUM: - // This occurred during enumeration. Therefore, we need to recover the failed enumeration + // This occurred during enumeration. Therefore, we need to cleanup the failed enumeration p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED; break; @@ -894,8 +858,7 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); if (pass_event_to_usbh) { - assert(p_hub_driver_obj->single_thread.root_dev_hdl); - ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_ERROR)); + ESP_ERROR_CHECK(usbh_hub_dev_gone(p_hub_driver_obj->single_thread.root_dev_hdl)); } break; } @@ -1028,9 +991,8 @@ esp_err_t hub_install(hub_config_t *hub_config) } p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); - // Indicate to USBH that the hub is installed - ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_req_callback, NULL)); ret = ESP_OK; + return ret; assign_err: @@ -1090,6 +1052,20 @@ esp_err_t hub_root_stop(void) return ret; } +esp_err_t hub_dev_is_free(uint8_t dev_addr) +{ + assert(dev_addr == ENUM_DEV_ADDR); + assert(p_hub_driver_obj->single_thread.root_dev_hdl); + p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + // Device is free, we can now request its port be recycled + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_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); + return ESP_OK; +} + esp_err_t hub_process(void) { HUB_DRIVER_ENTER_CRITICAL(); @@ -1098,33 +1074,33 @@ esp_err_t hub_process(void) HUB_DRIVER_EXIT_CRITICAL(); while (action_flags) { - /* - Mutually exclude Root event and Port disable: - If a device was waiting for its port to be disabled, and a port error occurs in that time, the root event - handler will send a USBH_HUB_EVENT_PORT_ERROR to the USBH already, thus freeing the device and canceling the - waiting of port disable. - */ if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); - } else if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_DISABLE) { - ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); - hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); - ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_DISABLED)); - // The root port has been disabled, so the root_dev_hdl is no longer valid - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; } - - if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_RECOVER) { - ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); - ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); - 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(); - // USBH requesting a port recovery means the device has already been freed. Clear root_dev_hdl - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT) { + // Check current state of port + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + switch (port_state) { + case HCD_PORT_STATE_ENABLED: + // Port is still enabled with a connect device. Disable it. + 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); + break; + case HCD_PORT_STATE_RECOVERY: + // Port is in recovery after a disconnect/error. Recover it. + ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); + ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); + 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(); + break; + default: + abort(); // Should never occur + break; + } } - if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { enum_handle_events(); } diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index dac89b7ea7..a096de8fd9 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -77,6 +77,17 @@ esp_err_t hub_root_start(void); */ esp_err_t hub_root_stop(void); +/** + * @brief Indicate to the Hub driver that a device has been freed + * + * Hub driver can now recover the port that the device was connected to + * + * @param dev_addr Device address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_dev_is_free(uint8_t dev_addr); + /** * @brief Hub driver's processing function * diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 1d683809bb..75be2b1f66 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -36,6 +36,7 @@ typedef enum { USBH_EVENT_CTRL_XFER, /**< A control transfer has completed */ USBH_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the device pool */ USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ + USBH_EVENT_DEV_FREE, /**< A device has been freed. Its upstream port can now be recycled */ USBH_EVENT_ALL_FREE, /**< All devices have been freed */ } usbh_event_t; @@ -56,6 +57,9 @@ typedef struct { uint8_t dev_addr; usb_device_handle_t dev_hdl; } dev_gone_data; + struct { + uint8_t dev_addr; + } dev_free_data; }; } usbh_event_data_t; @@ -73,43 +77,8 @@ typedef enum { USBH_EP_EVENT_ERROR_STALL, /**< EP received a STALL response */ } usbh_ep_event_t; -/** - * @brief Hub driver events for the USBH - * - * These events as passed by the Hub driver to the USBH via usbh_hub_pass_event() - * - * USBH_HUB_EVENT_PORT_ERROR: - * - The port has encountered an error (such as a sudden disconnection). The device connected to that port is no longer valid. - * - The USBH should: - * - Trigger a USBH_EVENT_DEV_GONE - * - Prevent further transfers to the device - * - Trigger the device's cleanup if it is already closed - * - When the last client closes the device via usbh_dev_close(), free the device object and issue a USBH_HUB_REQ_PORT_RECOVER request - * - * USBH_HUB_EVENT_PORT_DISABLED: - * - A previous USBH_HUB_REQ_PORT_DISABLE has completed. - * - The USBH should free the device object - */ -typedef enum { - USBH_HUB_EVENT_PORT_ERROR, /**< The port has encountered an error (such as a sudden disconnection). The device - connected to that port should be marked gone. */ - USBH_HUB_EVENT_PORT_DISABLED, /**< Previous USBH_HUB_REQ_PORT_DISABLE request completed */ -} usbh_hub_event_t; - // ------------------ Requests/Commands -------------------- -/** - * @brief Hub driver requests - * - * Various requests of the Hub driver that the USBH can make. - */ -typedef enum { - USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device - has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ - USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone - device has been freed). */ -} usbh_hub_req_t; - /** * @brief Endpoint commands * @@ -130,14 +99,6 @@ typedef enum { */ typedef void (*usbh_event_cb_t)(usbh_event_data_t *event_data, void *arg); -/** - * @brief Callback used by the USBH to request actions from the Hub driver - * - * The Hub Request Callback allows the USBH to request the Hub actions on a particular port. Conversely, the Hub driver - * will indicate completion of some of these requests to the USBH via the usbh_hub_event() funtion. - */ -typedef void (*usbh_hub_req_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); - /** * @brief Callback used to indicate an event on an endpoint * @@ -422,19 +383,6 @@ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); // ------------------- Device Related ---------------------- -/** - * @brief Indicates to USBH that the Hub driver is installed - * - * - The Hub driver must call this function in its installation to indicate the the USBH that it has been installed. - * - This should only be called after the USBH has already be installed - * - * @note Hub Driver only - * @param[in] hub_req_callback Hub request callback - * @param[in] callback_arg Callback argument - * @return esp_err_t - */ -esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg); - /** * @brief Indicates to USBH the start of enumeration for a device * @@ -453,13 +401,12 @@ esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callba esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); /** - * @brief Indicates to the USBH that a hub event has occurred for a particular device + * @brief Indicates to the USBH that a device is gone * * @param dev_hdl Device handle - * @param hub_event Hub event * @return esp_err_t */ -esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event); +esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl); // ----------------- Enumeration Related ------------------- diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 79a1952404..11fd7810aa 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -296,6 +296,11 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) send_event_msg_to_clients(&event_msg, false, event_data->dev_gone_data.dev_addr); break; } + case USBH_EVENT_DEV_FREE: { + // Let the Hub driver know that the device is free + ESP_ERROR_CHECK(hub_dev_is_free(event_data->dev_free_data.dev_addr)); + break; + } case USBH_EVENT_ALL_FREE: { // Notify the lib handler that all devices are free HOST_ENTER_CRITICAL(); diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 456b3dc4e5..a50f4fc944 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -32,10 +32,8 @@ typedef enum { DEV_ACTION_EP0_DEQUEUE = (1 << 2), // Dequeue all URBs from EP0 DEV_ACTION_EP0_CLEAR = (1 << 3), // Move EP0 to the the active state DEV_ACTION_PROP_GONE_EVT = (1 << 4), // Propagate a USBH_EVENT_DEV_GONE event - DEV_ACTION_FREE_AND_RECOVER = (1 << 5), // Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. - DEV_ACTION_FREE = (1 << 6), // Free the device object - DEV_ACTION_PORT_DISABLE = (1 << 7), // Request the hub driver to disable the port of the device - DEV_ACTION_PROP_NEW = (1 << 8), // Propagate a USBH_EVENT_NEW_DEV event + DEV_ACTION_FREE = (1 << 5), // Free the device object + DEV_ACTION_PROP_NEW_DEV = (1 << 6), // Propagate a USBH_EVENT_NEW_DEV event } dev_action_t; typedef struct device_s device_t; @@ -57,11 +55,9 @@ struct device_s { union { struct { uint32_t in_pending_list: 1; - uint32_t is_gone: 1; - uint32_t waiting_close: 1; - uint32_t waiting_port_disable: 1; - uint32_t waiting_free: 1; - uint32_t reserved27: 27; + uint32_t is_gone: 1; // Device is gone (disconnected or port error) + uint32_t waiting_free: 1; // Device object is awaiting to be freed + uint32_t reserved29: 29; }; uint32_t val; } flags; @@ -106,8 +102,6 @@ typedef struct { struct { usb_proc_req_cb_t proc_req_cb; void *proc_req_cb_arg; - usbh_hub_req_cb_t hub_req_cb; - void *hub_req_cb_arg; usbh_event_cb_t event_cb; void *event_cb_arg; SemaphoreHandle_t mux_lock; @@ -515,11 +509,11 @@ static inline void handle_prop_gone_evt(device_t *dev_obj) p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } -static void handle_free_and_recover(device_t *dev_obj, bool recover_port) +static inline void handle_free(device_t *dev_obj) { - // Cache a copy of the port handle as we are about to free the device object + // Cache a copy of the device's address as we are about to free the device object + const uint8_t dev_addr = dev_obj->constant.address; bool all_free; - hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); // We need to take the mux_lock to access mux_protected members @@ -538,28 +532,24 @@ static void handle_free_and_recover(device_t *dev_obj, bool recover_port) xSemaphoreGive(p_usbh_obj->constant.mux_lock); device_free(dev_obj); + // Propagate USBH_EVENT_DEV_FREE event + usbh_event_data_t event_data = { + .event = USBH_EVENT_DEV_FREE, + .dev_free_data = { + .dev_addr = dev_addr, + } + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); + // If all devices have been freed, propagate a USBH_EVENT_ALL_FREE event if (all_free) { ESP_LOGD(USBH_TAG, "Device all free"); - usbh_event_data_t event_data = { - .event = USBH_EVENT_ALL_FREE, - }; + event_data.event = USBH_EVENT_ALL_FREE; p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } - // Check if we need to recover the device's port - if (recover_port) { - p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg); - } } -static inline void handle_port_disable(device_t *dev_obj) -{ - // Request that the HUB disables this device's port - ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); - p_usbh_obj->constant.hub_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg); -} - -static inline void handle_prop_new_evt(device_t *dev_obj) +static inline void handle_prop_new_dev(device_t *dev_obj) { ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); usbh_event_data_t event_data = { @@ -694,16 +684,11 @@ esp_err_t usbh_process(void) in the order of precedence For example - New device event is requested followed immediately by a disconnection - - Port disable requested followed immediately by a disconnection */ - if (action_flags & DEV_ACTION_FREE_AND_RECOVER) { - handle_free_and_recover(dev_obj, true); - } else if (action_flags & DEV_ACTION_FREE) { - handle_free_and_recover(dev_obj, false); - } else if (action_flags & DEV_ACTION_PORT_DISABLE) { - handle_port_disable(dev_obj); - } else if (action_flags & DEV_ACTION_PROP_NEW) { - handle_prop_new_evt(dev_obj); + if (action_flags & DEV_ACTION_FREE) { + handle_free(dev_obj); + } else if (action_flags & DEV_ACTION_PROP_NEW_DEV) { + handle_prop_new_dev(dev_obj); } USBH_ENTER_CRITICAL(); /* --------------------------------------------------------------------- @@ -782,7 +767,7 @@ esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) exit: if (found_dev_obj != NULL) { // The device is not in a state to be referenced - if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_port_disable || dev_obj->dynamic.flags.waiting_free) { + if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { ret = ESP_ERR_INVALID_STATE; } else { dev_obj->dynamic.ref_count++; @@ -809,14 +794,9 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) // Sanity check. assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); // There cannot be any control transfer in-flight assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when ref count reaches 0 - if (dev_obj->dynamic.flags.is_gone) { - // Device is already gone so it's port is already disabled. Trigger the USBH process to free the device - dev_obj->dynamic.flags.waiting_free = 1; - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it - } else if (dev_obj->dynamic.flags.waiting_close) { - // Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled - dev_obj->dynamic.flags.waiting_port_disable = 1; - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PORT_DISABLE); + if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { + // Device is already gone or is awaiting to be freed. Trigger the USBH process to free the device + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); } // Else, there's nothing to do. Leave the device allocated } @@ -848,18 +828,17 @@ esp_err_t usbh_dev_mark_all_free(void) dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq); } while (dev_obj_cur != NULL) { - assert(!dev_obj_cur->dynamic.flags.waiting_close); // Sanity check // Keep a copy of the next item first in case we remove the current item dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); - if (dev_obj_cur->dynamic.ref_count == 0 && !dev_obj_cur->dynamic.flags.is_gone) { - // Device is not opened as is not gone, so we can disable it now - dev_obj_cur->dynamic.flags.waiting_port_disable = 1; - call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_PORT_DISABLE); + if (dev_obj_cur->dynamic.ref_count == 0) { + // Device is not referenced. Can free immediately. + call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_FREE); } else { - // Device is still opened. Just mark it as waiting to be closed - dev_obj_cur->dynamic.flags.waiting_close = 1; + // Device is still referenced. Just mark it as waiting to be freed + dev_obj_cur->dynamic.flags.waiting_free = 1; } - wait_for_free = true; // As long as there is still a device, we need to wait for an event indicating it is freed + // At least one device needs to be freed. User needs to wait for USBH_EVENT_ALL_FREE event + wait_for_free = true; dev_obj_cur = dev_obj_next; } } @@ -1145,22 +1124,6 @@ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) // ------------------- Device Related ---------------------- -esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg) -{ - USBH_CHECK(hub_req_callback != NULL, ESP_ERR_INVALID_ARG); - - USBH_ENTER_CRITICAL(); - // Check that USBH is already installed - USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); - // Check that Hub has not be installed yet - USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_req_cb == NULL, ESP_ERR_INVALID_STATE); - p_usbh_obj->constant.hub_req_cb = hub_req_callback; - p_usbh_obj->constant.hub_req_cb_arg = callback_arg; - USBH_EXIT_CRITICAL(); - - return ESP_OK; -} - esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl) { // Note: Parent device handle can be NULL if it's connected to the root hub @@ -1178,42 +1141,27 @@ esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, us return ret; } -esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event) +esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - bool call_proc_req_cb; - switch (hub_event) { - case USBH_HUB_EVENT_PORT_ERROR: { - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.flags.is_gone = 1; - // Check if the device can be freed now - if (dev_obj->dynamic.ref_count == 0) { - dev_obj->dynamic.flags.waiting_free = 1; - // Device is already waiting free so none of it's EP's will be in use. Can free immediately. - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it - } else { - call_proc_req_cb = _dev_set_actions(dev_obj, - DEV_ACTION_EPn_HALT_FLUSH | - DEV_ACTION_EP0_FLUSH | - DEV_ACTION_EP0_DEQUEUE | - DEV_ACTION_PROP_GONE_EVT); - } - USBH_EXIT_CRITICAL(); - break; - } - case USBH_HUB_EVENT_PORT_DISABLED: { - USBH_ENTER_CRITICAL(); - assert(dev_obj->dynamic.ref_count == 0); // At this stage, the device should have been closed by all users - dev_obj->dynamic.flags.waiting_free = 1; + + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.flags.is_gone = 1; + // Check if the device can be freed immediately + if (dev_obj->dynamic.ref_count == 0) { + // Device is not currently referenced at all. Can free immediately. call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); - USBH_EXIT_CRITICAL(); - break; - } - default: - return ESP_ERR_INVALID_ARG; + } else { + // Device is still being referenced. Flush endpoints and propagate device gone event + call_proc_req_cb = _dev_set_actions(dev_obj, + DEV_ACTION_EPn_HALT_FLUSH | + DEV_ACTION_EP0_FLUSH | + DEV_ACTION_EP0_DEQUEUE | + DEV_ACTION_PROP_GONE_EVT); } + USBH_EXIT_CRITICAL(); if (call_proc_req_cb) { p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); @@ -1303,7 +1251,7 @@ esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; // Add the device to list of devices, then trigger a device event TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); // Add it to the idle device list first - bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW); + bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); USBH_EXIT_CRITICAL(); p_usbh_obj->mux_protected.num_device++; xSemaphoreGive(p_usbh_obj->constant.mux_lock);