FX3 DMA Engine hangs(?) with small manual transfers

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

I have a manual single DMA channel which is dealing with multiple small packets, one at a time. After committing the buffer, the data arrives at the host just fine.

However, I cannot start a new DMA transfer because the previous one is ACTIVE indefinitely.

The setup is:

dmaCfg.size = 96;  // Multiple of 32 required
dmaCfg.count = 192;  // 256 seems to crash? Not sure why, should be plenty of memory?
dmaCfg.prodSckId = CY_U3P_PIB_SOCKET_0;
dmaCfg.consSckId = CY_U3P_UIB_SOCKET_CONS_1
dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE;
dmaCfg.prodHeader = 16; // Multiple of 16 required
dmaCfg.notification = 0xffff; // I'll take anything
dmaCfg.cb = funcCallback;
...
CyU3PDmaChannelCreate(&handle, CY_U3P_DMA_TYPE_MANUAL, &dmaCfg);
CyU3PDmaChannelSetXfer(&handle, 80);

In my callback, I get a CY_U3P_DMA_CB_PROD_EVENT:

CyU3PDmaChannelGetBuffer(&handle, &bufp, CYU3P_NO_WAIT);
CyU3PMemCopy(bufp.buffer - 16, &header, 16);
CyU3PDmaChannelCommitBuffer(&handle, bufp.count + 16, 0);
CyU3PEventSet(&glEvents, PROD_COMPLETE_EVENT, CYU3P_EVENT_OR);

Then, in my event handler, I try to queue up my next packet by waiting for the transfer to complete. 

CyU3PDmaChannelWaitForCompletion(&handle, 1000);

But it never completes. The state is ACTIVE forever. 

Is there a better way to handle multiple manual transfers? I'm sure I could completely reset & abort, but that seems like overkill.

Thanks!

0 Likes
1 Solution
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

For #3: Try not calling setXfer in the XFER_CPLT callback and pend it using an event. You will get a PROD, XFER_CPLT, PROD. If you blindly commit the second PROD, you get the NOT_STARTED error. I think this is expected given what we've found out about the DMA engine. setXfer() does not limit the producer, but rather the consumer. As a result, the XFER_CPLT is saying the consumer has received everything it should, but the producer is still filling buffers. Therefore, it's possible to get a PROD after the XFER_CPLT.

You've helped immensely figure this out. Key takeaways:

  1. SetXfer() sets the number of bytes/buffers that can be consumed, not the number of buffers which will be produced. The DMA engine will produce buffers until it runs out, no matter what size the SetXfer is.
  2. dmaCfg.count = 1 ensures only a single buffer is produced for each Commit().
  3. The DMA engine can deliver a PROD after a XFER_COMPLETE.
  4. In byte mode, setXfer() sets the total bytes to be consumed. The producer will grab this many bytes minus prodHeader.

I'm going to mark this as the solution for quick reference to future generations. If this keeps you from getting support credit or something, let me know and I'll change it. Thanks for your help!

 

View solution in original post

0 Likes
8 Replies
AliAsgar
Moderator
Moderator
Moderator
1000 replies posted 250 solutions authored 750 replies posted

Hi,

1. How fast is the host reading the data from the endpoint?
2. You could try queueing up your next packet when a CONSUME event is seen instead of using the WaitForCompletion API.
3. Also I could notice that the SetXfer size is 80. But the buffer size is 96. Why is that?

Best Regards,
AliAsgar

0 Likes
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

Hi AliAsgar,

1) The host is reading it "instantly" (i.e. no perceptible delay from when I commit the buffer to the host receiving it). The FX3 is reading at 4MHz on a synchronous 16-bit GPIF interface. On infinite block transfers, this works great at many times this speed, it's just this byte-mode (or small packets) that's causing an issue.

2) I actually don't get any other events. I get one PROD event, I commit the buffer, and then I never see any more callbacks.

3) Buffer size is 96 to leave 16 bytes for prodHeader. 

Thanks for your questions!

0 Likes
AliAsgar
Moderator
Moderator
Moderator
1000 replies posted 250 solutions authored 750 replies posted

Hi,

When setXfer is set to 80 and if 80 bytes of data are sent and received, XFER_CPLT DMA callback event comes and the setXfer can again be called here inside the DMA callback function to activate the channel again. We tried this implementation at our end and it worked fine. Please do try this and let us know if you have any more queries.

Best Regards,
AliAsgar 

0 Likes
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

Could there be an issue with the USB Descriptor (burst length? Max packet size?) or the CyU3PSetEpConfig() which is causing this odd behavior? I have a superspeed descriptor with a max burst length of 15, max packet size of 1024, and I'm connected with superspeed. I don't understand how these parameters interact with a smaller DMA packet size. 

Some more surprising experiments:

1) With a buffer size of 96 and count of 192, in MODE_BYTE, I set up a transfer of 80. I get 87(!) PROD messages for different buffers (all then committed), and not a single XFER_CPLT. This doesn't make sense, I should have gotten at most a single PROD.

2) Same setup, but in MODE_BUFFER, I setup a transfer of 1. I got 150 PROD messages for different buffers, and no XFER_CPLT. Again, at most, should have gotten 1 PROD.

3) Size of 4096, count of 4, MODE_BUFFER, I setup a transfer of 1. I got 2 PROD messages (the second failed CyU3PDmaChannelGetBuffer) and a XFER_CPLT.

Is there a sample code which does small MANUAL transfers from GPIF to USB? I just can't find what I'm doing wrong (and this code works reliably well in large buffer MANUAL and AUTO modes).

