You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by an...@apache.org on 2022/04/07 12:18:59 UTC

[mynewt-nimble] 03/04: nimble/ll: Add Connection Subrate Request/Update procedures

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

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

commit 7a79799238e9d9c9d2cc57130f08276b5642e3d1
Author: Andrzej Kaczmarek <an...@codecoup.pl>
AuthorDate: Tue Feb 8 16:01:55 2022 +0100

    nimble/ll: Add Connection Subrate Request/Update procedures
---
 nimble/controller/include/controller/ble_ll_conn.h |  36 +++++
 nimble/controller/include/controller/ble_ll_ctrl.h |   5 +-
 .../controller/include/controller/ble_ll_utils.h   |   4 +
 nimble/controller/src/ble_ll.c                     |   4 +
 nimble/controller/src/ble_ll_conn.c                | 166 ++++++++++++++++++-
 nimble/controller/src/ble_ll_conn_priv.h           |   7 +
 nimble/controller/src/ble_ll_ctrl.c                | 179 +++++++++++++++++++++
 7 files changed, 395 insertions(+), 6 deletions(-)

diff --git a/nimble/controller/include/controller/ble_ll_conn.h b/nimble/controller/include/controller/ble_ll_conn.h
index 85f59bcf..a50e6e69 100644
--- a/nimble/controller/include/controller/ble_ll_conn.h
+++ b/nimble/controller/include/controller/ble_ll_conn.h
@@ -132,6 +132,8 @@ union ble_ll_conn_sm_flags {
         uint32_t rxd_features:1;
         uint32_t pending_hci_rd_features:1;
         uint32_t pending_initiate_dle:1;
+        uint32_t subrate_trans:1;
+        uint32_t subrate_ind_txd:1;
     } cfbit;
     uint32_t conn_flags;
 } __attribute__((packed));
@@ -183,6 +185,22 @@ struct hci_conn_update
     uint16_t max_ce_len;
 };
 
