Skip to content

Commit bc944dc

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 10c3138 commit bc944dc

File tree

5 files changed

+786
-28
lines changed

5 files changed

+786
-28
lines changed

host/class/hid/usb_host_hid/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [unreleased]
2+
3+
- Added global suspend/resume support
4+
15
## 1.0.4
26
- Added support for ESP32-H4
37

host/class/hid/usb_host_hid/hid_host.c

Lines changed: 183 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ typedef enum {
9191
HID_INTERFACE_STATE_READY, /**< HID Interface opened and ready to start transfer */
9292
HID_INTERFACE_STATE_ACTIVE, /**< HID Interface is in use */
9393
HID_INTERFACE_STATE_WAIT_USER_DELETION, /**< HID Interface wait user to be removed */
94+
HID_INTERFACE_STATE_SUSPENDED, /**< HID Interface (and the whole device) is suspended */
9495
HID_INTERFACE_STATE_MAX
9596
} hid_iface_state_t;
9697

@@ -111,6 +112,7 @@ typedef struct hid_interface {
111112
hid_host_interface_event_cb_t user_cb; /**< Interface application callback */
112113
void *user_cb_arg; /**< Interface application callback arg */
113114
hid_iface_state_t state; /**< Interface state */
115+
hid_iface_state_t last_state; /**< Interface last state before entering suspended mode */
114116
} hid_iface_t;
115117

116118
/**
@@ -550,6 +552,155 @@ static esp_err_t hid_host_device_disconnected(usb_device_handle_t dev_hdl)
550552
return ESP_OK;
551553
}
552554

555+
#ifdef HID_HOST_SUSPEND_RESUME_API_SUPPORTED
556+
557+
/**
558+
* @brief Suspend interface
559+
*
560+
* @note endpoints are already halted and flushed when a global suspend is issues by the USB Host lib
561+
* @param[in] iface HID interface handle
562+
* @param[in] stop_ep Stop (halt and flush) endpoint
563+
*
564+
* @return esp_err_t
565+
*/
566+
static esp_err_t hid_host_suspend_interface(hid_iface_t *iface, bool stop_ep)
567+
{
568+
HID_RETURN_ON_INVALID_ARG(iface);
569+
HID_RETURN_ON_INVALID_ARG(iface->parent);
570+
571+
HID_RETURN_ON_FALSE(is_interface_in_list(iface),
572+
ESP_ERR_NOT_FOUND,
573+
"Interface handle not found");
574+
575+
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_SUSPENDED != iface->state),
576+
ESP_ERR_INVALID_STATE,
577+
"Interface wrong state");
578+
579+
// EP is usually stopped by usb_host_lib, in case of global suspend, thus no need to Halt->Flush EP again
580+
if (stop_ep) {
581+
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
582+
"Unable to HALT EP");
583+
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
584+
"Unable to FLUSH EP");
585+
// Don't clear EP, it must remain halted, when the device is in suspended state
586+
}
587+
588+
iface->last_state = iface->state;
589+
iface->state = HID_INTERFACE_STATE_SUSPENDED;
590+
591+
return ESP_OK;
592+
}
593+
594+
/**
595+
* @brief Resume interface
596+
*
597+
* @note endpoints are already cleared when a global resume is issues by the USB Host lib
598+
* @param[in] iface HID interface handle
599+
* @param[in] resume_ep Resume (clear) endpoint
600+
*
601+
* @return esp_err_t
602+
*/
603+
static esp_err_t hid_host_resume_interface(hid_iface_t *iface, bool resume_ep)
604+
{
605+
HID_RETURN_ON_INVALID_ARG(iface);
606+
HID_RETURN_ON_INVALID_ARG(iface->parent);
607+
608+
HID_RETURN_ON_FALSE(is_interface_in_list(iface),
609+
ESP_ERR_NOT_FOUND,
610+
"Interface handle not found");
611+
612+
HID_RETURN_ON_FALSE ((HID_INTERFACE_STATE_SUSPENDED == iface->state),
613+
ESP_ERR_INVALID_STATE,
614+
"Interface wrong state");
615+
616+
// EP is usually cleared by usb_host_lib, in case of global suspend, thus no need to Clear an EP again
617+
if (resume_ep) {
618+
usb_host_endpoint_clear(iface->parent->dev_hdl, iface->ep_in);
619+
}
620+
621+
iface->state = iface->last_state;
622+
623+
if (iface->in_xfer == NULL) {
624+
return ESP_OK;
625+
}
626+
627+
// start data transfer
628+
HID_RETURN_ON_ERROR( usb_host_transfer_submit(iface->in_xfer), "Unable to start data transfer");
629+
return ESP_OK;
630+
}
631+
632+
/**
633+
* @brief Suspend device
634+
*
635+
* Go through list, suspend all devices and deliver suspend events
636+
*
637+
* @param[in] dev_hdl USB Device handle
638+
*
639+
* @return esp_err_t
640+
*/
641+
static esp_err_t hid_host_device_suspended(usb_device_handle_t dev_hdl)
642+
{
643+
hid_device_t *hid_device = get_hid_device_by_handle(dev_hdl);
644+
HID_RETURN_ON_INVALID_ARG(hid_device);
645+
646+
HID_ENTER_CRITICAL();
647+
hid_iface_t *hid_iface_curr;
648+
hid_iface_t *hid_iface_next;
649+
// Go through list
650+
hid_iface_curr = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq);
651+
while (hid_iface_curr != NULL) {
652+
hid_iface_next = STAILQ_NEXT(hid_iface_curr, tailq_entry);
653+
HID_EXIT_CRITICAL();
654+
655+
if (hid_iface_curr->parent && (hid_iface_curr->parent->dev_addr == hid_device->dev_addr)) {
656+
hid_host_suspend_interface(hid_iface_curr, false);
657+
hid_host_user_interface_callback(hid_iface_curr, HID_HOST_INTERFACE_EVENT_SUSPENDED);
658+
}
659+
HID_ENTER_CRITICAL();
660+
hid_iface_curr = hid_iface_next;
661+
}
662+
HID_EXIT_CRITICAL();
663+
664+
return ESP_OK;
665+
}
666+
667+
/**
668+
* @brief Resume device
669+
*
670+
* Go through list, resume all devices and deliver resume events
671+
*
672+
* @param[in] dev_hdl USB Device handle
673+
*
674+
* @return esp_err_t
675+
*/
676+
static esp_err_t hid_host_device_resumed(usb_device_handle_t dev_hdl)
677+
{
678+
hid_device_t *hid_device = get_hid_device_by_handle(dev_hdl);
679+
HID_RETURN_ON_INVALID_ARG(hid_device);
680+
681+
HID_ENTER_CRITICAL();
682+
hid_iface_t *hid_iface_curr;
683+
hid_iface_t *hid_iface_next;
684+
// Go through list
685+
hid_iface_curr = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq);
686+
while (hid_iface_curr != NULL) {
687+
hid_iface_next = STAILQ_NEXT(hid_iface_curr, tailq_entry);
688+
HID_EXIT_CRITICAL();
689+
690+
if (hid_iface_curr->parent && (hid_iface_curr->parent->dev_addr == hid_device->dev_addr)) {
691+
hid_host_resume_interface(hid_iface_curr, false);
692+
hid_host_user_interface_callback(hid_iface_curr, HID_HOST_INTERFACE_EVENT_RESUMED);
693+
}
694+
HID_ENTER_CRITICAL();
695+
hid_iface_curr = hid_iface_next;
696+
}
697+
HID_EXIT_CRITICAL();
698+
699+
return ESP_OK;
700+
}
701+
702+
#endif // HID_HOST_SUSPEND_RESUME_API_SUPPORTED
703+
553704
/**
554705
* @brief USB Host Client's event callback
555706
*
@@ -558,10 +709,28 @@ static esp_err_t hid_host_device_disconnected(usb_device_handle_t dev_hdl)
558709
*/
559710
static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg)
560711
{
561-
if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
712+
switch (event->event) {
713+
case USB_HOST_CLIENT_EVENT_NEW_DEV:
714+
ESP_LOGD(TAG, "New device connected");
562715
hid_host_device_init_attempt(event->new_dev.address);
563-
} else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
716+
break;
717+
case USB_HOST_CLIENT_EVENT_DEV_GONE:
718+
ESP_LOGD(TAG, "Device suddenly disconnected");
564719
hid_host_device_disconnected(event->dev_gone.dev_hdl);
720+
break;
721+
#ifdef HID_HOST_SUSPEND_RESUME_API_SUPPORTED
722+
case USB_HOST_CLIENT_EVENT_DEV_SUSPENDED:
723+
ESP_LOGD(TAG, "Device suspended");
724+
hid_host_device_suspended(event->dev_suspend_resume.dev_hdl);
725+
break;
726+
case USB_HOST_CLIENT_EVENT_DEV_RESUMED:
727+
ESP_LOGD(TAG, "Device resumed");
728+
hid_host_device_resumed(event->dev_suspend_resume.dev_hdl);
729+
break;
730+
#endif // HID_HOST_SUSPEND_RESUME_API_SUPPORTED
731+
default:
732+
ESP_LOGW(TAG, "Unrecognized USB Host client event");
733+
break;
565734
}
566735
}
567736

