Skip to content

Commit c1fea8c

Browse files
committed
fix rp2040 critical timing when sending handshake
1 parent 1862cc0 commit c1fea8c

File tree

3 files changed

+74
-57
lines changed

3 files changed

+74
-57
lines changed

src/pio_usb.c

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void __not_in_flash_func(pio_usb_bus_usb_transfer)(pio_port_t *pp,
9797
continue;
9898
}
9999
pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag
100-
while (*pc <= PIO_USB_TX_ENCODED_DATA_COMP) {
100+
while (*pc < PIO_USB_TX_ENCODED_DATA_COMP) {
101101
continue;
102102
}
103103
}
@@ -146,76 +146,50 @@ void __no_inline_not_in_flash_func(pio_usb_bus_prepare_receive)(const pio_port_t
146146
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, true);
147147
}
148148

149-
static uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_packet)(pio_port_t* pp) {
150-
uint16_t crc = 0xffff;
151-
uint16_t crc_prev = 0xffff;
152-
uint16_t crc_prev2 = 0xffff;
153-
uint16_t crc_receive = 0xffff;
154-
uint16_t crc_receive_inverse = 0;
155-
bool crc_match = false;
156-
157-
const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]);
158-
149+
static inline __force_inline bool pio_usb_bus_wait_for_rx_start(const pio_port_t* pp) {
159150
// USB 2.0 specs: 7.1.19.1: handshake timeout
160151
// Full-Speed (12 Mbps): 1 bit time = 1 / 12 MHz = 83.3 ns --> 16 bit times = 1.33 µs
161152
// Low-Speed (1.5 Mbps): 1 bit time = 1 / 1.5 MHz = 666.7 ns --> 16 bit times = 10.67 µs
162153

163154
// We're starting the timing somewhere in the current microsecond so always assume the first one
164155
// is less than a full microsecond. For example, a wait of 2 could actually be 1.1 microseconds.
165-
// We will use 3 us (24 bit time) for Full speed and 12us (18 bit time) for Lowspeed.
166-
uint32_t start = time_us_32();
156+
// We will use 3 us (24 bit time) for Full speed and 12us (18 bit time) for Low speed.
157+
uint32_t start = get_time_us_32();
167158
uint32_t timeout = pp->low_speed ? 12 : 3;
168-
while (time_us_32() - start <= timeout) {
159+
while (get_time_us_32() - start <= timeout) {
169160
if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) != 0) {
170-
break;
161+
return true;
171162
}
172163
}
164+
return false;
165+
};
173166

174-
// Timeout if we're not started.
175-
if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) == 0) {
167+
uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) {
168+
if (!pio_usb_bus_wait_for_rx_start(pp)) {
176169
return 0;
177170
}
178171

179172
int16_t idx = 0;
180-
// Timeout in seven microseconds. That is enough time to receive one byte at
181-
// low speed. This is to detect packets without an EOP because the device was
182-
// unplugged.
183-
start = time_us_32();
184-
while (time_us_32() - start <= 7) {
173+
// Timeout in seven microseconds. That is enough time to receive one byte at low speed.
174+
// This is to detect packets without an EOP because the device was unplugged.
175+
uint32_t start = get_time_us_32();
176+
while (get_time_us_32() - start <= 7) {
185177
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
186178
uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
187-
if (idx < rx_buf_len) {
188-
pp->usb_rx_buffer[idx] = data;
189-
}
190-
start = time_us_32();
179+
pp->usb_rx_buffer[idx++] = data;
191180

192-
if (idx >= 2) {
193-
crc_prev2 = crc_prev;
194-
crc_prev = crc;
195-
crc = update_usb_crc16(crc, data);
196-
crc_receive = (crc_receive >> 8) | (data << 8);
197-
crc_receive_inverse = crc_receive ^ 0xffff;
198-
crc_match = (crc_receive_inverse == crc_prev2);
181+
start = get_time_us_32(); // reset timeout when a byte is received
182+
if (idx == 2) {
183+
break;
199184
}
200-
idx++;
201185
} else if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) != 0) {
202186
// Exit early if we've gotten an EOP. There *might* be a race between EOP
203187
// detection and NRZI decoding but it is unlikely.
204188
break;
205189
}
206190
}
207191

