| /* |
| * 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 "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; |
| |
| /* Connection handle associated with each mbuf in ACL pool */ |
| static uint16_t ble_hs_flow_mbuf_conn_handle[ MYNEWT_VAL(BLE_TRANSPORT_ACL_FROM_LL_COUNT) ]; |
| |
| static inline int |
| ble_hs_flow_mbuf_index(const struct os_mbuf *om) |
| { |
| const struct os_mempool *mp = om->om_omp->omp_pool; |
| uintptr_t addr = (uintptr_t)om; |
| int idx; |
| |
| idx = (addr - mp->mp_membuf_addr) / mp->mp_block_size; |
| |
| BLE_HS_DBG_ASSERT(mp->mp_membuf_addr + idx * mp->mp_block_size == addr); |
| |
| return idx; |
| } |
| |
| 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_tx_no_rsp( |
| 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_TRANSPORT_ACL_FROM_LL_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_TRANSPORT_ACL_FROM_LL_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 idx; |
| int rc; |
| |
| om = data; |
| |
| idx = ble_hs_flow_mbuf_index(om); |
| conn_handle = ble_hs_flow_mbuf_conn_handle[idx]; |
| |
| /* 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 |
| } |
| |
| /** |
| * Associates incoming data packet with a connection handle of the sender. |
| * |
| * If flow control is disabled, this function is a no-op. |
| */ |
| void |
| ble_hs_flow_track_data_mbuf(struct os_mbuf *om) |
| { |
| #if MYNEWT_VAL(BLE_HS_FLOW_CTRL) |
| const struct hci_data_hdr *hdr; |
| int idx = ble_hs_flow_mbuf_index(om); |
| |
| hdr = (void *)om->om_data; |
| ble_hs_flow_mbuf_conn_handle[idx] = 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_TRANSPORT_ACL_SIZE)), |
| .acl_num = htole16(MYNEWT_VAL(BLE_TRANSPORT_ACL_FROM_LL_COUNT)), |
| }; |
| int rc; |
| |
| ble_npl_event_init(&ble_hs_flow_ev, ble_hs_flow_event_cb, 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_transport_register_put_acl_from_ll_cb(ble_hs_flow_acl_free); |
| ble_npl_callout_init(&ble_hs_flow_timer, ble_hs_evq_get(), |
| ble_hs_flow_event_cb, NULL); |
| #endif |
| |
| return 0; |
| } |