Skip to content

BLE lifecycle memory: true BLE-off on nRF52, NimBLE host pools to PSRAM on S3 - #10902

Closed
thebentern wants to merge 3 commits into
developfrom
feat/ble-lifecycle-memory
Closed

BLE lifecycle memory: true BLE-off on nRF52, NimBLE host pools to PSRAM on S3#10902
thebentern wants to merge 3 commits into
developfrom
feat/ble-lifecycle-memory

Conversation

@thebentern

Copy link
Copy Markdown
Contributor

Draft: needs on-hardware validation before review (checklist below). Part of the heap-recovery series (#10898, #10901, #10900, #10899).

nRF52: being "off" should be free

When config.bluetooth.enabled == false, the firmware previously fully initialized SoftDevice + Bluefruit and then muted advertising — the #4055-era workaround ("device hangs several days after boot", root cause never found). Being off cost ~3.5 KB of Bluefruit heap plus ~6 KB of BLE/SOC FreeRTOS task stacks, out of a ~115 KB arena.

This PR leaves the SoftDevice entirely off in that case:

  • setBluetoothEnable() short-circuits and clears useSoftDevice, so checkSDEvents() stops polling.
  • The deep-sleep GPREGRET writes (DFU_MAGIC_SKIP) now mirror lfs_assert()'s existing pattern: try sd_power_gpregret_*, fall back to a direct NRF_POWER->GPREGRET write when the SD isn't enabled.
  • NRF52Bluetooth::startDisabled() is removed. clearBonds() keeps its lazy bring-up for explicit bond maintenance, and re-enabling bluetooth applies via reboot, so no runtime re-init state machine is needed.

Why this is safe: with the SD disabled, the Adafruit BSP flash driver checks sd_softdevice_is_enabled() per operation and takes its synchronous path — the exact path every boot already uses for all filesystem work before Bluefruit.begin() runs. HardwareRNG uses the CC310, not the SD. sd_* calls with the SD disabled return an error code rather than faulting (the lfs_assert fallback has relied on this for years). The failure mode this deliberately revisits is the un-root-caused #4055 hang — hence draft + soak test below, and the change is a one-branch revert if the ghost reappears.

(Note: the nrf54l15 platform carries its own copy of the startDisabled() workaround — left untouched here, flagged as follow-up.)

ESP32-S3 + PSRAM: NimBLE host pools out of internal DRAM

New esp32s3_psram_base (variants/esp32s3/esp32s3.ini) adds CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y; all PSRAM S3 envs re-parent to it. NimBLE host allocations (MSYS mbufs, GATT/CCCD tables, bond storage) move to PSRAM, freeing ~5–8 KB of internal DRAM where WiFi+BLE coexistence hurts most. Controller memory (CONFIG_BT_CTRL_*) intentionally stays internal. Bond record formats are unchanged, so purgeIncompatibleBleBonds() (#10741) does not trigger.

Non-PSRAM envs are untouched — verified by statically resolving the merged custom_sdkconfig per env (t-deck/station-g2/thinknode-g3 gain the flag; heltec-v3 does not).

Verification so far

  • Native suite: 535/535 passed; rak4631 builds green; trunk clean.
  • CI matrix will exercise all S3 envs (the sdkconfig change triggers a framework rebuild per env).

Hardware checklist before undrafting

  • nRF52 BT-off: boot, config-save loops, reboot cycles, deep-sleep enter/wake, DFU entry (GPREGRET fallback)
  • nRF52 BT-off multi-day soak (the Workaround to disable bluetooth on NRF52 #4055 ghost)
  • nRF52 BT-on regression: advertise, pair (FIXED_PIN + RANDOM_PIN), reconnect, clearBonds
  • S3+PSRAM (t-deck or station-g2): pair/bond/reconnect/stale-bond matrix, confirm ESP.getFreeHeap() delta and no purgeIncompatibleBleBonds on same-version reboot

…AM on S3

nRF52: when config.bluetooth.enabled is false, leave the SoftDevice
entirely off instead of fully initializing SoftDevice + Bluefruit and
then muting advertising (the #4055-era workaround). Being "off" was
costing ~3.5 KB of Bluefruit heap plus ~6 KB of BLE/SOC task stacks on
a ~115 KB arena. With the SD disabled the BSP flash driver stays on its
synchronous path (the same path every boot already uses before
Bluefruit.begin()), checkSDEvents() keys off useSoftDevice, and the
deep-sleep GPREGRET writes now mirror lfs_assert()'s direct-register
fallback. clearBonds() still lazily brings the stack up for explicit
bond maintenance; re-enabling bluetooth applies via reboot, so no
runtime re-init path is needed. NRF52Bluetooth::startDisabled() is
removed.

ESP32-S3 with PSRAM: new esp32s3_psram_base sets
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y so NimBLE host pools (MSYS
mbufs, GATT/CCCD tables, bond storage) allocate from PSRAM, freeing
~5-8 KB of internal DRAM on the WiFi+BLE coexistence path. Controller
memory (CONFIG_BT_CTRL_*) stays internal. All PSRAM S3 envs re-parent
to the new base; non-PSRAM envs are unchanged (verified by resolving
the merged custom_sdkconfig per env).

Native suite 535/535; rak4631 builds green. Hardware validation
(pairing matrix, BT-off soak for the #4055 ghost) tracked in the PR.
@thebentern thebentern added the enhancement New feature or request label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a01a3a59-9119-42b8-964c-01f6a0742004

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (26)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-05. Updated for b1cbc9b.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Firmware Size Report

22 targets | vs develop: 21 increased, 1 decreased, net +387,732 (+378.6 KB)

Target Size vs develop
picow 1,271,976 📈 +27,096 (+26.5 KB)
pico2w 1,246,660 📈 +26,144 (+25.5 KB)
rak11310 831,256 📈 +25,832 (+25.2 KB)
pico 808,504 📈 +25,816 (+25.2 KB)
seeed_xiao_rp2040 806,704 📈 +25,800 (+25.2 KB)
Show 17 more target(s)
Target Size vs develop
pico2 794,776 📈 +25,000 (+24.4 KB)
seeed_xiao_rp2350 792,920 📈 +24,984 (+24.4 KB)
t-deck-tft 3,832,064 📈 +21,392 (+20.9 KB)
heltec-vision-master-e213-inkhud 2,247,392 📈 +20,032 (+19.6 KB)
elecrow-adv-35-tft 3,435,520 📈 +18,896 (+18.5 KB)
seeed-xiao-s3 2,292,416 📈 +16,272 (+15.9 KB)
heltec-ht62-esp32c3-sx1262 2,150,576 📈 +15,584 (+15.2 KB)
heltec-v3 2,279,632 📈 +15,552 (+15.2 KB)
rak3312 2,287,600 📈 +15,360 (+15.0 KB)
rak11200 1,875,728 📈 +15,120 (+14.8 KB)
station-g2 2,281,072 📈 +14,720 (+14.4 KB)
station-g3 2,281,072 📈 +14,720 (+14.4 KB)
t-eth-elite 2,506,064 📈 +14,576 (+14.2 KB)
tlora-c6 2,382,272 📈 +13,968 (+13.6 KB)
heltec-v4 2,291,152 📈 +13,856 (+13.5 KB)
rak3172 182,492 📉 -3,804 (-3.7 KB)
wio-e5 239,444 📈 +816

Updated for 8a8d4f4

@thebentern

Copy link
Copy Markdown
Contributor Author

Hardware validation on rak4631 (integration build with the full heap PR stack, 120-node NodeDB):

  • ✅ BT-off boot logs Bluetooth disabled by config: leaving SoftDevice off; no Bluefruit init; node fully operational (LoRa TX/RX, PhoneAPI over serial, TMM, GPS probe).
  • +9,560 B free heap with BT off: 53,564 → 63,124 (reproducible across 2 boots) — top of the predicted 7–9 KB band.
  • Filesystem writes with the SoftDevice never enabled: set_owner (devicestate) + config write both persisted and survived reboot — the synchronous flash path works as designed.
  • ✅ Multiple clean reboot cycles in BT-off mode.
  • shutdown → deep sleep reaches system-off with the GPREGRET direct-register fallback (SD disabled, sd_power_gpregret_* unavailable); device woke by USB power-cycle and booted straight into the app (DFU correctly skipped).
  • ✅ Re-enabling bluetooth via config + reboot brings SoftDevice + Bluefruit back (heap returns to exactly the BT-on figure).

Updated checklist state — still open: multi-day BT-off soak (the #4055 ghost; the test rig can run it), phone pairing matrix (needs a phone), and the ESP32-S3 PSRAM half (needs an S3+PSRAM board).

The first sweep only caught envs whose platformio.ini declares
BOARD_HAS_PSRAM; another 23 variant dirs (42 envs) get the flag from
their board JSON instead (EoRa-S3/Hub, t-beam-1w, t-watch-s3,
tbeam-s3-core, unphone, vision-masters, mesh-tab, sensecap-indicator,
t-deck-pro, wismesh-tap-v2, and friends). Re-parent them all to
esp32s3_psram_base; inheritance verified by statically resolving the
merged custom_sdkconfig per env (all 42 gain
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y; non-PSRAM controls
heltec-v3 / heltec-wsl-v3 stay clean).

Hardware-validated on a CDEBYTE EoRa-S3 (embedded 2 MB quad PSRAM):
NimBLE initializes and advertises normally with host pools in PSRAM,
and free internal DRAM in the client-attached steady state rises from
161,608 B to 179,296/178,828 B across boots - about 17.5 KB reclaimed,
comfortably above the 5-8 KB estimate.
@thebentern

Copy link
Copy Markdown
Contributor Author

ESP32-S3 PSRAM half validated on hardware (CDEBYTE EoRa-S3, ESP32-S3 with embedded 2 MB quad PSRAM AP_3v3):

Steady state (serial client, uptime ~80 s) Free internal DRAM
esp32s3_base (NimBLE pools internal) 161,608 B
esp32s3_psram_base (this PR) 179,296 / 178,828 B (two boots)
  • ≈17.5 KB of internal DRAM reclaimed — above the 5–8 KB estimate (host pools, mbufs, GATT/CCCD and bond storage all move; only the host task stack stays internal).
  • ✅ NimBLE initializes and advertises normally with CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y: host task starts, MTU 517 set, advertising up, zero allocation errors across multiple boots.
  • Boot log confirms PSRAM residency for the rest of the LARGE-class profile too ([TM] NodeInfo cache … PSRAM flat array, warm store via ps_calloc).

Coverage fix pushed (3d0b7cc): the original sweep only re-parented envs whose platformio.ini declares BOARD_HAS_PSRAM; another 23 variant dirs (42 envs) get it from their board JSON (EoRa-S3/Hub, t-beam-1w, t-watch-s3, tbeam-s3-core, unphone, vision-masters, mesh-tab, sensecap-indicator, t-deck-pro, wismesh-tap-v2, …). All re-parented; merged custom_sdkconfig statically verified per env, with heltec-v3/wsl-v3 (no PSRAM) confirmed unaffected.

Still open before undraft: nRF52 BT-off multi-day soak, and a phone pairing/bond matrix on both platforms.

@github-actions github-actions Bot added the Stale Issues that will be closed if not triaged. label Aug 21, 2026
@github-actions github-actions Bot closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-review Needs human review Stale Issues that will be closed if not triaged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant