diff --git a/drivers/bluetooth/hci/CMakeLists.txt b/drivers/bluetooth/hci/CMakeLists.txt index 7465b2d5e47..e8ac3e6558e 100644 --- a/drivers/bluetooth/hci/CMakeLists.txt +++ b/drivers/bluetooth/hci/CMakeLists.txt @@ -3,5 +3,7 @@ zephyr_sources_ifdef(CONFIG_BT_H4 h4.c) zephyr_sources_ifdef(CONFIG_BT_H5 h5.c) zephyr_sources_ifdef(CONFIG_BT_SPI spi.c) -zephyr_sources_ifdef(CONFIG_BT_STM32_IPM ipm_stm32wb.c) +zephyr_sources_ifdef(CONFIG_BT_RPMSG rpmsg.c) +zephyr_sources_ifdef(CONFIG_BT_RPMSG_NRF53 rpmsg_nrf53.c) +zephyr_sources_ifdef(CONFIG_BT_STM32_IPM ipm_stm32wb.c) zephyr_sources_ifdef(CONFIG_BT_USERCHAN userchan.c) diff --git a/drivers/bluetooth/hci/Kconfig b/drivers/bluetooth/hci/Kconfig index 605afa38cc3..694a532e017 100644 --- a/drivers/bluetooth/hci/Kconfig +++ b/drivers/bluetooth/hci/Kconfig @@ -30,6 +30,12 @@ config BT_H5 Bluetooth three-wire (H:5) UART driver. Implementation of HCI Three-Wire UART Transport Layer. +config BT_RPMSG + bool "HCI using RPMsg" + help + Bluetooth HCI driver for communication with another CPU + using RPMsg framework. + config BT_SPI bool "SPI HCI" depends on SPI @@ -92,3 +98,33 @@ config BT_SPI_BLUENRG Stack. Current driver supports: ST BLUENRG-MS. endif # BT_SPI + +if BT_RPMSG + +config BT_RPMSG_NRF53 + bool "nRF53 configuration of RPMsg" + default y if BOARD_NRF5340_DK_NRF5340_CPUAPP + select IPM + select IPM_NRFX + select IPM_MSG_CH_1_ENABLE + select IPM_MSG_CH_0_ENABLE + select IPM_MSG_CH_0_TX + select IPM_MSG_CH_1_RX + select OPENAMP + help + Enable RPMsg configuration for nRF53. Two channels of the IPM driver + are used in the HCI driver: channel 0 for TX and channel 1 for RX. + +if BT_RPMSG_NRF53 + +config BT_RPMSG_NRF53_RX_STACK_SIZE + int "RPMsg stack size for RX thread" + default 1024 + +config BT_RPMSG_NRF53_RX_PRIO + int "RPMsg RX thread priority" + default 8 + +endif # BT_RPMSG_NRF53 + +endif # BT_RPMSG diff --git a/drivers/bluetooth/hci/rpmsg.c b/drivers/bluetooth/hci/rpmsg.c new file mode 100644 index 00000000000..42a50606be5 --- /dev/null +++ b/drivers/bluetooth/hci/rpmsg.c @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_hci_driver +#include "common/log.h" + +#define RPMSG_CMD 0x01 +#define RPMSG_ACL 0x02 +#define RPMSG_SCO 0x03 +#define RPMSG_EVT 0x04 + +int bt_rpmsg_platform_init(void); +int bt_rpmsg_platform_send(struct net_buf *buf); + +static struct net_buf *bt_rpmsg_evt_recv(u8_t *data, size_t remaining, + bool *prio) +{ + struct bt_hci_evt_hdr hdr; + struct net_buf *buf; + + if (remaining < sizeof(hdr)) { + BT_ERR("Not enough data for event header"); + return NULL; + } + + memcpy((void *)&hdr, data, sizeof(hdr)); + data += sizeof(hdr); + remaining -= sizeof(hdr); + + if (remaining != hdr.len) { + BT_ERR("Event payload length is not correct"); + return NULL; + } + BT_DBG("len %u", hdr.len); + + buf = bt_buf_get_evt(hdr.evt, false, K_NO_WAIT); + if (!buf) { + BT_ERR("No available event buffers!"); + return buf; + } + + net_buf_add_mem(buf, &hdr, sizeof(hdr)); + *prio = bt_hci_evt_is_prio(hdr.evt); + + net_buf_add_mem(buf, data, remaining); + + return buf; +} + +static struct net_buf *bt_rpmsg_acl_recv(u8_t *data, size_t remaining) +{ + struct bt_hci_acl_hdr hdr; + struct net_buf *buf; + + if (remaining < sizeof(hdr)) { + BT_ERR("Not enough data for ACL header"); + return NULL; + } + + buf = bt_buf_get_rx(BT_BUF_ACL_IN, K_NO_WAIT); + if (buf) { + memcpy((void *)&hdr, data, sizeof(hdr)); + data += sizeof(hdr); + remaining -= sizeof(hdr); + + net_buf_add_mem(buf, &hdr, sizeof(hdr)); + } else { + BT_ERR("No available ACL buffers!"); + return NULL; + } + + if (remaining != sys_le16_to_cpu(hdr.len)) { + BT_ERR("ACL payload length is not correct"); + net_buf_unref(buf); + return NULL; + } + + BT_DBG("len %u", remaining); + net_buf_add_mem(buf, data, remaining); + + return buf; +} + +void bt_rpmsg_rx(u8_t *data, size_t len) +{ + u8_t pkt_indicator; + bool prio = false; + struct net_buf *buf = NULL; + size_t remaining = len; + + BT_HEXDUMP_DBG(data, len, "RPMsg data:"); + + pkt_indicator = *data++; + remaining -= sizeof(pkt_indicator); + + switch (pkt_indicator) { + case RPMSG_EVT: + buf = bt_rpmsg_evt_recv(data, remaining, &prio); + break; + + case RPMSG_ACL: + buf = bt_rpmsg_acl_recv(data, remaining); + break; + + default: + BT_ERR("Unknown HCI type %u", pkt_indicator); + return; + } + + if (buf) { + BT_DBG("Calling bt_recv(%p)", buf); + if (prio) { + bt_recv_prio(buf); + } else { + bt_recv(buf); + } + + BT_HEXDUMP_DBG(buf->data, buf->len, "RX buf payload:"); + } +} + +static int bt_rpmsg_send(struct net_buf *buf) +{ + int err; + u8_t pkt_indicator; + + BT_DBG("buf %p type %u len %u", buf, bt_buf_get_type(buf), buf->len); + + switch (bt_buf_get_type(buf)) { + case BT_BUF_ACL_OUT: + pkt_indicator = RPMSG_ACL; + break; + case BT_BUF_CMD: + pkt_indicator = RPMSG_CMD; + break; + default: + BT_ERR("Unknown type %u", bt_buf_get_type(buf)); + goto done; + } + net_buf_push_u8(buf, pkt_indicator); + + BT_HEXDUMP_DBG(buf->data, buf->len, "Final HCI buffer:"); + err = bt_rpmsg_platform_send(buf); + if (err < 0) { + BT_ERR("Failed to send (err %d)", err); + } + +done: + net_buf_unref(buf); + return 0; +} + +static int bt_rpmsg_open(void) +{ + BT_DBG(""); + + return bt_rpmsg_platform_init(); +} + +static const struct bt_hci_driver drv = { + .name = "RPMsg", + .open = bt_rpmsg_open, + .send = bt_rpmsg_send, + .bus = BT_HCI_DRIVER_BUS_IPM, +}; + +static int bt_rpmsg_init(struct device *unused) +{ + ARG_UNUSED(unused); + + return bt_hci_driver_register(&drv); +} + +SYS_INIT(bt_rpmsg_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/drivers/bluetooth/hci/rpmsg_nrf53.c b/drivers/bluetooth/hci/rpmsg_nrf53.c new file mode 100644 index 00000000000..90743347471 --- /dev/null +++ b/drivers/bluetooth/hci/rpmsg_nrf53.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#define LOG_MODULE_NAME bt_hci_driver_nrf53 +#include "common/log.h" + +void bt_rpmsg_rx(u8_t *data, size_t len); + +static K_SEM_DEFINE(ready_sem, 0, 1); +static K_SEM_DEFINE(rx_sem, 0, 1); + +static K_THREAD_STACK_DEFINE(bt_rpmsg_rx_thread_stack, + CONFIG_BT_RPMSG_NRF53_RX_STACK_SIZE); +static struct k_thread bt_rpmsg_rx_thread_data; + +static struct device *ipm_tx_handle; +static struct device *ipm_rx_handle; + +/* Configuration defines */ + +#define SHM_START_ADDR (DT_IPC_SHM_BASE_ADDRESS + 0x400) +#define SHM_SIZE 0x7c00 +#define SHM_DEVICE_NAME "sram0.shm" + +#define VRING_COUNT 2 +#define VRING_TX_ADDRESS (SHM_START_ADDR + SHM_SIZE - 0x400) +#define VRING_RX_ADDRESS (VRING_TX_ADDRESS - 0x400) +#define VRING_ALIGNMENT 4 +#define VRING_SIZE 16 + +#define VDEV_STATUS_ADDR DT_IPC_SHM_BASE_ADDRESS + +/* End of configuration defines */ + +static metal_phys_addr_t shm_physmap[] = { SHM_START_ADDR }; +static struct metal_device shm_device = { + .name = SHM_DEVICE_NAME, + .bus = NULL, + .num_regions = 1, + .regions = { + { + .virt = (void *) SHM_START_ADDR, + .physmap = shm_physmap, + .size = SHM_SIZE, + .page_shift = 0xffffffff, + .page_mask = 0xffffffff, + .mem_flags = 0, + .ops = { NULL }, + }, + }, + .node = { NULL }, + .irq_num = 0, + .irq_info = NULL +}; + +static struct virtqueue *vq[2]; +static struct rpmsg_endpoint ep; + +static unsigned char virtio_get_status(struct virtio_device *vdev) +{ + return VIRTIO_CONFIG_STATUS_DRIVER_OK; +} + +static void virtio_set_status(struct virtio_device *vdev, unsigned char status) +{ + sys_write8(status, VDEV_STATUS_ADDR); +} + +static u32_t virtio_get_features(struct virtio_device *vdev) +{ + return BIT(VIRTIO_RPMSG_F_NS); +} + +static void virtio_set_features(struct virtio_device *vdev, u32_t features) +{ + /* No need for implementation */ +} + +static void virtio_notify(struct virtqueue *vq) +{ + int status; + + status = ipm_send(ipm_tx_handle, 0, 0, NULL, 0); + if (status != 0) { + BT_ERR("ipm_send failed to notify: %d", status); + } +} + +const struct virtio_dispatch dispatch = { + .get_status = virtio_get_status, + .set_status = virtio_set_status, + .get_features = virtio_get_features, + .set_features = virtio_set_features, + .notify = virtio_notify, +}; + +static void ipm_callback(void *context, u32_t id, volatile void *data) +{ + BT_DBG("Got callback of id %u", id); + k_sem_give(&rx_sem); +} + +static int endpoint_cb(struct rpmsg_endpoint *ept, void *data, size_t len, + u32_t src, void *priv) +{ + BT_DBG("Received message of %u bytes.", len); + BT_HEXDUMP_DBG((uint8_t *)data, len, "Data:"); + + bt_rpmsg_rx(data, len); + + return RPMSG_SUCCESS; +} + +static void rpmsg_service_unbind(struct rpmsg_endpoint *ep) +{ + rpmsg_destroy_ept(ep); +} + +static void ns_bind_cb(struct rpmsg_device *rdev, const char *name, u32_t dest) +{ + (void)rpmsg_create_ept(&ep, + rdev, + name, + RPMSG_ADDR_ANY, + dest, + endpoint_cb, + rpmsg_service_unbind); + + k_sem_give(&ready_sem); +} + +static void bt_rpmsg_rx_thread(void *p1, void *p2, void *p3) +{ + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + while (1) { + int status = k_sem_take(&rx_sem, K_FOREVER); + + if (status == 0) { + virtqueue_notification(vq[0]); + } + } +} + +int bt_rpmsg_platform_init(void) +{ + int err; + struct metal_init_params metal_params = METAL_INIT_DEFAULTS; + + static struct virtio_vring_info rvrings[2]; + static struct rpmsg_virtio_shm_pool shpool; + static struct virtio_device vdev; + static struct rpmsg_virtio_device rvdev; + static struct metal_io_region *io; + static struct metal_device *device; + + /* Setup thread for RX data processing. */ + k_thread_create(&bt_rpmsg_rx_thread_data, bt_rpmsg_rx_thread_stack, + K_THREAD_STACK_SIZEOF(bt_rpmsg_rx_thread_stack), + bt_rpmsg_rx_thread, NULL, NULL, NULL, + K_PRIO_COOP(CONFIG_BT_RPMSG_NRF53_RX_PRIO), + 0, K_NO_WAIT); + + /* Libmetal setup */ + err = metal_init(&metal_params); + if (err) { + BT_ERR("metal_init: failed - error code %d", err); + return err; + } + + err = metal_register_generic_device(&shm_device); + if (err) { + BT_ERR("Couldn't register shared memory device: %d", err); + return err; + } + + err = metal_device_open("generic", SHM_DEVICE_NAME, &device); + if (err) { + BT_ERR("metal_device_open failed: %d", err); + return err; + } + + io = metal_device_io_region(device, 0); + if (!io) { + BT_ERR("metal_device_io_region failed to get region"); + return -ENODEV; + } + + /* IPM setup */ + ipm_tx_handle = device_get_binding("IPM_0"); + if (!ipm_tx_handle) { + BT_ERR("Could not get TX IPM device handle"); + return -ENODEV; + } + + ipm_rx_handle = device_get_binding("IPM_1"); + if (!ipm_rx_handle) { + BT_ERR("Could not get RX IPM device handle"); + return -ENODEV; + } + + ipm_register_callback(ipm_rx_handle, ipm_callback, NULL); + + /* Virtqueue setup */ + vq[0] = virtqueue_allocate(VRING_SIZE); + if (!vq[0]) { + BT_ERR("virtqueue_allocate failed to alloc vq[0]"); + return -ENOMEM; + } + + vq[1] = virtqueue_allocate(VRING_SIZE); + if (!vq[1]) { + BT_ERR("virtqueue_allocate failed to alloc vq[1]"); + return -ENOMEM; + } + + rvrings[0].io = io; + rvrings[0].info.vaddr = (void *)VRING_TX_ADDRESS; + rvrings[0].info.num_descs = VRING_SIZE; + rvrings[0].info.align = VRING_ALIGNMENT; + rvrings[0].vq = vq[0]; + + rvrings[1].io = io; + rvrings[1].info.vaddr = (void *)VRING_RX_ADDRESS; + rvrings[1].info.num_descs = VRING_SIZE; + rvrings[1].info.align = VRING_ALIGNMENT; + rvrings[1].vq = vq[1]; + + vdev.role = RPMSG_MASTER; + vdev.vrings_num = VRING_COUNT; + vdev.func = &dispatch; + vdev.vrings_info = &rvrings[0]; + + rpmsg_virtio_init_shm_pool(&shpool, (void *)SHM_START_ADDR, SHM_SIZE); + err = rpmsg_init_vdev(&rvdev, &vdev, ns_bind_cb, io, &shpool); + if (err) { + BT_ERR("rpmsg_init_vdev failed %d", err); + return err; + } + + /* Wait til nameservice ep is setup */ + k_sem_take(&ready_sem, K_FOREVER); + + return 0; +} + +int bt_rpmsg_platform_send(struct net_buf *buf) +{ + return rpmsg_send(&ep, buf->data, buf->len); +}