From 11d53b610b1f983d17c39943f105a844ba57fdb5 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Fri, 4 Apr 2025 16:15:38 +0200 Subject: [PATCH 1/4] 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 8dcac02b30bab33bf9c0f5e1cb034776580eb91c Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Wed, 25 Jun 2025 12:49:07 +0200 Subject: [PATCH 2/4] eatt: Inital support for multiple EATT channels per connection Extend ble_eatt struct with channel array and channel count. Add helper functions for channel indexing and lookup. Update ble_eatt_find to search all channels for matching CID. Add channel allocation logic on connect event. Replace direct channel access with indexed lookup. Refactor ble_eatt_tx and L2CAP ev-handler to support multiple channels. --- nimble/host/src/ble_eatt.c | 144 ++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 18 deletions(-) diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 047a087641..69a031f168 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -35,8 +35,9 @@ struct ble_eatt { SLIST_ENTRY(ble_eatt) next; uint16_t conn_handle; - struct ble_l2cap_chan *chan; + struct ble_l2cap_chan *chan[MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN)]; uint8_t client_op; + uint8_t channels_to_connect; /* Packet transmit queue */ STAILQ_HEAD(, os_mbuf_pkthdr) eatt_tx_q; @@ -122,16 +123,79 @@ ble_eatt_find_by_conn_handle_and_busy_op(uint16_t conn_handle, uint8_t op) static struct ble_eatt * ble_eatt_find(uint16_t conn_handle, uint16_t cid) { + int i; struct ble_eatt *eatt; SLIST_FOREACH(eatt, &g_ble_eatt_list, next) { - if ((eatt->conn_handle == conn_handle) && - (eatt->chan) && - (eatt->chan->scid == cid)) { + if (eatt->conn_handle == conn_handle) { + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] && eatt->chan[i]->scid == cid) { return eatt; } } + } + } + return NULL; +} + +static size_t +ble_eatt_used_channels(uint16_t conn_handle) +{ + int i; + size_t used_chans = 0; + struct ble_eatt *eatt; + + eatt = ble_eatt_find_by_conn_handle(conn_handle); + if (!eatt) { + BLE_EATT_LOG_ERROR( + "ble_eatt_used_channels: No eatt for given conn handle\n"); + } + + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] != NULL) { + used_chans++; + } + } + + return used_chans; +} + +static int +ble_eatt_free_channel_idx(struct ble_eatt *eatt) +{ + int i; + + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] == NULL) + return i; + } + + return -1; +} + +static struct ble_l2cap_chan * +ble_eatt_find_chan(struct ble_eatt *eatt, struct ble_l2cap_chan *chan) +{ + int i; + + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] == chan) { + return eatt->chan[i]; + } + } + return NULL; +} +static struct ble_l2cap_chan * +ble_eatt_channel_find_by_cid(struct ble_eatt *eatt, uint16_t cid) +{ + int i; + + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] && eatt->chan[i]->scid == cid) { + return eatt->chan[i]; + } + } return NULL; } @@ -160,6 +224,7 @@ ble_eatt_prepare_rx_sdu(struct ble_l2cap_chan *chan) static void ble_eatt_wakeup_cb(struct ble_npl_event *ev) { + int i; struct ble_eatt *eatt; struct os_mbuf *txom; struct os_mbuf_pkthdr *omp; @@ -173,14 +238,24 @@ ble_eatt_wakeup_cb(struct ble_npl_event *ev) STAILQ_REMOVE_HEAD(&eatt->eatt_tx_q, omp_next); txom = OS_MBUF_PKTHDR_TO_MBUF(omp); - ble_l2cap_get_chan_info(eatt->chan, &info); + + /* Look for the already established channel. + * Send necessary data, then break. + */ + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] != NULL) { + ble_l2cap_get_chan_info(eatt->chan[i], &info); ble_eatt_tx(eatt->conn_handle, info.dcid, txom); + break; + } + } } } static struct ble_eatt * ble_eatt_alloc(void) { + int i; struct ble_eatt *eatt; eatt = os_memblock_get(&ble_eatt_conn_pool); @@ -192,7 +267,9 @@ ble_eatt_alloc(void) SLIST_INSERT_HEAD(&g_ble_eatt_list, eatt, next); eatt->conn_handle = BLE_HS_CONN_HANDLE_NONE; - eatt->chan = NULL; + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + eatt->chan[i] = NULL; + } eatt->client_op = 0; STAILQ_INIT(&eatt->eatt_tx_q); @@ -221,7 +298,9 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) { struct ble_eatt *eatt = arg; struct ble_gap_conn_desc desc; + struct ble_l2cap_chan *channel; uint8_t opcode; + int chan_idx; int rc; switch (event->type) { @@ -231,7 +310,15 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) ble_eatt_free(eatt); return 0; } - eatt->chan = event->connect.chan; + chan_idx = ble_eatt_free_channel_idx(eatt); + if (chan_idx > 0) { + eatt->chan[chan_idx] = event->connect.chan; + } else { + BLE_EATT_LOG_ERROR("eatt: Connect event: No free channels\n"); + ble_eatt_free(eatt); + return 0; + } + break; case BLE_L2CAP_EVENT_COC_DISCONNECTED: BLE_EATT_LOG_DEBUG("eatt: Disconnected \n"); @@ -266,7 +353,11 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) ble_npl_eventq_put(ble_hs_evq_get(), &eatt->wakeup_ev); break; case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: - assert(eatt->chan == event->receive.chan); + channel = ble_eatt_find_chan(eatt, event->receive.chan); + if (channel == NULL) { + BLE_EATT_LOG_ERROR("eatt: receive: Invalid channel\n"); + ble_eatt_free(eatt); + } opcode = event->receive.sdu_rx->om_data[0]; if (ble_eatt_supported_rsp(opcode)) { ble_npl_eventq_put(ble_hs_evq_get(), &eatt->wakeup_ev); @@ -279,7 +370,7 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) * • The Signed Write Without Response sub-procedure shall only be * supported on the LE Fixed Channel Unenhanced ATT bearer. */ - ble_l2cap_disconnect(eatt->chan); + ble_l2cap_disconnect(channel); return BLE_HS_EREJECT; } @@ -293,11 +384,12 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) * encryption. */ if (!desc.sec_state.encrypted) { - ble_l2cap_disconnect(eatt->chan); + ble_l2cap_disconnect(channel); return BLE_HS_EREJECT; } - ble_eatt_att_rx_cb(event->receive.conn_handle, eatt->chan->scid, &event->receive.sdu_rx); + ble_eatt_att_rx_cb(event->receive.conn_handle, channel->scid, + &event->receive.sdu_rx); if (event->receive.sdu_rx) { os_mbuf_free_chain(event->receive.sdu_rx); event->receive.sdu_rx = NULL; @@ -306,7 +398,7 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) /* Receiving L2CAP data is no longer possible, terminate connection */ rc = ble_eatt_prepare_rx_sdu(event->receive.chan); if (rc) { - ble_l2cap_disconnect(eatt->chan); + ble_l2cap_disconnect(channel); return BLE_HS_ENOMEM; } break; @@ -336,9 +428,10 @@ 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, - ble_eatt_l2cap_event_fn, eatt); + rc = ble_l2cap_enhanced_connect( + eatt->conn_handle, BLE_EATT_PSM, MYNEWT_VAL(BLE_EATT_MTU), + eatt->channels_to_connect, &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", eatt->conn_handle, rc); @@ -441,6 +534,7 @@ ble_eatt_gap_event(struct ble_gap_event *event, void *arg) uint16_t ble_eatt_get_available_chan_cid(uint16_t conn_handle, uint8_t op) { + int i; struct ble_eatt * eatt; eatt = ble_eatt_find_not_busy(conn_handle); @@ -450,7 +544,13 @@ ble_eatt_get_available_chan_cid(uint16_t conn_handle, uint8_t op) eatt->client_op = op; - return eatt->chan->scid; + for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { + if (eatt->chan[i] != NULL) { + return eatt->chan[i]->scid; + } + } + + return BLE_L2CAP_CID_ATT; } void @@ -472,17 +572,25 @@ int ble_eatt_tx(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txom) { struct ble_eatt *eatt; + static struct ble_l2cap_chan *channel; int rc; BLE_EATT_LOG_DEBUG("eatt: %s, size %d ", __func__, OS_MBUF_PKTLEN(txom)); eatt = ble_eatt_find(conn_handle, cid); - if (!eatt || !eatt->chan) { + if (!eatt) { + BLE_EATT_LOG_ERROR("Eatt not available"); + rc = BLE_HS_ENOENT; + goto error; + } + + channel = ble_eatt_channel_find_by_cid(eatt, cid); + if (!channel) { BLE_EATT_LOG_ERROR("Eatt not available"); rc = BLE_HS_ENOENT; goto error; } - rc = ble_l2cap_send(eatt->chan, txom); + rc = ble_l2cap_send(channel, txom); if (rc == 0) { goto done; } From 545db0e984aa14fcf5e6280f0b469511e4a58465 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Wed, 25 Jun 2025 12:57:23 +0200 Subject: [PATCH 3/4] eatt: Add support for manual EATT connection Introduce ble_eatt_connect() API to allow manual setup EATT. This change enables explicit control over EATT channel creation. The function validates the connection handle and requested channel count, supports requesting all channels by passing 0 Add declaration of ble_eatt_connect() to the ble_att.h header. --- nimble/host/include/host/ble_att.h | 12 +++++++ nimble/host/src/ble_eatt.c | 54 +++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 4 deletions(-) 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 69a031f168..35cb302eef 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -130,9 +130,9 @@ ble_eatt_find(uint16_t conn_handle, uint16_t cid) if (eatt->conn_handle == conn_handle) { for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { if (eatt->chan[i] && eatt->chan[i]->scid == cid) { - return eatt; - } - } + return eatt; + } + } } } return NULL; @@ -245,7 +245,7 @@ ble_eatt_wakeup_cb(struct ble_npl_event *ev) for (i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); i++) { if (eatt->chan[i] != NULL) { ble_l2cap_get_chan_info(eatt->chan[i], &info); - ble_eatt_tx(eatt->conn_handle, info.dcid, txom); + ble_eatt_tx(eatt->conn_handle, info.dcid, txom); break; } } @@ -641,6 +641,52 @@ ble_eatt_start(uint16_t conn_handle) ble_npl_eventq_put(ble_hs_evq_get(), &eatt->setup_ev); } +int +ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num) +{ + int rc; + uint8_t free_channels; + struct ble_eatt *eatt; + struct ble_gap_conn_desc desc; + + 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; + } + eatt->conn_handle = conn_handle; + } + + if (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); + + /* Connect all channels if invoked with 0 & all channels are free */ + if (chan_num == 0 && free_channels == MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN)) { + eatt->channels_to_connect = MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); + } else if (chan_num < free_channels) { + eatt->channels_to_connect = chan_num; + } else { + BLE_EATT_LOG_ERROR("ble_eatt_connect: Invalid channel number\n"); + return BLE_HS_ENOMEM; + } + + /* Setup EATT */ + ble_npl_eventq_put(ble_hs_evq_get(), &eatt->setup_ev); + + return 0; +} + void ble_eatt_init(ble_eatt_att_rx_fn att_rx_cb) { From 0fe92a447b6eeaac8e01ab232864dc987cbcf87d Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Wed, 25 Jun 2025 12:38:50 +0200 Subject: [PATCH 4/4] btshell: Add EATT connect shell command Introduc a new shell command `gatt_eatt_connect` to allow manual connection of multiple EATT channels per connection. The command parses the connection handle and requested number of channels, displays basic usage help, and calls the internal `ble_eatt_connect()` API. Register the command in the shell, add parameter descriptions and declaration in `cmd_gatt.h`. --- apps/btshell/src/cmd.c | 26 ++++++++++++++++++++++++++ apps/btshell/src/cmd_gatt.c | 35 +++++++++++++++++++++++++++++++++++ apps/btshell/src/cmd_gatt.h | 1 + 3 files changed, 62 insertions(+) diff --git a/apps/btshell/src/cmd.c b/apps/btshell/src/cmd.c index 3380de7547..47711f3632 100644 --- a/apps/btshell/src/cmd.c +++ b/apps/btshell/src/cmd.c @@ -3568,6 +3568,25 @@ static const struct shell_cmd_help gatt_clear_pending_notif_help = { .usage = NULL, .params = NULL, }; + +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) +/***************************************************************************** + * $gatt-eatt-connect * + *****************************************************************************/ + +static const struct shell_param gatt_eatt_connect_params[] = { + {"conn", "connection handle, usage: =" }, + {"chan_num", "number of channels to connect, usage =" }, + {NULL, NULL } +}; + +static const struct shell_cmd_help gatt_eatt_connect_help = { + .summary = "Connect EATT channels", + .usage = NULL, + .params = gatt_eatt_connect_params, +}; +#endif + #endif #if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) @@ -4643,6 +4662,13 @@ static const struct shell_cmd btshell_commands[] = { .sc_cmd_func = cmd_gatt_clear_pending_notif, #if MYNEWT_VAL(SHELL_CMD_HELP) .help = &gatt_clear_pending_notif_help, +#endif + }, + { + .sc_cmd = "gatt-eatt-connect", + .sc_cmd_func = cmd_gatt_eatt_connect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_eatt_connect_help, #endif }, #if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) diff --git a/apps/btshell/src/cmd_gatt.c b/apps/btshell/src/cmd_gatt.c index 3011dd9cab..4f5cee52db 100644 --- a/apps/btshell/src/cmd_gatt.c +++ b/apps/btshell/src/cmd_gatt.c @@ -664,3 +664,38 @@ cmd_gatt_clear_pending_notif(int argc, char **argv) return ENOTSUP; #endif } + +/***************************************************************************** + * $gatt-eatt-connect * + *****************************************************************************/ + +int +cmd_gatt_eatt_connect(int argc, char **argv) +{ +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) + uint16_t conn_handle; + uint16_t chan_num; + int rc; + + rc = parse_arg_init(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + chan_num = parse_arg_uint16("chan_num", &rc); + if (rc != 0) { + console_printf("invalid 'chan_num' parameter\n"); + return rc; + } + return ble_eatt_connect(conn_handle, chan_num); +#else + console_printf("To enable this features set BLE_EATT_CHAN_NUM\n"); + return ENOTSUP; +#endif +} diff --git a/apps/btshell/src/cmd_gatt.h b/apps/btshell/src/cmd_gatt.h index ae517f9a1c..917f429004 100644 --- a/apps/btshell/src/cmd_gatt.h +++ b/apps/btshell/src/cmd_gatt.h @@ -38,5 +38,6 @@ int cmd_gatt_write(int argc, char **argv); int cmd_gatt_enqueue_notif(int argc, char **argv); int cmd_gatt_send_pending_notif(int argc, char **argv); int cmd_gatt_clear_pending_notif(int argc, char **argv); +int cmd_gatt_eatt_connect(int argc, char **argv); #endif