Skip to content

Commit 39c2605

Browse files
jhedbergkartben
authored andcommitted
Bluetooth: Fix deadlock with settings and bt_hci_cmd_send_sync()
We can't do synchronous HCI command sending in any settings commit() callback, since we don't know what context the callback is being called from. One particular deadlock can happen if settings_load() is called from the preemptible main thread: main() |--> settings_load() (aquire settings_lock mutex) |-->commit callback A that defers to system wq |-->commit callback B that calls bt_hci_cmd_send_sync() system wq from the previous deferral from within settings_load(): |--> work item |--> settings_save_one() |--> attempt to aquire settings_lock mutex In the above scenario, the bt_hci_cmd_send_sync() call from the main thread depends on the system workqueue being processed (since that's what does HCI command processing by default), while at the same time holding the settings subsystem's mutex. At the same time, a system wq item tries to store something into settings, however it deadlocks waiting for the settings mutex. The actual scenario that we have in the Bluetooth subsystem is where "commit callback A" is commit_settings() in host/settings.c, and "commit callback B" is keys_commit() in host/keys.c. The solution to the deadlock is to take advantage of deferred bt_id_add() handling which already exists, i.e. set a flag and deferre the actual adding to the system workqueue. Signed-off-by: Johan Hedberg <johan.hedberg@silabs.com>
1 parent 58ea18e commit 39c2605

File tree

5 files changed

+26
-4
lines changed

5 files changed

+26
-4
lines changed

subsys/bluetooth/host/id.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ static void pending_id_update(struct bt_keys *keys, void *data)
921921
}
922922
}
923923

924-
static void bt_id_pending_keys_update_set(struct bt_keys *keys, uint8_t flag)
924+
void bt_id_pending_keys_update_set(struct bt_keys *keys, uint8_t flag)
925925
{
926926
atomic_set_bit(bt_dev.flags, BT_DEV_ID_PENDING);
927927
keys->state |= flag;

subsys/bluetooth/host/id.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* SPDX-License-Identifier: Apache-2.0
66
*/
77

8+
#include "keys.h"
9+
810
#define RPA_TIMEOUT_MS(_rpa_timeout) (_rpa_timeout * MSEC_PER_SEC)
911

1012
static inline bool bt_id_rpa_is_new(void)
@@ -44,4 +46,6 @@ int bt_id_set_private_addr(uint8_t id);
4446

4547
void bt_id_pending_keys_update(void);
4648

49+
void bt_id_pending_keys_update_set(struct bt_keys *keys, uint8_t flag);
50+
4751
void bt_id_adv_limited_stopped(struct bt_le_ext_adv *adv);

subsys/bluetooth/host/keys.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "hci_core.h"
2929
#include "smp.h"
3030
#include "settings.h"
31+
#include "id.h"
3132
#include "keys.h"
3233

3334
#define LOG_LEVEL CONFIG_BT_KEYS_LOG_LEVEL
@@ -435,11 +436,19 @@ static int keys_set(const char *name, size_t len_rd, settings_read_cb read_cb,
435436
return 0;
436437
}
437438

439+
static void add_id_cb(struct k_work *work)
440+
{
441+
bt_id_pending_keys_update();
442+
}
443+
444+
static K_WORK_DEFINE(add_id_work, add_id_cb);
445+
438446
static void id_add(struct bt_keys *keys, void *user_data)
439447
{
440448
__ASSERT_NO_MSG(keys != NULL);
441449

442-
bt_id_add(keys);
450+
bt_id_pending_keys_update_set(keys, BT_KEYS_ID_PENDING_ADD);
451+
k_work_submit(&add_id_work);
443452
}
444453

445454
static int keys_commit(void)

tests/bluetooth/host/keys/mocks/id.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
#include "mocks/id.h"
99

1010
DEFINE_FAKE_VOID_FUNC(bt_id_del, struct bt_keys *);
11+
DEFINE_FAKE_VOID_FUNC(bt_id_pending_keys_update);
12+
DEFINE_FAKE_VOID_FUNC(bt_id_pending_keys_update_set, struct bt_keys *, uint8_t);
13+
DEFINE_FAKE_VALUE_FUNC(int, k_work_submit, struct k_work *);

tests/bluetooth/host/keys/mocks/id.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
#include <host/keys.h>
1111

1212
/* List of fakes used by this unit tester */
13-
#define ID_FFF_FAKES_LIST(FAKE) \
14-
FAKE(bt_id_del) \
13+
#define ID_FFF_FAKES_LIST(FAKE) \
14+
FAKE(bt_id_del) \
15+
FAKE(bt_id_pending_keys_update) \
16+
FAKE(bt_id_pending_keys_update_set) \
17+
FAKE(k_work_submit)
1518

1619
DECLARE_FAKE_VOID_FUNC(bt_id_del, struct bt_keys *);
20+
DECLARE_FAKE_VOID_FUNC(bt_id_pending_keys_update);
21+
DECLARE_FAKE_VOID_FUNC(bt_id_pending_keys_update_set, struct bt_keys *, uint8_t);
22+
DECLARE_FAKE_VALUE_FUNC(int, k_work_submit, struct k_work *);

0 commit comments

Comments
 (0)