[Gen 3] Fix USB CDC write triggering an assertion failure; migrates wiring/usbserial to be run under test runner, more usb tests - #2939
Conversation
scott-brust
left a comment
There was a problem hiding this comment.
Works for me on msom
➜ device-os git:(fix/gen3-usb-cdc-migrate-usbserial-tests) ✗ device-os-test --device-os-dir=. run msom wiring/usbserial wiring/usb
USB
msom
systemThread=enabled
✓ USB_00_SystemEchoShort (4.8s)
✓ USB_01_SystemEchoLarge (1s)
✓ USB_02_SystemEchoConcurrent
✓ USB_03_AppCustomEcho
✓ USB_04_AppCustomEchoDeferred (3.3s)
✓ USB_05_DiagnosticInfo
✓ USB_06_RawVendorRequests (1.1s)
✓ USB_07_ServiceRequestCancellation
✓ USB_08_DeviceDescriptor
✓ USB_09_InterfaceLayout
✓ USB_10_MsftOsStringDescriptor
✓ USB_11_WcidCompatIdDescriptor
✓ USB_12_EnterListeningMode (1.4s)
✓ USB_13_DeviceObservesListeningMode (1.3s)
✓ USB_14_DeviceObservesNormalMode
✓ USB_15_HostBusResetRecovery (16.9s)
[2a] Hub-cycle disabled, device not found by portPath
[2a] Hub-cycle disabled, device not found by portPath
[2a] Hub-cycle disabled, device not found by portPath
[2a] Hub-cycle disabled, device not found by portPath
Opened USB CDC port /dev/cu.usbmodem1102 for 0a10aced202194944a04b000
✓ USB_16_DeviceEndBeginStress (34.6s)
✓ USB_17_FinalCdcSanity
USBSerial
msom
systemThread=enabled
Opened USB CDC port /dev/cu.usbmodem1102 for 0a10aced202194944a04b000
✓ USBSERIAL_00_RingBufferHelperIsSane (4.9s)
✓ USBSERIAL_01_SerialDoesNotDeadlockWhenInterruptsAreMasked
✓ USBSERIAL_02_ReadWrite
✓ USBSERIAL_03_ReadWriteVerifiesHostData
✓ USBSERIAL_04_isConnectedInitially
✓ USBSERIAL_05_ClosedPortWritesFailWithoutBlocking
✓ USBSERIAL_06_isConnectedDetectsReopenedPort
✓ USBSERIAL_07_EndBeginWhilePortIsClosed (4.3s)
✓ USBSERIAL_08_RxBufferSetup (1.1s)
✓ USBSERIAL_09_RxBufferFillsCompletelyFirst
✓ USBSERIAL_10_RxBufferFillsCompletelySecond
✓ USBSERIAL_11_RxBufferFillsCompletelyThird
✓ USBSERIAL_12_NonBlockingWriteStressSetup
✓ USBSERIAL_13_NonBlockingWriteStressHandlesBackpressure
✓ USBSERIAL_14_BlockingWriteStressWritesEveryBuffer (2.3s)
✓ USBSERIAL_15_NonBlockingCharacterWriteStressSetup
✓ USBSERIAL_16_NonBlockingCharacterWriteStressHandlesBackpressure (1.8s)
✓ USBSERIAL_17_BlockingCharacterWriteStressWritesEveryByte (16.3s)
✓ USBSERIAL_18_DeviceReceiveStressSetup
✓ USBSERIAL_19_DeviceReceiveStress (2.8s)
38 passing (2m)
user/tests/app/usb_ctrl_request predates the test runner and never runs
Do we want to delete this app since its effectively been replaced?
| } | ||
|
|
||
| int SerialUSBStream::skip(size_t size) { | ||
| return read(nullptr, size); |
There was a problem hiding this comment.
I think there is an issue with this.
On gen3 read(nullptr, size) -> hal_usb_cdc_pvt_recv_data() -> app_fifo_read() with a null buffer will be interpreted as querying the length of the fifo and return NRF_SUCCESS with the size skipped set to the entire length of the fifo:
uint32_t app_fifo_read(app_fifo_t * p_fifo, uint8_t * p_byte_array, uint32_t * p_size)
{
...
const uint32_t byte_count = fifo_length(p_fifo);
...
(*p_size) = byte_count;
// Check if application has requested only the size.
if (p_byte_array == NULL)
{
return NRF_SUCCESS;
}
...
I think we need to have a dummy buffer to actually consume the bytes
skip() doesnt appear to be used right now but if someone does use it, I dont think it will work
There was a problem hiding this comment.
Yup, looks like this is broken. Will fix and add tests, as it wasn't caught. Good catch.
There was a problem hiding this comment.
There aren't any tests for SerialUSBStream right now, right? Its only used for USB tethering, which have their own tests on top of it I suppose.
There was a problem hiding this comment.
yeah, but HAL_USB_USART_Peek_Buffer() and others are testable.
| if (r == SYSTEM_ERROR_NO_MEMORY) { | ||
| return 0; |
There was a problem hiding this comment.
For gen4, SYSTEM_ERROR_NO_MEMORY means there is no data to peek?
There was a problem hiding this comment.
Kinda weird, I admit. I'll take another look at peek/read in particular
| uint32_t result = app_fifo_write(&m_usb_instance.tx_fifo, data, &bytesToWrite); | ||
| if (result == NRF_ERROR_NO_MEM) { | ||
| return SYSTEM_ERROR_NO_MEMORY; | ||
| } | ||
| SPARK_ASSERT(result == NRF_SUCCESS); |
Problem
On Gen 3 a non-blocking
Serial.write()racing a full TX buffer could SOS the device.hal_usb_cdc_pvt_send_data()SPARK_ASSERTed thatapp_fifo_write()succeeds, but a full FIFO returnsNRF_ERROR_NO_MEM- so writing into a full buffer was an assertion failure instead of a short write.SerialUSBStreamhad the same problem one layer up: errors from the HAL propagated out ofread()/peek()/write()instead of being reported as "0 bytes done". This is a 6.5.0 regression introduced in USB Serial/UART serial buffer read/write/peek + wait event #2935wiring/usbserialwas a manual test, so it never ran on HIL.USB coverage beyond CDC data was basically nonexistent: control-request transport (chunking, concurrency, cancellation), descriptors/WCID, device modes and bus-reset robustness had no automated tests.
user/tests/app/usb_ctrl_requestpredates the test runner and never runs.Solution
hal_usb_cdc_pvt_send_data()returnsSYSTEM_ERROR_NO_MEMORYon a full FIFO instead of asserting;SerialUSBStreamtreats it as a zero-length transfer.Migrated
wiring/usbserialunder the test runner (20 tests). Host side drives the CDC tty via a newUsbCdcPorthelper (user/tests/integration/test/usb_cdc.js): resolves the tty by USB serial number through sysfs, toggles DTR via open/close, creates TX backpressure by pausing the reader. Covers blocking/non-blocking TX stress, RX stress,isConnected()transitions, RX buffer fill,end()/begin()while the port is closed.New
wiring/usbsuite (18 tests, gen3 + gen4) - everything USB that isn't CDC data:CTRL_REQUEST_ECHO) across the allocation/chunking boundaries (1–10000 bytes: pooled vs async alloc at 64, host-side chunked SEND/RECV above 4096), 4 concurrent requestsPENDINGacross multiple host CHECK polls and complete out of orderbRequest0x50:DEVICE_ID,SYSTEM_VERSION), raw INIT->RESET->CHECK cancellation,DIAGNOSTIC_INFO@particle/device-constants, iSerialNumber == device ID, per-platform interface layout, MS OS string + WCID compat ID / extended propertiesSerial.end()/begin()re-enumeration stressSupersedes
user/tests/app/usb_ctrl_request(GET_MODULE_INFOis already covered by the ota/* suites).Test runner application: the request handler gained an
eecho command with an optional delay - delayed requests are completed fromtestAppLoop(), same pattern as the oldusb_ctrl_requestapp. Removed the deadRequestHandler::instance()singleton: the live handler isTestSuite::reqHandler_, and the stale singleton silently absorbed the loop hook (deferred requests queued on one object, drained on the other). Addedtest/platform_util.js- wiring specs can'trequire()node modules directly (module resolution uses the spec's realpath, which is outsideintegration/), so helpers with npm deps have to live inintegration/test/.Steps to Test