usbd: keep RX_PENDING until the class consumes, not just through xfer_cb The whole point of the bit is that claim() fails until the buffer is consumed. Releasing it after xfer_cb returns reopened the #1292 window for a class that hands the buffer to the application and re-arms from another task later. Every in-tree OUT path consumes or re-arms via usbd_edpt_xfer, which clears the bit.
diff --git a/src/device/usbd.c b/src/device/usbd.c index 6878208..f702bce 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c
@@ -759,8 +759,8 @@ TU_ASSERT(driver,); } - // Clear busy + claimed. A class OUT endpoint goes straight to RX_PENDING, so no other task can claim - // and re-arm it before its buffer is consumed (#1292) + // Clear busy + claimed. An OUT endpoint changed to RX_PENDING, so no other task can claim and re-arm it before + // its buffer is consumed uint8_t const rx_pending = (0 != epnum && ep_dir == TUSB_DIR_OUT) ? TU_EDPT_STATE_RX_PENDING : 0u; _usbd_dev.ep_status[epnum][ep_dir] = (uint8_t) ( (_usbd_dev.ep_status[epnum][ep_dir] & ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) | rx_pending); @@ -777,7 +777,6 @@ } else { TU_LOG_USBD(" %s xfer callback\r\n", driver->name); driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); - usbd_edpt_rx_consume(event.rhport, ep_addr); // in case xfer_cb did not } break; }
diff --git a/test/unit-test/test/device/usbd/test_usbd.c b/test/unit-test/test/device/usbd/test_usbd.c index 56b01ca..a221f86 100644 --- a/test/unit-test/test/device/usbd/test_usbd.c +++ b/test/unit-test/test/device/usbd/test_usbd.c
@@ -456,10 +456,12 @@ return true; } -// xfer_cb that neither consumes nor re-arms: usbd releases the endpoint once xfer_cb returns -void test_usbd_out_complete_released_after_xfer_cb(void) { +// xfer_cb that neither consumes nor re-arms: the hold outlives xfer_cb until the class consumes +void test_usbd_out_complete_held_after_xfer_cb_until_consumed(void) { msc_out_armed(); msc_out_complete(xfer_cb_no_consume); + TEST_ASSERT_FALSE(usbd_edpt_claim(rhport, EDPT_MSC_OUT)); + usbd_edpt_rx_consume(rhport, EDPT_MSC_OUT); TEST_ASSERT_TRUE(usbd_edpt_claim(rhport, EDPT_MSC_OUT)); } @@ -473,7 +475,7 @@ return true; } -// the consume after xfer_cb returns must not touch the transfer xfer_cb re-armed +// a transfer re-armed inside xfer_cb stays BUSY and is not held by RX_PENDING void test_usbd_out_rearmed_in_xfer_cb_stays_busy(void) { msc_out_armed(); msc_out_complete(xfer_cb_consume_rearm);