device: scope the SetInterface wake reset, and ignore DEVICE_REMOTE_WAKEUP at SS

func_wakeup_clear_all() on SET_INTERFACE wiped every function's bit plus the
device-level aggregate, so on a composite SuperSpeed device a routine
SET_INTERFACE(audio, alt=1) revoked the wake authorization the host had armed on
an unrelated HID function: tud_remote_wakeup() silently no-ops while
GET_STATUS(that interface) still reports D1=1. USB 3.2 Table 9-10 scopes the
reset to the function being reconfigured, so it now clears only the addressed
interface and re-derives the aggregate.

SET/CLEAR_FEATURE(DEVICE_REMOTE_WAKEUP) were also the only device selectors left
ungated by link_is_superspeed(). USB 3.2 9.2.5.4 does not use that selector on
Enhanced SuperSpeed devices - Linux picks the recipient by speed for exactly
this reason - and honouring it desynchronised remote_wakeup_en from the
func_wakeup_bm it is now derived from, in both directions. They are refused on a
SuperSpeed link, guarded so non-SuperSpeed builds still compile.

The FUNCTION_SUSPEND handler's open-coded aggregate loop becomes
func_wakeup_recompute(), the single definition of that rule.
This commit is contained in:
hathach
2026-08-17 02:08:01 +07:00
parent 5a3cc44744
commit 5b741f43ed

View File

@ -449,6 +449,16 @@ TU_ATTR_ALWAYS_INLINE static inline bool link_is_superspeed(void);
// bit that SET_FEATURE(DEVICE_REMOTE_WAKEUP) sets, and USB 2.0 §9.4.5 clears it only on a device
// reset. On a runtime USB2 fallback link (CFG_TUD_WCH_USB30_FALLBACK) an ungated clear would let
// SET_CONFIGURATION revoke a host-granted wakeup the host never revoked.
static inline void func_wakeup_recompute(void) {
// remote_wakeup_en is the aggregate: keep it set while ANY function is wake-enabled, so one
// function's reset cannot revoke the authorization the host gave another.
uint8_t any = 0;
for (uint8_t i = 0; i < TU_ARRAY_SIZE(_usbd_dev.func_wakeup_bm); i++) {
any |= _usbd_dev.func_wakeup_bm[i];
}
_usbd_dev.remote_wakeup_en = any ? 1u : 0u;
}
static inline void func_wakeup_clear_all(void) {
if (!link_is_superspeed()) {
return;
@ -456,6 +466,19 @@ static inline void func_wakeup_clear_all(void) {
tu_memclr(_usbd_dev.func_wakeup_bm, sizeof(_usbd_dev.func_wakeup_bm));
_usbd_dev.remote_wakeup_en = 0;
}
// SetInterface resets FUNCTION REMOTE WAKEUP for the function being reconfigured (USB 3.2
// Table 9-10) - not for every other function on the device. Clearing them all would let a
// routine SET_INTERFACE(audio, alt=1) silently revoke the wake authorization the host armed on,
// say, a HID function, leaving tud_remote_wakeup() a no-op while the host still believes D1=1.
static inline void func_wakeup_clear_itf(uint8_t itf) {
if (!link_is_superspeed() || itf >= CFG_TUD_INTERFACE_MAX) {
return;
}
const uint8_t itf_mask = (uint8_t) (1u << (itf % 8));
_usbd_dev.func_wakeup_bm[itf / 8] = (uint8_t) (_usbd_dev.func_wakeup_bm[itf / 8] & ~itf_mask);
func_wakeup_recompute();
}
#endif
#if CFG_TUD_TEST_MODE
@ -1089,6 +1112,15 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co
switch (p_request->wValue) { //-V2520
case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
TU_LOG_USBD(" Enable Remote Wakeup\r\n");
// USB 3.2 §9.2.5.4: the DEVICE_REMOTE_WAKEUP selector is not used by Enhanced
// SuperSpeed devices, which arm wake per function via FUNCTION_SUSPEND instead (Linux
// picks the recipient by speed for exactly this reason). Accepting it on a SuperSpeed
// link would set the aggregate with func_wakeup_bm still empty, so tud_remote_wakeup()
// would drive a U-state exit no function was authorised for while GET_STATUS(Device)
// masks the bit out and the host cannot even observe it.
#if TUD_OPT_SUPER_SPEED
TU_VERIFY(!link_is_superspeed());
#endif
// Host may enable remote wake up before suspending especially HID device
_usbd_dev.remote_wakeup_en = 1;
tud_control_status(rhport, p_request);
@ -1137,6 +1169,12 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co
switch (p_request->wValue) { //-V2520
case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
TU_LOG_USBD(" Disable Remote Wakeup\r\n");
// Not used at SuperSpeed, same as the SET_FEATURE side above: clearing the aggregate
// there would leave func_wakeup_bm holding bits, so GET_STATUS(Interface) would report
// D1=1 while tud_remote_wakeup() refuses.
#if TUD_OPT_SUPER_SPEED
TU_VERIFY(!link_is_superspeed());
#endif
// Host may disable remote wake up after resuming
_usbd_dev.remote_wakeup_en = 0;
tud_control_status(rhport, p_request);
@ -1303,7 +1341,7 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const
// the devices hosts send SET_INTERFACE to.
if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type &&
TUSB_REQ_SET_INTERFACE == p_request->bRequest) {
func_wakeup_clear_all();
func_wakeup_clear_itf(itf);
}
#endif
@ -1374,11 +1412,7 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const
}
// Aggregate: keep remote wakeup allowed while ANY function is wake-enabled, so one
// function cannot revoke the host's authorization given to another one.
uint8_t wakeup_any = 0;
for (uint8_t i = 0; i < TU_ARRAY_SIZE(_usbd_dev.func_wakeup_bm); i++) {
wakeup_any |= _usbd_dev.func_wakeup_bm[i];
}
_usbd_dev.remote_wakeup_en = wakeup_any ? 1u : 0u;
func_wakeup_recompute();
tud_control_status(rhport, p_request);
break;
}