You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ry...@apache.org on 2020/03/07 00:09:39 UTC

[mynewt-nimble] 08/11: nimble/l2cap: Add support for Enhanced LE CoC

This is an automated email from the ASF dual-hosted git repository.

rymek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git

commit 1a57236fdecd7276b5bc9e17b59995c9997caada
Author: Ɓukasz Rymanowski <lu...@codecoup.pl>
AuthorDate: Sun Jan 12 00:05:30 2020 +0100

    nimble/l2cap: Add support for Enhanced LE CoC
    
    This patch adds initial support for enhanced LE CoC.
    For now we allow for MTU reconfiguration as it bilongs to the
    application.
    We do not allow for MPS reconfiguration, as it is assumed that MPS
    is always used the biggest possible.
    However that might change as we start PTS testing.
    
    Some of the initial verification has been done with PTS
    
    To enable feture BLE_L2CAP_ENHANCED_COC shall be enabled along with
    BLE_VERSION >= 52 and non zero BLE_L2CAP_COC_MAX_NUM
---
 nimble/host/include/host/ble_l2cap.h               |  37 +-
 nimble/host/src/ble_hs_conn.c                      |  18 +
 nimble/host/src/ble_hs_conn_priv.h                 |   1 +
 nimble/host/src/ble_l2cap.c                        |  32 +
 nimble/host/src/ble_l2cap_coc.c                    |  11 +
 nimble/host/src/ble_l2cap_coc_priv.h               |   1 +
 nimble/host/src/ble_l2cap_priv.h                   |   7 +
 nimble/host/src/ble_l2cap_sig.c                    | 652 ++++++++++++++++++++-
 nimble/host/src/ble_l2cap_sig_priv.h               |  50 ++
 nimble/host/syscfg.yml                             |   7 +
 nimble/host/test/src/ble_l2cap_test.c              | 116 +++-
 nimble/host/test/syscfg.yml                        |   4 +-
 porting/examples/linux/include/syscfg/syscfg.h     |   4 +
 .../examples/linux_blemesh/include/syscfg/syscfg.h |   4 +
 porting/nimble/include/syscfg/syscfg.h             |   4 +
 porting/npl/riot/include/syscfg/syscfg.h           |   4 +
 16 files changed, 923 insertions(+), 29 deletions(-)

diff --git a/nimble/host/include/host/ble_l2cap.h b/nimble/host/include/host/ble_l2cap.h
index 2f7c6a6..aef9682 100644
--- a/nimble/host/include/host/ble_l2cap.h
+++ b/nimble/host/include/host/ble_l2cap.h
@@ -54,7 +54,11 @@ struct ble_hs_conn;
 #define BLE_L2CAP_SIG_OP_LE_CREDIT_CONNECT_REQ  0x14
 #define BLE_L2CAP_SIG_OP_LE_CREDIT_CONNECT_RSP  0x15
 #define BLE_L2CAP_SIG_OP_FLOW_CTRL_CREDIT       0x16
-#define BLE_L2CAP_SIG_OP_MAX                    0x17
+#define BLE_L2CAP_SIG_OP_CREDIT_CONNECT_REQ     0x17
+#define BLE_L2CAP_SIG_OP_CREDIT_CONNECT_RSP     0x18
+#define BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_REQ    0x19
+#define BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_RSP    0x1A
+#define BLE_L2CAP_SIG_OP_MAX                    0x1B
 
 #define BLE_L2CAP_SIG_ERR_CMD_NOT_UNDERSTOOD    0x0000
 #define BLE_L2CAP_SIG_ERR_MTU_EXCEEDED          0x0001
@@ -70,12 +74,21 @@ struct ble_hs_conn;
 #define BLE_L2CAP_COC_ERR_INVALID_SOURCE_CID        0x0009
 #define BLE_L2CAP_COC_ERR_SOURCE_CID_ALREADY_USED   0x000A
 #define BLE_L2CAP_COC_ERR_UNACCEPTABLE_PARAMETERS   0x000B
+#define BLE_L2CAP_COC_ERR_INVALID_PARAMETERS        0x000C
+
+#define BLE_L2CAP_ERR_RECONFIG_SUCCEED                       0x0000
+#define BLE_L2CAP_ERR_RECONFIG_REDUCTION_MTU_NOT_ALLOWED     0x0001
+#define BLE_L2CAP_ERR_RECONFIG_REDUCTION_MPS_NOT_ALLOWED     0x0002
+#define BLE_L2CAP_ERR_RECONFIG_INVALID_DCID                  0x0003
+#define BLE_L2CAP_ERR_RECONFIG_UNACCAPTED_PARAM              0x0004
 
 #define BLE_L2CAP_EVENT_COC_CONNECTED                 0
 #define BLE_L2CAP_EVENT_COC_DISCONNECTED              1
 #define BLE_L2CAP_EVENT_COC_ACCEPT                    2
 #define BLE_L2CAP_EVENT_COC_DATA_RECEIVED             3
 #define BLE_L2CAP_EVENT_COC_TX_UNSTALLED              4
+#define BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED        5
+#define BLE_L2CAP_EVENT_COC_PEER_RECONFIGURED         6
 
 typedef void ble_l2cap_sig_update_fn(uint16_t conn_handle, int status,
                                      void *arg);
@@ -196,6 +209,28 @@ struct ble_l2cap_event {
              */
             int status;
         } tx_unstalled;
+
+        /**
+         * Represents reconfiguration done. Valid for the following event
+         * types:
+         *      o BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED
+         *      o BLE_L2CAP_EVENT_COC_PEER_RECONFIGURED
+         */
+        struct {
+            /**
+             * The status of the reconfiguration attempt;
+             *     o 0: the reconfiguration was successfully done.
+             *     o BLE host error code: the reconfiguration attempt failed for
+             *       the specified reason.
+             */
+            int status;
+
+            /** Connection handle of the relevant connection */
+            uint16_t conn_handle;
+
+            /** The L2CAP channel of the relevant L2CAP connection. */
+            struct ble_l2cap_chan *chan;
+        } reconfigured;
     };
 };
 
diff --git a/nimble/host/src/ble_hs_conn.c b/nimble/host/src/ble_hs_conn.c
index fa75a09..f7edb62 100644
--- a/nimble/host/src/ble_hs_conn.c
+++ b/nimble/host/src/ble_hs_conn.c
@@ -88,6 +88,24 @@ ble_hs_conn_chan_find_by_dcid(struct ble_hs_conn *conn, uint16_t cid)
     return NULL;
 }
 
