Skip to content

Commit 818e18e

Browse files
PavelVPVkartben
authored andcommitted
bluetooth: host: Use critical section when making channel ready to send
A public API call that sends ACL data — for example, `bt_gatt_notify` — can be invoked from a preemptive thread context. This API, in turn, calls the `raise_data_ready` function, which adds an L2CAP channel to the `l2cap_data_ready` list. The atomic variable used to create a critical section around `l2cap_data_ready` did not work, because a preemptive thread can be pre-empted at any time. This means that regardless of the `atomic_set` result, we cannot trust it — the thread could be preempted after the call, and the atomic variable state could be changed (specifically by the TX processor in this case). The same issue applies to `bt_conn_data_ready`, which is called next. When making an L2CAP channel ready for sending data, we need to use a critical section when appending a channel to the `l2cap_data_ready` list. The same applies to the `conn_ready` list. Since cooperative threads can only be rescheduled explicitly, we must ensure there are no rescheduling points when accessing either the `l2cap_data_ready` or `conn_ready` lists. For `l2cap_data_ready`, this occurs in `get_ready_chan`, which is called from `l2cap_data_pull`, which in turn is called by the TX processor. For `conn_ready`, it occurs in `get_conn_ready`, which is also called from the TX processor. Both functions have no rescheduling points when working with their respective lists, so they do not require a critical section. This change removes the atomic variables previously used to create a critical section for both lists, as they were ineffective. Instead, `k_sched_lock`/`k_sched_unlock` are used where code may be executed from a preemptive thread context. The `sys_slist_find` function is used to check whether a channel or connection is already in the corresponding list. Fixes #89705 Signed-off-by: Pavel Vasilyev <[email protected]>
1 parent faecad6 commit 818e18e

File tree

4 files changed

+48
-33
lines changed

4 files changed

+48
-33
lines changed

include/zephyr/bluetooth/l2cap.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ struct bt_l2cap_le_chan {
275275

276276
/** @internal To be used with @ref bt_conn.upper_data_ready */
277277
sys_snode_t _pdu_ready;
278-
/** @internal To be used with @ref bt_conn.upper_data_ready */
279-
atomic_t _pdu_ready_lock;
280278
/** @internal Holds the length of the current PDU/segment */
281279
size_t _pdu_remaining;
282280
};

subsys/bluetooth/host/conn.c

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -872,31 +872,34 @@ static bool should_stop_tx(struct bt_conn *conn)
872872

