diff --git a/apps/btshell/src/cmd.c b/apps/btshell/src/cmd.c index 3380de7547..93a019c0d7 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) > 0 +/***************************************************************************** + * $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) @@ -4645,6 +4664,15 @@ static const struct shell_cmd btshell_commands[] = { .help = &gatt_clear_pending_notif_help, #endif }, +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + { + .sc_cmd = "gatt-eatt-connect", + .sc_cmd_func = cmd_gatt_eatt_connect, +#if MYNEWT_VAL(SHELL_CMD_HELP) + .help = &gatt_eatt_connect_help, +#endif + }, +#endif #if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) { .sc_cmd = "l2cap-update", diff --git a/apps/btshell/src/cmd_gatt.c b/apps/btshell/src/cmd_gatt.c index 3011dd9cab..c1f699cc73 100644 --- a/apps/btshell/src/cmd_gatt.c +++ b/apps/btshell/src/cmd_gatt.c @@ -664,3 +664,35 @@ cmd_gatt_clear_pending_notif(int argc, char **argv) return ENOTSUP; #endif } + +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 +/***************************************************************************** + * $gatt-eatt-connect * + *****************************************************************************/ + +int +cmd_gatt_eatt_connect(int argc, char **argv) +{ + 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); +} +#endif diff --git a/apps/btshell/src/cmd_gatt.h b/apps/btshell/src/cmd_gatt.h index ae517f9a1c..df3e1d8b79 100644 --- a/apps/btshell/src/cmd_gatt.h +++ b/apps/btshell/src/cmd_gatt.h @@ -38,5 +38,8 @@ 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); +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 +int cmd_gatt_eatt_connect(int argc, char **argv); +#endif #endif diff --git a/nimble/host/include/host/ble_att.h b/nimble/host/include/host/ble_att.h index 8323c9d764..2b22776478 100644 --- a/nimble/host/include/host/ble_att.h +++ b/nimble/host/include/host/ble_att.h @@ -355,6 +355,17 @@ 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 sucess; + * NimBLE host error return code on + * error. + */ +int ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num); #ifdef __cplusplus } #endif diff --git a/nimble/host/include/host/ble_l2cap.h b/nimble/host/include/host/ble_l2cap.h index 65329b87a7..005b0534ab 100644 --- a/nimble/host/include/host/ble_l2cap.h +++ b/nimble/host/include/host/ble_l2cap.h @@ -34,6 +34,7 @@ #ifndef H_BLE_L2CAP_ #define H_BLE_L2CAP_ +#include #include "nimble/nimble_opt.h" #ifdef __cplusplus extern "C" { diff --git a/nimble/host/src/ble_eatt.c b/nimble/host/src/ble_eatt.c index 047a087641..b3a0e40ef4 100644 --- a/nimble/host/src/ble_eatt.c +++ b/nimble/host/src/ble_eatt.c @@ -17,6 +17,8 @@ * under the License. */ +#include "os/os_mbuf.h" +#include "os/os_mempool.h" #include "syscfg/syscfg.h" #define BLE_NPL_LOG_MODULE BLE_EATT_LOG #include @@ -45,11 +47,56 @@ struct ble_eatt { struct ble_npl_event wakeup_ev; }; +/** + * Outgoing EATT multi-channel connect request context + * Passed as cb_arg to ble_l2cap_enhanced_connect(); each successfull + * connected CoC channel is handed of to its own ble_eatt strcut via chan->cb_arg. + */ +#define BLE_EATT_CONN_REQ_MAGIC (0x45545452UL) +struct ble_eatt_conn_req { + uint32_t magic; + uint16_t conn_handle; + uint8_t wanted; + uint8_t connected; + struct ble_npl_event setup_ev; +}; + SLIST_HEAD(ble_eatt_list, ble_eatt); static struct ble_eatt_list g_ble_eatt_list; static ble_eatt_att_rx_fn ble_eatt_att_rx_cb; +static uint8_t +ble_eatt_max_per_conn(void) +{ + uint8_t max; + + max = (uint8_t)MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN); + + if (max > MYNEWT_VAL(BLE_EATT_CHAN_NUM)) { + max = MYNEWT_VAL(BLE_EATT_CHAN_NUM); + } + + return max; +} + +static uint8_t +ble_eatt_count(uint16_t conn_handle) +{ + struct ble_eatt *eatt; + uint8_t count; + + count = 0; + + SLIST_FOREACH(eatt, &g_ble_eatt_list, next) { + if (eatt->conn_handle == conn_handle) { + count++; + } + } + + return count; +} + #define BLE_EATT_DATABUF_SIZE ( \ MYNEWT_VAL(BLE_EATT_MTU) + \ 2 + \ @@ -61,11 +108,13 @@ static ble_eatt_att_rx_fn ble_eatt_att_rx_cb; #define BLE_EATT_MEMPOOL_SIZE \ OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_EATT_CHAN_NUM) + 1, BLE_EATT_MEMBLOCK_SIZE) -static os_membuf_t ble_eatt_conn_mem[ - OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_EATT_CHAN_NUM), - sizeof(struct ble_eatt)) +static os_membuf_t ble_eatt_conn_mem[OS_MEMPOOL_SIZE( + MYNEWT_VAL(BLE_EATT_CHAN_NUM), sizeof(struct ble_eatt)) ]; static struct os_mempool ble_eatt_conn_pool; +static os_membuf_t ble_eatt_conn_req_mem[OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_EATT_CHAN_NUM), + sizeof(struct ble_eatt_conn_req))]; +static struct os_mempool ble_eatt_conn_req_pool; static os_membuf_t ble_eatt_sdu_coc_mem[BLE_EATT_MEMPOOL_SIZE]; struct os_mbuf_pool ble_eatt_sdu_os_mbuf_pool; static struct os_mempool ble_eatt_sdu_mbuf_mempool; @@ -216,14 +265,89 @@ ble_eatt_free(struct ble_eatt *eatt) os_memblock_put(&ble_eatt_conn_pool, eatt); } +static struct ble_eatt_conn_req * +ble_eatt_conn_req_alloc(void) +{ + struct ble_eatt_conn_req *req; + + req = os_memblock_get(&ble_eatt_conn_req_pool); + if (!req) { + BLE_EATT_LOG_WARN("eatt: Failed to allocate eatt connect request\n"); + return NULL; + } + + memset(req, 0, sizeof(*req)); + req->magic = BLE_EATT_CONN_REQ_MAGIC; + ble_npl_event_init(&req->setup_ev, ble_eatt_setup_cb, req); + + return req; +} + +static void +ble_eatt_conn_req_free(struct ble_eatt_conn_req *req) +{ + if (!req) { + return; + } + + req->magic = 0; + os_memblock_put(&ble_eatt_conn_req_pool, req); +} + static int 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_eatt_conn_req *req; + struct ble_eatt *eatt; uint8_t opcode; int rc; + /** + * Outgoing multi-channel setup arg is a conn-req for CONNECTED callbacks. + * After allocating per-channel context, we hand off by setting chan->cb_arg. + */ + if (event->type == BLE_L2CAP_EVENT_COC_CONNECTED) { + req = (struct ble_eatt_conn_req *)arg; + if (req && req->magic == BLE_EATT_CONN_REQ_MAGIC && + req->conn_handle == event->connect.conn_handle) { + BLE_EATT_LOG_DEBUG("eatt: Connected (outgoing)\n"); + + if (event->connect.status) { + req->connected++; + if (req->connected >= req->wanted) { + ble_eatt_conn_req_free(req); + } + return 0; + } + eatt = ble_eatt_alloc(); + if (!eatt) { + ble_l2cap_disconnect(event->connect.chan); + req->connected++; + if (req->connected >= req->wanted) { + ble_eatt_conn_req_free(req); + } + return 0; + } + + eatt->conn_handle = event->connect.conn_handle; + eatt->chan = event->connect.chan; + + /* Handoff: Future events for this channel user per-channel eatt context */ + event->connect.chan->cb_arg = eatt; + + req->connected++; + if (req->connected >= req->wanted) { + ble_eatt_conn_req_free(req); + } + + return 0; + } + } + + /* Default: per channel callbacks use struct ble_eatt */ + eatt = (struct ble_eatt *)arg; + switch (event->type) { case BLE_L2CAP_EVENT_COC_CONNECTED: BLE_EATT_LOG_DEBUG("eatt: Connected \n"); @@ -232,6 +356,7 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) return 0; } eatt->chan = event->connect.chan; + break; case BLE_L2CAP_EVENT_COC_DISCONNECTED: BLE_EATT_LOG_DEBUG("eatt: Disconnected \n"); @@ -239,11 +364,8 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) break; case BLE_L2CAP_EVENT_COC_ACCEPT: BLE_EATT_LOG_DEBUG("eatt: Accept request\n"); - 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 - */ + if (ble_eatt_count(event->accept.conn_handle) >= ble_eatt_max_per_conn()) { + /* EATT bearer channel limit reached */ return BLE_HS_ENOMEM; } @@ -320,31 +442,56 @@ ble_eatt_l2cap_event_fn(struct ble_l2cap_event *event, void *arg) static void ble_eatt_setup_cb(struct ble_npl_event *ev) { - struct ble_eatt *eatt; - struct os_mbuf *om; + struct os_mbuf *om[MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN) > 0 ? MYNEWT_VAL(BLE_EATT_CHAN_PER_CONN) : 1]; + struct ble_eatt_conn_req *req; + uint8_t channels; int rc; + int i; + int j; - eatt = ble_npl_event_get_arg(ev); - assert(eatt); + req = ble_npl_event_get_arg(ev); + assert(req); + assert(req->magic == BLE_EATT_CONN_REQ_MAGIC); + + channels = req->wanted; + if (channels == 0) { + ble_eatt_conn_req_free(req); - om = os_mbuf_get_pkthdr(&ble_eatt_sdu_os_mbuf_pool, 0); - if (!om) { - ble_eatt_free(eatt); - BLE_EATT_LOG_ERROR("eatt: no memory for sdu\n"); return; } - BLE_EATT_LOG_DEBUG("eatt: connecting eatt on conn_handle 0x%04x\n", eatt->conn_handle); + for (i = 0; i < channels; i++) { + om[i] = os_mbuf_get_pkthdr(&ble_eatt_sdu_os_mbuf_pool, 0); + if (!om[i]) { + BLE_EATT_LOG_ERROR("eatt: no memory for sdu"); + + for (j = 0; j < i; j++) { + os_mbuf_free_chain(om[j]); + } + ble_eatt_conn_req_free(req); + return; + } + } + + BLE_EATT_LOG_DEBUG("eatt: connecting %u eatt channels on conn_handle 0x%04x", + channels, req->conn_handle); + + rc = ble_l2cap_enhanced_connect(req->conn_handle, BLE_EATT_PSM, + MYNEWT_VAL(BLE_EATT_MTU), channels, om, + ble_eatt_l2cap_event_fn, req); - rc = ble_l2cap_enhanced_connect(eatt->conn_handle, BLE_EATT_PSM, - MYNEWT_VAL(BLE_EATT_MTU), 1, &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); - os_mbuf_free_chain(om); - ble_eatt_free(eatt); + req->conn_handle, rc); + for (i = 0; i < channels; i++) { + os_mbuf_free_chain(om[i]); + } + ble_eatt_conn_req_free(req); + return; } + /* On success L2CAP owns 'om[]'. The req is freed when all CONNECTED + * callbacks (success or failure) have been delivered + */ } static int @@ -415,8 +562,9 @@ ble_eatt_gap_event(struct ble_gap_event *event, void *arg) return 0; } - /* don't try to connect if already connected */ - if (ble_eatt_find_by_conn_handle(event->enc_change.conn_handle)) { +#if MYNEWT_VAL(BLE_EATT_AUTO_CONNECT) + /* Don't try to connect if we already reached conn limit */ + if (ble_eatt_count(event->enc_change.conn_handle) >= ble_eatt_max_per_conn()) { return 0; } @@ -425,11 +573,14 @@ ble_eatt_gap_event(struct ble_gap_event *event, void *arg) ble_npl_event_set_arg(&g_read_sup_cl_feat_ev, UINT_TO_POINTER(event->enc_change.conn_handle)); ble_npl_eventq_put(ble_hs_evq_get(), &g_read_sup_cl_feat_ev); - +#endif break; case BLE_GAP_EVENT_DISCONNECT: - eatt = ble_eatt_find_by_conn_handle(event->disconnect.conn.conn_handle); - assert(eatt == NULL); + /* Free all EATT contexts associated with this connection */ + while ((eatt = ble_eatt_find_by_conn_handle( + event->disconnect.conn.conn_handle)) != NULL) { + ble_eatt_free(eatt); + } break; default: break; @@ -510,7 +661,10 @@ static void ble_eatt_start(uint16_t conn_handle) { struct ble_gap_conn_desc desc; - struct ble_eatt *eatt; + struct ble_eatt_conn_req *req; + uint8_t cur_cnt; + uint8_t max_cnt; + uint8_t add; int rc; rc = ble_gap_conn_find(conn_handle, &desc); @@ -522,15 +676,73 @@ ble_eatt_start(uint16_t conn_handle) return; } - eatt = ble_eatt_alloc(); - if (!eatt) { + cur_cnt = ble_eatt_count(conn_handle); + max_cnt = ble_eatt_max_per_conn(); + if (cur_cnt >= max_cnt) { return; } - eatt->conn_handle = conn_handle; + add = max_cnt - cur_cnt; + + req = ble_eatt_conn_req_alloc(); + if (!req) { + return; + } + + req->conn_handle = conn_handle; + req->wanted = add; + req->connected = 0; + + ble_npl_eventq_put(ble_hs_evq_get(), &req->setup_ev); +} + +int +ble_eatt_connect(uint16_t conn_handle, uint8_t chan_num) +{ + struct ble_gap_conn_desc desc; + struct ble_eatt_conn_req *req; + uint8_t target_cnt; + uint8_t cur_cnt; + uint8_t max_cnt; + uint8_t add; + int rc; + + rc = ble_gap_conn_find(conn_handle, &desc); + if (rc != 0) { + return rc; + } + + max_cnt = ble_eatt_max_per_conn(); + cur_cnt = ble_eatt_count(conn_handle); + + /* If CHAN_NUM == 0 -> connect max channels */ + if (chan_num == 0) { + target_cnt = max_cnt; + } else { + if (chan_num > max_cnt) { + return BLE_HS_EINVAL; + } + target_cnt = chan_num; + } - /* Setup EATT */ - ble_npl_eventq_put(ble_hs_evq_get(), &eatt->setup_ev); + if (cur_cnt >= target_cnt) { + return 0; + } + + add = target_cnt - cur_cnt; + + req = ble_eatt_conn_req_alloc(); + if (!req) { + return BLE_HS_ENOMEM; + } + + req->conn_handle = conn_handle; + req->wanted = add; + req->connected = 0; + + ble_npl_eventq_put(ble_hs_evq_get(), &req->setup_ev); + + return 0; } void @@ -551,6 +763,11 @@ ble_eatt_init(ble_eatt_att_rx_fn att_rx_cb) ble_eatt_conn_mem, "ble_eatt_conn_pool"); BLE_HS_DBG_ASSERT_EVAL(rc == 0); + rc = os_mempool_init(&ble_eatt_conn_req_pool, MYNEWT_VAL(BLE_EATT_CHAN_NUM), + sizeof(struct ble_eatt_conn_req), + ble_eatt_conn_req_mem, "ble_eatt_conn_req_pool"); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); + ble_gap_event_listener_register(&ble_eatt_listener, ble_eatt_gap_event, NULL); ble_l2cap_create_server(BLE_EATT_PSM, MYNEWT_VAL(BLE_EATT_MTU), ble_eatt_l2cap_event_fn, NULL); diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml index 873dcae4bf..dd72682bf5 100644 --- a/nimble/host/syscfg.yml +++ b/nimble/host/syscfg.yml @@ -327,10 +327,26 @@ syscfg.defs: - BLE_GATT_NOTIFY_MULTIPLE - BLE_L2CAP_ENHANCED_COC - 'BLE_L2CAP_COC_MAX_NUM >= BLE_EATT_CHAN_NUM' + - '(BLE_L2CAP_COC_MAX_NUM > 0) || (BLE_EATT_CHAN_NUM == 0)' + - 'BLE_L2CAP_COC_MAX_NUM >= BLE_EATT_CHAN_NUM' BLE_EATT_MTU: description: > MTU used for EATT channels. value: 128 + BLE_EATT_AUTO_CONNECT: + description: > + Enable auto connect for EATT after encryption + value: 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 + restrictions: + - 'BLE_EATT_CHAN_PER_CONN <= BLE_EATT_CHAN_NUM' + - 'BLE_L2CAP_COC_MAX_NUM >= BLE_EATT_CHAN_PER_CONN' + - '(BLE_EATT_CHAN_NUM == 0) || (BLE_EATT_CHAN_PER_CONN > 0)' # Supported server ATT commands. (0/1) BLE_ATT_SVR_FIND_INFO: