USB CDC : Help with Zero Packets

Apparently, some host device drivers, like in Windows, use the indication that you have no more data immediately to pass accumulated data up to the app. Without that, Windows apparently buffers the data and passes up to the app only when some limit is reached (seems to be 64 bytes, but not sure).

USB takes a less than full IN (received from device) packet as indication of no more immediate data. This means if you have no more data and the previous IN packet filled the buffer, then you have to send a 0 length packet in response to the next IN token. You would think that NACKing the next IN token clearly indicates you don't have immediate data, but apparently that's not correct for USB.

I just ran into this issue head on. I've been using my PIC 18 USB code for many years and over a dozen projects without issues. Up until now, I used 64 byte buffers for the app data endpoints. I just did another USB device, but this time using a smaller PIC 18 where I couldn't afford the three 64 byte buffers for each endpoint. I used 16 byte buffers instead.

This PIC was receiving information over a serial connection, then sending it on to the host via USB. Communication sometimes stalled. This was traced back to happening after a full 16 byte packet was sent, then no more data available. Windows kept the 16 bytes without passing them up to the application.

We temporarily kludged around this by having the serial data stream contain NULL commands occasionally when nothing else was going on. These are only 1 byte long. If the above case happened, Windows would keep the 16 bytes. Then a packet would come along shortly containing only the NULL command byte. Since that is not a full packet, Windows considers that the end of immediate data and releases the accumulated 17 bytes to the applications.

When I get back next week, I'll fix my PIC 18 USB code to send a empty packet whenever the previous packet was full and there is nothing new to send. Then it will NACK subsequent IN tokens. Currently it NACKs IN tokens whenever it doesn't have anything immediate to send.

Since I haven't made the change yet, I can't say for sure that this was the problem and that the intended solution addresses it. However, the occasional NULL command workaround worked, and the USB spec does seem to agree that you should send a non-full packet whenever there is no more immediate data.

I still don't understand why NACKing the next IN token doesn't convey the same information, but apparently it doesn't.

Update

I have meanwhile fixed my PIC 18 USB stack. It now sends one zero-length packet if the previous packet was full length, and there is nothing immediately available to send. After non-full-length packets, which includes the deliberate zero-length packets, with nothing immediate to send, IN tokens are NACKed as before. I believe this is now following the letter of the USB spec. A USB analyzer shows the correct sequence of packets according to this interpretation.

The NULL-sending temporary kludge mentioned above was removed, and everything seems to work correctly.


This is how the data flow control is historically implemented in CDC. This comes form the standard data flow model for USB pipes, see Section 5.3.2 "Pipes" of USB 2.0 Specifications:

"A software client normally requests data transfers via I/O Request Packets (IRPs) to a pipe and then either waits or is notified when they are completed.

[snip]

An IRP may require multiple data payloads to move the client data over the bus. The data payloads for such a multiple data payload IRP are expected to be of the maximum packet size until the last data payload that contains the remainder of the overall IRP. See the description of each transfer type for more details. For such an IRP, short packets (i.e., less than maximum-sized data payloads) on input that do not completely fill an IRP data buffer can have one of two possible meanings, depending upon the expectations of a client:

• A client can expect a variable-sized amount of data in an IRP. In this case, a short packet that does not fill an IRP data buffer can be used simply as an in-band delimiter to indicate “end of unit of data.” The IRP should be retired without error and the Host Controller should advance to the next IRP.

• A client can expect a specific-sized amount of data. In this case, a short packet that does not fill an IRP data buffer is an indication of an error. The IRP should be retired, the pipe should be stalled, and any pending IRPs associated with the pipe should also be retired.

Because the Host Controller must behave differently in the two cases and cannot know on its own which way to behave for a given IRP; it is possible to indicate per IRP which behavior the client desires.

An endpoint can inform the host that it is busy by responding with NAK. NAKs are not used as a retire condition for returning an IRP to a software client. Any number of NAKs can be encountered during the processing of a given IRP. A NAK response to a transaction does not constitute an error and is not counted as one of the three errors described above."

Tags:

Usb

Usb Device