| /* |
| * 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 <assert.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include "sysinit/sysinit.h" |
| #include "syscfg/syscfg.h" |
| #include "os/os_cputime.h" |
| #include "bsp/bsp.h" |
| #include "os/os.h" |
| #include "mem/mem.h" |
| #include "hal/hal_gpio.h" |
| #include "hal/hal_uart.h" |
| |
| /* BLE */ |
| #include "nimble/ble.h" |
| #include "nimble/nimble_opt.h" |
| #include "nimble/hci_common.h" |
| #include "nimble/ble_hci_trans.h" |
| |
| #include "transport/uart/ble_hci_uart.h" |
| |
| #define BLE_HCI_UART_EVT_COUNT \ |
| (MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT) + MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT)) |
| |
| /*** |
| * NOTES: |
| * The UART HCI transport doesn't use event buffer priorities. All incoming |
| * and outgoing events use buffers from the same pool. |
| * |
| * The "skip" definitions are here so that when buffers cannot be allocated, |
| * the command or acl packets are simply skipped so that the HCI interface |
| * does not lose synchronization and resets dont (necessarily) occur. |
| */ |
| |
| /* XXX: for now, define this here */ |
| #if MYNEWT_VAL(BLE_DEVICE) |
| extern void ble_ll_data_buffer_overflow(void); |
| extern void ble_ll_hw_error(uint8_t err); |
| |
| static const uint8_t ble_hci_uart_reset_cmd[4] = { 0x01, 0x03, 0x0C, 0x00 }; |
| #endif |
| |
| /*** |
| * NOTES: |
| * The "skip" definitions are here so that when buffers cannot be allocated, |
| * the command or acl packets are simply skipped so that the HCI interface |
| * does not lose synchronization and resets dont (necessarily) occur. |
| */ |
| #define BLE_HCI_UART_H4_NONE 0x00 |
| #define BLE_HCI_UART_H4_CMD 0x01 |
| #define BLE_HCI_UART_H4_ACL 0x02 |
| #define BLE_HCI_UART_H4_SCO 0x03 |
| #define BLE_HCI_UART_H4_EVT 0x04 |
| #define BLE_HCI_UART_H4_SYNC_LOSS 0x80 |
| #define BLE_HCI_UART_H4_SKIP_CMD 0x81 |
| #define BLE_HCI_UART_H4_SKIP_ACL 0x82 |
| #define BLE_HCI_UART_H4_LE_EVT 0x83 |
| #define BLE_HCI_UART_H4_SKIP_EVT 0x84 |
| |
| static ble_hci_trans_rx_cmd_fn *ble_hci_uart_rx_cmd_cb; |
| static void *ble_hci_uart_rx_cmd_arg; |
| |
| static ble_hci_trans_rx_acl_fn *ble_hci_uart_rx_acl_cb; |
| static void *ble_hci_uart_rx_acl_arg; |
| |
| static struct os_mempool ble_hci_uart_evt_hi_pool; |
| static os_membuf_t ble_hci_uart_evt_hi_buf[ |
| OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT), |
| MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)) |
| ]; |
| |
| static struct os_mempool ble_hci_uart_evt_lo_pool; |
| static os_membuf_t ble_hci_uart_evt_lo_buf[ |
| OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT), |
| MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)) |
| ]; |
| |
| static struct os_mempool ble_hci_uart_cmd_pool; |
| static os_membuf_t ble_hci_uart_cmd_buf[ |
| OS_MEMPOOL_SIZE(1, BLE_HCI_TRANS_CMD_SZ) |
| ]; |
| |
| static struct os_mbuf_pool ble_hci_uart_acl_mbuf_pool; |
| static struct os_mempool_ext ble_hci_uart_acl_pool; |
| |
| /* |
| * The MBUF payload size must accommodate the HCI data header size plus the |
| * maximum ACL data packet length. The ACL block size is the size of the |
| * mbufs we will allocate. |
| */ |
| #define ACL_BLOCK_SIZE OS_ALIGN(MYNEWT_VAL(BLE_ACL_BUF_SIZE) \ |
| + BLE_MBUF_MEMBLOCK_OVERHEAD \ |
| + BLE_HCI_DATA_HDR_SZ, OS_ALIGNMENT) |
| |
| static os_membuf_t ble_hci_uart_acl_buf[ |
| OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_ACL_BUF_COUNT), |
| ACL_BLOCK_SIZE) |
| ]; |
| |
| /** |
| * A packet to be sent over the UART. This can be a command, an event, or ACL |
| * data. |
| */ |
| struct ble_hci_uart_pkt { |
| STAILQ_ENTRY(ble_hci_uart_pkt) next; |
| void *data; |
| uint8_t type; |
| }; |
| |
| static struct os_mempool ble_hci_uart_pkt_pool; |
| static os_membuf_t ble_hci_uart_pkt_buf[ |
| OS_MEMPOOL_SIZE(BLE_HCI_UART_EVT_COUNT + 1 + |
| MYNEWT_VAL(BLE_HCI_ACL_OUT_COUNT), |
| sizeof (struct ble_hci_uart_pkt)) |
| ]; |
| |
| /** |
| * An incoming or outgoing command or event. |
| */ |
| struct ble_hci_uart_cmd { |
| uint8_t *data; /* Pointer to ble_hci_uart_cmd data */ |
| uint16_t cur; /* Number of bytes read/written */ |
| uint16_t len; /* Total number of bytes to read/write */ |
| }; |
| |
| /** |
| * An incoming ACL data packet. |
| */ |
| struct ble_hci_uart_acl { |
| struct os_mbuf *buf; /* Buffer containing the data */ |
| uint8_t *dptr; /* Pointer to where bytes should be placed */ |
| uint16_t len; /* Target size when buf is considered complete */ |
| uint16_t rxd_bytes; /* current count of bytes received for packet */ |
| }; |
| |
| /** |
| * Structure for transmitting ACL packets over UART |
| * |
| */ |
| struct ble_hci_uart_h4_acl_tx |
| { |
| uint8_t *dptr; |
| struct os_mbuf *tx_acl; |
| }; |
| |
| static struct { |
| /*** State of data received over UART. */ |
| uint8_t rx_type; /* Pending packet type. 0 means nothing pending */ |
| union { |
| struct ble_hci_uart_cmd rx_cmd; |
| struct ble_hci_uart_acl rx_acl; |
| }; |
| |
| /*** State of data transmitted over UART. */ |
| uint8_t tx_type; /* Pending packet type. 0 means nothing pending */ |
| uint16_t rem_tx_len; /* Used for acl tx only currently */ |
| union { |
| struct ble_hci_uart_cmd tx_cmd; |
| struct ble_hci_uart_h4_acl_tx tx_pkt; |
| }; |
| STAILQ_HEAD(, ble_hci_uart_pkt) tx_pkts; /* Packet queue to send to UART */ |
| } ble_hci_uart_state; |
| |
| /** |
| * Allocates a buffer (mbuf) for ACL operation. |
| * |
| * @return The allocated buffer on success; |
| * NULL on buffer exhaustion. |
| */ |
| static struct os_mbuf * |
| ble_hci_trans_acl_buf_alloc(void) |
| { |
| struct os_mbuf *m; |
| uint8_t usrhdr_len; |
| |
| #if MYNEWT_VAL(BLE_DEVICE) |
| usrhdr_len = sizeof(struct ble_mbuf_hdr); |
| #elif MYNEWT_VAL(BLE_HS_FLOW_CTRL) |
| usrhdr_len = BLE_MBUF_HS_HDR_LEN; |
| #else |
| usrhdr_len = 0; |
| #endif |
| |
| m = os_mbuf_get_pkthdr(&ble_hci_uart_acl_mbuf_pool, usrhdr_len); |
| return m; |
| } |
| |
| static int |
| ble_hci_uart_acl_tx(struct os_mbuf *om) |
| { |
| struct ble_hci_uart_pkt *pkt; |
| os_sr_t sr; |
| |
| /* If this packet is zero length, just free it */ |
| if (OS_MBUF_PKTLEN(om) == 0) { |
| os_mbuf_free_chain(om); |
| return 0; |
| } |
| |
| pkt = os_memblock_get(&ble_hci_uart_pkt_pool); |
| if (pkt == NULL) { |
| os_mbuf_free_chain(om); |
| return BLE_ERR_MEM_CAPACITY; |
| } |
| |
| pkt->type = BLE_HCI_UART_H4_ACL; |
| pkt->data = om; |
| |
| OS_ENTER_CRITICAL(sr); |
| STAILQ_INSERT_TAIL(&ble_hci_uart_state.tx_pkts, pkt, next); |
| OS_EXIT_CRITICAL(sr); |
| |
| hal_uart_start_tx(MYNEWT_VAL(BLE_HCI_UART_PORT)); |
| |
| return 0; |
| } |
| |
| static int |
| ble_hci_uart_cmdevt_tx(uint8_t *hci_ev, uint8_t h4_type) |
| { |
| struct ble_hci_uart_pkt *pkt; |
| os_sr_t sr; |
| |
| pkt = os_memblock_get(&ble_hci_uart_pkt_pool); |
| if (pkt == NULL) { |
| ble_hci_trans_buf_free(hci_ev); |
| return BLE_ERR_MEM_CAPACITY; |
| } |
| |
| pkt->type = h4_type; |
| pkt->data = hci_ev; |
| |
| OS_ENTER_CRITICAL(sr); |
| STAILQ_INSERT_TAIL(&ble_hci_uart_state.tx_pkts, pkt, next); |
| OS_EXIT_CRITICAL(sr); |
| |
| hal_uart_start_tx(MYNEWT_VAL(BLE_HCI_UART_PORT)); |
| |
| return 0; |
| } |
| |
| /** |
| * @return The packet type to transmit on success; |
| * -1 if there is nothing to send. |
| */ |
| static int |
| ble_hci_uart_tx_pkt_type(void) |
| { |
| struct ble_hci_uart_pkt *pkt; |
| struct os_mbuf *om; |
| os_sr_t sr; |
| int rc; |
| |
| OS_ENTER_CRITICAL(sr); |
| |
| pkt = STAILQ_FIRST(&ble_hci_uart_state.tx_pkts); |
| if (!pkt) { |
| OS_EXIT_CRITICAL(sr); |
| return -1; |
| } |
| |
| STAILQ_REMOVE(&ble_hci_uart_state.tx_pkts, pkt, ble_hci_uart_pkt, next); |
| |
| OS_EXIT_CRITICAL(sr); |
| |
| rc = pkt->type; |
| switch (pkt->type) { |
| case BLE_HCI_UART_H4_CMD: |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_CMD; |
| ble_hci_uart_state.tx_cmd.data = pkt->data; |
| ble_hci_uart_state.tx_cmd.cur = 0; |
| ble_hci_uart_state.tx_cmd.len = ble_hci_uart_state.tx_cmd.data[2] + |
| BLE_HCI_CMD_HDR_LEN; |
| break; |
| |
| case BLE_HCI_UART_H4_EVT: |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_EVT; |
| ble_hci_uart_state.tx_cmd.data = pkt->data; |
| ble_hci_uart_state.tx_cmd.cur = 0; |
| ble_hci_uart_state.tx_cmd.len = ble_hci_uart_state.tx_cmd.data[1] + |
| BLE_HCI_EVENT_HDR_LEN; |
| break; |
| |
| case BLE_HCI_UART_H4_ACL: |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_ACL; |
| om = (struct os_mbuf *)pkt->data; |
| /* NOTE: first mbuf must have non-zero length */ |
| os_mbuf_trim_front(om); |
| ble_hci_uart_state.tx_pkt.tx_acl = om; |
| ble_hci_uart_state.tx_pkt.dptr = om->om_data; |
| ble_hci_uart_state.rem_tx_len = OS_MBUF_PKTLEN(om); |
| break; |
| |
| default: |
| rc = -1; |
| break; |
| } |
| |
| os_memblock_put(&ble_hci_uart_pkt_pool, pkt); |
| |
| return rc; |
| } |
| |
| /** |
| * @return The byte to transmit on success; |
| * -1 if there is nothing to send. |
| */ |
| static int |
| ble_hci_uart_tx_char(void *arg) |
| { |
| uint8_t u8; |
| int rc; |
| struct os_mbuf *om; |
| |
| switch (ble_hci_uart_state.tx_type) { |
| case BLE_HCI_UART_H4_NONE: /* No pending packet, pick one from the queue */ |
| rc = ble_hci_uart_tx_pkt_type(); |
| break; |
| |
| case BLE_HCI_UART_H4_CMD: |
| case BLE_HCI_UART_H4_EVT: |
| rc = ble_hci_uart_state.tx_cmd.data[ble_hci_uart_state.tx_cmd.cur++]; |
| |
| if (ble_hci_uart_state.tx_cmd.cur == ble_hci_uart_state.tx_cmd.len) { |
| ble_hci_trans_buf_free(ble_hci_uart_state.tx_cmd.data); |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_NONE; |
| } |
| break; |
| |
| case BLE_HCI_UART_H4_ACL: |
| /* Copy the first unsent byte from the tx buffer and remove it from the |
| * source. |
| */ |
| u8 = ble_hci_uart_state.tx_pkt.dptr[0]; |
| --ble_hci_uart_state.rem_tx_len; |
| if (ble_hci_uart_state.rem_tx_len == 0) { |
| os_mbuf_free_chain(ble_hci_uart_state.tx_pkt.tx_acl); |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_NONE; |
| } else { |
| om = ble_hci_uart_state.tx_pkt.tx_acl; |
| --om->om_len; |
| if (om->om_len == 0) { |
| /* Remove and free any zero mbufs */ |
| while ((om != NULL) && (om->om_len == 0)) { |
| ble_hci_uart_state.tx_pkt.tx_acl = SLIST_NEXT(om, om_next); |
| os_mbuf_free(om); |
| om = ble_hci_uart_state.tx_pkt.tx_acl; |
| } |
| /* NOTE: om should never be NULL! What to do? */ |
| if (om == NULL) { |
| assert(0); |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_NONE; |
| } else { |
| ble_hci_uart_state.tx_pkt.dptr = om->om_data; |
| } |
| } else { |
| ble_hci_uart_state.tx_pkt.dptr++; |
| } |
| } |
| rc = u8; |
| break; |
| default: |
| rc = -1; |
| break; |
| } |
| |
| return rc; |
| } |
| |
| #if MYNEWT_VAL(BLE_DEVICE) |
| /** |
| * HCI uart sync lost. |
| * |
| * This occurs when the controller receives an invalid packet type or a length |
| * field that is out of range. The controller needs to send a HW error to the |
| * host and wait to find a LL reset command. |
| */ |
| static void |
| ble_hci_uart_sync_lost(void) |
| { |
| ble_hci_uart_state.rx_cmd.len = 0; |
| ble_hci_uart_state.rx_cmd.cur = 0; |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_CMD); |
| ble_ll_hw_error(BLE_HW_ERR_HCI_SYNC_LOSS); |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_SYNC_LOSS; |
| } |
| #endif |
| |
| /** |
| * @return The type of packet to follow success; |
| * -1 if there is no valid packet to receive. |
| */ |
| static int |
| ble_hci_uart_rx_pkt_type(uint8_t data) |
| { |
| struct os_mbuf *m; |
| |
| ble_hci_uart_state.rx_type = data; |
| |
| switch (ble_hci_uart_state.rx_type) { |
| /* Host should never receive a command! */ |
| #if MYNEWT_VAL(BLE_DEVICE) |
| case BLE_HCI_UART_H4_CMD: |
| ble_hci_uart_state.rx_cmd.len = 0; |
| ble_hci_uart_state.rx_cmd.cur = 0; |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_CMD); |
| if (ble_hci_uart_state.rx_cmd.data == NULL) { |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_SKIP_CMD; |
| } |
| break; |
| #endif |
| |
| /* Controller should never receive an event */ |
| #if MYNEWT_VAL(BLE_HOST) |
| case BLE_HCI_UART_H4_EVT: |
| /* |
| * The event code is unknown at the moment. Depending on event priority, |
| * buffer *shall* be allocated from ble_hci_uart_evt_hi_pool |
| * or "may* be allocated from ble_hci_uart_evt_lo_pool. |
| * Thus do not allocate the buffer yet. |
| */ |
| ble_hci_uart_state.rx_cmd.data = NULL; |
| ble_hci_uart_state.rx_cmd.len = 0; |
| ble_hci_uart_state.rx_cmd.cur = 0; |
| break; |
| #endif |
| |
| case BLE_HCI_UART_H4_ACL: |
| ble_hci_uart_state.rx_acl.len = 0; |
| ble_hci_uart_state.rx_acl.rxd_bytes = 0; |
| m = ble_hci_trans_acl_buf_alloc(); |
| if (m) { |
| ble_hci_uart_state.rx_acl.dptr = m->om_data; |
| } else { |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_SKIP_ACL; |
| } |
| ble_hci_uart_state.rx_acl.buf = m; |
| break; |
| |
| default: |
| #if MYNEWT_VAL(BLE_DEVICE) |
| /* |
| * If we receive an unknown HCI packet type this is considered a loss |
| * of sync. |
| */ |
| ble_hci_uart_sync_lost(); |
| #else |
| /* |
| * XXX: not sure what to do about host in this case. Just go back to |
| * none for now. |
| */ |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| #endif |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #if MYNEWT_VAL(BLE_DEVICE) |
| /** |
| * HCI uart sync loss. |
| * |
| * Find a LL reset command in the byte stream. The LL reset command is a |
| * sequence of 4 bytes: |
| * 0x01 HCI Packet Type = HCI CMD |
| * 0x03 OCF for reset command |
| * 0x0C OGF for reset command (0x03 shifted left by two bits as the OGF |
| * occupies the uopper 6 bits of this byte. |
| * 0x00 Parameter length of reset command (no parameters). |
| * |
| * @param data Byte received over serial port |
| */ |
| void |
| ble_hci_uart_rx_sync_loss(uint8_t data) |
| { |
| int rc; |
| int index; |
| |
| /* |
| * If we couldnt allocate a command buffer (should not occur but |
| * possible) try to allocate one on each received character. If we get |
| * a reset and buffer is not available we have to ignore reset. |
| */ |
| if (ble_hci_uart_state.rx_cmd.data == NULL) { |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_CMD); |
| } |
| |
| index = ble_hci_uart_state.rx_cmd.cur; |
| if (data == ble_hci_uart_reset_cmd[index]) { |
| if (index == 3) { |
| if (ble_hci_uart_state.rx_cmd.data == NULL) { |
| index = 0; |
| } else { |
| assert(ble_hci_uart_rx_cmd_cb != NULL); |
| ble_hci_uart_state.rx_cmd.data[0] = 0x03; |
| ble_hci_uart_state.rx_cmd.data[1] = 0x0C; |
| ble_hci_uart_state.rx_cmd.data[2] = 0x00; |
| rc = ble_hci_uart_rx_cmd_cb(ble_hci_uart_state.rx_cmd.data, |
| ble_hci_uart_rx_cmd_arg); |
| if (rc != 0) { |
| ble_hci_trans_buf_free(ble_hci_uart_state.rx_cmd.data); |
| } |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } else { |
| ++index; |
| } |
| } else { |
| index = 0; |
| } |
| |
| ble_hci_uart_state.rx_cmd.cur = index; |
| } |
| |
| static void |
| ble_hci_uart_rx_cmd(uint8_t data) |
| { |
| int rc; |
| |
| ble_hci_uart_state.rx_cmd.data[ble_hci_uart_state.rx_cmd.cur++] = data; |
| |
| if (ble_hci_uart_state.rx_cmd.cur < BLE_HCI_CMD_HDR_LEN) { |
| return; |
| } |
| |
| if (ble_hci_uart_state.rx_cmd.cur == BLE_HCI_CMD_HDR_LEN) { |
| ble_hci_uart_state.rx_cmd.len = ble_hci_uart_state.rx_cmd.data[2] + |
| BLE_HCI_CMD_HDR_LEN; |
| } |
| |
| if (ble_hci_uart_state.rx_cmd.cur == ble_hci_uart_state.rx_cmd.len) { |
| assert(ble_hci_uart_rx_cmd_cb != NULL); |
| rc = ble_hci_uart_rx_cmd_cb(ble_hci_uart_state.rx_cmd.data, |
| ble_hci_uart_rx_cmd_arg); |
| if (rc != 0) { |
| ble_hci_trans_buf_free(ble_hci_uart_state.rx_cmd.data); |
| } |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| |
| static void |
| ble_hci_uart_rx_skip_cmd(uint8_t data) |
| { |
| ble_hci_uart_state.rx_cmd.cur++; |
| |
| if (ble_hci_uart_state.rx_cmd.cur < BLE_HCI_CMD_HDR_LEN) { |
| return; |
| } |
| |
| if (ble_hci_uart_state.rx_cmd.cur == BLE_HCI_CMD_HDR_LEN) { |
| ble_hci_uart_state.rx_cmd.len = data + BLE_HCI_CMD_HDR_LEN; |
| } |
| |
| if (ble_hci_uart_state.rx_cmd.cur == ble_hci_uart_state.rx_cmd.len) { |
| /* |
| * XXX: for now we simply skip the command and do nothing. This |
| * should not happen but at least we retain HCI synch. The host |
| * can decide what to do in this case. It may be appropriate for |
| * the controller to attempt to send back a command complete or |
| * command status in this case. |
| */ |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| #endif |
| |
| #if MYNEWT_VAL(BLE_HOST) |
| static inline void |
| ble_hci_uart_rx_evt_cb() |
| { |
| int rc; |
| |
| if (ble_hci_uart_state.rx_cmd.cur == ble_hci_uart_state.rx_cmd.len) { |
| assert(ble_hci_uart_rx_cmd_cb != NULL); |
| rc = ble_hci_uart_rx_cmd_cb(ble_hci_uart_state.rx_cmd.data, |
| ble_hci_uart_rx_cmd_arg); |
| if (rc != 0) { |
| ble_hci_trans_buf_free(ble_hci_uart_state.rx_cmd.data); |
| } |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| |
| static void |
| ble_hci_uart_rx_evt(uint8_t data) |
| { |
| /* Determine event priority to allocate buffer */ |
| if (!ble_hci_uart_state.rx_cmd.data) { |
| /* In case of LE Meta Event priority might be still unknown */ |
| if (data == BLE_HCI_EVCODE_LE_META) { |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_LE_EVT; |
| ble_hci_uart_state.rx_cmd.cur++; |
| return; |
| } |
| |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); |
| assert(ble_hci_uart_state.rx_cmd.data != NULL); |
| } |
| |
| ble_hci_uart_state.rx_cmd.data[ble_hci_uart_state.rx_cmd.cur++] = data; |
| |
| if (ble_hci_uart_state.rx_cmd.cur < BLE_HCI_EVENT_HDR_LEN) { |
| return; |
| } |
| |
| if (ble_hci_uart_state.rx_cmd.cur == BLE_HCI_EVENT_HDR_LEN) { |
| ble_hci_uart_state.rx_cmd.len = ble_hci_uart_state.rx_cmd.data[1] + |
| BLE_HCI_EVENT_HDR_LEN; |
| } |
| |
| ble_hci_uart_rx_evt_cb(); |
| } |
| |
| static void |
| ble_hci_uart_rx_le_evt(uint8_t data) |
| { |
| ble_hci_uart_state.rx_cmd.cur++; |
| |
| if (ble_hci_uart_state.rx_cmd.cur == BLE_HCI_EVENT_HDR_LEN) { |
| /* LE Meta Event parameter length is never 0 */ |
| assert(data != 0); |
| ble_hci_uart_state.rx_cmd.len = data + BLE_HCI_EVENT_HDR_LEN; |
| return; |
| } |
| |
| /* Determine event priority to allocate buffer */ |
| if (!ble_hci_uart_state.rx_cmd.data) { |
| /* Determine event priority to allocate buffer */ |
| if (data == BLE_HCI_LE_SUBEV_ADV_RPT || |
| data == BLE_HCI_LE_SUBEV_EXT_ADV_RPT) { |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_LO); |
| if (ble_hci_uart_state.rx_cmd.data == NULL) { |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_SKIP_EVT; |
| return; |
| } |
| } else { |
| ble_hci_uart_state.rx_cmd.data = |
| ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI); |
| assert(ble_hci_uart_state.rx_cmd.data != NULL); |
| } |
| |
| ble_hci_uart_state.rx_cmd.data[0] = BLE_HCI_EVCODE_LE_META; |
| ble_hci_uart_state.rx_cmd.data[1] = |
| ble_hci_uart_state.rx_cmd.len - BLE_HCI_EVENT_HDR_LEN; |
| } |
| |
| ble_hci_uart_state.rx_cmd.data[ble_hci_uart_state.rx_cmd.cur - 1] = data; |
| ble_hci_uart_rx_evt_cb(); |
| } |
| |
| static void |
| ble_hci_uart_rx_skip_evt(uint8_t data) |
| { |
| if (++ble_hci_uart_state.rx_cmd.cur == ble_hci_uart_state.rx_cmd.len) { |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| #endif |
| |
| static void |
| ble_hci_uart_rx_acl(uint8_t data) |
| { |
| uint16_t rxd_bytes; |
| uint16_t pktlen; |
| |
| rxd_bytes = ble_hci_uart_state.rx_acl.rxd_bytes; |
| ble_hci_uart_state.rx_acl.dptr[rxd_bytes] = data; |
| ++rxd_bytes; |
| ble_hci_uart_state.rx_acl.rxd_bytes = rxd_bytes; |
| |
| if (rxd_bytes < BLE_HCI_DATA_HDR_SZ) { |
| return; |
| } |
| |
| if (rxd_bytes == BLE_HCI_DATA_HDR_SZ) { |
| pktlen = ble_hci_uart_state.rx_acl.dptr[3]; |
| pktlen = (pktlen << 8) + ble_hci_uart_state.rx_acl.dptr[2]; |
| ble_hci_uart_state.rx_acl.len = pktlen + BLE_HCI_DATA_HDR_SZ; |
| |
| /* |
| * Data portion cannot exceed data length of acl buffer. If it does |
| * this is considered to be a loss of sync. |
| */ |
| if (pktlen > MYNEWT_VAL(BLE_ACL_BUF_SIZE)) { |
| os_mbuf_free_chain(ble_hci_uart_state.rx_acl.buf); |
| #if MYNEWT_VAL(BLE_DEVICE) |
| ble_hci_uart_sync_lost(); |
| #else |
| /* |
| * XXX: not sure what to do about host in this case. Just go back to |
| * none for now. |
| */ |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| #endif |
| } |
| } |
| |
| if (rxd_bytes == ble_hci_uart_state.rx_acl.len) { |
| assert(ble_hci_uart_rx_acl_cb != NULL); |
| /* XXX: can this callback fail? What if it does? */ |
| OS_MBUF_PKTLEN(ble_hci_uart_state.rx_acl.buf) = rxd_bytes; |
| ble_hci_uart_state.rx_acl.buf->om_len = rxd_bytes; |
| ble_hci_uart_rx_acl_cb(ble_hci_uart_state.rx_acl.buf, |
| ble_hci_uart_rx_acl_arg); |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| |
| static void |
| ble_hci_uart_rx_skip_acl(uint8_t data) |
| { |
| uint16_t rxd_bytes; |
| uint16_t pktlen; |
| |
| rxd_bytes = ble_hci_uart_state.rx_acl.rxd_bytes; |
| ++rxd_bytes; |
| ble_hci_uart_state.rx_acl.rxd_bytes = rxd_bytes; |
| |
| if (rxd_bytes == (BLE_HCI_DATA_HDR_SZ - 1)) { |
| ble_hci_uart_state.rx_acl.len = data; |
| return; |
| } |
| |
| if (rxd_bytes == BLE_HCI_DATA_HDR_SZ) { |
| pktlen = data; |
| pktlen = (pktlen << 8) + ble_hci_uart_state.rx_acl.len; |
| ble_hci_uart_state.rx_acl.len = pktlen + BLE_HCI_DATA_HDR_SZ; |
| } |
| |
| if (rxd_bytes == ble_hci_uart_state.rx_acl.len) { |
| /* XXX: I dont like this but for now this denotes controller only */ |
| #if MYNEWT_VAL(BLE_DEVICE) |
| ble_ll_data_buffer_overflow(); |
| #endif |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| } |
| } |
| |
| static int |
| ble_hci_uart_rx_char(void *arg, uint8_t data) |
| { |
| switch (ble_hci_uart_state.rx_type) { |
| case BLE_HCI_UART_H4_NONE: |
| return ble_hci_uart_rx_pkt_type(data); |
| #if MYNEWT_VAL(BLE_DEVICE) |
| case BLE_HCI_UART_H4_CMD: |
| ble_hci_uart_rx_cmd(data); |
| return 0; |
| case BLE_HCI_UART_H4_SKIP_CMD: |
| ble_hci_uart_rx_skip_cmd(data); |
| return 0; |
| case BLE_HCI_UART_H4_SYNC_LOSS: |
| ble_hci_uart_rx_sync_loss(data); |
| return 0; |
| #endif |
| #if MYNEWT_VAL(BLE_HOST) |
| case BLE_HCI_UART_H4_EVT: |
| ble_hci_uart_rx_evt(data); |
| return 0; |
| case BLE_HCI_UART_H4_LE_EVT: |
| ble_hci_uart_rx_le_evt(data); |
| return 0; |
| case BLE_HCI_UART_H4_SKIP_EVT: |
| ble_hci_uart_rx_skip_evt(data); |
| return 0; |
| #endif |
| case BLE_HCI_UART_H4_ACL: |
| ble_hci_uart_rx_acl(data); |
| return 0; |
| case BLE_HCI_UART_H4_SKIP_ACL: |
| ble_hci_uart_rx_skip_acl(data); |
| return 0; |
| default: |
| /* This should never happen! */ |
| assert(0); |
| return 0; |
| } |
| } |
| |
| static void |
| ble_hci_uart_set_rx_cbs(ble_hci_trans_rx_cmd_fn *cmd_cb, |
| void *cmd_arg, |
| ble_hci_trans_rx_acl_fn *acl_cb, |
| void *acl_arg) |
| { |
| ble_hci_uart_rx_cmd_cb = cmd_cb; |
| ble_hci_uart_rx_cmd_arg = cmd_arg; |
| ble_hci_uart_rx_acl_cb = acl_cb; |
| ble_hci_uart_rx_acl_arg = acl_arg; |
| } |
| |
| static void |
| ble_hci_uart_free_pkt(uint8_t type, uint8_t *cmdevt, struct os_mbuf *acl) |
| { |
| switch (type) { |
| case BLE_HCI_UART_H4_NONE: |
| break; |
| |
| case BLE_HCI_UART_H4_CMD: |
| case BLE_HCI_UART_H4_EVT: |
| ble_hci_trans_buf_free(cmdevt); |
| break; |
| |
| case BLE_HCI_UART_H4_ACL: |
| os_mbuf_free_chain(acl); |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| static int |
| ble_hci_uart_config(void) |
| { |
| int rc; |
| |
| rc = hal_uart_init_cbs(MYNEWT_VAL(BLE_HCI_UART_PORT), |
| ble_hci_uart_tx_char, NULL, |
| ble_hci_uart_rx_char, NULL); |
| if (rc != 0) { |
| return BLE_ERR_UNSPECIFIED; |
| } |
| |
| rc = hal_uart_config(MYNEWT_VAL(BLE_HCI_UART_PORT), |
| MYNEWT_VAL(BLE_HCI_UART_BAUD), |
| MYNEWT_VAL(BLE_HCI_UART_DATA_BITS), |
| MYNEWT_VAL(BLE_HCI_UART_STOP_BITS), |
| MYNEWT_VAL(BLE_HCI_UART_PARITY), |
| MYNEWT_VAL(BLE_HCI_UART_FLOW_CTRL)); |
| if (rc != 0) { |
| return BLE_ERR_HW_FAIL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Sends an HCI event from the controller to the host. |
| * |
| * @param cmd The HCI event to send. This buffer must be |
| * allocated via ble_hci_trans_buf_alloc(). |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| int |
| ble_hci_trans_ll_evt_tx(uint8_t *cmd) |
| { |
| int rc; |
| |
| rc = ble_hci_uart_cmdevt_tx(cmd, BLE_HCI_UART_H4_EVT); |
| return rc; |
| } |
| |
| /** |
| * Sends ACL data from controller to host. |
| * |
| * @param om The ACL data packet to send. |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| int |
| ble_hci_trans_ll_acl_tx(struct os_mbuf *om) |
| { |
| int rc; |
| |
| rc = ble_hci_uart_acl_tx(om); |
| return rc; |
| } |
| |
| /** |
| * Sends an HCI command from the host to the controller. |
| * |
| * @param cmd The HCI command to send. This buffer must be |
| * allocated via ble_hci_trans_buf_alloc(). |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| int |
| ble_hci_trans_hs_cmd_tx(uint8_t *cmd) |
| { |
| int rc; |
| |
| rc = ble_hci_uart_cmdevt_tx(cmd, BLE_HCI_UART_H4_CMD); |
| return rc; |
| } |
| |
| /** |
| * Sends ACL data from host to controller. |
| * |
| * @param om The ACL data packet to send. |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| int |
| ble_hci_trans_hs_acl_tx(struct os_mbuf *om) |
| { |
| int rc; |
| |
| rc = ble_hci_uart_acl_tx(om); |
| return rc; |
| } |
| |
| /** |
| * Configures the HCI transport to call the specified callback upon receiving |
| * HCI packets from the controller. This function should only be called by by |
| * host. |
| * |
| * @param cmd_cb The callback to execute upon receiving an HCI |
| * event. |
| * @param cmd_arg Optional argument to pass to the command |
| * callback. |
| * @param acl_cb The callback to execute upon receiving ACL |
| * data. |
| * @param acl_arg Optional argument to pass to the ACL |
| * callback. |
| */ |
| void |
| ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb, |
| void *cmd_arg, |
| ble_hci_trans_rx_acl_fn *acl_cb, |
| void *acl_arg) |
| { |
| ble_hci_uart_set_rx_cbs(cmd_cb, cmd_arg, acl_cb, acl_arg); |
| } |
| |
| /** |
| * Configures the HCI transport to operate with a host. The transport will |
| * execute specified callbacks upon receiving HCI packets from the controller. |
| * |
| * @param cmd_cb The callback to execute upon receiving an HCI |
| * event. |
| * @param cmd_arg Optional argument to pass to the command |
| * callback. |
| * @param acl_cb The callback to execute upon receiving ACL |
| * data. |
| * @param acl_arg Optional argument to pass to the ACL |
| * callback. |
| */ |
| void |
| ble_hci_trans_cfg_ll(ble_hci_trans_rx_cmd_fn *cmd_cb, |
| void *cmd_arg, |
| ble_hci_trans_rx_acl_fn *acl_cb, |
| void *acl_arg) |
| { |
| ble_hci_uart_set_rx_cbs(cmd_cb, cmd_arg, acl_cb, acl_arg); |
| } |
| |
| /** |
| * Allocates a flat buffer of the specified type. |
| * |
| * @param type The type of buffer to allocate; one of the |
| * BLE_HCI_TRANS_BUF_[...] constants. |
| * |
| * @return The allocated buffer on success; |
| * NULL on buffer exhaustion. |
| */ |
| uint8_t * |
| ble_hci_trans_buf_alloc(int type) |
| { |
| uint8_t *buf; |
| |
| switch (type) { |
| case BLE_HCI_TRANS_BUF_CMD: |
| buf = os_memblock_get(&ble_hci_uart_cmd_pool); |
| break; |
| case BLE_HCI_TRANS_BUF_EVT_HI: |
| buf = os_memblock_get(&ble_hci_uart_evt_hi_pool); |
| if (buf == NULL) { |
| /* If no high-priority event buffers remain, try to grab a |
| * low-priority one. |
| */ |
| buf = os_memblock_get(&ble_hci_uart_evt_lo_pool); |
| } |
| break; |
| |
| case BLE_HCI_TRANS_BUF_EVT_LO: |
| buf = os_memblock_get(&ble_hci_uart_evt_lo_pool); |
| break; |
| |
| default: |
| assert(0); |
| buf = NULL; |
| } |
| |
| return buf; |
| } |
| |
| /** |
| * Frees the specified flat buffer. The buffer must have been allocated via |
| * ble_hci_trans_buf_alloc(). |
| * |
| * @param buf The buffer to free. |
| */ |
| void |
| ble_hci_trans_buf_free(uint8_t *buf) |
| { |
| int rc; |
| |
| /* |
| * XXX: this may look a bit odd, but the controller uses the command |
| * buffer to send back the command complete/status as an immediate |
| * response to the command. This was done to insure that the controller |
| * could always send back one of these events when a command was received. |
| * Thus, we check to see which pool the buffer came from so we can free |
| * it to the appropriate pool |
| */ |
| if (os_memblock_from(&ble_hci_uart_evt_hi_pool, buf)) { |
| rc = os_memblock_put(&ble_hci_uart_evt_hi_pool, buf); |
| assert(rc == 0); |
| } else if (os_memblock_from(&ble_hci_uart_evt_lo_pool, buf)) { |
| rc = os_memblock_put(&ble_hci_uart_evt_lo_pool, buf); |
| assert(rc == 0); |
| } else { |
| assert(os_memblock_from(&ble_hci_uart_cmd_pool, buf)); |
| rc = os_memblock_put(&ble_hci_uart_cmd_pool, buf); |
| assert(rc == 0); |
| } |
| } |
| |
| /** |
| * Configures a callback to get executed whenever an ACL data packet is freed. |
| * The function is called in lieu of actually freeing the packet. |
| * |
| * @param cb The callback to configure. |
| * |
| * @return 0 on success. |
| */ |
| int |
| ble_hci_trans_set_acl_free_cb(os_mempool_put_fn *cb, void *arg) |
| { |
| ble_hci_uart_acl_pool.mpe_put_cb = cb; |
| ble_hci_uart_acl_pool.mpe_put_arg = arg; |
| return 0; |
| } |
| |
| /** |
| * Resets the HCI UART transport to a clean state. Frees all buffers and |
| * reconfigures the UART. |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| int |
| ble_hci_trans_reset(void) |
| { |
| struct ble_hci_uart_pkt *pkt; |
| int rc; |
| |
| /* Close the UART to prevent race conditions as the buffers are freed. */ |
| rc = hal_uart_close(MYNEWT_VAL(BLE_HCI_UART_PORT)); |
| if (rc != 0) { |
| return BLE_ERR_HW_FAIL; |
| } |
| |
| ble_hci_uart_free_pkt(ble_hci_uart_state.rx_type, |
| ble_hci_uart_state.rx_cmd.data, |
| ble_hci_uart_state.rx_acl.buf); |
| ble_hci_uart_state.rx_type = BLE_HCI_UART_H4_NONE; |
| |
| ble_hci_uart_free_pkt(ble_hci_uart_state.tx_type, |
| ble_hci_uart_state.tx_cmd.data, |
| ble_hci_uart_state.tx_pkt.tx_acl); |
| ble_hci_uart_state.tx_type = BLE_HCI_UART_H4_NONE; |
| |
| while ((pkt = STAILQ_FIRST(&ble_hci_uart_state.tx_pkts)) != NULL) { |
| STAILQ_REMOVE(&ble_hci_uart_state.tx_pkts, pkt, ble_hci_uart_pkt, |
| next); |
| ble_hci_uart_free_pkt(pkt->type, pkt->data, pkt->data); |
| os_memblock_put(&ble_hci_uart_pkt_pool, pkt); |
| } |
| |
| /* Reopen the UART. */ |
| rc = ble_hci_uart_config(); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Initializes the UART HCI transport module. |
| * |
| * @return 0 on success; |
| * A BLE_ERR_[...] error code on failure. |
| */ |
| void |
| ble_hci_uart_init(void) |
| { |
| int rc; |
| |
| /* Ensure this function only gets called by sysinit. */ |
| SYSINIT_ASSERT_ACTIVE(); |
| |
| rc = os_mempool_ext_init(&ble_hci_uart_acl_pool, |
| MYNEWT_VAL(BLE_ACL_BUF_COUNT), |
| ACL_BLOCK_SIZE, |
| ble_hci_uart_acl_buf, |
| "ble_hci_uart_acl_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| rc = os_mbuf_pool_init(&ble_hci_uart_acl_mbuf_pool, |
| &ble_hci_uart_acl_pool.mpe_mp, |
| ACL_BLOCK_SIZE, |
| MYNEWT_VAL(BLE_ACL_BUF_COUNT)); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| /* |
| * Create memory pool of HCI command buffers. NOTE: we currently dont |
| * allow this to be configured. The controller will only allow one |
| * outstanding command. We decided to keep this a pool in case we allow |
| * allow the controller to handle more than one outstanding command. |
| */ |
| rc = os_mempool_init(&ble_hci_uart_cmd_pool, |
| 1, |
| BLE_HCI_TRANS_CMD_SZ, |
| ble_hci_uart_cmd_buf, |
| "ble_hci_uart_cmd_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| rc = os_mempool_init(&ble_hci_uart_evt_hi_pool, |
| MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT), |
| MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE), |
| ble_hci_uart_evt_hi_buf, |
| "ble_hci_uart_evt_hi_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| rc = os_mempool_init(&ble_hci_uart_evt_lo_pool, |
| MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT), |
| MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE), |
| ble_hci_uart_evt_lo_buf, |
| "ble_hci_uart_evt_lo_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| /* |
| * Create memory pool of packet list nodes. NOTE: the number of these |
| * buffers should be, at least, the total number of event buffers (hi |
| * and lo), the number of command buffers (currently 1) and the total |
| * number of buffers that the controller could possibly hand to the host. |
| */ |
| rc = os_mempool_init(&ble_hci_uart_pkt_pool, |
| BLE_HCI_UART_EVT_COUNT + 1 + |
| MYNEWT_VAL(BLE_HCI_ACL_OUT_COUNT), |
| sizeof (struct ble_hci_uart_pkt), |
| ble_hci_uart_pkt_buf, |
| "ble_hci_uart_pkt_pool"); |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| |
| rc = ble_hci_uart_config(); |
| SYSINIT_PANIC_ASSERT_MSG(rc == 0, "Failure configuring UART HCI"); |
| |
| memset(&ble_hci_uart_state, 0, sizeof ble_hci_uart_state); |
| STAILQ_INIT(&ble_hci_uart_state.tx_pkts); |
| } |