text: /*
   * 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;
  
  #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 < 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 = 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;
  }
  
  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_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);
  }
  