summaryrefslogtreecommitdiff
path: root/src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c')
-rw-r--r--src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c622
1 files changed, 622 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c
new file mode 100644
index 00000000..a334a747
--- /dev/null
+++ b/src/libs/mynewt-nimble/nimble/host/src/ble_hs_hci.c
@@ -0,0 +1,622 @@
+/*
+ * 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 <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include "os/os.h"
+#include "mem/mem.h"
+#include "nimble/ble_hci_trans.h"
+#include "host/ble_monitor.h"
+#include "ble_hs_priv.h"
+#include "ble_monitor_priv.h"
+
+#define BLE_HCI_CMD_TIMEOUT_MS 2000
+
+static struct ble_npl_mutex ble_hs_hci_mutex;
+static struct ble_npl_sem ble_hs_hci_sem;
+
+static struct ble_hci_ev *ble_hs_hci_ack;
+static uint16_t ble_hs_hci_buf_sz;
+static uint8_t ble_hs_hci_max_pkts;
+
+/* For now 32-bits of features is enough */
+static uint32_t ble_hs_hci_sup_feat;
+
+static uint8_t ble_hs_hci_version;
+
+#define BLE_HS_HCI_FRAG_DATABUF_SIZE \
+ (BLE_ACL_MAX_PKT_SIZE + \
+ BLE_HCI_DATA_HDR_SZ + \
+ sizeof (struct os_mbuf_pkthdr) + \
+ sizeof (struct os_mbuf))
+
+#define BLE_HS_HCI_FRAG_MEMBLOCK_SIZE \
+ (OS_ALIGN(BLE_HS_HCI_FRAG_DATABUF_SIZE, 4))
+
+#define BLE_HS_HCI_FRAG_MEMPOOL_SIZE \
+ OS_MEMPOOL_SIZE(1, BLE_HS_HCI_FRAG_MEMBLOCK_SIZE)
+
+/**
+ * A one-element mbuf pool dedicated to holding outgoing ACL data packets.
+ * This dedicated pool prevents a deadlock caused by mbuf exhaustion. Without
+ * this pool, all msys mbufs could be permanently allocated, preventing us
+ * from fragmenting outgoing packets and sending them (and ultimately freeing
+ * them).
+ */
+static os_membuf_t ble_hs_hci_frag_data[BLE_HS_HCI_FRAG_MEMPOOL_SIZE];
+static struct os_mbuf_pool ble_hs_hci_frag_mbuf_pool;
+static struct os_mempool ble_hs_hci_frag_mempool;
+
+/**
+ * The number of available ACL transmit buffers on the controller. This
+ * variable must only be accessed while the host mutex is locked.
+ */
+uint16_t ble_hs_hci_avail_pkts;
+
+#if MYNEWT_VAL(BLE_HS_PHONY_HCI_ACKS)
+static ble_hs_hci_phony_ack_fn *ble_hs_hci_phony_ack_cb;
+#endif
+
+#if MYNEWT_VAL(BLE_HS_PHONY_HCI_ACKS)
+void
+ble_hs_hci_set_phony_ack_cb(ble_hs_hci_phony_ack_fn *cb)
+{
+ ble_hs_hci_phony_ack_cb = cb;
+}
+#endif
+
+static void
+ble_hs_hci_lock(void)
+{
+ int rc;
+
+ rc = ble_npl_mutex_pend(&ble_hs_hci_mutex, BLE_NPL_TIME_FOREVER);
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0 || rc == OS_NOT_STARTED);
+}
+
+static void
+ble_hs_hci_unlock(void)
+{
+ int rc;
+
+ rc = ble_npl_mutex_release(&ble_hs_hci_mutex);
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0 || rc == OS_NOT_STARTED);
+}
+
+int
+ble_hs_hci_set_buf_sz(uint16_t pktlen, uint16_t max_pkts)
+{
+ if (pktlen == 0 || max_pkts == 0) {
+ return BLE_HS_EINVAL;
+ }
+
+ ble_hs_hci_buf_sz = pktlen;
+ ble_hs_hci_max_pkts = max_pkts;
+ ble_hs_hci_avail_pkts = max_pkts;
+
+ return 0;
+}
+
+/**
+ * Increases the count of available controller ACL buffers.
+ */
+void
+ble_hs_hci_add_avail_pkts(uint16_t delta)
+{
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ if (ble_hs_hci_avail_pkts + delta > UINT16_MAX) {
+ ble_hs_sched_reset(BLE_HS_ECONTROLLER);
+ } else {
+ ble_hs_hci_avail_pkts += delta;
+ }
+}
+
+static int
+ble_hs_hci_rx_cmd_complete(const void *data, int len,
+ struct ble_hs_hci_ack *out_ack)
+{
+ const struct ble_hci_ev_command_complete *ev = data;
+ const struct ble_hci_ev_command_complete_nop *nop = data;
+ uint16_t opcode;
+
+ if (len < sizeof(*ev)) {
+ if (len < sizeof(*nop)) {
+ return BLE_HS_ECONTROLLER;
+ }
+
+ /* nop is special as it doesn't have status and response */
+
+ opcode = le16toh(nop->opcode);
+ if (opcode != BLE_HCI_OPCODE_NOP) {
+ return BLE_HS_ECONTROLLER;
+ }
+
+ /* TODO Process num_pkts field. */
+
+ out_ack->bha_status = 0;
+ out_ack->bha_params = NULL;
+ out_ack->bha_params_len = 0;
+ return 0;
+ }
+
+ opcode = le16toh(ev->opcode);
+
+ /* TODO Process num_pkts field. */
+
+ out_ack->bha_opcode = opcode;
+
+ out_ack->bha_status = BLE_HS_HCI_ERR(ev->status);
+ out_ack->bha_params_len = len - sizeof(*ev);
+ if (out_ack->bha_params_len) {
+ out_ack->bha_params = ev->return_params;
+ } else {
+ out_ack->bha_params = NULL;
+ }
+
+ return 0;
+}
+
+static int
+ble_hs_hci_rx_cmd_status(const void *data, int len,
+ struct ble_hs_hci_ack *out_ack)
+{
+ const struct ble_hci_ev_command_status *ev = data;
+
+ if (len != sizeof(*ev)) {
+ return BLE_HS_ECONTROLLER;
+ }
+
+ /* XXX: Process num_pkts field. */
+
+ out_ack->bha_opcode = le16toh(ev->opcode);
+ out_ack->bha_params = NULL;
+ out_ack->bha_params_len = 0;
+ out_ack->bha_status = BLE_HS_HCI_ERR(ev->status);
+
+ return 0;
+}
+
+static int
+ble_hs_hci_process_ack(uint16_t expected_opcode,
+ uint8_t *params_buf, uint8_t params_buf_len,
+ struct ble_hs_hci_ack *out_ack)
+{
+ int rc;
+
+ BLE_HS_DBG_ASSERT(ble_hs_hci_ack != NULL);
+
+ /* Count events received */
+ STATS_INC(ble_hs_stats, hci_event);
+
+
+ /* Clear ack fields up front to silence spurious gcc warnings. */
+ memset(out_ack, 0, sizeof *out_ack);
+
+ switch (ble_hs_hci_ack->opcode) {
+ case BLE_HCI_EVCODE_COMMAND_COMPLETE:
+ rc = ble_hs_hci_rx_cmd_complete(ble_hs_hci_ack->data,
+ ble_hs_hci_ack->length, out_ack);
+ break;
+
+ case BLE_HCI_EVCODE_COMMAND_STATUS:
+ rc = ble_hs_hci_rx_cmd_status(ble_hs_hci_ack->data,
+ ble_hs_hci_ack->length, out_ack);
+ break;
+
+ default:
+ BLE_HS_DBG_ASSERT(0);
+ rc = BLE_HS_EUNKNOWN;
+ break;
+ }
+
+ if (rc == 0) {
+ if (params_buf == NULL || out_ack->bha_params == NULL) {
+ out_ack->bha_params_len = 0;
+ } else {
+ if (out_ack->bha_params_len > params_buf_len) {
+ out_ack->bha_params_len = params_buf_len;
+ rc = BLE_HS_ECONTROLLER;
+ }
+ memcpy(params_buf, out_ack->bha_params, out_ack->bha_params_len);
+ }
+ out_ack->bha_params = params_buf;
+
+ if (out_ack->bha_opcode != expected_opcode) {
+ rc = BLE_HS_ECONTROLLER;
+ }
+ }
+
+ if (rc != 0) {
+ STATS_INC(ble_hs_stats, hci_invalid_ack);
+ }
+
+ return rc;
+}
+
+static int
+ble_hs_hci_wait_for_ack(void)
+{
+ int rc;
+
+#if MYNEWT_VAL(BLE_HS_PHONY_HCI_ACKS)
+ if (ble_hs_hci_phony_ack_cb == NULL) {
+ rc = BLE_HS_ETIMEOUT_HCI;
+ } else {
+ ble_hs_hci_ack =
+ (void *) ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_CMD);
+ BLE_HS_DBG_ASSERT(ble_hs_hci_ack != NULL);
+ rc = ble_hs_hci_phony_ack_cb((void *)ble_hs_hci_ack, 260);
+ }
+#else
+ rc = ble_npl_sem_pend(&ble_hs_hci_sem,
+ ble_npl_time_ms_to_ticks32(BLE_HCI_CMD_TIMEOUT_MS));
+ switch (rc) {
+ case 0:
+ BLE_HS_DBG_ASSERT(ble_hs_hci_ack != NULL);
+
+#if BLE_MONITOR
+ ble_monitor_send(BLE_MONITOR_OPCODE_EVENT_PKT, (void *) ble_hs_hci_ack,
+ sizeof(*ble_hs_hci_ack) + ble_hs_hci_ack->length);
+#endif
+
+ break;
+ case OS_TIMEOUT:
+ rc = BLE_HS_ETIMEOUT_HCI;
+ STATS_INC(ble_hs_stats, hci_timeout);
+ break;
+ default:
+ rc = BLE_HS_EOS;
+ break;
+ }
+#endif
+
+ return rc;
+}
+
+int
+ble_hs_hci_cmd_tx(uint16_t opcode, const void *cmd, uint8_t cmd_len,
+ void *rsp, uint8_t rsp_len)
+{
+ struct ble_hs_hci_ack ack;
+ int rc;
+
+ BLE_HS_DBG_ASSERT(ble_hs_hci_ack == NULL);
+ ble_hs_hci_lock();
+
+ rc = ble_hs_hci_cmd_send_buf(opcode, cmd, cmd_len);
+ if (rc != 0) {
+ goto done;
+ }
+
+ rc = ble_hs_hci_wait_for_ack();
+ if (rc != 0) {
+ ble_hs_sched_reset(rc);
+ goto done;
+ }
+
+ rc = ble_hs_hci_process_ack(opcode, rsp, rsp_len, &ack);
+ if (rc != 0) {
+ ble_hs_sched_reset(rc);
+ goto done;
+ }
+
+ rc = ack.bha_status;
+
+ /* on success we should always get full response */
+ if (!rc && (ack.bha_params_len != rsp_len)) {
+ ble_hs_sched_reset(rc);
+ goto done;
+ }
+
+done:
+ if (ble_hs_hci_ack != NULL) {
+ ble_hci_trans_buf_free((uint8_t *) ble_hs_hci_ack);
+ ble_hs_hci_ack = NULL;
+ }
+
+ ble_hs_hci_unlock();
+ return rc;
+}
+
+static void
+ble_hs_hci_rx_ack(uint8_t *ack_ev)
+{
+ if (ble_npl_sem_get_count(&ble_hs_hci_sem) > 0) {
+ /* This ack is unexpected; ignore it. */
+ ble_hci_trans_buf_free(ack_ev);
+ return;
+ }
+ BLE_HS_DBG_ASSERT(ble_hs_hci_ack == NULL);
+
+ /* Unblock the application now that the HCI command buffer is populated
+ * with the acknowledgement.
+ */
+ ble_hs_hci_ack = (struct ble_hci_ev *) ack_ev;
+ ble_npl_sem_release(&ble_hs_hci_sem);
+}
+
+int
+ble_hs_hci_rx_evt(uint8_t *hci_ev, void *arg)
+{
+ struct ble_hci_ev *ev = (void *) hci_ev;
+ struct ble_hci_ev_command_complete *cmd_complete = (void *) ev->data;
+ struct ble_hci_ev_command_status *cmd_status = (void *) ev->data;
+ int enqueue;
+
+ BLE_HS_DBG_ASSERT(hci_ev != NULL);
+
+ switch (ev->opcode) {
+ case BLE_HCI_EVCODE_COMMAND_COMPLETE:
+ enqueue = (cmd_complete->opcode == BLE_HCI_OPCODE_NOP);
+ break;
+ case BLE_HCI_EVCODE_COMMAND_STATUS:
+ enqueue = (cmd_status->opcode == BLE_HCI_OPCODE_NOP);
+ break;
+ default:
+ enqueue = 1;
+ break;
+ }
+
+ if (enqueue) {
+ ble_hs_enqueue_hci_event(hci_ev);
+ } else {
+ ble_hs_hci_rx_ack(hci_ev);
+ }
+
+ return 0;
+}
+
+/**
+ * Calculates the largest ACL payload that the controller can accept.
+ */
+static uint16_t
+ble_hs_hci_max_acl_payload_sz(void)
+{
+ /* As per BLE 5.1 Standard, Vol. 2, Part E, section 7.8.2:
+ * The LE_Read_Buffer_Size command is used to read the maximum size of the
+ * data portion of HCI LE ACL Data Packets sent from the Host to the
+ * Controller.
+ */
+ return ble_hs_hci_buf_sz;
+}
+
+/**
+ * Allocates an mbuf to contain an outgoing ACL data fragment.
+ */
+static struct os_mbuf *
+ble_hs_hci_frag_alloc(uint16_t frag_size, void *arg)
+{
+ struct os_mbuf *om;
+
+ /* Prefer the dedicated one-element fragment pool. */
+ om = os_mbuf_get_pkthdr(&ble_hs_hci_frag_mbuf_pool, 0);
+ if (om != NULL) {
+ om->om_data += BLE_HCI_DATA_HDR_SZ;
+ return om;
+ }
+
+ /* Otherwise, fall back to msys. */
+ om = ble_hs_mbuf_acl_pkt();
+ if (om != NULL) {
+ return om;
+ }
+
+ return NULL;
+}
+
+/**
+ * Retrieves the total capacity of the ACL fragment pool (always 1).
+ */
+int
+ble_hs_hci_frag_num_mbufs(void)
+{
+ return ble_hs_hci_frag_mempool.mp_num_blocks;
+}
+
+/**
+ * Retrieves the the count of free buffers in the ACL fragment pool.
+ */
+int
+ble_hs_hci_frag_num_mbufs_free(void)
+{
+ return ble_hs_hci_frag_mempool.mp_num_free;
+}
+
+static struct os_mbuf *
+ble_hs_hci_acl_hdr_prepend(struct os_mbuf *om, uint16_t handle,
+ uint8_t pb_flag)
+{
+ struct hci_data_hdr hci_hdr;
+ struct os_mbuf *om2;
+
+ hci_hdr.hdh_handle_pb_bc =
+ ble_hs_hci_util_handle_pb_bc_join(handle, pb_flag, 0);
+ put_le16(&hci_hdr.hdh_len, OS_MBUF_PKTHDR(om)->omp_len);
+
+ om2 = os_mbuf_prepend(om, sizeof hci_hdr);
+ if (om2 == NULL) {
+ return NULL;
+ }
+
+ om = om2;
+ om = os_mbuf_pullup(om, sizeof hci_hdr);
+ if (om == NULL) {
+ return NULL;
+ }
+
+ memcpy(om->om_data, &hci_hdr, sizeof hci_hdr);
+
+#if !BLE_MONITOR
+ BLE_HS_LOG(DEBUG, "host tx hci data; handle=%d length=%d\n", handle,
+ get_le16(&hci_hdr.hdh_len));
+#endif
+
+ return om;
+}
+
+int
+ble_hs_hci_acl_tx_now(struct ble_hs_conn *conn, struct os_mbuf **om)
+{
+ struct os_mbuf *txom;
+ struct os_mbuf *frag;
+ uint8_t pb;
+ int rc;
+
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ txom = *om;
+ *om = NULL;
+
+ if (!(conn->bhc_flags & BLE_HS_CONN_F_TX_FRAG)) {
+ /* The first fragment uses the first-non-flush packet boundary value.
+ * After sending the first fragment, pb gets set appropriately for all
+ * subsequent fragments in this packet.
+ */
+ pb = BLE_HCI_PB_FIRST_NON_FLUSH;
+ } else {
+ pb = BLE_HCI_PB_MIDDLE;
+ }
+
+ /* Send fragments until the entire packet has been sent. */
+ while (txom != NULL && ble_hs_hci_avail_pkts > 0) {
+ frag = mem_split_frag(&txom, ble_hs_hci_max_acl_payload_sz(),
+ ble_hs_hci_frag_alloc, NULL);
+ if (frag == NULL) {
+ *om = txom;
+ return BLE_HS_EAGAIN;
+ }
+
+ frag = ble_hs_hci_acl_hdr_prepend(frag, conn->bhc_handle, pb);
+ if (frag == NULL) {
+ rc = BLE_HS_ENOMEM;
+ goto err;
+ }
+
+#if !BLE_MONITOR
+ BLE_HS_LOG(DEBUG, "ble_hs_hci_acl_tx(): ");
+ ble_hs_log_mbuf(frag);
+ BLE_HS_LOG(DEBUG, "\n");
+#endif
+
+ rc = ble_hs_tx_data(frag);
+ if (rc != 0) {
+ goto err;
+ }
+
+ /* If any fragments remain, they should be marked as 'middle'
+ * fragments.
+ */
+ conn->bhc_flags |= BLE_HS_CONN_F_TX_FRAG;
+ pb = BLE_HCI_PB_MIDDLE;
+
+ /* Account for the controller buf that will hold the txed fragment. */
+ conn->bhc_outstanding_pkts++;
+ ble_hs_hci_avail_pkts--;
+ }
+
+ if (txom != NULL) {
+ /* The controller couldn't accommodate some or all of the packet. */
+ *om = txom;
+ return BLE_HS_EAGAIN;
+ }
+
+ /* The entire packet was transmitted. */
+ conn->bhc_flags &= ~BLE_HS_CONN_F_TX_FRAG;
+
+ return 0;
+
+err:
+ BLE_HS_DBG_ASSERT(rc != 0);
+
+ conn->bhc_flags &= ~BLE_HS_CONN_F_TX_FRAG;
+ os_mbuf_free_chain(txom);
+ return rc;
+}
+
+/**
+ * Transmits an HCI ACL data packet. This function consumes the supplied mbuf,
+ * regardless of the outcome.
+ *
+ * @return 0 on success;
+ * BLE_HS_EAGAIN if the packet could not be sent
+ * in its entirety due to controller buffer
+ * exhaustion. The unsent data is pointed to
+ * by the `om` parameter.
+ * A BLE host core return code on unexpected
+ * error.
+ *
+ */
+int
+ble_hs_hci_acl_tx(struct ble_hs_conn *conn, struct os_mbuf **om)
+{
+ BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task());
+
+ /* If this conn is already backed up, don't even try to send. */
+ if (STAILQ_FIRST(&conn->bhc_tx_q) != NULL) {
+ return BLE_HS_EAGAIN;
+ }
+
+ return ble_hs_hci_acl_tx_now(conn, om);
+}
+
+void
+ble_hs_hci_set_le_supported_feat(uint32_t feat)
+{
+ ble_hs_hci_sup_feat = feat;
+}
+
+uint32_t
+ble_hs_hci_get_le_supported_feat(void)
+{
+ return ble_hs_hci_sup_feat;
+}
+
+void
+ble_hs_hci_set_hci_version(uint8_t hci_version)
+{
+ ble_hs_hci_version = hci_version;
+}
+
+uint8_t
+ble_hs_hci_get_hci_version(void)
+{
+ return ble_hs_hci_version;
+}
+
+void
+ble_hs_hci_init(void)
+{
+ int rc;
+
+ rc = ble_npl_sem_init(&ble_hs_hci_sem, 0);
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0);
+
+ rc = ble_npl_mutex_init(&ble_hs_hci_mutex);
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0);
+
+ rc = mem_init_mbuf_pool(ble_hs_hci_frag_data,
+ &ble_hs_hci_frag_mempool,
+ &ble_hs_hci_frag_mbuf_pool,
+ 1,
+ BLE_HS_HCI_FRAG_MEMBLOCK_SIZE,
+ "ble_hs_hci_frag");
+ BLE_HS_DBG_ASSERT_EVAL(rc == 0);
+}