/*
|
* 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 "nimble_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;
|
}
|