Skip to content

Commit c519534

Browse files
committed
sunxi-fel: pipeline bulk writes with libusb async transfers
Use a small queue of asynchronous libusb bulk OUT transfers for the normal write path. This keeps the host controller fed during fast high-speed RX-DMA writes without relying on large individual transfers, while still keeping the disconnect-prone re-enumeration paths on synchronous transfers. Use 16 KiB transfers with a queue depth of four. USB tracing on H616 shows this keeps the queue full during the main FIT write and reaches the same throughput plateau as deeper queues, while avoiding excessive in-flight allocation. Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
1 parent 2fbe3cc commit c519534

1 file changed

Lines changed: 150 additions & 12 deletions

File tree

fel_lib.c

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,13 @@ static void usb_error(int rc, const char *caption, int exitcode)
7474
static const int AW_USB_MAX_BULK_SEND = 512 * 1024;
7575
static const int AW_USB_PROGRESS_BULK_SEND = 128 * 1024;
7676
static const int AW_USB_FAST_PROGRESS_BULK_SEND = 1024 * 1024;
77+
static const int AW_USB_ASYNC_BULK_SEND = 16 * 1024;
7778
static const int FEL_RX_DMA_PACKET_SIZE = 512;
7879

80+
enum {
81+
AW_USB_ASYNC_DEPTH = 4,
82+
};
83+
7984
#define DRAM_BASE 0x40000000
8085
#define DRAM_SIZE 0x80000000
8186
#define DRAM_END (DRAM_BASE + DRAM_SIZE)
@@ -84,27 +89,160 @@ static uint32_t fel_rx_dma_thunk[] = {
8489
#include "thunks/fel-rx-dma-thunk.h"
8590
};
8691

