Skip to content

Commit 67d1737

Browse files
committed
usbdmx: Add support for having multiple inflight transfers
Currently we only use a single active transfer for both sending and reveiving usb packets. This is suboptimal in terms of latency, USB actually guarantees certain turnaround times for interrupt transfers for example but that only really works if there is always an IRP ready to go in the USB host controller. According to my measurments interrupt transfers with bInterval=1 can be delayed by some tens of milliseconds instead of happening evey millisecond on the dot if there are periods with no transfers ready to go. In order to support multiple transfers being inflight I replace the transfer state machinery in AsyncUsbTransceiverBase with a set of inflight transfers and a queue of idle transfers. The bookkeeping happens in a new TransferCompleteInternal method which wraps the TransferComplete() method provided by subclasses. This split also allowed moving some duplicated logic for LIBUSB_TRANSFER_NO_DEVICE to the superclass. The number of transfers to allow is passed to AsyncUsb{Sender,Receiver} as a constructor argument since supporting more than one needs some cooperation from the derived classes. Currently everything just defaults to one transfer which shouldn't change the behaviour one bit.
1 parent a95a6f7 commit 67d1737

File tree

6 files changed

+120
-72
lines changed

6 files changed

+120
-72
lines changed

plugins/usbdmx/AsyncUsbReceiver.cpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ namespace usbdmx {
2828

2929
AsyncUsbReceiver::AsyncUsbReceiver(ola::usb::LibUsbAdaptor *adaptor,
3030
libusb_device *usb_device,
31-
PluginAdaptor *plugin_adaptor)
32-
: AsyncUsbTransceiverBase(adaptor, usb_device),
31+
PluginAdaptor *plugin_adaptor,
32+
unsigned int num_transfers)
33+
: AsyncUsbTransceiverBase(adaptor, usb_device, num_transfers),
3334
m_plugin_adaptor(plugin_adaptor),
3435
m_inited_with_handle(false) {
3536
}
@@ -58,25 +59,20 @@ bool AsyncUsbReceiver::Start() {
5859
return false;
5960
}
6061
ola::thread::MutexLocker locker(&m_mutex);
61-
return PerformTransfer();
62+
bool success = true;
63+
while (!m_idle.empty() && success) {
64+
success = PerformTransfer();
65+
}
66+
return success;
6267
}
6368

