/* * 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 #include #include #include #include #include "sysinit/sysinit.h" #include "nimble_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_spi.h" /* BLE */ #include "nimble/ble.h" #include "nimble/nimble_opt.h" #include "nimble/hci_common.h" #include "nimble/transport.h" #include "transport/emspi/ble_hci_emspi.h" #include "am_mcu_apollo.h" #define BLE_HCI_EMSPI_PKT_NONE 0x00 #define BLE_HCI_EMSPI_PKT_CMD 0x01 #define BLE_HCI_EMSPI_PKT_ACL 0x02 #define BLE_HCI_EMSPI_PKT_EVT 0x04 #define BLE_HCI_EMSPI_CTLR_STATUS_OK 0xc0 #define BLE_HCI_EMSPI_OP_TX 0x42 #define BLE_HCI_EMSPI_OP_RX 0x81 static os_event_fn ble_hci_emspi_event_txrx; static struct os_event ble_hci_emspi_ev_txrx = { .ev_cb = ble_hci_emspi_event_txrx, }; static struct os_eventq ble_hci_emspi_evq; static struct os_task ble_hci_emspi_task; static os_stack_t ble_hci_emspi_stack[MYNEWT_VAL(BLE_HCI_EMSPI_STACK_SIZE)]; /** * A packet to be sent over the EMSPI. This can be a command, an event, or ACL * data. */ struct ble_hci_emspi_pkt { STAILQ_ENTRY(ble_hci_emspi_pkt) next; void *data; uint8_t type; }; STAILQ_HEAD(, ble_hci_emspi_pkt) ble_hci_emspi_tx_q; static struct os_mempool ble_hci_emspi_pkt_pool; static os_membuf_t ble_hci_emspi_pkt_buf[ OS_MEMPOOL_SIZE(1 + MYNEWT_VAL(BLE_TRANSPORT_ACL_FROM_HS_COUNT), sizeof (struct ble_hci_emspi_pkt)) ]; static void ble_hci_emspi_rdy_isr(void *arg) { os_eventq_put(&ble_hci_emspi_evq, &ble_hci_emspi_ev_txrx); } static void ble_hci_emspi_initiate_write(void) { hal_gpio_irq_disable(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN)); /* Assert slave select. */ hal_gpio_write(MYNEWT_VAL(BLE_HCI_EMSPI_SS_PIN), 0); /* Wait for controller to indicate ready-to-receive. */ while (!hal_gpio_read(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN))) { } } static void ble_hci_emspi_terminate_write(void) { const uint64_t rdy_mask = AM_HAL_GPIO_BIT(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN)); os_sr_t sr; am_hal_gpio_int_clear(rdy_mask); /* Deassert slave select. */ hal_gpio_write(MYNEWT_VAL(BLE_HCI_EMSPI_SS_PIN), 1); OS_ENTER_CRITICAL(sr); hal_gpio_irq_enable(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN)); if (hal_gpio_read(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN))) { am_hal_gpio_int_set(rdy_mask); } OS_EXIT_CRITICAL(sr); } static int ble_hci_emspi_write_hdr(uint8_t first_byte, uint8_t *out_buf_size) { const uint8_t hdr[2] = { first_byte, 0x00 }; uint8_t rx[2]; int rc; /* Send command header. */ rc = hal_spi_txrx(MYNEWT_VAL(BLE_HCI_EMSPI_SPI_NUM), (void *)hdr, rx, 2); if (rc != 0) { return rc; } /* Check for "OK" status. */ if (rx[0] != BLE_HCI_EMSPI_CTLR_STATUS_OK) { return BLE_ERR_HW_FAIL; } *out_buf_size = rx[1]; return 0; } /** * Transmits a chunk of bytes to the controller. */ static int ble_hci_emspi_tx_chunk(const uint8_t *data, int len, int *out_bytes_txed) { uint8_t buf_size; int rc; /* Silence spurious "may be used uninitialized" warning. */ *out_bytes_txed = 0; ble_hci_emspi_initiate_write(); rc = ble_hci_emspi_write_hdr(BLE_HCI_EMSPI_OP_TX, &buf_size); if (rc != 0) { goto done; } if (buf_size == 0) { rc = 0; goto done; } if (buf_size < len) { len = buf_size; } rc = hal_spi_txrx(MYNEWT_VAL(BLE_HCI_EMSPI_SPI_NUM), (void *)data, NULL, len); if (rc != 0) { goto done; } *out_bytes_txed = len; done: ble_hci_emspi_terminate_write(); return rc; } /** * Transmits a full command or ACL data packet to the controller. */ static int ble_hci_emspi_tx(const uint8_t *data, int len) { int bytes_txed; int rc; while (len > 0) { rc = ble_hci_emspi_tx_chunk(data, len, &bytes_txed); if (rc != 0) { goto done; } data += bytes_txed; len -= bytes_txed; } rc = 0; done: return rc; } /** * Reads the specified number of bytes from the controller. */ static int ble_hci_emspi_rx(uint8_t *data, int max_len) { uint8_t buf_size; int rc; ble_hci_emspi_initiate_write(); rc = ble_hci_emspi_write_hdr(BLE_HCI_EMSPI_OP_RX, &buf_size); if (rc != 0) { goto done; } if (buf_size > max_len) { buf_size = max_len; } rc = hal_spi_txrx(MYNEWT_VAL(BLE_HCI_EMSPI_SPI_NUM), NULL, data, buf_size); if (rc != 0) { rc = BLE_ERR_HW_FAIL; goto done; } done: ble_hci_emspi_terminate_write(); return rc; } /** * Transmits an ACL data packet to the controller. The caller relinquishes the * specified mbuf, regardless of return status. */ static int ble_hci_emspi_acl_tx(struct os_mbuf *om) { struct ble_hci_emspi_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_emspi_pkt_pool); if (pkt == NULL) { os_mbuf_free_chain(om); return BLE_ERR_MEM_CAPACITY; } pkt->type = BLE_HCI_EMSPI_PKT_ACL; pkt->data = om; OS_ENTER_CRITICAL(sr); STAILQ_INSERT_TAIL(&ble_hci_emspi_tx_q, pkt, next); OS_EXIT_CRITICAL(sr); os_eventq_put(&ble_hci_emspi_evq, &ble_hci_emspi_ev_txrx); return 0; } /** * Transmits a command packet to the controller. The caller relinquishes the * specified buffer, regardless of return status. */ static int ble_hci_emspi_cmdevt_tx(uint8_t *cmd_buf, uint8_t pkt_type) { struct ble_hci_emspi_pkt *pkt; os_sr_t sr; pkt = os_memblock_get(&ble_hci_emspi_pkt_pool); if (pkt == NULL) { ble_transport_free(cmd_buf); return BLE_ERR_MEM_CAPACITY; } pkt->type = pkt_type; pkt->data = cmd_buf; OS_ENTER_CRITICAL(sr); STAILQ_INSERT_TAIL(&ble_hci_emspi_tx_q, pkt, next); OS_EXIT_CRITICAL(sr); os_eventq_put(&ble_hci_emspi_evq, &ble_hci_emspi_ev_txrx); return 0; } static int ble_hci_emspi_tx_flat(const uint8_t *data, int len) { int rc; rc = ble_hci_emspi_tx(data, len); return rc; } static int ble_hci_emspi_tx_pkt_type(uint8_t pkt_type) { return ble_hci_emspi_tx_flat(&pkt_type, 1); } static int ble_hci_emspi_tx_cmd(const uint8_t *data) { int len; int rc; rc = ble_hci_emspi_tx_pkt_type(BLE_HCI_EMSPI_PKT_CMD); if (rc != 0) { return rc; } len = data[2] + sizeof(struct ble_hci_cmd); rc = ble_hci_emspi_tx_flat(data, len); if (rc != 0) { return rc; } return 0; } static int ble_hci_emspi_tx_acl(struct os_mbuf *om) { struct os_mbuf *cur; int rc; rc = ble_hci_emspi_tx_pkt_type(BLE_HCI_EMSPI_PKT_ACL); if (rc != 0) { return rc; } cur = om; while (cur != NULL) { rc = ble_hci_emspi_tx(cur->om_data, cur->om_len); if (rc != 0) { break; } cur = SLIST_NEXT(cur, om_next); } return rc; } static struct ble_hci_emspi_pkt * ble_hci_emspi_pull_next_tx(void) { struct ble_hci_emspi_pkt *pkt; os_sr_t sr; OS_ENTER_CRITICAL(sr); pkt = STAILQ_FIRST(&ble_hci_emspi_tx_q); if (pkt != NULL) { STAILQ_REMOVE(&ble_hci_emspi_tx_q, pkt, ble_hci_emspi_pkt, next); } OS_EXIT_CRITICAL(sr); return pkt; } static int ble_hci_emspi_tx_pkt(void) { struct ble_hci_emspi_pkt *pkt; int rc; pkt = ble_hci_emspi_pull_next_tx(); if (pkt == NULL) { return -1; } switch (pkt->type) { case BLE_HCI_EMSPI_PKT_CMD: rc = ble_hci_emspi_tx_cmd(pkt->data); ble_transport_free(pkt->data); break; case BLE_HCI_EMSPI_PKT_ACL: rc = ble_hci_emspi_tx_acl(pkt->data); os_mbuf_free_chain(pkt->data); break; default: rc = -1; break; } os_memblock_put(&ble_hci_emspi_pkt_pool, pkt); return rc; } static int ble_hci_emspi_rx_evt(void) { uint8_t *data; uint8_t len; int rc; /* XXX: we should not assert if host cannot allocate an event. Need * to determine what to do here. */ data = ble_transport_alloc_evt(0); assert(data != NULL); rc = ble_hci_emspi_rx(data, sizeof(struct ble_hci_ev)); if (rc != 0) { goto err; } len = data[1]; if (len > 0) { rc = ble_hci_emspi_rx(data + sizeof(struct ble_hci_ev), len); if (rc != 0) { goto err; } } rc = ble_transport_to_hs_evt(data); if (rc != 0) { goto err; } return 0; err: ble_transport_free(data); return rc; } static int ble_hci_emspi_rx_acl(void) { struct os_mbuf *om; uint16_t len; int rc; /* XXX: we should not assert if host cannot allocate an mbuf. Need to * determine what to do here. */ om = ble_transport_alloc_acl_from_ll(); assert(om != NULL); rc = ble_hci_emspi_rx(om->om_data, BLE_HCI_DATA_HDR_SZ); if (rc != 0) { goto err; } len = get_le16(om->om_data + 2); if (len > MYNEWT_VAL(BLE_TRANSPORT_ACL_SIZE)) { /* * Data portion cannot exceed data length of acl buffer. If it does * this is considered to be a loss of sync. */ rc = BLE_ERR_UNSPECIFIED; goto err; } if (len > 0) { rc = ble_hci_emspi_rx(om->om_data + BLE_HCI_DATA_HDR_SZ, len); if (rc != 0) { goto err; } } OS_MBUF_PKTLEN(om) = BLE_HCI_DATA_HDR_SZ + len; om->om_len = BLE_HCI_DATA_HDR_SZ + len; rc = ble_transport_to_hs_acl(om); if (rc != 0) { goto err; } return 0; err: os_mbuf_free_chain(om); return rc; } /** * @return The type of packet to follow success; * -1 if there is no valid packet to receive. */ static int ble_hci_emspi_rx_pkt(void) { uint8_t pkt_type; int rc; /* XXX: This is awkward; should read the full packet in "one go". */ rc = ble_hci_emspi_rx(&pkt_type, 1); if (rc != 0) { return rc; } switch (pkt_type) { case BLE_HCI_EMSPI_PKT_EVT: return ble_hci_emspi_rx_evt(); case BLE_HCI_EMSPI_PKT_ACL: return ble_hci_emspi_rx_acl(); default: /* XXX */ return -1; } } static void ble_hci_emspi_free_pkt(uint8_t type, uint8_t *cmdevt, struct os_mbuf *acl) { switch (type) { case BLE_HCI_EMSPI_PKT_NONE: break; case BLE_HCI_EMSPI_PKT_CMD: case BLE_HCI_EMSPI_PKT_EVT: ble_transport_free(cmdevt); break; case BLE_HCI_EMSPI_PKT_ACL: os_mbuf_free_chain(acl); break; default: assert(0); break; } } /** * 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_emspi_cmdevt_tx(cmd, BLE_HCI_EMSPI_PKT_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_emspi_acl_tx(om); return rc; } /** * 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_emspi_pkt *pkt; hal_gpio_write(MYNEWT_VAL(BLE_HCI_EMSPI_RESET_PIN), 1); while ((pkt = STAILQ_FIRST(&ble_hci_emspi_tx_q)) != NULL) { STAILQ_REMOVE(&ble_hci_emspi_tx_q, pkt, ble_hci_emspi_pkt, next); ble_hci_emspi_free_pkt(pkt->type, pkt->data, pkt->data); os_memblock_put(&ble_hci_emspi_pkt_pool, pkt); } return 0; } static void ble_hci_emspi_event_txrx(struct os_event *ev) { int rc; rc = ble_hci_emspi_rx_pkt(); do { rc = ble_hci_emspi_tx_pkt(); } while (rc == 0); } static void ble_hci_emspi_loop(void *unused) { while (1) { os_eventq_run(&ble_hci_emspi_evq); } } static void ble_hci_emspi_init_hw(void) { struct hal_spi_settings spi_cfg; int rc; rc = hal_gpio_init_out(MYNEWT_VAL(BLE_HCI_EMSPI_RESET_PIN), 0); SYSINIT_PANIC_ASSERT(rc == 0); rc = hal_gpio_init_out(MYNEWT_VAL(BLE_HCI_EMSPI_SS_PIN), 1); SYSINIT_PANIC_ASSERT(rc == 0); spi_cfg.data_order = HAL_SPI_MSB_FIRST; spi_cfg.data_mode = HAL_SPI_MODE0; spi_cfg.baudrate = MYNEWT_VAL(BLE_HCI_EMSPI_BAUD); spi_cfg.word_size = HAL_SPI_WORD_SIZE_8BIT; rc = hal_spi_config(MYNEWT_VAL(BLE_HCI_EMSPI_SPI_NUM), &spi_cfg); SYSINIT_PANIC_ASSERT(rc == 0); rc = hal_gpio_irq_init(MYNEWT_VAL(BLE_HCI_EMSPI_RDY_PIN), ble_hci_emspi_rdy_isr, NULL, HAL_GPIO_TRIG_RISING, HAL_GPIO_PULL_DOWN); SYSINIT_PANIC_ASSERT(rc == 0); rc = hal_spi_enable(MYNEWT_VAL(BLE_HCI_EMSPI_SPI_NUM)); assert(rc == 0); hal_gpio_write(MYNEWT_VAL(BLE_HCI_EMSPI_RESET_PIN), 1); } void ble_transport_ll_init(void) { int rc; /* Ensure this function only gets called by sysinit. */ SYSINIT_ASSERT_ACTIVE(); /* * Create memory pool of packet list nodes. NOTE: the number of these * buffers should be, at least, 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_emspi_pkt_pool, 1 + MYNEWT_VAL(BLE_TRANSPORT_ACL_FROM_HS_COUNT), sizeof (struct ble_hci_emspi_pkt), ble_hci_emspi_pkt_buf, "ble_hci_emspi_pkt_pool"); SYSINIT_PANIC_ASSERT(rc == 0); STAILQ_INIT(&ble_hci_emspi_tx_q); ble_hci_emspi_init_hw(); /* Initialize the LL task */ os_eventq_init(&ble_hci_emspi_evq); rc = os_task_init(&ble_hci_emspi_task, "ble_hci_emspi", ble_hci_emspi_loop, NULL, MYNEWT_VAL(BLE_HCI_EMSPI_PRIO), OS_WAIT_FOREVER, ble_hci_emspi_stack, MYNEWT_VAL(BLE_HCI_EMSPI_STACK_SIZE)); SYSINIT_PANIC_ASSERT(rc == 0); } int ble_transport_to_ll_cmd_impl(void *buf) { return ble_hci_emspi_cmdevt_tx(buf, BLE_HCI_EMSPI_PKT_CMD); } int ble_transport_to_ll_acl_impl(struct os_mbuf *om) { return ble_hci_emspi_acl_tx(om); }