tinyusb icon indicating copy to clipboard operation
tinyusb copied to clipboard

Provide a way to reset a CDC device with unsent flushed data

Open dkonigsberg opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe. USB CDC does not appear to provide a reliable way of knowing when there is a user application on the host that's actually connected to the device. At best there's the DTR flag, but it cannot always be trusted. As such, the only way to know if you're transmitting data to nowhere is to check after the fact. (Either by getting a 0 response after successive calls to tud_cdc_write_flush() or by waiting on tud_cdc_tx_complete_cb() with a timeout.)

Regardless, when you get to this point, data has already been flushed from tinyUSB's internal buffers to the transmit buffer of the device's USB peripheral. Calling tud_cdc_write_clear() will clear out unflushed data, but it won't do anything about flushed data. The flushed data will sit in the device peripheral's own buffer indefinitely, until the user opens a terminal app on the host and connects to the device.

Describe the solution you'd like I would like a way to reset the state of the USB endpoint so that the peripheral's transmit buffer gets cleared. I've done a fair bit of tinkering along these lines, and one possible solution seems to involve explicitly stalling then clearing the endpoint. Specifically, I wrote a function inside cdc_device.c that looks like this:

uint32_t tud_cdc_n_abort_transfer (uint8_t itf)
{
  cdcd_interface_t* p_cdc = &_cdcd_itf[itf];

  // Skip if usb is not ready yet
  TU_VERIFY( tud_ready(), 0 );

  uint8_t const rhport = TUD_OPT_RHPORT;

  // Claim the endpoint, if not marked busy
  usbd_edpt_claim(rhport, p_cdc->ep_in);

  // Stall the endpoint
  usbd_edpt_stall(rhport, p_cdc->ep_in);

  // Clear the stalled endpoint
  usbd_edpt_clear_stall(rhport, p_cdc->ep_in);

  // Release the endpoint
  usbd_edpt_release(rhport, p_cdc->ep_in);

  return 0;
}

It may not be correct, and it may not be the most robust solution, but in my own limited testing it does get the job done. I'd obviously prefer a more official way of handling this case.

dkonigsberg avatar Oct 06 '21 15:10 dkonigsberg

@dkonigsberg thanks for the issue, it seems like you want to reset endpoints for CDC. Could you

  1. Explain more about its use case,
  2. what is the issue you are dealing with and how to reproduce it

hathach avatar Oct 07 '21 08:10 hathach

The use case is wanting to make data sent out through a USB CDC device go to the bitbucket if that device is connected to a host, but that host isn't actually listening. (e.g. device plugged into a PC, but no terminal app has actually opened the resulting serial device on that PC) As far as I can tell, FTDI FT232 devices (which aren't CDC) behave that way, and the CDC device in one of my embedded debug probes does too.

There are two things that need to happen to gracefully deal with this situation:

  1. Attempt to detect whether anyone is listening on the host.
  2. Prevent data from being queued up to send if that detection fails to stop you from attempting to send data.

FYI, the device I'm working with is an STM32L072KB.

In order to reproduce the situation, do the following steps:

  1. Plug the tinyUSB USB CDC device into a host PC, but do not open any app that actually connects to the serial device
  2. Call tud_cdc_write() with a chunk of data to send.
  3. Call tud_cdc_write_flush() to flush that data from tinyUSB into the device's USB peripheral
  4. Wait a bit, note that tud_cdc_tx_complete_cb() doesn't get called.
  5. Open a terminal app on the host PC, notice that the data comes through.

The goal is to have a way of preventing any data from being received by the host in step 5, if I detect a timeout in step 4.

P.S. I've also observed strange behavior where sometimes in step 5 the host will apparently echo that line back to the device, but its unclear who is actually at fault. (Wireshark traces seem to implicate the host side, which is why I haven't opened a ticket about that.)

dkonigsberg avatar Oct 07 '21 16:10 dkonigsberg

I got here from googling when having a similar issue. Basically if you write into the cdc buffer (or vendor ifc buffer in my case) and then the host stops reading it just sits in there. Then when you say open up your PC program again and start reading again it will get that old data. It would be nice to have a way to flush it out on the device side if there is a timeout, but in my case I just made sure to write my host program and communication protocol in a way that at startup it reads out any existing data then resets the system to a known start state -i.e. by writing a reset/start command once there is no more stale data left to read.

kjrandez avatar Nov 12 '22 00:11 kjrandez