blob: c0f9f509780de50282dc6beae5fe101ac05593de [file] [log] [blame]
/*
* 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;
}