Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions host/usb/include/usb/usb_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 -------------------------------------------------

/**
Expand Down
41 changes: 40 additions & 1 deletion host/usb/include/usb/usb_types_ch9.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; \
Expand All @@ -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
*
Expand Down
1 change: 1 addition & 0 deletions host/usb/private_include/hcd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
} hcd_port_event_t;

/**
Expand Down
10 changes: 10 additions & 0 deletions host/usb/src/hcd_dwc.c
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,12 @@
port->flags.conn_dev_ena = 0;
break;
}
case USB_DWC_HAL_PORT_EVENT_REMOTE_WAKEUP: {

Check failure

Code scanning / clang-tidy

duplicate case value: 'USB_DWC_HAL_PORT_EVENT_DISABLED' and 'HCD_PORT_EVENT_REMOTE_WAKEUP' both equal '5' [clang-diagnostic-error] Error

duplicate case value: 'USB_DWC_HAL_PORT_EVENT_DISABLED' and 'HCD_PORT_EVENT_REMOTE_WAKEUP' both equal '5' [clang-diagnostic-error]

Check failure

Code scanning / clang-tidy

use of undeclared identifier 'USB_DWC_HAL_PORT_EVENT_REMOTE_WAKEUP'; did you mean 'HCD_PORT_EVENT_REMOTE_WAKEUP'? [clang-diagnostic-error] Error

use of undeclared identifier 'USB_DWC_HAL_PORT_EVENT_REMOTE_WAKEUP'; did you mean 'HCD_PORT_EVENT_REMOTE_WAKEUP'? [clang-diagnostic-error]
ESP_EARLY_LOGI(HCD_DWC_TAG, "WAKE");
port->state = HCD_PORT_STATE_ENABLED;
port_event = HCD_PORT_EVENT_REMOTE_WAKEUP;
break;
}
default: {
abort();
break;
Expand Down Expand Up @@ -1368,6 +1374,7 @@
ret = ESP_ERR_INVALID_STATE;
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;
Expand Down Expand Up @@ -1485,8 +1492,11 @@
return ESP_OK;
}

static port_t *s_port;

esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command)
{
s_port = (port_t *)port_hdl;
esp_err_t ret = ESP_ERR_INVALID_STATE;
port_t *port = (port_t *)port_hdl;
xSemaphoreTake(port->port_mux, portMAX_DELAY);
Expand Down
12 changes: 12 additions & 0 deletions host/usb/src/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,18 @@ 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);
//esp_rom_printf("p_hub_driver_obj->dynamic.root_port_state = %d\n", p_hub_driver_obj->dynamic.root_port_state);

// 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;
Expand Down
190 changes: 187 additions & 3 deletions host/usb/src/usb_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,14 @@
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);
Expand Down Expand Up @@ -1333,7 +1340,9 @@
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
Expand Down Expand Up @@ -1409,7 +1418,7 @@

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
Expand Down Expand Up @@ -1459,6 +1468,181 @@
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));

Check warning

Code scanning / clang-tidy

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]

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);

Check warning

Code scanning / clang-tidy

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
}

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 -------------------------
Expand Down Expand Up @@ -1880,7 +2064,7 @@
// 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) {
Expand Down
Loading
Loading