873873
void bt_conn_data_ready(struct bt_conn *conn)
874874
{
875+
bool added;
876+
875877
LOG_DBG("DR");
876878

877-
/* The TX processor will call the `pull_cb` to get the buf */
878-
if (!atomic_set(&conn->_conn_ready_lock, 1)) {
879-
/* Attach a reference to the `bt_dev.le.conn_ready` list.
880-
*
881-
* This reference will be consumed when the conn is popped off
882-
* the list (in `get_conn_ready`).
883-
*
884-
* The `bt_dev.le.conn_ready` list is accessed by the tx_processor
885-
* which runs in a workqueue, but `bt_conn_data_ready` can be called
886-
* from different threads so we need to make sure that nothing will
887-
* trigger a thread switch while we are manipulating the list since
888-
* sys_slist_*() functions are not thread safe.
889-
*/
890-
bt_conn_ref(conn);
891-
k_sched_lock();
892-
sys_slist_append(&bt_dev.le.conn_ready,
893-
&conn->_conn_ready);
894-
k_sched_unlock();
895-
LOG_DBG("raised");
879+
bt_conn_ref(conn);
880+
881+
/* This function is the only function which accesses conn_ready list that can be called
882+
* from a preemptive thread context, therefore requires a critical section to ensure that
883+
* the conn_ready list is not modified while we are checking and appending to it.
884+
*/
885+
k_sched_lock();
886+
887+
if (!sys_slist_find(&bt_dev.le.conn_ready, &conn->_conn_ready, NULL)) {
888+
sys_slist_append(&bt_dev.le.conn_ready, &conn->_conn_ready);
889+
890+
added = true;
896891
} else {
897-
LOG_DBG("already in list");
892+
added = false;
893+
}
894+
895+
k_sched_unlock();
896+
897+
if (!added) {
898+
bt_conn_unref(conn);
898899
}
899900

901+
LOG_DBG("Connection %p %s conn_ready list", conn, added ? "added to" : "already in");
902+
900903
/* Kick the TX processor */
901904
bt_tx_irq_raise();
902905
}
@@ -977,7 +980,6 @@ struct bt_conn *get_conn_ready(void)
977980
/* Move reference off the list */
978981
__ASSERT_NO_MSG(prev != &conn->_conn_ready);
979982
sys_slist_remove(&bt_dev.le.conn_ready, prev, &conn->_conn_ready);
980-
(void)atomic_set(&conn->_conn_ready_lock, 0);
981983

982984
/* Append connection to list if it is connected and still has data */
983985
if (conn->has_data(conn) && (conn->state == BT_CONN_CONNECTED)) {

subsys/bluetooth/host/conn_internal.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ struct bt_conn {
324324
* This will be used by the TX processor to then fetch HCI frags from it.
325325
*/
326326
sys_snode_t _conn_ready;
327-
atomic_t _conn_ready_lock;
328327

329328
/* Holds the number of packets that have been sent to the controller but
330329
* not yet ACKd (by receiving an Number of Completed Packets). This

subsys/bluetooth/host/l2cap.c

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,6 @@ static void init_le_chan_private(struct bt_l2cap_le_chan *le_chan)
353353
#endif /* CONFIG_BT_L2CAP_SEG_RECV */
354354
#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */
355355
memset(&le_chan->_pdu_ready, 0, sizeof(le_chan->_pdu_ready));
356-
le_chan->_pdu_ready_lock = 0;
357356
le_chan->_pdu_remaining = 0;
358357
}
359358

@@ -701,14 +700,28 @@ struct net_buf *bt_l2cap_create_pdu_timeout(struct net_buf_pool *pool,
701700

702701
static void raise_data_ready(struct bt_l2cap_le_chan *le_chan)
703702
{
704-
if (!atomic_set(&le_chan->_pdu_ready_lock, 1)) {
703+
__maybe_unused bool added;
704+
705+
/* This function is the only function which accesses l2cap_data_ready list that can be
706+
* called from a preemptive thread context, therefore requires a critical section to ensure
707+
* that the data ready list is not modified while we are checking and appending to it.
708+
*/
709+
k_sched_lock();
710+
711+
if (!sys_slist_find(&le_chan->chan.conn->l2cap_data_ready,
712+
&le_chan->_pdu_ready, NULL)) {
705713
sys_slist_append(&le_chan->chan.conn->l2cap_data_ready,
706714
&le_chan->_pdu_ready);
707-
LOG_DBG("data ready raised %p", le_chan);
715+
716+
added = true;
708717
} else {
709-
LOG_DBG("data ready already %p", le_chan);
718+
added = false;
710719
}
711720

721+
k_sched_unlock();
722+
723+
LOG_DBG("L2CAP channel %p data ready %s", le_chan, added ? "added" : "already added");
724+
712725
bt_conn_data_ready(le_chan->chan.conn);
713726
}
714727

@@ -720,10 +733,6 @@ static void lower_data_ready(struct bt_l2cap_le_chan *le_chan)
720733
LOG_DBG("%p", le_chan);
721734

722735
__ASSERT_NO_MSG(s == &le_chan->_pdu_ready);
723-
724-
__maybe_unused atomic_t old = atomic_set(&le_chan->_pdu_ready_lock, 0);
725-
726-
__ASSERT_NO_MSG(old);
727736
}
728737

729738
static void cancel_data_ready(struct bt_l2cap_le_chan *le_chan)
@@ -732,9 +741,16 @@ static void cancel_data_ready(struct bt_l2cap_le_chan *le_chan)
732741

733742
LOG_DBG("%p", le_chan);
734743

744+
/* Use critical section here as this function can be called from
745+
* a preemptive thread context and we need to ensure that the data ready list is not
746+
* modified while we are removing the channel from it.
747+
*/
748+
k_sched_lock();
749+
735750
sys_slist_find_and_remove(&conn->l2cap_data_ready,
736751
&le_chan->_pdu_ready);
737-
atomic_set(&le_chan->_pdu_ready_lock, 0);
752+
753+
k_sched_unlock();
738754
}
739755

740756
int bt_l2cap_send_pdu(struct bt_l2cap_le_chan *le_chan, struct net_buf *pdu,

0 commit comments

Comments
 (0)