@@ -628,14 +797,19 @@ static esp_err_t hid_host_disable_interface(hid_iface_t *iface)
628797
ESP_ERR_NOT_FOUND,
629798
"Interface handle not found");
630799

631-
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_ACTIVE == iface->state),
800+
HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_ACTIVE == iface->state ||
801+
HID_INTERFACE_STATE_SUSPENDED == iface->state),
632802
ESP_ERR_INVALID_STATE,
633803
"Interface wrong state");
634804

635-
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
636-
"Unable to HALT EP");
637-
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
638-
"Unable to FLUSH EP");
805+
if (HID_INTERFACE_STATE_ACTIVE == iface->state) {
806+
HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in),
807+
"Unable to HALT EP");
808+
HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in),
809+
"Unable to FLUSH EP");
810+
}
811+
// If interface state is suspended, the EP is already flushed and halted, only clear the EP
812+
// If suspended, may return ESP_ERR_INVALID_STATE
639813
usb_host_endpoint_clear(iface->parent->dev_hdl, iface->ep_in);
640814

641815
iface->state = HID_INTERFACE_STATE_READY;
@@ -1204,15 +1378,15 @@ esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle)
12041378
hid_iface->dev_params.iface_num,
12051379
hid_iface->state);
12061380

1207-
if (HID_INTERFACE_STATE_ACTIVE == hid_iface->state) {
1381+
if (HID_INTERFACE_STATE_ACTIVE == hid_iface->state ||
1382+
HID_INTERFACE_STATE_SUSPENDED == hid_iface->state) {
12081383
HID_RETURN_ON_ERROR( hid_host_disable_interface(hid_iface),
12091384
"Unable to disable HID Interface");
12101385
}
12111386

12121387
if (HID_INTERFACE_STATE_READY == hid_iface->state) {
12131388
HID_RETURN_ON_ERROR( hid_host_interface_release_and_free_transfer(hid_iface),
12141389
"Unable to release HID Interface");
1215-
12161390
// If the device is closing by user before device detached we need to flush user callback here
12171391
free(hid_iface->report_desc);
12181392
hid_iface->report_desc = NULL;

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

Lines changed: 10 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
/**

0 commit comments

Comments
 (0)