mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 02:53:35 +00:00
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:
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user