Skip to content

Commit f577a15

Browse files
committed
app: src: modules: fota: erase slot1 trailer before download
Adds `erase_secondary_trailer()` to wipe the last 4 KiB of the MCUboot secondary slot before a FOTA download starts. This fixes a failure case where a stale swap magic left by a J-Link flash of internal flash causes `boot_set_pending()` to fail. Updates the fota unit test to mock the flash area API and adds the missing `CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE` compile definition. Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
1 parent 3fd73d0 commit f577a15

3 files changed

Lines changed: 56 additions & 0 deletions

File tree

app/src/modules/fota/fota.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <net/nrf_cloud_fota_poll.h>
1313
#include <zephyr/sys/reboot.h>
1414
#include <zephyr/dfu/mcuboot.h>
15+
#include <zephyr/storage/flash_map.h>
1516
#include <zephyr/task_wdt/task_wdt.h>
1617
#include <nrf_cloud_fota.h>
1718
#include <zephyr/smf.h>
@@ -474,11 +475,40 @@ static enum smf_state_result state_polling_for_update_run(void *obj)
474475
return SMF_EVENT_PROPAGATE;
475476
}
476477

478+
/* Erase the MCUboot trailer page (last 4 KiB of slot1 on external SPI-NOR).
479+
* stream_flash only erases written sectors, so a stale trailer survives J-Link
480+
* flashes of internal flash; boot_set_pending() then fails on the swap magic.
481+
*/
482+
static void erase_secondary_trailer(void)
483+
{
484+
const struct flash_area *fa;
485+
int err = flash_area_open(PARTITION_ID(slot1_partition), &fa);
486+
487+
if (err) {
488+
LOG_WRN("flash_area_open(slot1) failed: %d, skipping trailer erase", err);
489+
return;
490+
}
491+
492+
const size_t page_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE;
493+
const off_t trailer_off = fa->fa_size - page_size;
494+
495+
err = flash_area_erase(fa, trailer_off, page_size);
496+
if (err) {
497+
LOG_WRN("flash_area_erase(slot1 trailer) failed: %d", err);
498+
} else {
499+
LOG_DBG("Erased slot1 trailer page at offset 0x%lx", (long)trailer_off);
500+
}
501+
502+
flash_area_close(fa);
503+
}
504+
477505
static void state_downloading_update_entry(void *obj)
478506
{
479507
ARG_UNUSED(obj);
480508

481509
LOG_DBG("%s", __func__);
510+
511+
erase_secondary_trailer();
482512
}
483513

484514
static enum smf_state_result state_downloading_update_run(void *obj)

tests/module/fota/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ zephyr_include_directories(${ZEPHYR_BASE}/include/zephyr/)
2626
zephyr_include_directories(${ZEPHYR_BASE}/subsys/testsuite/include)
2727
zephyr_include_directories(${ZEPHYR_BASE}/../nrfxlib/nrf_modem/include)
2828

29+
zephyr_include_directories(${NRF_DIR}/include)
2930
zephyr_include_directories(${NRF_DIR}/include/net)
3031
zephyr_include_directories(${NRF_DIR}/subsys/net/lib/nrf_cloud/include)
3132
zephyr_include_directories(${NRF_DIR}/subsys/net/lib/nrf_cloud/common/include)
@@ -54,4 +55,5 @@ target_compile_definitions(app PRIVATE
5455
-DCONFIG_COAP_CLIENT_BLOCK_SIZE=64
5556
-DCONFIG_COAP_CLIENT_MAX_EXTRA_OPTIONS=2
5657
-DCONFIG_COAP_CLIENT_MAX_PATH_LENGTH=128
58+
-DCONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
5759
)

tests/module/fota/src/fota_module_test.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include <zephyr/zbus/zbus.h>
99
#include <zephyr/task_wdt/task_wdt.h>
1010
#include <zephyr/logging/log.h>
11+
#include <zephyr/storage/flash_map.h>
12+
#include <dfu/dfu_target.h>
13+
#include <net/nrf_cloud.h>
1114
#include <net/nrf_cloud_fota_poll.h>
1215

1316
#include "app_common.h"
@@ -22,8 +25,23 @@ FAKE_VALUE_FUNC(int, nrf_cloud_fota_poll_process_pending, struct nrf_cloud_fota_
2225
FAKE_VALUE_FUNC(int, nrf_cloud_fota_poll_process, struct nrf_cloud_fota_poll_ctx *);
2326
FAKE_VALUE_FUNC(int, nrf_cloud_fota_poll_update_apply, struct nrf_cloud_fota_poll_ctx *);
2427
FAKE_VALUE_FUNC(int, fota_download_cancel);
28+
FAKE_VALUE_FUNC(int, flash_area_open, uint8_t, const struct flash_area **);
29+
FAKE_VALUE_FUNC(int, flash_area_erase, const struct flash_area *, off_t, size_t);
30+
FAKE_VOID_FUNC(flash_area_close, const struct flash_area *);
2531
FAKE_VOID_FUNC1(callback_t, int);
2632

33+
static struct flash_area fake_slot1_area = {
34+
.fa_size = 800 * 1024,
35+
};
36+
37+
static int flash_area_open_fake_impl(uint8_t id, const struct flash_area **fa)
38+
{
39+
ARG_UNUSED(id);
40+
41+
*fa = &fake_slot1_area;
42+
return 0;
43+
}
44+
2745
ZBUS_MSG_SUBSCRIBER_DEFINE(fota_subscriber);
2846
ZBUS_CHAN_ADD_OBS(fota_chan, fota_subscriber, 0);
2947

@@ -63,6 +81,12 @@ void setUp(void)
6381
RESET_FAKE(nrf_cloud_fota_poll_process);
6482
RESET_FAKE(nrf_cloud_fota_poll_update_apply);
6583
RESET_FAKE(fota_download_cancel);
84+
RESET_FAKE(flash_area_open);
85+
RESET_FAKE(flash_area_erase);
86+
RESET_FAKE(flash_area_close);
87+
88+
flash_area_open_fake.custom_fake = flash_area_open_fake_impl;
89+
flash_area_erase_fake.return_val = 0;
6690

6791
FFF_RESET_HISTORY();
6892

0 commit comments

Comments
 (0)