6469
void AsyncUsbReceiver::TransferComplete(struct libusb_transfer *transfer) {
65-
if (transfer != m_transfer) {
66-
OLA_WARN << "Mismatched libusb transfer: " << transfer << " != "
67-
<< m_transfer;
68-
return;
69-
}
70-
7170
if (transfer->status != LIBUSB_TRANSFER_COMPLETED &&
7271
transfer->status != LIBUSB_TRANSFER_TIMED_OUT ) {
7372
OLA_WARN << "Transfer returned " << transfer->status;
7473
}
7574

7675
ola::thread::MutexLocker locker(&m_mutex);
77-
m_transfer_state = (transfer->status == LIBUSB_TRANSFER_NO_DEVICE ?
78-
DISCONNECTED : IDLE);
79-
8076
if (m_suppress_continuation) {
8177
return;
8278
}

plugins/usbdmx/AsyncUsbReceiver.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ class AsyncUsbReceiver: public AsyncUsbTransceiverBase {
5050
* @param adaptor the LibUsbAdaptor to use.
5151
* @param usb_device the libusb_device to use for the widget.
5252
* @param plugin_adaptor the PluginAdaptor to use for the widget.
53+
* @param num_transfers maximum number of inflight transfers.
5354
*/
5455
AsyncUsbReceiver(ola::usb::LibUsbAdaptor* const adaptor,
5556
libusb_device *usb_device,
56-
PluginAdaptor *plugin_adaptor);
57+
PluginAdaptor *plugin_adaptor,
58+
unsigned int num_transfers = 1);
5759

5860
/**
5961
* @brief Destructor

plugins/usbdmx/AsyncUsbSender.cpp

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ namespace usbdmx {
3030
using ola::usb::LibUsbAdaptor;
3131

3232
AsyncUsbSender::AsyncUsbSender(LibUsbAdaptor *adaptor,
33-
libusb_device *usb_device)
34-
: AsyncUsbTransceiverBase(adaptor, usb_device),
33+
libusb_device *usb_device,
34+
unsigned int num_transfers)
35+
: AsyncUsbTransceiverBase(adaptor, usb_device, num_transfers),
3536
m_pending_tx(false) {
3637
}
3738

@@ -45,7 +46,7 @@ bool AsyncUsbSender::SendDMX(const DmxBuffer &buffer) {
4546
return false;
4647
}
4748
ola::thread::MutexLocker locker(&m_mutex);
48-
if (m_transfer_state == IDLE) {
49+
if (!m_idle.empty()) {
4950
PerformTransfer(buffer);
5051
} else {
5152
// Buffer incoming data so we can send it when the outstanding transfers
@@ -57,28 +58,19 @@ bool AsyncUsbSender::SendDMX(const DmxBuffer &buffer) {
5758
}
5859

5960
void AsyncUsbSender::TransferComplete(struct libusb_transfer *transfer) {
60-
if (transfer != m_transfer) {
61-
OLA_WARN << "Mismatched libusb transfer: " << transfer << " != "
62-
<< m_transfer;
63-
return;
64-
}
65-
6661
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
6762
OLA_WARN << "Transfer returned "
6863
<< m_adaptor->ErrorCodeToString(transfer->status);
6964
}
7065

7166
ola::thread::MutexLocker locker(&m_mutex);
72-
m_transfer_state = (transfer->status == LIBUSB_TRANSFER_NO_DEVICE ?
73-
DISCONNECTED : IDLE);
74-
7567
if (m_suppress_continuation) {
7668
return;
7769
}
7870

7971
PostTransferHook();
8072

81-
if ((m_transfer_state == IDLE) && m_pending_tx) {
73+
if (!m_device_disconnected && m_pending_tx) {
8274
m_pending_tx = false;
8375
PerformTransfer(m_tx_buffer);
8476
}

plugins/usbdmx/AsyncUsbSender.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ class AsyncUsbSender: public AsyncUsbTransceiverBase {
4545
* @brief Create a new AsyncUsbSender.
4646
* @param adaptor the LibUsbAdaptor to use.
4747
* @param usb_device the libusb_device to use for the widget.
48+
* @param num_transfers maximum number of inflight transfers.
4849
*/
4950
AsyncUsbSender(ola::usb::LibUsbAdaptor* const adaptor,
50-
libusb_device *usb_device);
51+
libusb_device *usb_device,
52+
unsigned int num_transfers = 1);
5153

5254
/**
5355
* @brief Destructor

plugins/usbdmx/AsyncUsbTransceiverBase.cpp

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
* Copyright (C) 2014 Simon Newton
1919
*/
2020

21+
#include <assert.h>
22+
2123
#include "plugins/usbdmx/AsyncUsbTransceiverBase.h"
2224

