Skip to content

Commit a31b56e

Browse files
feat(hid_host): Add support for global suspend/resume
- usb_host lib supports global suspend and resume - backward compatibility with older IDF releases - HID Host target tests
1 parent 5d21781 commit a31b56e

File tree

5 files changed

+780
-29
lines changed

5 files changed

+780
-29
lines changed

host/class/hid/usb_host_hid/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added
1313

1414
- Added a limitation for the HID report descriptor size to a maximum of 2048 bytes
15+
- Added global suspend/resume support
1516

1617
## [1.0.4] - 2025-09-24
1718

host/class/hid/usb_host_hid/hid_host.c

Lines changed: 217 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ typedef enum {
9696
HID_INTERFACE_STATE_READY, /**< HID Interface opened and ready to start transfer */
9797
HID_INTERFACE_STATE_ACTIVE, /**< HID Interface is in use */
9898
HID_INTERFACE_STATE_WAIT_USER_DELETION, /**< HID Interface wait user to be removed */
99+
HID_INTERFACE_STATE_SUSPENDED, /**< HID Interface (and the whole device) is suspended */
99100
HID_INTERFACE_STATE_MAX
100101
} hid_iface_state_t;
101102

@@ -116,6 +117,7 @@ typedef struct hid_interface {
116117
hid_host_interface_event_cb_t user_cb; /**< Interface application callback */
117118
void *user_cb_arg; /**< Interface application callback arg */
118119
hid_iface_state_t state; /**< Interface state */
120+
hid_iface_state_t last_state; /**< Interface last state before entering suspended mode */
119121
} hid_iface_t;
120122

