Skip to content

Commit dca6b87

Browse files
mathieuchopstmcfriedt
authored andcommitted
drivers: usb: udc: stm32: handle multi-packet-Data OUT Control transfers
During a Host-to-Device Control transfer, an arbitrary amount of data is sent from Host to the Device using OUT Data packets. If the total amount of data to transfer, communicated via the wLength field of the SETUP packet, exceeds the EP0 MaxPacketSize, several Data packets until all data has been transfered. Combined with HAL behavior, the STM32 driver did not handle this situation properly and always ended reception after a single Data packet was received regardless of whether or not all data had actually been received from Host. Modify driver to handle this situation properly by keeping track of how much data has been received and restarting transfers until we have received everything the Host promised it would send. Signed-off-by: Mathieu Choplain <[email protected]>
1 parent 2a0a6f5 commit dca6b87

File tree

1 file changed

+84
-10
lines changed

1 file changed

+84
-10
lines changed

drivers/usb/udc/udc_stm32.c

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ struct udc_stm32_data {
158158
const struct device *dev;
159159
uint32_t irq;
160160
uint32_t occupied_mem;
161+
/* wLength of SETUP packet for s-out-status */
162+
uint32_t ep0_out_wlength;
161163
void (*pcd_prepare)(const struct device *dev);
162164
int (*clk_enable)(void);
163165
int (*clk_disable)(void);
@@ -275,20 +277,50 @@ void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd)
275277
udc_submit_sof_event(priv->dev);
276278
}
277279

278-
static int usbd_ctrl_feed_dout(const struct device *dev, const size_t length)
280+
/*
281+
* Prepare OUT EP0 for reception.
282+
*
283+
* @param dev USB controller
284+
* @param length wLength from SETUP packet for s-out-status
285+
* 0 for s-in-status ZLP
286+
*/
287+
static int udc_stm32_prep_out_ep0_rx(const struct device *dev, const size_t length)
279288
{
280289
struct udc_stm32_data *priv = udc_get_private(dev);
281290
struct udc_ep_config *cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT);
282291
struct net_buf *buf;
292+
uint32_t buf_size;
293+
294+
udc_ep_set_busy(cfg, true);
295+
296+
/*
297+
* Make sure OUT EP0 can receive bMaxPacketSize0 bytes
298+
* from each Data packet by rounding up allocation size
299+
* even if "device behaviour is undefined if the host
300+
* should send more data than specified in wLength"
301+
* according to the USB Specification.
302+
*
303+
* Note that ROUND_UP() will return 0 for ZLP.
304+
*/
305+
buf_size = ROUND_UP(length, UDC_STM32_EP0_MAX_PACKET_SIZE);
283306

284-
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length);
307+
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, buf_size);
285308
if (buf == NULL) {
286309
return -ENOMEM;
287310
}
288311

289312
k_fifo_put(&cfg->fifo, buf);
290313

291-
HAL_PCD_EP_Receive(&priv->pcd, cfg->addr, buf->data, buf->size);
314+
/*
315+
* Keep track of how much data we're expecting from
316+
* host so we know when the transfer is complete.
317+
* Unlike other endpoints, this bookkeeping isn't
318+
* done by the HAL for OUT EP0.
319+
*/
320+
priv->ep0_out_wlength = length;
321+
322+
/* Don't try to receive more than bMaxPacketSize0 */
323+
HAL_PCD_EP_Receive(&priv->pcd, cfg->addr, net_buf_tail(buf), UDC_STM32_EP0_MAX_PACKET_SIZE);
292324

293325
return 0;
294326
}
@@ -339,7 +371,7 @@ static int udc_stm32_tx(const struct device *dev, struct udc_ep_config *epcfg,
339371
if (DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb)) {
340372
udc_stm32_flush_tx_fifo(dev);
341373
} else {
342-
usbd_ctrl_feed_dout(dev, 0);
374+
udc_stm32_prep_out_ep0_rx(dev, 0);
343375
}
344376
}
345377

