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 2020/05/18 09:27:21 UTC

[mynewt-nimble] 02/02: nimble/ll: Add controller-to-host flow control support

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 8341bfa7f5844ec1fb379efcd5c5935141e9e0a6
Author: Andrzej Kaczmarek <an...@codecoup.pl>
AuthorDate: Mon May 4 15:56:09 2020 +0200

    nimble/ll: Add controller-to-host flow control support
---
 nimble/controller/include/controller/ble_ll.h      |   6 +
 nimble/controller/include/controller/ble_ll_conn.h |   4 +
 nimble/controller/src/ble_ll_conn.c                | 216 ++++++++++++++++++++-
 nimble/controller/src/ble_ll_conn_priv.h           |   6 +
 nimble/controller/src/ble_ll_hci.c                 | 102 +++++++++-
 nimble/controller/src/ble_ll_supp_cmd.c            |  17 +-
 nimble/controller/syscfg.yml                       |   7 +
 nimble/include/nimble/ble.h                        |   9 +-
 8 files changed, 354 insertions(+), 13 deletions(-)

diff --git a/nimble/controller/include/controller/ble_ll.h b/nimble/controller/include/controller/ble_ll.h
index 47d3ac4..5f9e236 100644
--- a/nimble/controller/include/controller/ble_ll.h
+++ b/nimble/controller/include/controller/ble_ll.h
@@ -396,6 +396,12 @@ struct ble_dev_addr
 #define BLE_LL_LLID_DATA_START          (2)
 #define BLE_LL_LLID_CTRL                (3)
 
+#define BLE_LL_LLID_IS_CTRL(hdr) \
+    (((hdr) & BLE_LL_DATA_HDR_LLID_MASK) == BLE_LL_LLID_CTRL)
+#define BLE_LL_LLID_IS_DATA(hdr) \
+    ((((hdr) & BLE_LL_DATA_HDR_LLID_MASK) == BLE_LL_LLID_DATA_START) || \
+     (((hdr) & BLE_LL_DATA_HDR_LLID_MASK) == BLE_LL_LLID_DATA_FRAG))
+
 /*
  * CONNECT_REQ
  *      -> InitA        (6 bytes)
diff --git a/nimble/controller/include/controller/ble_ll_conn.h b/nimble/controller/include/controller/ble_ll_conn.h
index 94ea42c..8c07462 100644
--- a/nimble/controller/include/controller/ble_ll_conn.h
+++ b/nimble/controller/include/controller/ble_ll_conn.h
@@ -272,6 +272,10 @@ struct ble_ll_conn_sm
     uint8_t last_rxd_hdr_byte;  /* note: possibly can make 1 bit since we
                                    only use the MD bit now */
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    uint16_t cth_flow_pending;
+#endif
+
     /* connection event mgmt */
     uint8_t reject_reason;
     uint8_t host_reply_opcode;
