Skip to content
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

Bluetooth: scan: Implement non-blocking ext-adv report reassembly #78982

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions drivers/bluetooth/hci/h4.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,12 @@ static struct net_buf *get_rx(struct h4_data *h4, k_timeout_t timeout)
LOG_DBG("type 0x%02x, evt 0x%02x", h4->rx.type, h4->rx.evt.evt);

switch (h4->rx.type) {
case BT_HCI_H4_EVT:
return bt_buf_get_evt(h4->rx.evt.evt, h4->rx.discardable, timeout);
case BT_HCI_H4_EVT: {
uint8_t evt = h4->rx.evt.evt;
uint8_t meta = h4->rx.hdr[3];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any define for the offset instead of the magic number 3?


return bt_buf_get_evt_but_better(evt, meta, timeout);
}
case BT_HCI_H4_ACL:
return bt_buf_get_rx(BT_BUF_ACL_IN, timeout);
case BT_HCI_H4_ISO:
Expand Down
59 changes: 8 additions & 51 deletions drivers/bluetooth/hci/ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,45 +30,8 @@ struct ipc_data {
const struct device *ipc;
};

static bool is_hci_event_discardable(const uint8_t *evt_data)
{
uint8_t evt_type = evt_data[0];

switch (evt_type) {
#if defined(CONFIG_BT_CLASSIC)
case BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI:
case BT_HCI_EVT_EXTENDED_INQUIRY_RESULT:
return true;
#endif
case BT_HCI_EVT_LE_META_EVENT: {
uint8_t subevt_type = evt_data[sizeof(struct bt_hci_evt_hdr)];

switch (subevt_type) {
case BT_HCI_EVT_LE_ADVERTISING_REPORT:
return true;
#if defined(CONFIG_BT_EXT_ADV)
case BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT:
{
const struct bt_hci_evt_le_ext_advertising_report *ext_adv =
(void *)&evt_data[3];

return (ext_adv->num_reports == 1) &&
((ext_adv->adv_info[0].evt_type &
BT_HCI_LE_ADV_EVT_TYPE_LEGACY) != 0);
}
#endif
default:
return false;
}
}
default:
return false;
}
}

static struct net_buf *bt_ipc_evt_recv(const uint8_t *data, size_t remaining)
{
bool discardable;
struct bt_hci_evt_hdr hdr;
struct net_buf *buf;
size_t buf_tailroom;
Expand All @@ -78,8 +41,6 @@ static struct net_buf *bt_ipc_evt_recv(const uint8_t *data, size_t remaining)
return NULL;
}

discardable = is_hci_event_discardable(data);

memcpy((void *)&hdr, data, sizeof(hdr));
data += sizeof(hdr);
remaining -= sizeof(hdr);
Expand All @@ -88,18 +49,14 @@ static struct net_buf *bt_ipc_evt_recv(const uint8_t *data, size_t remaining)
LOG_ERR("Event payload length is not correct");
return NULL;
}
LOG_DBG("len %u", hdr.len);

do {
buf = bt_buf_get_evt(hdr.evt, discardable, discardable ? K_NO_WAIT : K_SECONDS(10));
if (!buf) {
if (discardable) {
LOG_DBG("Discardable buffer pool full, ignoring event");
return buf;
}
LOG_WRN("Couldn't allocate a buffer after waiting 10 seconds.");
}
} while (!buf);
LOG_DBG("evt 0x%2x len %u", hdr.evt, hdr.len);

buf = bt_buf_get_evt_but_better(hdr.evt, data[0], K_SECONDS(60));
if (!buf) {
/* If events are blocked for 60s, something has gone wrong. */
Thalley marked this conversation as resolved.
Show resolved Hide resolved
LOG_DBG("Buffer pool full, ignoring event");
return buf;
}

net_buf_add_mem(buf, &hdr, sizeof(hdr));

Expand Down
2 changes: 2 additions & 0 deletions include/zephyr/bluetooth/buf.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ static inline enum bt_buf_type bt_buf_get_type(struct net_buf *buf)
->type;
}

struct net_buf *bt_buf_get_evt_but_better(uint8_t evt, uint8_t meta, k_timeout_t timeout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we'd want to deprecate bt_buf_get_evt() as well? That said, you'd need to update all upstream HCI drivers first, I suppose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. I made this to allow a soft-migration. Now that I think of it, I should update bt_buf_can_steal_ext_adv_report_buf to take buf and double-check the pool too. Because it won't work if the driver uses a different pool.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. Found (and fixed) a bug in the process, yay.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. I made this to allow a soft-migration.

I assume that it's planned that @alwa-nordic will take over and finalize this?

If not, we should create a GH issue for it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alwa-nordic will take over, that is the plan.


/**
* @}
*/
Expand Down
36 changes: 33 additions & 3 deletions subsys/bluetooth/controller/hci/hci_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,29 @@ static void prio_recv_thread(void *p1, void *p2, void *p3)
}
}

