From 5b7a6d4e8175ed588b194e51ac7ad1cbae7f7043 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Aug 2026 16:09:17 -0500 Subject: [PATCH] Anchor all four wrap symbols, add 2-slot coverage, log outside the lock --- .../bluetooth_connection_rp2.cpp | 6 ++++-- .../btstack_memory_rp2.cpp | 21 ++++++++++++++----- .../config/rp2_proxy_two_slots.yaml | 16 ++++++++++++++ .../bluetooth_proxy/test_rp2_pool_wrap.py | 11 ++++++++++ .../validate.rp2040-ard.yaml | 3 ++- 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/component_tests/bluetooth_proxy/config/rp2_proxy_two_slots.yaml diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp index 14c9c70e38..4a926a5246 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp @@ -973,8 +973,10 @@ int RP2GattClient::gatt_disconnect() { } uint8_t status = ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; if (this->con_handle_ != HCI_CON_HANDLE_INVALID) { - BluetoothLock lock; - status = gap_disconnect(this->con_handle_); + { + BluetoothLock lock; + status = gap_disconnect(this->con_handle_); + } if (status != 0) { ESP_LOGW(TAG, "[%u] gap_disconnect failed, status=0x%02x", this->engine_index_, status); } diff --git a/esphome/components/bluetooth_connection/btstack_memory_rp2.cpp b/esphome/components/bluetooth_connection/btstack_memory_rp2.cpp index 4f9c73eb92..508f7aa7e8 100644 --- a/esphome/components/bluetooth_connection/btstack_memory_rp2.cpp +++ b/esphome/components/bluetooth_connection/btstack_memory_rp2.cpp @@ -60,17 +60,28 @@ struct PoolInit { } // namespace // Exact semantics of btstack_memory.c's static-pool arm: zeroed block on -// success, NULL when exhausted; free returns the block to the pool. +// success, NULL when exhausted; free returns the block to the pool. The +// prebuilt pools stay resident in .bss (~7.4 KB, kept live by +// btstack_memory_init in the archive) — dead weight here, not a leak. // NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) extern "C" gatt_client_t *__real_btstack_memory_gatt_client_get(void); +extern "C" void __real_btstack_memory_gatt_client_free(gatt_client_t *gatt_client); +extern "C" hci_connection_t *__real_btstack_memory_hci_connection_get(void); +extern "C" void __real_btstack_memory_hci_connection_free(hci_connection_t *hci_connection); namespace { // Never called: the linker silently ignores --wrap for an unreferenced -// symbol, and __real_* only resolves while --wrap is in effect — so this -// reference turns "pools compiled but flags not emitted" into a link error -// instead of a silent fallback to the prebuilt one-client pool. +// symbol, and __real_* only resolves while --wrap is in effect — so these +// references turn "pools compiled but a flag not emitted" into a link error +// instead of a silent fallback to the prebuilt one-client pool. One anchor +// per wrapped symbol, so dropping any single flag fails loudly. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -[[gnu::used]] gatt_client_t *(*const WRAP_ACTIVE_CHECK)() = &__real_btstack_memory_gatt_client_get; +[[gnu::used]] void (*const WRAP_ACTIVE_CHECKS[])() = { + reinterpret_cast(&__real_btstack_memory_gatt_client_get), + reinterpret_cast(&__real_btstack_memory_gatt_client_free), + reinterpret_cast(&__real_btstack_memory_hci_connection_get), + reinterpret_cast(&__real_btstack_memory_hci_connection_free), +}; } // namespace extern "C" { diff --git a/tests/component_tests/bluetooth_proxy/config/rp2_proxy_two_slots.yaml b/tests/component_tests/bluetooth_proxy/config/rp2_proxy_two_slots.yaml new file mode 100644 index 0000000000..c631562743 --- /dev/null +++ b/tests/component_tests/bluetooth_proxy/config/rp2_proxy_two_slots.yaml @@ -0,0 +1,16 @@ +esphome: + name: poolwrap-rp2-two + +rp2: + board: rpipicow + +wifi: + ssid: MySSID + password: password1 + +api: + +rp2_ble_tracker: + +bluetooth_proxy: + connection_slots: 2 diff --git a/tests/component_tests/bluetooth_proxy/test_rp2_pool_wrap.py b/tests/component_tests/bluetooth_proxy/test_rp2_pool_wrap.py index 694277c952..7ccf3c82a3 100644 --- a/tests/component_tests/bluetooth_proxy/test_rp2_pool_wrap.py +++ b/tests/component_tests/bluetooth_proxy/test_rp2_pool_wrap.py @@ -27,6 +27,17 @@ def test_default_slots_emit_the_pool_wrap( assert get_define_value("BLUETOOTH_PROXY_MAX_CONNECTIONS") == "3" +def test_two_slots_emit_the_pool_wrap( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + # Two slots: the wrap pools are smaller than the cap, sized from the count. + generate_main(component_config_path("rp2_proxy_two_slots.yaml")) + assert all(flag in CORE.build_flags for flag in WRAP_FLAGS) + assert get_define_value("ESPHOME_BLE_GATT_CLIENT_COUNT") == "2" + assert get_define_value("BLUETOOTH_PROXY_MAX_CONNECTIONS") == "2" + + def test_single_slot_keeps_the_prebuilt_pools( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], diff --git a/tests/components/bluetooth_connection/validate.rp2040-ard.yaml b/tests/components/bluetooth_connection/validate.rp2040-ard.yaml index 7c523b3fba..d3674b8406 100644 --- a/tests/components/bluetooth_connection/validate.rp2040-ard.yaml +++ b/tests/components/bluetooth_connection/validate.rp2040-ard.yaml @@ -6,6 +6,7 @@ packages: rp2_ble_tracker: +# Two slots: the one shape where the wrap pools are smaller than the cap. bluetooth_proxy: active: true - connection_slots: 3 + connection_slots: 2