@@ -352,6 +384,9 @@ static int udc_stm32_rx(const struct device *dev, struct udc_ep_config *epcfg,
352384
struct udc_stm32_data *priv = udc_get_private(dev);
353385
HAL_StatusTypeDef status;
354386

387+
/* OUT EP0 requires special logic! */
388+
__ASSERT_NO_MSG(epcfg->addr != USB_CONTROL_EP_OUT);
389+
355390
LOG_DBG("RX ep 0x%02x len %u", epcfg->addr, buf->size);
356391

357392
if (udc_ep_is_busy(epcfg)) {
@@ -411,33 +446,72 @@ static void handle_msg_data_out(struct udc_stm32_data *priv, uint8_t epnum, uint
411446
LOG_DBG("DataOut ep 0x%02x", ep);
412447

413448
epcfg = udc_get_ep_cfg(dev, ep);
414-
udc_ep_set_busy(epcfg, false);
415449

416-
buf = udc_buf_get(epcfg);
450+
buf = udc_buf_peek(epcfg);
417451
if (unlikely(buf == NULL)) {
418452
LOG_ERR("ep 0x%02x queue is empty", ep);
453+
udc_ep_set_busy(epcfg, false);
419454
return;
420455
}
421456

457+
/* HAL copies data - we just need to update bookkeeping */
422458
net_buf_add(buf, rx_count);
423459

424460
if (ep == USB_CONTROL_EP_OUT) {
461+
/*
462+
* OUT EP0 is used for two purposes:
463+
* - receive 'out' Data packets during s-(out)-status
464+
* - receive Status OUT ZLP during s-in-(status)
465+
*/
425466
if (udc_ctrl_stage_is_status_out(dev)) {
467+
/* s-in-status completed */
468+
__ASSERT_NO_MSG(rx_count == 0);
426469
udc_ctrl_update_stage(dev, buf);
427470
udc_ctrl_submit_status(dev, buf);
428471
} else {
429-
udc_ctrl_update_stage(dev, buf);
430-
}
472+
/* Verify that host did not send more data than it promised */
473+
__ASSERT(buf->len <= priv->ep0_out_wlength,
474+
"Received more data from Host than expected!");
475+
476+
/* Check if the data stage is complete */
477+
if (buf->len < priv->ep0_out_wlength) {
478+
/* Not yet - prepare to receive more data and wait */
479+
HAL_PCD_EP_Receive(&priv->pcd, epcfg->addr, net_buf_tail(buf),
480+
UDC_STM32_EP0_MAX_PACKET_SIZE);
481+
return;
482+
} /* else: buf->len == priv->ep0_out_wlength */
431483

432-
if (udc_ctrl_stage_is_status_in(dev)) {
484+
/*
485+
* Data stage is complete: update to next step
486+
* which should be Status IN, then submit the
487+
* Setup+Data phase buffers to UDC stack and
488+
* let it handle the next stage.
489+
*/
490+
udc_ctrl_update_stage(dev, buf);
491+
__ASSERT_NO_MSG(udc_ctrl_stage_is_status_in(dev));
433492
udc_ctrl_submit_s_out_status(dev, buf);
434493
}
435494
} else {
436495
udc_submit_ep_event(dev, buf, 0);
437496
}
438497

498+
/* Buffer was filled and submitted - remove it from queue */
499+
(void)udc_buf_get(epcfg);
500+
501+
/* Endpoint is no longer busy */
502+
udc_ep_set_busy(epcfg, false);
503+
504+
/* Prepare next transfer for EP if its queue is not empty */
439505
buf = udc_buf_peek(epcfg);
440506
if (buf) {
507+
/*
508+
* Only the driver is allowed to queue transfers on OUT EP0,
509+
* and it should only be doing so once per Control transfer.
510+
* If it has a queued transfer, something must be wrong.
511+
*/
512+
__ASSERT(epcfg->addr != USB_CONTROL_EP_OUT,
513+
"OUT EP0 should never have pending transfers!");
514+
441515
udc_stm32_rx(dev, epcfg, buf);
442516
}
443517
}
@@ -548,7 +622,7 @@ static void handle_msg_setup(struct udc_stm32_data *priv)
548622

549623
if (udc_ctrl_stage_is_data_out(dev)) {
550624
/* Allocate and feed buffer for data OUT stage */
551-
err = usbd_ctrl_feed_dout(dev, udc_data_stage_length(buf));
625+
err = udc_stm32_prep_out_ep0_rx(dev, udc_data_stage_length(buf));
552626
if (err == -ENOMEM) {
553627
udc_submit_ep_event(dev, buf, err);
554628
}

0 commit comments

Comments
 (0)