Skip to content

Conversation

@peppapighs
Copy link
Contributor

Describe the PR
Fix #3315 by only process SUSPEND event if the device is connected. Tested locally to confirm that _usb_dev state is as intended when the bus is suspended and resume. We probably don't need to repeat this change for RESUME event since enabling suspend interrupt during initialization should have no side effect.

Closes #3315

@HiFiPhile
Copy link
Collaborator

In TinyUSB's structure, DCD layer shouldn't call exported device stack functions (tud_*)

I found the cause of the issue. On initialization, AT32F405 receives a suspend interrupt once (usbsusp = usbsuspm = 1) before the device is connected. As a result, dcd_int_handler disables the suspend interrupt, even though the device is still unconnected.

When plugging/unplugging device, the D+/D- state are unstable and can accidentally meet the SUSPEND condition. But this shouldn't be an issue since the interrupt is enabled after the usb reset:

dwc2->gintmsk |= GINTMSK_USBSUSPM;

Unless GINTSTS_USBSUSP is already pending and should be cleared before enabling.

@peppapighs
Copy link
Contributor Author

In TinyUSB's structure, DCD layer shouldn't call exported device stack functions (tud_*)

I found the cause of the issue. On initialization, AT32F405 receives a suspend interrupt once (usbsusp = usbsuspm = 1) before the device is connected. As a result, dcd_int_handler disables the suspend interrupt, even though the device is still unconnected.

When plugging/unplugging device, the D+/D- state are unstable and can accidentally meet the SUSPEND condition. But this shouldn't be an issue since the interrupt is enabled after the usb reset:

dwc2->gintmsk |= GINTMSK_USBSUSPM;

Unless GINTSTS_USBSUSP is already pending and should be cleared before enabling.

You are right. I put a breakpoint at USB reset event, and usbsusp = 1, so if I understand this correctly, there is a pending suspend interrupt before USB reset. I'll make the change to clear it first.

@HiFiPhile
Copy link
Collaborator

I think you can simply write like this, otherwise pending Enum Done interrupt will trigger the IRQ again:

if (gintsts & GINTSTS_ENUMDNE) {
    // ENUMDNE is the end of reset where speed of the link is detected
    dwc2->gintsts = GINTSTS_ENUMDNE;
    // There may be a pending suspend event, so we clear it first
    dwc2->gintsts = GINTSTS_USBSUSP;
    dwc2->gintmsk |= GINTMSK_USBSUSPM;
    handle_enum_done(rhport);
  }

@peppapighs
Copy link
Contributor Author

I think you can simply write like this, otherwise pending Enum Done interrupt will trigger the IRQ again:

if (gintsts & GINTSTS_ENUMDNE) {
    // ENUMDNE is the end of reset where speed of the link is detected
    dwc2->gintsts = GINTSTS_ENUMDNE;
    // There may be a pending suspend event, so we clear it first
    dwc2->gintsts = GINTSTS_USBSUSP;
    dwc2->gintmsk |= GINTMSK_USBSUSPM;
    handle_enum_done(rhport);
  }

Right, I wasn't aware you can clear multiple interrupts at once. I've made the change.

Copy link
Collaborator

@HiFiPhile HiFiPhile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you !

@HiFiPhile HiFiPhile merged commit a6efc7d into hathach:master Oct 25, 2025
82 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AT32F405 gets stuck in a suspended state even though the bus is resumed

2 participants