Skip to content

Bug fixes, UF2 hardening, A/B partition support, verified flashing, BOOTSEL reset, tests + CI#1

Open
DavidMenting wants to merge 9 commits into
mainfrom
claude/goofy-allen-4585a2
Open

Bug fixes, UF2 hardening, A/B partition support, verified flashing, BOOTSEL reset, tests + CI#1
DavidMenting wants to merge 9 commits into
mainfrom
claude/goofy-allen-4585a2

Conversation

@DavidMenting

Copy link
Copy Markdown
Member

What

First development pass on the datomusic fork, keeping it generic/upstream-friendly (no Dato-specific features). Eight commits, each phase separately cherry-pickable:

  1. Bug fixespkg/index.js re-exported non-existent constants, breaking import 'picoflash' at module load; missing awaits in endpoint-halt checks; write-stall recovery cleared the halt on the wrong (IN) endpoint; the OTP-write UI logged success while the write was disabled; misc cleanup.
  2. Tests + CI — vitest suite (55 tests: command wire format, status parsing, UF2 decoding, Connection framing/stall recovery against a mock USBDevice) and a GitHub Actions workflow running eslint + vitest.
  3. UF2 hardening — new pkg/uf2.js: decodes into per-family segments, honors NOT_MAIN_FLASH/FAMILY_ID_PRESENT flags, validates families against the connected chip (refuses RP2040 UF2s on RP2350 and vice versa), rejects segments outside flash. Cross-checked byte-for-byte against picotool uf2 convert output.
  4. GET_INFO + A/B partitionspkg/info.js parses SYS_INFO and partition tables (A/B links, accepted families; wire format from pico-sdk bootrom headers and picotool). flashSegments asks the bootrom which partition a family targets — the inactive A/B slot — translates addresses, and rebootFlashUpdate boots the new image via the FLASH_UPDATE reboot type. Device Info panel in the UI.
  5. Chunked flash — per-chunk erase/write with optional read-back verification (fails with the mismatch address), page padding, real progress via onProgress driving the UI bar, and per-transfer endpoint timeouts in Connection.
  6. BOOTSEL reset for running devicespkg/serial-reset.js: vendor reset interface over WebUSB (same mechanism as picotool reboot -f), with the 1200-baud WebSerial touch as fallback. UI button next to Connect.
  7. Docs — CHANGELOG 0.2.0, README fork note, package metadata pointing at this repo.

Why

Basis for a web-based firmware-update flow for an RP2350-based instrument: it needs A/B updates, verified writes, and a way to reboot a running device into the bootloader without touching the BOOTSEL button.

