/* uart.c - Nordic BLE UART based Bluetooth driver */ /* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_DEBUG_HCI_DRIVER) #include #include "../util.h" #include "rpc.h" #if defined(CONFIG_BLUETOOTH_NRF51_PM) #include "../nrf51_pm.h" #endif /** * @note this structure must be self-aligned and self-packed */ struct ipc_uart_header { uint16_t len; /**< Length of IPC message. */ uint8_t channel; /**< Channel number of IPC message. */ uint8_t src_cpu_id; /**< CPU id of IPC sender. */ } __packed; /* TODO: check size */ #define NBLE_TX_BUF_COUNT 2 #define NBLE_RX_BUF_COUNT 10 #define NBLE_BUF_SIZE 384 NET_BUF_POOL_DEFINE(rx_pool, NBLE_RX_BUF_COUNT, NBLE_BUF_SIZE, 0, NULL); NET_BUF_POOL_DEFINE(tx_pool, NBLE_TX_BUF_COUNT, NBLE_BUF_SIZE, 0, NULL); static BT_STACK_NOINIT(rx_thread_stack, CONFIG_BLUETOOTH_RX_STACK_SIZE); static struct device *nble_dev; static K_FIFO_DEFINE(rx_queue); static void rx_thread(void) { BT_DBG("Started"); while (true) { struct net_buf *buf; buf = net_buf_get(&rx_queue, K_FOREVER); BT_DBG("Got buf %p", buf); rpc_deserialize(buf); net_buf_unref(buf); /* Make sure we don't hog the CPU if the rx_queue never * gets empty. */ k_yield(); } } struct net_buf *rpc_alloc_cb(uint16_t length) { struct net_buf *buf; BT_DBG("length %u", length); buf = net_buf_alloc(&tx_pool, K_FOREVER); if (!buf) { BT_ERR("Unable to get tx buffer"); return NULL; } net_buf_reserve(buf, sizeof(struct ipc_uart_header)); if (length > net_buf_tailroom(buf)) { BT_ERR("Too big tx buffer requested"); net_buf_unref(buf); return NULL; } return buf; } void rpc_transmit_cb(struct net_buf *buf) { struct ipc_uart_header *hdr; BT_DBG("buf %p length %u", buf, buf->len); hdr = net_buf_push(buf, sizeof(*hdr)); hdr->len = buf->len - sizeof(*hdr); hdr->channel = 0; hdr->src_cpu_id = 0; #if defined(CONFIG_BLUETOOTH_NRF51_PM) /* Wake-up nble */ nrf51_wakeup(); #endif while (buf->len) { uart_poll_out(nble_dev, net_buf_pull_u8(buf)); } net_buf_unref(buf); #if defined(CONFIG_BLUETOOTH_NRF51_PM) /* TODO check if FIFO is empty */ /* Allow nble to go to deep sleep */ nrf51_allow_sleep(); #endif } static size_t nble_discard(struct device *uart, size_t len) { /* FIXME: correct size for nble */ uint8_t buf[33]; return uart_fifo_read(uart, buf, min(len, sizeof(buf))); } static void bt_uart_isr(struct device *unused) { static struct net_buf *buf; ARG_UNUSED(unused); while (uart_irq_update(nble_dev) && uart_irq_is_pending(nble_dev)) { static struct ipc_uart_header hdr; static uint8_t hdr_bytes; int read; if (!uart_irq_rx_ready(nble_dev)) { if (uart_irq_tx_ready(nble_dev)) { BT_DBG("transmit ready"); /* * Implementing ISR based transmit requires * extra API for uart such as * uart_line_status(), etc. The support was * removed from the recent code, using polling * for transmit for now. */ } else { BT_DBG("spurious interrupt"); } continue; } if (hdr_bytes < sizeof(hdr)) { /* Get packet type */ hdr_bytes += uart_fifo_read(nble_dev, (uint8_t *)&hdr + hdr_bytes, sizeof(hdr) - hdr_bytes); if (hdr_bytes < sizeof(hdr)) { continue; } if (hdr.len > NBLE_BUF_SIZE) { BT_ERR("Too much data to fit buffer"); buf = NULL; } else { buf = net_buf_alloc(&rx_pool, K_NO_WAIT); if (!buf) { BT_ERR("No available IPC buffers"); } } } if (!buf) { hdr.len -= nble_discard(nble_dev, hdr.len); if (!hdr.len) { hdr_bytes = 0; } continue; } read = uart_fifo_read(nble_dev, net_buf_tail(buf), hdr.len); buf->len += read; hdr.len -= read; if (!hdr.len) { BT_DBG("full packet received"); hdr_bytes = 0; /* Pass buffer to the stack */ net_buf_put(&rx_queue, buf); } } } int nble_open(void) { BT_DBG(""); /* Initialize receive queue and start rx_thread */ k_thread_spawn(rx_thread_stack, sizeof(rx_thread_stack), (k_thread_entry_t)rx_thread, NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); uart_irq_rx_disable(nble_dev); uart_irq_tx_disable(nble_dev); #if defined(CONFIG_BLUETOOTH_NRF51_PM) if (nrf51_init(nble_dev) < 0) { return -EIO; } #else bt_uart_drain(nble_dev); #endif uart_irq_callback_set(nble_dev, bt_uart_isr); uart_irq_rx_enable(nble_dev); return 0; } static int _bt_nble_init(struct device *unused) { ARG_UNUSED(unused); nble_dev = device_get_binding(CONFIG_NBLE_UART_ON_DEV_NAME); if (!nble_dev) { return -EINVAL; } return 0; } DEVICE_INIT(bt_nble, "", _bt_nble_init, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);