2325
#include "libs/usb/LibUsbAdaptor.h"
@@ -29,36 +31,65 @@ namespace usbdmx {
2931

3032
using ola::usb::LibUsbAdaptor;
3133

32-
namespace {
33-
3434
/*
3535
* Called by libusb when the transfer completes.
3636
*/
3737
#ifdef _WIN32
3838
__attribute__((__stdcall__))
3939
#endif // _WIN32
40-
void AsyncCallback(struct libusb_transfer *transfer) {
40+
void AsyncTransferCallback(struct libusb_transfer *transfer) {
4141
AsyncUsbTransceiverBase *widget = reinterpret_cast<AsyncUsbTransceiverBase*>(
4242
transfer->user_data);
43-
widget->TransferComplete(transfer);
43+
widget->TransferCompleteInternal(transfer);
44+
}
45+
46+
void AsyncUsbTransceiverBase::TransferCompleteInternal(
47+
struct libusb_transfer *transfer) {
48+
{
49+
ola::thread::MutexLocker locker(&m_mutex);
50+
inflight_set::iterator i = m_inflight.find(transfer);
51+
if (i == m_inflight.end()) { // not found
52+
OLA_WARN << "Mismatched libusb transfer: "
53+
<< transfer << " not in inflight set";
54+
return;
55+
}
56+
57+
m_inflight.erase(i);
58+
m_idle.push(transfer);
59+
}
60+
61+
if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) {
62+
m_device_disconnected = true;
63+
}
64+
65+
TransferComplete(transfer);
4466
}
45-
} // namespace
4667

4768
AsyncUsbTransceiverBase::AsyncUsbTransceiverBase(LibUsbAdaptor *adaptor,
48-
libusb_device *usb_device)
69+
libusb_device *usb_device,
70+
unsigned int num_transfers)
4971
: m_adaptor(adaptor),
5072
m_usb_device(usb_device),
5173
m_usb_handle(NULL),
5274
m_suppress_continuation(false),
53-
m_transfer_state(IDLE) {
54-
m_transfer = m_adaptor->AllocTransfer(0);
75+
m_device_disconnected(false) {
76+
for (unsigned int i=0; i < num_transfers; i++) {
77+
m_idle.push(m_adaptor->AllocTransfer(0));
78+
}
5579
m_adaptor->RefDevice(usb_device);
5680
}
5781

5882
AsyncUsbTransceiverBase::~AsyncUsbTransceiverBase() {
83+
ola::thread::MutexLocker locker(&m_mutex);
84+
5985
CancelTransfer();
86+
6087
m_adaptor->UnrefDevice(m_usb_device);
61-
m_adaptor->FreeTransfer(m_transfer);
88+
89+
while (!m_idle.empty()) {
90+
m_adaptor->FreeTransfer(m_idle.front());
91+
m_idle.pop();
92+
}
6293
}
6394

6495
bool AsyncUsbTransceiverBase::Init() {
@@ -67,62 +98,76 @@ bool AsyncUsbTransceiverBase::Init() {
6798
}
6899

69100
void AsyncUsbTransceiverBase::CancelTransfer() {
70-
if (!m_transfer) {
71-
return;
72-
}
73-
74-
bool canceled = false;
75-
while (1) {
76-
ola::thread::MutexLocker locker(&m_mutex);
77-
if (m_transfer_state == IDLE || m_transfer_state == DISCONNECTED) {
78-
break;
79-
}
80-
if (!canceled) {
81-
m_suppress_continuation = true;
82-
if (m_adaptor->CancelTransfer(m_transfer) == 0) {
83-
canceled = true;
84-
} else {
85-
break;
86-
}
101+
m_suppress_continuation = true;
102+
103+
inflight_set::iterator i;
104+
for (i = m_inflight.begin(); i != m_inflight.end(); ++i) {
105+
int ret = m_adaptor->CancelTransfer(*i);
106+
/* We ignore NOT_FOUND errors here because transfer cancellation
107+
* naturally races with completion.
108+
*
109+
* This error simply means the transfer is not in the "in progress"
110+
* state anymore.
111+
*
112+
* Even if we were to check for this before cancelling it could still
113+
* happen that the transfer completes between the check and the
114+
* cancellation taking effect.
115+
*/
116+
if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND) {
117+
OLA_WARN << "libusb_cancel_transfer returned "
118+
<< m_adaptor->ErrorCodeToString(ret);
87119
}
88120
}
121+
}
89122

90-
m_suppress_continuation = false;
123+
struct libusb_transfer *AsyncUsbTransceiverBase::CurrentTransfer() {
124+
/* This should never happen. In AsyncUsbSender::SendDMX we only ask the
125+
* perform superclass to perform a transfer m_idle is not empty and in
126+
* AsyncUsbReceiver::Start we kick off the right number of transfers so
127+
* at completion, when another one would be performed, m_idle will not be
128+
* empty either. */
129+
assert(!m_idle.empty());
130+
131+
return m_idle.front();
91132
}
92133

134+
93135
void AsyncUsbTransceiverBase::FillControlTransfer(unsigned char *buffer,
94136
unsigned int timeout) {
95-
m_adaptor->FillControlTransfer(m_transfer, m_usb_handle, buffer,
96-
&AsyncCallback, this, timeout);
137+
m_adaptor->FillControlTransfer(CurrentTransfer(), m_usb_handle, buffer,
138+
&AsyncTransferCallback, this, timeout);
97139
}
98140

99141
void AsyncUsbTransceiverBase::FillBulkTransfer(unsigned char endpoint,
100142
unsigned char *buffer,
101143
int length,
102144
unsigned int timeout) {
103-
m_adaptor->FillBulkTransfer(m_transfer, m_usb_handle, endpoint, buffer,
104-
length, &AsyncCallback, this, timeout);
145+
m_adaptor->FillBulkTransfer(CurrentTransfer(), m_usb_handle, endpoint, buffer,
146+
length, &AsyncTransferCallback, this, timeout);
105147
}
106148

107149
void AsyncUsbTransceiverBase::FillInterruptTransfer(unsigned char endpoint,
108150
unsigned char *buffer,
109151
int length,
110152
unsigned int timeout) {
111-
m_adaptor->FillInterruptTransfer(m_transfer, m_usb_handle, endpoint, buffer,
112-
length, &AsyncCallback, this, timeout);
153+
m_adaptor->FillInterruptTransfer(CurrentTransfer(), m_usb_handle, endpoint,
154+
buffer, length, &AsyncTransferCallback,
155+
this, timeout);
113156
}
114157

115158
int AsyncUsbTransceiverBase::SubmitTransfer() {
116-
int ret = m_adaptor->SubmitTransfer(m_transfer);
159+
struct libusb_transfer *transfer = CurrentTransfer();
160+
int ret = m_adaptor->SubmitTransfer(transfer);
117161
if (ret) {
118162
OLA_WARN << "libusb_submit_transfer returned "
119163
<< m_adaptor->ErrorCodeToString(ret);
120164
if (ret == LIBUSB_ERROR_NO_DEVICE) {
121-
m_transfer_state = DISCONNECTED;
165+
m_device_disconnected = true;
122166
}
123167
return false;
124168
}
125-
m_transfer_state = IN_PROGRESS;
169+
m_inflight.insert(transfer);
170+
m_idle.pop();
126171
return ret;
127172
}
128173
} // namespace usbdmx

plugins/usbdmx/AsyncUsbTransceiverBase.h

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#define PLUGINS_USBDMX_ASYNCUSBTRANSCEIVERBASE_H_
2323

2424
#include <libusb.h>
25+
#include <queue>
26+
#include <set>
2527

2628
#include "libs/usb/LibUsbAdaptor.h"
2729
#include "ola/DmxBuffer.h"
@@ -42,9 +44,11 @@ class AsyncUsbTransceiverBase {
4244
* @brief Create a new AsyncUsbTransceiverBase.
4345
* @param adaptor the LibUsbAdaptor to use.
4446
* @param usb_device the libusb_device to use for the widget.
47+
* @param num_transfers maximum number of inflight transfers.
4548
*/
4649
AsyncUsbTransceiverBase(ola::usb::LibUsbAdaptor* const adaptor,
47-
libusb_device *usb_device);
50+
libusb_device *usb_device,
51+
unsigned int num_transfers);
4852

4953
/**
5054
* @brief Destructor
@@ -126,20 +130,27 @@ class AsyncUsbTransceiverBase {
126130
*/
127131
int SubmitTransfer();
128132

129-
enum TransferState {
130-
IDLE,
131-
IN_PROGRESS,
132-
DISCONNECTED,
133-
};
134-
135133
libusb_device_handle *m_usb_handle;
136134
bool m_suppress_continuation;
137-
struct libusb_transfer *m_transfer;
135+
bool m_device_disconnected;
136+
137+
typedef std::set<struct libusb_transfer*> inflight_set;
138+
inflight_set m_inflight; // GUARDED_BY(m_mutex);
139+
std::queue<struct libusb_transfer*> m_idle; // GUARDED_BY(m_mutex);
138140

139-
TransferState m_transfer_state; // GUARDED_BY(m_mutex);
140141
ola::thread::Mutex m_mutex;
141142

142143
private:
144+
/**
145+
* @brief Called from the libusb callback when the asynchronous transfer
146+
* completes.
147+
* @param transfer the completed transfer.
148+
*/
149+
void TransferCompleteInternal(struct libusb_transfer *transfer);
150+
friend void AsyncTransferCallback(struct libusb_transfer *transfer);
151+
152+
struct libusb_transfer *CurrentTransfer();
153+
143154
DISALLOW_COPY_AND_ASSIGN(AsyncUsbTransceiverBase);
144155
};
145156
} // namespace usbdmx

0 commit comments

Comments
 (0)