92+
static int usb_transfer_status_to_error(enum libusb_transfer_status status)
93+
{
94+
switch (status) {
95+
case LIBUSB_TRANSFER_COMPLETED:
96+
return 0;
97+
case LIBUSB_TRANSFER_TIMED_OUT:
98+
return LIBUSB_ERROR_TIMEOUT;
99+
case LIBUSB_TRANSFER_CANCELLED:
100+
return LIBUSB_ERROR_INTERRUPTED;
101+
case LIBUSB_TRANSFER_STALL:
102+
return LIBUSB_ERROR_PIPE;
103+
case LIBUSB_TRANSFER_NO_DEVICE:
104+
return LIBUSB_ERROR_NO_DEVICE;
105+
case LIBUSB_TRANSFER_OVERFLOW:
106+
return LIBUSB_ERROR_OVERFLOW;
107+
case LIBUSB_TRANSFER_ERROR:
108+
default:
109+
return LIBUSB_ERROR_IO;
110+
}
111+
}
112+
113+
static size_t usb_bulk_send_chunk_size(size_t length, size_t max_chunk)
114+
{
115+
size_t chunk = length;
116+
117+
if (chunk > (size_t)AW_USB_ASYNC_BULK_SEND)
118+
chunk = AW_USB_ASYNC_BULK_SEND;
119+
if (chunk > max_chunk)
120+
chunk = max_chunk;
121+
return chunk;
122+
}
123+
124+
struct usb_bulk_async_slot {
125+
struct libusb_transfer *transfer;
126+
size_t length;
127+
int *event_completed;
128+
bool completed;
129+
};
130+
131+
static void LIBUSB_CALL usb_bulk_send_async_cb(struct libusb_transfer *transfer)
132+
{
133+
struct usb_bulk_async_slot *slot = transfer->user_data;
134+
135+
slot->completed = true;
136+
*slot->event_completed = 1;
137+
}
138+
139+
static void usb_bulk_send_async_submit(libusb_device_handle *usb, int ep,
140+
struct usb_bulk_async_slot *slot,
141+
const unsigned char *data, size_t length)
142+
{
143+
int rc;
144+
145+
slot->length = length;
146+
slot->completed = false;
147+
libusb_fill_bulk_transfer(slot->transfer, usb, ep,
148+
(unsigned char *)data, length,
149+
usb_bulk_send_async_cb, slot, USB_TIMEOUT);
150+
rc = libusb_submit_transfer(slot->transfer);
151+
if (rc)
152+
usb_error(rc, "libusb_submit_transfer()", 2);
153+
}
154+
87155
/*
88156
* Keep progress updates frequent enough for slow BROM PIO transfers. Once an
89157
* RX-DMA thunk is installed, 1 MiB chunks avoid host-side progress overhead.
158+
*
159+
* The USB writes themselves use a small queue of asynchronous transfers. This
160+
* keeps the host controller fed during fast transfers without relying on large
161+
* per-transfer sizes that would be unsuitable for slow BROM PIO paths.
90162
*/
91163
static void usb_bulk_send(libusb_device_handle *usb, int ep, const void *data,
92164
size_t length, size_t max_chunk, bool progress)
93165
{
94-
size_t chunk;
95-
int rc, sent;
96-
while (length > 0) {
97-
chunk = length < max_chunk ? length : max_chunk;
98-
rc = libusb_bulk_transfer(usb, ep, (void *)data, chunk,
99-
&sent, USB_TIMEOUT);
100-
if (rc != 0)
101-
usb_error(rc, "usb_bulk_send()", 2);
102-
length -= sent;
103-
data += sent;
166+
struct usb_bulk_async_slot slots[AW_USB_ASYNC_DEPTH] = { 0 };
167+
const unsigned char *ptr = data;
168+
size_t progress_pending = 0;
169+
size_t offset = 0;
170+
int event_completed = 0;
171+
int active = 0;
172+
int rc;
173+
int i;
174+
175+
if (length == 0)
176+
return;
177+
178+
for (i = 0; i < AW_USB_ASYNC_DEPTH; i++) {
179+
slots[i].transfer = libusb_alloc_transfer(0);
180+
if (!slots[i].transfer)
181+
usb_error(LIBUSB_ERROR_NO_MEM,
182+
"libusb_alloc_transfer()", 2);
183+
slots[i].event_completed = &event_completed;
184+
}
185+
186+
for (i = 0; i < AW_USB_ASYNC_DEPTH && offset < length; i++) {
187+
size_t chunk = usb_bulk_send_chunk_size(length - offset,
188+
max_chunk);
189+
190+
usb_bulk_send_async_submit(usb, ep, &slots[i],
191+
ptr + offset, chunk);
192+
offset += chunk;
193+
active++;
194+
}
195+
196+
while (active > 0) {
197+
event_completed = 0;
198+
rc = libusb_handle_events_completed(NULL, &event_completed);
199+
if (rc) {
200+
if (rc == LIBUSB_ERROR_INTERRUPTED)
201+
continue;
202+
usb_error(rc, "libusb_handle_events_completed()", 2);
203+
}
104204

105-
if (progress)
106-
progress_update(sent); /* notification after each chunk */
205+
for (i = 0; i < AW_USB_ASYNC_DEPTH; i++) {
206+
struct libusb_transfer *transfer = slots[i].transfer;
207+
size_t chunk;
208+
209+
if (!slots[i].completed)
210+
continue;
211+
212+
slots[i].completed = false;
213+
active--;
214+
215+
rc = usb_transfer_status_to_error(transfer->status);
216+
if (rc)
217+
usb_error(rc, "usb_bulk_send()", 2);
218+
if (transfer->actual_length != (int)slots[i].length)
219+
usb_error(LIBUSB_ERROR_IO, "usb_bulk_send()", 2);
220+
221+
if (progress) {
222+
progress_pending += transfer->actual_length;
223+
if (progress_pending >= max_chunk) {
224+
progress_update(progress_pending);
225+
progress_pending = 0;
226+
}
227+
}
228+
229+
if (offset >= length)
230+
continue;
231+
232+
chunk = usb_bulk_send_chunk_size(length - offset,
233+
max_chunk);
234+
usb_bulk_send_async_submit(usb, ep, &slots[i],
235+
ptr + offset, chunk);
236+
offset += chunk;
237+
active++;
238+
}
107239
}
240+
241+
for (i = 0; i < AW_USB_ASYNC_DEPTH; i++)
242+
libusb_free_transfer(slots[i].transfer);
243+
244+
if (progress && progress_pending)
245+
progress_update(progress_pending);
108246
}
109247

110248
static void usb_bulk_recv(libusb_device_handle *usb, int ep, void *data,

0 commit comments

Comments
 (0)