diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index cfec61a28fe..69d46bf5f27 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -443,10 +443,8 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE") -# First release of each ESP-IDF series with espressif/esp-idf@82e71c1767, which -# stops Bluedroid from starting a queued connection on a released link block. -# 5.4.5, 5.5.6 and 6.1.1 are not tagged yet; the fix is on their release branches. -# Series older than 5.2 never got the fix, 6.2 and later always have it. +# First release per series with espressif/esp-idf@82e71c1767 (see bluedroid_stubs.cpp). +# 5.4.5, 5.5.6 and 6.1.1 are not tagged yet but the fix is on their branches. DIRECT_CONN_FIX_VERSIONS = { (5, 2): cv.Version(5, 2, 8), (5, 3): cv.Version(5, 3, 6), @@ -483,8 +481,7 @@ async def _add_ble_features() -> None: cg.add_define("USE_ESP32_BLE_DEVICE") cg.add_define("USE_ESP32_BLE_UUID") if cg.get_slot_count(CLIENT_COUNT_DEFINE) and _needs_direct_conn_guard(): - # See bluedroid_stubs.cpp; --undefined keeps the wrapper because libsrc.a - # is scanned before the IDF libraries. + # --undefined keeps the wrapper, libsrc.a is scanned before the IDF libraries cg.add_define("USE_ESP32_BLE_TRACKER_DIRECT_CONN_GUARD") cg.add_build_flag("-Wl,--wrap=l2cble_init_direct_conn") cg.add_build_flag("-Wl,--undefined=__wrap_l2cble_init_direct_conn") diff --git a/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp b/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp index f92c3af5600..a013942aa69 100644 --- a/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp +++ b/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp @@ -1,22 +1,9 @@ /* - * Linker wrap guard for Bluedroid's queued BLE connection requests. - * - * Bluedroid starts one outgoing BLE connection at a time and parks the rest - * as raw link control block pointers. A parked request is not dropped when - * its block is released, so btm_send_pending_direct_conn() can later pass a - * released block to l2cble_init_direct_conn(). That arms the link timer with - * a null parameter, and l2c_link_timeout() crashes when it expires. - * - * The same commit also releases a live block that l2cble_init_direct_conn() - * rejects on its unknown device path, which every other failure path already - * does; the wrapper releases it when the block is still in use afterwards. - * - * The wrap only covers calls from other files, which is where the stale - * pointer comes from; callers inside l2c_ble.c pass a live block. - * - * Fixed upstream in espressif/esp-idf commit 82e71c1767. Codegen only enables - * this guard for ESP-IDF releases without that commit; remove it once the - * minimum ESP-IDF includes it. + * Bluedroid queues outgoing BLE connections as raw link block pointers and does + * not drop them when the block is released, so btm_send_pending_direct_conn() + * can start a connect on a released block and l2c_link_timeout() later crashes + * on its null timer parameter. Mirrors espressif/esp-idf@82e71c1767; codegen + * only enables it for releases without that commit. */ #include "esphome/core/defines.h" @@ -27,8 +14,7 @@ namespace esphome::esp32_ble_tracker {} -// tL2C_LCB is private to Bluedroid, so its layout was checked by hand from ESP-IDF 5.0 to 6.1: -// in_use is the first member and BOOLEAN is bool. +// in_use is the first member of the private tL2C_LCB (checked ESP-IDF 5.0 to 6.1) static_assert(ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 2, 0), "ESP-IDF 6.2 and later have the fix, this guard should not be enabled (esphome/esphome#19373)"); @@ -43,6 +29,7 @@ bool __wrap_l2cble_init_direct_conn(void *p_lcb) { return false; } const bool started = __real_l2cble_init_direct_conn(p_lcb); + // Every failure path releases the block except unknown device, also fixed upstream if (!started && *static_cast(p_lcb)) { l2cu_release_lcb(p_lcb); } diff --git a/tests/component_tests/esp32_ble_tracker/test_direct_conn_guard.py b/tests/component_tests/esp32_ble_tracker/test_direct_conn_guard.py index 3e1292fc887..a58d2302fbf 100644 --- a/tests/component_tests/esp32_ble_tracker/test_direct_conn_guard.py +++ b/tests/component_tests/esp32_ble_tracker/test_direct_conn_guard.py @@ -1,9 +1,4 @@ -"""Tests for the Bluedroid queued connection guard. - -Builds that open BLE connections on an ESP-IDF release without the upstream -fix wrap l2cble_init_direct_conn; scan-only builds and fixed releases emit -nothing. -""" +"""The Bluedroid queued connection guard is emitted only for client builds on unfixed ESP-IDF.""" from __future__ import annotations @@ -16,8 +11,7 @@ from esphome import config_validation as cv from esphome.components import esp32_ble_tracker from esphome.core import CORE -# Spelled out rather than derived from the component, so a typo in the -# component's flags fails here instead of mirroring into the test. +# Spelled out so a typo in the component's flags fails here _GUARD_FLAGS = { "-Wl,--wrap=l2cble_init_direct_conn", "-Wl,--undefined=__wrap_l2cble_init_direct_conn", @@ -44,7 +38,6 @@ def test_guard_only_in_client_builds_on_unfixed_idf( idf: str, expected: bool, ) -> None: - # Pinned so the test does not follow the default framework version. _pin_idf(monkeypatch, idf) generate_main(component_config_path(config_file)) assert (CORE.build_flags >= _GUARD_FLAGS) is expected