USBD endpoint transfer completion event.

In my NRF52840 based device using NRF5 SDK 17.1.0, I notice that sometimes this loop in the nrfx usbd driver code of the SDK doesn't finish:

[modules/nrfx/drivers/src/nrfx_usbd.c: 1444]

/* There is a lot of USBD registers that cannot be accessed during EasyDMA transfer.
 * This is quick fix to maintain stability of the stack.
 * It cost some performance but makes stack stable. */
while (!nrf_usbd_event_check(nrfx_usbd_ep_to_endevent(ep)) &&
       !nrf_usbd_event_check(NRF_USBD_EVENT_USBRESET))
{
    /* Empty */
}

It seems the transfer-end EasyDMA hardware-event isn't generated reliably in all situations. This happens mostly with small transfers on interrupt-in-endpoints and only with some usb hosts (notably raspberry pi). This is a big problem, because this function is called from interrupt context (and therefore should actually not do such busy waiting).

Is this a known issue and is there a workaround? I tried to enter the loop conditionally only when transfer size is bigger than 4 or 8 bytes, which seems to fix this, but that causes other problems.  I tried to pad the 2-byte transfer to 8 bytes, but that doesn't change anything. I think it could work to skip the loop for interrupt endpoints, but this routine handles interrupt and bulk endpoints the same; there is no USBD register that keeps track of bulk vs. interrupt endpoints.

I know that NRF5 sdk is out of support, but my project was built with it and it isn't easy to move to NRF-Connect SDK.

  • Hi Ronald, 
    Please correct me if I'm wrong, what you are saying is that the hardware event ENDEPIN or ENDEPOUT didn't come as expected ? Could you check exactly which one ? 
    Is there any chance that it's been cleared by something else and got stuck there  ? Are you able to reproduce the issue with an example in the nRF5 SDK ? This way we can be sure it's not something else with the application may cause the problem. 

    As you know the nRF5 SDK is discontinued for a long time, but if you can reproduce with NCS then it's easier for us to request investigation/bug fix in NCS than on nRF5 SDK.

  • This is for an outgoing notification to the host, so it should be ENDEPIN. The endpoint address is 0x83 and the ep_to_endevent(ep) results in 0x114.

    Since this is involving interrupts, it's hard to see if anything else reset the end event before the handler code has the chance to look at it, but since this is code run in interrupt context, I guess the likelihood is very minimal.

    I will try to find an example that is suitable to demonstrate this issue. Maybe you have a suggestion which one?

    I don't have a working NC SDK at my disposal now. But it looks like that code didn't change much between nRF5 SDK and NRF Connect.

  • I dove a bit deeper in the situation and it seems the notification ep transfer isn't started at all (or at least it doesn't arrive at the host side) and therefore the end event doesn't occur either. The driver code just calls 2 inline functions (nrf_usbd_ep_easydma_set and usbd_dma_start) and assumes the transfer is started without checking if it really did.

    According to the nRF52840_PS_v1.8.pdf, there should be a 'STARTED' event when the PTR and MAXCOUNT have been 'captured'. Only when they have, it makes sense to wait for the ENDEPIN event to ensure the buffer is 'free' again (although I disagree that this should be done in the interrupt routine, alas).

    What could be the reason why a DMA action isn't captured? Could it be because a previous DMA isn't finished yet?

  • I have found the reason for the issue. Look at the comments on the function usbd_dma_pending_set:

    /**
     * @brief Mark that EasyDMA is working.
     *
     * Internal function to set the flag informing about EasyDMA transfer pending.
     * This function is called always just after the EasyDMA transfer is started.
     */
    static inline void usbd_dma_pending_set(void)
    {
        if (nrfx_usbd_errata_199())
        {
            *((volatile uint32_t *)0x40027C1C) = 0x00000082;
        }
        m_dma_pending = true;
    }

    It says that this function is (to be) called just AFTER the DMA is started, but in the function usbd_dmareq_process, it is called a little BEFORE the DMA is started. This isn't a problem for setting the value of m_dma_pending (of course) but it is a problem for the errata 199 workaround.

    I moved the call to the pending_set function from line 1429 to right after usbd_dma_start(ep), line 1443 and it solves the issue with raspberry pi. It also still works fine with the windows laptop.

    I do think this should be rectified in the NRF5 SDK and in the NRF Connect SDK (sdk-hal_nordic/nrfx/drivers/src/nrfx_usbd.c).

Related