From baeb34a950a3cfa52491bd724b7fc8ffc81cd38f Mon Sep 17 00:00:00 2001 From: Andrei Emeltchenko Date: Wed, 13 Jan 2016 17:14:39 +0200 Subject: [PATCH] Bluetooth: nble: Add UART driver for Nordic BLE chip Adds basic support for communication with Nordic BLE (NBLE) chip connected to UART. Change-Id: I3651e291ec18805a63ecd3d240dce62273e3c498 Signed-off-by: Andrei Emeltchenko --- arch/x86/soc/quark_se/Kconfig | 11 + drivers/nble/Kconfig | 24 +++ drivers/nble/Makefile | 2 +- drivers/nble/uart.c | 374 ++++++++++++++++++++++++++++++++++ drivers/nble/uart.h | 173 ++++++++++++++++ 5 files changed, 583 insertions(+), 1 deletion(-) create mode 100644 drivers/nble/uart.c create mode 100644 drivers/nble/uart.h diff --git a/arch/x86/soc/quark_se/Kconfig b/arch/x86/soc/quark_se/Kconfig index 73b63908b70..a1c21f216e9 100644 --- a/arch/x86/soc/quark_se/Kconfig +++ b/arch/x86/soc/quark_se/Kconfig @@ -324,6 +324,17 @@ config BLUETOOTH_UART_IRQ_PRI endif +if NBLE + +config NBLE_UART_ON_DEV_NAME + default UART_NS16550_PORT_0_NAME +config NBLE_UART_IRQ + default UART_NS16550_PORT_0_IRQ +config NBLE_UART_IRQ_PRI + default UART_NS16550_PORT_0_IRQ_PRI + +endif + if UART_PIPE config UART_PIPE_ON_DEV_NAME diff --git a/drivers/nble/Kconfig b/drivers/nble/Kconfig index 3bb01aa7663..7eb92eaba45 100644 --- a/drivers/nble/Kconfig +++ b/drivers/nble/Kconfig @@ -37,6 +37,28 @@ config BLUETOOTH_MAX_PAIRED int default 7 +config NBLE_UART_ON_DEV_NAME + string "Device Name of UART Device for Nordic BLE" + default "UART_0" + depends on NBLE + help + This option specifies the name of UART device to be used + for Nordic BLE. + +config NBLE_UART_IRQ + int "IRQ of UART Device for Nordic BLE" + depends on NBLE + help + This option specifies the IRQ of UART device to be used + for Nordic BLE. + +config NBLE_UART_IRQ_PRI + int "IRQ Priority of UART Device for Nordic BLE" + depends on NBLE + help + This option specifies the IRQ priority of UART device to be used + for Nordic BLE. + config NBLE bool "Support for custom Nordic Semiconductor BLE protocol" default n @@ -44,6 +66,8 @@ config NBLE select BLUETOOTH_CENTRAL select BLUETOOTH_GATT_CLIENT select BLUETOOTH_SMP + select UART_INTERRUPT_DRIVEN + select NET_BUF help Enables support for using Nordic Semiconductor nRF51 Bluetooth LE chips with a custom firmware. The API for this is a subset of diff --git a/drivers/nble/Makefile b/drivers/nble/Makefile index b1aa0e35a3a..2e94f487c70 100644 --- a/drivers/nble/Makefile +++ b/drivers/nble/Makefile @@ -1 +1 @@ -obj-$(CONFIG_NBLE) += gap.o conn.o gatt.o +obj-$(CONFIG_NBLE) += gap.o conn.o gatt.o uart.o diff --git a/drivers/nble/uart.c b/drivers/nble/uart.c new file mode 100644 index 00000000000..47c98d3aedd --- /dev/null +++ b/drivers/nble/uart.c @@ -0,0 +1,374 @@ +/* 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 + +#include +#include +#include +#include + +#include + +#include + +#include "uart.h" + +/* TODO: check size */ +#define NBLE_IPC_COUNT 1 +#define NBLE_BUF_SIZE 100 + +static struct nano_fifo rx; +static NET_BUF_POOL(rx_pool, NBLE_IPC_COUNT, NBLE_BUF_SIZE, &rx, NULL, 0); + + +enum { + STATUS_TX_IDLE = 0, + STATUS_TX_BUSY, + STATUS_TX_DONE, +}; + +enum { + STATUS_RX_IDLE = 0, + STATUS_RX_HDR, + STATUS_RX_DATA +}; + +/** + * Describes the uart IPC to handle + */ +struct ipc_uart_info { + int uart_num; /* UART device to use */ + uint32_t irq_vector; /* IRQ number */ + uint32_t irq_mask; /* IRQ mask */ + + /* callback to be called to set wake state when TX is starting + * or ending + */ + void (*tx_cb)(bool wake_state, void*); + void *tx_cb_param; /* tx_cb function parameter */ +}; + +struct ipc_uart { + uint8_t *tx_data; + uint8_t *rx_ptr; + struct ipc_uart_channels channels[IPC_UART_MAX_CHANNEL]; + struct ipc_uart_header tx_hdr; + struct ipc_uart_header rx_hdr; + uint16_t send_counter; + uint16_t rx_size; + uint8_t tx_state; + uint8_t rx_state; + uint8_t uart_enabled; + /* protect against multiple wakelock and wake assert calls */ + uint8_t tx_wakelock_acquired; + /* TODO: remove once IRQ will take a parameter */ + struct device *device; +}; + +static struct ipc_uart ipc; + +static struct device *nble_dev; + +static void uart_frame_recv(uint16_t len, uint8_t *p_data) +{ + BT_DBG("rcv: len: %d data len %d src %d channel %d", + ipc.rx_hdr.len, len, ipc.rx_hdr.src_cpu_id, ipc.rx_hdr.channel); + + if ((ipc.rx_hdr.channel < IPC_UART_MAX_CHANNEL) && + (ipc.channels[ipc.rx_hdr.channel].cb != NULL)) { + ipc.channels[ipc.rx_hdr.channel].cb(ipc.rx_hdr.channel, + IPC_MSG_TYPE_MESSAGE, + len, + p_data); + } else { + BT_ERR("uart_ipc: bad channel %d", ipc.rx_hdr.channel); + } +} + +static int nble_read(struct device *uart, uint8_t *buf, + size_t len, size_t min) +{ + int total = 0; + + while (len) { + int rx; + + rx = uart_fifo_read(uart, buf, len); + if (rx == 0) { + BT_DBG("Got zero bytes from UART"); + if (total < min) { + 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 */ + uart_frame_recv(buf->len, buf->data); + net_buf_unref(buf); + buf = NULL; + } + } +} + +void *ipc_uart_channel_open(int channel_id, + int (*cb)(int, int, int, void *)) +{ + struct ipc_uart_channels *chan; + + if (channel_id > (IPC_UART_MAX_CHANNEL - 1)) + return NULL; + + chan = &ipc.channels[channel_id]; + + if (chan->state != IPC_CHANNEL_STATE_CLOSED) + return NULL; + + chan->state = IPC_CHANNEL_STATE_OPEN; + chan->cb = cb; + + ipc.uart_enabled = 1; + + return chan; +} + +void ipc_uart_close_channel(int channel_id) +{ + ipc.channels[channel_id].state = IPC_CHANNEL_STATE_CLOSED; + ipc.channels[channel_id].cb = NULL; + ipc.channels[channel_id].index = channel_id; + + ipc.uart_enabled = 0; +} + +static void uart_poll_bytes(uint8_t *buf, size_t len) +{ + while (len--) { + uart_poll_out(nble_dev, *buf++); + } +} + +int ipc_uart_ns16550_send_pdu(struct device *dev, void *handle, int len, + void *p_data) +{ + struct ipc_uart_channels *chan = (struct ipc_uart_channels *)handle; + struct ipc_uart_header hdr; + + if (ipc.tx_state == STATUS_TX_BUSY) { + return IPC_UART_TX_BUSY; + } + + /* It is eventually possible to be in DONE state + * (sending last bytes of previous message), + * so we move immediately to BUSY and configure the next frame + */ + + /* FIXME: needed? */ + ipc.tx_state = STATUS_TX_BUSY; + + /* Using polling for transmit */ + + /* Send header */ + hdr.len = len; + hdr.channel = chan->index; + hdr.src_cpu_id = 0; + + uart_poll_bytes((uint8_t *)&hdr, sizeof(hdr)); + + /* Send data */ + uart_poll_bytes(p_data, len); + + return IPC_UART_ERROR_OK; +} + +void ipc_uart_ns16550_set_tx_cb(struct device *dev, void (*cb)(bool, void*), + void *param) +{ + struct ipc_uart_info *info = dev->driver_data; + + info->tx_cb = cb; + info->tx_cb_param = param; +} + +static int ipc_uart_ns16550_init(struct device *dev) +{ + struct ipc_uart_info *info = dev->driver_data; + int i; + + /* Fail init if no info defined */ + if (!info) { + BT_ERR("No driver data found"); + return -1; + } + + for (i = 0; i < IPC_UART_MAX_CHANNEL; i++) { + ipc_uart_close_channel(i); + } + + /* Set dev used in irq handler */ + ipc.device = dev; + + ipc.uart_enabled = 0; + + /* Initialize the reception pointer */ + ipc.rx_size = sizeof(ipc.rx_hdr); + ipc.rx_ptr = (uint8_t *)&ipc.rx_hdr; + ipc.rx_state = STATUS_RX_IDLE; + + return 0; +} + +int nble_open(void) +{ + BT_DBG(""); + + 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; +} + +struct ipc_uart_info info; + +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); + + nble_dev->driver_data = &info; + ipc_uart_ns16550_init(nble_dev); + /* TODO: Register nble driver */ + + return DEV_OK; +} + +DEVICE_INIT(bt_nble, "", _bt_nble_init, NULL, NULL, NANOKERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/drivers/nble/uart.h b/drivers/nble/uart.h new file mode 100644 index 00000000000..3898292596d --- /dev/null +++ b/drivers/nble/uart.h @@ -0,0 +1,173 @@ +/* uart.h - Nordic BLE UART based Bluetooth driver */ + +/* + * Copyright (c) 2015 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. + */ + +/** + * IPC Uart power management driver. + */ +extern struct driver ipc_uart_ns16550_driver; + +enum IPC_UART_RESULT_CODES { + IPC_UART_ERROR_OK = 0, + IPC_UART_ERROR_DATA_TO_BIG, + /**< A transmission is already ongoing, message is NOT sent */ + IPC_UART_TX_BUSY +}; + +/** + * Definitions valid for NONE sync IPC UART headers. + */ + +/** + * @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. */ +}; + +#define IPC_CHANNEL_STATE_CLOSED 0 +#define IPC_CHANNEL_STATE_OPEN 1 + +#define IPC_UART_MAX_CHANNEL 1 + +struct ipc_uart_channels { + uint16_t index; + uint16_t state; + int (*cb)(int chan, int request, int len, void *data); +}; + +void ipc_uart_isr(void); +void ipc_uart_ns16550_disable(struct device *dev); + +/** + * This function triggers the sending of PDU buffer over UART. + * + * This constructs an IPC message header and triggers the sending of it and + * message buffer. If a transmission is already ongoing, it will fail. + * In this case upper layer needs to queue the message buffer. + * + * @param dev structure of the opened device + * @param handle structure of opened IPC uart channel + * @param len length of message to send + * @param p_data message buffer to send + * + * @return IPC_UART_ERROR_OK TX has been initiated, IPC_UART_TX_BUSY a + * transmission is already going, message needs to be queued + * + * @note This function needs to be executed with (UART) irq off to avoid + * pre-emption from uart_ipc_isr causing state variable corruption. + * It also called from uart_ipc_isr() to send the next IPC message. + */ +int ipc_uart_ns16550_send_pdu(struct device *dev, void *handle, int len, + void *p_data); + +/** + * This function registers a callback function being called on TX start/end. + * + * This constructs an IPC message header and triggers the sending of it and + * message buffer. If a transmission is already ongoing, it will fail. In + * this case upper layer needs to queue the message buffer. + * + * @param dev structure of the opened device + * @param cb callback function for OOB sleep mode handling called at tx start + * and end + * @param param parameter passed to cb when being called + */ +void ipc_uart_ns16550_set_tx_cb(struct device *dev, void (*cb)(bool, void*), + void *param); + +/** + * Opens a UART channel for QRK/BLE Core IPC, and defines the callback function + * for receiving IPC messages. + * + * @param channel IPC channel ID to use + * @param cb Callback to handle messages + * + * @return + * - Pointer to channel structure if success, + * - NULL if opening fails. + */ +void *ipc_uart_channel_open(int channel, int (*cb)(int chan, int request, + int len, void *data)); + +/** + * The frame is a message. + */ +#define IPC_MSG_TYPE_MESSAGE 0x1 + +/** + * Requests to free a message. + */ +#define IPC_MSG_TYPE_FREE 0x2 + +/** + * Sets the MessageBox as synchronized. + */ +#define IPC_MSG_TYPE_SYNC 0x3 + +/** + * Allocates a port. + * + * This request is always flowing from a slave to the master. + */ +#define IPC_REQUEST_ALLOC_PORT 0x10 + +/** + * Registers a service. + * + * This request is always flowing from a slave to the master. + */ +#define IPC_REQUEST_REGISTER_SERVICE 0x11 + +/** + * Unregisters a service. + */ +#define IPC_REQUEST_DEREGISTER_SERVICE 0x12 + +/** + * The message is for test commands engine. + */ +#define IPC_REQUEST_REG_TCMD_ENGINE 0x13 + +/** + * Registers a Service Manager Proxy to the Service Manager. + * + * This request always flow from a slave to the master. + */ +#define IPC_REQUEST_REGISTER_PROXY 0x14 + +/** + * Notifies a panic (for log dump). + */ +#define IPC_PANIC_NOTIFICATION 0x15 + +/** + * The message is for power management. + */ +#define IPC_REQUEST_POWER_MANAGEMENT 0x16 + +/** + * Sends the log of a slave to the master (for log aggregation). + */ +#define IPC_REQUEST_LOGGER 0x17 + +/** + * The message is for power management (deep sleep). + */ +#define IPC_REQUEST_INFRA_PM 0x18