+struct ble_ll_conn_subrate_params {
+    uint16_t subrate_factor;
+    uint16_t subrate_base_event;
+    uint16_t periph_latency;
+    uint16_t cont_num;
+    uint16_t supervision_tmo;
+};
+
+struct ble_ll_conn_subrate_req_params {
+    uint16_t subrate_min;
+    uint16_t subrate_max;
+    uint16_t max_latency;
+    uint16_t cont_num;
+    uint16_t supervision_tmo;
+};
+
 /* Connection state machine */
 struct ble_ll_conn_sm
 {
@@ -284,10 +302,21 @@ struct ble_ll_conn_sm
 
     uint16_t periph_latency;
 #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    uint16_t acc_subrate_min;
+    uint16_t acc_subrate_max;
+    uint16_t acc_max_latency;
+    uint16_t acc_cont_num;
+    uint16_t acc_supervision_tmo;
+
     uint16_t subrate_base_event;
     uint16_t subrate_factor;
     uint16_t cont_num;
     uint16_t last_pdu_event;
+
+    union {
+        struct ble_ll_conn_subrate_params subrate_trans;
+        struct ble_ll_conn_subrate_req_params subrate_req;
+    };
 #endif
 
     /*
@@ -464,6 +493,13 @@ void ble_ll_conn_created_on_aux(struct os_mbuf *rxpdu,
                                 uint8_t *targeta);
 #endif
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+int ble_ll_conn_subrate_req_llcp(struct ble_ll_conn_sm *connsm,
+                                 struct ble_ll_conn_subrate_req_params *srp);
+void ble_ll_conn_subrate_set(struct ble_ll_conn_sm *connsm,
+                             struct ble_ll_conn_subrate_params *sp);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/nimble/controller/include/controller/ble_ll_ctrl.h b/nimble/controller/include/controller/ble_ll_ctrl.h
index 423d13af..17300dc2 100644
--- a/nimble/controller/include/controller/ble_ll_ctrl.h
+++ b/nimble/controller/include/controller/ble_ll_ctrl.h
@@ -41,7 +41,9 @@ extern "C" {
 #define BLE_LL_CTRL_PROC_PHY_UPDATE     (9)
 #define BLE_LL_CTRL_PROC_SCA_UPDATE     (10)
 #define BLE_LL_CTRL_PROC_CIS_CREATE     (11)
-#define BLE_LL_CTRL_PROC_NUM            (12)
+#define BLE_LL_CTRL_PROC_SUBRATE_REQ    (12)
+#define BLE_LL_CTRL_PROC_SUBRATE_UPDATE (13)
+#define BLE_LL_CTRL_PROC_NUM            (14)
 #define BLE_LL_CTRL_PROC_IDLE           (255)
 
 /* Checks if a particular control procedure is running */
@@ -307,6 +309,7 @@ int ble_ll_ctrl_reject_ind_send(struct ble_ll_conn_sm *connsm,
 int ble_ll_ctrl_start_enc_send(struct ble_ll_conn_sm *connsm);
 int ble_ll_ctrl_enc_allowed_pdu_rx(struct os_mbuf *rxpdu);
 int ble_ll_ctrl_enc_allowed_pdu_tx(struct os_mbuf_pkthdr *pkthdr);
+int ble_ll_ctrl_tx_start(struct ble_ll_conn_sm *connsm, struct os_mbuf *txpdu);
 int ble_ll_ctrl_tx_done(struct os_mbuf *txpdu, struct ble_ll_conn_sm *connsm);
 int ble_ll_ctrl_is_start_enc_rsp(struct os_mbuf *txpdu);
 
diff --git a/nimble/controller/include/controller/ble_ll_utils.h b/nimble/controller/include/controller/ble_ll_utils.h
index ae8abfb3..16b91a0c 100644
--- a/nimble/controller/include/controller/ble_ll_utils.h
+++ b/nimble/controller/include/controller/ble_ll_utils.h
@@ -19,6 +19,10 @@
 
 #include <stdint.h>
 
+#define INT16_GT(_a, _b) ((int16_t)((_a) - (_b)) > 0)
+#define INT16_LT(_a, _b) ((int16_t)((_a) - (_b)) < 0)
+#define INT16_LTE(_a, _b) ((int16_t)((_a) - (_b)) <= 0)
+
 uint32_t ble_ll_utils_calc_access_addr(void);
 uint8_t ble_ll_utils_remapped_channel(uint8_t remap_index, const uint8_t *chanmap);
 uint8_t ble_ll_utils_dci_csa2(uint16_t counter, uint16_t chan_id,
diff --git a/nimble/controller/src/ble_ll.c b/nimble/controller/src/ble_ll.c
index f7ab7751..b9d9d7b1 100644
--- a/nimble/controller/src/ble_ll.c
+++ b/nimble/controller/src/ble_ll.c
@@ -1943,6 +1943,10 @@ ble_ll_init(void)
     features |= BLE_LL_FEAT_ISO_HOST_SUPPORT;
 #endif
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    features |= BLE_LL_FEAT_CONN_SUBRATING;
+#endif
+
     lldata->ll_supp_features = features;
 
     /* Initialize random number generation */
diff --git a/nimble/controller/src/ble_ll_conn.c b/nimble/controller/src/ble_ll_conn.c
index b328296d..4c60c461 100644
--- a/nimble/controller/src/ble_ll_conn.c
+++ b/nimble/controller/src/ble_ll_conn.c
@@ -21,6 +21,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <errno.h>
 #include "syscfg/syscfg.h"
 #include "os/os.h"
 #include "nimble/ble.h"
@@ -948,6 +949,17 @@ ble_ll_conn_chk_csm_flags(struct ble_ll_conn_sm *connsm)
         }
     }
 #endif
+
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
+    if (connsm->csmflags.cfbit.subrate_ind_txd) {
+        ble_ll_conn_subrate_set(connsm, &connsm->subrate_trans);
+        connsm->subrate_trans.subrate_factor = 0;
+        ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE);
+        connsm->csmflags.cfbit.subrate_ind_txd = 0;
+    }
+#endif /* BLE_LL_CTRL_SUBRATE_IND */
+#endif /* BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE */
 }
 
 /**
@@ -1109,6 +1121,7 @@ ble_ll_conn_tx_pdu(struct ble_ll_conn_sm *connsm)
         pktlen = pkthdr->omp_len;
         if (llid == BLE_LL_LLID_CTRL) {
             cur_txlen = pktlen;
+            ble_ll_ctrl_tx_start(connsm, m);
         } else {
             cur_txlen = ble_ll_conn_adjust_pyld_len(connsm, pktlen);
         }
@@ -1689,6 +1702,14 @@ ble_ll_conn_central_common_init(struct ble_ll_conn_sm *connsm)
     memcpy(connsm->chanmap, g_ble_ll_conn_params.central_chan_map,
            BLE_LL_CONN_CHMAP_LEN);
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    connsm->acc_subrate_min = g_ble_ll_conn_params.acc_subrate_min;
+    connsm->acc_subrate_max = g_ble_ll_conn_params.acc_subrate_max;
+    connsm->acc_max_latency = g_ble_ll_conn_params.acc_max_latency;
+    connsm->acc_cont_num = g_ble_ll_conn_params.acc_cont_num;
+    connsm->acc_supervision_tmo = g_ble_ll_conn_params.acc_supervision_tmo;
+#endif
+
     /*  Calculate random access address and crc initialization value */
     connsm->access_addr = ble_ll_utils_calc_access_addr();
     connsm->crcinit = ble_ll_rand() & 0xffffff;
