Skip to content

Commit 51918fc

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 7df89f7 commit 51918fc

File tree

6 files changed

+106
-72
lines changed

6 files changed

+106
-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 n_transfers)
33+
: AsyncUsbTransceiverBase(adaptor, usb_device, n_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 = 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 n_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 n_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 n_transfers)
35+
: AsyncUsbTransceiverBase(adaptor, usb_device, n_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 n_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 n_transfers = 1);
5153

5254
/**
5355
* @brief Destructor

plugins/usbdmx/AsyncUsbTransceiverBase.cpp

Lines changed: 67 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 <stdexcept>
22+
2123
#include "plugins/usbdmx/AsyncUsbTransceiverBase.h"
2224

2325
#include "libs/usb/LibUsbAdaptor.h"
@@ -29,36 +31,64 @@ 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+
TransferComplete(transfer);
4465
}
45-
} // namespace
4666

4767
AsyncUsbTransceiverBase::AsyncUsbTransceiverBase(LibUsbAdaptor *adaptor,
48-
libusb_device *usb_device)
68+
libusb_device *usb_device,
69+
unsigned int n_transfers)
4970
: m_adaptor(adaptor),
5071
m_usb_device(usb_device),
5172
m_usb_handle(NULL),
5273
m_suppress_continuation(false),
53-
m_transfer_state(IDLE) {
54-
m_transfer = m_adaptor->AllocTransfer(0);
74+
m_device_disconnected(false) {
75+
for (unsigned int i=0; i < n_transfers; i++) {
76+
m_idle.push(m_adaptor->AllocTransfer(0));
77+
}
5578
m_adaptor->RefDevice(usb_device);
5679
}
5780

5881
AsyncUsbTransceiverBase::~AsyncUsbTransceiverBase() {
82+
ola::thread::MutexLocker locker(&m_mutex);
83+
5984
CancelTransfer();
85+
6086
m_adaptor->UnrefDevice(m_usb_device);
61-
m_adaptor->FreeTransfer(m_transfer);
87+
88+
while (!m_idle.empty()) {
89+
m_adaptor->FreeTransfer(m_idle.front());
90+
m_idle.pop();
91+
}
6292
}
6393

6494
bool AsyncUsbTransceiverBase::Init() {
@@ -67,62 +97,63 @@ bool AsyncUsbTransceiverBase::Init() {
6797
}
6898

6999
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-
}
100+
m_suppress_continuation = true;
101+
102+
inflight_set::iterator i;
103+
for (i = m_inflight.begin(); i != m_inflight.end(); ++i)
104+
{
105+
int ret = m_adaptor->CancelTransfer(*i);
106+
if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND) {
107+
OLA_WARN << "libusb_cancel_transfer returned "
108+
<< m_adaptor->ErrorCodeToString(ret);
87109
}
88110
}
111+
}
89112

90-
m_suppress_continuation = false;
113+
struct libusb_transfer *AsyncUsbTransceiverBase::CurrentTransfer() {
114+
if (m_idle.empty())
115+
throw std::out_of_range("AsyncUsbTransceiverBase::m_idle is empty");
116+
117+
return m_idle.front();
91118
}
92119

120+
93121
void AsyncUsbTransceiverBase::FillControlTransfer(unsigned char *buffer,
94122
unsigned int timeout) {
95-
m_adaptor->FillControlTransfer(m_transfer, m_usb_handle, buffer,
96-
&AsyncCallback, this, timeout);
123+
m_adaptor->FillControlTransfer(CurrentTransfer(), m_usb_handle, buffer,
124+
&AsyncTransferCallback, this, timeout);
97125
}
98126

99127
void AsyncUsbTransceiverBase::FillBulkTransfer(unsigned char endpoint,
100128
unsigned char *buffer,
101129
int length,
102130
unsigned int timeout) {
103-
m_adaptor->FillBulkTransfer(m_transfer, m_usb_handle, endpoint, buffer,
104-
length, &AsyncCallback, this, timeout);
131+
m_adaptor->FillBulkTransfer(CurrentTransfer(), m_usb_handle, endpoint, buffer,
132+
length, &AsyncTransferCallback, this, timeout);
105133
}
106134

107135
void AsyncUsbTransceiverBase::FillInterruptTransfer(unsigned char endpoint,
108136
unsigned char *buffer,
109137
int length,
110138
unsigned int timeout) {
111-
m_adaptor->FillInterruptTransfer(m_transfer, m_usb_handle, endpoint, buffer,
112-
length, &AsyncCallback, this, timeout);
139+
m_adaptor->FillInterruptTransfer(CurrentTransfer(), m_usb_handle, endpoint,
140+
buffer, length, &AsyncTransferCallback,
141+
this, timeout);
113142
}
114143

115144
int AsyncUsbTransceiverBase::SubmitTransfer() {
116-
int ret = m_adaptor->SubmitTransfer(m_transfer);
145+
struct libusb_transfer *transfer = CurrentTransfer();
146+
int ret = m_adaptor->SubmitTransfer(transfer);
117147
if (ret) {
118148
OLA_WARN << "libusb_submit_transfer returned "
119149
<< m_adaptor->ErrorCodeToString(ret);
120150
if (ret == LIBUSB_ERROR_NO_DEVICE) {
121-
m_transfer_state = DISCONNECTED;
151+
m_device_disconnected = true;
122152
}
123153
return false;
124154
}
125-
m_transfer_state = IN_PROGRESS;
155+
m_inflight.insert(transfer);
156+
m_idle.pop();
126157
return ret;
127158
}
128159
} // 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 n_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 n_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)