The Bluetooth data buffer API currently lacks a mechanism to notify when a buffer is freed in the RX pool. This limitation forces HCI drivers to adopt inefficient workarounds to manage buffer allocation. HCI drivers face two suboptimal options: - Blocking calls: Use bt_buf_get_rx with K_FOREVER, which blocks the execution context until a buffer becomes available. - Polling: Repeatedly call bt_buf_get_rx with K_NO_WAIT, which increases CPU load and reduces efficiency. This commit introduces a callback mechanism that is triggered each time a buffer is freed in the RX pool. With this feature, HCI drivers can: - Call bt_buf_get_rx with K_NO_WAIT. - Wait for the callback notification if a NULL buffer is returned, avoiding unnecessary polling. The new callback improves efficiency by enabling event-driven behavior for buffer management, reducing CPU overhead while maintaining responsiveness. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
257 lines
6.4 KiB
C
257 lines
6.4 KiB
C
/*
|
|
* Copyright (c) 2017 Nordic Semiconductor ASA
|
|
* Copyright (c) 2015 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/bluetooth/buf.h>
|
|
#include <zephyr/bluetooth/l2cap.h>
|
|
|
|
#include "buf_view.h"
|
|
#include "hci_core.h"
|
|
#include "conn_internal.h"
|
|
#include "iso_internal.h"
|
|
|
|
#include <zephyr/bluetooth/hci.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_buf, CONFIG_BT_LOG_LEVEL);
|
|
|
|
/* Events have a length field of 1 byte. This size fits all events.
|
|
*
|
|
* It's true that we don't put all kinds of events there (yet). However, the
|
|
* command complete event has an arbitrary payload, depending on opcode.
|
|
*/
|
|
#define SYNC_EVT_SIZE (BT_BUF_RESERVE + BT_HCI_EVT_HDR_SIZE + 255)
|
|
|
|
static bt_buf_rx_freed_cb_t buf_rx_freed_cb;
|
|
|
|
static void buf_rx_freed_notify(enum bt_buf_type mask)
|
|
{
|
|
k_sched_lock();
|
|
|
|
if (buf_rx_freed_cb) {
|
|
buf_rx_freed_cb(mask);
|
|
}
|
|
|
|
k_sched_unlock();
|
|
}
|
|
|
|
#if defined(CONFIG_BT_ISO_RX)
|
|
static void iso_rx_freed_cb(void)
|
|
{
|
|
buf_rx_freed_notify(BT_BUF_ISO_IN);
|
|
}
|
|
#endif
|
|
|
|
/* Pool for RX HCI buffers that are always freed by `bt_recv`
|
|
* before it returns.
|
|
*
|
|
* A singleton buffer shall be sufficient for correct operation.
|
|
* The buffer count may be increased as an optimization to allow
|
|
* the HCI transport to fill buffers in parallel with `bt_recv`
|
|
* consuming them.
|
|
*/
|
|
NET_BUF_POOL_FIXED_DEFINE(sync_evt_pool, 1, SYNC_EVT_SIZE, sizeof(struct bt_buf_data), NULL);
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(discardable_pool, CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT,
|
|
BT_BUF_EVT_SIZE(CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE),
|
|
sizeof(struct bt_buf_data), NULL);
|
|
|
|
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
|
|
static void acl_in_pool_destroy(struct net_buf *buf)
|
|
{
|
|
bt_hci_host_num_completed_packets(buf);
|
|
buf_rx_freed_notify(BT_BUF_ACL_IN);
|
|
}
|
|
|
|
static void evt_pool_destroy(struct net_buf *buf)
|
|
{
|
|
net_buf_destroy(buf);
|
|
buf_rx_freed_notify(BT_BUF_EVT);
|
|
}
|
|
|
|
NET_BUF_POOL_DEFINE(acl_in_pool, BT_BUF_ACL_RX_COUNT, BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_RX_SIZE),
|
|
sizeof(struct acl_data), acl_in_pool_destroy);
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(evt_pool, CONFIG_BT_BUF_EVT_RX_COUNT, BT_BUF_EVT_RX_SIZE,
|
|
sizeof(struct bt_buf_data), evt_pool_destroy);
|
|
#else
|
|
static void hci_rx_pool_destroy(struct net_buf *buf)
|
|
{
|
|
net_buf_destroy(buf);
|
|
|
|
/* When ACL Flow Control is disabled, a single pool is used for events and acl data.
|
|
* Therefore the callback will always notify about both types of buffers, BT_BUF_EVT and
|
|
* BT_BUF_ACL_IN.
|
|
*/
|
|
buf_rx_freed_notify(BT_BUF_EVT | BT_BUF_ACL_IN);
|
|
}
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(hci_rx_pool, BT_BUF_RX_COUNT, BT_BUF_RX_SIZE, sizeof(struct acl_data),
|
|
hci_rx_pool_destroy);
|
|
#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */
|
|
|
|
struct net_buf *bt_buf_get_rx(enum bt_buf_type type, k_timeout_t timeout)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
__ASSERT(type == BT_BUF_EVT || type == BT_BUF_ACL_IN ||
|
|
type == BT_BUF_ISO_IN, "Invalid buffer type requested");
|
|
|
|
if (IS_ENABLED(CONFIG_BT_ISO_RX) && type == BT_BUF_ISO_IN) {
|
|
return bt_iso_get_rx(timeout);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
|
|
if (type == BT_BUF_EVT) {
|
|
buf = net_buf_alloc(&evt_pool, timeout);
|
|
} else {
|
|
buf = net_buf_alloc(&acl_in_pool, timeout);
|
|
}
|
|
#else
|
|
buf = net_buf_alloc(&hci_rx_pool, timeout);
|
|
#endif
|
|
|
|
if (buf) {
|
|
net_buf_reserve(buf, BT_BUF_RESERVE);
|
|
bt_buf_set_type(buf, type);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
void bt_buf_rx_freed_cb_set(bt_buf_rx_freed_cb_t cb)
|
|
{
|
|
k_sched_lock();
|
|
|
|
buf_rx_freed_cb = cb;
|
|
|
|
#if defined(CONFIG_BT_ISO_RX)
|
|
bt_iso_buf_rx_freed_cb_set(cb != NULL ? iso_rx_freed_cb : NULL);
|
|
#endif
|
|
|
|
k_sched_unlock();
|
|
}
|
|
|
|
struct net_buf *bt_buf_get_evt(uint8_t evt, bool discardable,
|
|
k_timeout_t timeout)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
switch (evt) {
|
|
#if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_ISO)
|
|
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
|
|
#endif /* CONFIG_BT_CONN || CONFIG_BT_ISO */
|
|
case BT_HCI_EVT_CMD_STATUS:
|
|
case BT_HCI_EVT_CMD_COMPLETE:
|
|
buf = net_buf_alloc(&sync_evt_pool, timeout);
|
|
break;
|
|
default:
|
|
if (discardable) {
|
|
buf = net_buf_alloc(&discardable_pool, timeout);
|
|
} 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;
|
|
}
|
|
|
|
#ifdef ZTEST_UNITTEST
|
|
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
|
|
struct net_buf_pool *bt_buf_get_evt_pool(void)
|
|
{
|
|
return &evt_pool;
|
|
}
|
|
|
|
struct net_buf_pool *bt_buf_get_acl_in_pool(void)
|
|
{
|
|
return &acl_in_pool;
|
|
}
|
|
#else
|
|
struct net_buf_pool *bt_buf_get_hci_rx_pool(void)
|
|
{
|
|
return &hci_rx_pool;
|
|
}
|
|
#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */
|
|
|
|
#if defined(CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT)
|
|
struct net_buf_pool *bt_buf_get_discardable_pool(void)
|
|
{
|
|
return &discardable_pool;
|
|
}
|
|
#endif /* CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT */
|
|
|
|
#if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_ISO)
|
|
struct net_buf_pool *bt_buf_get_num_complete_pool(void)
|
|
{
|
|
return &sync_evt_pool;
|
|
}
|
|
#endif /* CONFIG_BT_CONN || CONFIG_BT_ISO */
|
|
#endif /* ZTEST_UNITTEST */
|
|
|
|
struct net_buf *bt_buf_make_view(struct net_buf *view,
|
|
struct net_buf *parent,
|
|
size_t len,
|
|
struct bt_buf_view_meta *meta)
|
|
{
|
|
__ASSERT_NO_MSG(len);
|
|
__ASSERT_NO_MSG(view);
|
|
/* The whole point of this API is to allow prepending data. If the
|
|
* headroom is 0, that will not happen.
|
|
*/
|
|
__ASSERT_NO_MSG(net_buf_headroom(parent) > 0);
|
|
|
|
__ASSERT_NO_MSG(!bt_buf_has_view(parent));
|
|
|
|
LOG_DBG("make-view %p viewsize %zu meta %p", view, len, meta);
|
|
|
|
net_buf_simple_clone(&parent->b, &view->b);
|
|
view->size = net_buf_headroom(parent) + len;
|
|
view->len = len;
|
|
view->flags = NET_BUF_EXTERNAL_DATA;
|
|
|
|
/* we have a view, eat `len`'s worth of data from the parent */
|
|
(void)net_buf_pull(parent, len);
|
|
|
|
meta->backup.data = parent->data;
|
|
parent->data = NULL;
|
|
|
|
meta->backup.size = parent->size;
|
|
parent->size = 0;
|
|
|
|
/* The ref to `parent` is moved in by passing `parent` as argument. */
|
|
/* save backup & "clip" the buffer so the next `make_view` will fail */
|
|
meta->parent = parent;
|
|
parent = NULL;
|
|
|
|
return view;
|
|
}
|
|
|
|
void bt_buf_destroy_view(struct net_buf *view, struct bt_buf_view_meta *meta)
|
|
{
|
|
LOG_DBG("destroy-view %p meta %p", view, meta);
|
|
__ASSERT_NO_MSG(meta->parent);
|
|
|
|
/* "unclip" the parent buf */
|
|
meta->parent->data = meta->backup.data;
|
|
meta->parent->size = meta->backup.size;
|
|
|
|
net_buf_unref(meta->parent);
|
|
|
|
memset(meta, 0, sizeof(*meta));
|
|
net_buf_destroy(view);
|
|
}
|
|
|
|
bool bt_buf_has_view(const struct net_buf *parent)
|
|
{
|
|
/* This is enforced by `make_view`. see comment there. */
|
|
return parent->size == 0 && parent->data == NULL;
|
|
}
|