Bluetooth and Wi-Fi improvements (#622)
- Enable BT by default in DTS, and create separate radio on/of functionality (like recent Wi-Fi changes) - Bluetooth event handling now uses queued subscriptions, improving event delivery and application responsiveness. - Improved synchronization of Bluetooth advertising, connections, HID, MIDI, and SPP operations. - Enhanced cleanup during Bluetooth shutdown and subscription removal. - Clarified Bluetooth start and stop behavior. - Improved wifi driver subscription stability to prevent endless loops
This commit is contained in:
committed by
GitHub
parent
ab75d2022d
commit
0ee2415f3b
@@ -30,17 +30,16 @@
|
||||
#include <bluetooth/esp32_ble_midi.h>
|
||||
#include <bluetooth/esp32_ble_hid.h>
|
||||
|
||||
#define BLE_MAX_CALLBACKS 8
|
||||
|
||||
struct BleCallbackEntry {
|
||||
BtEventCallback fn;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
struct BleCtx {
|
||||
// Mutexes
|
||||
SemaphoreHandle_t radio_mutex; // guards radio state transitions
|
||||
SemaphoreHandle_t cb_mutex; // guards callbacks array
|
||||
// Guards GAP/GATT advertising & profile-switch calls (on_sync()'s advertising decision vs.
|
||||
// hid/spp/midi's _start()/_stop()). Deliberately separate from radio_mutex: radio_mutex can
|
||||
// be held for the duration of nimble_port_stop(), which blocks waiting for the NimBLE host
|
||||
// task to exit its run loop - taking that same mutex from code that can run ON the host task
|
||||
// (on_sync()) would deadlock disable against it. Nothing held under gap_mutex ever waits on
|
||||
// host-task progress, so it's safe to take from the host task itself.
|
||||
SemaphoreHandle_t gap_mutex;
|
||||
|
||||
// Radio / scan state (atomic — read from multiple tasks)
|
||||
std::atomic<BtRadioState> radio_state;
|
||||
@@ -48,9 +47,9 @@ struct BleCtx {
|
||||
// Set by Tactility HID host to prevent simultaneous central connection during name resolution
|
||||
std::atomic<bool> hid_host_active;
|
||||
|
||||
// Event callbacks (guarded by cb_mutex)
|
||||
BleCallbackEntry callbacks[BLE_MAX_CALLBACKS];
|
||||
size_t callback_count;
|
||||
// Event subscriptions (guarded by subscriptionsMutex)
|
||||
Mutex subscriptionsMutex;
|
||||
BtEventSubscription* subscriptions;
|
||||
|
||||
// Connection handles + active flags (atomic — accessed from multiple tasks)
|
||||
std::atomic<uint16_t> spp_conn_handle;
|
||||
@@ -98,7 +97,7 @@ struct BleCtx {
|
||||
ble_addr_t scan_addrs[64];
|
||||
size_t scan_count;
|
||||
|
||||
// Device reference (passed to BtEventCallback)
|
||||
// Device reference (passed to subscribers via BtEvent delivery)
|
||||
struct Device* device;
|
||||
|
||||
// Child devices (created by esp32_ble_start_device, destroyed by stop_device)
|
||||
|
||||
@@ -96,34 +96,47 @@ void ble_set_scan_active(struct Device* device, bool v) {
|
||||
void ble_publish_event(struct Device* device, struct BtEvent event) {
|
||||
BleCtx* ctx = ble_get_ctx(device);
|
||||
if (!ctx) return;
|
||||
// Copy under mutex so callbacks can safely call add/remove_event_callback
|
||||
BleCallbackEntry local[BLE_MAX_CALLBACKS];
|
||||
size_t count;
|
||||
xSemaphoreTake(ctx->cb_mutex, portMAX_DELAY);
|
||||
count = ctx->callback_count;
|
||||
memcpy(local, ctx->callbacks, count * sizeof(BleCallbackEntry));
|
||||
xSemaphoreGive(ctx->cb_mutex);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
local[i].fn(ctx->device, local[i].ctx, event);
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
for (BtEventSubscription* sub = ctx->subscriptions; sub != nullptr; sub = sub->internal.next) {
|
||||
mutex_lock(&sub->internal.ring_mutex);
|
||||
if (sub->internal.count < BT_EVENT_QUEUE_CAPACITY) {
|
||||
uint8_t tail = (sub->internal.head + sub->internal.count) % BT_EVENT_QUEUE_CAPACITY;
|
||||
sub->internal.queue[tail] = event;
|
||||
sub->internal.count++;
|
||||
}
|
||||
mutex_unlock(&sub->internal.ring_mutex);
|
||||
task_event_group_signal(sub->internal.event_group, sub->bit);
|
||||
}
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
}
|
||||
|
||||
// ---- Advertising restart helper ----
|
||||
//TODO: Fix Restart name-only advertising after a rename.
|
||||
|
||||
// Delay before on_sync() falls back to name-only advertising - see its call site.
|
||||
constexpr uint64_t ADV_ON_SYNC_DELAY_US = 100'000;
|
||||
|
||||
static void adv_restart_callback(void* arg) {
|
||||
struct Device* device = (struct Device*)arg;
|
||||
BleCtx* ctx = ble_get_ctx(device);
|
||||
if (ctx->radio_state.load() != BT_RADIO_STATE_ON) return;
|
||||
|
||||
// gap_mutex (not radio_mutex - this can run on the host task and must never wait on a mutex
|
||||
// disable can hold across nimble_port_stop()) serializes this against hid/spp/midi_device_
|
||||
// start()/_stop(), which touch the same GAP/GATT state from another task.
|
||||
xSemaphoreTake(ctx->gap_mutex, portMAX_DELAY);
|
||||
uint16_t hid_conn = ble_hid_get_conn_handle(ctx->hid_device_child);
|
||||
|
||||
if (ctx->midi_active.load() && ctx->midi_conn_handle.load() == BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_start_advertising(device, &MIDI_SVC_UUID);
|
||||
} else if (ctx->spp_active.load() && ctx->spp_conn_handle.load() == BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_start_advertising(device, &NUS_SVC_UUID);
|
||||
} else if (ctx->hid_active.load() && hid_conn == BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_start_advertising_hid(device, hid_appearance);
|
||||
} else if (!ctx->hid_active.load() && !ctx->spp_active.load() && !ctx->midi_active.load()) {
|
||||
// Nothing profile-specific active - name-only keeps the radio discoverable/connectable.
|
||||
ble_start_advertising(device, nullptr);
|
||||
}
|
||||
xSemaphoreGive(ctx->gap_mutex);
|
||||
}
|
||||
|
||||
void ble_schedule_adv_restart(struct Device* device, uint64_t delay_us) {
|
||||
@@ -441,17 +454,13 @@ static void on_sync() {
|
||||
e.radio_state = BT_RADIO_STATE_ON;
|
||||
ble_publish_event(ctx->device, e);
|
||||
|
||||
// The Tactility bridge handles auto-start (SPP/MIDI/HID) in response to
|
||||
// BT_EVENT_RADIO_STATE_CHANGED(ON), dispatched to the main task above.
|
||||
// On a multi-core ESP32 the main task can preempt the NimBLE host task
|
||||
// between ble_publish_event() and here, call hid/spp/midi_device_start(),
|
||||
// set hid/spp/midi_active, and already start profile-specific advertising.
|
||||
// Only start name-only advertising if no profile has been activated yet;
|
||||
// otherwise we would overwrite the correct profile advertising with name-only,
|
||||
// causing Windows to connect without seeing the HID/SPP/MIDI service UUID.
|
||||
if (!ctx->hid_active.load() && !ctx->spp_active.load() && !ctx->midi_active.load()) {
|
||||
ble_start_advertising(ctx->device, nullptr);
|
||||
}
|
||||
// Deferred, not synchronous: an app reacting to the event just published above (e.g.
|
||||
// starting HID) races this to set hid/spp/midi_active, and immediately starting name-only
|
||||
// advertising only to tear it down again a few ms later for a profile switch was observed to
|
||||
// corrupt the NimBLE host's HCI event pool (crash in os_memblock_get, called from
|
||||
// lld_adv_end_ind_handler_hack). The delay gives that app time to react first;
|
||||
// adv_restart_callback() re-checks the active flags, so this is a no-op if one did.
|
||||
ble_schedule_adv_restart(ctx->device, ADV_ON_SYNC_DELAY_US);
|
||||
}
|
||||
|
||||
static void dispatch_disable_timer_cb(void* arg) {
|
||||
@@ -995,38 +1004,54 @@ static error_t api_disconnect(struct Device* device, const BtAddr addr, enum BtP
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static error_t api_add_event_callback(struct Device* device, void* cb_ctx, BtEventCallback fn) {
|
||||
static error_t api_event_subscribe(struct Device* device, BtEventSubscription* sub, TaskEventGroup* event_group) {
|
||||
BleCtx* ctx = (BleCtx*)device_get_driver_data(device);
|
||||
if (!ctx || !fn) return ERROR_INVALID_ARGUMENT;
|
||||
xSemaphoreTake(ctx->cb_mutex, portMAX_DELAY);
|
||||
if (ctx->callback_count >= BLE_MAX_CALLBACKS) {
|
||||
xSemaphoreGive(ctx->cb_mutex);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
if (!ctx || !sub || !event_group) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
uint32_t bit;
|
||||
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
|
||||
if (claim_result != ERROR_NONE) {
|
||||
return claim_result;
|
||||
}
|
||||
ctx->callbacks[ctx->callback_count].fn = fn;
|
||||
ctx->callbacks[ctx->callback_count].ctx = cb_ctx;
|
||||
ctx->callback_count++;
|
||||
xSemaphoreGive(ctx->cb_mutex);
|
||||
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
|
||||
// Avoid cyclic subscription list that would loop forever
|
||||
for (BtEventSubscription* existing = ctx->subscriptions; existing != nullptr; existing = existing->internal.next) {
|
||||
if (existing == sub) {
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
task_event_group_release_bit(event_group, bit);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
sub->internal.event_group = event_group;
|
||||
sub->bit = bit;
|
||||
sub->internal.next = ctx->subscriptions;
|
||||
ctx->subscriptions = sub;
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t api_remove_event_callback(struct Device* device, BtEventCallback fn) {
|
||||
static error_t api_event_unsubscribe(struct Device* device, BtEventSubscription* sub) {
|
||||
BleCtx* ctx = (BleCtx*)device_get_driver_data(device);
|
||||
if (!ctx || !fn) return ERROR_INVALID_ARGUMENT;
|
||||
xSemaphoreTake(ctx->cb_mutex, portMAX_DELAY);
|
||||
for (size_t i = 0; i < ctx->callback_count; i++) {
|
||||
if (ctx->callbacks[i].fn == fn) {
|
||||
// Shift remaining entries down
|
||||
for (size_t j = i; j + 1 < ctx->callback_count; j++) {
|
||||
ctx->callbacks[j] = ctx->callbacks[j + 1];
|
||||
}
|
||||
ctx->callback_count--;
|
||||
xSemaphoreGive(ctx->cb_mutex);
|
||||
return ERROR_NONE;
|
||||
if (!ctx || !sub) return ERROR_INVALID_ARGUMENT;
|
||||
|
||||
error_t result = ERROR_NOT_FOUND;
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
for (BtEventSubscription** link = &ctx->subscriptions; *link != nullptr; link = &(*link)->internal.next) {
|
||||
if (*link == sub) {
|
||||
*link = sub->internal.next;
|
||||
result = ERROR_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->cb_mutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
|
||||
if (result == ERROR_NONE) {
|
||||
task_event_group_release_bit(sub->internal.event_group, sub->bit);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static error_t api_set_device_name(struct Device* device, const char* name) {
|
||||
@@ -1079,8 +1104,8 @@ const BluetoothApi nimble_bluetooth_api = {
|
||||
.get_paired_peers = api_get_paired_peers,
|
||||
.connect = api_connect,
|
||||
.disconnect = api_disconnect,
|
||||
.add_event_callback = api_add_event_callback,
|
||||
.remove_event_callback = api_remove_event_callback,
|
||||
.event_subscribe = api_event_subscribe,
|
||||
.event_unsubscribe = api_event_unsubscribe,
|
||||
.set_device_name = api_set_device_name,
|
||||
.get_device_name = api_get_device_name,
|
||||
.set_hid_host_active = api_set_hid_host_active,
|
||||
@@ -1116,11 +1141,12 @@ static void destroy_child_device(struct Device*& child) {
|
||||
static error_t esp32_ble_start_device(struct Device* device) {
|
||||
BleCtx* ctx = new BleCtx();
|
||||
ctx->radio_mutex = xSemaphoreCreateMutex();
|
||||
ctx->cb_mutex = xSemaphoreCreateMutex();
|
||||
ctx->gap_mutex = xSemaphoreCreateMutex();
|
||||
mutex_construct(&ctx->subscriptionsMutex);
|
||||
ctx->subscriptions = nullptr;
|
||||
ctx->radio_state.store(BT_RADIO_STATE_OFF);
|
||||
ctx->scan_active.store(false);
|
||||
ctx->hid_host_active.store(false);
|
||||
ctx->callback_count = 0;
|
||||
ctx->spp_conn_handle.store(BLE_HS_CONN_HANDLE_NONE);
|
||||
ctx->spp_active.store(false);
|
||||
ctx->midi_conn_handle.store(BLE_HS_CONN_HANDLE_NONE);
|
||||
@@ -1206,6 +1232,29 @@ static error_t esp32_ble_stop_device(struct Device* device) {
|
||||
ctx->disable_timer = nullptr;
|
||||
}
|
||||
|
||||
if (ctx->gap_mutex != nullptr) {
|
||||
vSemaphoreDelete(ctx->gap_mutex);
|
||||
ctx->gap_mutex = nullptr;
|
||||
}
|
||||
|
||||
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
|
||||
// using this device, so a later bluetooth_event_unsubscribe() would find no ctx and skip
|
||||
// releasing the bit.
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
for (BtEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
|
||||
BtEventSubscription* next = sub->internal.next;
|
||||
// Force-close, don't destruct: a concurrent bluetooth_event_poll() may hold/await this
|
||||
// same lock (see BtEventSubscription::internal::closed).
|
||||
mutex_lock(&sub->internal.ring_mutex);
|
||||
sub->internal.closed = true;
|
||||
mutex_unlock(&sub->internal.ring_mutex);
|
||||
task_event_group_release_bit(sub->internal.event_group, sub->bit);
|
||||
sub = next;
|
||||
}
|
||||
ctx->subscriptions = nullptr;
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
mutex_destruct(&ctx->subscriptionsMutex);
|
||||
|
||||
s_ctx = nullptr;
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
|
||||
@@ -388,17 +388,27 @@ static error_t hid_device_start(struct Device* device, enum BtHidDeviceMode mode
|
||||
}
|
||||
|
||||
hid_appearance = appearance;
|
||||
if (!ble_hid_switch_profile(device, profile)) {
|
||||
// gap_mutex (not radio_mutex - see BleCtx::gap_mutex's comment) serializes this GAP/GATT
|
||||
// sequence against on_sync()'s own advertising decision and spp/midi's start/stop.
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
bool switched = ble_hid_switch_profile(device, profile);
|
||||
if (switched) {
|
||||
ble_hid_set_active(device, true);
|
||||
ble_start_advertising_hid(device, hid_appearance);
|
||||
}
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
if (!switched) {
|
||||
delete (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
device_set_driver_data(device, nullptr);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
ble_hid_set_active(device, true);
|
||||
ble_start_advertising_hid(device, hid_appearance);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t hid_device_stop(struct Device* device) {
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
ble_hid_set_active(device, false);
|
||||
ble_gap_adv_stop();
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
@@ -421,6 +431,7 @@ static error_t hid_device_stop(struct Device* device) {
|
||||
delete hid_ctx;
|
||||
device_set_driver_data(device, nullptr);
|
||||
}
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,13 +167,19 @@ static void midi_keepalive_cb(void* arg) {
|
||||
// All functions receive the midi child Device*.
|
||||
|
||||
static error_t midi_start(struct Device* device) {
|
||||
// gap_mutex (not radio_mutex - see BleCtx::gap_mutex's comment) serializes this GAP sequence
|
||||
// against on_sync()'s own advertising decision and hid/spp's start/stop.
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
ble_midi_set_active(device, true);
|
||||
// Create 2-second periodic Active Sensing timer to prevent Windows BLE MIDI
|
||||
// driver from declaring the connection idle and disconnecting (~8-10 s timeout).
|
||||
error_t rc = ble_midi_ensure_keepalive(device, midi_keepalive_cb, 2'000'000);
|
||||
if (rc != ERROR_NONE) return rc;
|
||||
ble_start_advertising(device, &MIDI_SVC_UUID);
|
||||
return ERROR_NONE;
|
||||
if (rc == ERROR_NONE) {
|
||||
ble_start_advertising(device, &MIDI_SVC_UUID);
|
||||
}
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
error_t ble_midi_start_internal(struct Device* midi_child) {
|
||||
@@ -181,6 +187,8 @@ error_t ble_midi_start_internal(struct Device* midi_child) {
|
||||
}
|
||||
|
||||
static error_t midi_stop(struct Device* device) {
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
ble_midi_set_active(device, false);
|
||||
ble_midi_stop_keepalive(device);
|
||||
uint16_t conn = ble_midi_get_conn_handle(device);
|
||||
@@ -191,6 +199,7 @@ static error_t midi_stop(struct Device* device) {
|
||||
if (!ble_spp_get_active(device) && !ble_hid_get_active(device)) {
|
||||
ble_gap_adv_stop();
|
||||
}
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,8 +126,13 @@ void ble_spp_set_conn_handle(struct Device* device, uint16_t h) {
|
||||
// All functions receive the serial child Device*.
|
||||
|
||||
static error_t spp_start(struct Device* device) {
|
||||
// gap_mutex (not radio_mutex - see BleCtx::gap_mutex's comment) serializes this GAP sequence
|
||||
// against on_sync()'s own advertising decision and hid/midi's start/stop.
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
ble_spp_set_active(device, true);
|
||||
ble_start_advertising(device, &NUS_SVC_UUID);
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
@@ -136,6 +141,8 @@ error_t ble_spp_start_internal(struct Device* serial_child) {
|
||||
}
|
||||
|
||||
static error_t spp_stop(struct Device* device) {
|
||||
BleCtx* root_ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(root_ctx->gap_mutex, portMAX_DELAY);
|
||||
ble_spp_set_active(device, false);
|
||||
uint16_t conn = ble_spp_get_conn_handle(device);
|
||||
if (conn != BLE_HS_CONN_HANDLE_NONE) {
|
||||
@@ -147,6 +154,7 @@ static error_t spp_stop(struct Device* device) {
|
||||
if (!ble_midi_get_active(device) && !ble_hid_get_active(device)) {
|
||||
ble_gap_adv_stop();
|
||||
}
|
||||
xSemaphoreGive(root_ctx->gap_mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -542,10 +542,12 @@ error_t api_event_subscribe(Device* device, WifiEventSubscription* sub, TaskEven
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
|
||||
// Avoid cyclic subscription list that would loop forever
|
||||
if (ctx->subscriptions == sub) {
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
task_event_group_release_bit(event_group, bit);
|
||||
return ERROR_INVALID_STATE;
|
||||
for (WifiEventSubscription* existing = ctx->subscriptions; existing != nullptr; existing = existing->internal.next) {
|
||||
if (existing == sub) {
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
task_event_group_release_bit(event_group, bit);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
sub->internal.event_group = event_group;
|
||||
@@ -639,12 +641,16 @@ error_t stop_device(Device* device) {
|
||||
|
||||
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
|
||||
// using this device, so a later wifi_event_unsubscribe() would find no ctx and skip releasing
|
||||
// the bit and destructing sub->internal.ring_mutex.
|
||||
// the bit.
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
|
||||
WifiEventSubscription* next = sub->internal.next;
|
||||
// Force-close, don't destruct: a concurrent wifi_event_poll() may hold/await this same
|
||||
// lock (see WifiEventSubscription::internal::closed).
|
||||
mutex_lock(&sub->internal.ring_mutex);
|
||||
sub->internal.closed = true;
|
||||
mutex_unlock(&sub->internal.ring_mutex);
|
||||
task_event_group_release_bit(sub->internal.event_group, sub->bit);
|
||||
mutex_destruct(&sub->internal.ring_mutex);
|
||||
sub = next;
|
||||
}
|
||||
ctx->subscriptions = nullptr;
|
||||
|
||||
@@ -302,10 +302,12 @@ error_t apiEventSubscribe(Device* device, WifiEventSubscription* sub, TaskEventG
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
|
||||
// Avoid cyclic subscription list that would loop forever
|
||||
if (ctx->subscriptions == sub) {
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
task_event_group_release_bit(event_group, bit);
|
||||
return ERROR_INVALID_STATE;
|
||||
for (WifiEventSubscription* existing = ctx->subscriptions; existing != nullptr; existing = existing->internal.next) {
|
||||
if (existing == sub) {
|
||||
mutex_unlock(&ctx->subscriptionsMutex);
|
||||
task_event_group_release_bit(event_group, bit);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
sub->internal.event_group = event_group;
|
||||
@@ -382,12 +384,16 @@ error_t stopDevice(Device* device) {
|
||||
|
||||
// Release any subscribers that never unsubscribed: device_stop() doesn't wait for apps still
|
||||
// using this device, so a later wifi_event_unsubscribe() would find no ctx and skip releasing
|
||||
// the bit and destructing sub->internal.ring_mutex.
|
||||
// the bit.
|
||||
mutex_lock(&ctx->subscriptionsMutex);
|
||||
for (WifiEventSubscription* sub = ctx->subscriptions; sub != nullptr;) {
|
||||
WifiEventSubscription* next = sub->internal.next;
|
||||
// Force-close, don't destruct: a concurrent wifi_event_poll() may hold/await this same
|
||||
// lock (see WifiEventSubscription::internal::closed).
|
||||
mutex_lock(&sub->internal.ring_mutex);
|
||||
sub->internal.closed = true;
|
||||
mutex_unlock(&sub->internal.ring_mutex);
|
||||
task_event_group_release_bit(sub->internal.event_group, sub->bit);
|
||||
mutex_destruct(&sub->internal.ring_mutex);
|
||||
sub = next;
|
||||
}
|
||||
ctx->subscriptions = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user