diff --git a/nimble/controller/src/ble_ll_conn.c b/nimble/controller/src/ble_ll_conn.c
index c5a2a41..8799ff2 100644
--- a/nimble/controller/src/ble_ll_conn.c
+++ b/nimble/controller/src/ble_ll_conn.c
@@ -225,6 +225,166 @@ STATS_NAME_END(ble_ll_conn_stats)
 
 static void ble_ll_conn_event_end(struct ble_npl_event *ev);
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+struct ble_ll_conn_cth_flow {
+    bool enabled;
+    uint16_t max_buffers;
+    uint16_t num_buffers;
+};
+
+static struct ble_ll_conn_cth_flow g_ble_ll_conn_cth_flow;
+
+static struct ble_npl_event g_ble_ll_conn_cth_flow_error_ev;
+
+static bool
+ble_ll_conn_cth_flow_is_enabled(void)
+{
+    return g_ble_ll_conn_cth_flow.enabled;
+}
+
+static bool
+ble_ll_conn_cth_flow_alloc_credit(struct ble_ll_conn_sm *connsm)
+{
+    struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
+    os_sr_t sr;
+
+    OS_ENTER_CRITICAL(sr);
+
+    if (!cth->num_buffers) {
+        OS_EXIT_CRITICAL(sr);
+        return false;
+    }
+
+    connsm->cth_flow_pending++;
+    cth->num_buffers--;
+
+    OS_EXIT_CRITICAL(sr);
+
+    return true;
+}
+
+static void
+ble_ll_conn_cth_flow_free_credit(struct ble_ll_conn_sm *connsm, uint16_t credits)
+{
+    struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
+    os_sr_t sr;
+
+    OS_ENTER_CRITICAL(sr);
+
+    /*
+     * It's not quite clear what we should do if host gives back more credits
+     * that we have allocated. For now let's just set invalid values back to
+     * sane values and continue.
+     */
+
+    cth->num_buffers += credits;
+    if (cth->num_buffers > cth->max_buffers) {
+        cth->num_buffers = cth->max_buffers;
+    }
+
+    if (connsm->cth_flow_pending < credits) {
+        connsm->cth_flow_pending = 0;
+    } else {
+        connsm->cth_flow_pending -= credits;
+    }
+
+    OS_EXIT_CRITICAL(sr);
+}
+
+static void
+ble_ll_conn_cth_flow_error_fn(struct ble_npl_event *ev)
+{
+    struct ble_hci_ev *hci_ev;
+    struct ble_hci_ev_command_complete *hci_ev_cp;
+    uint16_t opcode;
+
+    hci_ev = (void *)ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI);
+    if (!hci_ev) {
+        /* Not much we can do anyway... */
+        return;
+    }
+
+    /*
+     * We are here in case length of HCI_Host_Number_Of_Completed_Packets was
+     * invalid. We will send an error back to host and we can only hope host is
+     * reasonable and will do some actions to recover, e.g. it should disconnect
+     * all connections to guarantee that all credits are back in pool and we're
+     * back in sync (although spec does not really say what should happen).
+     */
+
+    opcode = BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND,
+                        BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS);
+
+    hci_ev->opcode = BLE_HCI_EVCODE_COMMAND_COMPLETE;
+    hci_ev->length = sizeof(*hci_ev_cp);
+
+    hci_ev_cp = (void *)hci_ev->data;
+    hci_ev_cp->num_packets = BLE_LL_CFG_NUM_HCI_CMD_PKTS;
+    hci_ev_cp->opcode = htole16(opcode);
+    hci_ev_cp->status = BLE_ERR_INV_HCI_CMD_PARMS;
+
+    ble_ll_hci_event_send(hci_ev);
+}
+
+void
+ble_ll_conn_cth_flow_set_buffers(uint16_t num_buffers)
+{
+    BLE_LL_ASSERT(num_buffers);
+
+    g_ble_ll_conn_cth_flow.max_buffers = num_buffers;
+    g_ble_ll_conn_cth_flow.num_buffers = num_buffers;
+}
+
+bool
+ble_ll_conn_cth_flow_enable(bool enabled)
+{
+    struct ble_ll_conn_cth_flow *cth = &g_ble_ll_conn_cth_flow;
+
+    if (cth->enabled == enabled) {
+        return true;
+    }
+
+    if (!SLIST_EMPTY(&g_ble_ll_conn_active_list)) {
+        return false;
+    }
+
+    cth->enabled = enabled;
+
+    return true;
+}
+
+void
+ble_ll_conn_cth_flow_process_cmd(const uint8_t *cmdbuf)
+{
+    const struct ble_hci_cmd *cmd;
+    const struct ble_hci_cb_host_num_comp_pkts_cp *cp;
+    struct ble_ll_conn_sm *connsm;
+    int i;
+
+    cmd = (const void *)cmdbuf;
+    cp = (const void *)cmd->data;
+
+    if (cmd->length != sizeof(cp->handles) + cp->handles * sizeof(cp->h[0])) {
+        ble_npl_eventq_put(&g_ble_ll_data.ll_evq, &g_ble_ll_conn_cth_flow_error_ev);
+        return;
+    }
+
+    for (i = 0; i < cp->handles; i++) {
+        /*
+         * It's probably ok that we do not have active connection with given
+         * handle - this can happen if disconnection already happened in LL but
+         * host sent credits back before processing disconnection event. In such
+         * case we can simply ignore command for that connection since credits
+         * are returned by LL already.
+         */
+        connsm = ble_ll_conn_find_active_conn(cp->h[i].handle);
+        if (connsm) {
+            ble_ll_conn_cth_flow_free_credit(connsm, cp->h[i].count);
+        }
+    }
+}
+#endif
+
 #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
 /**
  * Checks to see if we should start a PHY update procedure
@@ -1861,6 +2021,10 @@ ble_ll_conn_end(struct ble_ll_conn_sm *connsm, uint8_t ble_err)
     /* Remove from the active connection list */
     SLIST_REMOVE(&g_ble_ll_conn_active_list, connsm, ble_ll_conn_sm, act_sle);
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_HOST_TO_CTRL_FLOW_CONTROL)
+    ble_ll_conn_cth_flow_free_credit(connsm, connsm->cth_flow_pending);
+#endif
+
     /* Free the current transmit pdu if there is one. */
     if (connsm->cur_tx_pdu) {
         os_mbuf_free_chain(connsm->cur_tx_pdu);
@@ -3577,6 +3741,13 @@ ble_ll_conn_rx_data_pdu(struct os_mbuf *rxpdu, struct ble_mbuf_hdr *hdr)
 
     /* Free buffer */
 conn_rx_data_pdu_end:
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    /* Need to give credit back if we allocated one for this PDU */
+    if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_CONN_CREDIT) {
+        ble_ll_conn_cth_flow_free_credit(connsm, 1);
+    }
+#endif
+
     os_mbuf_free_chain(rxpdu);
 }
 