@@ -2213,6 +2234,12 @@ ble_ll_conn_next_event(struct ble_ll_conn_sm *connsm)
     uint8_t next_is_subrated;
     uint16_t subrate_factor;
     uint16_t event_cntr_diff;
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    struct ble_ll_conn_subrate_params *cstp;
+    uint16_t trans_next_event_cntr;
+    uint16_t subrate_conn_upd_event_cntr;
+#endif
+
 
     /* XXX: deal with connection request procedure here as well */
     ble_ll_conn_chk_csm_flags(connsm);
@@ -2272,18 +2299,63 @@ ble_ll_conn_next_event(struct ble_ll_conn_sm *connsm)
         if (use_periph_latency) {
             next_event_cntr += subrate_factor * connsm->periph_latency;
         }
+
 #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
-        /* Make this our next base event. This is technically incorrect, because
-         * subrate base event is determined by LL_SUBRATE_IND and shall only be
-         * changed if counter wrapped, but that does not really matter as once
-         * set it's only used internally.
+        /* If we are in subrate transition mode, we should also listen on
+         * subrated connection events based on new parameters.
          */
-        connsm->subrate_base_event = next_event_cntr;
+        if (connsm->csmflags.cfbit.subrate_trans) {
+            BLE_LL_ASSERT(CONN_IS_CENTRAL(connsm));
+
+            cstp = &connsm->subrate_trans;
+            trans_next_event_cntr = cstp->subrate_base_event;
+            while (INT16_LTE(trans_next_event_cntr, connsm->event_cntr)) {
+                trans_next_event_cntr += cstp->subrate_factor;
+            }
+            cstp->subrate_base_event = trans_next_event_cntr;
+
+            if (INT16_LT(trans_next_event_cntr, next_event_cntr)) {
+                next_event_cntr = trans_next_event_cntr;
+                next_is_subrated = 0;
+            }
+        }
 #endif
     } else {
         next_event_cntr = connsm->event_cntr + 1;
     }
 
+
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    /* If connection update is scheduled, peripheral shall listen at instant
+     * and one connection event before instant regardless of subrating.
+     */
+    if (CONN_IS_PERIPHERAL(connsm) &&
+        connsm->csmflags.cfbit.conn_update_sched &&
+        (connsm->subrate_factor > 1)) {
+        subrate_conn_upd_event_cntr = connsm->conn_update_req.instant - 1;
+        if (connsm->event_cntr == subrate_conn_upd_event_cntr) {
+            subrate_conn_upd_event_cntr++;
+        }
+
+        if (INT16_GT(next_event_cntr, subrate_conn_upd_event_cntr)) {
+            next_event_cntr = subrate_conn_upd_event_cntr;
+            next_is_subrated = 0;
+        }
+    }
+
+    /* Set next connection event as a subrate base event if that connection
+     * event is a subrated event, this simplifies calculations later.
+     * Note that according to spec base event should only be changed on
+     * wrap-around, but since we only use this value internally we can use any
+     * valid value.
+     */
+    if (next_is_subrated ||
+        (connsm->subrate_base_event +
+         connsm->subrate_factor == next_event_cntr)) {
+        connsm->subrate_base_event = next_event_cntr;
+    }
+#endif
+
     event_cntr_diff = next_event_cntr - connsm->event_cntr;
     BLE_LL_ASSERT(event_cntr_diff > 0);
 
