From 1834ecc5bfeb94620268eec6ef56c179d5444a8b Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Fri, 4 Apr 2025 16:15:38 +0200 Subject: [PATCH 1/7] nimble/eatt: New syscfg options for EATT Introduce BLE_EATT_AUTO_CONNECT syscfg option to allow manual EATT connection setup Introduce a configurable limit for maximum number of L2CAP channels per connection --- nimble/host/syscfg.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml index 873dcae4bf..4205a7a405 100644 --- a/nimble/host/syscfg.yml +++ b/nimble/host/syscfg.yml @@ -327,10 +327,21 @@ syscfg.defs: - BLE_GATT_NOTIFY_MULTIPLE - BLE_L2CAP_ENHANCED_COC - 'BLE_L2CAP_COC_MAX_NUM >= BLE_EATT_CHAN_NUM' + - 'BLE_EATT_CHAN_PER_CONN >= 1 if 1' BLE_EATT_MTU: description: > MTU used for EATT channels. value: 128 + BLE_EATT_AUTO_CONNECT: + description: > + Enable auto connect for EATT + value: (BLE_EATT_CHAN_NUM > 0) + restrictions: + - '(BLE_EATT_CHAN_NUM >= 1) if 1' + BLE_EATT_CHAN_PER_CONN: + description: > + Maximum number of supported EATT channels per connection + value: 0 # Supported server ATT commands. (0/1) BLE_ATT_SVR_FIND_INFO: From 151a9ab0cb250403bd5b66c8a80097795e331103 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Fri, 4 Apr 2025 16:46:21 +0200 Subject: [PATCH 2/7] nimble/eatt: Add option for manual EATT connection Provide flexibility in managing EATT. Introduce function that manually establishes EATT. Add function that iterates over specific connection EATT and returns the maximum number of channels left for use. --- nimble/host/include/host/ble_att.h | 12 ++++++ nimble/host/src/ble_eatt.c | 65 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/nimble/host/include/host/ble_att.h b/nimble/host/include/host/ble_att.h index 8323c9d764..c6bff22e67 100644 --- a/nimble/host/include/host/ble_att.h +++ b/nimble/host/include/host/ble_att.h @@ -355,6 +355,18 @@ uint16_t ble_att_preferred_mtu(void); */ int ble_att_set_preferred_mtu(uint16_t mtu); +/** + * Manually establish L2CAP Enhanced Connection + * + * @param conn_handle ACL connection handle + * @param chan_num Number of channels to establish + * + * @return 0 on success; + * NimBLE host core return code on unexpected + * error. + */ +int ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num); + #ifdef __cplusplus } #endif diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 047a087641..c705a17169 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -37,6 +37,8 @@ struct ble_eatt { uint16_t conn_handle; struct ble_l2cap_chan *chan; uint8_t client_op; + uint8_t chan_num; + uint8_t used_channels; /* Packet transmit queue */ STAILQ_HEAD(, os_mbuf_pkthdr) eatt_tx_q; @@ -135,6 +137,21 @@ ble_eatt_find(uint16_t conn_handle, uint16_t cid) return NULL; } +static size_t +ble_eatt_used_channels(uint16_t conn_handle) +{ + struct ble_eatt *eatt; + size_t used_channels = 0; + + SLIST_FOREACH(eatt, &g_ble_eatt_list, next) { + if (eatt->conn_handle == conn_handle) { + used_channels += eatt->used_channels; + } + } + + return used_channels; +} + static int ble_eatt_prepare_rx_sdu(struct ble_l2cap_chan *chan) { @@ -232,6 +249,7 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) return 0; } eatt->chan = event->connect.chan; + eatt->used_channels++; break; case BLE_L2CAP_EVENT_COC_DISCONNECTED: BLE_EATT_LOG_DEBUG("eatt: Disconnected \n"); @@ -506,6 +524,53 @@ ble_eatt_tx(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txom) return rc; } +int +ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num) +{ + struct ble_eatt *eatt; + struct ble_gap_conn_desc desc; + uint8_t free_channels; + int rc; + + rc = ble_gap_conn_find(conn_handle, &desc); + assert(rc == 0); + + eatt = ble_eatt_find_by_conn_handle(conn_handle); + if (!eatt) { + eatt = ble_eatt_alloc(); + if (!eatt) { + BLE_EATT_LOG_ERROR("ble_eatt_connect: Can't allocate EATT\n"); + return BLE_HS_ENOMEM; + } + } + + /* + return BLE_HS_EALREADY; + * Warn about exceeding the number + * of maximum per-conn EATT connections. + */ + if (chan_num == 0 || chan_num > MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN)) { + BLE_EATT_LOG_WARN("ble_eatt_connect | Invalid channel number\n"); + return BLE_HS_EREJECT; + } + + /* Get number of free channels for this connection */ + free_channels = MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN) - ble_eatt_used_channels(conn_handle); + + if (free_channels == 0) { + BLE_EATT_LOG_ERROR("ble_eatt_connect | No free channel slots\n"); + return BLE_HS_ENOMEM; + } + + eatt->conn_handle = conn_handle; + eatt->chan_num = (free_channels > chan_num) ? chan_num : free_channels; + + /* Setup EATT */ + ble_npl_eventq_put(ble_hs_evq_get(), &eatt->setup_ev); + + return 0; +} + static void ble_eatt_start(uint16_t conn_handle) { From 06fb0338a7f675c49db127abff1d73dc3ed10e8e Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Thu, 10 Apr 2025 12:18:39 +0200 Subject: [PATCH 3/7] nimble/eatt: Allow connect to use more channels Previously we established only one channel for EATT. Now application can define this number. --- nimble/host/src/ble_eatt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index c705a17169..02088dabcf 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -355,7 +355,8 @@ ble_eatt_setup_cb(struct ble_npl_event *ev) BLE_EATT_LOG_DEBUG("eatt: connecting eatt on conn_handle 0x%04x\n", eatt->conn_handle); rc = ble_l2cap_enhanced_connect(eatt->conn_handle, BLE_EATT_PSM, - MYNEWT_VAL(BLE_EATT_MTU), 1, &om, + MYNEWT_VAL(BLE_EATT_MTU), + eatt->chan_num, &om, ble_eatt_l2cap_event_fn, eatt); if (rc) { BLE_EATT_LOG_ERROR("eatt: Failed to connect EATT on conn_handle 0x%04x (status=%d)\n", From 57bc7c9eb2e5acef888081d19cea459257e3d58c Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Fri, 4 Apr 2025 18:11:38 +0200 Subject: [PATCH 4/7] nimble/eatt: Remove ble_eatt_start() from EATT Function is removed, as ble_eatt_connect() is designed to perform this role for both manual & auto-connect scenarios. --- nimble/host/src/ble_eatt.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 02088dabcf..fb13ae7a73 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -77,7 +77,6 @@ static struct ble_gap_event_listener ble_eatt_listener; static struct ble_npl_event g_read_sup_cl_feat_ev; static void ble_eatt_setup_cb(struct ble_npl_event *ev); -static void ble_eatt_start(uint16_t conn_handle); static struct ble_eatt * ble_eatt_find_not_busy(uint16_t conn_handle) @@ -376,7 +375,6 @@ ble_gatt_eatt_write_cl_cb(uint16_t conn_handle, return 0; } - ble_eatt_start(conn_handle); return 0; } @@ -572,33 +570,6 @@ ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num) return 0; } -static void -ble_eatt_start(uint16_t conn_handle) -{ - struct ble_gap_conn_desc desc; - struct ble_eatt *eatt; - int rc; - - rc = ble_gap_conn_find(conn_handle, &desc); - assert(rc == 0); - if (desc.role != BLE_GAP_ROLE_MASTER) { - /* Let master to create ecoc. - * TODO: Slave could setup after some timeout - */ - return; - } - - eatt = ble_eatt_alloc(); - if (!eatt) { - return; - } - - eatt->conn_handle = conn_handle; - - /* Setup EATT */ - ble_npl_eventq_put(ble_hs_evq_get(), &eatt->setup_ev); -} - void ble_eatt_init(ble_eatt_att_rx_fn att_rx_cb) { From 2642c9373ce6b9b983a7833650c4ce9bc78f1e60 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Wed, 23 Apr 2025 18:11:22 +0200 Subject: [PATCH 5/7] nimble/eatt: Implement new auto-connect handling Implement new way for auto-connect. Add initial delay to avoid collision. --- nimble/host/src/ble_eatt.c | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index fb13ae7a73..8493e0631a 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -45,6 +45,10 @@ struct ble_eatt { struct ble_npl_event setup_ev; struct ble_npl_event wakeup_ev; + +#if MYNEWT_VAL(BLE_EATT_AUTO_CONNECT) + struct os_callout auto_conn_delay; +#endif }; SLIST_HEAD(ble_eatt_list, ble_eatt); @@ -194,6 +198,17 @@ ble_eatt_wakeup_cb(struct ble_npl_event *ev) } } +#if (MYNEWT_VAL(BLE_EATT_AUTO_CONNECT)) +void +ble_eatt_auto_conn_cb(struct os_event *ev) +{ + struct os_callout *co = (struct os_callout *)ev; + struct ble_eatt *eatt = CONTAINER_OF(co, struct ble_eatt, auto_conn_delay); + + ble_eatt_connect(eatt->conn_handle, MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN)); +} +#endif + static struct ble_eatt * ble_eatt_alloc(void) { @@ -212,6 +227,11 @@ ble_eatt_alloc(void) eatt->client_op = 0; STAILQ_INIT(&eatt->eatt_tx_q); + +#if (MYNEWT_VAL(BLE_EATT_AUTO_CONNECT)) + os_callout_init(&eatt->auto_conn_delay, os_eventq_dflt_get(), + ble_eatt_auto_conn_cb, NULL); +#endif ble_npl_event_init(&eatt->setup_ev, ble_eatt_setup_cb, eatt); ble_npl_event_init(&eatt->wakeup_ev, ble_eatt_wakeup_cb, eatt); @@ -375,6 +395,50 @@ ble_gatt_eatt_write_cl_cb(uint16_t conn_handle, return 0; } +#if (MYNEWT_VAL(BLE_EATT_AUTO_CONNECT)) + struct ble_gap_conn_desc desc; + struct ble_eatt *eatt; + uint8_t delay_rand; + uint8_t delay; + int rc; + + rc = ble_gap_conn_find(conn_handle, &desc); + assert(rc == 0); + + eatt = ble_eatt_find_by_conn_handle(conn_handle); + if (eatt && eatt->used_channels != 0) { + BLE_EATT_LOG_DEBUG("eatt: EATT channels already established" + " for this connection\n"); + return 0; + } else { + eatt = ble_eatt_alloc(); + if (!eatt) { + return 0; + } + } + + /* Add initial delay as peripheral to avoid collision. + * Central is allowed to connect instantly. + * If there is at least one active connection - ommitt. + */ + if (desc.role == BLE_GAP_ROLE_SLAVE && eatt->used_channels == 0) { + rc = ble_hs_hci_rand(&delay_rand, 1); + if (rc != 0) { + return rc; + } + + delay = (delay_rand % 5) + 2 * (desc.conn_latency + 1) * desc.conn_itvl; + eatt->conn_handle = conn_handle; + os_callout_reset(&eatt->auto_conn_delay, OS_TICKS_PER_SEC / 1000 * delay); + } else if (desc.role == BLE_GAP_ROLE_MASTER && eatt->used_channels == 0) { + rc = ble_eatt_connect(conn_handle, MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN)); + if (rc) { + BLE_EATT_LOG_DEBUG("eatt: EATT connect failed for conn_handle: %d\n", + conn_handle); + } + } +#endif + return 0; } From 011d33882e36ac571a9cc3a47abae7ead5b88e44 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Wed, 23 Apr 2025 18:57:12 +0200 Subject: [PATCH 6/7] nimble/eatt: Handle multiple channels in event handler Add support for multiple-channels in event function. Refactor logging within event handler function. Introduce accepted channels field in eatt structure. --- nimble/host/src/ble_eatt.c | 73 ++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 8493e0631a..46453d1709 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -39,6 +39,7 @@ struct ble_eatt { uint8_t client_op; uint8_t chan_num; uint8_t used_channels; + uint8_t accept_channels; /* Packet transmit queue */ STAILQ_HEAD(, os_mbuf_pkthdr) eatt_tx_q; @@ -225,6 +226,8 @@ ble_eatt_alloc(void) eatt->conn_handle = BLE_HS_CONN_HANDLE_NONE; eatt->chan = NULL; eatt->client_op = 0; + eatt->accept_channels = 0; + eatt->used_channels = 0; STAILQ_INIT(&eatt->eatt_tx_q); @@ -257,47 +260,89 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) { struct ble_eatt *eatt = arg; struct ble_gap_conn_desc desc; + uint8_t free_channels; uint8_t opcode; int rc; switch (event->type) { case BLE_L2CAP_EVENT_COC_CONNECTED: - BLE_EATT_LOG_DEBUG("eatt: Connected \n"); + BLE_EATT_LOG_DEBUG("eatt: Connected event | conn_handle: %d |" + " scid: %d | dcid: %d | status: %d\n", + event->connect.conn_handle, event->connect.chan->scid, + event->connect.chan->dcid, event->connect.status); + if (event->connect.status) { ble_eatt_free(eatt); return 0; } eatt->chan = event->connect.chan; + eatt->conn_handle = event->connect.conn_handle; + eatt->used_channels++; + BLE_EATT_LOG_DEBUG("eatt: Channels already used for this connection %d\n", + eatt->used_channels); break; case BLE_L2CAP_EVENT_COC_DISCONNECTED: - BLE_EATT_LOG_DEBUG("eatt: Disconnected \n"); - ble_eatt_free(eatt); + BLE_EATT_LOG_DEBUG("eatt: Disconnected event | conn_handle: %d | " + "scid: %d | dcid: %d\n", event->disconnect.conn_handle, + event->disconnect.chan->scid, + event->disconnect.chan->dcid); + + eatt = ble_eatt_find_by_conn_handle(event->disconnect.conn_handle); + if (!eatt) { + BLE_EATT_LOG_ERROR("eatt: Disconnected event | No EATT for conn_handle: %d\n", + event->disconnect.conn_handle); + return 0; + } + + /* Decrease number of channels on disconnect event + * If no channels are left - free the resources + */ + eatt->used_channels--; + eatt->accept_channels--; + + if (eatt->used_channels == 0) { + ble_eatt_free(eatt); + } break; case BLE_L2CAP_EVENT_COC_ACCEPT: - BLE_EATT_LOG_DEBUG("eatt: Accept request\n"); + /* Lookup if EATT already exsits for this connection */ eatt = ble_eatt_find_by_conn_handle(event->accept.conn_handle); - if (eatt) { - /* For now we accept only one additional coc channel per ACL - * TODO: improve it - */ - return BLE_HS_ENOMEM; - } - - eatt = ble_eatt_alloc(); if (!eatt) { - return BLE_HS_ENOMEM; + eatt = ble_eatt_alloc(); + if (!eatt) { + BLE_EATT_LOG_DEBUG("eatt: Can't allocate EATT for conn_handle: %d\n", + event->accept.conn_handle); + return 0; + } + } else { + free_channels = MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN) - ble_eatt_used_channels(eatt->conn_handle); + + if (free_channels == 0) { + BLE_EATT_LOG_ERROR("eatt: Accept event | No free channels for " + "conn_handle: %d\n", event->accept.conn_handle); + return BLE_HS_ENOMEM; + } } eatt->conn_handle = event->accept.conn_handle; event->accept.chan->cb_arg = eatt; + /* Do not increase number of used channels here. + * Only do it on succesfull connected event & + * while initiating connection. + * Instead increase accept channels - yet to be connected */ + eatt->accept_channels++; + rc = ble_eatt_prepare_rx_sdu(event->accept.chan); if (rc) { ble_eatt_free(eatt); return rc; } - + BLE_EATT_LOG_DEBUG("eatt | Accept event | conn_handle: %d" + "| scid: %d | dcid: %d\n", + event->accept.conn_handle, event->accept.chan->scid, + event->accept.chan->dcid); break; case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: ble_npl_eventq_put(ble_hs_evq_get(), &eatt->wakeup_ev); From 60e1fb48966197995f11b0a4541ed6095e6b13b5 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Thu, 24 Apr 2025 12:09:50 +0200 Subject: [PATCH 7/7] nimble/eatt: Implement collision mitigation handling Introduce operations for collision mitigation handling. Add specification-defined delay for retrying connection. --- nimble/host/src/ble_eatt.c | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 46453d1709..961a3d3350 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -40,10 +40,13 @@ struct ble_eatt { uint8_t chan_num; uint8_t used_channels; uint8_t accept_channels; + uint8_t collision_ctrl; + uint8_t retry_count; /* Packet transmit queue */ STAILQ_HEAD(, os_mbuf_pkthdr) eatt_tx_q; + struct os_callout collision_co; struct ble_npl_event setup_ev; struct ble_npl_event wakeup_ev; @@ -199,6 +202,17 @@ ble_eatt_wakeup_cb(struct ble_npl_event *ev) } } +static void +ble_eatt_collision_ev(struct os_event *ev) +{ + struct os_callout *co = (struct os_callout *)ev; + struct ble_eatt *eatt = CONTAINER_OF(co, struct ble_eatt, collision_co); + + if (eatt->retry_count < 2) { + ble_eatt_connect(eatt->conn_handle, eatt->chan_num); + } +} + #if (MYNEWT_VAL(BLE_EATT_AUTO_CONNECT)) void ble_eatt_auto_conn_cb(struct os_event *ev) @@ -231,6 +245,9 @@ ble_eatt_alloc(void) STAILQ_INIT(&eatt->eatt_tx_q); + os_callout_init(&eatt->collision_co, os_eventq_dflt_get(), + ble_eatt_collision_ev, NULL); + #if (MYNEWT_VAL(BLE_EATT_AUTO_CONNECT)) os_callout_init(&eatt->auto_conn_delay, os_eventq_dflt_get(), ble_eatt_auto_conn_cb, NULL); @@ -261,6 +278,8 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) struct ble_eatt *eatt = arg; struct ble_gap_conn_desc desc; uint8_t free_channels; + uint8_t collision_delay; + uint8_t collision_rand_time; uint8_t opcode; int rc; @@ -271,13 +290,39 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) event->connect.conn_handle, event->connect.chan->scid, event->connect.chan->dcid, event->connect.status); + if (event->connect.status == BLE_HS_ENOMEM && eatt->collision_ctrl) { + BLE_EATT_LOG_DEBUG("eatt: Connect collision handle: %d\n", + event->connect.conn_handle); + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + + rc = ble_hs_hci_rand(&collision_rand_time, 1); + if (rc != 0) { + return rc; + } + + collision_delay = (collision_rand_time % 5) + 2 * (desc.conn_latency + 1) * desc.conn_itvl; + + os_callout_reset(&eatt->collision_co, collision_delay); + + eatt->retry_count++; + eatt->used_channels--; + + return 0; + } + if (event->connect.status) { - ble_eatt_free(eatt); + eatt->used_channels--; return 0; } eatt->chan = event->connect.chan; eatt->conn_handle = event->connect.conn_handle; + /* Delete collision callout on successful connection */ + os_callout_stop(&eatt->collision_co); + eatt->collision_ctrl = false; + eatt->used_channels++; BLE_EATT_LOG_DEBUG("eatt: Channels already used for this connection %d\n", eatt->used_channels); @@ -319,6 +364,7 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) free_channels = MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN) - ble_eatt_used_channels(eatt->conn_handle); if (free_channels == 0) { + eatt->collision_ctrl = true; BLE_EATT_LOG_ERROR("eatt: Accept event | No free channels for " "conn_handle: %d\n", event->accept.conn_handle); return BLE_HS_ENOMEM; @@ -418,6 +464,8 @@ ble_eatt_setup_cb(struct ble_npl_event *ev) BLE_EATT_LOG_DEBUG("eatt: connecting eatt on conn_handle 0x%04x\n", eatt->conn_handle); + eatt->used_channels += eatt->chan_num; + rc = ble_l2cap_enhanced_connect(eatt->conn_handle, BLE_EATT_PSM, MYNEWT_VAL(BLE_EATT_MTU), eatt->chan_num, &om, @@ -653,7 +701,18 @@ ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num) } /* + * 5.3 Vol 3, Part G, Sec. 5.4 L2CAP collision mitigation + * Peripheral shall wait some time before retrying connection. + * Central may reconnect without any delay. + * To reconnect user has to call ble_eatt_connect again. + */ + if (desc.role == BLE_GAP_ROLE_SLAVE && os_callout_queued(&eatt->collision_co)) { + BLE_EATT_LOG_WARN("ble_eatt_connect: Connection collision for handle %d\n", + conn_handle); return BLE_HS_EALREADY; + } + + /* * Warn about exceeding the number * of maximum per-conn EATT connections. */