Skip to content

Commit d6db7a0

Browse files
committed
WIP timeouts
1 parent 268f333 commit d6db7a0

File tree

2 files changed

+124
-6
lines changed

2 files changed

+124
-6
lines changed

cores/arduino/USBCore.cpp

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ void EPBuffer<L>::init(uint8_t ep)
133133
this->reset();
134134
this->rxWaiting = false;
135135
this->txWaiting = false;
136+
this->timedOut = false;
137+
this->timeout = 100;
138+
}
139+
140+
template<size_t L>
141+
void EPBuffer<L>::setTimeout(uint16_t timeout)
142+
{
143+
this->timeout = timeout;
136144
}
137145

138146
template<size_t L>
@@ -236,6 +244,9 @@ void EPBuffer<L>::flush()
236244
// fall through
237245
case USBD_CONFIGURED:
238246
case USBD_SUSPENDED: {
247+
if (this->timedOut) {
248+
break;
249+
}
239250
// This will temporarily reenable and disable interrupts
240251
auto canWrite = this->waitForWriteComplete();
241252
if (canWrite) {
@@ -246,6 +257,7 @@ void EPBuffer<L>::flush()
246257
// Only start the next transmission if the device hasn't been
247258
// reset.
248259
this->txWaiting = true;
260+
this->startTime = millis();
249261
usbd_ep_send(&USBCore().usbDev(), this->ep, (uint8_t *)this->buf, this->len());
250262
USBCore().logEP('>', this->ep, '>', this->len());
251263
}
@@ -287,6 +299,7 @@ template<size_t L>
287299
void EPBuffer<L>::transcIn()
288300
{
289301
this->txWaiting = false;
302+
this->timedOut = false;
290303
}
291304

292305
// Unused?
@@ -302,23 +315,117 @@ uint8_t* EPBuffer<L>::ptr()
302315
template<size_t L>
303316
bool EPBuffer<L>::waitForWriteComplete()
304317
{
305-
// auto start = getCurrentMillis();
306318
auto ok = true;
319+
auto desc = *EPBuffers().desc(this->ep);
320+
auto udev = &USBCore().usbDev();
321+
322+
/*
323+
* For interrupt EPs, count from now instead of from previous send.
324+
* Otherwise, if a send occurs while the bus is suspended, the packet will
325+
* be cheated of its full timeout.
326+
*/
327+
if (desc.type() == USB_EP_ATTR_INT) {
328+
this->startTime = millis();
329+
}
307330
do {
308331
usb_enable_interrupts();
309-
switch (USBCore().usbDev().cur_status) {
332+
switch (udev->cur_status) {
310333
case USBD_DEFAULT:
311334
case USBD_ADDRESSED:
312335
ok = false;
313336
break;
314337
default:
315338
break;
316339
}
317-
// if (getCurrentMillis() - start > 5) {
318-
// EPBuffers().buf(ep).transcIn();
319-
// ok = false;
320-
// }
321340
usb_disable_interrupts();
341+
if (this->txWaiting && millis() - this->startTime > this->timeout) {
342+
USBCore().logEP('X', this->ep, '>', this->len());
343+
/*
344+
* For interrupt EPs, overwrite the previous packet. In the common
345+
* case of a key press HID report initiating a remote wakeup,
346+
* transmission of the key release event might be delayed, because
347+
* the host can't poll while the bus is suspended. If the
348+
* application sends a key release event before the key press event
349+
* is transmitted, that might time out. If the key release event is
350+
* discarded due to the timeout, the host will never receive the
351+
* key release event, leading to a stuck or repeating key.
352+
*/
353+
if (desc.type() == USB_EP_ATTR_INT) {
354+
/*
355+
* Set the endpoint status to NAK, in an attempt to prevent
356+
* the queued packet from being transmitted while being
357+
* overwritten.
358+
*
359+
* This must be done in two steps. Unfortunately, the USBD
360+
* peripheral's design doesn't seem to make it possible to
361+
* atomically abort a queued transmission. Even if interrupts
362+
* are disabled, the peripheral state machine continues to run.
363+
*
364+
* The endpoint status control bits are toggle-only (toggle on
365+
* writing 1; unchanged on writing 0). If the TX state
366+
* transitions from VALID to NAK during the read-modify-write,
367+
* a toggle operation (XOR 0b01) intended to transition from
368+
* VALID (0b11) to NAK (0b10) will set the status back to
369+
* VALID. This will cause the previous packet to be sent again.
370+
*
371+
* Instead, set the status to DISABLED (0b00, via XOR 0b11),
372+
* which will become STALL (0b01) if the peripheral transitions
373+
* the status to NAK between the read and the write. This can
374+
* happen if the transmission was in progress and the host ACKs
375+
* exactly between the read and the write to update the status
376+
* bits.
377+
*
378+
* Possible states after the first USBD_EP_TX_STAT_SET:
379+
*
380+
* - DISABLED (metastable if TX in progress)
381+
* - STALL (stable; only possible if we lost the r-m-w race)
382+
* - NAK (stable; only possible if TX completed after toggle)
383+
*
384+
* There is a small theoretical chance that the host will poll
385+
* the endpoint again between the two updates to the status
386+
* bits, which could lead to the host receiving a STALL
387+
* handshake, which would put the data toggle bit out of sync.
388+
*
389+
* The second host poll cannot complete sooner than 34 bit times
390+
* after the ACK from the first host poll. (2 bits inter-packet
391+
* delay, 8 bits sync, 8 bits PID, 16 bits addr/endpoint/CRC,
392+
* about 2.83 microseconds) The second status bit update will
393+
* complete before then, assuming reasonable CPU clock speeds
394+
* and no lengthy interrupt handler executions occurring.
395+
*
396+
* On the other hand, it's unknown when exactly the peripheral
397+
* checks the endpoint status bits to decide which response to
398+
* send to an IN poll. It might latch them at EOP, or SOP, or
399+
* maybe PID, or it might wait until after checking the CRC. So
400+
* the actual available time might be between 0 and 34 bit
401+
* times.
402+
*
403+
* We could globally disable interrupts to mitigate that, if
404+
* necessary.
405+
*/
406+
USBD_EP_TX_STAT_SET(this->ep, EPTX_DISABLED);
407+
if (USBD_EPxCS(this->ep) & EPxCS_TX_STA == EPTX_DISABLED) {
408+
/*
409+
* If the endpoint is disabled, there might still be a
410+
* transmission in progress. Wait for it to complete:
411+
*
412+
* 1 byte sync + 1 byte PID + 64 bytes data + 2 bytes CRC,
413+
* 1 turn-around time,
414+
* 1 byte sync + 1 byte PID (handshake)
415+
* assuming maximum bit stuffing at full speed (12MHz).
416+
*/
417+
delayMicroseconds(56);
418+
}
419+
// In case TX was in progress and completed
420+
USBD_EP_TX_ST_CLEAR(this->ep);
421+
USBD_EP_TX_STAT_SET(this->ep, EPTX_NAK);
422+
break;
423+
} else {
424+
ok = false;
425+
this->timedOut = true;
426+
break;
427+
}
428+
}
322429
} while (ok && this->txWaiting);
323430
return ok;
324431
}
@@ -878,6 +985,11 @@ int USBCore_::flush(uint8_t ep)
878985
return 0;
879986
}
880987

988+
void USBCore_::setTimeout(uint8_t ep, uint16_t timeout)
989+
{
990+
EPBuffers().buf(ep).setTimeout(timeout);
991+
}
992+
881993
void USBCore_::transcSetupHelper(usb_dev* usbd, uint8_t ep)
882994
{
883995
USBCore_* core = (USBCore_*)usbd->user_data;

cores/arduino/USBCore.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class EPBuffer
9595
void flush();
9696
uint8_t* ptr();
9797
void enableOutEndpoint();
98+
void setTimeout(uint16_t timeout);
9899

99100
void transcIn();
100101
void transcOut();
@@ -131,6 +132,10 @@ class EPBuffer
131132
*/
132133
volatile bool currentlyFlushing = false;
133134

135+
volatile uint32_t startTime;
136+
uint16_t timeout;
137+
volatile bool timedOut;
138+
134139
uint8_t ep;
135140
};
136141

@@ -174,6 +179,7 @@ class USBCore_
174179
int recv(uint8_t ep);
175180
int flush(uint8_t ep);
176181
void setResetHook(void (*hook)());
182+
void setTimeout(uint8_t ep, uint16_t timeout);
177183

178184
// Debug counters
179185
volatile uint16_t nreset;

0 commit comments

Comments
 (0)