static bool node_to_evt(struct node_rx_pdu *node_rx, uint8_t *evt, uint8_t *meta)
{
#if defined(CONFIG_BT_OBSERVER) && defined(CONFIG_BT_EXT_ADV)
switch (node_rx->hdr.type) {
case NODE_RX_TYPE_EXT_1M_REPORT:
case NODE_RX_TYPE_EXT_2M_REPORT:
case NODE_RX_TYPE_EXT_CODED_REPORT:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT;
return true;
case NODE_RX_TYPE_EXT_SCAN_TERMINATE:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_SCAN_TIMEOUT;
return true;
default:
return false;
}

#else
return false;
#endif
Comment on lines +436 to +454
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if defined(CONFIG_BT_OBSERVER) && defined(CONFIG_BT_EXT_ADV)
switch (node_rx->hdr.type) {
case NODE_RX_TYPE_EXT_1M_REPORT:
case NODE_RX_TYPE_EXT_2M_REPORT:
case NODE_RX_TYPE_EXT_CODED_REPORT:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT;
return true;
case NODE_RX_TYPE_EXT_SCAN_TERMINATE:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_SCAN_TIMEOUT;
return true;
default:
return false;
}
#else
return false;
#endif
if IS_ENABLED((CONFIG_BT_OBSERVER) && IS_ENABLED(CONFIG_BT_EXT_ADV)) {
switch (node_rx->hdr.type) {
case NODE_RX_TYPE_EXT_1M_REPORT:
case NODE_RX_TYPE_EXT_2M_REPORT:
case NODE_RX_TYPE_EXT_CODED_REPORT:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT;
return true;
case NODE_RX_TYPE_EXT_SCAN_TERMINATE:
*evt = BT_HCI_EVT_LE_META_EVENT;
*meta = BT_HCI_EVT_LE_SCAN_TIMEOUT;
return true;
default:
return false;
}
else {
return false;
}

}

static inline struct net_buf *encode_node(struct node_rx_pdu *node_rx,
int8_t class)
{
Expand All @@ -443,10 +466,17 @@ static inline struct net_buf *encode_node(struct node_rx_pdu *node_rx,
case HCI_CLASS_EVT_CONNECTION:
case HCI_CLASS_EVT_LLCP:
if (class == HCI_CLASS_EVT_DISCARDABLE) {
buf = bt_buf_get_evt(BT_HCI_EVT_UNKNOWN, true,
K_NO_WAIT);
buf = bt_buf_get_evt(BT_HCI_EVT_UNKNOWN, true, K_NO_WAIT);
} else {
buf = bt_buf_get_rx(BT_BUF_EVT, K_FOREVER);
uint8_t evt = 0;
uint8_t meta = 0;
bool recognized = node_to_evt(node_rx, &evt, &meta);

if (recognized) {
buf = bt_buf_get_evt_but_better(evt, meta, K_FOREVER);
} else {
buf = bt_buf_get_rx(BT_BUF_EVT, K_FOREVER);
}
}
if (buf) {
hci_evt_encode(node_rx, buf);
Expand Down
19 changes: 19 additions & 0 deletions subsys/bluetooth/host/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,19 @@ config BT_EXT_SCAN_BUF_SIZE
provided by the controller is larger than this buffer size,
the remaining data will be discarded.

config BT_EXT_SCAN_BUF_NUM
int "Maximum number of report reassembly buffers"
range 1 65535
default 2
help
Advertising reports can be fragmented when transmitted over HCI to the
Host. They are re-assembled into a dedicated buffer pool and the
application is notified asynchronously. This number governs the amount
of pending reassembled advertising reports.

Note that this option only uses up resources if extended advertising
is enabled.
Comment on lines +792 to +793
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it then not depend on BT_EXT_ADV?


endif # BT_OBSERVER

config BT_SCAN_WITH_IDENTITY
Expand Down Expand Up @@ -1035,6 +1048,12 @@ config BT_TESTING
This option enables custom Bluetooth testing interface.
Shall only be used for testing purposes.

config BT_HTOP
bool "Bluetooth Statistics"
help
Enable printing of Bluetooth performance statistics.
Sort of like THREAD_ANALYZER.
Comment on lines +1051 to +1055
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why HTOP? Why not BT_STATS or BT_ANALYZER?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

htop is better than top. I'll let you and @alwa-nordic + @jhedberg decide on a proper name.
Note that this commit could def be yeeted out to its own PR.


config BT_CONN_DISABLE_SECURITY
bool "Disable security"
depends on BT_TESTING
Expand Down
95 changes: 95 additions & 0 deletions subsys/bluetooth/host/buf.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,101 @@ struct net_buf *bt_buf_get_rx(enum bt_buf_type type, k_timeout_t timeout)
return buf;
}

static bool is_hci_event_discardable(const uint8_t evt_type, const uint8_t subevt_type)
{
switch (evt_type) {
#if defined(CONFIG_BT_CLASSIC)
case BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI:
case BT_HCI_EVT_EXTENDED_INQUIRY_RESULT:
return true;
#endif
case BT_HCI_EVT_LE_META_EVENT: {
switch (subevt_type) {
case BT_HCI_EVT_LE_ADVERTISING_REPORT:
return true;
default:
return false;
}
}
default:
return false;
}
}

NET_BUF_POOL_FIXED_DEFINE(sync_adv_pool, 2, SYNC_EVT_SIZE, sizeof(struct bt_buf_data), NULL);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 2 intentionally hardcoded here? Is it limited to 2 for some reason?


/* This is a host-private function */
bool bt_buf_can_steal_ext_adv_report_buf(struct net_buf *buf)
{
if (net_buf_pool_get(buf->pool_id) != &sync_adv_pool) {
/* The driver did not use `bt_buf_get_evt_but_better` */
return false;
}

/* Host can steal the buffer if we have at least one available.
* This is the case when:
* - There are buffers in the `free` LIFO
* - There are un-initialized buffers
*/
return (!k_fifo_is_empty(&sync_adv_pool.free)) || (sync_adv_pool.uninit_count != 0);
Copy link
Member

@jhedberg jhedberg Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This almost begs for a proper net_buf_pool API to test for "is it empty?". That said, it's inherently racy in premptible contexts, so we might not want to give people such a foot gun.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. Ideally net_buf would init all buffers on boot and place them in the free LIFO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, though that's a bit of a separate issue. Even with everything in the LIFO it's still racy to first check and then try to allocate (or make any other decisions based on the pool status)

}

struct net_buf *bt_buf_get_evt_but_better(uint8_t evt, uint8_t meta, k_timeout_t timeout)
Thalley marked this conversation as resolved.
Show resolved Hide resolved
{
/* Always allocs so-called "discardable" events with K_NO_WAIT. Most of
* the drivers will call from ISR so they should not block at all.
*
* For non-meta events, the `meta` param is just the next byte.
*/
struct net_buf *buf = NULL;

switch (evt) {
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
case BT_HCI_EVT_CMD_STATUS:
case BT_HCI_EVT_CMD_COMPLETE:
case BT_HCI_EVT_LE_META_EVENT: {
bool is_ext_adv = meta == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT;
bool is_meta = evt == BT_HCI_EVT_LE_META_EVENT;

if (is_ext_adv) {
buf = net_buf_alloc(&sync_adv_pool, timeout);
/* 60% of the time we get a buffer every time */
__ASSERT_NO_MSG(buf);
break;
}

if (is_meta && !is_ext_adv) {
/* For now we use the sync pool only for ext adv
* reports. They cannot be marked as discardable thanks
* to the geniuses in SIG that decided to save space and
* only use two bits for reassembly status.
*/
break;
}
Comment on lines +146 to +155
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
if (is_meta && !is_ext_adv) {
/* For now we use the sync pool only for ext adv
* reports. They cannot be marked as discardable thanks
* to the geniuses in SIG that decided to save space and
* only use two bits for reassembly status.
*/
break;
}
} else {
bool is_meta = evt == BT_HCI_EVT_LE_META_EVENT;
if (is_meta) {
/* For now we use the sync pool only for ext adv
* reports. They cannot be marked as discardable thanks
* to the geniuses in SIG that decided to save space and
* only use two bits for reassembly status.
*/
break;
}

Minor optimization; we don't need the is_meta variable if is_ext_adv == true


buf = net_buf_alloc(&sync_evt_pool, timeout);
break;
}
default:
break;
}

if (!buf) {
if (is_hci_event_discardable(evt, meta)) {
buf = net_buf_alloc(&discardable_pool, K_NO_WAIT);
} else {
return bt_buf_get_rx(BT_BUF_EVT, timeout);
}
}

if (buf) {
net_buf_reserve(buf, BT_BUF_RESERVE);
bt_buf_set_type(buf, BT_BUF_EVT);
}

return buf;
}

struct net_buf *bt_buf_get_evt(uint8_t evt, bool discardable,
k_timeout_t timeout)
{
Expand Down
2 changes: 1 addition & 1 deletion subsys/bluetooth/host/hci_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct net_buf *bt_hci_evt_create(uint8_t evt, uint8_t len)
struct bt_hci_evt_hdr *hdr;
struct net_buf *buf;

buf = bt_buf_get_evt(evt, false, K_FOREVER);
buf = bt_buf_get_evt_but_better(evt, 0, K_FOREVER);

BT_ASSERT(buf);

Expand Down
Loading
Loading