|
|
@@ -0,0 +1,198 @@
|
|
|
+"""
|
|
|
+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)
|