libftdi Archives

Subject: [PATCH] Deprecate ftdi_usb_purge_* functions & add ftdi_tc*flush methods

From: "Eric L. Schott" <els6@xxxxxxx>
To: libftdi@xxxxxxxxxxxxxxxxxxxxxxx
Date: Thu, 18 Oct 2018 11:20:33 -0400
The libFTDI implementation of the purge functionality is incorrect
because the library's authors understanding of the perspective of the
SIO_RESET_PURGE_TX and SIO_RESET_PURGE_RX was the opposite of the
vendor's documentation. That is, the library authors viewed purge TX
as purging data sent from the CPU through the FTDI chip to the
attached serial device. The vendor viewed purge TX from the chip's
perspective as data received from the attached serial device and
transmitted through the FTDI chip to the CPU.

In order to maintain the legacy API, the old functions are now marked
as deprecated and new functions are introduced.  The mapping is
      Deprecated function         Replacement function
      ------------------------    ----------------------
      ftdi_usb_purge_rx_buffer    ftdi_tciflush
      ftdi_usb_purge_tx_buffer    ftdi_tcoflush
      ftdi_usb_purge_buffers      ftdi_tcioflush
      Ftdi::Context::flush        Ftdi::Context::tcflush

The provided patch is at the user's risk. No licensing of this patch can
preclude its use by the government of the United States or The
Applied Research Laboratory of The Pennsylvania State University.
---
 doc/Doxyfile.in   |  6 ++--
 examples/async.c  |  4 +--
 ftdipp/ftdi.cpp   | 27 ++++++++++++++
 ftdipp/ftdi.hpp   |  3 +-
 src/ftdi.c        | 92 +++++++++++++++++++++++++++++++++++++++++++++++
 src/ftdi.h        | 58 +++++++++++++++++++++++++++---
 src/ftdi_stream.c |  4 +--
 7 files changed, 182 insertions(+), 12 deletions(-)

diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 84006c8..a4f59ee 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -1965,7 +1965,7 @@ ENABLE_PREPROCESSING   = YES
 # The default value is: NO.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

-MACRO_EXPANSION        = NO
+MACRO_EXPANSION        = YES

# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and
@@ -1973,7 +1973,7 @@ MACRO_EXPANSION        = NO
 # The default value is: NO.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

-EXPAND_ONLY_PREDEF     = NO
+EXPAND_ONLY_PREDEF     = YES

 # If the SEARCH_INCLUDES tag is set to YES, the include files in the
 # INCLUDE_PATH will be searched if a #include is found.
@@ -2005,7 +2005,7 @@ INCLUDE_FILE_PATTERNS  =
 # recursively expanded use the := operator instead of the = operator.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

-PREDEFINED             = LIBFTDI_LINUX_ASYNC_MODE
+PREDEFINED             = LIBFTDI_LINUX_ASYNC_MODE=

# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The
diff --git a/examples/async.c b/examples/async.c
index 0589479..18989c1 100644
--- a/examples/async.c
+++ b/examples/async.c
@@ -93,9 +93,9 @@ int main(int argc, char **argv)
         goto do_deinit;
     }
     ftdi_list_free(&devlist);
