diff --git a/formatter/SoftDevice_v6_formatter.uf2 b/formatter/SoftDevice_v6_formatter.uf2 index e04d140..d28d086 100644 Binary files a/formatter/SoftDevice_v6_formatter.uf2 and b/formatter/SoftDevice_v6_formatter.uf2 differ diff --git a/formatter/SoftDevice_v7_formatter.uf2 b/formatter/SoftDevice_v7_formatter.uf2 index 9b11cff..b7872d3 100644 Binary files a/formatter/SoftDevice_v7_formatter.uf2 and b/formatter/SoftDevice_v7_formatter.uf2 differ diff --git a/zephcore/adapters/ble/ZephyrBLE.cpp b/zephcore/adapters/ble/ZephyrBLE.cpp index 7dbc473..7ed103e 100644 --- a/zephcore/adapters/ble/ZephyrBLE.cpp +++ b/zephcore/adapters/ble/ZephyrBLE.cpp @@ -1271,6 +1271,10 @@ void zephcore_ble_init(const struct ble_callbacks *cbs) void zephcore_ble_start(const char *name) { if (IS_ENABLED(CONFIG_SETTINGS)) { + /* NVS self-initializes the storage_partition on first mount: a + * region carved from the old app slot reads as "all sectors closed", + * which nvs_startup() reformats (erase-all) before settings load. + * No app-side seeding needed. */ settings_load(); #if IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) gatt_layout_check_after_settings_load(); diff --git a/zephcore/app/RepeaterDataStore.cpp b/zephcore/app/RepeaterDataStore.cpp index 8c7cb59..a617303 100644 --- a/zephcore/app/RepeaterDataStore.cpp +++ b/zephcore/app/RepeaterDataStore.cpp @@ -5,6 +5,7 @@ #include "RepeaterDataStore.h" #include +#include #include #include #include @@ -354,6 +355,17 @@ bool RepeaterDataStore::formatFileSystem() { } fs_closedir(&dir); +#if FIXED_PARTITION_EXISTS(storage_partition) + /* Erase the NVS bonds partition too — a factory reset should clear BLE + * bonds, not just repeater files. Caller reboots so NVS re-inits clean. */ + const struct flash_area *fap; + if (flash_area_open(PARTITION_ID(storage_partition), &fap) == 0) { + LOG_INF("Formatting NVS storage (%u bytes)", (unsigned)fap->fa_size); + flash_area_flatten(fap, 0, fap->fa_size); + flash_area_close(fap); + } +#endif + LOG_INF("Repeater data erased"); return true; } diff --git a/zephcore/boards/common/nrf52_common.conf b/zephcore/boards/common/nrf52_common.conf index 1d59be0..4a4ea51 100644 --- a/zephcore/boards/common/nrf52_common.conf +++ b/zephcore/boards/common/nrf52_common.conf @@ -28,6 +28,17 @@ CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 # Coded PHY — enable in nRF LL controller (host auto-request in zephcore_common.conf) CONFIG_BT_CTLR_PHY_CODED=y +# ========== BLE bonds: dedicated NVS partition (not /lfs) ========== +# Bonds live in their own `storage_partition` NVS region (see +# nrf52_partitions_sdv{6,7}.dtsi), isolated from the /lfs LittleFS volume +# that holds identity/prefs/contacts. This overrides the file-backed +# default in zephcore_common.conf (loaded earlier): a busy bond store can +# no longer corrupt /lfs, and the per-bond writes never touch user data. +# CONFIG_SETTINGS_FILE_PATH from the common file is inert once FILE=n. +CONFIG_SETTINGS_FILE=n +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y + # ========== Flash: nRF52-specific ========== # Partial erase — splits 4KB page erase into small sub-erases that fit # between BLE connection events. Without this, the flash driver waits for diff --git a/zephcore/boards/common/nrf52_partitions_sdv6.dtsi b/zephcore/boards/common/nrf52_partitions_sdv6.dtsi index c94d94d..af70bb3 100644 --- a/zephcore/boards/common/nrf52_partitions_sdv6.dtsi +++ b/zephcore/boards/common/nrf52_partitions_sdv6.dtsi @@ -17,7 +17,8 @@ * * Memory map (1MB internal flash): * 0x00000 - 0x26000 (152KB) SoftDevice s140 v6 (reserved) - * 0x26000 - 0xD4000 (696KB) Application + * 0x26000 - 0xD0000 (680KB) Application + * 0xD0000 - 0xD4000 (16KB) NVS settings/bonds (storage_partition) * 0xD4000 - 0xF4000 (128KB) LittleFS (/lfs automount) * 0xF4000 - 0x100000 (48KB) UF2 bootloader (reserved) */ @@ -31,6 +32,9 @@ * script, causing "region RAM overflowed" on the first build. * Set it here so all boards using this DTSI get it for free. */ zephyr,sram = &sram0; + /* BLE bonds live in the NVS storage_partition, not the /lfs + * settings file — see boards/common/nrf52_common.conf. */ + zephyr,settings-partition = &storage_partition; }; }; @@ -47,10 +51,22 @@ reg = <0x00000000 0x00026000>; }; - /* Application — 696KB */ + /* Application — 680KB (16KB trimmed from the top for the NVS + * settings partition below; lfs is left byte-identical). */ code_partition: partition@26000 { label = "Application"; - reg = <0x00026000 0x000AE000>; + reg = <0x00026000 0x000AA000>; + }; + + /* BLE bond store — 16KB NVS (Zephyr settings backend). Carved + * from the END of the app slot so lfs_partition keeps the same + * offset+size: identity/prefs/contacts in /lfs survive this change. + * Isolating bonds here stops settings-write churn from corrupting + * the /lfs LittleFS volume. Label "storage" so the existing + * factory-reset path (formatFileSystem) erases it too. */ + storage_partition: partition@d0000 { + label = "storage"; + reg = <0x000D0000 0x00004000>; }; /* LittleFS — 128KB: identity, prefs, contacts, channels, blobs */ diff --git a/zephcore/boards/common/nrf52_partitions_sdv7.dtsi b/zephcore/boards/common/nrf52_partitions_sdv7.dtsi index ce4f2b2..df69396 100644 --- a/zephcore/boards/common/nrf52_partitions_sdv7.dtsi +++ b/zephcore/boards/common/nrf52_partitions_sdv7.dtsi @@ -36,7 +36,7 @@ * │ │ * │ CHOSEN NODE: │ * │ zephyr,code-partition = &code_partition; │ - * │ (Do NOT set zephyr,settings-partition — uses file settings) │ + * │ zephyr,settings-partition = &storage_partition; (NVS bonds) │ * │ │ * │ QSPI ADD-ON (if board has external QSPI flash): │ * │ In board.overlay: │ @@ -46,7 +46,8 @@ * * Memory map (1MB internal flash): * 0x00000 - 0x27000 (156KB) SoftDevice s140 v7 (reserved) - * 0x27000 - 0xD4000 (692KB) Application + * 0x27000 - 0xD0000 (676KB) Application + * 0xD0000 - 0xD4000 (16KB) NVS settings/bonds (storage_partition) * 0xD4000 - 0xF4000 (128KB) LittleFS (/lfs automount) * 0xF4000 - 0x100000 (48KB) UF2 bootloader (reserved) */ @@ -60,6 +61,9 @@ * script, causing "region RAM overflowed" on the first build. * Set it here so all boards using this DTSI get it for free. */ zephyr,sram = &sram0; + /* BLE bonds live in the NVS storage_partition, not the /lfs + * settings file — see boards/common/nrf52_common.conf. */ + zephyr,settings-partition = &storage_partition; }; }; @@ -76,10 +80,22 @@ reg = <0x00000000 0x00027000>; }; - /* Application — 692KB */ + /* Application — 676KB (16KB trimmed from the top for the NVS + * settings partition below; lfs is left byte-identical). */ code_partition: partition@27000 { label = "Application"; - reg = <0x00027000 0x000AD000>; + reg = <0x00027000 0x000A9000>; + }; + + /* BLE bond store — 16KB NVS (Zephyr settings backend). Carved + * from the END of the app slot so lfs_partition keeps the same + * offset+size: identity/prefs/contacts in /lfs survive this change. + * Isolating bonds here stops settings-write churn from corrupting + * the /lfs LittleFS volume. Label "storage" so the existing + * factory-reset path (formatFileSystem) erases it too. */ + storage_partition: partition@d0000 { + label = "storage"; + reg = <0x000D0000 0x00004000>; }; /* LittleFS — 128KB: identity, prefs, contacts, channels, blobs */ diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index 822b912..33b4c4d 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -914,7 +914,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) { bool s = _callbacks->formatFileSystem(); - snprintf(reply, CLI_REPLY_SIZE, "File system erase: %s", s ? "OK" : "Err"); + if (s) { + /* formatFileSystem() flattens the mounted NVS bonds partition, + * leaving stale in-RAM bond state. Reboot (deferred so this + * reply transmits first) so NVS + the BT stack re-init cleanly. */ + snprintf(reply, CLI_REPLY_SIZE, "File system erase: OK - rebooting"); + scheduleReboot(REBOOT_NORMAL); + } else { + snprintf(reply, CLI_REPLY_SIZE, "File system erase: Err"); + } } else if (memcmp(command, "ver", 3) == 0) { snprintf(reply, CLI_REPLY_SIZE, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) {