/* * 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[ BLE_HCI_HOST_NUM_COMP_PKTS_HDR_LEN + BLE_HCI_HOST_NUM_COMP_PKTS_ENT_LEN ]; struct hci_host_num_comp_pkts_entry entry; 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. */ buf[0] = 1; /* Append entry for this connection. */ entry.conn_handle = conn->bhc_handle; entry.num_pkts = conn->bhc_completed_pkts; rc = ble_hs_hci_cmd_build_host_num_comp_pkts_entry( &entry, buf + BLE_HCI_HOST_NUM_COMP_PKTS_HDR_LEN, sizeof buf - BLE_HCI_HOST_NUM_COMP_PKTS_HDR_LEN); BLE_HS_DBG_ASSERT(rc == 0); 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 hci_host_buf_size buf_size_cmd; 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); ble_npl_callout_stop(&ble_hs_flow_timer); rc = ble_hs_hci_cmd_tx_set_ctlr_to_host_fc(BLE_HCI_CTLR_TO_HOST_FC_ACL); if (rc != 0) { return rc; } buf_size_cmd = (struct hci_host_buf_size) { .acl_pkt_len = MYNEWT_VAL(BLE_ACL_BUF_SIZE), .num_acl_pkts = MYNEWT_VAL(BLE_ACL_BUF_COUNT), }; rc = ble_hs_hci_cmd_tx_host_buf_size(&buf_size_cmd); if (rc != 0) { ble_hs_hci_cmd_tx_set_ctlr_to_host_fc(BLE_HCI_CTLR_TO_HOST_FC_OFF); 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; }