diff --git a/components/usb_device/include/usb_device.hpp b/components/usb_device/include/usb_device.hpp index 100154a532..105035a5a7 100644 --- a/components/usb_device/include/usb_device.hpp +++ b/components/usb_device/include/usb_device.hpp @@ -237,6 +237,32 @@ class UsbDevice : public BaseComponent { /// @brief Convenience overload of write_vendor() that ignores errors. bool write_vendor(std::span data); + /// @brief Bytes of free space currently in the vendor TX FIFO. + /// @return How many bytes write_vendor() can accept right now without + /// blocking, or 0 if not initialized / no vendor interface / not + /// mounted. A point-in-time hint: with a single serialized writer it + /// is stable, otherwise treat it as advisory. Use it to skip or defer + /// a streaming frame when the host has stopped draining the endpoint, + /// instead of building the frame and having write_vendor() drop it. + size_t vendor_write_available() const; + + /// @brief Bytes of free space currently in the CDC TX FIFO. + /// @return How many bytes write_cdc() can accept right now, or 0 if not + /// initialized / no CDC interface / not mounted. See + /// vendor_write_available() for usage notes. + size_t cdc_write_available() const; + + /// @brief Discard any bytes queued in the vendor TX FIFO that have not been + /// sent yet. Call this when the host goes away (e.g. on a detected + /// disconnect / stream stall) so a stale backlog (queued telemetry) is + /// not delivered to the next host that connects and mis-parsed as a + /// reply to its first command. + void vendor_write_clear(); + + /// @brief Discard any bytes queued in the CDC TX FIFO that have not been sent + /// yet. See vendor_write_clear() for usage notes. + void cdc_write_clear(); + /** * @brief Send a HID input report on the HID function's interrupt IN endpoint. * @param report_id HID report id (0 if the report descriptor has no report id; diff --git a/components/usb_device/src/usb_device.cpp b/components/usb_device/src/usb_device.cpp index 1887cd2bea..df0ee821a4 100644 --- a/components/usb_device/src/usb_device.cpp +++ b/components/usb_device/src/usb_device.cpp @@ -209,6 +209,21 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, #endif // CFG_TUD_VENDOR > 0 +// Device unmount: drop any bytes still queued in the TX FIFOs. A host that goes +// away (cable pull / re-enumeration / suspend) leaves its unread backlog in the +// software FIFO; clearing it here means the next host to mount starts from an +// empty pipe and cannot mis-parse a stale frame as the reply to its first +// command. (An abrupt tab close does NOT unmount, so it does not reach here -- +// that path relies on the streaming producer's own backpressure handling.) +void tud_umount_cb(void) { +#if (CFG_TUD_VENDOR > 0) + tud_vendor_write_clear(); +#endif +#if (CFG_TUD_CDC > 0) + tud_cdc_n_write_clear(kCdcPort); +#endif +} + #if (CFG_TUD_HID > 0) // HID: return the application-supplied report descriptor for the given instance. @@ -1142,6 +1157,34 @@ bool UsbDevice::is_vendor_connected() const { return tud_mounted(); } +size_t UsbDevice::vendor_write_available() const { +#if (CFG_TUD_VENDOR > 0) + if (!initialized_ || !config_.vendor || !tud_mounted()) + return 0; + return tud_vendor_write_available(); +#else + return 0; +#endif +} + +size_t UsbDevice::cdc_write_available() const { + if (!initialized_ || !config_.cdc || !tud_mounted()) + return 0; + return tud_cdc_n_write_available(kCdcPort); +} + +void UsbDevice::vendor_write_clear() { +#if (CFG_TUD_VENDOR > 0) + if (initialized_ && config_.vendor) + tud_vendor_write_clear(); +#endif +} + +void UsbDevice::cdc_write_clear() { + if (initialized_ && config_.cdc) + tud_cdc_n_write_clear(kCdcPort); +} + bool UsbDevice::is_hid_ready() const { #if (CFG_TUD_HID > 0) if (!initialized_ || !config_.hid)