Skip to content

Commit 86b156b

Browse files
committed
Bluetooth: Host: Don't call user callback from TX thread
ATT is invoking user callbacks in its net_buf destroy function. It is common practice that these callbacks can block on bt_hci_cmd_alloc(), so we must consider these callbacks as blocking. This is a deadlock when the net_buf_unref() happens inside the HCI driver, driver invoked from tx_processor. Blocking callbacks like this appear in our own samples. See further down about how this problem was detected. Currently, tx_processor not protect against blocking callbacks so it is de-facto forbidden. The Host should not equip net_bufs with dangerous destroy callbacks. This commit makes ATT defer its net_buf destruction and user callback invocation to the system workqueue, so that net_buf_unref is safe to call from non-blocking threads. Unsafe code is banished to the system workqueue wild west. Future improvement may be to allow the user to provide their own workqueue for ATT callbacks. This deadlock was detected because the following test was failing while tx_processor to the bt_taskq: tests/bsim/bluetooth/ll/throughput/tests_scripts/gatt_write.sh The above test has an ATT callback `write_cmd_cb` invokes `bt_conn_le_param_update` can block waiting for `tx_processor`. Signed-off-by: Aleksander Wasaznik <[email protected]>
1 parent a64b617 commit 86b156b

File tree

1 file changed

+17
-1
lines changed
  • subsys/bluetooth/host

1 file changed

+17
-1
lines changed

subsys/bluetooth/host/att.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,13 @@ const char *bt_att_err_to_str(uint8_t att_err)
248248
}
249249
#endif /* CONFIG_BT_ATT_ERR_TO_STR */
250250

251-
static void att_tx_destroy(struct net_buf *buf)
251+
static void att_tx_destroy_work_handler(struct k_work *work);
252+
static K_WORK_DEFINE(att_tx_destroy_work, att_tx_destroy_work_handler);
253+
static sys_slist_t tx_destroy_queue;
254+
255+
static void att_tx_destroy_work_handler(struct k_work *work)
252256
{
257+
struct net_buf *buf = net_buf_slist_get(&tx_destroy_queue);
253258
struct bt_att_tx_meta_data *p_meta = att_get_tx_meta_data(buf);
254259
struct bt_att_tx_meta_data meta;
255260

@@ -278,6 +283,17 @@ static void att_tx_destroy(struct net_buf *buf)
278283
if (meta.opcode != 0) {
279284
att_on_sent_cb(&meta);
280285
}
286+
287+
if (!sys_slist_is_empty(&tx_destroy_queue)) {
288+
k_work_submit_to_queue(bt_workq_chosen, &att_tx_destroy_work);
289+
}
290+
}
291+
292+
static void att_tx_destroy(struct net_buf *buf)
293+
{
294+
/* We need to invoke `att_on_sent_cb` which may block. Defer to bt_workq. */
295+
net_buf_slist_put(&tx_destroy_queue, buf);
296+
k_work_submit_to_queue(bt_workq_chosen, &att_tx_destroy_work); /* att_tx_destroy_work_handler */
281297
}
282298

283299
NET_BUF_POOL_DEFINE(att_pool, CONFIG_BT_ATT_TX_COUNT,

0 commit comments

Comments
 (0)