@@ -3597,7 +3768,6 @@ int
 ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
 {
     int rc;
-    int is_ctrl;
     uint8_t hdr_byte;
     uint8_t hdr_sn;
     uint8_t hdr_nesn;
@@ -3616,6 +3786,9 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
     int rx_phy_mode;
     bool alloc_rxpdu = true;
 
+    rc = -1;
+    connsm = g_ble_ll_conn_cur_sm;
+
     /* Retrieve the header and payload length */
     hdr_byte = rxbuf[0];
     rx_pyld_len = rxbuf[1];
@@ -3628,6 +3801,23 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
         alloc_rxpdu = false;
     }
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    /*
+     * If flow control is enabled, we need to have credit available for each
+     * non-empty data packet that LL may send to host. If there are no credits
+     * available, we don't need to allocate buffer for this packet so LL will
+     * nak it.
+     */
+    if (ble_ll_conn_cth_flow_is_enabled() &&
+        BLE_LL_LLID_IS_DATA(hdr_byte) && (rx_pyld_len > 0)) {
+        if (ble_ll_conn_cth_flow_alloc_credit(connsm)) {
+            rxhdr->rxinfo.flags |= BLE_MBUF_HDR_F_CONN_CREDIT;
+        } else {
+            alloc_rxpdu = false;
+        }
+    }
+#endif
+
     /*
      * We need to attempt to allocate a buffer here. The reason we do this
      * now is that we should not ack the packet if we have no receive
@@ -3643,8 +3833,6 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
      * We should have a current connection state machine. If we dont, we just
      * hand the packet to the higher layer to count it.
      */
