| /* |
| * 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 "ble_hs_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; |
| |
| static struct ble_hs_hci_sup_cmd ble_hs_hci_sup_cmd; |
| |
| #if MYNEWT_VAL(BLE_CONTROLLER) |
| #define BLE_HS_HCI_FRAG_DATABUF_SIZE \ |
| (BLE_ACL_MAX_PKT_SIZE + \ |
| BLE_HCI_DATA_HDR_SZ + \ |
| sizeof (struct os_mbuf_pkthdr) + \ |
| sizeof (struct ble_mbuf_hdr) + \ |
| sizeof (struct os_mbuf)) |
| #else |
| #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)) |
| #endif |
| |
| #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 < (int)sizeof(*ev)) { |
| if (len < (int)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 = ble_transport_alloc_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); |
| 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_no_rsp(uint16_t opcode, const void *cmd, uint8_t cmd_len) |
| { |
| int rc; |
| |
| ble_hs_hci_lock(); |
| |
| rc = ble_hs_hci_cmd_send_buf(opcode, cmd, cmd_len); |
| |
| ble_hs_hci_unlock(); |
| |
| 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_transport_free((uint8_t *) ble_hs_hci_ack); |
| ble_hs_hci_ack = NULL; |
| } |
| |
| ble_hs_hci_unlock(); |
| return rc; |
| } |
| |
| #if MYNEWT_VAL(BLE_HCI_VS) |
| int |
| ble_hs_hci_send_vs_cmd(uint16_t ocf, const void *cmdbuf, uint8_t cmdlen, |
| void *rspbuf, uint8_t rsplen) |
| { |
| int rc; |
| |
| rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_VENDOR, ocf), |
| cmdbuf, cmdlen, rspbuf, rsplen); |
| |
| return rc; |
| } |
| #endif |
| |
| 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_transport_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. */ |
| #if MYNEWT_VAL(BLE_CONTROLLER) |
| om = os_mbuf_get_pkthdr(&ble_hs_hci_frag_mbuf_pool, sizeof(struct ble_mbuf_hdr)); |
| #else |
| om = os_mbuf_get_pkthdr(&ble_hs_hci_frag_mbuf_pool, 0); |
| #endif |
| 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; |
| |
| put_le16(&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_set_hci_supported_cmd(struct ble_hs_hci_sup_cmd sup_cmd) |
| { |
| ble_hs_hci_sup_cmd = sup_cmd; |
| } |
| |
| struct ble_hs_hci_sup_cmd |
| ble_hs_hci_get_hci_supported_cmd(void) |
| { |
| return ble_hs_hci_sup_cmd; |
| } |
| |
| 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); |
| } |