@@ -2339,6 +2411,14 @@ ble_ll_conn_next_event(struct ble_ll_conn_sm *connsm)
             connsm->csmflags.cfbit.host_expects_upd_event = 1;
         }
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+        if (connsm->conn_itvl != upd->interval) {
+            connsm->subrate_base_event = connsm->event_cntr;
+            connsm->subrate_factor = 1;
+            connsm->cont_num = 0;
+        }
+#endif
+
         connsm->supervision_tmo = upd->timeout;
         connsm->periph_latency = upd->latency;
         connsm->tx_win_size = upd->winsize;
@@ -3959,6 +4039,74 @@ err_periph_start:
 }
 #endif
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+int
+ble_ll_conn_subrate_req_llcp(struct ble_ll_conn_sm *connsm,
+                             struct ble_ll_conn_subrate_req_params *srp)
+{
+    BLE_LL_ASSERT(connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL);
+
+    if ((srp->subrate_min < 0x0001) || (srp->subrate_min > 0x01f4) ||
+        (srp->subrate_max < 0x0001) || (srp->subrate_max > 0x01f4) ||
+        (srp->max_latency > 0x01f3) || (srp->cont_num > 0x01f3) ||
+        (srp->supervision_tmo < 0x000a) || (srp->supervision_tmo > 0x0c80)) {
+        return -EINVAL;
+    }
+
+    if (connsm->cur_ctrl_proc == BLE_LL_CTRL_PROC_CONN_PARAM_REQ) {
+        return -EBUSY;
+    }
+
+    if ((srp->max_latency > connsm->acc_max_latency) ||
+        (srp->supervision_tmo > connsm->acc_supervision_tmo) ||
+        (srp->subrate_max < connsm->acc_subrate_min) ||
+        (srp->subrate_min > connsm->acc_subrate_max) ||
+        ((connsm->conn_itvl * BLE_LL_CONN_ITVL_USECS * srp->subrate_min *
+          (srp->max_latency + 1)) * 2 >= srp->supervision_tmo *
+                                         BLE_HCI_CONN_SPVN_TMO_UNITS * 1000)) {
+        return -EINVAL;
+    }
+
+    connsm->subrate_trans.subrate_factor = min(connsm->acc_subrate_max,
+                                               srp->subrate_max);
+    connsm->subrate_trans.subrate_base_event = connsm->event_cntr;
+    connsm->subrate_trans.periph_latency = min(connsm->acc_max_latency,
+                                               srp->max_latency);
+    connsm->subrate_trans.cont_num = min(max(connsm->acc_cont_num,
+                                             srp->cont_num),
+                                         connsm->subrate_trans.subrate_factor - 1);
+    connsm->subrate_trans.supervision_tmo = min(connsm->supervision_tmo,
+                                                srp->supervision_tmo);
+
+    ble_ll_ctrl_proc_start(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE, NULL);
+
+    return 0;
+}
+
+void
+ble_ll_conn_subrate_set(struct ble_ll_conn_sm *connsm,
+                        struct ble_ll_conn_subrate_params *sp)
+{
+    int16_t event_cntr_diff;
+    int16_t subrate_events_diff;
+
+    /* Assume parameters were checked by caller */
+
+    connsm->subrate_factor = sp->subrate_factor;
+    connsm->subrate_base_event = sp->subrate_base_event;
+    connsm->periph_latency = sp->periph_latency;
+    connsm->cont_num = sp->cont_num;
+    connsm->supervision_tmo = sp->supervision_tmo;
+
+    /* Let's update subrate base event to "latest" one */
+    event_cntr_diff = connsm->event_cntr - connsm->subrate_base_event;
+    subrate_events_diff = event_cntr_diff / connsm->subrate_factor;
+    connsm->subrate_base_event += connsm->subrate_factor * subrate_events_diff;
+
+    /* TODO send hci event */
+}
+#endif
+
 #define MAX_TIME_UNCODED(_maxbytes) \
         ble_ll_pdu_tx_time_get(_maxbytes + BLE_LL_DATA_MIC_LEN, \
                                BLE_PHY_MODE_1M);
