usb: drivers: Fix ZLP handling for Nordic driver.

ZLP - zero length packet is used to indicate that the device
has no more data to send. If the Host asks for more data that the
device can provide and the data size is mutliplication of Endpoint
wMaxPacketSize then the device must terminate the data transfer
with ZLP.

Until this patch Nordic device driver controller was not aware of
the requested data length and could not determine when the ZLP was
required.

This patch introduces a fix that prevents the driver from starting
setup stage before the ZLP is being send.

For consistance with the Zephyr USB stack sending ZLP must be
issued from the stack level. Making trans_zlp flag true results
in blocking the driver from starting setup stage without required
ZLP.

After the data transfer finishes the driver will be prepared for ZLP
and will call back the stack to start writing ZLP. After the ZLP
is being send the driver will automatically start status stage and
end the Control Transfer.

This patch also removes CONFIG_USB_DEVICE_DISABLE_ZLP_EPIN_HANDLING
and aligns Nordic driver with others.

Without this patch the issue could occur when handling get requests.
Typical case is string descriptor of length equal to wMaxPacketSize.
Hosts usually asks for wLength = 255 Bytes when string descriptors
are being requested. In that case to successfully finish the data
stage of the Control transfer the device must send wMacPacketSize
Bytes of actual string descriptor and then ZLP to indicate that no
more data are present. After ZLP the status stage may start and the
request is finished successfully.

Without this patch the driver will not send ZLP making it unable
to end the Control Request successful - this may lead to failing
'Device Descriptor Test' from USB3CV test tool.

Signed-off-by: Emil Obalski <emil.obalski@nordicsemi.no>
This commit is contained in:
Emil Obalski 2020-10-20 13:58:08 +02:00 committed by Carles Cufí
commit 773f02e6e9
4 changed files with 32 additions and 10 deletions

View file

@ -54,7 +54,6 @@ menuconfig USB_NRFX
select USB_DEVICE_DRIVER
select NRFX_USBD
select NRFX_POWER
select USB_DEVICE_DISABLE_ZLP_EPIN_HANDLING
help
nRF USB Device Controller Driver

View file

@ -111,6 +111,9 @@ struct nrf_usbd_ep_buf {
* a data transfer.
* @param write_in_progress A flag indicating that write operation has
* been scheduled.
* @param trans_zlp Flag required for Control IN Endpoint. It
* indicates that ZLP is required to end data
* stage of the control request.
*/
struct nrf_usbd_ep_ctx {
struct nrf_usbd_ep_cfg cfg;
@ -118,6 +121,7 @@ struct nrf_usbd_ep_ctx {
volatile bool read_complete;
volatile bool read_pending;
volatile bool write_in_progress;
bool trans_zlp;
};
/**
@ -612,6 +616,7 @@ static void ep_ctx_reset(struct nrf_usbd_ep_ctx *ep_ctx)
ep_ctx->read_complete = true;
ep_ctx->read_pending = false;
ep_ctx->write_in_progress = false;
ep_ctx->trans_zlp = false;
}
/**
@ -871,7 +876,12 @@ static inline void usbd_work_process_ep_events(struct usbd_ep_event *ep_evt)
break;
case EP_EVT_WRITE_COMPLETE:
if (ep_ctx->cfg.type == USB_DC_EP_CONTROL) {
if (ep_ctx->cfg.type == USB_DC_EP_CONTROL &&
!ep_ctx->trans_zlp) {
/* Trigger the hardware to perform
* status stage, but only if there is
* no ZLP required.
*/
k_mutex_lock(&ctx->drv_lock, K_FOREVER);
nrfx_usbd_setup_clear();
k_mutex_unlock(&ctx->drv_lock);
@ -1695,6 +1705,27 @@ int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data,
return -EAGAIN;
}
/** Clear the ZLP flag if current write is ZLP. After the ZLP will be
* send the driver will perform status stage.
*/
if (!data_len && ep_ctx->trans_zlp) {
ep_ctx->trans_zlp = false;
}
/** If writing to a Control Endpoint there might be a need to transfer
* ZLP. If the Hosts asks for more data that the device may return and
* the last packet is wMaxPacketSize long. The driver must send ZLP.
* For consistance with the Zephyr USB stack sending ZLP must be issued
* from the stack level. Making trans_zlp flag true results in blocking
* the driver from starting setup stage without required ZLP.
*/
if (ep_ctx->cfg.type == USB_DC_EP_CONTROL) {
if (data_len && usbd_ctx.setup.wLength > data_len &&
!(data_len % ep_ctx->cfg.max_sz)) {
ep_ctx->trans_zlp = true;
}
}
/* Setup stage is handled by hardware.
* Detect the setup stage initiated by the stack
* and perform appropriate action.

View file

@ -88,12 +88,6 @@ config USB_DEVICE_REMOTE_WAKEUP
help
This option requires USBD peripheral driver to also support remote wakeup.
config USB_DEVICE_DISABLE_ZLP_EPIN_HANDLING
bool
help
Stack should not handle ZLP for Variable-length Data Stage
because it is taken over by the hardware.
config USB_DEVICE_BOS
bool "Enable USB Binary Device Object Store (BOS)"

View file

@ -227,7 +227,6 @@ static void usb_data_to_host(uint16_t len)
usb_dev.data_buf += chunk;
usb_dev.data_buf_residue -= chunk;
#ifndef CONFIG_USB_DEVICE_DISABLE_ZLP_EPIN_HANDLING
/*
* Set ZLP flag when host asks for a bigger length and the
* last chunk is wMaxPacketSize long, to indicate the last
@ -242,7 +241,6 @@ static void usb_data_to_host(uint16_t len)
usb_dev.zlp_flag = true;
}
}
#endif
} else {
usb_dev.zlp_flag = false;