| /* uart.c - Nordic BLE UART based Bluetooth driver */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * Licensed 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 <nanokernel.h> |
| #include <sections.h> |
| |
| #include <board.h> |
| #include <init.h> |
| #include <uart.h> |
| #include <string.h> |
| |
| #include <net/buf.h> |
| |
| #include <bluetooth/log.h> |
| |
| #include "uart.h" |
| #include "rpc.h" |
| |
| /* TODO: check size */ |
| #define NBLE_IPC_COUNT 2 |
| #define NBLE_BUF_SIZE 384 |
| |
| static struct nano_fifo rx; |
| static NET_BUF_POOL(rx_pool, NBLE_IPC_COUNT, NBLE_BUF_SIZE, &rx, NULL, 0); |
| |
| static struct nano_fifo tx; |
| static NET_BUF_POOL(tx_pool, NBLE_IPC_COUNT, NBLE_BUF_SIZE, &tx, NULL, 0); |
| |
| static BT_STACK_NOINIT(rx_fiber_stack, 2048); |
| |
| static struct device *nble_dev; |
| |
| static struct nano_fifo rx_queue; |
| |
| static void rx_fiber(void) |
| { |
| BT_DBG("Started"); |
| |
| while (true) { |
| struct net_buf *buf; |
| |
| buf = nano_fifo_get(&rx_queue, TICKS_UNLIMITED); |
| BT_DBG("Got buf %p", buf); |
| |
| rpc_deserialize(buf->data, buf->len); |
| |
| net_buf_unref(buf); |
| } |
| } |
| |
| uint8_t *rpc_alloc_cb(uint16_t length) |
| { |
| struct net_buf *buf; |
| |
| BT_DBG("length %u", length); |
| |
| buf = net_buf_get(&tx, 0); |
| if (!buf) { |
| BT_ERR("Unable to get tx buffer"); |
| return NULL; |
| } |
| |
| if (length > net_buf_tailroom(buf)) { |
| BT_ERR("Too big tx buffer requested"); |
| net_buf_unref(buf); |
| return NULL; |
| } |
| |
| return buf->__buf; |
| } |
| |
| static void poll_out(const void *buf, size_t length) |
| { |
| const uint8_t *ptr = buf; |
| |
| while (length--) { |
| uart_poll_out(nble_dev, *ptr++); |
| } |
| } |
| |
| void rpc_transmit_cb(uint8_t *data, uint16_t length) |
| { |
| struct net_buf *buf = CONTAINER_OF(data, struct net_buf, __buf); |
| struct ipc_uart_header hdr; |
| |
| BT_DBG("buf %p length %u", data, length); |
| |
| hdr.len = length; |
| hdr.channel = 0; |
| hdr.src_cpu_id = 0; |
| |
| /* Send header */ |
| poll_out(&hdr, sizeof(hdr)); |
| |
| /* Send data */ |
| poll_out(buf->data, length); |
| |
| net_buf_unref(buf); |
| } |
| |
| static int nble_read(struct device *uart, uint8_t *buf, |
| size_t len, size_t min) |
| { |
| int total = 0; |
| int tries = 10; |
| |
| while (len) { |
| int rx; |
| |
| rx = uart_fifo_read(uart, buf, len); |
| if (rx == 0) { |
| BT_DBG("Got zero bytes from UART"); |
| if (total < min && tries--) { |
| continue; |
| } |
| break; |
| } |
| |
| BT_DBG("read %d remaining %d", rx, len - rx); |
| len -= rx; |
| total += rx; |
| buf += rx; |
| } |
| |
| return total; |
| } |
| |
| 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))); |
| } |
| |
| void bt_uart_isr(void *unused) |
| { |
| static struct net_buf *buf; |
| static int remaining; |
| |
| ARG_UNUSED(unused); |
| |
| while (uart_irq_update(nble_dev) && uart_irq_is_pending(nble_dev)) { |
| 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; |
| } |
| |
| /* Beginning of a new packet */ |
| if (!remaining) { |
| struct ipc_uart_header hdr; |
| |
| /* Get packet type */ |
| read = nble_read(nble_dev, (uint8_t *)&hdr, |
| sizeof(hdr), sizeof(hdr)); |
| if (read != sizeof(hdr)) { |
| BT_WARN("Unable to read NBLE header"); |
| continue; |
| } |
| |
| remaining = hdr.len; |
| |
| buf = net_buf_get(&rx, 0); |
| if (!buf) { |
| BT_ERR("No available IPC buffers"); |
| } |
| #if 0 |
| } else { |
| memcpy(net_buf_add(buf, sizeof(hdr)), &hdr, |
| sizeof(hdr)); |
| } |
| #endif |
| |
| BT_DBG("need to get %u bytes", remaining); |
| |
| if (buf && remaining > net_buf_tailroom(buf)) { |
| BT_ERR("Not enough space in buffer"); |
| net_buf_unref(buf); |
| buf = NULL; |
| } |
| } |
| |
| if (!buf) { |
| read = nble_discard(nble_dev, remaining); |
| BT_WARN("Discarded %d bytes", read); |
| remaining -= read; |
| continue; |
| } |
| |
| read = nble_read(nble_dev, net_buf_tail(buf), remaining, 0); |
| |
| buf->len += read; |
| remaining -= read; |
| |
| BT_DBG("received %d bytes", read); |
| |
| if (!remaining) { |
| BT_DBG("full packet received"); |
| |
| /* Pass buffer to the stack */ |
| nano_fifo_put(&rx_queue, buf); |
| } |
| } |
| } |
| |
| int nble_open(void) |
| { |
| BT_DBG(""); |
| |
| /* Initialize receive queue and start rx_fiber */ |
| nano_fifo_init(&rx_queue); |
| fiber_start(rx_fiber_stack, sizeof(rx_fiber_stack), |
| (nano_fiber_entry_t)rx_fiber, 0, 0, 7, 0); |
| |
| uart_irq_rx_disable(nble_dev); |
| uart_irq_tx_disable(nble_dev); |
| |
| IRQ_CONNECT(CONFIG_NBLE_UART_IRQ, CONFIG_NBLE_UART_IRQ_PRI, |
| bt_uart_isr, 0, UART_IRQ_FLAGS); |
| irq_enable(CONFIG_NBLE_UART_IRQ); |
| |
| /* Drain the fifo */ |
| while (uart_irq_rx_ready(nble_dev)) { |
| unsigned char c; |
| |
| uart_fifo_read(nble_dev, &c, 1); |
| } |
| |
| 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 DEV_INVALID_CONF; |
| } |
| |
| net_buf_pool_init(rx_pool); |
| net_buf_pool_init(tx_pool); |
| |
| return DEV_OK; |
| } |
| |
| DEVICE_INIT(bt_nble, "", _bt_nble_init, NULL, NULL, NANOKERNEL, |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE); |