From 8f3100f47335bb7ff8c7135b22ed154fc6face44 Mon Sep 17 00:00:00 2001
From: Micah Elizabeth Scott <micah@scanlime.org>
Date: Sun, 20 Oct 2013 10:14:38 -0700
Subject: [PATCH] fcserver: Limit the number of frames to queue as USB
 transactions

This can get out of hand if the client is producing frames faster than our USB device accepts them, leading to large kernel-space queues and high latency. This patch causes intermediate frames to be dropped, but it will always update the device with the latest frame buffer state.
---
 server/fcdevice.cpp | 40 +++++++++++++++++++++++++++++++++-------
 server/fcdevice.h   | 12 ++++++++++--
 2 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/server/fcdevice.cpp b/server/fcdevice.cpp
index 82d9d59..5d51a03 100644
--- a/server/fcdevice.cpp
+++ b/server/fcdevice.cpp
@@ -28,9 +28,9 @@
 #include <stdio.h>
 
 
-FCDevice::Transfer::Transfer(FCDevice *device, void *buffer, int length)
+FCDevice::Transfer::Transfer(FCDevice *device, void *buffer, int length, PacketType type)
     : transfer(libusb_alloc_transfer(0)),
-      device(device)
+      device(device), type(type)
 {
     libusb_fill_bulk_transfer(transfer, device->mHandle,
         OUT_ENDPOINT, (uint8_t*) buffer, length, FCDevice::completeTransfer, this, 2000);
@@ -43,7 +43,7 @@ FCDevice::Transfer::~Transfer()
 
 FCDevice::FCDevice(libusb_device *device, bool verbose)
     : USBDevice(device, verbose),
-      mConfigMap(0)
+      mConfigMap(0), mNumFramesPending(0), mFrameWaitingForSubmit(false)
 {
     mSerial[0] = '\0';
 
@@ -143,7 +143,7 @@ void FCDevice::configureDevice(const Value &config)
     writeFirmwareConfiguration();
 }
 
-void FCDevice::submitTransfer(Transfer *fct)
+bool FCDevice::submitTransfer(Transfer *fct)
 {
     /*
      * Submit a new USB transfer. The Transfer object is guaranteed to be freed eventually.
@@ -157,8 +157,11 @@ void FCDevice::submitTransfer(Transfer *fct)
             std::clog << "Error submitting USB transfer: " << libusb_strerror(libusb_error(r)) << "\n";
         }
         delete fct;
+        return false;
+
     } else {
         mPending.insert(fct);
+        return true;
     }
 }
 
@@ -173,6 +176,19 @@ void FCDevice::completeTransfer(struct libusb_transfer *transfer)
     FCDevice *self = fct->device;
 
     if (self) {
+        switch (fct->type) {
+
+            case FRAME:
+                self->mNumFramesPending--;
+                if (self->mFrameWaitingForSubmit) {
+                    self->writeFramebuffer();
+                }
+                break;
+
+            default:
+                break;
+        }
+
         self->mPending.erase(fct);
     }
 
@@ -268,11 +284,21 @@ void FCDevice::writeFramebuffer()
      * Asynchronously write the current framebuffer.
      * Note that the OS will copy our framebuffer at submit-time.
      *
-     * XXX: To-do, flow control. If more than one frame is pending, we need to be able to
-     *      tell clients that we're going too fast, *or* we need to drop frames.
+     * TODO: Currently if this gets ahead of what the USB device is capable of,
+     *       we always drop frames. Alternatively, it would be nice to have end-to-end
+     *       flow control so that the client can produce frames slower.
      */
 
-    submitTransfer(new Transfer(this, &mFramebuffer, sizeof mFramebuffer));
+    if (mNumFramesPending >= 2) {
+        // Too many outstanding frames. Wait to submit until a previous frame completes.
+        mFrameWaitingForSubmit = true;
+        return;
+    }
+
+    if (submitTransfer(new Transfer(this, &mFramebuffer, sizeof mFramebuffer, FRAME))) {
+        mFrameWaitingForSubmit = false;
+        mNumFramesPending++;
+    }
 }
 
 void FCDevice::writeMessage(const OPCSink::Message &msg)
diff --git a/server/fcdevice.h b/server/fcdevice.h
index 4f2ae18..a042c4c 100644
--- a/server/fcdevice.h
+++ b/server/fcdevice.h
@@ -75,15 +75,23 @@ private:
         uint8_t data[63];
     };
 
+    enum PacketType {
+        OTHER = 0,
+        FRAME,
+    };
+
     struct Transfer {
-        Transfer(FCDevice *device, void *buffer, int length);
+        Transfer(FCDevice *device, void *buffer, int length, PacketType type = OTHER);
         ~Transfer();
         libusb_transfer *transfer;
         FCDevice *device;
+        PacketType type;
     };
 
     const Value *mConfigMap;
     std::set<Transfer*> mPending;
+    int mNumFramesPending;
+    bool mFrameWaitingForSubmit;
 
     char mSerial[256];
     libusb_device_descriptor mDD;
@@ -91,7 +99,7 @@ private:
     Packet mColorLUT[LUT_PACKETS];
     Packet mFirmwareConfig;
 
-    void submitTransfer(Transfer *fct);
+    bool submitTransfer(Transfer *fct);
     void configureDevice(const Value &config);
     void writeFirmwareConfiguration();
     static void completeTransfer(struct libusb_transfer *transfer);
-- 
GitLab