-
Notifications
You must be signed in to change notification settings - Fork 677
[fromtree] bluetooth: fix bug when destroying tx buffers on disconnected #3026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f9e37e5
5394bcc
8622c2d
2aabbe6
3f54299
d9f3c30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -649,8 +649,8 @@ | |
} | ||
|
||
static int send_buf(struct bt_conn *conn, struct net_buf *buf, | ||
size_t len, void *cb, void *ud) | ||
size_t len, bt_conn_tx_cb_t cb, void *ud) | ||
{ | ||
Check notice on line 653 in subsys/bluetooth/host/conn.c
|
||
struct net_buf *frag = NULL; | ||
struct bt_conn_tx *tx = NULL; | ||
uint8_t flags; | ||
|
@@ -659,13 +659,15 @@ | |
if (buf->len == 0) { | ||
__ASSERT_NO_MSG(0); | ||
|
||
return -EMSGSIZE; | ||
err = -EMSGSIZE; | ||
goto error_return; | ||
} | ||
|
||
if (bt_buf_has_view(buf)) { | ||
__ASSERT_NO_MSG(0); | ||
|
||
return -EIO; | ||
err = -EIO; | ||
goto error_return; | ||
} | ||
|
||
LOG_DBG("conn %p buf %p len %zu buf->len %u cb %p ud %p", | ||
|
@@ -680,7 +682,8 @@ | |
*/ | ||
__ASSERT(0, "No controller bufs"); | ||
|
||
return -ENOMEM; | ||
err = -ENOMEM; | ||
goto error_return; | ||
} | ||
|
||
/* Allocate and set the TX context */ | ||
|
@@ -689,37 +692,42 @@ | |
/* See big comment above */ | ||
if (!tx) { | ||
__ASSERT(0, "No TX context"); | ||
|
||
return -ENOMEM; | ||
k_sem_give(bt_conn_get_pkts(conn)); | ||
err = -ENOMEM; | ||
goto error_return; | ||
} | ||
|
||
tx->cb = cb; | ||
tx->user_data = ud; | ||
|
||
uint16_t frag_len = MIN(conn_mtu(conn), len); | ||
|
||
/* If ATT sent callback is delayed until data transmission is done by BLE controller, the | ||
* transmitted buffer may have an additional reference. The reference is used to extend | ||
* lifetime of the net buffer until the data transmission is confirmed by ACK of the remote. | ||
/* If ATT sent callback is delayed until data transmission | ||
* is done by BLE controller, the transmitted buffer may | ||
* have an additional reference. The reference is used to | ||
* extend lifetime of the net buffer until the data | ||
* transmission is confirmed by ACK of the remote. | ||
* | ||
* send_buf function can be called multiple times, if buffer | ||
* has to be fragmented over HCI. In that case, the callback | ||
* is provided as an argument only for the last transmitted | ||
* fragment. The `buf->ref == 1` (or 2) check is skipped | ||
* because it's impossible to properly validate number of | ||
* references for the sent fragments if buffers may have the | ||
* additional reference. | ||
* | ||
* send_buf function can be called multiple times, if buffer has to be fragmented over HCI. | ||
* In that case, the callback is provided as an argument only for the last transmitted | ||
* fragment. The `buf->ref == 1` check is skipped because it's impossible to properly | ||
* validate number of references for the sent fragments if buffers may have the additional | ||
* reference. | ||
* Otherwise, check that buf->ref is 1 or 2. It would be 1 | ||
* if this was the only reference (e.g. buf was removed from | ||
* the conn tx_queue). It would be 2 if the tx_data_pull | ||
* kept it on the tx_queue for segmentation. | ||
*/ | ||
__ASSERT_NO_MSG(IS_ENABLED(CONFIG_BT_ATT_SENT_CB_AFTER_TX) || (buf->ref == 1)); | ||
__ASSERT_NO_MSG(IS_ENABLED(CONFIG_BT_ATT_SENT_CB_AFTER_TX) || (buf->ref == 1) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we squash the 2 noups? This one reverts part of the first noup There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline with @alwa-nordic . Will not squash in scope of this PR. |
||
(buf->ref == 2)); | ||
|
||
if (buf->len > frag_len) { | ||
LOG_DBG("keep %p around", buf); | ||
frag = get_data_frag(net_buf_ref(buf), frag_len); | ||
} else { | ||
LOG_DBG("move %p ref in", buf); | ||
/* Move the ref into `frag` for the last TX. That way `buf` will | ||
* get destroyed when `frag` is destroyed. | ||
*/ | ||
frag = get_data_frag(buf, frag_len); | ||
} | ||
/* The reference is always transferred to the frag, so when | ||
* the frag is destroyed, the parent reference is decremented. | ||
*/ | ||
frag = get_data_frag(buf, frag_len); | ||
|
||
/* Caller is supposed to check we have all resources to send */ | ||
__ASSERT_NO_MSG(frag != NULL); | ||
|
@@ -733,7 +741,7 @@ | |
conn->next_is_frag = false; | ||
} | ||
|
||
LOG_DBG("send frag: buf %p len %d", buf, frag_len); | ||
LOG_DBG("send frag: buf %p len %d", frag, frag_len); | ||
|
||
/* At this point, the buffer is either a fragment or a full HCI packet. | ||
* The flags are also valid. | ||
|
@@ -776,15 +784,26 @@ | |
*/ | ||
net_buf_unref(frag); | ||
|
||
/* `buf` might not get destroyed right away, and its `tx` | ||
* pointer will still be reachable. Make sure that we don't try | ||
* to use the destroyed context later. | ||
/* `buf` might not get destroyed right away because it may | ||
* still be on a conn tx_queue, and its `tx` pointer will still | ||
* be reachable. Make sure that we don't try to use the | ||
* destroyed context later. | ||
*/ | ||
conn_tx_destroy(conn, tx); | ||
k_sem_give(bt_conn_get_pkts(conn)); | ||
|
||
/* Merge HCI driver errors */ | ||
return -EIO; | ||
|
||
error_return: | ||
/* Runtime handling of fatal errors when ASSERTS are disabled. | ||
* Unref the buf and invoke callback with the error. | ||
*/ | ||
net_buf_unref(buf); | ||
if (cb) { | ||
cb(conn, ud, err); | ||
} | ||
return err; | ||
} | ||
|
||
static struct k_poll_signal conn_change = | ||
|
@@ -966,8 +985,8 @@ | |
sys_slist_remove(&bt_dev.le.conn_ready, prev, &conn->_conn_ready); | ||
(void)atomic_set(&conn->_conn_ready_lock, 0); | ||
|
||
/* Append connection to list if it still has data */ | ||
if (conn->has_data(conn)) { | ||
/* Append connection to list if it is connected and still has data */ | ||
if (conn->has_data(conn) && (conn->state == BT_CONN_CONNECTED)) { | ||
LOG_DBG("appending %p to back of TX queue", conn); | ||
bt_conn_data_ready(conn); | ||
} | ||
|
@@ -995,30 +1014,6 @@ | |
} | ||
#endif /* defined(CONFIG_BT_CONN) */ | ||
|
||
/* Acts as a "null-routed" bt_send(). This fn will decrease the refcount of | ||
* `buf` and call the user callback with an error code. | ||
*/ | ||
static void destroy_and_callback(struct bt_conn *conn, | ||
struct net_buf *buf, | ||
bt_conn_tx_cb_t cb, | ||
void *ud) | ||
{ | ||
if (!cb) { | ||
conn->get_and_clear_cb(conn, buf, &cb, &ud); | ||
} | ||
|
||
LOG_DBG("pop: cb %p userdata %p", cb, ud); | ||
|
||
/* bt_send() would've done an unref. Do it here also, so the buffer is | ||
* hopefully destroyed and the user callback can allocate a new one. | ||
*/ | ||
net_buf_unref(buf); | ||
|
||
if (cb) { | ||
cb(conn, ud, -ESHUTDOWN); | ||
} | ||
} | ||
|
||
static volatile bool _suspend_tx; | ||
|
||
#if defined(CONFIG_BT_TESTING) | ||
|
@@ -1061,17 +1056,7 @@ | |
|
||
if (conn->state != BT_CONN_CONNECTED) { | ||
LOG_WRN("conn %p: not connected", conn); | ||
|
||
/* Call the user callbacks & destroy (final-unref) the buffers | ||
* we were supposed to send. | ||
*/ | ||
buf = conn->tx_data_pull(conn, SIZE_MAX, &buf_len); | ||
while (buf) { | ||
destroy_and_callback(conn, buf, cb, ud); | ||
buf = conn->tx_data_pull(conn, SIZE_MAX, &buf_len); | ||
} | ||
|
||
goto exit; | ||
goto raise_and_exit; | ||
} | ||
|
||
/* now that we are guaranteed resources, we can pull data from the upper | ||
|
@@ -1105,25 +1090,12 @@ | |
int err = send_buf(conn, buf, buf_len, cb, ud); | ||
|
||
if (err) { | ||
/* -EIO means `unrecoverable error`. It can be an assertion that | ||
* failed or an error from the HCI driver. | ||
* | ||
* -ENOMEM means we thought we had all the resources to send the | ||
* buf (ie. TX context + controller buffer) but one of them was | ||
* not available. This is likely due to a failure of | ||
* assumption, likely that we have been pre-empted somehow and | ||
* that `tx_processor()` has been re-entered. | ||
* | ||
* In both cases, we destroy the buffer and mark the connection | ||
* as dead. | ||
*/ | ||
LOG_ERR("Fatal error (%d). Disconnecting %p", err, conn); | ||
destroy_and_callback(conn, buf, cb, ud); | ||
bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); | ||
|
||
goto exit; | ||
} | ||
|
||
raise_and_exit: | ||
/* Always kick the TX work. It will self-suspend if it doesn't get | ||
* resources or there is nothing left to send. | ||
*/ | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: "letter count" (forgot proper word) in one line had been increased. So better to go with the new one. Probably you can just have two long lines and run clang-format.