Skip to content

fix: size CURRENT.UF2 to the real installed app, not the max region - #20

Merged
jamesarich merged 3 commits into
masterfrom
fix/current-uf2-real-app-size
Aug 19, 2026
Merged

fix: size CURRENT.UF2 to the real installed app, not the max region#20
jamesarich merged 3 commits into
masterfrom
fix/current-uf2-real-app-size

Conversation

@jamesarich

@jamesarich jamesarich commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

While hardware-testing #19 (nrfx/tinyusb bump) on a real RAK4631, restoring the app via the bootloader's own CURRENT.UF2 (a live dump of the app region, generated by ghostfat.c's read_block()) reproducibly hung the device — even though the bytes being written back were byte-identical to what was already there. A control test confirmed this is pre-existing, not caused by #19: the unmodified, currently-shipped bootloader hangs the exact same way.

Digging into it turned up real history:

  • CURRENT.UF2 currently reports/generates content sized to TRUE_USER_FLASH_SIZE — the max possible app+SoftDevice region — regardless of how big the actually-installed app is. On the test device (RAK4631, Meshtastic 2.7.26), that's ~390KB more than the real firmware image (3728 vs 2966 UF2 blocks), padding out with whatever's physically sitting in flash past the real app+SD boundary.
  • adafruit/Adafruit_nRF52_Bootloader#38 (2018) fixed a related CURRENT.UF2 problem by sizing it off bootloader_settings.bank_0_size (the real recorded size of the currently-installed app) instead of a fixed constant. That fix has no trace left in the current file — it was dropped somewhere along the way, likely during #128's SoftDevice-decoupling rewrite in 2020 (whose own checklist claims CURRENT.UF2 was tested working at the time).
  • adafruit/Adafruit_nRF52_Bootloader#201 (2020) documents copying CURRENT.UF2 back onto the drive as a working community-known recovery trick for devices stuck in DFU mode — so this round-trip is supposed to be reliable, not "expected to be flaky."

Fix