-    rc = -1;
-    connsm = g_ble_ll_conn_cur_sm;
     if (!connsm) {
         STATS_INC(ble_ll_conn_stats, rx_data_pdu_no_conn);
         goto conn_exit;
@@ -3706,9 +3894,7 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
         /* Set last received header byte */
         connsm->last_rxd_hdr_byte = hdr_byte;
 
-        is_ctrl = 0;
-        if ((hdr_byte & BLE_LL_DATA_HDR_LLID_MASK) == BLE_LL_LLID_CTRL) {
-            is_ctrl = 1;
+        if (BLE_LL_LLID_IS_CTRL(hdr_byte)) {
             opcode = rxbuf[2];
         }
 
@@ -3797,7 +3983,7 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
                         /* Adjust payload for max TX time and octets */
 
 #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
-                        if (is_ctrl &&
+                        if (BLE_LL_LLID_IS_CTRL(hdr_byte) &&
                             (connsm->conn_role == BLE_LL_CONN_ROLE_SLAVE) &&
                             (opcode == BLE_LL_CTRL_PHY_UPDATE_IND)) {
                             connsm->phy_tx_transition =
@@ -3816,8 +4002,9 @@ ble_ll_conn_rx_isr_end(uint8_t *rxbuf, struct ble_mbuf_hdr *rxhdr)
         /* If this is a TERMINATE_IND, we have to reply */
 chk_rx_terminate_ind:
         /* If we received a terminate IND, we must set some flags */
-        if (is_ctrl && (opcode == BLE_LL_CTRL_TERMINATE_IND)
-                    && (rx_pyld_len == (1 + BLE_LL_CTRL_TERMINATE_IND_LEN))) {
+        if (BLE_LL_LLID_IS_CTRL(hdr_byte) &&
+            (opcode == BLE_LL_CTRL_TERMINATE_IND) &&
+            (rx_pyld_len == (1 + BLE_LL_CTRL_TERMINATE_IND_LEN))) {
             connsm->csmflags.cfbit.terminate_ind_rxd = 1;
             connsm->rxd_disconnect_reason = rxbuf[3];
         }
@@ -4239,6 +4426,12 @@ ble_ll_conn_module_reset(void)
     g_ble_ll_conn_sync_transfer_params.mode = 0;
     g_ble_ll_conn_sync_transfer_params.sync_timeout_us = 0;
 #endif
+
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    g_ble_ll_conn_cth_flow.enabled = false;
+    g_ble_ll_conn_cth_flow.max_buffers = 1;
+    g_ble_ll_conn_cth_flow.num_buffers = 1;
+#endif
 }
 
 /* Initialize the connection module */
@@ -4278,6 +4471,11 @@ ble_ll_conn_module_init(void)
                             "ble_ll_conn");
     BLE_LL_ASSERT(rc == 0);
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    ble_npl_event_init(&g_ble_ll_conn_cth_flow_error_ev,
+                       ble_ll_conn_cth_flow_error_fn, NULL);
+#endif
+
     /* Call reset to finish reset of initialization */
     ble_ll_conn_module_reset();
 }
diff --git a/nimble/controller/src/ble_ll_conn_priv.h b/nimble/controller/src/ble_ll_conn_priv.h
index 9891bfa..53358c4 100644
--- a/nimble/controller/src/ble_ll_conn_priv.h
+++ b/nimble/controller/src/ble_ll_conn_priv.h
@@ -207,6 +207,12 @@ void ble_ll_conn_auth_pyld_timer_start(struct ble_ll_conn_sm *connsm);
 #define ble_ll_conn_auth_pyld_timer_start(x)
 #endif
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+void ble_ll_conn_cth_flow_set_buffers(uint16_t num_buffers);
+bool ble_ll_conn_cth_flow_enable(bool enabled);
+void ble_ll_conn_cth_flow_process_cmd(const uint8_t *cmdbuf);
+#endif
+
 int ble_ll_hci_cmd_rx(uint8_t *cmd, void *arg);
 int ble_ll_hci_acl_rx(struct os_mbuf *om, void *arg);
 
diff --git a/nimble/controller/src/ble_ll_hci.c b/nimble/controller/src/ble_ll_hci.c
index c42e24d..a3da98d 100644
--- a/nimble/controller/src/ble_ll_hci.c
+++ b/nimble/controller/src/ble_ll_hci.c
@@ -1343,6 +1343,64 @@ ble_ll_hci_cb_set_event_mask(const uint8_t *cmdbuf, uint8_t len)
     return BLE_ERR_SUCCESS;
 }
 
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+static int
+ble_ll_hci_cb_set_ctrlr_to_host_fc(const uint8_t *cmdbuf, uint8_t len)
+{
+    const struct ble_hci_cb_ctlr_to_host_fc_cp *cmd = (const void *) cmdbuf;
+
+    if (len != sizeof (*cmd)) {
+        return BLE_ERR_INV_HCI_CMD_PARMS;
+    }
+
+    /* We only allow to either disable flow control or enable for ACL only */
+    if (cmd->enable > 1) {
+        return BLE_ERR_INV_HCI_CMD_PARMS;
+    }
+
+    if (!ble_ll_conn_cth_flow_enable(cmd->enable)) {
+        return BLE_ERR_CMD_DISALLOWED;
+    }
+
+    return BLE_ERR_SUCCESS;
+}
+
+static int
+ble_ll_hci_cb_host_buf_size(const uint8_t *cmdbuf, uint8_t len)
+{
+    const struct ble_hci_cb_host_buf_size_cp *cmd = (const void *) cmdbuf;
+    uint16_t acl_num;
+    uint16_t acl_data_len;
+
+    if (len != sizeof (*cmd)) {
+        return BLE_ERR_INV_HCI_CMD_PARMS;
+    }
+
+    /* We do not support SCO so those parameters should be set to 0 */
+    if (cmd->sco_num || cmd->sco_data_len) {
+        return BLE_ERR_INV_HCI_CMD_PARMS;
+    }
+
+    /*
+     * Core 5.2 Vol 4 Part E section 7.3.39 states that "Both the Host and the
+     * Controller shall support command and event packets, where the data portion
+     * (excluding header) contained in the packets is 255 octets in size.".
+     * This means we can basically accept any allowed value since LL does not
+     * reassemble incoming data thus will not send more than 255 octets in single
+     * data packet.
+     */
+    acl_num = le16toh(cmd->acl_num);
+    acl_data_len = le16toh(cmd->acl_data_len);
+    if (acl_data_len < 255) {
+        return BLE_ERR_INV_HCI_CMD_PARMS;
+    }
+
+    ble_ll_conn_cth_flow_set_buffers(acl_num);
+
+    return BLE_ERR_SUCCESS;
+}
+#endif
+
 static int
 ble_ll_hci_cb_set_event_mask2(const uint8_t *cmdbuf, uint8_t len)
 {
@@ -1375,6 +1433,22 @@ ble_ll_hci_ctlr_bb_cmd_proc(const uint8_t *cmdbuf, uint8_t len, uint16_t ocf,
             rc = ble_ll_reset();
         }
         break;
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    case BLE_HCI_OCF_CB_SET_CTLR_TO_HOST_FC:
+        rc = ble_ll_hci_cb_set_ctrlr_to_host_fc(cmdbuf, len);
+        break;
+    case BLE_HCI_OCF_CB_HOST_BUF_SIZE:
+        rc = ble_ll_hci_cb_host_buf_size(cmdbuf, len);
+        break;
+    case BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS:
+        /*
+         * HCI_Host_Number_Of_Completed_Packets is handled immediately when
+         * received from transport so we should never receive it here.
+         */
+        BLE_LL_ASSERT(0);
+        rc = BLE_ERR_UNKNOWN_HCI_CMD;
+        break;
+#endif
     case BLE_HCI_OCF_CB_SET_EVENT_MASK2:
         rc = ble_ll_hci_cb_set_event_mask2(cmdbuf, len);
         break;
@@ -1570,9 +1644,33 @@ ble_ll_hci_cmd_proc(struct ble_npl_event *ev)
  *                              BLE_ERR_MEM_CAPACITY on HCI buffer exhaustion.
  */
 int
-ble_ll_hci_cmd_rx(uint8_t *cmd, void *arg)
+ble_ll_hci_cmd_rx(uint8_t *cmdbuf, void *arg)
 {
     struct ble_npl_event *ev;
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+    const struct ble_hci_cmd *cmd;
+    uint16_t opcode;
+    uint16_t ocf;
+    uint16_t ogf;
+
+    cmd = (const void *)cmdbuf;
+    opcode = le16toh(cmd->opcode);
+    ogf = BLE_HCI_OGF(opcode);
+    ocf = BLE_HCI_OCF(opcode);
+
+    /*
+     * HCI_Host_Number_Of_Completed_Packets is processed outside standard flow
+     * thus it can be sent at any time, even if another command is already
+     * pending. This means we should better process it here and send an event to
+     * LL in case of error.
+     */
+    if ((ogf == BLE_HCI_OGF_CTLR_BASEBAND) &&
+        (ocf == BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS)) {
+        ble_ll_conn_cth_flow_process_cmd(cmdbuf);
+        ble_hci_trans_buf_free(cmdbuf);
+        return 0;
+    }
+#endif
 
     /* Get an event structure off the queue */
     ev = &g_ble_ll_hci_cmd_ev;
@@ -1581,7 +1679,7 @@ ble_ll_hci_cmd_rx(uint8_t *cmd, void *arg)
     }
 
     /* Fill out the event and post to Link Layer */
-    ble_npl_event_set_arg(ev, cmd);
+    ble_npl_event_set_arg(ev, cmdbuf);
     ble_npl_eventq_put(&g_ble_ll_data.ll_evq, ev);
 
     return 0;
diff --git a/nimble/controller/src/ble_ll_supp_cmd.c b/nimble/controller/src/ble_ll_supp_cmd.c
index 863e698..cae9eb7 100644
--- a/nimble/controller/src/ble_ll_supp_cmd.c
+++ b/nimble/controller/src/ble_ll_supp_cmd.c
@@ -36,7 +36,22 @@
 
 /* Octet 10 */
 #define BLE_SUPP_CMD_RD_TX_PWR              (0 << 2)
-#define BLE_LL_SUPP_CMD_OCTET_10            (BLE_SUPP_CMD_RD_TX_PWR)
+#if MYNEWT_VAL(BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL)
+#define BLE_SUPP_CMD_SET_CTRL_TO_HOST_FLOW  (1 << 5)
+#define BLE_SUPP_CMD_HOST_BUFFER_SIZE       (1 << 6)
+#define BLE_SUPP_CMD_HOST_NUM_COMP_PACKETS  (1 << 7)
+#else
+#define BLE_SUPP_CMD_SET_CTRL_TO_HOST_FLOW  (0 << 5)
+#define BLE_SUPP_CMD_HOST_BUFFER_SIZE       (0 << 6)
+#define BLE_SUPP_CMD_HOST_NUM_COMP_PACKETS  (0 << 7)
+#endif
+#define BLE_LL_SUPP_CMD_OCTET_10            \
+(                                           \
+    BLE_SUPP_CMD_RD_TX_PWR              |   \
+    BLE_SUPP_CMD_SET_CTRL_TO_HOST_FLOW  |   \
+    BLE_SUPP_CMD_HOST_BUFFER_SIZE       |   \
+    BLE_SUPP_CMD_HOST_NUM_COMP_PACKETS      \
+)
 
 /* Octet 14 */
 #define BLE_SUPP_CMD_RD_LOC_VER             (1 << 3)
diff --git a/nimble/controller/syscfg.yml b/nimble/controller/syscfg.yml
index ea04388..2c7c2cb 100644
--- a/nimble/controller/syscfg.yml
+++ b/nimble/controller/syscfg.yml
@@ -260,6 +260,13 @@ syscfg.defs:
             Advertising Sync Transfer Feature.
         value: MYNEWT_VAL(BLE_PERIODIC_ADV_SYNC_TRANSFER)
 
+    BLE_LL_CFG_FEAT_CTRL_TO_HOST_FLOW_CONTROL:
+        description: >
+            Enable controller-to-host flow control support. This allows host to
+            limit number of ACL packets sent at once from controller to avoid
+            congestion on HCI transport if feature is also supported by host.
+        value: 0
+
     BLE_LL_CFG_FEAT_LL_SCA_UPDATE:
         description: >
             This option is used to enable/disable support for SCA update procedure
diff --git a/nimble/include/nimble/ble.h b/nimble/include/nimble/ble.h
index 3fc2902..248e8ae 100644
--- a/nimble/include/nimble/ble.h
+++ b/nimble/include/nimble/ble.h
@@ -81,7 +81,14 @@ struct ble_mbuf_hdr_rxinfo
 #endif
 };
 
-/* Flag definitions for rxinfo  */
+/*
+ * Flag definitions for rxinfo
+ *
+ * Note: it's ok to have symbols with the same values as long as they cannot be
+ *       set for the same PDU (e.g. one use by scanner, other one used by
+ *       connection)
+ */
+#define BLE_MBUF_HDR_F_CONN_CREDIT      (0x8000)
 #define BLE_MBUF_HDR_F_IGNORED          (0x8000)
 #define BLE_MBUF_HDR_F_SCAN_REQ_TXD     (0x4000)
 #define BLE_MBUF_HDR_F_INITA_RESOLVED   (0x2000)