Skip to content

Commit e3acf5f

Browse files
tmon-nordicnashif
authored andcommitted
drivers: udc_nrf: handle overwritten Set Address commands
USBD peripheral automatically handles Set Address command which can lead to state mismatch between USB stack and the host. Keep track of device address and issue fake Set Address commands on mismatch. This fixes default vs addressed state mismatch that can occur due to sufficently high SETUP handling latency. The state mismatch was most commonly seen as SET CONFIGURATION failure when the enumeration happened during periods with increased latency. Signed-off-by: Tomasz Moń <[email protected]>
1 parent 54dc011 commit e3acf5f

File tree

1 file changed

+110
-9
lines changed

1 file changed

+110
-9
lines changed

drivers/usb/udc/udc_nrf.c

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ static struct k_thread drv_stack_data;
7070

7171
static struct udc_ep_config ep_cfg_out[CFG_EPOUT_CNT + CFG_EP_ISOOUT_CNT + 1];
7272
static struct udc_ep_config ep_cfg_in[CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + 1];
73-
static bool udc_nrf_setup_rcvd;
73+
static bool udc_nrf_setup_rcvd, udc_nrf_setup_set_addr, udc_nrf_fake_setup;
74+
static uint8_t udc_nrf_address;
7475
const static struct device *udc_nrf_dev;
7576

