1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
|
/*
* SPDX-FileCopyrightText: Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
* SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*/
#include "tusb_option.h"
#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED)
#include <stdlib.h>
#include "rp2040_usb.h"
#include "device/dcd.h"
#include "host/hcd.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTOTYPE
//--------------------------------------------------------------------+
#if CFG_TUSB_RP2_ERRATA_E15
static bool e15_is_critical_frame_period(void);
#endif
#if CFG_TUSB_RP2_ERRATA_E2
static uint8_t rp2040_chipversion = 2;
#endif
critical_section_t rp2usb_lock;
//--------------------------------------------------------------------+
// Implementation
//--------------------------------------------------------------------+
// Provide own byte by byte memcpy as not all copies are aligned.
// Use volatile to prevent compiler from widening to 16/32-bit accesses
// which cause hard fault on RP2350 when dst/src point to USB DPRAM.
static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) {
volatile uint8_t *vdst = dst;
const volatile uint8_t *vsrc = src;
while (n--) {
*vdst++ = *vsrc++;
}
}
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) {
(void)access_mode;
unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len);
}
void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) {
(void)access_mode;
unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len);
}
#endif
void rp2usb_init(void) {
// Reset usb controller
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
#ifdef __GNUC__
// Clear any previous state just in case
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#if __GNUC__ > 6
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
#endif
memset(usb_dpram, 0, sizeof(*usb_dpram));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
// Mux the controller to the onboard usb phy
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
#if CFG_TUSB_RP2_ERRATA_E2
rp2040_chipversion = rp2040_chip_version();
#endif
TU_LOG2_INT(sizeof(hw_endpoint_t));
critical_section_init(&rp2usb_lock);
}
void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) {
ep->state = EPSTATE_IDLE;
ep->remaining_len = 0;
ep->xferred_len = 0;
ep->user_buf = 0;
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
ep->is_xfer_fifo = false;
#endif
}
void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) {
const uint32_t current = *buf_reg;
const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16);
if (current & value & avail_mask) {
panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg);
}
*buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first
// Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control,
// wait for USB controller to see the update before setting AVAILABLE.
// Don't need delay in host mode as host is in charge of when to start the transaction.
if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) {
if (!rp2usb_is_host_mode()) {
busy_wait_at_least_cycles(12);
}
*buf_reg = value; // then set AVAILABLE bit last
}
}
void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) {
const uint16_t current = *buf_reg16;
if (current & value & USB_BUF_CTRL_AVAIL) {
panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16);
}
*buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first
// Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access
if (value & USB_BUF_CTRL_AVAIL) {
if (!rp2usb_is_host_mode()) {
busy_wait_at_least_cycles(12);
}
*buf_reg16 = value; // then set AVAILABLE bit last
}
}
// prepare buffer, move data if tx, return buffer control
uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) {
const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size);
ep->remaining_len -= buflen;
uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
if (ep->next_pid) {
buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
}
ep->next_pid ^= 1u;
if (!is_rx) {
if (buflen) {
// Copy data from user buffer/fifo to hw buffer
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL);
} else
#endif
{
unaligned_memcpy(dpram_buf, ep->user_buf, buflen);
ep->user_buf += buflen;
}
}
buf_ctrl |= USB_BUF_CTRL_FULL;
}
// Is this the last buffer? Will trigger the trans complete irq but also stop it polling.
// This is used to detect setup packets being sent in host mode
if (ep->remaining_len == 0) {
buf_ctrl |= USB_BUF_CTRL_LAST;
}
return buf_ctrl;
}
// Start transaction on hw buffer
void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) {
// always compute and start with buffer 0
uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL;
// Note: device EP0 does not have an endpoint control register
if (ep_reg != NULL) {
uint32_t ep_ctrl = *ep_reg;
// Isochronous endpoints get a single DPRAM buffer (hw_endpoint_open only double-sizes BULK), so
// they must never be double-buffered here even when a transfer spans multiple packets, or buffer
// 1 (at dpram_buf+64) would spill into the next endpoint's DPRAM. (Never true for BULK, so the
// double-buffered bulk path is unaffected.)
const bool is_iso = (((ep_ctrl >> EP_CTRL_BUFFER_TYPE_LSB) & 0x3u) == TUSB_XFER_ISOCHRONOUS);
#if CFG_TUH_ENABLED
const bool force_single = is_iso || (rp2usb_is_host_mode() && ep->interrupt_num > 0);
#else
const bool force_single = is_iso;
#endif
if (ep->remaining_len && !force_single) {
// Use buffer 1 (double buffered) if there is still data
buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16;
ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS;
} else {
// Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector
ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS;
}
*ep_reg = ep_ctrl;
}
// Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint
bufctrl_write32(buf_reg, buf_ctrl);
}
void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
uint16_t total_len) {
(void)ff;
hw_endpoint_lock_update(ep, 1);
if (ep->state == EPSTATE_ACTIVE) {
TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr);
rp2usb_reset_transfer(ep);
}
// Fill in info now that we're kicking off the hw
ep->remaining_len = total_len;
ep->xferred_len = 0;
ep->state = EPSTATE_ACTIVE;
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ff != NULL) {
ep->user_fifo = ff;
ep->is_xfer_fifo = true;
} else
#endif
{
ep->user_buf = buffer;
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
ep->is_xfer_fifo = false;
#endif
}
const bool is_host = rp2usb_is_host_mode();
const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN));
#if CFG_TUD_ENABLED
if (!is_host && ep->future_len > 0) {
// Device only: previous short-packet abort saved data from the other buffer
const uint8_t future_len = ep->future_len;
memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len);
ep->xferred_len += future_len;
ep->remaining_len -= future_len;
ep->user_buf += future_len;
ep->future_len = 0;
ep->future_bufid = 0;
if (ep->remaining_len == 0) {
const uint16_t xferred_len = ep->xferred_len;
rp2usb_reset_transfer(ep);
dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false);
hw_endpoint_lock_update(ep, -1);
return;
}
}
#if CFG_TUSB_RP2_ERRATA_E15
if (ep->e15_bulk_in) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
// skip transfer if we are in critical frame period
if (e15_is_critical_frame_period()) {
ep->state = EPSTATE_PENDING;
hw_endpoint_lock_update(ep, -1);
return;
}
}
#endif // CFG_TUSB_RP2_ERRATA_E15
#endif // CFG_TUD_ENABLED
rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
hw_endpoint_lock_update(ep, -1);
}
// sync endpoint buffer and return transferred bytes
static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl,
uint8_t *dpram_buf) {
const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK;
if (!is_rx) {
// We are continuing a transfer here. If we are TX, we have successfully
// sent some data can increase the length we have sent
assert(!(buf_ctrl & USB_BUF_CTRL_FULL));
} else {
// If we have received some data, so can increase the length
// we have received AFTER we have copied it to the user buffer at the appropriate offset
assert(buf_ctrl & USB_BUF_CTRL_FULL);
#if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL);
} else
#endif
{
unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes);
ep->user_buf += xferred_bytes;
}
}
ep->xferred_len += xferred_bytes;
// Short packet
if (xferred_bytes < ep->max_packet_size) {
// Reduce total length as this is last packet
ep->remaining_len = 0;
}
return xferred_bytes;
}
// Returns true if transfer is complete.
// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered).
bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id,
bool is_rx) {
hw_endpoint_lock_update(ep, 1);
if (ep->state == EPSTATE_IDLE) {
// probably land here due to short packet on rx with double buffered
hw_endpoint_lock_update(ep, -1);
return false;
}
const bool is_host = rp2usb_is_host_mode();
const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
// Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed.
// RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to
// BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1
// since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered.
uint8_t *dpram_buf = ep->dpram_buf;
if (buf_id) {
#if CFG_TUSB_RP2_ERRATA_E4
if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0
#endif
{
dpram_buf += 64; // buf1 offset
}
}
io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg;
uint16_t buf_ctrl16 = *(buf_reg16 + buf_id);
const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf);
const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST;
const bool is_short = xact_bytes < ep->max_packet_size;
const bool is_done = is_short || is_last;
// Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control.
// The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed.
// We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer.
// Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4
// causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale)
if (is_short && is_double && is_rx && !is_last) {
const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint
if (is_host) {
// host stop current transfer, not safe, can be racing
const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
usb_hw->sie_ctrl = sie_ctrl;
while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
} else {
// device abort current transfer
#if CFG_TUSB_RP2_ERRATA_E2
if (rp2040_chipversion >= 2)
#endif
{
usb_hw_set->abort = abort_bit;
while ((usb_hw->abort_done & abort_bit) != abort_bit) {}
}
}
// After abort, check if the other buffer received valid data
io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1);
const uint16_t buf_ctrl16_other = *buf_reg16_other;
if (buf_ctrl16_other & USB_BUF_CTRL_FULL) {
// Data already sent into this buffer. Save it for the next transfer.
// buff_status will be clear by the next run
#if CFG_TUD_ENABLED
if (!is_host) {
ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK);
ep->future_bufid = buf_id ^ 1;
}
#endif
} else {
ep->next_pid ^= 1u; // roll back pid if aborted
}
*buf_reg = 0; // reset buffer control
if (!is_host) {
#if CFG_TUSB_RP2_ERRATA_E2
if (rp2040_chipversion >= 2)
#endif
{
usb_hw_clear->abort_done = abort_bit;
usb_hw_clear->abort = abort_bit;
}
}
hw_endpoint_lock_update(ep, -1);
return true;
}
if (!is_done && ep->remaining_len > 0) {
#if CFG_TUSB_RP2_ERRATA_E15
const bool need_e15 = ep->e15_bulk_in;
if (need_e15 && e15_is_critical_frame_period()) {
// mark as pending if matches E15 condition
ep->state = EPSTATE_PENDING;
} else if (need_e15 && ep->state == EPSTATE_PENDING) {
// if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it
// do nothing
} else
#endif
{
// ping-pong: arm the completed buffer with new data
const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx);
bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new);
}
}
hw_endpoint_lock_update(ep, -1);
return is_done;
}
//--------------------------------------------------------------------+
// Errata 15
//--------------------------------------------------------------------+
#if CFG_TUSB_RP2_ERRATA_E15
// E15 is fixed with RP2350
/* Don't mark IN buffers as available during the last 200us of a full-speed
frame. This avoids a situation seen with the USB2.0 hub on a Raspberry
Pi 4 where a late IN token before the next full-speed SOF can cause port
babble and a corrupt ACK packet. The nature of the data corruption has a
chance to cause device lockup.
Use the next SOF to mark delayed buffers as available. This reduces
available Bulk IN bandwidth by approximately 20%, and requires that the
SOF interrupt is enabled while these transfers are ongoing.
Inherit the top-level enable from the corresponding Pico-SDK flag.
Applications that will not use the device in a situation where it could
be plugged into a Pi 4 or Pi 400 (for example, when directly connected
to a commodity hub or other host) can turn off the flag in the SDK.
*/
volatile uint32_t e15_last_sof = 0;
// check if it is currently in critical frame period i.e 20% of last usb frame
static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) {
/* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point.
* The device state machine cannot recover from receiving an incorrect PID
* when it is expecting an ACK. */
uint32_t delta = time_us_32() - e15_last_sof;
if (delta < 800 || delta > 998) {
return false;
}
// TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
// e15_last_sof);
return true;
}
#endif
#endif
|