Bluetooth: host: Introduce "view" buffer concept

Instead of allocating segments/fragments and copying data into them, we
allocate segments as "views" (or slices) into the original buffer.

The view also gives access to the headroom of the original buffer, allowing
lower layers to push their headers.

We choose not to allow multiple views into the same buffer as the headroom
of a view would overlap with the data of the previous view.

We mark a buffer as locked (or "in-view") by temporarily setting its
headroom to zero. This effectively stops create_view because the requested
headroom is not available.

Each layer that does some kind of fragmentation and wants to use views for
that needs to maintain a buffer pool (bufsize 0, count = max views) and a
metadata array (size = max views) for the view mechanism to work.

Maximum number of views: number of parallel buffers from the upper layer,
e.g. number of L2CAP channels for L2CAP segmentation or number of ACL
connections for HCI fragmentation.

Reason for the change:
1. prevent deadlocks or (ATT/SMP) requests timing out
2. save time (zero-copy)
3. save memory (gets rid of frag pools)

L2CAP CoC: would either allocate from the `alloc_seg` application callback,
or worse _steal_ from the same pool, or allocate from the global ACL pool.

Conn/HCI: would either allocate from `frag_pool` or the global ACL pool.

Signed-off-by: Jonathan Rico <jonathan.rico@nordicsemi.no>
Co-authored-by: Aleksander Wasaznik <aleksander.wasaznik@nordicsemi.no>
This commit is contained in:
Jonathan Rico 2023-07-18 10:39:44 +02:00 committed by Alberto Escolar
commit 1c8cae30a8
9 changed files with 408 additions and 212 deletions

View file

@ -20,7 +20,6 @@ CREATE_FLAG(flag_l2cap_connected);
#define L2CAP_CHANS NUM_PERIPHERALS
#define SDU_NUM 20
#define SDU_LEN 3000
#define NUM_SEGMENTS 100
#define RESCHEDULE_DELAY K_MSEC(100)
static void sdu_destroy(struct net_buf *buf)
@ -30,13 +29,6 @@ static void sdu_destroy(struct net_buf *buf)
net_buf_destroy(buf);
}
static void segment_destroy(struct net_buf *buf)
{
LOG_DBG("%p", buf);
net_buf_destroy(buf);
}
static void rx_destroy(struct net_buf *buf)
{
LOG_DBG("%p", buf);
@ -49,11 +41,6 @@ NET_BUF_POOL_DEFINE(sdu_tx_pool,
CONFIG_BT_MAX_CONN, BT_L2CAP_SDU_BUF_SIZE(SDU_LEN),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, sdu_destroy);
NET_BUF_POOL_DEFINE(segment_pool,
/* MTU + 4 l2cap hdr + 4 ACL hdr */
NUM_SEGMENTS, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, segment_destroy);
/* Only one SDU per link will be received at a time */
NET_BUF_POOL_DEFINE(sdu_rx_pool,
CONFIG_BT_MAX_CONN, BT_L2CAP_SDU_BUF_SIZE(SDU_LEN),
@ -62,7 +49,6 @@ NET_BUF_POOL_DEFINE(sdu_rx_pool,
static uint8_t tx_data[SDU_LEN];
static uint16_t rx_cnt;
static uint8_t disconnect_counter;
static uint32_t max_seg_allocated;
struct test_ctx {
struct k_work_delayable work_item;
@ -113,19 +99,6 @@ int l2cap_chan_send(struct bt_l2cap_chan *chan, uint8_t *data, size_t len)
return ret;
}
struct net_buf *alloc_seg_cb(struct bt_l2cap_chan *chan)
{
struct net_buf *buf = net_buf_alloc(&segment_pool, K_NO_WAIT);
if ((NUM_SEGMENTS - segment_pool.avail_count) > max_seg_allocated) {
max_seg_allocated++;
}
ASSERT(buf, "Ran out of segment buffers");
return buf;
}
struct net_buf *alloc_buf_cb(struct bt_l2cap_chan *chan)
{
return net_buf_alloc(&sdu_rx_pool, K_NO_WAIT);
@ -163,7 +136,19 @@ int recv_cb(struct bt_l2cap_chan *chan, struct net_buf *buf)
rx_cnt++;
/* Verify SDU data matches TX'd data. */
ASSERT(memcmp(buf->data, tx_data, buf->len) == 0, "RX data doesn't match TX");
int pos = memcmp(buf->data, tx_data, buf->len);
if (pos != 0) {
LOG_ERR("RX data doesn't match TX: pos %d", pos);
LOG_HEXDUMP_ERR(buf->data, buf->len, "RX data");
LOG_HEXDUMP_INF(tx_data, buf->len, "TX data");
for (uint16_t p = 0; p < buf->len; p++) {
__ASSERT(buf->data[p] == tx_data[p],
"Failed rx[%d]=%x != expect[%d]=%x",
p, buf->data[p], p, tx_data[p]);
}
}
return 0;
}
@ -192,7 +177,6 @@ static struct bt_l2cap_chan_ops ops = {
.connected = l2cap_chan_connected_cb,
.disconnected = l2cap_chan_disconnected_cb,
.alloc_buf = alloc_buf_cb,
.alloc_seg = alloc_seg_cb,
.recv = recv_cb,
.sent = sent_cb,
};
@ -474,8 +458,6 @@ static void test_central_main(void)
}
LOG_DBG("All peripherals disconnected.");
LOG_INF("Max segment pool usage: %u bufs", max_seg_allocated);
PASS("L2CAP STRESS Central passed\n");
}