7677
struct udc_nrf_config {
@@ -143,7 +144,9 @@ static void udc_event_xfer_ctrl_in(const struct device *dev,
143144
/* Update to next stage of control transfer */
144145
udc_ctrl_update_stage(dev, buf);
145146

146-
nrf_usbd_common_setup_clear();
147+
if (!udc_nrf_setup_set_addr) {
148+
nrf_usbd_common_setup_clear();
149+
}
147150
}
148151

149152
static void udc_event_fake_status_in(const struct device *dev)
@@ -317,6 +320,7 @@ static int usbd_ctrl_feed_dout(const struct device *dev,
317320

318321
static int udc_event_xfer_setup(const struct device *dev)
319322
{
323+
nrf_usbd_common_setup_t *setup;
320324
struct net_buf *buf;
321325
int err;
322326

@@ -328,7 +332,77 @@ static int udc_event_xfer_setup(const struct device *dev)
328332
}
329333

330334
udc_ep_buf_set_setup(buf);
331-
nrf_usbd_common_setup_get((nrf_usbd_common_setup_t *)buf->data);
335+
setup = (nrf_usbd_common_setup_t *)buf->data;
336+
nrf_usbd_common_setup_get(setup);
337+
338+
/* USBD peripheral automatically handles Set Address in slightly
339+
* different manner than the USB stack.
340+
*
341+
* USBD peripheral doesn't care about wLength, but the peripheral
342+
* switches to new address only after status stage. The device won't
343+
* automatically accept Data Stage packets.
344+
*
345+
* However, in the case the host:
346+
* * sends SETUP Set Address with non-zero wLength
347+
* * does not send corresponding OUT DATA packets (to match wLength)
348+
* or sends the packets but disregards NAK
349+
* or sends the packets that device ACKs
350+
* * sends IN token (either incorrectly proceeds to status stage, or
351+
* manages to send IN before SW sets STALL)
352+
* then the USBD peripheral will accept the address and USB stack won't.
353+
* This will lead to state mismatch between the stack and peripheral.
354+
*
355+
* In cases where the USB stack would like to STALL the request there is
356+
* a race condition between host issuing Set Address status stage (IN
357+
* token) and SW setting STALL bit. If host wins the race, the device
358+
* ACKs status stage and use new address. If device wins the race, the
359+
* device STALLs status stage and address remains unchanged.
360+
*/
361+
udc_nrf_setup_set_addr =
362+
setup->bmRequestType == 0 &&
363+
setup->bRequest == USB_SREQ_SET_ADDRESS;
364+
if (udc_nrf_setup_set_addr) {
365+
if (setup->wLength) {
366+
/* Currently USB stack only STALLs OUT Data Stage when
367+
* buffer allocation fails. To prevent the device from
368+
* ACKing the Data Stage, simply ignore the request
369+
* completely.
370+
*
371+
* If host incorrectly proceeds to status stage there
372+
* will be address mismatch (unless the new address is
373+
* equal to current device address). If host does not
374+
* issue IN token then the mismatch will be avoided.
375+
*/
376+
net_buf_unref(buf);
377+
return 0;
378+
}
379+
380+
/* nRF52/nRF53 USBD doesn't care about wValue bits 8..15 and
381+
* wIndex value but USB device stack does.
382+
*
383+
* Just clear the bits so stack will handle the request in the
384+
* same way as USBD peripheral does, avoiding the mismatch.
385+
*/
386+
setup->wValue &= 0x7F;
387+
setup->wIndex = 0;
388+
}
389+
390+
if (!udc_nrf_setup_set_addr && udc_nrf_address != NRF_USBD->USBADDR) {
391+
/* Address mismatch detected. Fake Set Address handling to
392+
* correct the situation, then repeat handling.
393+
*/
394+
udc_nrf_fake_setup = true;
395+
udc_nrf_setup_set_addr = true;
396+
397+
setup->bmRequestType = 0;
398+
setup->bRequest = USB_SREQ_SET_ADDRESS;
399+
setup->wValue = NRF_USBD->USBADDR;
400+
setup->wIndex = 0;
401+
setup->wLength = 0;
402+
} else {
403+
udc_nrf_fake_setup = false;
404+
}
405+
332406
net_buf_add(buf, sizeof(nrf_usbd_common_setup_t));
333407
udc_nrf_setup_rcvd = true;
334408

@@ -494,7 +568,8 @@ static bool udc_nrf_fake_status_in(const struct device *dev)
494568
.ep = USB_CONTROL_EP_IN,
495569
};
496570

497-
if (nrf_usbd_common_last_setup_dir_get() == USB_CONTROL_EP_OUT) {
571+
if (nrf_usbd_common_last_setup_dir_get() == USB_CONTROL_EP_OUT ||
572+
udc_nrf_fake_setup) {
498573
/* Let controller perform status IN stage */
499574
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
500575
return true;
@@ -612,12 +687,38 @@ static int udc_nrf_ep_clear_halt(const struct device *dev,
612687

613688
static int udc_nrf_set_address(const struct device *dev, const uint8_t addr)
614689
{
615-
/**
616-
* Nothing to do here. The USBD HW already takes care of initiating
617-
* STATUS stage. Just double check the address for sanity.
690+
/*
691+
* If the status stage already finished (which depends entirely on when
692+
* the host sends IN token) then NRF_USBD->USBADDR will have the same
693+
* address, otherwise it won't (unless new address is unchanged).
694+
*
695+
* Store the address so the driver can detect address mismatches
696+
* between USB stack and USBD peripheral. The mismatches can occur if:
697+
* * SW has high enough latency in SETUP handling, or
698+
* * Host did not issue Status Stage after Set Address request
699+
*
700+
* The SETUP handling latency is a problem because the Set Address is
701+
* automatically handled by device. Because whole Set Address handling
702+
* can finish in less than 21 us, the latency required (with perfect
703+
* timing) to hit the issue is relatively short (2 ms Set Address
704+
* recovery interval + negligible Set Address handling time). If host
705+
* sends new SETUP before SW had a chance to read the Set Address one,
706+
* the Set Address one will be overwritten without a trace.
618707
*/
619-
if (addr != (uint8_t)NRF_USBD->USBADDR) {
620-
LOG_WRN("USB Address incorrect 0x%02x", addr);
708+
udc_nrf_address = addr;
709+
710+
if (udc_nrf_fake_setup) {
711+
struct udc_nrf_evt evt = {
712+
.type = UDC_NRF_EVT_HAL,
713+
.hal_evt = {
714+
.type = NRF_USBD_COMMON_EVT_SETUP,
715+
},
716+
};
717+
718+
/* Finished handling lost Set Address, now handle the pending
719+
* SETUP transfer.
720+
*/
721+
k_msgq_put(&drv_msgq, &evt, K_NO_WAIT);
621722
}
622723

623724
return 0;

0 commit comments

Comments
 (0)