121123
/**
@@ -555,6 +557,179 @@ static esp_err_t hid_host_device_disconnected(usb_device_handle_t dev_hdl)
555557
return ESP_OK;
556558
}
557559

560+
#ifdef HID_HOST_SUSPEND_RESUME_API_SUPPORTED
561+
562+
/**
563+
* @brief Suspend interface
564+
*
565+
* @note endpoints are already halted and flushed when a global suspend is issues by the USB Host lib
566+
* @param[in] iface HID interface handle
567+
* @param[in] stop_ep Stop (halt and flush) endpoint
568+
*
569+
* @return esp_err_t
570+
*/
571+
static esp_err_t hid_host_suspend_interface(hid_iface_t *iface, bool stop_ep)
572+
{
573+
HID_RETURN_ON_INVALID_ARG(iface);
574+
HID_RETURN_ON_INVALID_ARG(iface->parent);
575+
576+
HID_RETURN_ON_FALSE(is_interface_in_list(iface),
577+
ESP_ERR_NOT_FOUND,
578+
"Interface handle not found");
579+
580+
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_SUSPENDED != iface->state),
581+
ESP_ERR_INVALID_STATE,
582+
"Interface wrong state");
583+
584+
// EP is usually stopped by usb_host_lib, in case of global suspend, thus no need to Halt->Flush EP again
585+
if (stop_ep) {
586+
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
587+
"Unable to HALT EP");
588+
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
589+
"Unable to FLUSH EP");
590+
// Don't clear EP, it must remain halted, when the device is in suspended state
591+
}
592+
593+
iface->last_state = iface->state;
594+
iface->state = HID_INTERFACE_STATE_SUSPENDED;
595+
596+
return ESP_OK;
597+
}
598+
599+
/**
600+
* @brief Resume interface
601+
*
602+
* @note endpoints are already cleared when a global resume is issues by the USB Host lib
603+
* @param[in] iface HID interface handle
604+
* @param[in] resume_ep Resume (clear) endpoint
605+
*
606+
* @return esp_err_t
607+
*/
608+
static esp_err_t hid_host_resume_interface(hid_iface_t *iface, bool resume_ep)
609+
{
610+
HID_RETURN_ON_INVALID_ARG(iface);
611+
HID_RETURN_ON_INVALID_ARG(iface->parent);
612+
613+
HID_RETURN_ON_FALSE(is_interface_in_list(iface),
614+
ESP_ERR_NOT_FOUND,
615+
"Interface handle not found");
616+
617+
if (HID_INTERFACE_STATE_ACTIVE == iface->state) {
618+
// Interface already auto-resumed by hid_host_device_start(), return early and continue to resume event delivery
619+
return ESP_OK;
620+
}
621+
622+
HID_RETURN_ON_FALSE ((HID_INTERFACE_STATE_SUSPENDED == iface->state),
623+
ESP_ERR_INVALID_STATE,
624+
"Interface wrong state");
625+
626+
// EP is usually cleared by usb_host_lib, in case of global suspend, thus no need to Clear an EP again
627+
if (resume_ep) {
628+
usb_host_endpoint_clear(iface->parent->dev_hdl, iface->ep_in);
629+
}
630+
631+
// Use the last device state before the device went to suspended state as the current state
632+
iface->state = iface->last_state;
633+
634+
if (iface->in_xfer == NULL) {
635+
return ESP_OK;
636+
}
637+
638+
// If the last state before the device went to suspended state was active state, start the data transfer
639+
if (iface->last_state == HID_INTERFACE_STATE_ACTIVE) {
640+
// start data transfer
641+
HID_RETURN_ON_ERROR( usb_host_transfer_submit(iface->in_xfer), "Unable to start data transfer");
642+
}
643+
644+
return ESP_OK;
645+
}
646+
647+
/**
648+
* @brief Suspend device
649+
*
650+
* Go through list, suspend all devices and deliver suspend events
651+
*
652+
* @param[in] dev_hdl USB Device handle
653+
*
654+
* @return esp_err_t
655+
*/
656+
static esp_err_t hid_host_device_suspended(usb_device_handle_t dev_hdl)
657+
{
658+
hid_device_t *hid_device = get_hid_device_by_handle(dev_hdl);
659+
HID_RETURN_ON_INVALID_ARG(hid_device);
660+
661+
HID_ENTER_CRITICAL();
662+
hid_iface_t *hid_iface_curr;
663+
hid_iface_t *hid_iface_next;
664+
// Go through list
665+
hid_iface_curr = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq);
666+
while (hid_iface_curr != NULL) {
667+
hid_iface_next = STAILQ_NEXT(hid_iface_curr, tailq_entry);
668+
HID_EXIT_CRITICAL();
669+
670+
if (hid_iface_curr->parent && (hid_iface_curr->parent->dev_addr == hid_device->dev_addr)) {
671+
esp_err_t ret = hid_host_suspend_interface(hid_iface_curr, false);
672+
673+
// Make sure the device is connected and the interface is found otherwise don't deliver suspend event
674+
if (ret != ESP_ERR_NOT_FOUND) {
675+
676+
// We will deliver the suspend event, if the hid_host_suspend_interface fails with other errors,
677+
// as the usb_host_lib has already suspended the root port anyway
678+
hid_host_user_interface_callback(hid_iface_curr, HID_HOST_INTERFACE_EVENT_SUSPENDED);
679+
}
680+
}
681+
HID_ENTER_CRITICAL();
682+
hid_iface_curr = hid_iface_next;
683+
}
684+
HID_EXIT_CRITICAL();
685+
686+
return ESP_OK;
687+
}
688+
689+
/**
690+
* @brief Resume device
691+
*
692+
* Go through list, resume all devices and deliver resume events
693+
*
694+
* @param[in] dev_hdl USB Device handle
695+
*
696+
* @return esp_err_t
697+
*/
698+
static esp_err_t hid_host_device_resumed(usb_device_handle_t dev_hdl)
699+
{
700+
hid_device_t *hid_device = get_hid_device_by_handle(dev_hdl);
701+
HID_RETURN_ON_INVALID_ARG(hid_device);
702+
703+
HID_ENTER_CRITICAL();
704+
hid_iface_t *hid_iface_curr;
705+
hid_iface_t *hid_iface_next;
706+
// Go through list
707+
hid_iface_curr = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq);
708+
while (hid_iface_curr != NULL) {
709+
hid_iface_next = STAILQ_NEXT(hid_iface_curr, tailq_entry);
710+
HID_EXIT_CRITICAL();
711+
712+
if (hid_iface_curr->parent && (hid_iface_curr->parent->dev_addr == hid_device->dev_addr)) {
713+
esp_err_t ret = hid_host_resume_interface(hid_iface_curr, false);
714+
715+
// Make sure the device is connected and the interface is found otherwise don't deliver resume event
716+
if (ret != ESP_ERR_NOT_FOUND) {
717+
718+
// We will deliver the resume event, if the hid_host_resume_interface fails with other errors,
719+
// as the usb_host_lib has already resumed the root port anyway
720+
hid_host_user_interface_callback(hid_iface_curr, HID_HOST_INTERFACE_EVENT_RESUMED);
721+
}
722+
}
723+
HID_ENTER_CRITICAL();
724+
hid_iface_curr = hid_iface_next;
725+
}
726+
HID_EXIT_CRITICAL();
727+
728+
return ESP_OK;
729+
}
730+
731+
#endif // HID_HOST_SUSPEND_RESUME_API_SUPPORTED
732+
558733
/**
559734
* @brief USB Host Client's event callback
560735
*
@@ -563,10 +738,28 @@ static esp_err_t hid_host_device_disconnected(usb_device_handle_t dev_hdl)
563738
*/
564739
static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg)
565740
{
566-
if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
741+
switch (event->event) {
742+
case USB_HOST_CLIENT_EVENT_NEW_DEV:
743+
ESP_LOGD(TAG, "New device connected");
567744
hid_host_device_init_attempt(event->new_dev.address);
568-
} else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
745+
break;
746+
case USB_HOST_CLIENT_EVENT_DEV_GONE:
747+
ESP_LOGD(TAG, "Device suddenly disconnected");
569748
hid_host_device_disconnected(event->dev_gone.dev_hdl);
749+
break;
750+
#ifdef HID_HOST_SUSPEND_RESUME_API_SUPPORTED
751+
case USB_HOST_CLIENT_EVENT_DEV_SUSPENDED:
752+
ESP_LOGD(TAG, "Device suspended");
753+
hid_host_device_suspended(event->dev_suspend_resume.dev_hdl);
754+
break;
755+
case USB_HOST_CLIENT_EVENT_DEV_RESUMED:
756+
ESP_LOGD(TAG, "Device resumed");
757+
hid_host_device_resumed(event->dev_suspend_resume.dev_hdl);
758+
break;
759+
#endif // HID_HOST_SUSPEND_RESUME_API_SUPPORTED
760+
default:
761+
ESP_LOGW(TAG, "Unrecognized USB Host client event");
762+
break;
570763
}
571764
}
572765

