Garbled long pastes to CircuitPython: one raw-paste window at a time (#50) - #57
Merged
Merged
Conversation
Long exec/run pastes to a CircuitPython board over serial arrived garbled now and then: one 64-byte USB packet lost and the next one received twice, so the paste keeps its length. On CircuitPython's ESP32 ports the VM task can re-arm the CDC OUT endpoint between TinyUSB's USB task marking a transfer complete and copying the packet out (usbd.c clears busy before cdcd_xfer_cb), and mpremote keeps two raw-paste windows in flight, so the host's next packet lands on the uncopied one. The serial transport now waits for the board's window acknowledgement before each 128-byte window when the board runs CircuitPython, so nothing arrives while a packet is still in the endpoint buffer. MicroPython keeps mpremote's writer. On the T-Embed (CircuitPython 10.3.0), 32,000-character pastes through mpftp run: main 22 of 30 intact, this branch 30 of 30 on the same inputs. Through mpremote directly, 89 of 120 stock against 120 of 120 paced.
bdbarnett
force-pushed
the
issue-50-cp-paste
branch
from
September 25, 2026 21:02
5f49795 to
9eaf83a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #50.
What you'd see: a long
execorrunto a CircuitPython board over serial sometimes arrived garbled. So did serialputwith the drive off. It always went wrong the same way: the paste kept its length, but one 64-byte block held the next block's bytes, and that next block came through again straight after it. Put another way, one USB packet was lost and its successor arrived twice. A syntax error, or a string with a patch of wrong characters, is how that shows up.Cause: a race in CircuitPython's USB stack on its ESP32 ports, where TinyUSB runs in a FreeRTOS task of its own.
lib/tinyusb/src/device/usbd.cclears the OUT endpoint'sbusyflag and only then callscdcd_xfer_cb, which copies the packet out of the endpoint buffer.tud_cdc_n_read→_prep_out_transactionre-arms the endpoint on the same buffer.This isn't mpftp's chunking. The host respects the flow control. The firmware loses the data after the flow control has let it through.
Fix:
mpftp/rawpaste.pyadds a serial transport whose raw-paste writer sends one window at a time. It waits for the board's\x01before each next window, which the board sends only once it has read everything sent. So nothing arrives while a packet is still in the endpoint buffer. The sidecar uses the paced writer only when the board runs CircuitPython. MicroPython keeps mpremote's writer.What I proved on the T-Embed (CircuitPython 10.3.0 on COM25; identified by
boot_out.txt's UID on the CIRCUITPY drive; no soft reset, no erase, no file written):mpftp run --followmpftp run --followEach paste is a random base64 string. The board prints its length and CRC-32, and the host compares them with its own.
Speed: paced ran at 43-44 KB/s. Stock ran at 55 KB/s on one run and 6.5 KB/s on the other.
The unit tests drive a fake board that does raw-paste as
pyexec.cdoes. They check that the paced writer never has more than one window unread, and that mpremote's own writer, against the same fake, has more than one (the control). They also check the stop-early\x04path, and that the sidecar paces CircuitPython only.Not done, and your call: the firmware race itself is still there for any other host that runs ahead of the board, mpremote included. It would be an upstream TinyUSB/CircuitPython report. I haven't filed or drafted one.