If you create 192 buffers @ 96 bytes, can you transfer just one from GPIF to USB and get a single PROD message?

Thanks again!

0 Likes
lock attach
Attachments are accessible only for community members.
AliAsgar
Moderator
Moderator
Moderator
1000 replies posted 250 solutions authored 750 replies posted

Hi,

I have attached a firmware project below. It is a GPIF to USB firmware example. Manual channel with limited transfers. It works fine on our side. Please do have a look.

Best Regards,
AliAsgar

0 Likes
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

Lots more experiments this morning... Some questions:

  1. In your code, you setup a 96 byte buffer with a 16 byte header, then transfer 96 bytes. In the PROD function, you actually get 80 bytes. In fact, if you run the manual dma channel in byte mode and transfer 80 bytes, you will never get a XFER_COMPLETE. If, you transfer 96 bytes, you will. This doesn't make sense--where'd the other 16 bytes go? Into the next buffer? Or does it know the 96 bytes are partially accounted for in the header?
  2. How many PRODs do you get? If I tell it to transfer 1 buffer with dmaCfg.count = 16, I get 16 prods. This would seem to mean the DMA engine is filling buffers beyond the transfer I requested. Is that right? In fact, when I setup a second DMA transfer, I get the committed data from the previous transfer.
  3. How many errors from the 16 CyU3PDmaChannelCommitBuffer() do you get? I get NOT_STARTED errors for exactly as many buffers as were correctly transferred to the host.

In other words, it appears as though the DMA engine fills buffers up to dmaCfg.count even if you ask it to transfer fewer. Those buffers arrive as PROD messages after the transfer is complete. If you then try to commit those, you get NOT_STARTED errors (because the transfer is actually complete).

Since the DMA engine queueing is not at all what I need (I need a new buffer taken from the GPIF bus "now", when I start the new DMA transfer, not from a previous transfer), let's try the simplest case:

dmaCfg.size = 96;
dmaCfg.count = 1:
dmaCfg.dmaMode = CY_U3P_DMA_MODE_BUFFER;
dmaCfg.prodHeader = 16;
...
CyU3PDmaChannelCreate(&chHandle, CY_U3P_DMA_TYPE_MANUAL, &dmaCfg);
CyU3PDmaChannelSetXfer(&chHandle, 1);

 

The DMA engine now seems totally confused. I get:

  • PROD (which I commit and receive on the host)
  • XFER_COMPLETE (which I pend using a CyU3PEventSet() )
  • PROD (which gets NOT_STARTED error on commit)
  • My event is then handled which starts a new Xfer
  • That Xfer never results in anything (no PRODs, XFER_COMPLETEs, nothing)

See if you can reproduce this simple case in your code--drop the dmaCfg.count to 1, transfer 1 buffer, don't restart the xfer on XFER_COMPLETE and count how many PRODs you get. Is it 2? Did you get a NOT_STARTED error on the 2nd commit? Does this make sense?

Thanks again for looking into this!

 

0 Likes
AliAsgar
Moderator
Moderator
Moderator
1000 replies posted 250 solutions authored 750 replies posted

Hi,

1. For a MANUAL channel, the count parameter in the setXfer API is corresponding to the number of bytes transferred from the consumer socket (in this case 96). 
80 bytes are written at the producer side. This raises a PROD_EVENT interrupt. 16 bytes header is added in the callback, and the 96 bytes are sent to the consumer. Reading this 96 byte from the consumer socket raises an XFER_CPLT interrupt. If a starting a new transfer is intended, setXfer should be called when this XFER_CPLT interrupt is seen.

2. Before receiving any data on the host side form the consumer, we get dmaCfg.count number of PRODs, as all these buffers are queued up, and then based on host requests, these buffers are emptied(read).

3. I do not understand why NOT_STARTED errors are coming for you? Is it because you are trying to commit buffers, without calling setXfer API in the XFER_CPLT interrupt? This is not possible.

SetXfer controls the number of bytes that can be received by the consumer socket. It does not control the number of bytes or buffers that the producer can write.
Although if there are free buffers on the producer side, those can be written only when the channel is in active state.
Using the firmware I had shared earlier with dmaCfg.count = 1 should solve your issue.Please let us know if you have any more queries.

Best Regards,
AliAsgar

 

0 Likes
mcn
Level 2
Level 2
10 replies posted 5 questions asked 10 sign-ins

For #3: Try not calling setXfer in the XFER_CPLT callback and pend it using an event. You will get a PROD, XFER_CPLT, PROD. If you blindly commit the second PROD, you get the NOT_STARTED error. I think this is expected given what we've found out about the DMA engine. setXfer() does not limit the producer, but rather the consumer. As a result, the XFER_CPLT is saying the consumer has received everything it should, but the producer is still filling buffers. Therefore, it's possible to get a PROD after the XFER_CPLT.

You've helped immensely figure this out. Key takeaways:

  1. SetXfer() sets the number of bytes/buffers that can be consumed, not the number of buffers which will be produced. The DMA engine will produce buffers until it runs out, no matter what size the SetXfer is.
  2. dmaCfg.count = 1 ensures only a single buffer is produced for each Commit().
  3. The DMA engine can deliver a PROD after a XFER_COMPLETE.
  4. In byte mode, setXfer() sets the total bytes to be consumed. The producer will grab this many bytes minus prodHeader.

I'm going to mark this as the solution for quick reference to future generations. If this keeps you from getting support credit or something, let me know and I'll change it. Thanks for your help!

 

0 Likes