@@ -633,14 +826,19 @@ static esp_err_t hid_host_disable_interface(hid_iface_t *iface)
633826
ESP_ERR_NOT_FOUND,
634827
"Interface handle not found");
635828

636-
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_ACTIVE == iface->state),
829+
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_ACTIVE == iface->state ||
830+
HID_INTERFACE_STATE_SUSPENDED == iface->state),
637831
ESP_ERR_INVALID_STATE,
638832
"Interface wrong state");
639833

640-
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
641-
"Unable to HALT EP");
642-
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
643-
"Unable to FLUSH EP");
834+
if (HID_INTERFACE_STATE_ACTIVE == iface->state) {
835+
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
836+
"Unable to HALT EP");
837+
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
838+
"Unable to FLUSH EP");
839+
}
840+
// If interface state is suspended, the EP is already flushed and halted, only clear the EP
841+
// If suspended, may return ESP_ERR_INVALID_STATE
644842
usb_host_endpoint_clear(iface->parent->dev_hdl, iface->ep_in);
645843

646844
iface->state = HID_INTERFACE_STATE_READY;
@@ -1220,15 +1418,15 @@ esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle)
12201418
hid_iface->dev_params.iface_num,
12211419
hid_iface->state);
12221420

1223-
if (HID_INTERFACE_STATE_ACTIVE == hid_iface->state) {
1421+
if (HID_INTERFACE_STATE_ACTIVE == hid_iface->state ||
1422+
HID_INTERFACE_STATE_SUSPENDED == hid_iface->state) {
12241423
HID_RETURN_ON_ERROR( hid_host_disable_interface(hid_iface),
12251424
"Unable to disable HID Interface");
12261425
}
12271426

12281427
if (HID_INTERFACE_STATE_READY == hid_iface->state) {
12291428
HID_RETURN_ON_ERROR( hid_host_interface_release_and_free_transfer(hid_iface),
12301429
"Unable to release HID Interface");
1231-
12321430
// If the device is closing by user before device detached we need to flush user callback here
12331431
free(hid_iface->report_desc);
12341432
hid_iface->report_desc = NULL;
@@ -1329,7 +1527,7 @@ esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle)
13291527
ESP_ERR_NOT_FOUND,
13301528
"Interface handle not found");
13311529

