diff --git a/components/driver/usb_serial_jtag/usb_serial_jtag.c b/components/driver/usb_serial_jtag/usb_serial_jtag.c index 0349074571..3163ae3fa0 100644 --- a/components/driver/usb_serial_jtag/usb_serial_jtag.c +++ b/components/driver/usb_serial_jtag/usb_serial_jtag.c @@ -94,7 +94,7 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { uint32_t sent_size = usb_serial_jtag_write_and_flush(queued_buff, queued_size); if (sent_size < queued_size) { - // Not all bytes could be sent at once, stash the unwritten bytes in a tx buffer + // Not all bytes could be sent at once; stash the unwritten bytes in a tx buffer // stash_size will not larger than USB_SER_JTAG_ENDP_SIZE because queued_size is got from xRingbufferReceiveUpToFromISR size_t stash_size = queued_size - sent_size; memcpy(p_usb_serial_jtag_obj->tx_data_buf, &queued_buff[sent_size], stash_size); @@ -109,6 +109,14 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { vRingbufferReturnItemFromISR(p_usb_serial_jtag_obj->tx_ring_buf, queued_buff, &xTaskWoken); } usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); + } else { + // The last transmit may have sent a full EP worth of data. The host will interpret + // this as a transaction that hasn't finished yet and keep the data in its internal + // buffers rather than releasing it to the program listening on the CDC serial port. + // We need to flush again in order to send a 0-byte packet that ends the transaction. + usb_serial_jtag_ll_txfifo_flush(); + // Note that since this doesn't re-enable USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY, the + // flush will not by itself cause this ISR to be called again. } } else { usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); diff --git a/components/hal/esp32c3/include/hal/usb_serial_jtag_ll.h b/components/hal/esp32c3/include/hal/usb_serial_jtag_ll.h index 2f8b667eb2..1a62070718 100644 --- a/components/hal/esp32c3/include/hal/usb_serial_jtag_ll.h +++ b/components/hal/esp32c3/include/hal/usb_serial_jtag_ll.h @@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void) * @brief Flushes the TX buffer, that is, make it available for the * host to pick up. * - * @note When fifo is full (with 64 byte), HW will flush the buffer automatically. - * It won't be executed if there is nothing in the fifo. + * @note When fifo is full (with 64 byte), HW will flush the buffer automatically, + * if this function is called directly after, this effectively turns into a + * no-op. Because a 64-byte packet will be interpreted as a not-complete USB + * transaction, you need to transfer either more data or a zero-length packet + * for the data to actually end up at the program listening to the CDC-ACM + * serial port. To send a zero-length packet, call + * usb_serial_jtag_ll_txfifo_flush() again when + * usb_serial_jtag_ll_txfifo_writable() returns true. * * @return na */ diff --git a/components/hal/esp32c6/include/hal/usb_serial_jtag_ll.h b/components/hal/esp32c6/include/hal/usb_serial_jtag_ll.h index f29f12604a..e0776eb923 100644 --- a/components/hal/esp32c6/include/hal/usb_serial_jtag_ll.h +++ b/components/hal/esp32c6/include/hal/usb_serial_jtag_ll.h @@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void) * @brief Flushes the TX buffer, that is, make it available for the * host to pick up. * - * @note When fifo is full (with 64 byte), HW will flush the buffer automatically. - * It won't be executed if there is nothing in the fifo. + * @note When fifo is full (with 64 byte), HW will flush the buffer automatically, + * if this function is called directly after, this effectively turns into a + * no-op. Because a 64-byte packet will be interpreted as a not-complete USB + * transaction, you need to transfer either more data or a zero-length packet + * for the data to actually end up at the program listening to the CDC-ACM + * serial port. To send a zero-length packet, call + * usb_serial_jtag_ll_txfifo_flush() again when + * usb_serial_jtag_ll_txfifo_writable() returns true. * * @return na */ diff --git a/components/hal/esp32h2/include/hal/usb_serial_jtag_ll.h b/components/hal/esp32h2/include/hal/usb_serial_jtag_ll.h index cdd5031615..3e3cf1cea2 100644 --- a/components/hal/esp32h2/include/hal/usb_serial_jtag_ll.h +++ b/components/hal/esp32h2/include/hal/usb_serial_jtag_ll.h @@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void) * @brief Flushes the TX buffer, that is, make it available for the * host to pick up. * - * @note When fifo is full (with 64 byte), HW will flush the buffer automatically. - * It won't be executed if there is nothing in the fifo. + * @note When fifo is full (with 64 byte), HW will flush the buffer automatically, + * if this function is called directly after, this effectively turns into a + * no-op. Because a 64-byte packet will be interpreted as a not-complete USB + * transaction, you need to transfer either more data or a zero-length packet + * for the data to actually end up at the program listening to the CDC-ACM + * serial port. To send a zero-length packet, call + * usb_serial_jtag_ll_txfifo_flush() again when + * usb_serial_jtag_ll_txfifo_writable() returns true. * * @return na */ diff --git a/components/hal/esp32s3/include/hal/usb_serial_jtag_ll.h b/components/hal/esp32s3/include/hal/usb_serial_jtag_ll.h index 3eecc0702f..ac7c849120 100644 --- a/components/hal/esp32s3/include/hal/usb_serial_jtag_ll.h +++ b/components/hal/esp32s3/include/hal/usb_serial_jtag_ll.h @@ -161,6 +161,15 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void) * @brief Flushes the TX buffer, that is, make it available for the * host to pick up. * + * @note When fifo is full (with 64 byte), HW will flush the buffer automatically, + * if this function is called directly after, this effectively turns into a + * no-op. Because a 64-byte packet will be interpreted as a not-complete USB + * transaction, you need to transfer either more data or a zero-length packet + * for the data to actually end up at the program listening to the CDC-ACM + * serial port. To send a zero-length packet, call + * usb_serial_jtag_ll_txfifo_flush() again when + * usb_serial_jtag_ll_txfifo_writable() returns true. + * * @return na */ static inline void usb_serial_jtag_ll_txfifo_flush(void) diff --git a/components/vfs/vfs_usb_serial_jtag.c b/components/vfs/vfs_usb_serial_jtag.c index d17a2a4d6c..afbb9dccd0 100644 --- a/components/vfs/vfs_usb_serial_jtag.c +++ b/components/vfs/vfs_usb_serial_jtag.c @@ -270,6 +270,10 @@ static int usb_serial_jtag_fsync(int fd) while ((esp_timer_get_time() - s_ctx.last_tx_ts) < TX_FLUSH_TIMEOUT_US) { if (usb_serial_jtag_ll_txfifo_writable()) { s_ctx.last_tx_ts = esp_timer_get_time(); + //The last transfer may have been a 64-byte one. Flush again in order to + //send a 0-byte packet to indicate the end of the USB transfer, otherwise + //those 64 bytes will get stuck in the hosts buffer. + usb_serial_jtag_ll_txfifo_flush(); break; } } diff --git a/docs/en/api-guides/usb-serial-jtag-console.rst b/docs/en/api-guides/usb-serial-jtag-console.rst index 5188e811bf..de309b6c1e 100644 --- a/docs/en/api-guides/usb-serial-jtag-console.rst +++ b/docs/en/api-guides/usb-serial-jtag-console.rst @@ -89,6 +89,10 @@ For data transmitted from {IDF_TARGET_NAME} to PC Terminal (e.g., stdout, logs), For data transmitted from the PC Terminal to {IDF_TARGET_NAME} (e.g., console commands), many PC Terminals wait for the {IDF_TARGET_NAME} to ingest the bytes before allowing you to send more data. This is in contrast to using a USB-to-Serial (UART) bridge chip, which always ingests the bytes and sends them to a (possibly not listening) {IDF_TARGET_NAME}. +.. note:: + + In rare cases it's possible that data sent from the {IDF_TARGET_NAME} to the host gets 'stuck' in host memory. Sending more data will get it 'unstuck', but if the application does not send more data, depending on the driver, this data needs to be flushed to the host manually. The non-blocking (default) driver and the VFS implementation will flush automatically after a newline. The blocking (interrupt-based) driver will automatically flush when its transmit buffer becomes empty. + Sleep Mode Considerations -------------------------