Two commits, both required together:

  1. ghostfat.c: restores the same idea as the 2018 fix — current_flash_size(), reading bootloader_settings.bank_0_size with the same zero/erased-flash fallback to the max region — grafted onto the current SoftDevice-decoupled code, which computes the max bound differently (TRUE_USER_FLASH_SIZE, not the 2018 code's constant).
  2. msc_uf2.c: found while testing commit 1 — bank_0_size was still being recorded as 0 for any app flashed via plain UF2 drag-and-drop (as opposed to DFU-serial, which populates it correctly), because tud_msc_write10_complete_cb()'s app-completion branch never set update_status.app_size before calling bootloader_dfu_update_process(). Commit 1 does nothing for the common case without this.

Test plan

  • tools/build_all.py — all 14 boards build clean
  • Hardware retest of the exact CURRENT.UF2 dump-and-restore sequence that hung before, on the same RAK4631 — confirmed fixed. After both commits: CURRENT.UF2 reports exactly 1518592 bytes, an exact match with the official release UF2 (previously 1908736 bytes of padding). Dumped it and copied it straight back — device rebooted in ~2 seconds and came back fully functional, versus the previous indefinite hang / 36-second-then-still-broken behavior (details in PR comments).
  • Only one board (RAK4631) hardware-tested. The bank_0_size fix in particular is board-independent (it's in shared usb/ code, not src/boards/), but broader testing before merge is still reasonable for boot-critical code like this.

Summary by CodeRabbit

  • Bug Fixes
    • UF2 application updates now accurately reflect the application’s actual size.
    • Generated update files use the appropriate storage range, reducing unnecessary file size and improving compatibility with update tools.
    • Added safe fallback behavior when application size information is unavailable or invalid.

CURRENT.UF2 (the on-the-fly dump of whatever's currently flashed,
generated by read_block()) has reported and generated content sized to
TRUE_USER_FLASH_SIZE - the max possible app+SoftDevice region - rather
than how big the actually-installed app is. On a RAK4631 running
Meshtastic 2.7.26, that meant CURRENT.UF2 was ~390KB larger than the
official firmware image (3728 vs 2966 UF2 blocks), padding out with
whatever's physically sitting in flash past the real app+SD boundary.

Copying CURRENT.UF2 back onto the drive to restore the app it came
from - a workaround documented as working by users hitting stuck-in-
DFU-mode bugs (adafruit#201) - reproducibly
hung the device on real RAK4631 hardware while investigating the
nrfx/tinyusb bump in #19, on both this repo's current bootloader and
the bumped one (a control test on the unmodified bootloader hung the
same way, ruling out the bump as the cause).

adafruit#38 (2018) fixed a related
CURRENT.UF2 problem by sizing it off bootloader_settings.bank_0_size -
the real recorded size of the currently-installed app - instead of a
fixed constant. That fix has no trace left in the current file; this
restores the same idea (current_flash_size(), with the same
zero/erased-flash fallback to the max region) grafted onto the current
SoftDevice-decoupled code from adafruit#128, which computes the max bound
differently (TRUE_USER_FLASH_SIZE) than the 2018 code did.

Verified: all 14 boards still build via tools/build_all.py. Hardware
retest of the exact CURRENT.UF2 dump-and-restore sequence that hung
before is next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d15d947-30db-42ef-95f6-2947d58e98e4

📥 Commits

Reviewing files that changed from the base of the PR and between f41dab2 and edd1e4c.

📒 Files selected for processing (2)
  • src/usb/msc_uf2.c
  • src/usb/uf2/ghostfat.c

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The update process records the written application size. The virtual FAT filesystem uses this value to calculate CURRENT.UF2 sectors, directory size, FAT chaining, and generated UF2 block counts.

Changes

UF2 application sizing

Layer / File(s) Summary
Record written application size
src/usb/msc_uf2.c
Application completion stores _wr_state.numWritten * 256 in update_status.app_size.
Size CURRENT.UF2 dynamically
src/usb/uf2/ghostfat.c
The installed application size is rounded to UF2 payload boundaries. Invalid values use TRUE_USER_FLASH_SIZE. FAT chaining, directory metadata, UF2 generation bounds, and block counts use the calculated size.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to edd1e

The change sizes CURRENT.UF2 to the installed application and records that size for UF2 installs; no actionable merge-blocking risk remains, so the PR is merge-ready after normal checks.

Poem

A rabbit checks each UF2 byte,
Records the size just right.
The FAT chain follows every bound,
While smaller files are neatly found.
“Hop!” says the hare, “the blocks align!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: sizing CURRENT.UF2 to the installed application instead of the maximum region.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jamesarich

Copy link
Copy Markdown
Collaborator Author

Hardware test: confirmed fixed, on the same RAK4631 that reproduced the hang

Found a second bug needed to make this fix actually take effect for the common case (app flashed via plain UF2 drag-and-drop, not DFU-serial): msc_uf2.c's tud_msc_write10_complete_cb() never set update_status.app_size before calling bootloader_dfu_update_process() for a completed app write — it stayed 0 from the initial memset, so bootloader_settings.bank_0_size was always recorded as 0 for any UF2-flashed app, regardless of this PR's ghostfat.c change. Added the missing update_status.app_size = _wr_state.numWritten * 256; assignment (256 = the UF2 format's fixed payload-per-block size) alongside the ghostfat.c fix.

Verified on hardware, step by step:

  1. Self-updated the bootloader to this branch's build via the UF2 self-update mechanism (no serial DFU needed for this step).
  2. Reflashed the app (official firmware-rak4631-2.7.26.54e0d8d.uf2) under the fixed bootloader.
  3. CURRENT.UF2 now reports 1518592 bytes — an exact match with the official release UF2's size (previously 1908736 bytes, ~390KB of padding past the real app).
  4. Dumped CURRENT.UF2 and copied it straight back (the exact sequence that hung before, both on this branch's earlier pre-msc_uf2.c-fix state and on the unmodified currently-shipped bootloader in the control test).
  5. Device rebooted in ~2 seconds and came back fully functional (screen normal, same node identity) — matching the healthy pattern from a normal firmware flash, not the 36s-or-indefinite-hang pattern from before.

Both commits in this PR are needed together — ghostfat.c alone doesn't change anything until bank_0_size is actually populated correctly.

ghostfat.c's current_flash_size() (this branch's other commit) reads
bootloader_settings.bank_0_size to size CURRENT.UF2 to the real
installed app. But tud_msc_write10_complete_cb()'s app-completion
branch never set update_status.app_size before calling
bootloader_dfu_update_process() - it stayed 0 from the initial memset,
so bank_0_size was always recorded as 0 for any app flashed via plain
UF2 drag-and-drop (as opposed to the DFU-serial protocol, which
transmits size upfront and was unaffected).

Verified on hardware: after this fix, CURRENT.UF2 exactly matches the
real firmware size (1518592 bytes, matching the official release UF2
byte for byte) instead of the previous 1908736 bytes of padding. The
CURRENT.UF2 dump-and-restore sequence that reproducibly hung the
device before now completes in ~2 seconds with no hang.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jamesarich added a commit that referenced this pull request Aug 19, 2026
#20 root-caused and fixed what this note previously flagged as an open
mystery hit during this PR's hardware testing.
jamesarich added a commit that referenced this pull request Aug 19, 2026
* fix: bump lib/nrfx and lib/tinyusb together, fix the fallout

Renovate's PR #11 (nrfx digest) and #12 (tinyusb digest) each failed CI on
every board. Investigated both:

- nrfx alone (bumped to Renovate's target, nrfx 4.5.0/1b7bedb) breaks the
  build immediately: nrfx 4.0 restructured its whole layout (mdk/ ->
  bsp/stable/mdk/, etc.), so this repo's Makefile IPATHs can't find nrf.h at
  all. That's a real rework, not a digest bump - pinned to v3.14.0 instead,
  the last tag before the 4.0 restructure.
- tinyusb alone (Renovate's target, 5c0e31c) fails to compile against the
  old 2019 nrfx pin: its nrf5x USB port calls nrfx's chip-errata functions
  (nrf52_errata_199()) that don't exist yet at that nrfx version. The two
  submodules move together.

With nrfx at v3.14.0 and tinyusb at 5c0e31c, three more issues surfaced,
all fixed here:
- tinyusb merged device/usbd_control.c into usbd.c; the Makefile's hardcoded
  tinyusb C_SRC list still named the now-gone file.
- nrfx renamed nrf_wdt_started()/nrf_wdt_request_status() in the WDT HAL
  (see lib/nrfx/CHANGELOG.md) - the one in-repo call site needed updating.
- nrfx's gcc_startup_<mcu>.S (since nrfx 3.x) copies .data via
  __data_start/__data_end (no trailing underscore) instead of this repo's
  __data_start__/__data_end__, plus three new RAM-loaded regions
  (.sdata/.tdata/.fast) nothing here uses. Added alias symbols and
  zero-length filler for the unused regions to linker/nrf_common.ld -
  deliberately not swapping in nrfx's own updated linker script, which also
  redefines .bss/.noinit placement and would fight this repo's board .ld
  files' fixed-address NOINIT region (BLE bond exchange across a DFU
  reset).

Verified: all 14 boards build and link clean via tools/build_all.py.

Not done here: real hardware testing. This is still the multi-year
boot-critical submodule bump AGENTS.md already flagged as needing it
before merge - a clean build is necessary, not sufficient.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: record the CURRENT.UF2 restore gotcha found during hardware testing

Real RAK4631 hardware testing for this branch (see PR #19) hit this:
restoring an app via ghostfat's live CURRENT.UF2 dump left the device
hung on boot even though the bytes matched what was there before. A
normal release UF2 or OTA-DFU flash both worked fine immediately after.
Recording it so it doesn't get mistaken for a regression from this PR's
actual change (nrfx/tinyusb) next time someone hits it.

* docs: update the CURRENT.UF2 gotcha now that it's fixed, not untriaged

#20 root-caused and fixed what this note previously flagged as an open
mystery hit during this PR's hardware testing.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@jamesarich
jamesarich merged commit 18758d9 into master Aug 19, 2026
17 checks passed
@jamesarich
jamesarich deleted the fix/current-uf2-real-app-size branch August 19, 2026 12:38
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