fix(usb): implement hardware SOF and DSTS sleep/wake auto-recovery
This commit is contained in:
1 parent
44415fa442
commit
59d378ed42
2 files changed
+179
-31
No files matched your search
+48
-27
@@ -42,7 +42,9 @@ static void uac_watchdog_task(void* arg) {
|
||||
uint32_t stuck_ticks = 0;
|
||||
while(1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
if (s_uac_streaming) {
|
||||
|
||||
// ONLY monitor if USB is fully mounted, active, NOT suspended by host, and streaming
|
||||
if (tud_mounted() && !tud_suspended() && s_uac_streaming) {
|
||||
if (s_xfer_cb_count == last_count) {
|
||||
stuck_ticks += 10;
|
||||
if (stuck_ticks >= 50) {
|
||||
@@ -67,6 +69,7 @@ static void uac_watchdog_task(void* arg) {
|
||||
}
|
||||
} else {
|
||||
stuck_ticks = 0;
|
||||
last_count = s_xfer_cb_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +126,8 @@ static void uac_driver_reset(uint8_t rhport) {
|
||||
(void)rhport;
|
||||
s_uac_streaming = false;
|
||||
s_uac_alt = 0;
|
||||
s_xfer_cb_count = 0;
|
||||
app_log("UAC", "USB Bus Reset detected -> UAC state reset");
|
||||
}
|
||||
|
||||
static uint16_t uac_driver_open(uint8_t rhport, tusb_desc_interface_t const *desc_intf, uint16_t max_len) {
|
||||
@@ -144,39 +149,54 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage,
|
||||
tusb_control_request_t const *req) {
|
||||
if (stage != CONTROL_STAGE_SETUP) return true;
|
||||
|
||||
// 1. Standard USB Requests
|
||||
if (req->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) {
|
||||
if (req->bRequest == TUSB_REQ_SET_INTERFACE) {
|
||||
uint8_t alt = (uint8_t)req->wValue;
|
||||
s_uac_alt = alt;
|
||||
s_uac_streaming = (alt == 1);
|
||||
|
||||
// Force release endpoint to clear stuck busy flags from aborted transfers
|
||||
if (req->bRequest == TUSB_REQ_SET_INTERFACE) {
|
||||
uint8_t itf = (uint8_t)req->wIndex;
|
||||
if (itf != s_uac_itf_as && itf != s_uac_itf_ac) {
|
||||
return false; // Crucial: Let other class drivers (HID, CDC) handle their own interfaces!
|
||||
}
|
||||
uint8_t alt = (uint8_t)req->wValue;
|
||||
s_uac_alt = alt;
|
||||
s_uac_streaming = (alt == 1);
|
||||
|
||||
// Force release endpoint to clear stuck busy flags from aborted transfers
|
||||
usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80));
|
||||
|
||||
if (alt == 1) {
|
||||
tusb_desc_endpoint_t ep;
|
||||
ep.bLength = sizeof(tusb_desc_endpoint_t);
|
||||
ep.bDescriptorType = TUSB_DESC_ENDPOINT;
|
||||
ep.bEndpointAddress = (uint8_t)(s_uac_ep_in | 0x80);
|
||||
ep.bmAttributes.xfer = TUSB_XFER_ISOCHRONOUS;
|
||||
ep.bmAttributes.sync = 1;
|
||||
ep.wMaxPacketSize = 64;
|
||||
ep.bInterval = 2;
|
||||
usbd_edpt_open(rhport, &ep);
|
||||
|
||||
// Kick off the continuous Isochronous IN chain
|
||||
memset(s_tx_buf, 0, 64);
|
||||
usbd_edpt_xfer(rhport, ep.bEndpointAddress, (uint8_t*)s_tx_buf, 64);
|
||||
} else {
|
||||
usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80));
|
||||
|
||||
if (alt == 1) {
|
||||
tusb_desc_endpoint_t ep;
|
||||
ep.bLength = sizeof(tusb_desc_endpoint_t);
|
||||
ep.bDescriptorType = TUSB_DESC_ENDPOINT;
|
||||
ep.bEndpointAddress = (uint8_t)(s_uac_ep_in | 0x80);
|
||||
ep.bmAttributes.xfer = TUSB_XFER_ISOCHRONOUS;
|
||||
ep.bmAttributes.sync = 1;
|
||||
ep.wMaxPacketSize = 64;
|
||||
ep.bInterval = 2;
|
||||
usbd_edpt_open(rhport, &ep);
|
||||
|
||||
// Kick off the continuous Isochronous IN chain
|
||||
memset(s_tx_buf, 0, 64);
|
||||
usbd_edpt_xfer(rhport, ep.bEndpointAddress, (uint8_t*)s_tx_buf, 64);
|
||||
} else {
|
||||
usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80));
|
||||
}
|
||||
return tud_control_status(rhport, req);
|
||||
}
|
||||
return tud_control_status(rhport, req);
|
||||
} else if (req->bRequest == TUSB_REQ_GET_INTERFACE) {
|
||||
uint8_t itf = (uint8_t)req->wIndex;
|
||||
if (itf != s_uac_itf_as && itf != s_uac_itf_ac) {
|
||||
return false; // Crucial: Let other class drivers handle their own interfaces!
|
||||
}
|
||||
return tud_control_xfer(rhport, req, &s_uac_alt, 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Class-Specific USB Requests
|
||||
if (req->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) {
|
||||
uint8_t target_itf = (uint8_t)req->wIndex;
|
||||
if (target_itf != s_uac_itf_ac && target_itf != s_uac_itf_as && target_itf != 0x02) {
|
||||
return false; // Crucial: Let HID / CDC class drivers handle their own class requests!
|
||||
}
|
||||
uint8_t r = req->bRequest;
|
||||
uint8_t cs = (uint8_t)(req->wValue >> 8);
|
||||
if (r == 0x81) { // GET_CUR
|
||||
@@ -188,8 +208,9 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage,
|
||||
if (r == 0x82) return tud_control_xfer(rhport, req, &vol_min, 2);
|
||||
if (r == 0x83) return tud_control_xfer(rhport, req, &vol_max, 2);
|
||||
if (r == 0x84) return tud_control_xfer(rhport, req, &vol_res, 2);
|
||||
return false;
|
||||
}
|
||||
return tud_control_status(rhport, req);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool uac_driver_xfer_cb(uint8_t rhport, uint8_t ep_addr,
|
||||
|
||||
+131
-4
@@ -8,6 +8,9 @@
|
||||
#include "USBCDC.h"
|
||||
#include "USBHIDKeyboard.h"
|
||||
#include "USBHIDConsumerControl.h"
|
||||
#include "tusb.h"
|
||||
#include "esp32-hal-tinyusb.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#if !ARDUINO_USB_CDC_ON_BOOT
|
||||
USBCDC USBSerial;
|
||||
@@ -17,6 +20,17 @@ static USBHIDKeyboard s_keyboard;
|
||||
static USBHIDConsumerControl s_consumer;
|
||||
static bool s_usb_ready = false;
|
||||
|
||||
extern "C" void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr);
|
||||
extern "C" bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr);
|
||||
|
||||
#include "soc/usb_struct.h"
|
||||
|
||||
static bool s_hw_sleep_detected = false;
|
||||
static uint32_t s_hw_sleep_start_ms = 0;
|
||||
static uint16_t s_last_soffn = 0;
|
||||
static uint32_t s_last_soffn_change_ms = 0;
|
||||
static uint32_t s_boot_grace_until_ms = 0;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void usb_composite_init(void) {
|
||||
@@ -28,6 +42,27 @@ void usb_composite_init(void) {
|
||||
USB.usbClass(0xEF);
|
||||
USB.usbSubClass(0x02);
|
||||
USB.usbProtocol(0x01); // MISC_PROTOCOL_IAD
|
||||
USB.usbAttributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP);
|
||||
|
||||
USB.onEvent([](void* arg, esp_event_base_t base, int32_t id, void* data) {
|
||||
if (id == ARDUINO_USB_SUSPEND_EVENT) {
|
||||
app_log("USB", "USB Suspend Event (PC Sleep/Standby)");
|
||||
} else if (id == ARDUINO_USB_RESUME_EVENT) {
|
||||
app_log("USB", "USB Resume Event (PC Woke up)");
|
||||
for (uint8_t ep = 1; ep <= 4; ep++) {
|
||||
usbd_edpt_clear_stall(0, (uint8_t)(ep | 0x80));
|
||||
}
|
||||
s_keyboard.releaseAll();
|
||||
s_consumer.release();
|
||||
} else if (id == ARDUINO_USB_STARTED_EVENT) {
|
||||
app_log("USB", "USB Started / Mounted");
|
||||
for (uint8_t ep = 1; ep <= 4; ep++) {
|
||||
usbd_edpt_clear_stall(0, (uint8_t)(ep | 0x80));
|
||||
}
|
||||
} else if (id == ARDUINO_USB_STOPPED_EVENT) {
|
||||
app_log("USB", "USB Stopped / Bus Reset");
|
||||
}
|
||||
});
|
||||
|
||||
#if !ARDUINO_USB_CDC_ON_BOOT
|
||||
USBSerial.begin();
|
||||
@@ -43,6 +78,51 @@ void usb_composite_init(void) {
|
||||
|
||||
void usb_composite_task(void) {
|
||||
uac_microphone_task();
|
||||
|
||||
uint32_t now = millis();
|
||||
if (s_boot_grace_until_ms == 0) {
|
||||
s_boot_grace_until_ms = now + 5000; // 5s boot grace period
|
||||
s_last_soffn_change_ms = now;
|
||||
}
|
||||
if (now < s_boot_grace_until_ms) {
|
||||
return; // Don't check during initial bootup
|
||||
}
|
||||
|
||||
// Read hardware DWC2 register on ESP32-S3
|
||||
uint32_t dsts = USB0.dsts;
|
||||
bool is_hw_suspended = (dsts & 1) != 0; // Bit 0: SuspSts (1 = Suspended)
|
||||
uint16_t current_soffn = (dsts >> 8) & 0x3FFF; // Bits 8-21: Frame Number (increments every 1ms when PC is awake)
|
||||
|
||||
if (current_soffn != s_last_soffn) {
|
||||
s_last_soffn = current_soffn;
|
||||
s_last_soffn_change_ms = now;
|
||||
}
|
||||
|
||||
// If SOF packets stopped for > 800ms, Windows is asleep/disconnected
|
||||
bool sof_active = (now - s_last_soffn_change_ms < 800);
|
||||
|
||||
// 1. Detect PC entering Sleep
|
||||
if ((is_hw_suspended || !sof_active) && !s_hw_sleep_detected) {
|
||||
s_hw_sleep_detected = true;
|
||||
s_hw_sleep_start_ms = now;
|
||||
app_log("USB_HW", "PC Sleep Detected! (SuspSts=%d, SOF stopped)", is_hw_suspended ? 1 : 0);
|
||||
}
|
||||
// 2. Detect PC waking up (SOF resumed after sleeping for > 2s)
|
||||
else if (s_hw_sleep_detected && !is_hw_suspended && sof_active) {
|
||||
if (now - s_hw_sleep_start_ms >= 2000) {
|
||||
app_log("USB_HW", "PC Wakeup Detected! SOF resumed -> performing clean USB hardware re-enumeration...");
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
// Pull D+ low to signal disconnect to Windows kernel
|
||||
pinMode(20, OUTPUT);
|
||||
pinMode(19, OUTPUT);
|
||||
digitalWrite(20, LOW);
|
||||
digitalWrite(19, LOW);
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
esp_restart();
|
||||
} else {
|
||||
s_hw_sleep_detected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SemaphoreHandle_t s_hid_mutex = NULL;
|
||||
@@ -65,10 +145,29 @@ static void hid_unlock(void) {
|
||||
bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) {
|
||||
if (!s_usb_ready) return false;
|
||||
hid_lock();
|
||||
|
||||
if (s_hw_sleep_detected || (USB0.dsts & 1) != 0 || tud_suspended()) {
|
||||
app_log("USB_HW", "Key pressed while PC asleep -> Sending Remote Wakeup!");
|
||||
tud_remote_wakeup();
|
||||
vTaskDelay(pdMS_TO_TICKS(15));
|
||||
}
|
||||
|
||||
if (usbd_edpt_busy(0, 0x81)) {
|
||||
usbd_edpt_clear_stall(0, 0x81);
|
||||
}
|
||||
|
||||
// 1. Direct TinyUSB driver report
|
||||
hid_keyboard_report_t kbd = {0};
|
||||
kbd.modifier = modifier;
|
||||
kbd.keycode[0] = keycode;
|
||||
tud_hid_n_report(0, HID_REPORT_ID_KEYBOARD, &kbd, sizeof(kbd));
|
||||
|
||||
// 2. Arduino helper state update
|
||||
KeyReport report = {0};
|
||||
report.modifiers = modifier;
|
||||
report.keys[0] = keycode;
|
||||
s_keyboard.sendReport(&report);
|
||||
|
||||
hid_unlock();
|
||||
return true;
|
||||
}
|
||||
@@ -76,9 +175,18 @@ bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) {
|
||||
bool usb_hid_keyboard_release(void) {
|
||||
if (!s_usb_ready) return false;
|
||||
hid_lock();
|
||||
|
||||
if (usbd_edpt_busy(0, 0x81)) {
|
||||
usbd_edpt_clear_stall(0, 0x81);
|
||||
}
|
||||
|
||||
hid_keyboard_report_t kbd = {0};
|
||||
tud_hid_n_report(0, HID_REPORT_ID_KEYBOARD, &kbd, sizeof(kbd));
|
||||
|
||||
KeyReport report = {0}; // All zeroes
|
||||
s_keyboard.sendReport(&report);
|
||||
s_keyboard.releaseAll();
|
||||
|
||||
hid_unlock();
|
||||
return true;
|
||||
}
|
||||
@@ -94,7 +202,20 @@ bool usb_hid_keyboard_tap(uint8_t modifier, uint8_t keycode) {
|
||||
bool usb_hid_consumer_press(uint16_t usage_code) {
|
||||
if (!s_usb_ready) return false;
|
||||
hid_lock();
|
||||
|
||||
if (tud_suspended()) {
|
||||
app_log("USB", "PC Suspended -> tud_remote_wakeup");
|
||||
tud_remote_wakeup();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
if (usbd_edpt_busy(0, 0x81)) {
|
||||
usbd_edpt_clear_stall(0, 0x81);
|
||||
}
|
||||
|
||||
tud_hid_n_report(0, HID_REPORT_ID_CONSUMER_CONTROL, &usage_code, 2);
|
||||
s_consumer.press(usage_code);
|
||||
|
||||
hid_unlock();
|
||||
return true;
|
||||
}
|
||||
@@ -102,18 +223,24 @@ bool usb_hid_consumer_press(uint16_t usage_code) {
|
||||
bool usb_hid_consumer_release(void) {
|
||||
if (!s_usb_ready) return false;
|
||||
hid_lock();
|
||||
|
||||
if (usbd_edpt_busy(0, 0x81)) {
|
||||
usbd_edpt_clear_stall(0, 0x81);
|
||||
}
|
||||
|
||||
uint16_t zero = 0;
|
||||
tud_hid_n_report(0, HID_REPORT_ID_CONSUMER_CONTROL, &zero, 2);
|
||||
s_consumer.release();
|
||||
|
||||
hid_unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool usb_hid_consumer_tap(uint16_t usage_code) {
|
||||
if (!s_usb_ready) return false;
|
||||
hid_lock();
|
||||
s_consumer.press(usage_code);
|
||||
usb_hid_consumer_press(usage_code);
|
||||
delay(15);
|
||||
s_consumer.release();
|
||||
hid_unlock();
|
||||
usb_hid_consumer_release();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user