Repository navigation
Faster CIRCUITPY saves on Zephyr boards - #11615
Merged
Merged
Conversation
On zephyr-cp, USB MSC writes run in their own thread while the idle flush runs in the VM thread. supervisor_flash_flush() cleared filesystem_dirty and stopped the tick after flushing, and flash_write_blocks() marked dirty before writing. A block cached in between stayed in RAM with no tick to write it out. Today the flush on every MSC write hides this. Clear the flag before flushing and mark dirty after writing. This also changes ports whose write path calls supervisor_flash_flush() on a page change: external_flash.c, nordic, stm, silabs, mimxrt and cxd56. That flush cleared the flag and stopped the tick, so the newly cached page waited for the next write, sync or reload. Now the 1 s flush stays armed for it. raspberrypi and espressif flush the old page with port_internal_flash_flush() and are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a write moved to a different flash page, _flash_write_block() flushed the old page with supervisor_flash_flush(). That also clears filesystem_dirty and stops the supervisor tick. littlefs marks the filesystem dirty before writing, so its newly cached page was never written by the background flush. Called from the USB MSC thread, it could also race the VM thread's background flush, with both seeing the flag set and both disabling the tick. Flush only the old page, as ports/raspberrypi does with port_internal_flash_flush(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Zephyr's USB MSC class issues DISK_IOCTL_CTRL_SYNC at the end of every WRITE10, and _zephyr_disk_ioctl() turned that into a full flash flush. The host waited for a page erase and reprogram after each command, and a page written by consecutive commands, such as the FAT, was erased once per command. Leave the cached page for the next page change, the background flush and autoreload, as the TinyUSB ports do. Unlike them, eject doesn't flush: Zephyr's MSC class doesn't pass START STOP UNIT to the disk. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Overwrite a sector, then one three pages later, and never flush or close the file. Only the background flush can write the last sector before the simulator is stopped. Fails on main, where the page change stopped the tick. Do the same on littlefs, which marks the filesystem dirty before writing. Fill and free the drive first so new blocks aren't erased, since writes to erased flash skip the page cache. Fails without the previous commit to ports/zephyr-cp/supervisor/flash.c. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the default 512 byte SCSI buffer every sector of a write is its own round trip through the MSC thread and the disk layer, about 4 ms per sector on RP2040. A 4 KB buffer moves eight sectors at a time. It costs 7 KB of RAM with double buffering. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dhalbert
reviewed
Oct 16, 2026
dhalbert
left a comment
Collaborator
There was a problem hiding this comment.
Just questions, not a review, but changes make sense.
- I'm not clear how the
supervisor/shared/flash.cchange changes the behavior of the non-zephyr ports. - Is there a non-zephyr test? I guess it would have to be manual.
- Does #11613 need some rework after this?
Collaborator
Author
|
Thanks!
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dhalbert
approved these changes
Oct 16, 2026
dhalbert
left a comment
Collaborator
There was a problem hiding this comment.
Thanks, this seems like an improvement in several ways.
dhalbert
enabled auto-merge
October 16, 2026 21:27
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.
What
Saving files to CIRCUITPY over USB is faster on Zephyr builds.
Why
Zephyr builds rewrote flash after every USB write, and the computer waited each time.
Changes
supervisor/shared/flash.cports/zephyr-cp/supervisor/flash.cports/zephyr-cp/supervisor/usb.cports/zephyr-cp/prj.confports/zephyr-cp/tests/test_flash.pyResults
Linux host, seconds, median of 10.
* Without the shared
flash.ccommit, which was added after these runs.Cost
Tested
feather_rp2040_zephyrimage), Pico W (rpi_pico_w_zephyrimage), Feather nRF52840 Expressflash.cmerges cleanly;test_flash.pyneeds a trivial mergeAI assistance
Code and measurements by Claude at my direction, on my test boards.
I reviewed the code and checked the results.
🤖 Generated with Claude Code