@@ -4044,6 +4192,14 @@ ble_ll_conn_module_reset(void)
     memset(conn_params->central_chan_map, 0xff, BLE_LL_CONN_CHMAP_LEN - 1);
     conn_params->central_chan_map[4] = 0x1f;
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    conn_params->acc_subrate_min = 0x0001;
+    conn_params->acc_subrate_max = 0x0001;
+    conn_params->acc_max_latency = 0x0000;
+    conn_params->acc_cont_num = 0x0000;
+    conn_params->acc_supervision_tmo = 0x0c80;
+#endif
+
     /* Reset statistics */
     STATS_RESET(ble_ll_conn_stats);
 
diff --git a/nimble/controller/src/ble_ll_conn_priv.h b/nimble/controller/src/ble_ll_conn_priv.h
index 7541f642..c05e1955 100644
--- a/nimble/controller/src/ble_ll_conn_priv.h
+++ b/nimble/controller/src/ble_ll_conn_priv.h
@@ -79,6 +79,13 @@ struct ble_ll_conn_global_params
     uint16_t conn_init_max_tx_time_coded;
     uint16_t supp_max_tx_time;
     uint16_t supp_max_rx_time;
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    uint16_t acc_subrate_min;
+    uint16_t acc_subrate_max;
+    uint16_t acc_max_latency;
+    uint16_t acc_cont_num;
+    uint16_t acc_supervision_tmo;
+#endif
 #endif
 };
 extern struct ble_ll_conn_global_params g_ble_ll_conn_params;
diff --git a/nimble/controller/src/ble_ll_ctrl.c b/nimble/controller/src/ble_ll_ctrl.c
index 04b93543..f29d3154 100644
--- a/nimble/controller/src/ble_ll_ctrl.c
+++ b/nimble/controller/src/ble_ll_ctrl.c
@@ -19,6 +19,7 @@
 #include <stdint.h>
 #include <assert.h>
 #include <string.h>
+#include <errno.h>
 #include "syscfg/syscfg.h"
 #include "nimble/ble.h"
 #include "nimble/nimble_opt.h"
