From 99ec1c98f568de2a2b0feccaad6dc422e4f9b468 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 10 Apr 2024 05:06:46 +0800 Subject: [PATCH] refactor(usb/usbh): Update USBH device creation and enumeration handling This commit updates how the USBH handles device creation and enumeration so that upper layers (such as the Hub driver) can use the USBH API for enumeration instead of calling the HCD. USBH Updates: USBH now creates unenumerated devices set to address 0 with no device/config descriptor. A newly created device can be opened and communicated with immediately (using control transfers). This allows the Hub driver to call the USBH instead of the HCD. Summary of USBH changes: - Added new APIs to add/remove a device. Devices are now created as unenumerated and can be immediately opened and communicated with. - Added new APIs to enumerate a device (see 'usbh_dev_set_...()' functions). Device must be locked (see 'usbh_dev_enum_lock()') before enumeration functions can be called. - Added UID for each device. This allows the particular USBH without needing to use the device's handle (which implies opening the device). Hub Driver Updates: Hub driver now calls the USBH for enumeration. Summary of USBH changes: - Replace all 'hcd_pipe_...()' calls with 'usbh_dev_...()' calls - Refactored port event handling to fit with new USBH API - Updated to use UID to uniquely identify devices without opening them USB Host Updates: - Reroute USBH control transfers to clients and hub driver --- components/usb/hcd_dwc.c | 14 - components/usb/hub.c | 230 ++++---- components/usb/private_include/hcd.h | 16 - components/usb/private_include/hub.h | 12 +- components/usb/private_include/usbh.h | 245 +++++---- components/usb/usb_host.c | 38 +- components/usb/usbh.c | 737 +++++++++++++++++--------- 7 files changed, 769 insertions(+), 523 deletions(-) diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index bc0c24ddaf..ebfd2c8e79 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -1983,20 +1983,6 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) return ESP_OK; } -esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg) -{ - pipe_t *pipe = (pipe_t *)pipe_hdl; - HCD_ENTER_CRITICAL(); - // Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb, - ESP_ERR_INVALID_STATE); - pipe->callback = callback; - pipe->callback_arg = user_arg; - HCD_EXIT_CRITICAL(); - return ESP_OK; -} - void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl) { pipe_t *pipe = (pipe_t *)pipe_hdl; diff --git a/components/usb/hub.c b/components/usb/hub.c index 14b8f029c7..2735ef5a46 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -40,6 +40,7 @@ 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 @@ -63,9 +64,8 @@ implement the bare minimum to control the root HCD port. 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_ENUM, /**< A device has been connected to the root port and is undergoing enumeration */ - ROOT_PORT_STATE_ENUM_FAILED, /**< Enumeration of a connected device has failed. Waiting for that device to be disconnected */ - ROOT_PORT_ACTIVE, /**< The connected device was enumerated and port is active */ + 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_RECOVERY, /**< Root port encountered an error and needs to be recovered */ } root_port_state_t; @@ -159,7 +159,6 @@ typedef struct { urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */ // Initialized at start of a particular enumeration usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */ - hcd_pipe_handle_t pipe; /**< Default pipe handle of the device being enumerated */ // Updated during enumeration enum_stage_t stage; /**< Current enumeration stage */ int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */ @@ -192,7 +191,7 @@ typedef struct { } dynamic; // Single thread members don't require a critical section so long as they are never accessed from multiple threads struct { - usb_device_handle_t root_dev_hdl; // Indicates if an enumerated device is connected to the root port + unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected enum_ctrl_t enum_ctrl; } single_thread; // Constant members do no change after installation thus do not require a critical section @@ -245,40 +244,26 @@ 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); /** - * @brief HCD pipe callback for the default pipe of the device under enumeration + * @brief Control transfer callback used for enumeration * - * - This callback is called from the context of the HCD, 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 pipe_hdl HCD pipe handle - * @param pipe_event Pipe event - * @param user_arg Callback argument - * @param in_isr Whether callback is in an ISR context - * @return Whether a yield is required + * @param transfer Transfer object */ -static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); +static void enum_transfer_callback(usb_transfer_t *transfer); // ------------------------------------------------- Enum Functions ---------------------------------------------------- static bool enum_stage_start(enum_ctrl_t *enum_ctrl) { - // Get the speed of the device, and set the enum MPS to the worst case size for now - usb_speed_t speed; - if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { - return false; - } - enum_ctrl->bMaxPacketSize0 = (speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; - // Try to add the device to USBH - usb_device_handle_t enum_dev_hdl; - hcd_pipe_handle_t enum_dflt_pipe_hdl; - // We use NULL as the parent device to indicate the Root Hub port 1. We currently only support a single device - if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) { - return false; - } - // Set our own default pipe callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL)); - enum_ctrl->dev_hdl = enum_dev_hdl; - enum_ctrl->pipe = enum_dflt_pipe_hdl; + // Open the newly added device (at address 0) + ESP_ERROR_CHECK(usbh_devs_open(0, &p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); + + // Get the speed of the device to set the initial MPS of EP0 + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl, &dev_info)); + enum_ctrl->bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; + + // Lock the device for enumeration. This allows us call usbh_dev_set_...() functions during enumeration + ESP_ERROR_CHECK(usbh_dev_enum_lock(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); // Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb #ifdef ENABLE_ENUM_FILTER_CALLBACK @@ -445,7 +430,7 @@ static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl) abort(); break; } - if (hcd_urb_enqueue(enum_ctrl->pipe, enum_ctrl->urb) != ESP_OK) { + if (usbh_dev_submit_ctrl_urb(enum_ctrl->dev_hdl, enum_ctrl->urb) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to submit: %s", enum_stage_strings[enum_ctrl->stage]); return false; } @@ -470,18 +455,10 @@ static bool enum_stage_wait(enum_ctrl_t *enum_ctrl) static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) { - // Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(enum_ctrl->pipe); - assert(dequeued_enum_urb == enum_ctrl->urb); - // Check transfer status - usb_transfer_t *transfer = &dequeued_enum_urb->transfer; + usb_transfer_t *transfer = &enum_ctrl->urb->transfer; if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) { ESP_LOGE(HUB_DRIVER_TAG, "Bad transfer status %d: %s", transfer->status, enum_stage_strings[enum_ctrl->stage]); - if (transfer->status == USB_TRANSFER_STATUS_STALL) { - // EP stalled, clearing the pipe to execute further stages - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_CLEAR)); - } return false; } // Check IN transfer returned the expected correct number of bytes @@ -508,8 +485,8 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) ret = false; break; } - // Update and save the MPS of the default pipe - if (hcd_pipe_update_mps(enum_ctrl->pipe, device_desc->bMaxPacketSize0) != ESP_OK) { + // Update and save the MPS of the EP0 + if (usbh_dev_set_ep0_mps(enum_ctrl->dev_hdl, device_desc->bMaxPacketSize0) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to update MPS"); ret = false; break; @@ -520,16 +497,15 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) break; } case ENUM_STAGE_CHECK_ADDR: { - // Update the pipe and device's address, and fill the address into the device object - ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(enum_ctrl->pipe, ENUM_DEV_ADDR)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); + // Update the device's address + ESP_ERROR_CHECK(usbh_dev_set_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); ret = true; break; } case ENUM_STAGE_CHECK_FULL_DEV_DESC: { - // Fill device descriptor into the device object + // Set the device's descriptor const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(enum_ctrl->dev_hdl, device_desc)); + ESP_ERROR_CHECK(usbh_dev_set_desc(enum_ctrl->dev_hdl, device_desc)); enum_ctrl->iManufacturer = device_desc->iManufacturer; enum_ctrl->iProduct = device_desc->iProduct; enum_ctrl->iSerialNumber = device_desc->iSerialNumber; @@ -558,10 +534,10 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) break; } case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: { - // Fill configuration descriptor into the device object + // Set the device's configuration descriptor const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); enum_ctrl->bConfigurationValue = config_desc->bConfigurationValue; - ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(enum_ctrl->dev_hdl, config_desc)); + ESP_ERROR_CHECK(usbh_dev_set_config_desc(enum_ctrl->dev_hdl, config_desc)); ret = true; break; } @@ -638,7 +614,7 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) } else { // ENUM_STAGE_CHECK_FULL_PROD_STR_DESC select = 2; } - ESP_ERROR_CHECK(usbh_hub_enum_fill_str_desc(enum_ctrl->dev_hdl, str_desc, select)); + ESP_ERROR_CHECK(usbh_dev_set_str_desc(enum_ctrl->dev_hdl, str_desc, select)); ret = true; break; } @@ -653,43 +629,27 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) { - // We currently only support a single device connected to the root port. Move the device handle from enum to root - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_ACTIVE; - HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.root_dev_hdl = enum_ctrl->dev_hdl; - usb_device_handle_t dev_hdl = enum_ctrl->dev_hdl; + // Unlock the device as we are done with the enumeration + ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); + // Propagate a new device event + ESP_ERROR_CHECK(usbh_devs_new_dev_event(enum_ctrl->dev_hdl)); + // We are done with using the device. Close it. + ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); // Clear values in enum_ctrl enum_ctrl->dev_hdl = NULL; - enum_ctrl->pipe = NULL; - // Update device object after enumeration is done - ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl)); } static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) { - // Enumeration failed. Clear the enum device handle and pipe if (enum_ctrl->dev_hdl) { - // If enumeration failed due to a port event, we need to Halt, flush, and dequeue enum default pipe in case there - // was an in-flight URB. - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_FLUSH)); - hcd_urb_dequeue(enum_ctrl->pipe); // This could return NULL if there - ESP_ERROR_CHECK(usbh_hub_enum_failed(enum_ctrl->dev_hdl)); // Free the underlying device object first before recovering the port + // Close the device and unlock it as we done with enumeration + ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); + ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); + // We allow this to fail in case the device object was already freed + usbh_devs_remove(ENUM_DEV_UID); } // Clear values in enum_ctrl enum_ctrl->dev_hdl = NULL; - enum_ctrl->pipe = NULL; - 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.port_reqs |= PORT_REQ_RECOVER; - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; - } 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; - } - HUB_DRIVER_EXIT_CRITICAL(); } static enum_stage_t get_next_stage(enum_stage_t old_stage, enum_ctrl_t *enum_ctrl) @@ -803,13 +763,13 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port 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) +static void enum_transfer_callback(usb_transfer_t *transfer) { - // Note: This callback may have triggered when a failed enumeration is already cleaned up (e.g., due to a failed port reset) + // We simply trigger a processing request to handle the completed enumeration control transfer HUB_DRIVER_ENTER_CRITICAL_SAFE(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; 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); + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } // ---------------------- Handlers ------------------------- @@ -822,17 +782,32 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) // Nothing to do break; case HCD_PORT_EVENT_CONNECTION: { - if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) == ESP_OK) { - ESP_LOGD(HUB_DRIVER_TAG, "Root port reset"); - // 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_ENUM; - HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; - } else { + 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; + } + // Allocate a new device. We use a fixed ENUM_DEV_UID for now since we only support a single device + if (usbh_devs_add(ENUM_DEV_UID, speed, p_hub_driver_obj->constant.root_port_hdl) != ESP_OK) { + 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; + // 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; + 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: @@ -842,18 +817,13 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) 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_ENUM_FAILED: // This occurred after a failed enumeration. - // Therefore, there's no device and we can go straight to port recovery + 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_FLAG_ACTION_PORT_REQ; break; - case ROOT_PORT_STATE_ENUM: - // 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; - case ROOT_PORT_ACTIVE: - // There was an enumerated device. We need to indicate to USBH that the device is gone + 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; break; default: @@ -863,7 +833,10 @@ 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) { - ESP_ERROR_CHECK(usbh_hub_dev_gone(p_hub_driver_obj->single_thread.root_dev_hdl)); + // 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); } break; } @@ -955,7 +928,6 @@ static void enum_handle_events(void) stage_pass = true; break; default: - // Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup. stage_pass = true; break; } @@ -977,18 +949,22 @@ static void enum_handle_events(void) // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- -esp_err_t hub_install(hub_config_t *hub_config) +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); urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0); if (hub_driver_obj == NULL || enum_urb == NULL) { return ESP_ERR_NO_MEM; } - esp_err_t ret; + enum_urb->usb_host_client = (void *)hub_driver_obj; + enum_urb->transfer.callback = enum_transfer_callback; + // Install HCD port hcd_port_config_t port_config = { .fifo_bias = HUB_ROOT_HCD_PORT_FIFO_BIAS, @@ -1001,6 +977,7 @@ esp_err_t hub_install(hub_config_t *hub_config) if (ret != ESP_OK) { goto err; } + // Initialize Hub driver object hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; @@ -1020,6 +997,9 @@ esp_err_t hub_install(hub_config_t *hub_config) } p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); + + // Write-back client_ret pointer + *client_ret = (void *)hub_driver_obj; ret = ESP_OK; return ret; @@ -1081,31 +1061,31 @@ esp_err_t hub_root_stop(void) return ret; } -esp_err_t hub_dev_is_free(uint8_t dev_addr) +esp_err_t hub_port_recycle(unsigned int dev_uid) { - assert(dev_addr == ENUM_DEV_ADDR); - assert(p_hub_driver_obj->single_thread.root_dev_hdl); - // 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_hdl = NULL; + 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(); - // 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->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } - 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; } diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index 24ec22b085..1fd3617307 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -418,22 +418,6 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); */ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr); -/** - * @brief Update a pipe's callback - * - * This function is intended to be called on default pipes at the end of enumeration to switch to a callback that - * handles the completion of regular control transfer. - * - Pipe is not current processing a command - * - Pipe does not have any enqueued URBs - * - Port cannot be resetting - * - * @param pipe_hdl Pipe handle - * @param callback Callback - * @param user_arg Callback argument - * @return esp_err_t - */ -esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg); - /** * @brief Get the context variable of a pipe from its handle * diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index a096de8fd9..cf85ad861d 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -42,9 +42,10 @@ typedef struct { * - Initializes the HCD root port * * @param[in] hub_config Hub driver configuration + * @param[out] client_ret Unique pointer to identify the Hub as a USB Host client * @return esp_err_t */ -esp_err_t hub_install(hub_config_t *hub_config); +esp_err_t hub_install(hub_config_t *hub_config, void **client_ret); /** * @brief Uninstall Hub driver @@ -78,15 +79,16 @@ 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 + * @brief Indicate to the Hub driver that a device's port can be recycled * - * Hub driver can now recover the port that the device was connected to + * The device connected to the port has been freed. The Hub driver can now + * recycled the port. * - * @param dev_addr Device address + * @param dev_uid Device's unique ID * @return * - ESP_OK: Success */ -esp_err_t hub_dev_is_free(uint8_t dev_addr); +esp_err_t hub_port_recycle(unsigned int dev_uid); /** * @brief Hub driver's processing function diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 6dee5e55b4..a1b5b2a845 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -58,7 +58,7 @@ typedef struct { usb_device_handle_t dev_hdl; } dev_gone_data; struct { - uint8_t dev_addr; + unsigned int dev_uid; } dev_free_data; }; } usbh_event_data_t; @@ -195,6 +195,32 @@ esp_err_t usbh_devs_num(int *num_devs_ret); */ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); +/** + * @brief Create a device and add it to the device pool + * + * The created device will not be enumerated where the device's address is 0, + * device and config descriptor are NULL. The device will still have a default + * pipe, thus allowing control transfers to be submitted. + * + * - Call usbh_devs_open() before communicating with the device + * - Call usbh_dev_enum_lock() before enumerating the device via the various + * usbh_dev_set_...() functions. + * + * @param[in] uid Unique ID assigned to the device + * @param[in] dev_speed Device's speed + * @param[in] port_hdl Handle of the port that the device is connected to + * @return esp_err_t + */ +esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl); + +/** + * @brief Indicates to the USBH that a device is gone + * + * @param[in] uid Unique ID assigned to the device on creation (see 'usbh_devs_add()') + * @return esp_err_t + */ +esp_err_t usbh_devs_remove(unsigned int uid); + /** * @brief Mark that all devices should be freed at the next possible opportunity * @@ -227,6 +253,16 @@ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); */ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl); +/** + * @brief Trigger a USBH_EVENT_NEW_DEV event for the device + * + * This is typically called after a device has been fully enumerated. + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl); + // ------------------------------------------------ Device Functions --------------------------------------------------- // ----------------------- Getters ------------------------- @@ -245,7 +281,8 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr); /** * @brief Get a device's information * - * @note This function can block + * @note It is possible that the device has not been enumerated yet, thus some + * fields may be NULL. * @param[in] dev_hdl Device handle * @param[out] dev_info Device information * @return esp_err_t @@ -257,6 +294,8 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ * * - The device descriptor is cached when the device is created by the Hub driver * + * @note It is possible that the device has not been enumerated yet, thus the + * device descriptor could be NULL. * @param[in] dev_hdl Device handle * @param[out] dev_desc_ret Device descriptor * @return esp_err_t @@ -268,13 +307,109 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t * * Simply returns a reference to the internally cached configuration descriptor * - * @note This function can block + * @note It is possible that the device has not been enumerated yet, thus the + * configuration descriptor could be NULL. * @param[in] dev_hdl Device handle * @param config_desc_ret * @return esp_err_t */ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); +// ----------------------- Setters ------------------------- + +/** + * @brief Lock a device for enumeration + * + * - A device's enumeration lock must be set before any of its enumeration fields + * (e.g., address, device/config descriptors) can be set/updated. + * - The caller must be the sole opener of the device (see 'usbh_devs_open()') + * when locking the device for enumeration. + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl); + +/** + * @brief Release a device's enumeration lock + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl); + +/** + * @brief Set the maximum packet size of EP0 for a device + * + * Typically called during enumeration after obtaining the first 8 bytes of the + * device's descriptor. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] wMaxPacketSize Maximum packet size + * @return esp_err_t + */ +esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketSize); + +/** + * @brief Set a device's address + * + * Typically called during enumeration after a SET_ADDRESSS request has be + * sent to the device. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] dev_addr + * @return esp_err_t + */ +esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); + +/** + * @brief Set a device's descriptor + * + * Typically called during enumeration after obtaining the device's descriptor + * via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] device_desc Device descriptor to copy + * @return esp_err_t + */ +esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); + +/** + * @brief Set a device's configuration descriptor + * + * Typically called during enumeration after obtaining the device's configuration + * descriptor via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] config_desc_full Configuration descriptor to copy + * @return esp_err_t + */ +esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); + +/** + * @brief Set a device's string descriptor + * + * Typically called during enumeration after obtaining one of the device's string + * descriptor via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] str_desc String descriptor to copy + * @param[in] select Select string descriptor. 0/1/2 for Manufacturer/Product/Serial + * Number string descriptors respectively + * @return esp_err_t + */ +esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); + // ----------------------------------------------- Endpoint Functions ------------------------------------------------- /** @@ -381,110 +516,6 @@ esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb); */ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret); -// -------------------------------------------------- Hub Functions ---------------------------------------------------- - -// ------------------- Device Related ---------------------- - -/** - * @brief Indicates to USBH the start of enumeration for a device - * - * - The Hub driver calls this function before it starts enumerating a new device. - * - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration - * functions. - * - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration. - * - * @note Hub Driver only - * @param[in] port_hdl Handle of the port that the device is connected to - * @param[in] dev_speed Device's speed - * @param[out] new_dev_hdl Device's handle - * @param[out] default_pipe_hdl Device's default pipe handle - * @return esp_err_t - */ -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 device is gone - * - * @param dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl); - -// ----------------- Enumeration Related ------------------- - -/** - * @brief Assign the enumerating device's address - * - * @note Hub Driver only - * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param dev_addr - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); - -/** - * @brief Fill the enumerating device's descriptor - * - * @note Hub Driver only - * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param device_desc - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); - -/** - * @brief Fill the enumerating device's active configuration descriptor - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @param config_desc_full - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); - -/** - * @brief Fill one of the string descriptors of the enumerating device - * - * @note Hub Driver only - * @note Must call in sequence - * @param dev_hdl Device handle - * @param str_desc Pointer to string descriptor - * @param select Select which string descriptor. 0/1/2 for Manufacturer/Product/Serial Number string descriptors respectively - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); - -/** - * @brief Indicate the device enumeration is completed - * - * This will allow the device to be opened by clients, and also trigger a USBH_EVENT_NEW_DEV event. - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl); - -/** - * @brief Indicate that device enumeration has failed - * - * This will cause the enumerating device's resources to be cleaned up - * The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued. - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl); - #ifdef __cplusplus } #endif diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index b9b12dafcc..b2e5ed137a 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -148,6 +148,7 @@ typedef struct { SemaphoreHandle_t event_sem; SemaphoreHandle_t mux_lock; usb_phy_handle_t phy_handle; // Will be NULL if host library is installed with skip_phy_setup + void *hub_client; // Pointer to Hub driver (acting as a client). Used to reroute completed USBH control transfers } constant; } host_lib_t; @@ -171,8 +172,15 @@ static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr) { - assert(dev_addr != 0); - return (client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1))); + bool ret; + + if (dev_addr != 0) { + ret = client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1)); + } else { + ret = false; + } + + return ret; } static bool _unblock_client(client_t *client_obj, bool in_isr) @@ -268,14 +276,18 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) case USBH_EVENT_CTRL_XFER: { assert(event_data->ctrl_xfer_data.urb != NULL); assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL); - // Redistribute done control transfer to the clients that submitted them - client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; - - HOST_ENTER_CRITICAL(); - TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry); - client_obj->dynamic.num_done_ctrl_xfer++; - _unblock_client(client_obj, false); - HOST_EXIT_CRITICAL(); + // Redistribute completed control transfers to the clients that submitted them + if (event_data->ctrl_xfer_data.urb->usb_host_client == p_host_lib_obj->constant.hub_client) { + // Redistribute to Hub driver. Simply call the transfer callback + event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer); + } else { + client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; + HOST_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry); + client_obj->dynamic.num_done_ctrl_xfer++; + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); + } break; } case USBH_EVENT_NEW_DEV: { @@ -297,8 +309,8 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) 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)); + // 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)); break; } case USBH_EVENT_ALL_FREE: { @@ -420,7 +432,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config) .enum_filter_cb = config->enum_filter_cb, #endif // ENABLE_ENUM_FILTER_CALLBACK }; - ret = hub_install(&hub_config); + ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client); if (ret != ESP_OK) { goto hub_err; } diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 06d6f8d942..b402e72a99 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -57,7 +57,8 @@ struct device_s { uint32_t in_pending_list: 1; 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 enum_lock: 1; // Device is locked for enumeration. Enum information (e.g., address, device/config desc etc) may change + uint32_t reserved28: 28; }; uint32_t val; } flags; @@ -74,17 +75,22 @@ struct device_s { */ endpoint_t *endpoints[NUM_NON_DEFAULT_EP]; } mux_protected; - // Constant members do not change after device allocation and enumeration thus do not require a critical section + // Constant members do not require a critical section struct { + // Assigned on device allocation and remain constant for the device's lifetime hcd_pipe_handle_t default_pipe; hcd_port_handle_t port_hdl; - uint8_t address; usb_speed_t speed; - const usb_device_desc_t *desc; - const usb_config_desc_t *config_desc; - const usb_str_desc_t *str_desc_manu; - const usb_str_desc_t *str_desc_product; - const usb_str_desc_t *str_desc_ser_num; + unsigned int uid; + /* + These fields are can only be changed when enum_lock is set, thus can be treated as constant + */ + uint8_t address; + usb_device_desc_t *desc; + usb_config_desc_t *config_desc; + usb_str_desc_t *str_desc_manu; + usb_str_desc_t *str_desc_product; + usb_str_desc_t *str_desc_ser_num; } constant; }; @@ -143,6 +149,50 @@ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags); // ----------------------------------------------------- Helpers ------------------------------------------------------- +static device_t *_find_dev_from_uid(unsigned int uid) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + device_t *dev_iter; + + // Search the device lists for a device with the specified address + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.uid == uid) { + return dev_iter; + } + } + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.uid == uid) { + return dev_iter; + } + } + + return NULL; +} + +static device_t *_find_dev_from_addr(uint8_t dev_addr) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + device_t *dev_iter; + + // Search the device lists for a device with the specified address + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.address == dev_addr) { + return dev_iter; + } + } + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.address == dev_addr) { + return dev_iter; + } + } + + return NULL; +} + static inline bool check_ep_addr(uint8_t bEndpointAddress) { /* @@ -204,13 +254,21 @@ static bool urb_check_args(urb_t *urb) return true; } -static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in) +static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, unsigned int mps, bool is_in) { if (type == USB_TRANSFER_TYPE_CTRL) { // Check that num_bytes and wLength are set correctly usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; - if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) { - ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes and usb_setup_packet_t wLength mismatch"); + bool mismatch = false; + if (is_in) { + // For IN transfers, 'num_bytes >= sizeof(usb_setup_packet_t) + setup_pkt->wLength' due to MPS rounding + mismatch = (transfer->num_bytes < sizeof(usb_setup_packet_t) + setup_pkt->wLength); + } else { + // For OUT transfers, num_bytes must match 'sizeof(usb_setup_packet_t) + setup_pkt->wLength' + mismatch = (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength); + } + if (mismatch) { + ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes %d and usb_setup_packet_t wLength %d mismatch", transfer->num_bytes, setup_pkt->wLength); return false; } } else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) { @@ -300,19 +358,21 @@ static void endpoint_free(endpoint_t *ep_obj) heap_caps_free(ep_obj); } -static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, device_t **dev_obj_ret) +static esp_err_t device_alloc(unsigned int uid, + usb_speed_t speed, + hcd_port_handle_t port_hdl, + device_t **dev_obj_ret) { - esp_err_t ret; device_t *dev_obj = heap_caps_calloc(1, sizeof(device_t), MALLOC_CAP_DEFAULT); - usb_device_desc_t *dev_desc = heap_caps_calloc(1, sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT); - if (dev_obj == NULL || dev_desc == NULL) { - ret = ESP_ERR_NO_MEM; - goto err; + if (dev_obj == NULL) { + return ESP_ERR_NO_MEM; } - // Allocate a pipe for EP0. We set the pipe callback to NULL for now + + esp_err_t ret; + // Allocate a pipe for EP0 hcd_pipe_config_t pipe_config = { - .callback = NULL, - .callback_arg = NULL, + .callback = ep0_pipe_callback, + .callback_arg = (void *)dev_obj, .context = (void *)dev_obj, .ep_desc = NULL, // No endpoint descriptor means we're allocating a pipe for EP0 .dev_speed = speed, @@ -327,15 +387,17 @@ static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, dev dev_obj->dynamic.state = USB_DEVICE_STATE_DEFAULT; dev_obj->constant.default_pipe = default_pipe_hdl; dev_obj->constant.port_hdl = port_hdl; - // Note: dev_obj->constant.address is assigned later during enumeration dev_obj->constant.speed = speed; - dev_obj->constant.desc = dev_desc; + dev_obj->constant.uid = uid; + // Note: Enumeration related dev_obj->constant fields are initialized later using usbh_dev_set_...() functions + + // Write-back device object *dev_obj_ret = dev_obj; ret = ESP_OK; + return ret; err: - heap_caps_free(dev_desc); heap_caps_free(dev_obj); return ret; } @@ -345,21 +407,24 @@ static void device_free(device_t *dev_obj) if (dev_obj == NULL) { return; } - // Configuration might not have been allocated (in case of early enumeration failure) - if (dev_obj->constant.config_desc) { - heap_caps_free((usb_config_desc_t *)dev_obj->constant.config_desc); + // Device descriptor might not have been set yet + if (dev_obj->constant.desc) { + heap_caps_free(dev_obj->constant.desc); } - // String descriptors might not have been allocated (in case of early enumeration failure) + // Configuration descriptor might not have been set yet + if (dev_obj->constant.config_desc) { + heap_caps_free(dev_obj->constant.config_desc); + } + // String descriptors might not have been set yet if (dev_obj->constant.str_desc_manu) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_manu); + heap_caps_free(dev_obj->constant.str_desc_manu); } if (dev_obj->constant.str_desc_product) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_product); + heap_caps_free(dev_obj->constant.str_desc_product); } if (dev_obj->constant.str_desc_ser_num) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_ser_num); + heap_caps_free(dev_obj->constant.str_desc_ser_num); } - heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc); ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe)); heap_caps_free(dev_obj); } @@ -426,6 +491,9 @@ static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) { + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ if (action_flags == 0) { return false; } @@ -512,7 +580,7 @@ static inline void handle_prop_gone_evt(device_t *dev_obj) static inline void handle_free(device_t *dev_obj) { // 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; + const unsigned int dev_uid = dev_obj->constant.uid; bool all_free; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); @@ -536,7 +604,7 @@ static inline void handle_free(device_t *dev_obj) usbh_event_data_t event_data = { .event = USBH_EVENT_DEV_FREE, .dev_free_data = { - .dev_addr = dev_addr, + .dev_uid = dev_uid, } }; p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); @@ -661,8 +729,6 @@ esp_err_t usbh_process(void) --------------------------------------------------------------------- */ USBH_EXIT_CRITICAL(); ESP_LOGD(USBH_TAG, "Processing actions 0x%"PRIx32"", action_flags); - // Sanity check. If the device is being freed, there must not be any other action flags set - assert(!(action_flags & DEV_ACTION_FREE) || action_flags == DEV_ACTION_FREE); if (action_flags & DEV_ACTION_EPn_HALT_FLUSH) { handle_epn_halt_flush(dev_obj); @@ -714,24 +780,42 @@ esp_err_t usbh_devs_num(int *num_devs_ret) esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) { USBH_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG); - USBH_ENTER_CRITICAL(); int num_filled = 0; device_t *dev_obj; - // Fill list with devices from idle tailq + + USBH_ENTER_CRITICAL(); + /* + Fill list with devices from idle tailq and pending tailq. Only devices that + are fully enumerated are added to the list. Thus, the following devices are + not excluded: + - Devices with their enum_lock set + - Devices not in the configured state + - Devices with address 0 + */ TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { if (num_filled < list_len) { - dev_addr_list[num_filled] = dev_obj->constant.address; - num_filled++; + if (!dev_obj->dynamic.flags.enum_lock && + dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED && + dev_obj->constant.address != 0) { + dev_addr_list[num_filled] = dev_obj->constant.address; + num_filled++; + } } else { + // Address list is already full break; } } // Fill list with devices from pending tailq TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { if (num_filled < list_len) { - dev_addr_list[num_filled] = dev_obj->constant.address; - num_filled++; + if (!dev_obj->dynamic.flags.enum_lock && + dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED && + dev_obj->constant.address != 0) { + dev_addr_list[num_filled] = dev_obj->constant.address; + num_filled++; + } } else { + // Address list is already full break; } } @@ -741,6 +825,82 @@ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *nu return ESP_OK; } +esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl) +{ + USBH_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj; + + // Allocate a device object (initialized to address 0) + ret = device_alloc(uid, dev_speed, port_hdl, &dev_obj); + if (ret != ESP_OK) { + return ret; + } + + // We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + USBH_ENTER_CRITICAL(); + + // Check that there is not already a device with the same uid + if (_find_dev_from_uid(uid) != NULL) { + ret = ESP_ERR_INVALID_ARG; + goto exit; + } + // Check that there is not already a device currently with address 0 + if (_find_dev_from_addr(0) != NULL) { + ret = ESP_ERR_NOT_FINISHED; + goto exit; + } + // Add the device to the idle device list + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + p_usbh_obj->mux_protected.num_device++; + ret = ESP_OK; + +exit: + USBH_EXIT_CRITICAL(); + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + + return ret; +} + +esp_err_t usbh_devs_remove(unsigned int uid) +{ + esp_err_t ret; + device_t *dev_obj; + bool call_proc_req_cb = false; + + USBH_ENTER_CRITICAL(); + dev_obj = _find_dev_from_uid(uid); + if (dev_obj == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto exit; + } + // Mark the device as gone + dev_obj->dynamic.flags.is_gone = 1; + // Check if the device can be freed immediately + if (dev_obj->dynamic.open_count == 0) { + // Device is not currently opened at all. Can free immediately. + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); + } else { + // Device is still opened. 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); + } + ret = ESP_OK; +exit: + USBH_EXIT_CRITICAL(); + + // Call the processing request callback + 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); + } + + return ret; +} + esp_err_t usbh_devs_mark_all_free(void) { USBH_ENTER_CRITICAL(); @@ -790,28 +950,17 @@ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) USBH_ENTER_CRITICAL(); // Go through the device lists to find the device with the specified address - device_t *found_dev_obj = NULL; - device_t *dev_obj; - TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { - if (dev_obj->constant.address == dev_addr) { - found_dev_obj = dev_obj; - goto exit; - } - } - TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { - if (dev_obj->constant.address == dev_addr) { - found_dev_obj = dev_obj; - goto exit; - } - } -exit: - if (found_dev_obj != NULL) { - // The device is not in a state to be opened - if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { + device_t *dev_obj = _find_dev_from_addr(dev_addr); + if (dev_obj != NULL) { + // Check if the device is in a state to be opened + if (dev_obj->dynamic.flags.is_gone || // Device is already gone (disconnected) + dev_obj->dynamic.flags.waiting_free) { // Device is waiting to be freed ret = ESP_ERR_INVALID_STATE; + } else if (dev_obj->dynamic.flags.enum_lock) { // Device's enum_lock is set + ret = ESP_ERR_NOT_ALLOWED; } else { dev_obj->dynamic.open_count++; - *dev_hdl = (usb_device_handle_t)found_dev_obj; + *dev_hdl = (usb_device_handle_t)dev_obj; ret = ESP_OK; } } else { @@ -828,6 +977,8 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); + // Device should never be closed while its enum_lock is + USBH_CHECK_FROM_CRIT(!dev_obj->dynamic.flags.enum_lock, ESP_ERR_NOT_ALLOWED); dev_obj->dynamic.open_count--; bool call_proc_req_cb = false; if (dev_obj->dynamic.open_count == 0) { @@ -845,6 +996,26 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) 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); } + + return ESP_OK; +} + +esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl) +{ + device_t *dev_obj = (device_t *)dev_hdl; + bool call_proc_req_cb = false; + + USBH_ENTER_CRITICAL(); + // Device must be in the configured state + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); + USBH_EXIT_CRITICAL(); + + // Call the processing request callback + 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); + } + return ESP_OK; } @@ -870,28 +1041,26 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ USBH_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - esp_err_t ret; - // Device must be configured, or not attached (if it suddenly disconnected) - USBH_ENTER_CRITICAL(); - if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED)) { - USBH_EXIT_CRITICAL(); - ret = ESP_ERR_INVALID_STATE; - goto exit; - } - // Critical section for the dynamic members dev_info->speed = dev_obj->constant.speed; dev_info->dev_addr = dev_obj->constant.address; - dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; - USBH_EXIT_CRITICAL(); - assert(dev_obj->constant.config_desc); - dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue; - // String descriptors are allowed to be NULL as not all devices support them + // Device descriptor might not have been set yet + if (dev_obj->constant.desc) { + dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; + } else { + // Use the default pipe's MPS instead + dev_info->bMaxPacketSize0 = hcd_pipe_get_mps(dev_obj->constant.default_pipe); + } + // Configuration descriptor might not have been set yet + if (dev_obj->constant.config_desc) { + dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue; + } else { + dev_info->bConfigurationValue = 0; + } dev_info->str_desc_manufacturer = dev_obj->constant.str_desc_manu; dev_info->str_desc_product = dev_obj->constant.str_desc_product; dev_info->str_desc_serial_num = dev_obj->constant.str_desc_ser_num; - ret = ESP_OK; -exit: - return ret; + + return ESP_OK; } esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret) @@ -899,10 +1068,6 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t USBH_CHECK(dev_hdl != NULL && dev_desc_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - USBH_EXIT_CRITICAL(); - *dev_desc_ret = dev_obj->constant.desc; return ESP_OK; } @@ -912,19 +1077,256 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config USBH_CHECK(dev_hdl != NULL && config_desc_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; + *config_desc_ret = dev_obj->constant.config_desc; + + return ESP_OK; +} + +// ----------------------- Setters ------------------------- + +esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret; - // Device must be in the configured state - USBH_ENTER_CRITICAL(); - if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { - USBH_EXIT_CRITICAL(); + device_t *dev_obj = (device_t *)dev_hdl; + + // We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + + /* + The device's enum_lock can only be set when the following conditions are met: + - No other endpoints except EP0 have been allocated + - We are the sole opener + - Device's enum_lock is not already set + */ + // Check that no other endpoints except EP0 have been allocated + bool ep_found = false; + for (int i = 0; i < NUM_NON_DEFAULT_EP; i++) { + if (dev_obj->mux_protected.endpoints[i] != NULL) { + ep_found = true; + break; + } + } + if (ep_found) { ret = ESP_ERR_INVALID_STATE; goto exit; } + // Check that we are the sole opener and enum_lock is not already set + USBH_ENTER_CRITICAL(); + if (!dev_obj->dynamic.flags.enum_lock && (dev_obj->dynamic.open_count == 1)) { + dev_obj->dynamic.flags.enum_lock = true; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } USBH_EXIT_CRITICAL(); - assert(dev_obj->constant.config_desc); - *config_desc_ret = dev_obj->constant.config_desc; - ret = ESP_OK; + exit: + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + + return ret; +} + +esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's enum_lock must have been previously set + if (dev_obj->dynamic.flags.enum_lock) { + assert(dev_obj->dynamic.open_count == 1); // We must still be the sole opener + dev_obj->dynamic.flags.enum_lock = false; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketSize) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's EP0 MPS can only be updated when in the default state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_DEFAULT) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + // Device's enum_lock must be set before enumeration related data fields can be set + if (dev_obj->dynamic.flags.enum_lock) { + ret = hcd_pipe_update_mps(dev_obj->constant.default_pipe, wMaxPacketSize); + } else { + ret = ESP_ERR_NOT_ALLOWED; + } +exit: + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's address can only be set when in the default state + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_DEFAULT, ESP_ERR_INVALID_STATE); + // Device's enum_lock must be set before enumeration related data fields can be set + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.flags.enum_lock, ESP_ERR_NOT_ALLOWED); + // Update the device and default pipe's target address + ret = hcd_pipe_update_dev_addr(dev_obj->constant.default_pipe, dev_addr); + if (ret == ESP_OK) { + dev_obj->constant.address = dev_addr; + dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; + } + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc) +{ + USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_device_desc_t *new_desc, *old_desc; + + // Allocate and copy new device descriptor + new_desc = heap_caps_malloc(sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, device_desc, sizeof(usb_device_desc_t)); + + USBH_ENTER_CRITICAL(); + // Device's descriptor can only be set in the default or addressed state + if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_DEFAULT || dev_obj->dynamic.state == USB_DEVICE_STATE_ADDRESS)) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its device descriptor + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + old_desc = dev_obj->constant.desc; // Save old descriptor for cleanup + dev_obj->constant.desc = new_desc; // Assign new descriptor + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); + return ret; +} + +esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full) +{ + USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_config_desc_t *new_desc, *old_desc; + + // Allocate and copy new config descriptor + new_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, config_desc_full, config_desc_full->wTotalLength); + + USBH_ENTER_CRITICAL(); + // Device's config descriptor can only be set when in the addressed state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_ADDRESS) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its config descriptor + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + old_desc = dev_obj->constant.config_desc; // Save old descriptor for cleanup + dev_obj->constant.config_desc = new_desc; // Assign new descriptor + dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); + return ret; +} + +esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select) +{ + USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_str_desc_t *new_desc, *old_desc; + + // Allocate and copy new string descriptor + new_desc = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, str_desc, str_desc->bLength); + + USBH_ENTER_CRITICAL(); + // Device's string descriptors can only be set when in the default state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its string descriptors + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + // Assign to the selected descriptor + switch (select) { + case 0: + old_desc = dev_obj->constant.str_desc_manu; + dev_obj->constant.str_desc_manu = new_desc; + break; + case 1: + old_desc = dev_obj->constant.str_desc_product; + dev_obj->constant.str_desc_product = new_desc; + break; + default: // 2 + old_desc = dev_obj->constant.str_desc_ser_num; + dev_obj->constant.str_desc_ser_num = new_desc; + break; + } + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); return ret; } @@ -939,6 +1341,7 @@ esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config esp_err_t ret; device_t *dev_obj = (device_t *)dev_hdl; endpoint_t *ep_obj; + USBH_CHECK(dev_obj->constant.config_desc, ESP_ERR_INVALID_STATE); // Configuration descriptor must be set // Find the endpoint descriptor from the device's current configuration descriptor const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_address(dev_obj->constant.config_desc, ep_config->bInterfaceNumber, ep_config->bAlternateSetting, ep_config->bEndpointAddress, NULL); @@ -1065,10 +1468,11 @@ esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) device_t *dev_obj = (device_t *)dev_hdl; USBH_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG); bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; - USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); + // Device descriptor could still be NULL at this point, so we get the MPS from the pipe instead. + unsigned int mps = hcd_pipe_get_mps(dev_obj->constant.default_pipe); + USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, mps, xfer_is_in), ESP_ERR_INVALID_ARG); USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); // Increment the control transfer count first dev_obj->dynamic.num_ctrl_xfers_inflight++; USBH_EXIT_CRITICAL(); @@ -1121,156 +1525,3 @@ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret) *urb_ret = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); return ESP_OK; } - -// -------------------------------------------------- Hub Functions ---------------------------------------------------- - -// ------------------- Device Related ---------------------- - -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 - USBH_CHECK(new_dev_hdl != NULL, ESP_ERR_INVALID_ARG); - esp_err_t ret; - device_t *dev_obj; - ret = device_alloc(port_hdl, dev_speed, &dev_obj); - if (ret != ESP_OK) { - return ret; - } - // Write-back device handle - *new_dev_hdl = (usb_device_handle_t)dev_obj; - *default_pipe_hdl = dev_obj->constant.default_pipe; - ret = ESP_OK; - return ret; -} - -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; - - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.flags.is_gone = 1; - // Check if the device can be freed immediately - if (dev_obj->dynamic.open_count == 0) { - // Device is not currently opened at all. Can free immediately. - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); - } else { - // Device is still opened. 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); - } - return ESP_OK; -} - -// ----------------- Enumeration Related ------------------- - -esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; - USBH_EXIT_CRITICAL(); - - // We can modify the info members outside the critical section - dev_obj->constant.address = dev_addr; - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc) -{ - USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // We can modify the info members outside the critical section - memcpy((usb_device_desc_t *)dev_obj->constant.desc, device_desc, sizeof(usb_device_desc_t)); - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full) -{ - USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // Allocate memory to store the configuration descriptor - usb_config_desc_t *config_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); // Buffer to copy over full configuration descriptor (wTotalLength) - if (config_desc == NULL) { - return ESP_ERR_NO_MEM; - } - // Copy the configuration descriptor - memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength); - // Assign the config desc to the device object - assert(dev_obj->constant.config_desc == NULL); - dev_obj->constant.config_desc = config_desc; - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select) -{ - USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // Allocate memory to store the manufacturer string descriptor - usb_str_desc_t *str_desc_fill = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT); - if (str_desc_fill == NULL) { - return ESP_ERR_NO_MEM; - } - // Copy the string descriptor - memcpy(str_desc_fill, str_desc, str_desc->bLength); - // Assign filled string descriptor to the device object - switch (select) { - case 0: - assert(dev_obj->constant.str_desc_manu == NULL); - dev_obj->constant.str_desc_manu = str_desc_fill; - break; - case 1: - assert(dev_obj->constant.str_desc_product == NULL); - dev_obj->constant.str_desc_product = str_desc_fill; - break; - default: // 2 - assert(dev_obj->constant.str_desc_ser_num == NULL); - dev_obj->constant.str_desc_ser_num = str_desc_fill; - break; - } - return ESP_OK; -} - -esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - // We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - USBH_ENTER_CRITICAL(); - 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_DEV); - USBH_EXIT_CRITICAL(); - p_usbh_obj->mux_protected.num_device++; - xSemaphoreGive(p_usbh_obj->constant.mux_lock); - - // Update the EP0's underlying pipe's callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, ep0_pipe_callback, (void *)dev_obj)); - // Call the processing request callback - 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); - } - return ESP_OK; -} - -esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - device_free(dev_obj); - return ESP_OK; -}