208-
if (idx >= 4 && !crc_match) {
209-
// CRC failed, discard the packet.
210-
return 0;
211-
}
212-
return idx;
213-
}
214-
215-
uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) {
216-
217-
uint8_t len = pio_usb_bus_wait_packet(pp);
218-
if (len != 2) {
192+
if (idx != 2) {
219193
return 0;
220194
}
221195

@@ -224,18 +198,54 @@ uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp
224198

225199
int __no_inline_not_in_flash_func(pio_usb_bus_receive_packet_and_handshake)(
226200
pio_port_t *pp, uint8_t handshake) {
227-
uint8_t len = pio_usb_bus_wait_packet(pp);
228-
if (len == 0) {
229-
// Return and don't respond with a handshake.
201+
if (!pio_usb_bus_wait_for_rx_start(pp)) {
230202
return -1;
231203
}
232-
// Only handshake if the other end gave data.
233-
if (len >= 4) {
234-
pio_usb_bus_send_handshake(pp, handshake);
235-
}
236204

237-
if (handshake == USB_PID_ACK) {
238-
return len - 4;
205+
uint16_t crc = 0xffff;
206+
uint16_t crc_prev = 0xffff;
207+
uint16_t crc_prev2 = 0xffff;
208+
uint16_t crc_receive = 0xffff;
209+
uint16_t crc_receive_inverse = 0;
210+
bool crc_match = false;
211+
const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]);
212+
int16_t idx = 0;
213+
214+
// Timeout in seven microseconds. That is enough time to receive one byte at low speed.
215+
// This is to detect packets without an EOP because the device was unplugged.
216+
uint32_t start = get_time_us_32();
217+
while (get_time_us_32() - start <= 7) {
218+
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
219+
uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
220+
if (idx < rx_buf_len) {
221+
pp->usb_rx_buffer[idx] = data;
222+
}
223+
start = get_time_us_32(); // reset timeout when a byte is received
224+
225+
if (idx >= 2) {
226+
crc_prev2 = crc_prev;
227+
crc_prev = crc;
228+
crc = update_usb_crc16(crc, data);
229+
crc_receive = (crc_receive >> 8) | (data << 8);
230+
crc_receive_inverse = crc_receive ^ 0xffff;
231+
crc_match = (crc_receive_inverse == crc_prev2);
232+
}
233+
idx++;
234+
} else if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) != 0) {
235+
// Exit early if we've gotten an EOP. There *might* be a race between EOP
236+
// detection and NRZI decoding but it is unlikely.
237+
if (handshake == USB_PID_ACK) {
238+
// Only ACK if crc matches
239+
if (idx >= 4 && crc_match) {
240+
pio_usb_bus_send_handshake(pp, USB_PID_ACK);
241+
return idx - 4;
242+
}
243+
} else {
244+
// always send other handshake NAK/STALL
245+
pio_usb_bus_send_handshake(pp, handshake);
246+
}
247+
break;
248+
}
239249
}
240250

241251
return -1;

src/pio_usb_host.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ static void __no_inline_not_in_flash_func(restore_fs_bus)(pio_port_t *pp) {
212212

213213
// Time about 1us ourselves so it lives in RAM.
214214
static void __not_in_flash_func(busy_wait_1_us)(void) {
215-
uint32_t start = time_us_32();
216-
while (time_us_32() == start) {
215+
uint32_t start = get_time_us_32();
216+
while (get_time_us_32() == start) {
217217
tight_loop_contents();
218218
}
219219
}

src/usb_crc.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,11 @@ extern const uint16_t crc16_tbl[256];
1212
static inline uint16_t __time_critical_func(update_usb_crc16)(uint16_t crc, uint8_t data) {
1313
crc = (crc >> 8) ^ crc16_tbl[(crc ^ data) & 0xff];
1414
return crc;
15-
}
15+
}
16+
17+
#ifndef PICO_DEFAULT_TIMER_INSTANCE // not defined in sdk v1
18+
#define PICO_DEFAULT_TIMER_INSTANCE() timer_hw
19+
#endif
20+
21+
// time_us_32() not force inline and may be in flash, implement timestamp ourselves
22+
#define get_time_us_32() (PICO_DEFAULT_TIMER_INSTANCE()->timerawl)

0 commit comments

Comments
 (0)