Repository navigation
fix(recovery): carry out a requested factory reset instead of looping #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -511,6 +511,22 @@ int eos_recovery_enter(eos_bootctl_t *bctl) | |
| bctl->flags &= ~EOS_FLAG_FORCE_RECOVERY; | ||
| eos_bootctl_save(bctl); | ||
|
|
||
| /* Carry out a factory reset the application requested with | ||
| * eos_fw_factory_reset(): erase both slots and return the boot control | ||
| * block to its defaults, which also clears the request. The flag used to | ||
| * route the boot here and nothing more -- no slot was erased, and since | ||
| * only FORCE_RECOVERY is cleared above, every later boot came back to | ||
| * recovery. If an erase fails the flag is left set, so the reset is | ||
| * retried on the next boot rather than reported as done. */ | ||
| if (bctl->flags & EOS_FLAG_FACTORY_RESET) { | ||
| if (eos_slot_erase(EOS_SLOT_A) == EOS_OK && | ||
| eos_slot_erase(EOS_SLOT_B) == EOS_OK) { | ||
|
Comment on lines
+522
to
+523
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Failed reset is not retried When a slot erase fails and a recovery client then requests boot or factory reset, Keep the factory-reset request set until both slot erases succeed, including when handling recovery commands. Prompt for AI agentsIn `core/recovery.c` at lines 522-523:
**Failed reset is not retried**
When a slot erase fails and a recovery client then requests boot or factory reset, `recovery_handle_boot()` clears the factory-reset flag, and the factory-command handler resets boot control even when an erase fails, so the next boot proceeds without retrying the incomplete reset.
Keep the factory-reset request set until both slot erases succeed, including when handling recovery commands.From - Distinguish recoverable from unrecoverable. |
||
| eos_bootctl_init_defaults(bctl); | ||
|
Comment on lines
+522
to
+524
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Factory reset leaves most firmware intact eos_recovery_enter() treats successful eos_slot_erase() calls as proof that both complete slots were erased. On STM32F4, the flash driver ignores the requested length and erases only the sector at each slot’s starting address, leaving the remaining firmware contents in flash while the reset request is cleared. Agent Prompt
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (eos_bootctl_save(bctl) == EOS_OK) | ||
|
Comment on lines
+522
to
+525
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On STM32F4, this path clears the reset request after erasing only part of each slot. Prompt To Fix With AIThis is a comment left during a code review.
Path: core/recovery.c
Line: 522-525
Comment:
**Reset leaves old bytes**
On STM32F4, this path clears the reset request after erasing only part of each slot. `eos_slot_erase()` calls the board erase once, but `stm32f4_flash_erase()` ignores the length and erases one sector. Both slots are 448 KiB, so 384 KiB in Slot A and 320 KiB in Slot B retain old bytes even though the reset is recorded as complete. Make full-slot erasure work on this board before clearing the request.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Comment on lines
+522
to
+525
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Reset failure paths go untested The new tests cover successful erasure and ordinary recovery, but not either erase failing or the control-block save failing. A regression that drops the request instead of retrying after those failures would still pass the suite. Add failure-injection tests for each erase and the control-block save, asserting the request remains persisted for retry. Also at Prompt for AI agentsIn `core/recovery.c` at lines 522-525:
**Reset failure paths go untested**
The new tests cover successful erasure and ordinary recovery, but not either erase failing or the control-block save failing. A regression that drops the request instead of retrying after those failures would still pass the suite.
Add failure-injection tests for each erase and the control-block save, asserting the request remains persisted for retry.
Also at `tests/unit/test_recovery.c:617-643`.From - **Failure cases** — the error paths, not only the happy path. |
||
| eos_boot_log_append(EOS_LOG_FACTORY_RESET, EOS_SLOT_NONE, 0); | ||
|
Comment on lines
+524
to
+526
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Reset misplaces subsequent boot log entries eos_bootctl_init_defaults() resets the persisted log_head to zero without clearing the log flash or synchronizing the boot-log writer’s separate head. When a requested reset follows existing log entries, the next stage-1 boot starts writing at position zero even though older entries remain, making log ordering and the next write position inconsistent with the stored entries. Agent Prompt
Comment on lines
+525
to
+526
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Requested resets leave no audit event The new EOS_LOG_FACTORY_RESET append depends on boot-log initialization, but stage-0 enters recovery without calling eos_boot_log_init(). A persisted factory-reset flag routes the next boot through stage-0 recovery, so the append returns without writing the event even after both erase calls and the boot-control save succeed. Agent Prompt
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
|
|
||
| /* Reset auth state on entry */ | ||
| auth_state = RCVR_AUTH_NONE; | ||
| auth_fail_count = 0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -609,6 +609,58 @@ TEST(test_info_reports_a_board_without_an_entropy_source) | |
| ASSERT((out_buf[21] & RCVR_CAP_OTP) != 0); /* OTP is still there */ | ||
| } | ||
|
|
||
| /* eos_fw_factory_reset() sets EOS_FLAG_FACTORY_RESET and documents that the | ||
| * next boot "erases both slots and resets boot control to defaults". The | ||
| * flag routed the boot into recovery and nothing else: the slots were left | ||
| * as they were, and the flag was never cleared, so every boot after that | ||
| * came back to recovery. */ | ||
| TEST(test_requested_factory_reset_is_carried_out_on_entry) | ||
| { | ||
| memset(&sim_flash[SIM_SLOT_A_ADDR], 0x5A, SIM_SLOT_A_SIZE); | ||
| memset(&sim_flash[SIM_SLOT_B_ADDR], 0x5A, SIM_SLOT_B_SIZE); | ||
|
|
||
| eos_bootctl_t bctl; | ||
| eos_bootctl_init_defaults(&bctl); | ||
| bctl.active_slot = EOS_SLOT_B; | ||
| bctl.boot_count = 42; | ||
| ASSERT(eos_bootctl_request_factory_reset(&bctl) == EOS_OK); | ||
|
|
||
| if (setjmp(exit_jmp) == 0) { | ||
| eos_recovery_enter(&bctl); | ||
| } | ||
|
|
||
| for (uint32_t i = 0; i < SIM_SLOT_A_SIZE; i++) | ||
| ASSERT(sim_flash[SIM_SLOT_A_ADDR + i] == 0xFF); | ||
| for (uint32_t i = 0; i < SIM_SLOT_B_SIZE; i++) | ||
| ASSERT(sim_flash[SIM_SLOT_B_ADDR + i] == 0xFF); | ||
|
|
||
| /* The persisted control block is back to defaults, request cleared. */ | ||
| eos_bootctl_t saved; | ||
| ASSERT(eos_bootctl_load(&saved) == EOS_OK); | ||
| ASSERT((saved.flags & EOS_FLAG_FACTORY_RESET) == 0); | ||
| ASSERT(saved.active_slot == EOS_SLOT_A); | ||
| ASSERT(saved.boot_count == 0); | ||
|
Comment on lines
+637
to
+642
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new tests cover successful reset and ordinary recovery, but not the promised retry after an erase fails. The flash mock always succeeds for these slot ranges, so a later change could drop a failed request without either test catching it. Add separate Slot A and Slot B failure cases, reload the saved control block to check that Prompt To Fix With AIThis is a comment left during a code review.
Path: tests/unit/test_recovery.c
Line: 637-642
Comment:
**Failed reset retry lacks tests**
The new tests cover successful reset and ordinary recovery, but not the promised retry after an erase fails. The flash mock always succeeds for these slot ranges, so a later change could drop a failed request without either test catching it. Add separate Slot A and Slot B failure cases, reload the saved control block to check that `EOS_FLAG_FACTORY_RESET` remains set, then allow erasure and check that a later entry completes the reset.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| } | ||
|
|
||
| /* Only a factory-reset request erases anything: an ordinary entry into | ||
| * recovery leaves both slots alone. */ | ||
| TEST(test_plain_recovery_entry_erases_nothing) | ||
| { | ||
| memset(&sim_flash[SIM_SLOT_A_ADDR], 0x5A, SIM_SLOT_A_SIZE); | ||
| memset(&sim_flash[SIM_SLOT_B_ADDR], 0x5A, SIM_SLOT_B_SIZE); | ||
|
|
||
| eos_bootctl_t bctl; | ||
| eos_bootctl_init_defaults(&bctl); | ||
| ASSERT(eos_bootctl_request_recovery(&bctl) == EOS_OK); | ||
|
|
||
| if (setjmp(exit_jmp) == 0) { | ||
| eos_recovery_enter(&bctl); | ||
| } | ||
|
|
||
| ASSERT(sim_flash[SIM_SLOT_A_ADDR] == 0x5A); | ||
| ASSERT(sim_flash[SIM_SLOT_B_ADDR] == 0x5A); | ||
| } | ||
|
|
||
| int main(void) | ||
| { | ||
| printf("=== test_recovery ===\n"); | ||
|
|
@@ -621,6 +673,8 @@ int main(void) | |
| run_test_verify_rejects_oversized_image_before_reading_payload(); | ||
| run_test_info_sends_the_packed_layout_the_client_parses_and_nothing_else(); | ||
| run_test_info_reports_a_board_without_an_entropy_source(); | ||
| run_test_requested_factory_reset_is_carried_out_on_entry(); | ||
| run_test_plain_recovery_entry_erases_nothing(); | ||
| printf("%d/%d tests passed\n", tests_passed, tests_run); | ||
| return (tests_passed == tests_run) ? 0 : 1; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: embeddedos-org/eBoot
Length of output: 2859
Script executed:
Repository: embeddedos-org/eBoot
Length of output: 30364
Erase the full slot range in the STM32F4 flash hook.
stm32f4_flash_erase()ignoreslen((void)len;). It erases only the sector that containsaddr, then returnsEOS_OK. Each STM32F4 slot is 448 KB and spans several sectors, soeos_slot_erase(EOS_SLOT_A)andeos_slot_erase(EOS_SLOT_B)can return success while most of each slot remains.eos_recovery_enter()then restores the boot-control defaults and clears the factory-reset request, so the reset is not retried.Fix the hook in
boards/stm32f4/board_stm32f4.c. Erase every sector that overlaps[addr, addr + len). Return an error if any sector erase fails. Therecovery.cchange depends on this behavior, and other callers of the same hook, such as the boot log, boot control, and firmware update paths, depend on it too.