1332-
HID_RETURN_ON_FALSE ((HID_INTERFACE_STATE_READY == iface->state),
1530+
HID_RETURN_ON_FALSE ((HID_INTERFACE_STATE_READY == iface->state || HID_INTERFACE_STATE_SUSPENDED == iface->state),
13331531
ESP_ERR_INVALID_STATE,
13341532
"Interface wrong state");
13351533

@@ -1353,6 +1551,15 @@ esp_err_t hid_host_device_stop(hid_host_device_handle_t hid_dev_handle)
13531551

13541552
HID_RETURN_ON_INVALID_ARG(iface);
13551553

1554+
HID_RETURN_ON_FALSE(is_interface_in_list(iface), ESP_ERR_NOT_FOUND, "Interface handle not found");
1555+
1556+
if (iface->state == HID_INTERFACE_STATE_SUSPENDED) {
1557+
// If interface is suspended, mark the last state as READY,
1558+
// as if the interface was stopped before entering suspended state
1559+
iface->last_state = HID_INTERFACE_STATE_READY;
1560+
return ESP_OK;
1561+
}
1562+
13561563
return hid_host_disable_interface(iface);
13571564
}
13581565

host/class/hid/usb_host_hid/include/usb/hid_host.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "esp_err.h"
1212
#include <freertos/FreeRTOS.h>
1313

14+
#include "usb/usb_host.h"
1415
#include "hid.h"
1516

1617
#ifdef __cplusplus
@@ -29,6 +30,11 @@ extern "C" {
2930
*/
3031
#define HID_STR_DESC_MAX_LENGTH 32
3132

33+
// For backward compatibility with IDF versions which do not have suspend/resume api
34+
#ifdef USB_HOST_LIB_EVENT_FLAGS_AUTO_SUSPEND
35+
#define HID_HOST_SUSPEND_RESUME_API_SUPPORTED
36+
#endif
37+
3238
typedef struct hid_interface *hid_host_device_handle_t; /**< Device Handle. Handle to a particular HID interface */
3339

3440
// ------------------------ USB HID Host events --------------------------------
@@ -46,6 +52,10 @@ typedef enum {
4652
HID_HOST_INTERFACE_EVENT_INPUT_REPORT = 0x00, /**< HID Device input report */
4753
HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR, /**< HID Device transfer error */
4854
HID_HOST_INTERFACE_EVENT_DISCONNECTED, /**< HID Device has been disconnected */
55+
#ifdef HID_HOST_SUSPEND_RESUME_API_SUPPORTED
56+
HID_HOST_INTERFACE_EVENT_SUSPENDED, /**< HID Device has been suspended */
57+
HID_HOST_INTERFACE_EVENT_RESUMED, /**< HID Device has been resumed */
58+
#endif // HID_HOST_SUSPEND_RESUME_API_SUPPORTED
4959
} hid_host_interface_event_t;
5060

5161
/**
@@ -194,6 +204,7 @@ esp_err_t hid_host_device_get_raw_input_report_data(hid_host_device_handle_t hid
194204
*
195205
* Calls a callback when the HID Interface event has occurred.
196206
*
207+
* @note Can be called on device in suspended state, it will effectively resume and start the device
197208
* @param[in] hid_dev_handle HID Device handle
198209
* @return esp_err_t
199210
*/
@@ -202,6 +213,8 @@ esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle);
202213
/**
203214
* @brief HID Host stop device
204215
*
216+
* @note Can be called on device in suspended state, it will effectively prevent the device from starting automatically
217+
* in case the device was started before entering suspended state
205218
* @param[in] hid_dev_handle HID Device handle
206219
*
207220
* @return esp_err_t

0 commit comments

Comments
 (0)