diff options
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c')
| -rw-r--r-- | src/libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c new file mode 100644 index 0000000..d224e6e --- /dev/null +++ b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_flow.c @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "syscfg/syscfg.h" +#include "nimble/ble_hci_trans.h" +#include "ble_hs_priv.h" + +#if MYNEWT_VAL(BLE_HS_FLOW_CTRL) + +#define BLE_HS_FLOW_ITVL_TICKS \ + ble_npl_time_ms_to_ticks32(MYNEWT_VAL(BLE_HS_FLOW_CTRL_ITVL)) + +/** + * The number of freed buffers since the most-recent + * number-of-completed-packets event was sent. This is used to determine if an + * immediate event transmission is required. + */ +static uint16_t ble_hs_flow_num_completed_pkts; + +/** Periodically sends number-of-completed-packets events. */ +static struct ble_npl_callout ble_hs_flow_timer; + +static ble_npl_event_fn ble_hs_flow_event_cb; + +static struct ble_npl_event ble_hs_flow_ev; + +static int +ble_hs_flow_tx_num_comp_pkts(void) +{ + uint8_t buf[ + sizeof(struct ble_hci_cb_host_num_comp_pkts_cp) + + sizeof(struct ble_hci_cb_host_num_comp_pkts_entry) + ]; + struct ble_hci_cb_host_num_comp_pkts_cp *cmd = (void *) buf; + struct ble_hs_conn *conn; + int rc; + + BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task()); + + /* For each connection with completed packets, send a separate + * host-number-of-completed-packets command. + */ + for (conn = ble_hs_conn_first(); + conn != NULL; + conn = SLIST_NEXT(conn, bhc_next)) { + + if (conn->bhc_completed_pkts > 0) { + /* Only specify one connection per command. */ + /* TODO could combine this in single HCI command */ + cmd->handles = 1; + + /* Append entry for this connection. */ + cmd->h[0].handle = htole16(conn->bhc_handle); + cmd->h[0].count = htole16(conn->bhc_completed_pkts); + + conn->bhc_completed_pkts = 0; + + /* The host-number-of-completed-packets command does not elicit a + * response from the controller, so don't use the normal blocking + * HCI API when sending it. + */ + rc = ble_hs_hci_cmd_send_buf( + BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND, + BLE_HCI_OCF_CB_HOST_NUM_COMP_PKTS), + buf, sizeof(buf)); + if (rc != 0) { + return rc; + } + } + } + + return 0; +} + +static void +ble_hs_flow_event_cb(struct ble_npl_event *ev) +{ + int rc; + + ble_hs_lock(); + + if (ble_hs_flow_num_completed_pkts > 0) { + rc = ble_hs_flow_tx_num_comp_pkts(); + if (rc != 0) { + ble_hs_sched_reset(rc); + } + + ble_hs_flow_num_completed_pkts = 0; + } + + ble_hs_unlock(); +} + +static void +ble_hs_flow_inc_completed_pkts(struct ble_hs_conn *conn) +{ + uint16_t num_free; + + int rc; + + BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task()); + + conn->bhc_completed_pkts++; + ble_hs_flow_num_completed_pkts++; + + if (ble_hs_flow_num_completed_pkts > MYNEWT_VAL(BLE_ACL_BUF_COUNT)) { + ble_hs_sched_reset(BLE_HS_ECONTROLLER); + return; + } + + /* If the number of free buffers is at or below the configured threshold, + * send an immediate number-of-copmleted-packets event. + */ + num_free = MYNEWT_VAL(BLE_ACL_BUF_COUNT) - ble_hs_flow_num_completed_pkts; + if (num_free <= MYNEWT_VAL(BLE_HS_FLOW_CTRL_THRESH)) { + ble_npl_eventq_put(ble_hs_evq_get(), &ble_hs_flow_ev); + ble_npl_callout_stop(&ble_hs_flow_timer); + } else if (ble_hs_flow_num_completed_pkts == 1) { + rc = ble_npl_callout_reset(&ble_hs_flow_timer, BLE_HS_FLOW_ITVL_TICKS); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); + } +} + +static os_error_t +ble_hs_flow_acl_free(struct os_mempool_ext *mpe, void *data, void *arg) +{ + struct ble_hs_conn *conn; + const struct os_mbuf *om; + uint16_t conn_handle; + int rc; + + om = data; + + /* An ACL data packet must be a single mbuf, and it must contain the + * corresponding connection handle in its user header. + */ + assert(OS_MBUF_IS_PKTHDR(om)); + assert(OS_MBUF_USRHDR_LEN(om) >= sizeof conn_handle); + + /* Copy the connection handle out of the mbuf. */ + memcpy(&conn_handle, OS_MBUF_USRHDR(om), sizeof conn_handle); + + /* Free the mbuf back to its pool. */ + rc = os_memblock_put_from_cb(&mpe->mpe_mp, data); + if (rc != 0) { + return rc; + } + + /* Allow nested locks - there are too many places where acl buffers can get + * freed. + */ + ble_hs_lock_nested(); + + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL) { + ble_hs_flow_inc_completed_pkts(conn); + } + + ble_hs_unlock_nested(); + + return 0; +} +#endif /* MYNEWT_VAL(BLE_HS_FLOW_CTRL) */ + +void +ble_hs_flow_connection_broken(uint16_t conn_handle) +{ +#if MYNEWT_VAL(BLE_HS_FLOW_CTRL) && \ + MYNEWT_VAL(BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT) + ble_hs_lock(); + ble_hs_flow_tx_num_comp_pkts(); + ble_hs_unlock(); +#endif +} + +/** + * Fills the user header of an incoming data packet. On function return, the + * header contains the connection handle associated with the sender. + * + * If flow control is disabled, this function is a no-op. + */ +void +ble_hs_flow_fill_acl_usrhdr(struct os_mbuf *om) +{ +#if MYNEWT_VAL(BLE_HS_FLOW_CTRL) + const struct hci_data_hdr *hdr; + uint16_t *conn_handle; + + BLE_HS_DBG_ASSERT(OS_MBUF_USRHDR_LEN(om) >= sizeof *conn_handle); + conn_handle = OS_MBUF_USRHDR(om); + + hdr = (void *)om->om_data; + *conn_handle = BLE_HCI_DATA_HANDLE(hdr->hdh_handle_pb_bc); +#endif +} + +/** + * Sends the HCI commands to the controller required for enabling host flow + * control. + * + * If flow control is disabled, this function is a no-op. + */ +int +ble_hs_flow_startup(void) +{ +#if MYNEWT_VAL(BLE_HS_FLOW_CTRL) + struct ble_hci_cb_ctlr_to_host_fc_cp enable_cmd; + struct ble_hci_cb_host_buf_size_cp buf_size_cmd = { + .acl_data_len = htole16(MYNEWT_VAL(BLE_ACL_BUF_SIZE)), + .acl_num = htole16(MYNEWT_VAL(BLE_ACL_BUF_COUNT)), + }; + int rc; + + ble_npl_event_init(&ble_hs_flow_ev, ble_hs_flow_event_cb, NULL); + + /* Assume failure. */ + ble_hci_trans_set_acl_free_cb(NULL, NULL); + +#if MYNEWT_VAL(SELFTEST) + ble_npl_callout_stop(&ble_hs_flow_timer); +#endif + + enable_cmd.enable = BLE_HCI_CTLR_TO_HOST_FC_ACL; + + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND, + BLE_HCI_OCF_CB_SET_CTLR_TO_HOST_FC), + &enable_cmd, sizeof(enable_cmd), NULL, 0); + if (rc != 0) { + return rc; + } + + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND, + BLE_HCI_OCF_CB_HOST_BUF_SIZE), + &buf_size_cmd, sizeof(buf_size_cmd), NULL, 0); + if (rc != 0) { + enable_cmd.enable = BLE_HCI_CTLR_TO_HOST_FC_OFF; + ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_CTLR_BASEBAND, + BLE_HCI_OCF_CB_SET_CTLR_TO_HOST_FC), + &enable_cmd, sizeof(enable_cmd), NULL, 0); + return rc; + } + + /* Flow control successfully enabled. */ + ble_hs_flow_num_completed_pkts = 0; + ble_hci_trans_set_acl_free_cb(ble_hs_flow_acl_free, NULL); + ble_npl_callout_init(&ble_hs_flow_timer, ble_hs_evq_get(), + ble_hs_flow_event_cb, NULL); +#endif + + return 0; +} |