Verification

  • 55 unit tests + eslint green (CI workflow included).
  • Hardware-tested end to end on a Pico 2 running Dato DRUM firmware (3-partition A/B table): vendor reset re-enumerates the running device into BOOTSEL; Device Info matches picotool info -a / picotool partition info; a multi-segment DRUM UF2 flashes with verify to the inactive slot (picotool's absolute-block marker at 0x10ffff00 is correctly skipped) and the FLASH_UPDATE reboot boots it.

Reviewer notes

  • GET_INFO/partition wire formats were taken from pico-sdk boot/picoboot_constants.h, boot/bootrom_constants.h, boot/picobin.h and picotool's usage, not guessed; constants are quoted in pkg/info.js.
  • flash devinfo reports the XIP window (16MB on unfused parts), not fitted flash size — the UI labels it "up to … (devinfo)". A mirror-detection size guess like picotool's would be a follow-up.
  • OTP write remains safety-locked in Connection.otpWrite; the UI now surfaces that honestly instead of logging a fake success.

…ndpoint, honest OTP write reporting

- pkg/index.js re-exported FLASH_END_RP2040/RP2350 which don't exist in
  constants.js, making 'import picoflash' throw at module load
- checkAndClearHalts tested unawaited promises (always truthy)
- bulkWrite stall recovery cleared halt on the IN endpoint instead of OUT
- removed duplicate getHaltState (isEndpointHalted kept)
- removed spurious resetInterface(true) argument
- OTP write UI logged success while the write was commented out; now calls
  otpWrite (which throws its safety lockout) so the user sees the truth
- JSDoc cast typos
- 20 unit tests: PICOBOOT command wire format, status parsing, UF2
  reassembly, Connection framing and stall recovery (with mock USBDevice)
- GitHub Actions: eslint + vitest on push/PR
- root:true in .eslintrc.json, fix floating promise in footer.js
- New pkg/uf2.js: uf2ToSegments groups blocks by family, honors
  NOT_MAIN_FLASH and FAMILY_ID_PRESENT flags, fills sub-sector gaps with
  0xFF and splits across larger gaps; rejects overlapping/corrupt blocks
- validateSegmentsForTarget refuses wrong-chip families (picotool rules)
  and segments addressed outside the XIP flash window
- uf2ToFlashBuffer kept as legacy single-image wrapper
- js/uf2/uf2.js is now a re-export shim; pkg ships uf2.js
- UI loads firmware as segments, logs each, validates at flash time
- 16 new tests; decoder cross-checked against picotool uf2 convert output
- PicobootCmd.getInfo + Connection.getInfo (RP2350)
- pkg/info.js: parseSysInfo, parsePartitionTable (Partition/PartitionTable
  with A/B link and accepts-family decoding), parseUf2TargetPartition,
  flashSizeFromDevinfo; constants from pico-sdk bootrom headers
- Picoboot.getSysInfo/getPartitionTable/getUf2TargetPartition;
  flashSegments writes family segments to the bootrom-designated partition
  (the inactive A/B slot) with address translation and fit checking;
  rebootFlashUpdate uses the FLASH_UPDATE reboot type
- UI: Device Info block (chip id, cpu, devinfo flash window, partitions)
  on connect; Flash uses flashSegments and logs target partitions; Reboot
  uses FLASH_UPDATE after a partition flash
- 10 new tests; wire format cross-checked against picotool sources and
  the connected Pico 2 (3-partition A/B table)
- flashEraseAndWrite now erases/writes per chunk (default 64KB, sector
  aligned), pads the final write to a page boundary, optionally verifies
  each chunk by read-back (failing with the mismatch address), and
  reports progress via an onProgress callback
- flashSegments forwards options with cumulative cross-segment progress
- Connection.bulkRead/bulkWrite race against this.timeouts.endpoint, so
  hung transfers surface as errors and recovery resets the interface
- UI: Verify checkbox (default on); flash progress bar driven by real
  progress instead of a time estimate
- 8 new tests covering chunk sequencing, padding, progress, verify
- pkg/serial-reset.js: resetToBootsel() opens the device's CDC port at
  1200 baud (pico-sdk stdio-USB reset convention) via WebSerial;
  waitForPicobootDevice() polls for re-enumeration of authorized devices
- UI: 'Reboot to BOOTSEL (serial)' button next to Connect, disabled with
  explanation on browsers without WebSerial
- CHANGELOG 0.2.0 entry covering fixes and new functionality
- README fork note and feature list
- pkg version 0.2.0, repository URL points at datomusic/picoflash
Hardware testing on a Pico 2 found two issues:

- Flashing a UF2 containing picotool's absolute-block marker (one block
  at 0x10ffff00) failed with an alignment error: the marker is bootrom
  UF2-download metadata, not image data, and sits outside fitted flash.
  flashSegments now skips it (and tolerates partition-table read
  failures by falling back to absolute addressing).
- The 1200-baud WebSerial touch did not reset the device on macOS.
  Added resetToBootselVendor(): the pico-sdk vendor reset interface via
  WebUSB (class 0xFF/0x00/0x01, RESET_REQUEST_BOOTSEL class request),
  the same mechanism as picotool reboot -f, which is verified working
  with this firmware.  The UI button now uses it, falling back to the
  serial touch when WebUSB is unavailable; the serial recipe also gained
  the conventional DTR-deassert + delay.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant