diff --git a/host/usb/include/usb/usb_host.h b/host/usb/include/usb/usb_host.h index 98660ce2..598b42f1 100644 --- a/host/usb/include/usb/usb_host.h +++ b/host/usb/include/usb/usb_host.h @@ -548,6 +548,50 @@ esp_err_t usb_host_get_config_desc(usb_host_client_handle_t client_hdl, usb_devi */ esp_err_t usb_host_free_config_desc(const usb_config_desc_t *config_desc); +/** + * @brief Set remote wakeup feature on a device + * + * This function enables, or disables remote wakeup feature on a connected device. Device must support remote wakeup + * at fist place + * + * @note A control transfer is sent to a device, to enable/disable the feature + * @note A client must open the device first + * + * @param[in] client_hdl Handle of a client, that opened the device + * @param[in] dev_hdl Handle of a device, on which the remote wakeup is about to be enabled/disabled + * @param[in] enable Remote wakeup enable/disable + * + * @return + * - ESP_OK: Remote wakeup set successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Client did not open the device + * - ESP_ERR_NOT_ALLOWED: Device does not support remote wakeup + * - ESP_ERR_NO_MEM: Not enough memory + */ +esp_err_t usb_host_device_remote_wakeup_enable(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, bool enable); + +/** + * @brief Check if a remote wakeup is currently set + * + * This function checks if a remote wakeup feature is currently enabled or disabled on a connected device. Device must + * support remote wakeup at fist place + * + * @note A control transfer is sent to a device, get a device status descriptor + * @note A client must open the device first + * + * @param[in] client_hdl Handle of a client, that opened the device + * @param[in] dev_hdl Handle of a device, on which the remote wakeup is about to be checked + * @param[out] enabled Remote wakeup is currently enabled/disabled + * + * @return + * - ESP_OK: Remote wakeup status checked successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Client did not open the device + * - ESP_ERR_NOT_ALLOWED: Device does not support remote wakeup + * - ESP_ERR_NO_MEM: Not enough memory + */ +esp_err_t usb_host_device_remote_wakeup_check(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, bool *enabled); + // ----------------------------------------------- Interface Functions ------------------------------------------------- /** diff --git a/host/usb/include/usb/usb_types_ch9.h b/host/usb/include/usb/usb_types_ch9.h index 9faa3332..037306cf 100644 --- a/host/usb/include/usb/usb_types_ch9.h +++ b/host/usb/include/usb/usb_types_ch9.h @@ -153,10 +153,19 @@ ESP_STATIC_ASSERT(sizeof(usb_device_status_t) == sizeof(uint16_t), "Size of usb_ #define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 #define USB_W_VALUE_DT_INTERFACE_POWER 0x08 +/** + * @brief Feature selector bit masks belonging to the wValue field of a setup packet + * + * See Table 9-6 of USB2.0 specification for more details + */ +#define ENDPOINT_HALT 0x00 +#define DEVICE_REMOTE_WAKEUP 0x01 +#define TEST_MODE 0x02 + /** * @brief Initializer for a GET_STATUS request * - * Sets the address of a connected device + * Gets the status of a connected device */ #define USB_SETUP_PACKET_INIT_GET_STATUS(setup_pkt_ptr) ({ \ (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ @@ -166,6 +175,36 @@ ESP_STATIC_ASSERT(sizeof(usb_device_status_t) == sizeof(uint16_t), "Size of usb_ (setup_pkt_ptr)->wLength = 2; \ }) +/** + * @brief Initializer for a CLEAR_FEATURE request + * + * Clears the feature of a connected device + * + * See Chapter 9.4.1 of USB2.0 specification for more details + */ +#define USB_SETUP_PACKET_INIT_CLEAR_FEATURE(setup_pkt_ptr, feature_to_clear) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_CLEAR_FEATURE; \ + (setup_pkt_ptr)->wValue = feature_to_clear; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +/** + * @brief Initializer for a SET_FEATURE request + * + * Sets the feature of a connected device + * + * See Chapter 9.4.9 of USB2.0 specification for more details + */ +#define USB_SETUP_PACKET_INIT_SET_FEATURE(setup_pkt_ptr, feature_to_set) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_FEATURE; \ + (setup_pkt_ptr)->wValue = feature_to_set; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 0; \ +}) + /** * @brief Initializer for a SET_ADDRESS request * diff --git a/host/usb/private_include/hcd.h b/host/usb/private_include/hcd.h index eda365f8..bd9c837d 100644 --- a/host/usb/private_include/hcd.h +++ b/host/usb/private_include/hcd.h @@ -69,6 +69,7 @@ typedef enum { HCD_PORT_EVENT_DISCONNECTION, /**< A device disconnection has been detected */ HCD_PORT_EVENT_ERROR, /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY */ HCD_PORT_EVENT_OVERCURRENT, /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */ + HCD_PORT_EVENT_REMOTE_WAKEUP, /**< A remote-wakeup event from device has been detected */ } hcd_port_event_t; /** diff --git a/host/usb/src/hcd_dwc.c b/host/usb/src/hcd_dwc.c index 21879210..c2163acf 100644 --- a/host/usb/src/hcd_dwc.c +++ b/host/usb/src/hcd_dwc.c @@ -831,6 +831,12 @@ static hcd_port_event_t _intr_hdlr_hprt(port_t *port, usb_dwc_hal_port_event_t h port->flags.conn_dev_ena = 0; break; } + case USB_DWC_HAL_PORT_EVENT_REMOTE_WAKEUP: { + esp_rom_printf("WAKE\n"); + //port->state = HCD_PORT_STATE_ENABLED; + port_event = HCD_PORT_EVENT_REMOTE_WAKEUP; + break; + } default: { abort(); break; @@ -1217,6 +1223,34 @@ static inline bool _is_fifo_config_by_bias(const usb_dwc_hal_fifo_config_t *cfg) cfg->ptx_fifo_lines == 0); } +/** + * @brief Suspend PHY clock + * + * @param[in] port Pointer to the port object + * @param[in] suspend PHY is about to be suspended/resumed + * @return True PHY clock successfully suspended/resumed + * @return False PHY clock not suspended/resumed + */ +static inline bool _suspend_phy_clk(port_t *port, bool suspend) +{ + // Stop PHY Clock and gate HCLK + usb_dwc_hal_pwr_clk_toggle_phy_suspend(port->hal, suspend); + + // Wait 10 PHY clock cycles, PHY Clock is 30MHz when using 16bit interface, 60MHz when 8bit interface + // which makes 33.3 nS. Busy wait for 1uS just to be sure + esp_rom_delay_us(1); + + const bool phy_clk_stopped = usb_dwc_hal_pwr_clk_check_phy_clk_stopped(port->hal); + const bool hclk_gated = usb_dwc_hal_pwr_clk_check_hclk_gated(port->hal); + + // supend == phy_clk_stopped == hclk_gated + // When suspending, all 3 variables must be 1. When resuming, all 3 must be 0. + if ((suspend == phy_clk_stopped) && (suspend == hclk_gated)) { + return true; + } + return false; +} + // ---------------------- Commands ------------------------- static esp_err_t _port_cmd_power_on(port_t *port) @@ -1227,6 +1261,7 @@ static esp_err_t _port_cmd_power_on(port_t *port) port->state = HCD_PORT_STATE_DISCONNECTED; usb_dwc_hal_port_init(port->hal); usb_dwc_hal_port_toggle_power(port->hal, true); + //_suspend_phy_clk(port, false); // Un-gate the phy clock TODO: Test this ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; @@ -1240,6 +1275,7 @@ static esp_err_t _port_cmd_power_off(port_t *port) // Port can only be unpowered if already powered if (port->state != HCD_PORT_STATE_NOT_POWERED) { port->state = HCD_PORT_STATE_NOT_POWERED; + //_suspend_phy_clk(port, false); // Un-gate the phy clock TODO: Test this usb_dwc_hal_port_deinit(port->hal); usb_dwc_hal_port_toggle_power(port->hal, false); // If a device is currently connected, this should trigger a disconnect event @@ -1255,7 +1291,8 @@ static esp_err_t _port_cmd_reset(port_t *port) esp_err_t ret; // Port can only a reset when it is in the enabled or disabled (in the case of a new connection)states. - if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_DISABLED) { + // Or suspended, to exit suspended state through host initiated reset + if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_DISABLED && port->state != HCD_PORT_STATE_SUSPENDED) { ret = ESP_ERR_INVALID_STATE; goto exit; } @@ -1264,6 +1301,10 @@ static esp_err_t _port_cmd_reset(port_t *port) ret = ESP_ERR_INVALID_STATE; goto exit; } + // If resetting from suspended state, we must un-gate the internal clock + if (port->state == HCD_PORT_STATE_SUSPENDED) { + _suspend_phy_clk(port, false); + } /* Proceed to resetting the bus - Update the port's state variable @@ -1320,6 +1361,21 @@ static esp_err_t _port_cmd_reset(port_t *port) return ret; } +/** + * @brief Suspend the root port + * + * This sequence equals to a sequence from the DesignWare Cores USB 2.0 Programming Guide version 4.00a + * 14.2.3.3 External HCLK Gating When the Host Core is in Partial Power Down Mode + * Sequence Entering suspend state + * + * @param[in] port Pointer to the port object + * @return: + * - ESP_ERR_INVALID_STATE Port is not in a correct state to be suspended, or pipe(s) routed through this port is not halted + * - ESP_ERR_INVALID_RESPONSE Port state unexpectedly changed (for example: device was disconnected) + * - ESP_ERR_NOT_FINISHED Port did not finish the suspending sequence and is not in the suspended state + * - ESP_ERR_NOT_ALLOWED PHY clk was not suspended + * - ESP_OK Root port suspended + */ static esp_err_t _port_cmd_bus_suspend(port_t *port) { esp_err_t ret; @@ -1353,6 +1409,11 @@ static esp_err_t _port_cmd_bus_suspend(port_t *port) goto exit; } + if (! _suspend_phy_clk(port, true)) { + ret = ESP_ERR_NOT_ALLOWED; + goto exit; + } + port->state = HCD_PORT_STATE_SUSPENDED; ret = ESP_OK; @@ -1360,6 +1421,22 @@ static esp_err_t _port_cmd_bus_suspend(port_t *port) return ret; } +/** + * @brief Resume the root port + * + * This sequence equals to a sequence from the DesignWare Cores USB 2.0 Programming Guide version 4.00a + * 14.2.3.3 External HCLK Gating When the Host Core is in Partial Power Down Mode + * Sequence Exiting Suspend State Through Host Initiated Resume + * Exiting Suspend State Through Device Initiated Remote Wakeup + * + * @note this sequence is used for both resume scenarios: the host initiated and the device initiated (remote wakeup) resume + * @param[in] port Pointer to the port object + * @return: + * - ESP_ERR_INVALID_STATE Port is not in a correct state to be resumed + * - ESP_ERR_NOT_ALLOWED PHY clk was not resumed + * - ESP_ERR_INVALID_RESPONSE Port state unexpectedly changed (for example: device was disconnected) + * - ESP_OK Root port resumed + */ static esp_err_t _port_cmd_bus_resume(port_t *port) { esp_err_t ret; @@ -1368,6 +1445,12 @@ static esp_err_t _port_cmd_bus_resume(port_t *port) ret = ESP_ERR_INVALID_STATE; goto exit; } + + if (!_suspend_phy_clk(port, false)) { + ret = ESP_ERR_NOT_ALLOWED; + goto exit; + } + // Put and hold the bus in the K state. usb_dwc_hal_port_toggle_resume(port->hal, true); port->state = HCD_PORT_STATE_RESUMING; @@ -1485,6 +1568,7 @@ esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl) return ESP_OK; } + esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command) { esp_err_t ret = ESP_ERR_INVALID_STATE; diff --git a/host/usb/src/hub.c b/host/usb/src/hub.c index 817bada9..98f8fb9e 100644 --- a/host/usb/src/hub.c +++ b/host/usb/src/hub.c @@ -441,6 +441,17 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) break; } + case HCD_PORT_EVENT_REMOTE_WAKEUP: + // Root port, including all the connected devices were resumed (global resume) + // Clear all EPs and propagate the resumed event to clients + //usbh_devs_set_pm_actions_all(USBH_DEV_RESUME | USBH_DEV_RESUME_EVT); + + // Change Port state + //HUB_DRIVER_ENTER_CRITICAL(); + //p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED; + //HUB_DRIVER_EXIT_CRITICAL(); + hub_root_mark_resume(); + break; default: abort(); // Should never occur break; diff --git a/host/usb/src/usb_host.c b/host/usb/src/usb_host.c index 77ae271c..4e184d5b 100644 --- a/host/usb/src/usb_host.c +++ b/host/usb/src/usb_host.c @@ -486,7 +486,14 @@ static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, return yield; } -static void get_config_desc_transfer_cb(usb_transfer_t *transfer) +/** + * @brief Control transfer callback + * + * This callback is used in ctrl transfers for remote wakeup feature and multiconfig feature + * + * @param[in] transfer Pointer to ctrl transfer + */ +static void ctrl_transfer_cb(usb_transfer_t *transfer) { SemaphoreHandle_t transfer_done = (SemaphoreHandle_t)transfer->context; xSemaphoreGive(transfer_done); @@ -1333,7 +1340,9 @@ esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, con static usb_transfer_status_t wait_for_transmission_done(usb_transfer_t *transfer) { SemaphoreHandle_t transfer_done = (SemaphoreHandle_t)transfer->context; + esp_rom_printf("Transfer wait\n"); xSemaphoreTake(transfer_done, portMAX_DELAY); + esp_rom_printf("Transfer done\n"); usb_transfer_status_t status = transfer->status; // EP0 halt->flush->clear is managed by USBH and lower layers @@ -1409,7 +1418,7 @@ esp_err_t usb_host_get_config_desc(usb_host_client_handle_t client_hdl, usb_devi ctrl_transfer->device_handle = dev_hdl; ctrl_transfer->bEndpointAddress = 0; - ctrl_transfer->callback = get_config_desc_transfer_cb; + ctrl_transfer->callback = ctrl_transfer_cb; ctrl_transfer->context = (void *)transfer_done; // Initiate control transfer for short config descriptor @@ -1459,6 +1468,181 @@ esp_err_t usb_host_free_config_desc(const usb_config_desc_t *config_desc) return ESP_OK; } +/** + * @brief Do control transfer to get/set remote wakeup feature + * + * This function: allocates transfer, sets the transfer params, submits the transfer and evaluates it + * + * @note A control transfer is sent + * + * @param[in] client_hdl Handle of a client + * @param[in] dev_hdl Device handle + * @param[in] setup_packet Pointer to setup packet + * @param[in] data_buf Pointer to transfer data buffer + * @param[in] data_len Length of data + * + * @return + * - ESP_OK: Transfer successful + * - ESP_ERR_NO_MEM: Not enough memory + * - ESP_ERR_INVALID_STATE: Control transfer failed + * - ESP_ERR_INVALID_RESPONSE: Invalid number of bytest returned by IN transfer stage + */ +static esp_err_t remote_wake_do_control_transfer(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, + usb_setup_packet_t *setup_packet, void *data_buf, size_t data_len) +{ + esp_err_t ret; + usb_transfer_t *ctrl_transfer = NULL; + if (usb_host_transfer_alloc(sizeof(usb_setup_packet_t) + 512, 0, &ctrl_transfer) != ESP_OK) { + ret = ESP_ERR_NO_MEM; + goto transfer_alloc_error; + } + + SemaphoreHandle_t transfer_done = xSemaphoreCreateBinary(); + if (transfer_done == NULL) { + ret = ESP_ERR_NO_MEM; + goto semaphore_alloc_error; + } + + ctrl_transfer->device_handle = dev_hdl; + ctrl_transfer->bEndpointAddress = 0; + ctrl_transfer->callback = ctrl_transfer_cb; + ctrl_transfer->context = (void *)transfer_done; + + // Copy setup packet + memcpy(ctrl_transfer->data_buffer, setup_packet, sizeof(usb_setup_packet_t)); + + const usb_device_desc_t *dev_desc; + ESP_ERROR_CHECK(usbh_dev_get_desc(dev_hdl, &dev_desc)); + ctrl_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(data_len, dev_desc->bMaxPacketSize0); + const int expect_num_bytes = (data_len > 0) ? (sizeof(usb_setup_packet_t) + data_len) : (0); + + // Submit transfer + ret = usb_host_transfer_submit_control(client_hdl, ctrl_transfer); + if (ret != ESP_OK) { + ESP_LOGE(USB_HOST_TAG, "Submit ctrl transfer failed %s", esp_err_to_name(ret)); + goto transfer_error; + } + + // Wait for transfer to finish + const usb_transfer_status_t status_short_desc = wait_for_transmission_done(ctrl_transfer); + if (status_short_desc != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(USB_HOST_TAG, "Control transfer failed, status=%d", ctrl_transfer->status); + ret = ESP_ERR_INVALID_STATE; + goto transfer_error; + } + + // Check IN transfer returned the expected correct number of bytes + if ((expect_num_bytes != 0) && (ctrl_transfer->actual_num_bytes != expect_num_bytes)) { + if (ctrl_transfer->actual_num_bytes > expect_num_bytes) { + // The device returned more bytes than requested. + // This violates the USB specs chapter 9.3.5, but we can continue + ESP_LOGW(USB_HOST_TAG, "Incorrect number of bytes returned %d, %d expected", ctrl_transfer->actual_num_bytes, expect_num_bytes); + } else { + // The device returned less bytes than requested. We cannot continue. + ESP_LOGE(USB_HOST_TAG, "Incorrect number of bytes returned %d, %d expected", ctrl_transfer->actual_num_bytes, expect_num_bytes); + ret = ESP_ERR_INVALID_RESPONSE; + goto transfer_error; + } + } + + // For IN transfers, copy data back to caller buffer + if (data_buf && (data_len > 0)) { + memcpy(data_buf, ctrl_transfer->data_buffer + sizeof(usb_setup_packet_t), data_len); + } + + ret = ESP_OK; + +transfer_error: + vSemaphoreDelete(transfer_done); + +semaphore_alloc_error: + usb_host_transfer_free(ctrl_transfer); + +transfer_alloc_error: + return ret; +} + +/** + * @brief Validate device's remote wakeup capability + * + * This function: + * - checks, whether the device is opened by the client which is trying to modify it's remote wakeup settings + * - checks, whether the device supports remote wakeup capability from it's configuration descriptor + * + * @note No control transfer is sent + * + * @param[in] client_hdl Handle of a client + * @param[in] dev_hdl Device handle + * + * @return + * - ESP_OK: Device eligible for modifying remote wakeup settings + * - ESP_ERR_INVALID_STATE: Provided client did not open this device + * - ESP_ERR_NOT_ALLOWED: Device does not support remote wakeup + */ +static esp_err_t validate_device_remote_wakeup(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl) +{ + client_t *client_obj = (client_t *)client_hdl; + uint8_t dev_addr; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + + HOST_ENTER_CRITICAL(); + if (!_check_client_opened_device(client_obj, dev_addr)) { + // Client did not open this device, it can't enable/disable it's remote wake, return an error + HOST_EXIT_CRITICAL(); + ESP_LOGE(USB_HOST_TAG, "Client did not open this device"); + return ESP_ERR_INVALID_STATE; + } + HOST_EXIT_CRITICAL(); + + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK(usbh_dev_get_config_desc(dev_hdl, &config_desc)); + if (!(config_desc->bmAttributes & USB_BM_ATTRIBUTES_WAKEUP)) { + ESP_LOGE(USB_HOST_TAG, "Device does not support remote wakeup"); + return ESP_ERR_NOT_ALLOWED; + } + + return ESP_OK; +} + +esp_err_t usb_host_device_remote_wakeup_enable(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, bool enable) +{ + HOST_CHECK(client_hdl != NULL && dev_hdl != NULL, ESP_ERR_INVALID_ARG); + ESP_RETURN_ON_ERROR(validate_device_remote_wakeup(client_hdl, dev_hdl), USB_HOST_TAG, "Device remote wakeup validation failed"); + + usb_setup_packet_t setup_packet = {0}; + if (enable) { + ESP_LOGI(USB_HOST_TAG, "Enabling device remote wake-up"); + USB_SETUP_PACKET_INIT_SET_FEATURE(&setup_packet, DEVICE_REMOTE_WAKEUP); + } else { + ESP_LOGI(USB_HOST_TAG, "Disabling device remote wake-up"); + USB_SETUP_PACKET_INIT_CLEAR_FEATURE(&setup_packet, DEVICE_REMOTE_WAKEUP); + } + + // Send ctrl transfer enabling/disabling the remote wakeup + ESP_RETURN_ON_ERROR(remote_wake_do_control_transfer(client_hdl, dev_hdl, &setup_packet, NULL, 0), USB_HOST_TAG, "CTRL transfer failed"); + + return ESP_OK; +} + +esp_err_t usb_host_device_remote_wakeup_check(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, bool *enabled) +{ + HOST_CHECK(client_hdl != NULL && dev_hdl != NULL && enabled != NULL, ESP_ERR_INVALID_ARG); + ESP_RETURN_ON_ERROR(validate_device_remote_wakeup(client_hdl, dev_hdl), USB_HOST_TAG, "Device remote wakeup validation failed"); + + usb_setup_packet_t setup_packet = {0}; + usb_device_status_t device_status; + USB_SETUP_PACKET_INIT_GET_STATUS(&setup_packet); + + // Send ctrl transfer checking the remote wakeup + ESP_RETURN_ON_ERROR(remote_wake_do_control_transfer(client_hdl, dev_hdl, &setup_packet, &device_status, sizeof(device_status)), USB_HOST_TAG, "CTRL transfer failed"); + + // Check current status of remote wakeup + *enabled = device_status.remote_wakeup; + ESP_LOGI(USB_HOST_TAG, "Remote wakeup is currently %s", ((*enabled)) ? ("enabled") : ("disabled") ); + + return ESP_OK; +} + // ----------------------------------------------- Interface Functions ------------------------------------------------- // ----------------------- Private ------------------------- @@ -1880,7 +2064,7 @@ esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, // Check if the root port is suspended (global suspend) if (hub_root_is_suspended()) { // Root port is suspended at the time we are submitting a ctrl transfer - ESP_LOGD(USB_HOST_TAG, "Resuming the root port"); + ESP_LOGI(USB_HOST_TAG, "Resuming the root port"); ret = usb_host_lib_root_port_resume(); if (ret != ESP_OK) { diff --git a/host/usb/test/target_test/common/hcd_common.c b/host/usb/test/target_test/common/hcd_common.c index 685f7f1c..30c674fe 100644 --- a/host/usb/test/target_test/common/hcd_common.c +++ b/host/usb/test/target_test/common/hcd_common.c @@ -46,6 +46,7 @@ #define EVENT_QUEUE_LEN 5 #define ENUM_ADDR 1 // Device address to use for tests that enumerate the device #define ENUM_CONFIG 1 // Device configuration number to use for tests that enumerate the device +#define TRANSFER_MAX_BYTES 256 typedef struct { hcd_port_handle_t port_hdl; @@ -382,6 +383,8 @@ void test_hcd_free_urb(urb_t *urb) heap_caps_free(urb); } +// --------------------------------------------------- Enumeration ----------------------------------------------------- + uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe) { // We need to create a URB for the enumeration control transfers @@ -427,3 +430,74 @@ uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe) test_hcd_free_urb(urb); return ENUM_ADDR; } + +// ---------------------------------------------- Transfer submit ------------------------------------------------------ + +void test_hcd_remote_wake_enable(hcd_pipe_handle_t *default_pipe, urb_t *feature_urb, bool enable) +{ + feature_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); + feature_urb->transfer.context = URB_CONTEXT_VAL; + + if (enable) { + printf("Enabling device remote wake-up\n"); + // Initialize feature_urb with the set feature request to enable remote wakeup + USB_SETUP_PACKET_INIT_SET_FEATURE((usb_setup_packet_t *)feature_urb->transfer.data_buffer, DEVICE_REMOTE_WAKEUP); + } else { + printf("Disabling device remote wake-up\n"); + // Initialize feature_urb with the clear feature request to disable remote wakeup + USB_SETUP_PACKET_INIT_CLEAR_FEATURE((usb_setup_packet_t *)feature_urb->transfer.data_buffer, DEVICE_REMOTE_WAKEUP); + } + + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, feature_urb)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); + urb_t *urb = hcd_urb_dequeue(default_pipe); + TEST_ASSERT_EQUAL_MESSAGE(feature_urb, urb, "URB pointers not equal"); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed"); + TEST_ASSERT_EQUAL_MESSAGE(URB_CONTEXT_VAL, urb->transfer.context, "URB context not equal"); + + TEST_ASSERT_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); +} + +bool test_hcd_remote_wake_check(hcd_pipe_handle_t *default_pipe, urb_t *get_status_urb) +{ + // Initialize with a "Get status request" USB_SETUP_PACKET_INIT_GET_STATUS + get_status_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_status_t); + USB_SETUP_PACKET_INIT_GET_STATUS((usb_setup_packet_t *)get_status_urb->transfer.data_buffer); + get_status_urb->transfer.context = URB_CONTEXT_VAL; + + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, get_status_urb)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); + urb_t *urb = hcd_urb_dequeue(default_pipe); + TEST_ASSERT_EQUAL_MESSAGE(get_status_urb, urb, "URB pointers not equal"); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed"); + TEST_ASSERT_EQUAL_MESSAGE(URB_CONTEXT_VAL, urb->transfer.context, "URB context not equal"); + + TEST_ASSERT_EQUAL(sizeof(usb_setup_packet_t) + sizeof(usb_device_status_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + usb_device_status_t *device_status = (usb_device_status_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + + const bool remote_wake_enabled = device_status->remote_wakeup; + printf("Remote wake-up is currently %s\n", ((remote_wake_enabled)) ? ("enabled") : ("disabled") ); + return remote_wake_enabled; +} + +void test_hcd_ping_device(hcd_pipe_handle_t *default_pipe, urb_t *default_urb) +{ + default_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)default_urb->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); + default_urb->transfer.context = URB_CONTEXT_VAL; + + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, default_urb)); + test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); + urb_t *urb = hcd_urb_dequeue(default_pipe); + TEST_ASSERT_EQUAL_MESSAGE(default_urb, urb, "URB pointers not equal"); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed"); + TEST_ASSERT_EQUAL_MESSAGE(URB_CONTEXT_VAL, urb->transfer.context, "URB context not equal"); + + // We must have transmitted at least the setup packet, but device may return less than bytes requested + TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); + TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); + usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION, config_desc->bDescriptorType); +} diff --git a/host/usb/test/target_test/common/hcd_common.h b/host/usb/test/target_test/common/hcd_common.h index 5af2c818..62ec76f2 100644 --- a/host/usb/test/target_test/common/hcd_common.h +++ b/host/usb/test/target_test/common/hcd_common.h @@ -190,11 +190,40 @@ void test_hcd_free_urb(urb_t *urb); */ uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe); +// ---------------------------------------------- Transfer submit ------------------------------------------------------ + /** - * @brief Set endpoint descriptor + * @brief Enable remote wakeup feature on device * - * Set endpoint descriptor of the mock device with different wMaxPacketSize according to the connected device's speed + * This function enables or disables remote wakeup on device by sending set feature or clear feature requests * - * @param port_speed Port speed after the device is connected + * @note this function sends a control transfer to the device + * @param default_pipe The connected device's default pipe + * @param feature_urb A default_pipe's URB used for set feature / clear feature + * @param enable Enable/Disable remote wake-up + */ +void test_hcd_remote_wake_enable(hcd_pipe_handle_t *default_pipe, urb_t *feature_urb, bool enable); + +/** + * @brief Check if remote wakeup feature is currently enabled on device + * + * @note this function sends a control transfer to the device + * @param default_pipe The connected device's default pipe + * @param feature_urb A default_pipe's URB used for get status request + * @return True if remote wake-up is currently enabled + * False if remote wake-up is currently disabled + */ +bool test_hcd_remote_wake_check(hcd_pipe_handle_t *default_pipe, urb_t *get_status_urb); + +/** + * @brief Ping device to check whether the device is responsive or not + * + * Use this function to check whether it is possible to communicate with a device. + * For example after resuming a device, to check whether the device has been resumed correctly + * The function sends a get configuration descriptor request to a device, checking both, the IN and OUT transfers + * + * @note this function sends a control transfer to the device + * @param default_pipe The connected device's default pipe + * @param default_urb A default_pipe's URB used for get configuration descriptor request */ -void test_hcd_set_mock_msc_ep_descriptor(usb_speed_t port_speed); +void test_hcd_ping_device(hcd_pipe_handle_t *default_pipe, urb_t *default_urb); diff --git a/host/usb/test/target_test/hcd/main/test_hcd_port.c b/host/usb/test/target_test/hcd/main/test_hcd_port.c index 3f3a12fe..9b983023 100644 --- a/host/usb/test/target_test/hcd/main/test_hcd_port.c +++ b/host/usb/test/target_test/hcd/main/test_hcd_port.c @@ -146,10 +146,12 @@ Test port suspend and resume with active pipes - Trigger a port connection - Create a default pipe - Test that port can't be suspended with an active pipe + - Ping a device (send get configuration descriptor transfer) - Halt the default pipe after a short delay - Suspend the port - Resume the port - Check that all the pipe is still halted + - Ping a device - Cleanup default pipe - Trigger disconnection and teardown */ @@ -160,6 +162,8 @@ TEST_CASE("Test HCD port suspend and resume", "[port][low_speed][full_speed][hig // Allocate some URBs and initialize their data buffers with control transfers hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); // Create a default pipe (using a NULL EP descriptor) + urb_t *urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + test_hcd_ping_device(default_pipe, urb); // Test that suspending the port now fails as there is an active pipe TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); @@ -185,6 +189,8 @@ TEST_CASE("Test HCD port suspend and resume", "[port][low_speed][full_speed][hig TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); vTaskDelay(pdMS_TO_TICKS(100)); // Give some time for resumed URBs to complete + test_hcd_ping_device(default_pipe, urb); + test_hcd_free_urb(urb); test_hcd_pipe_free(default_pipe); // Cleanup test_hcd_wait_for_disconn(port_hdl, false); @@ -197,17 +203,20 @@ Test port suspend and resume sudden disconnect - Test port suspend and resume procedure with sudden disconnect - When suspended, the port must react to power off command by disconnecting a device - When no device is connected, the port must NOT allow to enter and exit the suspended state + - Test exiting suspend state Through device disconnect Procedure: - Setup the HCD and a port - Trigger a port connection - Create a default pipe + - Ping a device (send get configuration descriptor transfer) - Halt the default pipe after a short delay - Suspend the port - - Cleanup default pipe - Power off the port and recover the port - Try to Suspend and Resume the port with no devices connected - - Trigger connection and disconnection again (to make sure the port works post recovery) + - Trigger connection again (to make sure the port works post recovery) + - Ping a device + - Trigger disconnection and cleanup - Teardown port and HCD */ TEST_CASE("Test HCD port suspend and resume sudden disconnect", "[port][low_speed][full_speed][high_speed]") @@ -217,9 +226,8 @@ TEST_CASE("Test HCD port suspend and resume sudden disconnect", "[port][low_spee // Allocate some URBs and initialize their data buffers with control transfers hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); // Create a default pipe (using a NULL EP descriptor) - - // Test that suspending the port now fails as there is an active pipe - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); + urb_t *urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + test_hcd_ping_device(default_pipe, urb); // Halt the default pipe before suspending TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); @@ -240,7 +248,8 @@ TEST_CASE("Test HCD port suspend and resume sudden disconnect", "[port][low_spee TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); printf("Sudden disconnect\n"); - // Free the pipe, to be able to recover the port + // Free the pipe and it's urb, to be able to recover the port + test_hcd_free_urb(urb); test_hcd_pipe_free(default_pipe); // Recover the port should return to the NOT POWERED state TEST_ASSERT_EQUAL(ESP_OK, hcd_port_recover(port_hdl)); @@ -250,8 +259,76 @@ TEST_CASE("Test HCD port suspend and resume sudden disconnect", "[port][low_spee TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); - // Recovered port should be able to connect and disconnect again - test_hcd_wait_for_conn(port_hdl); + // Recovered port should be able to connect again + port_speed = test_hcd_wait_for_conn(port_hdl); + vTaskDelay(pdMS_TO_TICKS(100)); // Short delay send of SOF (for FS, HS) or EOPs (for LS) + + // Allocate some URBs and initialize their data buffers with control transfers + default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); // Create a default pipe (using a NULL EP descriptor) + urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + test_hcd_ping_device(default_pipe, urb); + + test_hcd_free_urb(urb); + test_hcd_pipe_free(default_pipe); + test_hcd_wait_for_disconn(port_hdl, false); +} + +/* +Test port suspend and resume with port reset + +Purpose: + - Test port suspend and resume procedure with port reset + - When suspended, the port must react to reset command + - Test exiting suspend state Through port reset + +Procedure: + - Setup the HCD and a port + - Trigger a port connection + - Create a default pipe + - Ping a device (send get configuration descriptor transfer) + - Halt the default pipe + - Suspend the port + - Reset and recover the port + - Ping a device + - Trigger disconnection and cleanup + - Teardown port and HCD +*/ +TEST_CASE("Test HCD port suspend and resume port reset", "[port][low_speed][full_speed][high_speed]") +{ + usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); // Trigger a connection + vTaskDelay(pdMS_TO_TICKS(100)); // Short delay send of SOF (for FS, HS) or EOPs (for LS) + + // Allocate some URBs and initialize their data buffers with control transfers + hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); // Create a default pipe (using a NULL EP descriptor) + urb_t *urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + test_hcd_ping_device(default_pipe, urb); + + // Halt the default pipe before suspending + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + + // Suspend the port + TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); + TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl)); + printf("Suspended\n"); + vTaskDelay(pdMS_TO_TICKS(100)); // Give some time for bus to remain suspended + + // Reset the port + TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESET)); + printf("Port reset\n"); + + // Port should be in enabled state, and the default pipe still halted + TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + // Clear the pipe + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + + test_hcd_ping_device(default_pipe, urb); + + test_hcd_free_urb(urb); + test_hcd_pipe_free(default_pipe); test_hcd_wait_for_disconn(port_hdl, false); } @@ -392,3 +469,56 @@ TEST_CASE("Test HCD port command bailout", "[port][low_speed][full_speed][high_s vTaskDelete(task_handle); vSemaphoreDelete(sync_sem); } + +TEST_CASE("Test HCD port remote wakeup", "[port][low_speed][full_speed][high_speed]") +{ + usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); // Trigger a connection + vTaskDelay(pdMS_TO_TICKS(100)); // Short delay send of SOF (for FS) or EOPs (for LS) + + hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); // Create a default pipe (using a NULL EP descriptor) + uint8_t dev_addr = test_hcd_enum_device(default_pipe); + + urb_t *get_status_urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + urb_t *set_feature_urb = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); + + // Enable and check remote wake-up feature + TEST_ASSERT_EQUAL_MESSAGE(false, test_hcd_remote_wake_check(default_pipe, get_status_urb), "Remote wake-up is enabled, but expected to be disabled"); + test_hcd_remote_wake_enable(default_pipe, set_feature_urb, true); + TEST_ASSERT_EQUAL_MESSAGE(true, test_hcd_remote_wake_check(default_pipe, get_status_urb), "Remote wake-up is disabled, but expected to be enabled"); + + // Halt the default pipe before suspending + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + + // Suspend the port + TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); + TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl)); + printf("Root port suspended\n"); + + // Expect remote wakeup event from the device + test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_REMOTE_WAKEUP); + TEST_ASSERT_EQUAL(HCD_PORT_EVENT_REMOTE_WAKEUP, hcd_port_handle_event(port_hdl)); + printf("Remote wake-up detected\n"); + + // Resume the port + TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); + TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); + printf("Resumed\n"); + + //vTaskDelay(pdMS_TO_TICKS(5000)); + + // Clear the pipe after the port has been resumed + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); + TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); + + // Check the current wake-up status, (issue transfer to ping the device) + TEST_ASSERT_EQUAL_MESSAGE(true, test_hcd_remote_wake_check(default_pipe, get_status_urb), "Remote wake-up is disabled, but expected to be enabled"); + + // Cleanup + test_hcd_pipe_free(default_pipe); + test_hcd_free_urb(get_status_urb); + test_hcd_free_urb(set_feature_urb); + test_hcd_wait_for_disconn(port_hdl, false); +} diff --git a/host/usb/test/target_test/usb_host/main/remote_wake_client.c b/host/usb/test/target_test/usb_host/main/remote_wake_client.c new file mode 100644 index 00000000..5f927327 --- /dev/null +++ b/host/usb/test/target_test/usb_host/main/remote_wake_client.c @@ -0,0 +1,200 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "remote_wake_client.h" +#include "usb/usb_host.h" +#include "unity.h" + +/* +Implementation of remote wakeup client used for USB Host tests + +- The remote wakeup client will: + - Register itself as a client + - Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device + - Start handling client events + - Wait for a request from main task to: + - read and check current status of the remote wakeup feature from the device + - enable remote wakeup feature on the device + - read and check current status of the remote wakeup feature from the device + - disable remote wakeup feature on the device + - read and check current status of the remote wakeup feature from the device + - Close the device, deregister client +*/ + +const char *REMOTE_WAKE_CLIENT_TAG = "Remote wakeup Client"; + +#define CLIENT_NUM_EVENT_MSG 5 + +/** + * @brief Test stages of this test + */ +typedef enum { + TEST_STAGE_WAIT_CONN, /**< Wait for device connection */ + TEST_STAGE_DEV_OPEN, /**< Open device */ + TEST_STAGE_REMOTE_WAKE_SETUP, /**< Setup remote wakeup (enable, disable, check) */ + TEST_STAGE_DEV_CLOSE, /**< Close device */ +} test_stage_t; + +/** + * @brief Remote wakeup client object + */ +typedef struct { + remote_wake_client_test_param_t test_param; /**< Test params for the main task */ + uint8_t dev_addr; /**< Connected device address */ + usb_device_handle_t dev_hdl; /**< Connected device handle */ + usb_host_client_handle_t client_hdl; /**< Remote wake client handle */ + test_stage_t cur_stage; /**< Current test stage from test_stage_t */ + test_stage_t next_stage; /**< Next test stage from test_stage_t */ +} remote_wake_client_obj_t; + +static remote_wake_client_obj_t *s_remote_wake_obj; + +static void remote_wake_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + remote_wake_client_obj_t *remote_wake_obj = (remote_wake_client_obj_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + printf("\t-> New device\n"); + TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, remote_wake_obj->cur_stage); + remote_wake_obj->next_stage = TEST_STAGE_DEV_OPEN; + remote_wake_obj->dev_addr = event_msg->new_dev.address; + break; + case USB_HOST_CLIENT_EVENT_DEV_SUSPENDED: + printf("\t-> Device suspended\n"); + break; + case USB_HOST_CLIENT_EVENT_DEV_RESUMED: + printf("\t-> Device resumed\n"); + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + printf("\t-> Device gone\n"); + break; + default: + TEST_FAIL_MESSAGE("Unrecognized client event"); + break; + } +} + +void remote_wake_client_async_task(void *arg) +{ + remote_wake_client_obj_t remote_wake_obj; + // Initialize test params + memcpy(&remote_wake_obj.test_param, arg, sizeof(remote_wake_client_test_param_t)); + // Initialize client variables + remote_wake_obj.client_hdl = NULL; + remote_wake_obj.dev_hdl = NULL; + // Initialize test stage + remote_wake_obj.cur_stage = TEST_STAGE_WAIT_CONN; + remote_wake_obj.next_stage = TEST_STAGE_WAIT_CONN; + remote_wake_obj.dev_addr = 0; + + // Register client + usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = CLIENT_NUM_EVENT_MSG, + .async = { + .client_event_callback = remote_wake_client_event_cb, + .callback_arg = (void *) &remote_wake_obj, + }, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &remote_wake_obj.client_hdl)); + s_remote_wake_obj = &remote_wake_obj; + + // Wait to be started by main thread + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)), "Main task did not start on time"); + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Starting"); + + // Handle device enumeration separately, wait for 1000ms for the device to be enumerated + // Catch an error in case the device is not enumerated correctly + esp_err_t enum_ret = usb_host_client_handle_events(remote_wake_obj.client_hdl, pdMS_TO_TICKS(2000)); + TEST_ASSERT_EQUAL_MESSAGE(TEST_STAGE_DEV_OPEN, remote_wake_obj.next_stage, "USB_HOST_CLIENT_EVENT_NEW_DEV not generated on time"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, enum_ret, "Client handle events timed out"); + + bool exit_loop = false; + bool skip_event_handling = true; // Skip first event handling (we have handled the new device event separately) + while (!exit_loop) { + if (!skip_event_handling) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(remote_wake_obj.client_hdl, portMAX_DELAY)); + } + skip_event_handling = false; + if (remote_wake_obj.cur_stage == remote_wake_obj.next_stage) { + continue; + } + remote_wake_obj.cur_stage = remote_wake_obj.next_stage; + + switch (remote_wake_obj.next_stage) { + case TEST_STAGE_DEV_OPEN: { + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Open"); + // Open the device + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, usb_host_device_open(remote_wake_obj.client_hdl, remote_wake_obj.dev_addr, &remote_wake_obj.dev_hdl), "Failed to open the device"); + + remote_wake_obj.next_stage = TEST_STAGE_REMOTE_WAKE_SETUP; + skip_event_handling = true; + break; + } + case TEST_STAGE_REMOTE_WAKE_SETUP: { + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Device ready"); + // Give semaphore signalizing that the device has been opened + xSemaphoreGive(remote_wake_obj.test_param.dev_ready_smp); + break; + } + case TEST_STAGE_DEV_CLOSE: { + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Close"); + vTaskDelay(10); // Give USB Host Lib some time to process all transfers + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(remote_wake_obj.client_hdl, remote_wake_obj.dev_hdl)); + exit_loop = true; + break; + } + default: + abort(); + break; + } + } + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(remote_wake_obj.client_hdl)); + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Done"); + vTaskDelete(NULL); +} + +void test_remote_wake_finish(void) +{ + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "finish test"); + // Go to next stage + s_remote_wake_obj->next_stage = TEST_STAGE_DEV_CLOSE; + ESP_ERROR_CHECK(usb_host_client_unblock(s_remote_wake_obj->client_hdl)); +} + +/** + * Following functions must not be called from the usb host client task, because a control transfer is sent by those functions + */ + +void test_remote_wake_enable(void) +{ + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Remote wake enable"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_remote_wakeup_enable(s_remote_wake_obj->client_hdl, s_remote_wake_obj->dev_hdl, true)); + xSemaphoreGive(s_remote_wake_obj->test_param.dev_ready_smp); +} + +void test_remote_wake_disable(void) +{ + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Remote wake disable"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_remote_wakeup_enable(s_remote_wake_obj->client_hdl, s_remote_wake_obj->dev_hdl, false)); + xSemaphoreGive(s_remote_wake_obj->test_param.dev_ready_smp); +} + +void test_remote_wake_check(bool expected_remote_wake) +{ + ESP_LOGI(REMOTE_WAKE_CLIENT_TAG, "Remote wake check"); + bool remote_wake_enabled = false; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_remote_wakeup_check(s_remote_wake_obj->client_hdl, s_remote_wake_obj->dev_hdl, &remote_wake_enabled)); + TEST_ASSERT_EQUAL_MESSAGE(expected_remote_wake, remote_wake_enabled, "Expected remote wakeup status not equal to the current status"); + xSemaphoreGive(s_remote_wake_obj->test_param.dev_ready_smp); +} diff --git a/host/usb/test/target_test/usb_host/main/remote_wake_client.h b/host/usb/test/target_test/usb_host/main/remote_wake_client.h new file mode 100644 index 00000000..00c65a4e --- /dev/null +++ b/host/usb/test/target_test/usb_host/main/remote_wake_client.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/** + * @brief Remote wakeup client test parameters + */ +typedef struct { + SemaphoreHandle_t dev_ready_smp; /**< Device ready state semaphore */ +} remote_wake_client_test_param_t; + +/** + * @brief Remote wake-up client task + */ +void remote_wake_client_async_task(void *arg); + +/** + * @brief Enable remote wakeup on the device + * @note A control transfer (SET_FEATURE) is sent to device + * @note This function must not be called from the same task as the client task, as a control transfer is sent + */ +void test_remote_wake_enable(void); + +/** + * @brief Disable remote wakeup on the device + * @note A control transfer (CLEAR_FEATURE) is sent to device + * @note This function must not be called from the same task as the client task, as a control transfer is sent + */ +void test_remote_wake_disable(void); + +/** + * @brief Get current remote wakeup state from the device + * @note A control transfer (GET_STATUS) is sent to device + * @note This function must not be called from the same task as the client task, as a control transfer is sent + * + * @param[in] expected_remote_wake: Expected current state of the remote wakeup feature on the device + */ +void test_remote_wake_check(bool expected_remote_wake); + +/** + * @brief Finish the test + * + * Close the device, deregister the client.. + */ +void test_remote_wake_finish(void); diff --git a/host/usb/test/target_test/usb_host/main/test_usb_host_async.c b/host/usb/test/target_test/usb_host/main/test_usb_host_async.c index e9067974..9ece3ea0 100644 --- a/host/usb/test/target_test/usb_host/main/test_usb_host_async.c +++ b/host/usb/test/target_test/usb_host/main/test_usb_host_async.c @@ -15,6 +15,7 @@ #include "msc_client.h" #include "ctrl_client.h" #include "multiconf_client.h" +#include "remote_wake_client.h" #include "usb/usb_host.h" #include "unity.h" @@ -298,6 +299,8 @@ Test USB Host Asynchronous API single client - Free all devices - Uninstall USB Host Library */ +#define TEST_MULTICONFIG_SMP_WAIT_MS 2000 + static void host_lib_task(void *arg) { TaskHandle_t pending_task = (TaskHandle_t)arg; @@ -323,6 +326,7 @@ static void host_lib_task(void *arg) TEST_CASE("Test USB Host multiconfig client (single client)", "[usb_host][full_speed][high_speed]") { SemaphoreHandle_t dev_open_smp = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL_MESSAGE(dev_open_smp, "Failed to create semaphore"); TaskHandle_t client_task; multiconf_client_test_param_t multiconf_params = { @@ -342,10 +346,14 @@ TEST_CASE("Test USB Host multiconfig client (single client)", "[usb_host][full_s TEST_ASSERT_NOT_NULL_MESSAGE(host_lib_task_hdl, "Failed to create host lib task"); // Wait for the device to be open - xSemaphoreTake(dev_open_smp, portMAX_DELAY); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_open_smp, TEST_MULTICONFIG_SMP_WAIT_MS), "Device not opened on time"); + multiconf_client_get_conf_desc(); + // Wait for the host library task to finish - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, ulTaskNotifyTake(pdTRUE, TEST_MULTICONFIG_SMP_WAIT_MS), "usb_host_lib task not finished on time"); // Cleanup vSemaphoreDelete(dev_open_smp); } @@ -410,9 +418,6 @@ Test USB Host Resume by transfer submit - Free all devices - Uninstall USB Host Library */ - -#define TEST_MSC_AUTO_RESUME_TRANSFERS 2 - TEST_CASE("Test USB Host resume by submit transfer", "[usb_host][full_speed][high_speed]") { // Create task to run client that communicates with MSC SCSI interface @@ -636,3 +641,91 @@ TEST_CASE("Test USB Host suspend/resume multiple tasks access (no client)", "[us // Wait for the host library task to finish TEST_ASSERT_MESSAGE(ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)), "usb host lib task did not finish"); } + +/* +Test: +- Enabling/Disabling of the remote wakeup feature on the device +- Reading the current state of the remote wakeup from the device + +Purpose: +- Test CLEAR_FEATURE, SET_FEATURE and GET_STATUS request types +- Test usb_host public API calls related to remote wakeup setup + +Procedure: + - Install USB Host Library + - Read the current remote wakeup state from the device + - Enable the remote wakeup on the device -> Read the current remote wakeup state from the device + - Disable the remote wakeup on the device -> Read the current remote wakeup state from the device + - Teardown +*/ +#define TEST_REMOTE_WAKE_SMP_WAIT_MS 2000 + +TEST_CASE("Test USB Host remote wakeup setup", "[usb_host][low_speed][full_speed][high_speed]") +{ + SemaphoreHandle_t dev_ready_smp = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL_MESSAGE(dev_ready_smp, "Failed to create semaphore"); + TaskHandle_t client_task; + + remote_wake_client_test_param_t multiconf_params = { + .dev_ready_smp = dev_ready_smp, + }; + + xTaskCreatePinnedToCore(remote_wake_client_async_task, "remote_wake client", 4096, (void *)&multiconf_params, 2, &client_task, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(client_task, "Failed to create async client task"); + // Start the task + xTaskNotifyGive(client_task); + + TaskHandle_t host_lib_task_hdl; + // Get Current task handle + TaskHandle_t pending_task = xTaskGetCurrentTaskHandle(); + xTaskCreatePinnedToCore(host_lib_task, "host lib", 4096, (void *)pending_task, 2, &host_lib_task_hdl, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(host_lib_task_hdl, "Failed to create host lib task"); + + // Wait for the device to be open + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Device not opened on time"); + + // Read the current remote wakeup state from the device, expect it to be disabled (default after device reset) + test_remote_wake_check(false); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not checked on time"); + + // Enable remote wakeup on the device + test_remote_wake_enable(); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not enabled on time"); + + // Read the current remote wakeup state from the device, expect it to be enabled from the previous operation + test_remote_wake_check(true); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not checked on time"); + +// printf("Remote wake enabled\n"); + usb_host_lib_root_port_suspend(); + vTaskDelay(pdMS_TO_TICKS(5000)); +// usb_host_lib_root_port_resume(); + + +// // Read the current remote wakeup state from the device, expect it to be enabled from the previous operation +// test_remote_wake_check(true); +// TEST_ASSERT_EQUAL_MESSAGE( +// pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not checked on time"); + + // Disable remote wakeup on the device + test_remote_wake_disable(); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not disabled on time"); +// +// // Read the current remote wakeup state from the device, expect it to be disabled from the previous operation +// test_remote_wake_check(false); +// TEST_ASSERT_EQUAL_MESSAGE( +// pdTRUE, xSemaphoreTake(dev_ready_smp, TEST_REMOTE_WAKE_SMP_WAIT_MS), "Remote wake not checked on time"); + + // Test teardown + test_remote_wake_finish(); + TEST_ASSERT_EQUAL_MESSAGE( + pdTRUE, ulTaskNotifyTake(pdTRUE, TEST_REMOTE_WAKE_SMP_WAIT_MS), "usb_host_lib task not finished on time"); + + // Cleanup + vSemaphoreDelete(dev_ready_smp); +}