@@ -408,7 +409,12 @@ ble_ll_ctrl_conn_upd_make(struct ble_ll_conn_sm *connsm, uint8_t *pyld,
      * request will actually get sent. We add one more event plus the
      * minimum as per the spec of 6 connection events.
      */
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    instant = connsm->subrate_base_event + 6 * connsm->subrate_factor *
+                                           (connsm->periph_latency + 1);
+#else
     instant = connsm->event_cntr + connsm->periph_latency + 6 + 1;
+#endif
 
     /*
      * XXX: This should change in the future, but for now we will just
@@ -1144,6 +1150,126 @@ ble_ll_ctrl_rx_sca_rsp(struct ble_ll_conn_sm *connsm, uint8_t *dptr)
 
 #endif
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+static void
+ble_ll_ctrl_subrate_req_make(struct ble_ll_conn_sm *connsm, uint8_t *pyld,
+                             struct ble_ll_conn_subrate_req_params *srp)
+{
+    put_le16(pyld + 0, srp->subrate_min);
+    put_le16(pyld + 2, srp->subrate_max);
+    put_le16(pyld + 4, srp->max_latency);
+    put_le16(pyld + 6, srp->cont_num);
+    put_le16(pyld + 8, srp->supervision_tmo);
+}
+
+static void
+ble_ll_ctrl_subrate_ind_make(struct ble_ll_conn_sm *connsm, uint8_t *pyld,
+                             struct ble_ll_conn_subrate_params *sp)
+{
+    put_le16(pyld + 0, sp->subrate_factor);
+    put_le16(pyld + 2, sp->subrate_base_event);
+    put_le16(pyld + 4, sp->periph_latency);
+    put_le16(pyld + 6, sp->cont_num);
+    put_le16(pyld + 8, sp->supervision_tmo);
+}
+
+static uint8_t
+ble_ll_ctrl_rx_subrate_req(struct ble_ll_conn_sm *connsm, uint8_t *req,
+                           uint8_t *rsp)
+{
+    struct ble_ll_conn_subrate_req_params params;
+    struct ble_ll_conn_subrate_req_params *srp = &params;
+    uint8_t err;
+    int rc;
+
+#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
+    if (connsm->conn_role == BLE_LL_CONN_ROLE_PERIPHERAL) {
+        return BLE_LL_CTRL_UNKNOWN_RSP;
+    }
+#endif
+
+    if ((ble_ll_read_supp_features() & BLE_LL_FEAT_CONN_SUBRATING_HOST) == 0) {
+        ble_ll_ctrl_rej_ext_ind_make(BLE_LL_CTRL_SUBRATE_REQ,
+                                     BLE_ERR_UNSUPP_REM_FEATURE, rsp);
+        return BLE_LL_CTRL_REJECT_IND_EXT;
+    }
+
+    srp->subrate_min = get_le16(req + 0);
+    srp->subrate_max = get_le16(req + 2);
+    srp->max_latency = get_le16(req + 4);
+    srp->cont_num = get_le16(req + 6);
+    srp->supervision_tmo = get_le16(req + 8);
+
+    rc = ble_ll_conn_subrate_req_llcp(connsm, srp);
+    if (rc < 0) {
+        if (rc == -EINVAL) {
+            err = BLE_ERR_INV_LMP_LL_PARM;
+        } else if (rc == -ENOTSUP) {
+            err = BLE_ERR_UNSUPP_REM_FEATURE;
+        } else if (rc == -EBUSY) {
+            err = BLE_ERR_DIFF_TRANS_COLL;
+        } else {
+            err = BLE_ERR_UNSPECIFIED;
+        }
+
+        ble_ll_ctrl_rej_ext_ind_make(BLE_LL_CTRL_SUBRATE_REQ, err, rsp);
+
+        return BLE_LL_CTRL_REJECT_IND_EXT;
+    }
+
+    return BLE_ERR_MAX;
+}
+
+static uint8_t
+ble_ll_ctrl_rx_subrate_ind(struct ble_ll_conn_sm *connsm, uint8_t *req,
+                           uint8_t *rsp)
+{
+    struct ble_ll_conn_subrate_params params;
+    struct ble_ll_conn_subrate_params *sp = &params;
+    uint32_t t1, t2;
+
+#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
+    if (connsm->conn_role == BLE_LL_CONN_ROLE_CENTRAL) {
+        return BLE_LL_CTRL_UNKNOWN_RSP;
+    }
+#endif
+
+    sp->subrate_factor = get_le16(req + 0);
+    sp->subrate_base_event = get_le16(req + 2);
+    sp->periph_latency = get_le16(req + 4);
+    sp->cont_num = get_le16(req + 6);
+    sp->supervision_tmo = get_le16(req + 8);
+
+    /* This is probably not really useful since we shall apply new parameters
+     * immediately after receiving LL_SUBRATE_IND and central shall apply those
+     * parameters after receiving ack which it already did, so it's too late
+     * here to do anything useful. Let's just send LL_REJECT_EXT_IND anyway just
+     * for debugging purposes and reset to subrate factor of 1 and no latency,
+     * perhaps we can find some connection event from central and send our PDU.
+     */
+    t1 = connsm->conn_itvl * sp->subrate_factor * (sp->periph_latency + 1) *
+         BLE_LL_CONN_ITVL_USECS;
+    t2 = sp->supervision_tmo * BLE_HCI_CONN_SPVN_TMO_UNITS * 1000 / 2;
+    if ((sp->subrate_factor < 1) || (sp->subrate_factor > 500) ||
+        (sp->cont_num > sp->subrate_factor - 1) ||
+        (sp->subrate_factor * (sp->periph_latency + 1) > 500) || (t1 >= t2)) {
+
+        sp->subrate_factor = 1;
+        sp->subrate_base_event = connsm->event_cntr;
+        sp->periph_latency = 0;
+        sp->cont_num = 0;
+        sp->supervision_tmo = connsm->supervision_tmo;
+
+        return BLE_ERR_MAX;
+    }
+
+    ble_ll_conn_subrate_set(connsm, sp);
+    ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_SUBRATE_REQ);
+
+    return BLE_ERR_MAX;
+}
+#endif
+
 #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ISO)
 static uint8_t
 ble_ll_ctrl_rx_cis_req(struct ble_ll_conn_sm *connsm, uint8_t *dptr,
@@ -1865,6 +1991,13 @@ ble_ll_ctrl_rx_reject_ind(struct ble_ll_conn_sm *connsm, uint8_t *dptr,
         ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_SCA_UPDATE);
         break;
 #endif
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    case BLE_LL_CTRL_PROC_SUBRATE_REQ:
+        /* TODO: send event to host */
+        ble_ll_ctrl_proc_stop(connsm, BLE_LL_CTRL_PROC_SUBRATE_UPDATE);
+        break;
+#endif
+
     default:
         break;
     }
