USBD EPDATA event for OUT endpoints firing prematurely

I'm trying to integrate the Micrium OS-III USB stack into my product using a custom USB driver abstraction later. The USB class is mass-storage if it matters but the problem is more fundamental. The problem is as follows, after the enumeration and exchange of the descriptors, the USB stack opens EP1 IN and OUT for bulk transfers.

OUT transfers are done in 3 stages:

1) Configure the EasyDMA for the transfer by initialising the PTR and MAXCNT registers and writing to the SIZE register:

/* Configure the EasyDMA: */
lcx_usbd(LCX_USBD_0)->EPOUT[addr].PTR.PTR       = (uint32_t) p_buf;
lcx_usbd(LCX_USBD_0)->EPOUT[addr].MAXCNT.MAXCNT = 0U;

/* Mark the transfer to be serviced by the EasyDMA: */
lcx_usbd(LCX_USBD_0)->SIZE.EPOUT[addr].SIZE = 0U;

2) Wait for the EPDATA event (handled in the ISR) and check EPDATASTATUS for the corresponding bit for the endpoint, Set the MAXCNT register to SIZE.EPOUT and trigger the corresponding STARTEPOUT task:

lcx_usbd(LCX_USBD_0)->EPOUT[addr].MAXCNT.MAXCNT = lcx_usbd(LCX_USBD_0)->SIZE.EPOUT[addr].SIZE;
lcx_usbd_trigger_task(LCX_USBD_0, LCX_USBD_TASKS_STARTEPOUT_0 + addr);

3) Wait for the EPOUTEND event to mark the end of the transfer (or prepare for the next transfer).

I've put trace messages in each of these stages to monitor the events. The first few transactions happen as expected (the following messages are received from the trace console). In "O" or "I" indicate whether it's an IN or OUT transfer, the number following that indicated the endpoint number.

O1_INIT
O1_START
O1_END

I1_INIT
I1_START
I1_END

O1_INIT
O1_START
O1_END

I1_INIT
I1_START
I1_END

O1_START
O1_END
O1_INIT

As you can see, during the last transaction, state 2 is already triggered (EPDATA event generated) before we set SIZE.EPOUT[addr].SIZE to 0 to indicate to the DMA engine that the buffer is ready to receive data. According to the reference manual: "Once the EasyDMA is prepared and enabled, by writing the EPOUT[n] registers and triggering the STARTEPOUT[n] task, the incoming data will be transferred to Data RAM. Until that transfer is finished, the hardware will automatically NAK any other incoming OUT+DATA packets. Only when the EasyDMA transfer is done (signalled by the ENDEPOUT[n] event), or as soon as any values are written by the software in register SIZE.EPOUT[n], the endpoint n will accept incoming OUT+DATA again." The last sentence seem to suggest that when the previous transaction is finished (signalled by the ENDEPOUT event) the USBD peripheral will start to ACK further OUT transactions (generate EPDATA event) even before the software has had the chance to configure the buffer pointer etc. This looks like what is happening in my case. The problem is, how can I tell the USBD peripheral to NACK all further transactions until I've configured the pointers and the USB stack is ready to receive more data?

  • P.S. My NRF52840 is variant FICR->INFO.VARIANT.VARIANT = 0xAAD0 (which i believe is revision 2). I've implemented fixes for errata 187 and 199.

  • Hi 

    Any particular reason you are not using the USB stack we provide? 
    The support we can provide for third party drivers is fairly limited.

    The last sentence seem to suggest that when the previous transaction is finished (signalled by the ENDEPOUT event) the USBD peripheral will start to ACK further OUT transactions (generate EPDATA event) even before the software has had the chance to configure the buffer pointer etc. This looks like what is happening in my case. The problem is, how can I tell the USBD peripheral to NACK all further transactions until I've configured the pointers and the USB stack is ready to receive more data?

    The way it works is that as soon as you transfer a packet from the USBD peripheral into RAM, the USB peripheral will be able to receive another packet. In other words it will automatically ACK the next packet, but it won't accept any more packets until you transfer the next packet into RAM as well. 

    In other words the USBD peripheral is allowed to be one packet ahead since it has it's own internal endpoint buffers that don't conflict with the buffers assigned through the EasyDMA mechanism. 

    So there should be no need to explicitly tell the USBD peripheral to NACK packets. It will automatically start NACK'ing once the internal buffer is full, until you transfer it to RAM. 

    Best regards
    Torbjørn

  • Thank you for the reply and sorry that it took so long to respond. Indeed your answer was the missing piece i needed to get this to work. Unfortunately as far as I can tell that info is missing from the documentation (which is otherwise the best in the industry). We got both the Micrium USB stack as was as out custom USB stack (for testing purposes) working!

    The reason we went with the Micrium USB stack is because our product was already using the uC-III os and it seemed like the logical choice when it comes to USB stacks. As for why we chose to use uC-III over ZephyrOS was because our product needs to be certified for aviation. uC-III has been certified for this purpose in the past.

  • Hi 

    My answer was based on this chapter in the documentation so I don't think it is missing, but possibly it is not described in the most intuitive manner. 

    I will check if there are any progress on providing functional safety certification for the Zephyr RTOS and/or the nRF Connect SDK. I found this 2019 article discussing functional safety certification for the Zephyr RTOS, but I don't know what the status is today. 

    Best regards
    Torbjørn

  • Hi again

    Apparently there hasn't really been any updates on this, so if you need this kind of certification today you would need to go with some non Zephyr based solution. 

    Best regards
    Torbjørn

Related