From cd7b8c243dadc6c480294e8f11069e39d3daf724 Mon Sep 17 00:00:00 2001 From: wslee Date: Wed, 26 Aug 2026 03:09:53 +0900 Subject: [PATCH 1/2] fix(cdc): stop arming OUT endpoint with a NULL buffer USBD_CDC_Receive() falls back to USBD_CDC_ClearBuffer() when the receive queue has no room left for another 64 byte block. That helper re-arms the OUT endpoint with a NULL application buffer: USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, 0, 0); The endpoint is valid again while ep->xfer_buff is NULL. The next OUT packet the host sends reaches HAL_PCD_IRQHandler(), which copies the packet without checking the destination: if (count != 0U) { USB_ReadPMA(hpcd->Instance, ep->xfer_buff, ep->pmaadress, count); } The packet lands at address 0 and the write faults. On a STM32F103C8 this is an imprecise bus fault escalated to a hard fault (CFSR IMPRECISERR, HFSR FORCED), and the board stops answering until reset. Do not arm the endpoint when there is no room. An unarmed bulk OUT endpoint NAKs, the host retries, and USBSerial::read() and the readBytes() family already call CDC_resume_receive() after dequeuing, so the endpoint is armed with a real block as soon as the sketch drains. That is the flow control this transfer needs. Fixes #1399 Co-Authored-By: Claude Opus 5 --- libraries/USBDevice/src/cdc/usbd_cdc_if.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/USBDevice/src/cdc/usbd_cdc_if.c b/libraries/USBDevice/src/cdc/usbd_cdc_if.c index 922b7d57e3..2fe9614fb0 100644 --- a/libraries/USBDevice/src/cdc/usbd_cdc_if.c +++ b/libraries/USBDevice/src/cdc/usbd_cdc_if.c @@ -240,10 +240,15 @@ static int8_t USBD_CDC_Receive(uint8_t *Buf, uint32_t *Len) /* It always contains required amount of free space for writing */ CDC_ReceiveQueue_CommitBlock(&ReceiveQueue, (uint16_t)(*Len)); receivePended = false; - /* If enough space in the queue for a full buffer then continue receive */ - if (!CDC_resume_receive()) { - USBD_CDC_ClearBuffer(&hUSBD_Device_CDC); - } + /* + * If there is enough space in the queue for a full packet, continue receive. + * Else leave the OUT endpoint unarmed: it then NAKs and the host retries. + * USBSerial::read() and the readBytes() family call CDC_resume_receive() + * after dequeuing, so the endpoint is armed again with a valid block as soon + * as the sketch drains data. Arming it with a NULL buffer instead makes the + * low level driver copy the retried packet to address 0. + */ + (void)CDC_resume_receive(); return ((int8_t)USBD_OK); } From 22f7dd47235552099adba3fbb8d36edb7601f639 Mon Sep 17 00:00:00 2001 From: wslee Date: Wed, 26 Aug 2026 03:10:24 +0900 Subject: [PATCH 2/2] chore(cdc): drop the unused USBD_CDC_ClearBuffer USBD_CDC_Receive() was its only caller. The helper is not part of the ST USB device middleware, it is local to this core, and the only thing it does is arm the OUT endpoint with a NULL application buffer, which is what made a receive overrun fault the MCU. Remove it so it cannot be reused. This also drops a USE_USBD_COMPOSITE branch that would not compile: it reads a bare classId instead of pdev->classId. --- libraries/USBDevice/inc/usbd_cdc.h | 2 -- libraries/USBDevice/src/cdc/usbd_cdc.c | 18 ------------------ 2 files changed, 20 deletions(-) diff --git a/libraries/USBDevice/inc/usbd_cdc.h b/libraries/USBDevice/inc/usbd_cdc.h index 3c32c7c9c2..8db2fbc648 100644 --- a/libraries/USBDevice/inc/usbd_cdc.h +++ b/libraries/USBDevice/inc/usbd_cdc.h @@ -148,12 +148,10 @@ uint8_t USBD_CDC_RegisterInterface(USBD_HandleTypeDef *pdev, uint8_t USBD_CDC_SetTxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff, uint32_t length, uint8_t ClassId); uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t ClassId); -uint8_t USBD_CDC_ClearBuffer(USBD_HandleTypeDef *pdev, uint8_t ClassId); #else uint8_t USBD_CDC_SetTxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff, uint32_t length); uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev); -uint8_t USBD_CDC_ClearBuffer(USBD_HandleTypeDef *pdev); #endif /* USE_USBD_COMPOSITE */ uint8_t USBD_CDC_SetRxBuffer(USBD_HandleTypeDef *pdev, uint8_t *pbuff); uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev); diff --git a/libraries/USBDevice/src/cdc/usbd_cdc.c b/libraries/USBDevice/src/cdc/usbd_cdc.c index d2e8cf7dfb..f3f7200a75 100644 --- a/libraries/USBDevice/src/cdc/usbd_cdc.c +++ b/libraries/USBDevice/src/cdc/usbd_cdc.c @@ -1019,24 +1019,6 @@ uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev) return (uint8_t)USBD_OK; } -#ifdef USE_USBD_COMPOSITE -uint8_t USBD_CDC_ClearBuffer(USBD_HandleTypeDef *pdev, uint8_t ClassId) -{ - /* Suspend or Resume USB Out process */ - if (pdev->pClassDataCmsit[classId] != NULL) { -#else -uint8_t USBD_CDC_ClearBuffer(USBD_HandleTypeDef *pdev) -{ - /* Suspend or Resume USB Out process */ - if (pdev->pClassDataCmsit[pdev->classId] != NULL) { -#endif /* USE_USBD_COMPOSITE */ - /* Prepare Out endpoint to receive next packet */ - USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, 0, 0); - return (uint8_t)USBD_OK; - } else { - return (uint8_t)USBD_FAIL; - } -} #endif /* USBD_USE_CDC */ #endif /* USBCON */