Skip to content

Speed up firmware updates - #2002

Open
NickeZ wants to merge 2 commits into
BitBoxSwiss:masterfrom
NickeZ:nickez/erase-first-then-flash
Open

Speed up firmware updates#2002
NickeZ wants to merge 2 commits into
BitBoxSwiss:masterfrom
NickeZ:nickez/erase-first-then-flash

Conversation

@NickeZ

@NickeZ NickeZ commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Approximately halfs the current flashing time to 55s.


Erase the full firmware area on OP_ERASE, then flash 4 KiB firmware chunks.

@NickeZ
NickeZ requested a review from benma June 30, 2026 08:14

@benma benma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems overly complicated, I think the cache/flush is not needed?

Why not just erase in OP_ERASE, and just use flash_append with 4kB chunks during flashing?

Also reject signature data before all declared firmware chunks have been received and flushed.

Why? One can't successfully do it anyway beforehand, as that API call rejects on invalid signatures. Can't hurt but seems not needed.

@adam2k adam2k left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm new here and disclaimer up front I do not have a dev device, so I am doing static analysis only.

@benma asked whether the cache is needed. I believe it is not, and the HAL explains the speedup: flash_write -> _flash_write (hpl_nvmctrl.c:158) is read-modify-write per 8 kB block, so two 4 kB chunks cost two erase-and-reprogram cycles. flash_append -> _flash_append (:204) programs page by page with no erase. Chunks sit at FLASH_APP_START + n*4096 with FLASH_APP_START == 0x10000, so they're always page-aligned. After the upfront erase, plain flash_append per chunk should be enough.

Two branches in the current shape already look dead as a result. I've noted those inline.

Comment thread src/bootloader/bootloader.c Outdated
return OP_STATUS_OK;
}

if (_firmware_app_erased || _is_erased((const void*)addr, FIRMWARE_BLOCK_LEN)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch looks unreachable. _firmware_app_erased is cleared only at 588, immediately after _loading_ready = false (586), and set true at 604 before _loading_ready = true (608). _api_write_chunk requires _loading_ready on entry and only re-arms it on success, so _loading_ready implies _firmware_app_erased. Both the _is_erased() call and the flash_write branch below are dead.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, not applicable anymore.

Comment thread src/bootloader/bootloader.c Outdated
}

const uint32_t addr = FLASH_APP_START + _firmware_cached_block * FIRMWARE_BLOCK_LEN;
if (MEMEQ((const void*)addr, _firmware_block_cache, FIRMWARE_BLOCK_LEN)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Near-dead: post-erase the block is 0xFF and each block is written once, so this only hits on an all-0xFF chunk pair, at the cost of an 8 kB compare per block. The old per-chunk MEMEQ paid for itself because flash wasn't pre-erased.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, not applicable anymore.

Comment thread src/bootloader/bootloader.c Outdated
if (chunknum > _firmware_num_chunks - 1 || chunknum > FIRMWARE_MAX_NUM_CHUNKS - 1) {
// The second is redundant, as _firmware_num_chunks <= FIRMWARE_MAX_NUM_CHUNKS.
if (chunknum > _firmware_num_chunks - 1 || chunknum > FIRMWARE_MAX_NUM_CHUNKS - 1 ||
chunknum != _firmware_next_chunk) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes out-of-order writes a hard error, tightening the host protocol permanently in the bootloader. bootloader.py:191 is sequential so the bundled client is fine, but worth confirming BitBoxApp is too, and that it's intended as a contract change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, not applicable anymore.

if (!MEMEQ((const void*)addr, empty_page, sizeof(empty_page))) {
return _report_status(OP_STATUS_ERR_CHECK, output);

for (uint32_t addr = FLASH_APP_START; addr < FLASH_APP_START + FLASH_APP_LEN;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This erases 864 kB (108 blocks) in one blocking call, where previously a full-size firmware erased almost nothing here. Any host-side timeout on OP_ERASE? _render_progress(0) is also drawn before the loop, so the screen sits at 0% throughout. Disclaimer: I do not have a dev device, so I can't measure it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't noticed any issues in testing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That call to _render_progress(0) was probably removed, so now it sits at "Upgrading" instead.

bootloader_render_default_screen();
} else {
_render_progress((float)chunk_num / (float)(_firmware_num_chunks - 1));
float progress = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real fix rather than cleanup: at _firmware_num_chunks == 1 the old expression is 0/0 = NaN. Unrelated to the caching change, so maybe its own commit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@adam2k

adam2k commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Why not just erase in OP_ERASE, and just use flash_append with 4kB chunks during flashing?

+1 for the above comment. _api_write_sig_data -> _set_firmware_data -> _firmware_verified_jump() verifies over what's actually in flash, so an incomplete firmware fails there regardless.

@NickeZ
NickeZ force-pushed the nickez/erase-first-then-flash branch from 1185c09 to 1956603 Compare August 25, 2026 07:55
@NickeZ

NickeZ commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Simplified PR. Still flashes in 55 seconds. It is on "Upgrading" screen while erasing for about 4-5 seconds. It doesn't stand still at 0%.

@NickeZ NickeZ changed the title Cache bootloader firmware writes Speed up firmware updates Aug 25, 2026
NickeZ added 2 commits August 31, 2026 15:02
Erase the whole app flash area up front.

Append each 4 KiB firmware chunk directly when received. Allow arbitrary chunk order and reject writes to non-erased ranges.
Render one-chunk firmware updates as complete without evaluating the progress ratio with a zero denominator.
@NickeZ
NickeZ force-pushed the nickez/erase-first-then-flash branch from db26bae to 64c9d84 Compare August 31, 2026 13:02
@NickeZ

NickeZ commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Simplified PR. Still flashes in 55 seconds. It is on "Upgrading" screen while erasing for about 4-5 seconds. It doesn't stand still at 0%.

Actually flashing only takes about 60 seconds. So there is more like a 10% upgrade. I guess we already removed a lot of double work in some earlier PR.

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.

3 participants