+bool
+ble_hs_conn_chan_exist(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
+{
+#if !NIMBLE_BLE_CONNECT
+    return NULL;
+#endif
+
+    struct ble_l2cap_chan *tmp;
+
+    SLIST_FOREACH(tmp, &conn->bhc_channels, next) {
+        if (chan == tmp) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 int
 ble_hs_conn_chan_insert(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan)
 {
diff --git a/nimble/host/src/ble_hs_conn_priv.h b/nimble/host/src/ble_hs_conn_priv.h
index 7578145..0e45119 100644
--- a/nimble/host/src/ble_hs_conn_priv.h
+++ b/nimble/host/src/ble_hs_conn_priv.h
@@ -126,6 +126,7 @@ struct ble_l2cap_chan *ble_hs_conn_chan_find_by_scid(struct ble_hs_conn *conn,
                                              uint16_t cid);
 struct ble_l2cap_chan *ble_hs_conn_chan_find_by_dcid(struct ble_hs_conn *conn,
                                              uint16_t cid);
+bool ble_hs_conn_chan_exist(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan);
 int ble_hs_conn_chan_insert(struct ble_hs_conn *conn,
                             struct ble_l2cap_chan *chan);
 void ble_hs_conn_delete_chan(struct ble_hs_conn *conn,
diff --git a/nimble/host/src/ble_l2cap.c b/nimble/host/src/ble_l2cap.c
index 19cf8c7..0d9f082 100644
--- a/nimble/host/src/ble_l2cap.c
+++ b/nimble/host/src/ble_l2cap.c
@@ -178,6 +178,38 @@ ble_l2cap_get_chan_info(struct ble_l2cap_chan *chan, struct ble_l2cap_chan_info
 }
 
 int
+ble_l2cap_enhanced_connect(uint16_t conn_handle,
+                               uint16_t psm, uint16_t mtu,
+                               uint8_t num, struct os_mbuf *sdu_rx[],
+                               ble_l2cap_event_fn *cb, void *cb_arg)
+{
+    return ble_l2cap_sig_ecoc_connect(conn_handle, psm, mtu,
+                                      num, sdu_rx, cb, cb_arg);
+}
+
+int
+ble_l2cap_reconfig(struct ble_l2cap_chan *chans[], uint8_t num, uint16_t new_mtu)
+{
+    int i;
+    uint16_t conn_handle;
+
+    if (num == 0 || !chans) {
+        return BLE_HS_EINVAL;
+    }
+
+    conn_handle = chans[0]->conn_handle;
+
+    for (i = 1; i < num; i++) {
+        if (conn_handle != chans[i]->conn_handle) {
+            BLE_HS_LOG(ERROR, "All channels should have same conn handle\n");
+            return BLE_HS_EINVAL;
+        }
+    }
+
+    return ble_l2cap_sig_coc_reconfig(conn_handle, chans, num, new_mtu);
+}
+
+int
 ble_l2cap_disconnect(struct ble_l2cap_chan *chan)
 {
     return ble_l2cap_sig_disconnect(chan);
diff --git a/nimble/host/src/ble_l2cap_coc.c b/nimble/host/src/ble_l2cap_coc.c
index 798b0dc..41a8315 100644
--- a/nimble/host/src/ble_l2cap_coc.c
+++ b/nimble/host/src/ble_l2cap_coc.c
@@ -274,6 +274,17 @@ ble_l2cap_coc_rx_fn(struct ble_l2cap_chan *chan)
     return 0;
 }
 
+void
+ble_l2cap_coc_set_new_mtu_mps(struct ble_l2cap_chan *chan, uint16_t mtu, uint16_t mps)
+{
+    chan->my_coc_mps = mps;
+    chan->coc_rx.mtu = mtu;
+    chan->initial_credits = mtu / chan->my_coc_mps;
+    if (mtu % chan->my_coc_mps) {
+        chan->initial_credits++;
+    }
+}
+
 struct ble_l2cap_chan *
 ble_l2cap_coc_chan_alloc(struct ble_hs_conn *conn, uint16_t psm, uint16_t mtu,
                          struct os_mbuf *sdu_rx, ble_l2cap_event_fn *cb,
diff --git a/nimble/host/src/ble_l2cap_coc_priv.h b/nimble/host/src/ble_l2cap_coc_priv.h
index f8d4850..5ebdaa0 100644
--- a/nimble/host/src/ble_l2cap_coc_priv.h
+++ b/nimble/host/src/ble_l2cap_coc_priv.h
@@ -70,6 +70,7 @@ void ble_l2cap_coc_le_credits_update(uint16_t conn_handle, uint16_t dcid,
 int ble_l2cap_coc_recv_ready(struct ble_l2cap_chan *chan,
                              struct os_mbuf *sdu_rx);
 int ble_l2cap_coc_send(struct ble_l2cap_chan *chan, struct os_mbuf *sdu_tx);
+void ble_l2cap_coc_set_new_mtu_mps(struct ble_l2cap_chan *chan, uint16_t mtu, uint16_t mps);
 #else
 static inline int
 ble_l2cap_coc_init(void) {
diff --git a/nimble/host/src/ble_l2cap_priv.h b/nimble/host/src/ble_l2cap_priv.h
index 937dbaf..e340974 100644
--- a/nimble/host/src/ble_l2cap_priv.h
+++ b/nimble/host/src/ble_l2cap_priv.h
@@ -130,6 +130,13 @@ void ble_l2cap_remove_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan);
 
 int ble_l2cap_init(void);
 
+/* Below experimental API is available when BLE_VERSION >= 52 */
+int ble_l2cap_enhanced_connect(uint16_t conn_handle,
+                               uint16_t psm, uint16_t mtu,
+                               uint8_t num, struct os_mbuf *sdu_rx[],
+                               ble_l2cap_event_fn *cb, void *cb_arg);
+int ble_l2cap_reconfig(struct ble_l2cap_chan *chans[], uint8_t num, uint16_t new_mtu);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/nimble/host/src/ble_l2cap_sig.c b/nimble/host/src/ble_l2cap_sig.c
index b8beefe..65c826c 100644
--- a/nimble/host/src/ble_l2cap_sig.c
+++ b/nimble/host/src/ble_l2cap_sig.c
@@ -56,8 +56,17 @@
 
 #define BLE_L2CAP_SIG_PROC_OP_UPDATE            0
 #define BLE_L2CAP_SIG_PROC_OP_CONNECT           1
-#define BLE_L2CAP_SIG_PROC_OP_DISCONNECT        2
-#define BLE_L2CAP_SIG_PROC_OP_MAX               3
+#define BLE_L2CAP_SIG_PROC_OP_RECONFIG          2
+#define BLE_L2CAP_SIG_PROC_OP_DISCONNECT        3
+#define BLE_L2CAP_SIG_PROC_OP_MAX               4
+
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+#define BLE_L2CAP_ECOC_MIN_MTU  (64)
+
+#define BLE_L2CAP_MAX_COC_CONN_REQ  (5)
+#else
+#define BLE_L2CAP_MAX_COC_CONN_REQ  (1)
+#endif
 
 struct ble_l2cap_sig_proc {
     STAILQ_ENTRY(ble_l2cap_sig_proc) next;
@@ -73,11 +82,20 @@ struct ble_l2cap_sig_proc {
             void *cb_arg;
         } update;
         struct {
-            struct ble_l2cap_chan *chan;
+            uint8_t chan_cnt;
+            struct ble_l2cap_chan *chan[BLE_L2CAP_MAX_COC_CONN_REQ];
         } connect;
         struct {
             struct ble_l2cap_chan *chan;
         } disconnect;
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+        struct {
+            uint8_t cid_cnt;
+            uint16_t cids[BLE_L2CAP_MAX_COC_CONN_REQ];
+            uint16_t new_mps;
+            uint16_t new_mtu;
+        } reconfig;
+#endif
     };
 };
 
@@ -105,7 +123,19 @@ static ble_l2cap_sig_rx_fn ble_l2cap_sig_le_credits_rx;
 #define ble_l2cap_sig_coc_rsp_rx    ble_l2cap_sig_rx_noop
 #define ble_l2cap_sig_disc_rsp_rx   ble_l2cap_sig_rx_noop
 #define ble_l2cap_sig_disc_req_rx   ble_l2cap_sig_rx_noop
-#define ble_l2cap_sig_le_credits_rx   ble_l2cap_sig_rx_noop
+#define ble_l2cap_sig_le_credits_rx                  ble_l2cap_sig_rx_noop
+#endif
+
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+static ble_l2cap_sig_rx_fn ble_l2cap_sig_credit_base_con_req_rx;
+static ble_l2cap_sig_rx_fn ble_l2cap_sig_credit_base_con_rsp_rx;
+static ble_l2cap_sig_rx_fn ble_l2cap_sig_credit_base_reconfig_req_rx;
+static ble_l2cap_sig_rx_fn ble_l2cap_sig_credit_base_reconfig_rsp_rx;
+#else
+#define ble_l2cap_sig_credit_base_con_req_rx      ble_l2cap_sig_rx_noop
+#define ble_l2cap_sig_credit_base_con_rsp_rx      ble_l2cap_sig_rx_noop
+#define ble_l2cap_sig_credit_base_reconfig_req_rx ble_l2cap_sig_rx_noop
+#define ble_l2cap_sig_credit_base_reconfig_rsp_rx ble_l2cap_sig_rx_noop
 #endif
 
 static ble_l2cap_sig_rx_fn * const ble_l2cap_sig_dispatch[] = {
@@ -124,6 +154,10 @@ static ble_l2cap_sig_rx_fn * const ble_l2cap_sig_dispatch[] = {
     [BLE_L2CAP_SIG_OP_LE_CREDIT_CONNECT_REQ]   = ble_l2cap_sig_coc_req_rx,
     [BLE_L2CAP_SIG_OP_LE_CREDIT_CONNECT_RSP]   = ble_l2cap_sig_coc_rsp_rx,
     [BLE_L2CAP_SIG_OP_FLOW_CTRL_CREDIT]     = ble_l2cap_sig_le_credits_rx,
+    [BLE_L2CAP_SIG_OP_CREDIT_CONNECT_REQ]   = ble_l2cap_sig_credit_base_con_req_rx,
+    [BLE_L2CAP_SIG_OP_CREDIT_CONNECT_RSP]   = ble_l2cap_sig_credit_base_con_rsp_rx,
+    [BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_REQ]  = ble_l2cap_sig_credit_base_reconfig_req_rx,
+    [BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_RSP]  = ble_l2cap_sig_credit_base_reconfig_rsp_rx,
 };
 
 static uint8_t ble_l2cap_sig_cur_id;
@@ -609,32 +643,458 @@ ble_l2cap_sig_coc_connect_cb(struct ble_l2cap_sig_proc *proc, int status)
 {
     struct ble_hs_conn *conn;
     struct ble_l2cap_chan *chan;
+    int i;
+    bool some_not_connected = false;
 
     if (!proc) {
             return;
     }
 
-    chan = proc->connect.chan;
-    if (!chan || !chan->cb) {
+    for (i = 0; i < proc->connect.chan_cnt; i++) {
+        chan = proc->connect.chan[i];
+        if (!chan || !chan->cb) {
+            continue;
+        }
+
+        if ((status == 0) && (chan->dcid != 0)) {
+            ble_l2cap_event_coc_connected(chan, status);
+            /* Let's forget about connected channel now.
+             * Not connected will be freed later on.
+             */
+            proc->connect.chan[i] = NULL;
+            continue;
+        }
+        some_not_connected = true;
+        ble_l2cap_event_coc_connected(chan, status ? status : BLE_HS_EREJECT);
+    }
+
+    if (!some_not_connected) {
         return;
     }
 
-    ble_l2cap_event_coc_connected(chan, status);
+    /* Free not connected channels*/
+
+    ble_hs_lock();
+    conn = ble_hs_conn_find(chan->conn_handle);
+    for (i = 0; i < proc->connect.chan_cnt; i++) {
+        chan = proc->connect.chan[i];
+        if (chan) {
+            /* Normally in channel free we send disconnected event to application.
+             * However in case on error during creation connection we send connected
+             * event with error status. To avoid additional disconnected event lets
+             * clear callbacks since we don't needed it anymore.
+             */
+            chan->cb = NULL;
+            ble_l2cap_chan_free(conn, chan);
+        }
+    }
+    ble_hs_unlock();
+}
+
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+static void
+ble_l2cap_event_coc_reconfigured(uint16_t conn_handle, uint16_t status,
+                                 struct ble_l2cap_chan *chan, bool peer)
+{
+    struct ble_l2cap_event event = { };
+
+    if (peer) {
+        event.type = BLE_L2CAP_EVENT_COC_PEER_RECONFIGURED;
+    } else {
+        event.type = BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED;
+    }
+    event.reconfigured.conn_handle = conn_handle;
+    event.reconfigured.chan = chan;
+    event.reconfigured.status = status;
+
+    chan->cb(&event, chan->cb_arg);
+}
+
+static int
+ble_l2cap_sig_credit_base_reconfig_req_rx(uint16_t conn_handle,
+                                     struct ble_l2cap_sig_hdr *hdr,
+                                     struct os_mbuf **om)
+{
+    struct ble_l2cap_chan *chan[BLE_L2CAP_MAX_COC_CONN_REQ] = {0};
+    struct ble_l2cap_sig_credit_base_reconfig_req *req;
+    struct ble_l2cap_sig_credit_base_reconfig_rsp *rsp;
+    struct ble_hs_conn *conn;
+    struct os_mbuf *txom;
+    int i;
+    int rc;
+    uint8_t cid_cnt;
+    uint8_t reduction_mps = 0;
+
+    rc = ble_hs_mbuf_pullup_base(om, hdr->length);
+    if (rc != 0) {
+        return rc;
+    }
+
+    ble_hs_lock();
+    conn = ble_hs_conn_find(conn_handle);
+    if (!conn) {
+        ble_hs_unlock();
+        return 0;
+    }
+
+    rsp = ble_l2cap_sig_cmd_get(BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_RSP,
+                                    hdr->identifier, sizeof(*rsp) , &txom);
+    if (!rsp) {
+        /* TODO: Reuse request buffer for the response. For now in such a case
+         * remote will timeout.
+         */
+        BLE_HS_LOG(ERROR, "No memory for the response\n");
+        ble_hs_unlock();
+        return 0;
+    }
+
+    if (hdr->length <= sizeof(*req)) {
+        rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_UNACCAPTED_PARAM);
+        goto done;
+    }
+
+    req = (struct ble_l2cap_sig_credit_base_reconfig_req *)(*om)->om_data;
+
+    if ((req->mps < BLE_L2CAP_ECOC_MIN_MTU) || (req->mtu < BLE_L2CAP_ECOC_MIN_MTU)) {
+        rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_UNACCAPTED_PARAM);
+        goto done;
+    }
+
+    /* Assume request will succeed. If not, result will be updated */
+    rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_SUCCEED);
+
+    cid_cnt = (hdr->length - sizeof(*req)) / sizeof(uint16_t);
+    if (cid_cnt > BLE_L2CAP_MAX_COC_CONN_REQ) {
+        rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_UNACCAPTED_PARAM);
+        goto done;
+    }
+
+    for (i = 0; i < cid_cnt; i++) {
+        chan[i] = ble_hs_conn_chan_find_by_dcid(conn, req->dcids[i]);
+        if (!chan[i]) {
+             rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_INVALID_DCID);
+             ble_hs_unlock();
+             goto done;
+        }
+
+        if (chan[i]->peer_coc_mps > req->mps) {
+            reduction_mps++;
+            if (reduction_mps > 1) {
+                rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_REDUCTION_MPS_NOT_ALLOWED);
+                ble_hs_unlock();
+                goto done;
+            }
+        }
+
+        if (chan[i]->coc_tx.mtu > req->mtu) {
+            rsp->result = htole16(BLE_L2CAP_ERR_RECONFIG_REDUCTION_MTU_NOT_ALLOWED);
+            ble_hs_unlock();
+            goto done;
+        }
+    }
+
+    for (i = 0; i < cid_cnt; i++) {
+        chan[i]->coc_tx.mtu = req->mtu;
+        chan[i]->peer_coc_mps = req->mps;
+        ble_l2cap_event_coc_reconfigured(conn_handle, 0, chan[i], true);
+    }
+
+    ble_hs_unlock();
+done:
+    ble_l2cap_sig_tx(conn_handle, txom);
+    return 0;
+}
+
+static void
+ble_l2cap_sig_coc_reconfig_cb(struct ble_l2cap_sig_proc *proc, int status)
+{
+    int i;
+    struct ble_l2cap_chan *chan[BLE_L2CAP_MAX_COC_CONN_REQ] = {0};
+    struct ble_hs_conn *conn;
+
+    ble_hs_lock();
+
+    conn = ble_hs_conn_find(proc->conn_handle);
+    if (!conn) {
+        ble_hs_unlock();
+        return;
+    }
+
+    for (i = 0; i< proc->reconfig.cid_cnt; i++) {
+        chan[i] = ble_hs_conn_chan_find_by_scid(conn, proc->reconfig.cids[i]);
+        if (status == 0) {
+            ble_l2cap_coc_set_new_mtu_mps(chan[i], proc->reconfig.new_mtu, proc->reconfig.new_mps);
+        }
+    }
+
+    ble_hs_unlock();
+
+    for (i = 0; i < proc->reconfig.cid_cnt; i++) {
+        ble_l2cap_event_coc_reconfigured(proc->conn_handle, status, chan[i], false);
+    }
+}
+
+static int
+ble_l2cap_sig_credit_base_reconfig_rsp_rx(uint16_t conn_handle,
+                                     struct ble_l2cap_sig_hdr *hdr,
+                                     struct os_mbuf **om)
+{
+    struct ble_l2cap_sig_proc *proc;
+    struct ble_l2cap_sig_credit_base_reconfig_rsp *rsp;
+    int rc;
+
+    proc = ble_l2cap_sig_proc_extract(conn_handle,
+                                      BLE_L2CAP_SIG_PROC_OP_RECONFIG,
+                                      hdr->identifier);
+    if (!proc) {
+        return 0;
+    }
+
+    rc = ble_hs_mbuf_pullup_base(om, hdr->length);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rsp = (struct ble_l2cap_sig_credit_base_reconfig_rsp *)(*om)->om_data;
+    ble_l2cap_sig_coc_reconfig_cb(proc, (rsp->result > 0) ? BLE_HS_EREJECT : 0);
+
+    return 0;
+}
+
+static int
+ble_l2cap_sig_credit_base_con_req_rx(uint16_t conn_handle,
+                                     struct ble_l2cap_sig_hdr *hdr,
+                                     struct os_mbuf **om)
+{
+    int rc;
+    struct ble_l2cap_sig_credit_base_connect_req *req;
+    struct os_mbuf *txom;
+    struct ble_l2cap_sig_credit_base_connect_rsp *rsp;
+    struct ble_l2cap_chan *chans[5] = { 0 };
+    struct ble_hs_conn *conn;
+    uint16_t scid;
+    uint8_t num_of_scids;
+    uint8_t chan_created = 0;
+    int i;
+    uint8_t len;
+
+    rc = ble_hs_mbuf_pullup_base(om, hdr->length);
+    if (rc != 0) {
+        return rc;
+    }
+
+    len = (hdr->length > sizeof(*req)) ? hdr->length : sizeof(*req);
+
+    rsp = ble_l2cap_sig_cmd_get(BLE_L2CAP_SIG_OP_CREDIT_CONNECT_RSP,
+                                hdr->identifier, len , &txom);
+    if (!rsp) {
+        /* Well, nothing smart we can do if there is no memory for response.
+         * Remote will timeout.
+         */
+        return 0;
+    }
+
+    ble_hs_lock();
+
+    memset(rsp, 0, len);
+
+    /* Initial dummy values in case of error, just to satisfy PTS */
+    rsp->credits = htole16(1);
+    rsp->mps = htole16(BLE_L2CAP_ECOC_MIN_MTU);
+    rsp->mtu = htole16(BLE_L2CAP_ECOC_MIN_MTU);
+
+    if (hdr->length <= sizeof(*req)) {
+        rsp->result = htole16(BLE_L2CAP_COC_ERR_INVALID_PARAMETERS);
+        goto failed;
+    }
+
+    req = (struct ble_l2cap_sig_credit_base_connect_req *)(*om)->om_data;
+
+    num_of_scids = (hdr->length - sizeof(*req)) / sizeof(uint16_t);
+    if (num_of_scids > 5) {
+        rsp->result = htole16(BLE_L2CAP_COC_ERR_INVALID_PARAMETERS);
+        goto failed;
+    }
+
+    if ((req->mtu < BLE_L2CAP_ECOC_MIN_MTU) || (req->mps < BLE_L2CAP_ECOC_MIN_MTU)) {
+        rsp->result = htole16(BLE_L2CAP_COC_ERR_INVALID_PARAMETERS);
+        goto failed;
+    }
+
+    conn = ble_hs_conn_find_assert(conn_handle);
+
+    /* First verify that provided SCIDs are good */
+    for (i = 0; i < num_of_scids; i++) {
+        scid = le16toh(req->scids[i]);
+        if (scid < BLE_L2CAP_COC_CID_START || scid > BLE_L2CAP_COC_CID_END) {
+            rsp->result = htole16(BLE_L2CAP_COC_ERR_INVALID_SOURCE_CID);
+            goto failed;
+        }
+    }
+
+    /* Let us try to connect channels */
+    for (i = 0; i < num_of_scids; i++) {
+        /* Verify CID. Note, scid in the request is dcid for out local channel */
+        scid = le16toh(req->scids[i]);
+        chans[i] = ble_hs_conn_chan_find_by_dcid(conn, scid);
+        if (chans[i]) {
+            rsp->result = htole16(BLE_L2CAP_COC_ERR_SOURCE_CID_ALREADY_USED);
+            rsp->dcids[i] = htole16(chans[i]->scid);
+            continue;
+        }
+
+        rc = ble_l2cap_coc_create_srv_chan(conn, le16toh(req->psm), &chans[i]);
+        if (rc != 0) {
+            if (i == 0) {
+                /* In case it is very first channel we cannot create it means PSM is incorrect
+                 * or we are out of resources. Just send a response now.
+                 */
+                rsp->result = htole16(ble_l2cap_sig_ble_hs_err2coc_err(rc));
+                goto failed;
+            } else {
+                /* We cannot create number of channels req by peer due to limited resources. */
+                rsp->result = htole16(BLE_L2CAP_COC_ERR_NO_RESOURCES);
+                goto done;
+            }
+        }
+
+        /* Fill up remote configuration. Note MPS is the L2CAP MTU*/
+        chans[i]->dcid = scid;
+        chans[i]->peer_coc_mps = le16toh(req->mps);
+        chans[i]->coc_tx.credits = le16toh(req->credits);
+        chans[i]->coc_tx.mtu = le16toh(req->mtu);
+
+        ble_hs_conn_chan_insert(conn, chans[i]);
+        /* Sending event to the app. Unlock hs */
+        ble_hs_unlock();
+
+        rc = ble_l2cap_event_coc_accept(chans[i], le16toh(req->mtu));
+        if (rc == 0) {
+            rsp->dcids[i] = htole16(chans[i]->scid);
+            chan_created++;
+            if (chan_created == 1) {
+                /* We need to set it once as there are same initial parameters
+                 * for all the channels
+                 */
+                rsp->credits = htole16(chans[i]->coc_rx.credits);
+                rsp->mps = htole16(chans[i]->my_mtu);
+                rsp->mtu = htole16(chans[i]->coc_rx.mtu);
+            }
+        } else {
+            /* Make sure we do not send disconnect event when removing channel */
+            chans[i]->cb = NULL;
+
+            ble_hs_lock();
+            conn = ble_hs_conn_find_assert(conn_handle);
+            ble_hs_conn_delete_chan(conn, chans[i]);
+            chans[i] = NULL;
+            rsp->result = htole16(ble_l2cap_sig_ble_hs_err2coc_err(rc));
+            rc = 0;
+            ble_hs_unlock();
+        }
 
-    if (status) {
-        /* Normally in channel free we send disconnected event to application.
-         * However in case on error during creation connection we send connected
-         * event with error status. To avoid additional disconnected event lets
-         * clear callbacks since we don't needed it anymore.*/
-        chan->cb = NULL;
         ble_hs_lock();
-        conn = ble_hs_conn_find(chan->conn_handle);
-        ble_l2cap_chan_free(conn, chan);
+        conn = ble_hs_conn_find_assert(conn_handle);
+    }
+
+done:
+    ble_hs_unlock();
+    rc = ble_l2cap_sig_tx(conn_handle, txom);
+    if (rc != 0) {
+        ble_hs_lock();
+        conn = ble_hs_conn_find_assert(conn_handle);
+        for (i = 0; i < num_of_scids; i++) {
+            if (chans[i]) {
+                ble_hs_conn_delete_chan(conn, chans[i]);
+            }
+        }
         ble_hs_unlock();
+        return 0;
+    }
+
+    /* Notify user about connection status */
+    for (i = 0; i < num_of_scids; i++) {
+        if (chans[i]) {
+            ble_l2cap_event_coc_connected(chans[i], rc);
+        }
     }
+
+    return 0;
+
+failed:
+    ble_hs_unlock();
+    ble_l2cap_sig_tx(conn_handle, txom);
+    return 0;
 }
 
 static int
+ble_l2cap_sig_credit_base_con_rsp_rx(uint16_t conn_handle,
+                                     struct ble_l2cap_sig_hdr *hdr,
+                                     struct os_mbuf **om)
+{
+    struct ble_l2cap_sig_proc *proc;
+    struct ble_l2cap_sig_credit_base_connect_rsp *rsp;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    int rc;
+    int i;
+
+#if !BLE_MONITOR
+    BLE_HS_LOG(DEBUG, "L2CAP LE COC connection response received\n");
+#endif
+
+    proc = ble_l2cap_sig_proc_extract(conn_handle,
+                                      BLE_L2CAP_SIG_PROC_OP_CONNECT,
+                                      hdr->identifier);
+    if (!proc) {
+        return 0;
+    }
+
+    rc = ble_hs_mbuf_pullup_base(om, hdr->length);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rsp = (struct ble_l2cap_sig_credit_base_connect_rsp *)(*om)->om_data;
+
+    if (rsp->result) {
+        rc = ble_l2cap_sig_coc_err2ble_hs_err(le16toh(rsp->result));
+        goto done;
+    }
+
+    ble_hs_lock();
+    conn = ble_hs_conn_find(conn_handle);
+    assert(conn != NULL);
+
+    for (i = 0; i < proc->connect.chan_cnt; i++) {
+        chan = proc->connect.chan[i];
+        if (rsp->dcids[i] == 0) {
+            /* Channel rejected, dont put it on the list.
+             * User will get notified later in that function
+             */
+            chan->dcid = 0;
+            continue;
+        }
+        chan->peer_coc_mps = le16toh(rsp->mps);
+        chan->dcid = le16toh(rsp->dcids[i]);
+        chan->coc_tx.mtu = le16toh(rsp->mtu);
+        chan->coc_tx.credits = le16toh(rsp->credits);
+
+        ble_hs_conn_chan_insert(conn, chan);
+    }
+
+    ble_hs_unlock();
+
+done:
+    ble_l2cap_sig_coc_connect_cb(proc, rc);
+    ble_l2cap_sig_proc_free(proc);
+
+    /* Silently ignore errors as this is response signal */
+    return 0;
+}
+#endif
+
+static int
 ble_l2cap_sig_coc_req_rx(uint16_t conn_handle, struct ble_l2cap_sig_hdr *hdr,
                          struct os_mbuf **om)
 {
@@ -767,7 +1227,7 @@ ble_l2cap_sig_coc_rsp_rx(uint16_t conn_handle, struct ble_l2cap_sig_hdr *hdr,
 
     rsp = (struct ble_l2cap_sig_le_con_rsp *)(*om)->om_data;
 
-    chan = proc->connect.chan;
+    chan = proc->connect.chan[0];
 
     if (rsp->result) {
         rc = ble_l2cap_sig_coc_err2ble_hs_err(le16toh(rsp->result));
@@ -838,7 +1298,8 @@ ble_l2cap_sig_coc_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu,
     proc->op = BLE_L2CAP_SIG_PROC_OP_CONNECT;
     proc->id = ble_l2cap_sig_next_id();
     proc->conn_handle = conn_handle;
-    proc->connect.chan = chan;
+    proc->connect.chan[0] = chan;
+    proc->connect.chan_cnt = 1;
 
     req = ble_l2cap_sig_cmd_get(BLE_L2CAP_SIG_OP_LE_CREDIT_CONNECT_REQ, proc->id,
                                 sizeof(*req), &txom);
@@ -869,6 +1330,158 @@ ble_l2cap_sig_coc_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu,
     return rc;
 }
 
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+int
+ble_l2cap_sig_ecoc_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu,
+                           uint8_t num, struct os_mbuf *sdu_rx[],
+                           ble_l2cap_event_fn *cb, void *cb_arg)
+{
+    struct ble_hs_conn *conn;
+    struct ble_l2cap_sig_proc *proc;
+    struct ble_l2cap_chan *chan = NULL;
+    struct os_mbuf *txom;
+    struct ble_l2cap_sig_credit_base_connect_req *req;
+    int rc;
+    int i;
+    int j;
+
+    if (!sdu_rx || !cb) {
+        return BLE_HS_EINVAL;
+    }
+
+    ble_hs_lock();
+    conn = ble_hs_conn_find(conn_handle);
+
+    if (!conn) {
+        ble_hs_unlock();
+        return BLE_HS_ENOTCONN;
+    }
+
+    proc = ble_l2cap_sig_proc_alloc();
+    if (!proc) {
+        ble_hs_unlock();
+        return BLE_HS_ENOMEM;
+    }
+
+    req = ble_l2cap_sig_cmd_get(BLE_L2CAP_SIG_OP_CREDIT_CONNECT_REQ, proc->id,
+                                sizeof(*req) + num * sizeof(uint16_t), &txom);
+    if (!req) {
+        ble_hs_unlock();
+        rc = BLE_HS_ENOMEM;
+        /* Goto done to clear proc */
+        goto done;
+    }
+
+    for (i = 0; i < num; i++) {
+        chan = ble_l2cap_coc_chan_alloc(conn, psm, mtu, sdu_rx[i], cb, cb_arg);
+        if (!chan) {
+            /* Clear request buffer */
+            os_mbuf_free_chain(txom);
+
+            for (j = 0; j < i; j++) {
+                /* Clear callback to make sure "Disconnected event" to the user */
+                chan[j].cb = NULL;
+                ble_l2cap_chan_free(conn, proc->connect.chan[j]);
+            }
+            ble_hs_unlock();
+            rc = BLE_HS_ENOMEM;
+            goto done;
+        }
+        proc->connect.chan[i] = chan;
+    }
+    proc->connect.chan_cnt = num;
+
+    proc->op = BLE_L2CAP_SIG_PROC_OP_CONNECT;
+    proc->id = ble_l2cap_sig_next_id();
+    proc->conn_handle = conn_handle;
+
+    req->psm = htole16(psm);
+    req->mtu = htole16(chan->coc_rx.mtu);
+    req->mps = htole16(chan->my_mtu);
+    req->credits = htole16(chan->coc_rx.credits);
+    for (i = 0; i < num; i++) {
+        req->scids[i] = htole16(proc->connect.chan[i]->scid);
+    }
+
+    ble_hs_unlock();
+
+    rc = ble_l2cap_sig_tx(proc->conn_handle, txom);
+
+done:
+    ble_l2cap_sig_process_status(proc, rc);
+
+    return rc;
+}
+
+int
+ble_l2cap_sig_coc_reconfig(uint16_t conn_handle, struct ble_l2cap_chan *chans[],
+                           uint8_t num, uint16_t new_mtu)
+{
+    struct ble_hs_conn *conn;
+    struct ble_l2cap_sig_proc *proc;
+    struct os_mbuf *txom;
+    struct ble_l2cap_sig_credit_base_reconfig_req *req;
+    int rc;
+    int i;
+
+    ble_hs_lock();
+    conn = ble_hs_conn_find(conn_handle);
+
+    if (!conn) {
+        ble_hs_unlock();
+        return BLE_HS_ENOTCONN;
+    }
+
+    proc = ble_l2cap_sig_proc_alloc();
+    if (!proc) {
+        ble_hs_unlock();
+        return BLE_HS_ENOMEM;
+    }
+
+    for (i = 0; i < num; i++) {
+        if (ble_hs_conn_chan_exist(conn, chans[i])) {
+            proc->reconfig.cids[i] = chans[i]->scid;
+        } else {
+            ble_hs_unlock();
+            rc = BLE_HS_ENOMEM;
+            goto done;
+        }
+    }
+
+    proc->op = BLE_L2CAP_SIG_PROC_OP_RECONFIG;
+    proc->reconfig.cid_cnt = num;
+    proc->reconfig.new_mtu = new_mtu;
+    proc->reconfig.new_mps = MYNEWT_VAL(BLE_L2CAP_COC_MPS);
+    proc->id = ble_l2cap_sig_next_id();
+    proc->conn_handle = conn_handle;
+
+    req = ble_l2cap_sig_cmd_get(BLE_L2CAP_SIG_OP_CREDIT_RECONFIG_REQ, proc->id,
+                                sizeof(*req) + num * sizeof(uint16_t), &txom);
+    if (!req) {
+        ble_hs_unlock();
+        rc = BLE_HS_ENOMEM;
+        goto done;
+    }
+
+    /* For now we allow to change CoC MTU only.*/
+    req->mtu = htole16(proc->reconfig.new_mtu);
+    req->mps = htole16(proc->reconfig.new_mps);
+
+    for (i = 0; i < num; i++) {
+        req->dcids[i] = htole16(proc->reconfig.cids[i]);
+    }
+
+    ble_hs_unlock();
+
+    rc = ble_l2cap_sig_tx(proc->conn_handle, txom);
+
+done:
+    ble_l2cap_sig_process_status(proc, rc);
+
+    return rc;
+}
+#endif
+
 /*****************************************************************************
  * $disconnect                                                               *
  *****************************************************************************/
@@ -1246,6 +1859,11 @@ ble_l2cap_sig_conn_broken(uint16_t conn_handle, int reason)
             case BLE_L2CAP_SIG_PROC_OP_DISCONNECT:
                 ble_l2cap_sig_coc_disconnect_cb(proc, reason);
             break;
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+            case BLE_L2CAP_SIG_PROC_OP_RECONFIG:
+                ble_l2cap_sig_coc_reconfig_cb(proc, reason);
+            break;
+#endif
 #endif
             }
 
diff --git a/nimble/host/src/ble_l2cap_sig_priv.h b/nimble/host/src/ble_l2cap_sig_priv.h
index 462a9e4..a698cd0 100644
--- a/nimble/host/src/ble_l2cap_sig_priv.h
+++ b/nimble/host/src/ble_l2cap_sig_priv.h
@@ -74,6 +74,32 @@ struct ble_l2cap_sig_le_con_rsp {
     uint16_t result;
 } __attribute__((packed));
 
+struct ble_l2cap_sig_credit_base_connect_req {
+    uint16_t psm;
+    uint16_t mtu;
+    uint16_t mps;
+    uint16_t credits;
+    uint16_t scids[0];
+} __attribute__((packed));
+
+struct ble_l2cap_sig_credit_base_connect_rsp {
+    uint16_t mtu;
+    uint16_t mps;
+    uint16_t credits;
+    uint16_t result;
+    uint16_t dcids[0];
+} __attribute__((packed));
+
+struct ble_l2cap_sig_credit_base_reconfig_req {
+    uint16_t mtu;
+    uint16_t mps;
+    uint16_t dcids[0];
+} __attribute__((packed));
+
+struct ble_l2cap_sig_credit_base_reconfig_rsp {
+    uint16_t result;
+} __attribute__((packed));
+
 struct ble_l2cap_sig_disc_req {
     uint16_t dcid;
     uint16_t scid;
@@ -122,6 +148,30 @@ ble_l2cap_sig_disconnect(struct ble_l2cap_chan *chan)
 }
 #endif
 
+#if MYNEWT_VAL(BLE_L2CAP_ENHANCED_COC)
+int ble_l2cap_sig_ecoc_connect(uint16_t conn_handle,
+                                       uint16_t psm, uint16_t mtu,
+                                       uint8_t num, struct os_mbuf *sdu_rx[],
+                                       ble_l2cap_event_fn *cb, void *cb_arg);
+int ble_l2cap_sig_coc_reconfig(uint16_t conn_handle, struct ble_l2cap_chan *chans[],
+                               uint8_t num, uint16_t new_mtu);
+#else
+static inline int
+ble_l2cap_sig_ecoc_connect(uint16_t conn_handle,
+                           uint16_t psm, uint16_t mtu,
+                           uint8_t num, struct os_mbuf *sdu_rx[],
+                           ble_l2cap_event_fn *cb, void *cb_arg)
+{
+    return BLE_HS_ENOTSUP;
+}
+static inline int
+ble_l2cap_sig_coc_reconfig(uint16_t conn_handle, struct ble_l2cap_chan *chans[],
+                           uint8_t num, uint16_t new_mtu)
+{
+    return BLE_HS_ENOTSUP;
+}
+#endif
+
 void ble_l2cap_sig_conn_broken(uint16_t conn_handle, int reason);
 int32_t ble_l2cap_sig_timer(void);
 struct ble_l2cap_chan *ble_l2cap_sig_create_chan(uint16_t conn_handle);
diff --git a/nimble/host/syscfg.yml b/nimble/host/syscfg.yml
index 7e6eb44..e72e8d5 100644
--- a/nimble/host/syscfg.yml
+++ b/nimble/host/syscfg.yml
@@ -118,6 +118,13 @@ syscfg.defs:
             MSYS blocks.
         value: 'MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8'
 
+    BLE_L2CAP_ENHANCED_COC:
+        description: >
+            Enables LE Enhanced CoC mode.
+        value: 0
+        restrictions:
+            - '(BLE_L2CAP_COC_MAX_NUM > 0) && (BLE_VERSION >= 52) if 1'
+
     # Security manager settings.
     BLE_SM_LEGACY:
         description: 'Security manager legacy pairing.'
diff --git a/nimble/host/test/src/ble_l2cap_test.c b/nimble/host/test/src/ble_l2cap_test.c
index aa5e2a4..95523d8 100644
--- a/nimble/host/test/src/ble_l2cap_test.c
+++ b/nimble/host/test/src/ble_l2cap_test.c
@@ -704,7 +704,8 @@ struct test_data {
     uint16_t event_iter;
     uint16_t psm;
     uint16_t mtu;
-    struct ble_l2cap_chan *chan;
+    uint8_t num;
+    struct ble_l2cap_chan *chan[5];
 };
 
 static int
@@ -719,7 +720,7 @@ ble_l2cap_test_event(struct ble_l2cap_event *event, void *arg)
     switch(event->type) {
     case BLE_L2CAP_EVENT_COC_CONNECTED:
         assert(ev->app_status == event->connect.status);
-        t->chan = event->connect.chan;
+        t->chan[0] = event->connect.chan;
         return 0;
     case BLE_L2CAP_EVENT_COC_DISCONNECTED:
         return 0;
@@ -760,6 +761,74 @@ static uint16_t ble_l2cap_calculate_credits(uint16_t mtu, uint16_t mps)
 }
 
 static void
+ble_l2cap_test_coc_connect_multi(struct test_data *t)
+{
+    struct ble_l2cap_sig_credit_base_connect_req req = {};
+    struct ble_l2cap_sig_credit_base_connect_rsp rsp = {};
+    struct os_mbuf *sdu_rx[t->num];
+    struct event *ev = &t->event[t->event_iter++];
+    uint8_t id;
+    int rc;
+    int i;
+
+    ble_l2cap_test_util_init();
+
+    ble_l2cap_test_util_create_conn(2, ((uint8_t[]){1,2,3,4,5,6}),
+                                    ble_l2cap_test_util_conn_cb, NULL);
+
+    for (i = 0; i < t->num; i++) {
+        sdu_rx[i] = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
+        assert(sdu_rx[i] != NULL);
+    }
+
+    rc = ble_l2cap_sig_ecoc_connect(2, t->psm, t->mtu, t->num, sdu_rx,
+                                   ble_l2cap_test_event, t);
+    TEST_ASSERT_FATAL(rc == ev->early_error);
+
+    if (rc != 0) {
+        for (i = 0; i< t->num; i++) {
+            rc = os_mbuf_free_chain(sdu_rx[i]);
+            TEST_ASSERT_FATAL(rc == 0);
+        }
+
+        return;
+    }
+
+    req.credits = htole16(
+                        ble_l2cap_calculate_credits(t->mtu,
+                                                    MYNEWT_VAL(BLE_L2CAP_COC_MPS)));
+    req.mps = htole16(MYNEWT_VAL(BLE_L2CAP_COC_MPS));
+    req.mtu = htole16(t->mtu);
+    req.psm = htole16(t->psm);
+    for (i = 0; i < t->num; i++) {
+        req.scids[i] = htole16(current_cid + i);
+    }
+
+    /* Ensure an update request got sent. */
+    id = ble_hs_test_util_verify_tx_l2cap_sig(
+                                            BLE_L2CAP_SIG_OP_CREDIT_CONNECT_REQ,
+                                            &req, sizeof(req) + t->num * sizeof(uint16_t));
+
+    /* Use some different parameters for peer. Just keep mtu same for testing
+     * only*/
+    rsp.credits = htole16(10);
+    for (i = 0; i < t->num; i++) {
+        rsp.dcids[i] = htole16(current_cid + i);
+    }
+    rsp.mps = htole16(MYNEWT_VAL(BLE_L2CAP_COC_MPS) + 16);
+    rsp.mtu = htole16(t->mtu);
+    rsp.result = htole16(ev->l2cap_status);
+
+    rc = ble_hs_test_util_inject_rx_l2cap_sig(2,
+                                              BLE_L2CAP_SIG_OP_CREDIT_CONNECT_RSP,
+                                              id, &rsp, sizeof(rsp) + t->num * sizeof(uint16_t));
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure callback got called. */
+    TEST_ASSERT(ev->handled);
+}
+
+static void
 ble_l2cap_test_coc_connect(struct test_data *t)
 {
     struct ble_l2cap_sig_le_con_req req = {};
@@ -881,11 +950,11 @@ ble_l2cap_test_coc_disc(struct test_data *t)
     uint8_t id;
     int rc;
 
-    rc = ble_l2cap_sig_disconnect(t->chan);
+    rc = ble_l2cap_sig_disconnect(t->chan[0]);
     TEST_ASSERT_FATAL(rc == 0);
 
-    req.dcid = htole16(t->chan->dcid);
-    req.scid = htole16(t->chan->scid);
+    req.dcid = htole16(t->chan[0]->dcid);
+    req.scid = htole16(t->chan[0]->scid);
 
     /* Ensure an update request got sent. */
     id = ble_hs_test_util_verify_tx_l2cap_sig(BLE_L2CAP_SIG_OP_DISCONN_REQ,
@@ -910,8 +979,8 @@ ble_l2cap_test_coc_disc_by_peer(struct test_data *t)
 
     /* Receive disconnect request from peer. Note that source cid
      * and destination cid are from peer perspective */
-    req.dcid = htole16(t->chan->scid);
-    req.scid = htole16(t->chan->dcid);
+    req.dcid = htole16(t->chan[0]->scid);
+    req.scid = htole16(t->chan[0]->dcid);
 
     rc = ble_hs_test_util_inject_rx_l2cap_sig(2, BLE_L2CAP_SIG_OP_DISCONN_REQ,
                                        id, &req, sizeof(req));
@@ -937,7 +1006,7 @@ ble_l2cap_test_coc_invalid_disc_by_peer(struct test_data *t)
 
     /* Receive disconnect request from peer. Note that source cid
      * and destination cid are from peer perspective */
-    req.dcid = htole16(t->chan->scid);
+    req.dcid = htole16(t->chan[0]->scid);
     req.scid = htole16(0);
 
     rc = ble_hs_test_util_inject_rx_l2cap_sig(2, BLE_L2CAP_SIG_OP_DISCONN_REQ,
@@ -974,7 +1043,7 @@ ble_l2cap_test_coc_send_data(struct test_data *t)
     rc = os_mbuf_append(sdu_copy, ev->data, ev->data_len);
     TEST_ASSERT(rc == 0);
 
-    rc = ble_l2cap_send(t->chan, sdu);
+    rc = ble_l2cap_send(t->chan[0], sdu);
     TEST_ASSERT(rc == ev->early_error);
 
     if (rc) {
@@ -1017,7 +1086,7 @@ ble_l2cap_test_coc_recv_data(struct test_data *t)
     assert(sdu != NULL);
     put_le16(sdu->om_data, ev->data_len);
 
-    ble_hs_test_util_inject_rx_l2cap(2, t->chan->scid, sdu);
+    ble_hs_test_util_inject_rx_l2cap(2, t->chan[0]->scid, sdu);
 }
 
 static void
@@ -1369,6 +1438,32 @@ TEST_CASE_SELF(ble_l2cap_test_case_coc_recv_data_succeed)
     ble_hs_test_util_assert_mbufs_freed(NULL);
 }
 
+TEST_CASE_SELF(ble_l2cap_test_case_sig_coc_conn_multi)
+{
+    struct test_data t;
+    int rc;
+
+    ble_l2cap_test_util_init();
+    ble_l2cap_test_set_chan_test_conf(BLE_L2CAP_TEST_PSM,
+                                      BLE_L2CAP_TEST_COC_MTU, &t);
+    t.expected_num_of_ev = 2;
+    t.num = 2;
+
+    t.event[0].type = BLE_L2CAP_EVENT_COC_CONNECTED;
+    t.event[1].type = BLE_L2CAP_EVENT_COC_CONNECTED;
+
+    /* Register server */
+    rc = ble_l2cap_create_server(t.psm, BLE_L2CAP_TEST_COC_MTU,
+                                 ble_l2cap_test_event, &t);
+    TEST_ASSERT(rc == 0);
+
+    ble_l2cap_test_coc_connect_multi(&t);
+
+    TEST_ASSERT(t.expected_num_of_ev == t.event_cnt);
+
+    ble_hs_test_util_assert_mbufs_freed(NULL);
+}
+
 TEST_SUITE(ble_l2cap_test_suite)
 {
     ble_l2cap_test_case_bad_header();
@@ -1398,4 +1493,5 @@ TEST_SUITE(ble_l2cap_test_suite)
     ble_l2cap_test_case_coc_send_data_succeed();
     ble_l2cap_test_case_coc_send_data_failed_too_big_sdu();
     ble_l2cap_test_case_coc_recv_data_succeed();
+    ble_l2cap_test_case_sig_coc_conn_multi();
 }
diff --git a/nimble/host/test/syscfg.yml b/nimble/host/test/syscfg.yml
index ad55f1e..6307398 100644
--- a/nimble/host/test/syscfg.yml
+++ b/nimble/host/test/syscfg.yml
@@ -25,5 +25,7 @@ syscfg.vals:
     BLE_SM: 1
     BLE_SM_SC: 1
     MSYS_1_BLOCK_COUNT: 100
-    BLE_L2CAP_COC_MAX_NUM: 1
+    BLE_L2CAP_COC_MAX_NUM: 2
     CONFIG_FCB: 1
+    BLE_VERSION: 52
+    BLE_L2CAP_ENHANCED_COC: 1
diff --git a/porting/examples/linux/include/syscfg/syscfg.h b/porting/examples/linux/include/syscfg/syscfg.h
index 6e34cb8..7e9bfca 100644
--- a/porting/examples/linux/include/syscfg/syscfg.h
+++ b/porting/examples/linux/include/syscfg/syscfg.h
@@ -629,6 +629,10 @@
 #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8)
 #endif
 
+#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC
+#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0)
+#endif
+
 #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
 #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
 #endif
diff --git a/porting/examples/linux_blemesh/include/syscfg/syscfg.h b/porting/examples/linux_blemesh/include/syscfg/syscfg.h
index dcacad4..24ad0c5 100644
--- a/porting/examples/linux_blemesh/include/syscfg/syscfg.h
+++ b/porting/examples/linux_blemesh/include/syscfg/syscfg.h
@@ -630,6 +630,10 @@
 #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8)
 #endif
 
+#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC
+#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0)
+#endif
+
 #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
 #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
 #endif
diff --git a/porting/nimble/include/syscfg/syscfg.h b/porting/nimble/include/syscfg/syscfg.h
index 0b2ee41..5a89954 100644
--- a/porting/nimble/include/syscfg/syscfg.h
+++ b/porting/nimble/include/syscfg/syscfg.h
@@ -628,6 +628,10 @@
 #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8)
 #endif
 
+#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC
+#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0)
+#endif
+
 #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
 #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
 #endif
diff --git a/porting/npl/riot/include/syscfg/syscfg.h b/porting/npl/riot/include/syscfg/syscfg.h
index 3d98b13..e78ebe0 100644
--- a/porting/npl/riot/include/syscfg/syscfg.h
+++ b/porting/npl/riot/include/syscfg/syscfg.h
@@ -1296,6 +1296,10 @@
 #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8)
 #endif
 
+#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC
+#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0)
+#endif
+
 #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
 #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
 #endif