Skip to content

Commit 341158c

Browse files
jimmodpgeorge
authored andcommitted
extmod/nimble: Add "memory stalling" mechanism for l2cap_send.
When l2cap_send detects that the sys mempool is running low (used to store the outgoing HCI payloads), it will report stalled back to the application, and then only unstall once these HCI payloads have been sent. This prevents a situation where a remote receiver with very large MTU can cause NimBLE to queue up more than MYNEWT_VAL_MSYS_1_BLOCK_COUNT (i.e. 12) payloads, causing further attempts to send to fail with ENOMEM (even though the channel is not stalled and we have room in the channel mbufs). The regular credit/stall flow control is not effective here because the receiver's MTU is large enough that it will not activate (i.e. there are lots of credits available). Thresholds of 1/2 (stall) and 1/4 (unstall) chosen to allow headroom for other payloads (e.g. notifications) and that when a regular stall occurs it might keep sending (and creating more payloads) in the background.
1 parent edfb5d5 commit 341158c

File tree

1 file changed

+42
-6
lines changed

1 file changed

+42
-6
lines changed

extmod/nimble/modbluetooth_nimble.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,16 @@ int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) {
13921392

13931393
#endif // MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
13941394

1395+
#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS
1396+
STATIC void unstall_l2cap_channel(void);
1397+
#endif
1398+
13951399
void mp_bluetooth_nimble_sent_hci_packet(void) {
1400+
#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS
1401+
if (os_msys_num_free() >= os_msys_count() * 3 / 4) {
1402+
unstall_l2cap_channel();
1403+
}
1404+
#endif
13961405
}
13971406

13981407
#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS
@@ -1416,6 +1425,7 @@ typedef struct _mp_bluetooth_nimble_l2cap_channel_t {
14161425
struct os_mempool sdu_mempool;
14171426
struct os_mbuf *rx_pending;
14181427
bool irq_in_progress;
1428+
bool mem_stalled;
14191429
uint16_t mtu;
14201430
os_membuf_t sdu_mem[];
14211431
} mp_bluetooth_nimble_l2cap_channel_t;
@@ -1433,6 +1443,19 @@ STATIC void destroy_l2cap_channel() {
14331443
}
14341444
}
14351445

1446+
STATIC void unstall_l2cap_channel(void) {
1447+
// Whenever we send an HCI packet and the sys mempool is now less than 1/4 full,
1448+
// we can unstall the L2CAP channel if it was marked as "mem_stalled" by
1449+
// mp_bluetooth_l2cap_send. (This happens if the pool is half-empty).
1450+
mp_bluetooth_nimble_l2cap_channel_t *chan = MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_chan;
1451+
if (!chan || !chan->mem_stalled) {
1452+
return;
1453+
}
1454+
DEBUG_printf("unstall_l2cap_channel: count %d, free: %d\n", os_msys_count(), os_msys_num_free());
1455+
chan->mem_stalled = false;
1456+
mp_bluetooth_on_l2cap_send_ready(chan->chan->conn_handle, chan->chan->scid, 0);
1457+
}
1458+
14361459
STATIC int l2cap_channel_event(struct ble_l2cap_event *event, void *arg) {
14371460
DEBUG_printf("l2cap_channel_event: type=%d\n", event->type);
14381461
mp_bluetooth_nimble_l2cap_channel_t *chan = (mp_bluetooth_nimble_l2cap_channel_t *)arg;
@@ -1528,9 +1551,13 @@ STATIC int l2cap_channel_event(struct ble_l2cap_event *event, void *arg) {
15281551
}
15291552
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: {
15301553
DEBUG_printf("l2cap_channel_event: tx_unstalled: conn_handle=%d status=%d\n", event->tx_unstalled.conn_handle, event->tx_unstalled.status);
1531-
ble_l2cap_get_chan_info(event->receive.chan, &info);
1532-
// Map status to {0,1} (i.e. "sent everything", or "partial send").
1533-
mp_bluetooth_on_l2cap_send_ready(event->tx_unstalled.conn_handle, info.scid, event->tx_unstalled.status == 0 ? 0 : 1);
1554+
assert(event->tx_unstalled.conn_handle == chan->chan->conn_handle);
1555+
// Don't unstall if we're still waiting for room in the sys pool.
1556+
if (!chan->mem_stalled) {
1557+
ble_l2cap_get_chan_info(event->receive.chan, &info);
1558+
// Map status to {0,1} (i.e. "sent everything", or "partial send").
1559+
mp_bluetooth_on_l2cap_send_ready(event->tx_unstalled.conn_handle, info.scid, event->tx_unstalled.status == 0 ? 0 : 1);
1560+
}
15341561
break;
15351562
}
15361563
case BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED: {
@@ -1678,26 +1705,35 @@ int mp_bluetooth_l2cap_send(uint16_t conn_handle, uint16_t cid, const uint8_t *b
16781705
return MP_ENOMEM;
16791706
}
16801707

1708+
*stalled = false;
1709+
16811710
err = ble_l2cap_send(chan->chan, sdu_tx);
16821711
if (err == BLE_HS_ESTALLED) {
16831712
// Stalled means that this one will still send but any future ones
16841713
// will fail until we receive an unstalled event.
1714+
DEBUG_printf("mp_bluetooth_l2cap_send: credit stall\n");
16851715
*stalled = true;
16861716
err = 0;
16871717
} else {
16881718
if (err) {
16891719
// Anything except stalled means it won't attempt to send,
16901720
// so free the mbuf (we're failing the op entirely).
16911721
os_mbuf_free_chain(sdu_tx);
1692-
} else {
1693-
*stalled = false;
16941722
}
16951723
}
16961724

1725+
if (os_msys_num_free() <= os_msys_count() / 2) {
1726+
// If the sys mempool is less than half-full, then back off sending more
1727+
// on this channel.
1728+
DEBUG_printf("mp_bluetooth_l2cap_send: forcing mem stall: count %d, free: %d\n", os_msys_count(), os_msys_num_free());
1729+
chan->mem_stalled = true;
1730+
*stalled = true;
1731+
}
1732+
16971733
// Sometimes we see what looks like BLE_HS_EAGAIN (but it's actually
16981734
// OS_ENOMEM in disguise). Fixed in NimBLE v1.4.
16991735
if (err == OS_ENOMEM) {
1700-
return MP_ENOMEM;
1736+
err = BLE_HS_ENOMEM;
17011737
}
17021738

17031739
// Other error codes such as BLE_HS_EBUSY (we're stalled) or BLE_HS_EBADDATA (bigger than MTU).

0 commit comments

Comments
 (0)