7
\$\begingroup\$

I'm trying to understand the need for Zero packets in USB-CDC transmissions. From what I gather, this seems to touch upon some grey areas of the USB spec or in differences in implementation of host USB drivers. I'm interested in both the 'correct' behavior (as per the USB spec) and typical behavior as far as regular USB hosts are concerned (Linux, Windows, OSX). Any relevant insight would be appreciated.

  1. A hypothetical example of a USB device would be one which is transmitting over a single USB-CDC endpoint, simply continuously generating and transmitting bytes infinitely. For this example, assume that the generation of data is considerably slower than USB-CDC transmission. The approach I would like to use is collect 64 bytes of data (device packet size), and when 64 bytes are ready, transmit a single packet. No zero packets will ever be sent, no short packets will ever sent. Any host-initiated polling will be NAKed when there is no complete packet ready.

    1. Is this a valid way to transmit CDC data?

    2. Are there any USB hosts that are known to have issues with this approach?

    3. If the USB device is a composite USB device, and one of the provided endpoints is a CDC endpoint as described, is there any change to the answers of a. and b.?

  2. In the device of 1., if, instead of sending NAKs when there is nothing to send, the device were to send back a 0 packet, is it likely to still work? How does the overall transmission change - in terms of how it is interpreted by a serial port emulator and/or the effective bandwidth?

  3. If the device, instead, transmits a few bytes, waits for a response. The length of these 'few' bytes is not known apriori. The host must be able to receive, process, and respond to these few bytes - they can't just sit in a hardware / driver buffer waiting for the entire transmission to end. Assume for the moment that USB-CDC recieve-by-the-device is somehow working and stable. All I'm interested in in zero packet transmission from the device.

    1. If the number of bytes to be sent out is not a multiple of 64 bytes (packet size), then simply ending with the 'short' packet would be enough to mark end of transmission, yes?

    2. If the number of bytes to be sent is a multiple of 64 (packet size), then what happens if a zero packet is not sent after the last transmitted packet to mark end of transmission?

\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ From what I gather, the size of the endpoint buffer is somehow determined by the content of the descriptor. It seems to be a device-defined thing (though I've only actually seen 64 byte and 512 byte). Do you see this behavior even if windows is told that the buffer is only 16 bytes long? \$\endgroup\$ Commented Aug 23, 2016 at 1:56
  • \$\begingroup\$ @Chin: Other than enpoints 0 IN and OUT, the device defines the buffer sizes. These can be up to 64 bytes for bulk endpoints. The device communicates the configuration of the endpoints via descriptors requested by the host during enumeration. \$\endgroup\$ Commented Aug 23, 2016 at 1:59
3
\$\begingroup\$

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."

\$\endgroup\$
2
  • \$\begingroup\$ What exactly do you mean by 'retire' an IRP? \$\endgroup\$ Commented Aug 23, 2016 at 9:48
  • \$\begingroup\$ This is not what I mean, it is what the USB specification says. USB pipe usually starts by software with certain expectations from application layer. When the transfer satisfies some application-level protocol, the request is "retired" (aka "completed"), the corresponding queue heads and buffers are removed from schedule (or whatever the host software is doing). Usually after checking multiple statuses, the driver starts new transfer. Al this is above USB and belongs to USB controller specifications, UHCI/OHCI, or EHCI, or the latest xHCI. \$\endgroup\$ Commented Aug 23, 2016 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.