-    int err = ftdi_usb_purge_buffers(ftdi);
+    int err = ftdi_tcioflush(ftdi);
     if (err != 0) {
-        fprintf(stderr, "ftdi_usb_purge_buffer: %d: %s\n",
+        fprintf(stderr, "ftdi_tcioflush: %d: %s\n",
                 err, ftdi_get_error_string(ftdi));
         retval = -1;
         goto do_deinit;
diff --git a/ftdipp/ftdi.cpp b/ftdipp/ftdi.cpp
index 7bf4ca6..dd777be 100644
--- a/ftdipp/ftdi.cpp
+++ b/ftdipp/ftdi.cpp
@@ -27,6 +27,7 @@ This exception does not invalidate any other reasons why a work based
 on this file might be covered by the GNU General Public License.
 */
 #include <libusb.h>
+#define _FTDI_DISABLE_DEPRECATED
 #include "ftdi.hpp"
 #include "ftdi_i.h"
 #include "ftdi.h"
@@ -168,6 +169,32 @@ int Context::flush(int mask)
     return ret;
 }

+int Context::tcflush(int mask)
+{
+    int ret;
+
+    switch (mask & (Input | Output)) {
+    case Input:
+        ret = ftdi_tciflush(d->ftdi);
+        break;
+
+    case Output:
+        ret = ftdi_tcoflush(d->ftdi);
+        break;
+
+    case Input | Output:
+        ret = ftdi_tcioflush(d->ftdi);
+        break;
+
+    default:
+        // Emulate behavior of previous version.
+        ret = 1;
+        break;
+    }
+
+    return ret;
+}
+
 int Context::set_interface(enum ftdi_interface interface)
 {
     return ftdi_set_interface(d->ftdi, interface);
diff --git a/ftdipp/ftdi.hpp b/ftdipp/ftdi.hpp
index 2289a84..a5ede3c 100644
--- a/ftdipp/ftdi.hpp
+++ b/ftdipp/ftdi.hpp
@@ -85,7 +85,8 @@ public:
     int open(const std::string& description);
     int close();
     int reset();
-    int flush(int mask = Input|Output);
+    int DEPRECATED(flush)(int mask = Input|Output);
+    int tcflush(int mask = Input|Output);
     int set_interface(enum ftdi_interface interface);
     void set_usb_device(struct libusb_device_handle *dev);

diff --git a/src/ftdi.c b/src/ftdi.c
index 52266d0..155a770 100644
--- a/src/ftdi.c
+++ b/src/ftdi.c
@@ -35,6 +35,8 @@
 #include <stdlib.h>

 #include "ftdi_i.h"
+/* Prevent deprecated messages when building library */
+#define _FTDI_DISABLE_DEPRECATED
 #include "ftdi.h"
 #include "ftdi_version_i.h"

@@ -1016,6 +1018,7 @@ int ftdi_usb_reset(struct ftdi_context *ftdi)

 /**
     Clears the read buffer on the chip and the internal read buffer.
+    This is the correct behavior for an RX flush.

     \param ftdi pointer to ftdi_context

@@ -1023,6 +1026,36 @@ int ftdi_usb_reset(struct ftdi_context *ftdi)
     \retval -1: read buffer purge failed
     \retval -2: USB device unavailable
 */
+int ftdi_tciflush(struct ftdi_context *ftdi)
+{
+    if (ftdi == NULL || ftdi->usb_dev == NULL)
+        ftdi_error_return(-2, "USB device unavailable");
+
+    if (libusb_control_transfer(ftdi->usb_dev, FTDI_DEVICE_OUT_REQTYPE,
+                                SIO_RESET_REQUEST, SIO_TCIFLUSH,
+ ftdi->index, NULL, 0, ftdi->usb_write_timeout) < 0)
+        ftdi_error_return(-1, "FTDI purge of RX buffer failed");
+
+    // Invalidate data in the readbuffer
+    ftdi->readbuffer_offset = 0;
+    ftdi->readbuffer_remaining = 0;
+
+    return 0;
+}
+
+
+/**
+    Clears the write buffer on the chip and the internal read buffer.
+    This is incorrect behavior for an RX flush.
+
+    \param ftdi pointer to ftdi_context
+
+    \retval  0: all fine
+    \retval -1: write buffer purge failed
+    \retval -2: USB device unavailable
+
+    \deprecated Use \ref ftdi_tciflush(struct ftdi_context *ftdi)
+*/
 int ftdi_usb_purge_rx_buffer(struct ftdi_context *ftdi)
 {
     if (ftdi == NULL || ftdi->usb_dev == NULL)
@@ -1042,6 +1075,7 @@ int ftdi_usb_purge_rx_buffer(struct ftdi_context *ftdi)

 /**
     Clears the write buffer on the chip.
+    This is correct behavior for a TX flush.

     \param ftdi pointer to ftdi_context

@@ -1049,6 +1083,32 @@ int ftdi_usb_purge_rx_buffer(struct ftdi_context *ftdi)
     \retval -1: write buffer purge failed
     \retval -2: USB device unavailable
 */
+int ftdi_tcoflush(struct ftdi_context *ftdi)
+{
+    if (ftdi == NULL || ftdi->usb_dev == NULL)
+        ftdi_error_return(-2, "USB device unavailable");
+
+    if (libusb_control_transfer(ftdi->usb_dev, FTDI_DEVICE_OUT_REQTYPE,
+                                SIO_RESET_REQUEST, SIO_TCOFLUSH,
+ ftdi->index, NULL, 0, ftdi->usb_write_timeout) < 0)
+        ftdi_error_return(-1, "FTDI purge of TX buffer failed");
+
+    return 0;
+}
+
+
+/**
+    Clears the read buffer on the chip.
+    This is incorrect behavior for a TX flush.
+
+    \param ftdi pointer to ftdi_context
+
+    \retval  0: all fine
+    \retval -1: read buffer purge failed
+    \retval -2: USB device unavailable
+
+    \deprecated Use \ref ftdi_tcoflush(struct ftdi_context *ftdi)
+*/
 int ftdi_usb_purge_tx_buffer(struct ftdi_context *ftdi)
 {
     if (ftdi == NULL || ftdi->usb_dev == NULL)
@@ -1062,8 +1122,38 @@ int ftdi_usb_purge_tx_buffer(struct ftdi_context *ftdi)
     return 0;
 }

+/**
+    Clears the RX and TX FIFOs on the chip and the internal read buffer.
+    This is correct behavior for both RX and TX flush.
+
+    \param ftdi pointer to ftdi_context
+
+    \retval  0: all fine
+    \retval -1: read buffer purge failed
+    \retval -2: write buffer purge failed
+    \retval -3: USB device unavailable
+*/
+int ftdi_tcioflush(struct ftdi_context *ftdi)
+{
+    int result;
+
+    if (ftdi == NULL || ftdi->usb_dev == NULL)
+        ftdi_error_return(-3, "USB device unavailable");
+
+    result = ftdi_tcoflush(ftdi);
+    if (result < 0)
+        return -1;
+
+    result = ftdi_tciflush(ftdi);
+    if (result < 0)
+        return -2;
+
+    return 0;
+}
+
 /**
     Clears the buffers on the chip and the internal read buffer.
+    While coded incorrectly, the result is satisfactory,

     \param ftdi pointer to ftdi_context

@@ -1071,6 +1161,8 @@ int ftdi_usb_purge_tx_buffer(struct ftdi_context *ftdi)
     \retval -1: read buffer purge failed
     \retval -2: write buffer purge failed
     \retval -3: USB device unavailable
+
+    \deprecated Use \ref ftdi_tcioflush(struct ftdi_context *ftdi)
 */
 int ftdi_usb_purge_buffers(struct ftdi_context *ftdi)
 {
diff --git a/src/ftdi.h b/src/ftdi.h
index f170881..d740617 100644
--- a/src/ftdi.h
+++ b/src/ftdi.h
@@ -22,6 +22,12 @@
 #include <sys/time.h>
 #endif

+#ifdef _FTDI_DISABLE_DEPRECATED
+#define _Ftdi_Pragma(_msg)
+#else
+#define _Ftdi_Pragma(_msg) _Pragma(_msg)
+#endif
+
 /* 'interface' might be defined as a macro on Windows, so we need to
  * undefine it so as not to break the current libftdi API, because
  * struct ftdi_context has an 'interface' member
@@ -174,8 +180,45 @@ enum ftdi_module_detach_mode


 #define SIO_RESET_SIO 0
+
+/* ** WARNING ** SIO_RESET_PURGE_RX or SIO_RESET_PURGE_TX are values used
+ * internally by libftdi to purge the RX and/or TX FIFOs (buffers).
+ * APPLICATION PROGRAMS SHOULD NOT BE USING THESE VALUES. Application
+ * programs should use one of the ftdi_tciflush, ftdi_tcoflush, or
+ * ftdi_tcioflush functions which emulate the Linux serial port tcflush(3)
+ * function.
+ *
+ * History:
+ *
+ * The defintions for these values are with respect to the FTDI chip, not the + * CPU. That is, when the FTDI chip receives a USB control transfer request
+ * with the command SIO_RESET_PURGE_RX, the FTDI chip empties the FIFO
+ * containing data received from the CPU awaiting tranfer out the serial
+ * port to the connected serial device (e.g., a modem). Likewise, upon
+ * reception of the SIO_RESET_PURGE_TX command, the FTDI chip empties the
+ * FIFO of data received from the attached serial device destined to be
+ * transmitted to the CPU.
+ *
+ * Unfortunately the coding of the previous releases of libfti assumed these
+ * commands had the opposite effect. This resulted in the function
+ * ftdi_usb_purge_tx_buffer clearing data received from the attached serial
+ * device.  Similarly, the function ftdi_usb_purge_rx_buffer cleared the
+ * FTDI FIFO containing data to be transmitted to the attached serial
+ * device.  More seriously, this latter function clear the libftid's
+ * internal buffer of data received from the serial device, destined
+ * to the application program.
+ */
+#ifdef __GNUC__
+#define SIO_RESET_PURGE_RX _Ftdi_Pragma("GCC warning \"SIO_RESET_PURGE_RX\" deprecated: - use tciflush() method") 1 +#define SIO_RESET_PURGE_TX _Ftdi_Pragma("GCC warning \"SIO_RESET_PURGE_RX\" deprecated: - use tcoflush() method") 2
+#else
+#pragma message("WARNING: You need to implement deprecated #define for this compiler")
 #define SIO_RESET_PURGE_RX 1
 #define SIO_RESET_PURGE_TX 2
+#endif
+/* New names for the values used internally to flush (purge). */
+#define SIO_TCIFLUSH 2
+#define SIO_TCOFLUSH 1

 #define SIO_DISABLE_FLOW_CTRL 0x0
 #define SIO_RTS_CTS_HS (0x1 << 8)
@@ -195,14 +238,18 @@ enum ftdi_module_detach_mode
    (taken from libusb) */
 #define FTDI_URB_USERCONTEXT_COOKIE ((void *)0x1)

+#ifdef _FTDI_DISABLE_DEPRECATED
+#define DEPRECATED(func) func
+#else
 #ifdef __GNUC__
-#define DEPRECATED(func) func __attribute__ ((deprecated))
+#define DEPRECATED(func) __attribute__ ((deprecated)) func
 #elif defined(_MSC_VER)
 #define DEPRECATED(func) __declspec(deprecated) func
 #else
#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
 #define DEPRECATED(func) func
 #endif
+#endif

 struct ftdi_transfer_control
 {
@@ -509,9 +556,12 @@ extern "C"

     int ftdi_usb_close(struct ftdi_context *ftdi);
     int ftdi_usb_reset(struct ftdi_context *ftdi);
-    int ftdi_usb_purge_rx_buffer(struct ftdi_context *ftdi);
-    int ftdi_usb_purge_tx_buffer(struct ftdi_context *ftdi);
-    int ftdi_usb_purge_buffers(struct ftdi_context *ftdi);
+    int ftdi_tciflush(struct ftdi_context *ftdi);
+    int ftdi_tcoflush(struct ftdi_context *ftdi);
+    int ftdi_tcioflush(struct ftdi_context *ftdi);
+    int DEPRECATED(ftdi_usb_purge_rx_buffer(struct ftdi_context *ftdi));
+    int DEPRECATED(ftdi_usb_purge_tx_buffer(struct ftdi_context *ftdi));
+    int DEPRECATED(ftdi_usb_purge_buffers(struct ftdi_context *ftdi));

     int ftdi_set_baudrate(struct ftdi_context *ftdi, int baudrate);
int ftdi_set_line_property(struct ftdi_context *ftdi, enum ftdi_bits_type bits,
diff --git a/src/ftdi_stream.c b/src/ftdi_stream.c
index 1dc46ea..75f1ace 100644
--- a/src/ftdi_stream.c
+++ b/src/ftdi_stream.c
@@ -172,9 +172,9 @@ ftdi_readstream(struct ftdi_context *ftdi,
     }

     /* Purge anything remaining in the buffers*/
-    if (ftdi_usb_purge_buffers(ftdi) < 0)
+    if (ftdi_tcioflush(ftdi) < 0)
     {
-        fprintf(stderr,"Can't Purge\n");
+        fprintf(stderr,"Can't flush FIFOs & buffers\n");
         return 1;
     }

--
2.17.1


--
libftdi - see http://www.intra2net.com/en/developer/libftdi for details.
To unsubscribe send a mail to libftdi+unsubscribe@xxxxxxxxxxxxxxxxxxxxxxx
Current Thread