@@ -2376,6 +2509,21 @@ ble_ll_ctrl_proc_init(struct ble_ll_conn_sm *connsm, int ctrl_proc, void *data)
             opcode = BLE_LL_CTRL_CIS_REQ;
             ble_ll_ctrl_cis_create(connsm, ctrdata);
             break;
+#endif
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
+        case BLE_LL_CTRL_PROC_SUBRATE_REQ:
+            opcode = BLE_LL_CTRL_SUBRATE_REQ;
+            ble_ll_ctrl_subrate_req_make(connsm, ctrdata, &connsm->subrate_req);
+            break;
+#endif
+#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
+        case BLE_LL_CTRL_PROC_SUBRATE_UPDATE:
+            opcode = BLE_LL_CTRL_SUBRATE_IND;
+            ble_ll_ctrl_subrate_ind_make(connsm, ctrdata,
+                                         &connsm->subrate_trans);
+            break;
+#endif
 #endif
         default:
             BLE_LL_ASSERT(0);
@@ -2840,6 +2988,14 @@ ble_ll_ctrl_rx_pdu(struct ble_ll_conn_sm *connsm, struct os_mbuf *om)
     case BLE_LL_CTRL_PERIODIC_SYNC_IND:
         rsp_opcode = ble_ll_ctrl_rx_periodic_sync_ind(connsm, dptr);
         break;
+#endif
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+    case BLE_LL_CTRL_SUBRATE_REQ:
+        rsp_opcode = ble_ll_ctrl_rx_subrate_req(connsm, dptr, rspdata);
+        break;
+    case BLE_LL_CTRL_SUBRATE_IND:
+        rsp_opcode = ble_ll_ctrl_rx_subrate_ind(connsm, dptr, rspdata);
+        break;
 #endif
     default:
         /* Nothing to do here */
@@ -2924,6 +3080,21 @@ ble_ll_ctrl_reject_ind_send(struct ble_ll_conn_sm *connsm, uint8_t rej_opcode,
     return rc;
 }
 
+int
+ble_ll_ctrl_tx_start(struct ble_ll_conn_sm *connsm, struct os_mbuf *txpdu)
+{
+    uint8_t opcode;
+
+    opcode = txpdu->om_data[0];
+    switch (opcode) {
+    case BLE_LL_CTRL_SUBRATE_IND:
+        connsm->csmflags.cfbit.subrate_trans = 1;
+        break;
+    }
+
+    return 0;
+}
+
 /**
  * Called when a Link Layer Control pdu has been transmitted successfully.
  * This is called when we have a received a PDU during the ISR.
@@ -3017,6 +3188,14 @@ ble_ll_ctrl_tx_done(struct os_mbuf *txpdu, struct ble_ll_conn_sm *connsm)
                     ble_ll_ctrl_phy_tx_transition_get(txpdu->om_data[2]);
         break;
 #endif
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE)
+#if MYNEWT_VAL(BLE_LL_ROLE_CENTRAL)
+    case BLE_LL_CTRL_SUBRATE_IND:
+        connsm->csmflags.cfbit.subrate_trans = 0;
+        connsm->csmflags.cfbit.subrate_ind_txd = 1;
+        break;
+#endif /* BLE_LL_CTRL_SUBRATE_IND */
+#endif /* BLE_LL_CFG_FEAT_LL_ENHANCED_CONN_UPDATE */
     default:
         break;
     }