| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- """
- Bluefruit BLE Patch Script
- Patches Bluefruit library to fix semaphore leak bug that causes device lockup
- when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout).
- Patches applied:
- 1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size
- 2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect
- Bug description:
- - When a BLE central disconnects unexpectedly (reason=8 supervision timeout),
- the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire
- - This leaves the _hvn_sem counting semaphore in a decremented state
- - Since BLEConnection objects are reused (destructor never called), the
- semaphore count is never restored
- - Eventually all semaphore counts are exhausted and notify() blocks/fails
- """
- from pathlib import Path
- Import("env") # pylint: disable=undefined-variable
- def _patch_ble_connection_header(source: Path) -> bool:
- """
- Add _hvn_qsize member variable to BLEConnection class.
-
- This is needed to restore the semaphore to its correct count on disconnect.
-
- Returns True if patch was applied or already applied, False on error.
- """
- try:
- content = source.read_text()
-
- # Check if already patched
- if "_hvn_qsize" in content:
- return True # Already patched
-
- # Find the location to insert - after _phy declaration
- original_pattern = ''' uint8_t _phy;
- uint8_t _role;'''
-
- patched_pattern = ''' uint8_t _phy;
- uint8_t _hvn_qsize;
- uint8_t _role;'''
-
- if original_pattern not in content:
- print("Bluefruit patch: WARNING - BLEConnection.h pattern not found")
- return False
-
- content = content.replace(original_pattern, patched_pattern)
- source.write_text(content)
-
- # Verify
- if "_hvn_qsize" not in source.read_text():
- return False
-
- return True
- except Exception as e:
- print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}")
- return False
- def _patch_ble_connection_source(source: Path) -> bool:
- """
- Patch BLEConnection.cpp to:
- 1. Store hvn_qsize in constructor
- 2. Restore _hvn_sem semaphore to full count on disconnect
-
- Returns True if patch was applied or already applied, False on error.
- """
- try:
- content = source.read_text()
-
- # Check if already patched (look for the restore loop)
- if "uxSemaphoreGetCount(_hvn_sem)" in content:
- return True # Already patched
-
- # Patch 1: Store queue size in constructor
- constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);'''
-
- constructor_patched = ''' _hvn_qsize = hvn_qsize;
- _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);'''
-
- if constructor_original not in content:
- print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found")
- return False
-
- content = content.replace(constructor_original, constructor_patched)
-
- # Patch 2: Restore semaphore on disconnect
- disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED:
- // mark as disconnected
- _connected = false;
- break;'''
-
- disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED:
- // Restore notification semaphore to full count
- // This fixes lockup when disconnect occurs with notifications in flight
- while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) {
- xSemaphoreGive(_hvn_sem);
- }
- // Release indication semaphore if waiting
- if (_hvc_sem) {
- _hvc_received = false;
- xSemaphoreGive(_hvc_sem);
- }
- // mark as disconnected
- _connected = false;
- break;'''
-
- if disconnect_original not in content:
- print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found")
- return False
-
- content = content.replace(disconnect_original, disconnect_patched)
- source.write_text(content)
-
- # Verify
- verify_content = source.read_text()
- if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content:
- return False
- if "_hvn_qsize = hvn_qsize" not in verify_content:
- return False
-
- return True
- except Exception as e:
- print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}")
- return False
- def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument
- framework_path = env.get("PLATFORMFW_DIR")
- if not framework_path:
- framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52")
- if not framework_path:
- print("Bluefruit patch: ERROR - framework directory not found")
- env.Exit(1)
- return
- framework_dir = Path(framework_path)
- bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src"
- patch_failed = False
-
- # Patch BLEConnection.h
- conn_header = bluefruit_lib / "BLEConnection.h"
- if conn_header.exists():
- before = conn_header.read_text()
- success = _patch_ble_connection_header(conn_header)
- after = conn_header.read_text()
-
- if success:
- if before != after:
- print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)")
- else:
- print("Bluefruit patch: OK - BLEConnection.h already patched")
- else:
- print("Bluefruit patch: FAILED - BLEConnection.h")
- patch_failed = True
- else:
- print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}")
- patch_failed = True
-
- # Patch BLEConnection.cpp
- conn_source = bluefruit_lib / "BLEConnection.cpp"
- if conn_source.exists():
- before = conn_source.read_text()
- success = _patch_ble_connection_source(conn_source)
- after = conn_source.read_text()
-
- if success:
- if before != after:
- print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)")
- else:
- print("Bluefruit patch: OK - BLEConnection.cpp already patched")
- else:
- print("Bluefruit patch: FAILED - BLEConnection.cpp")
- patch_failed = True
- else:
- print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}")
- patch_failed = True
-
- if patch_failed:
- print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.")
- env.Exit(1)
- # Register the patch to run before build
- bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...")
- env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action)
- # Also run immediately to patch before any compilation
- _apply_bluefruit_patches(None, None, env)
|