diff --git a/boards/posix/native_posix/board.h b/boards/posix/native_posix/board.h index 39d984a6c10..fcf5adafb68 100644 --- a/boards/posix/native_posix/board.h +++ b/boards/posix/native_posix/board.h @@ -9,4 +9,8 @@ #include +#if defined(CONFIG_BT_USERCHAN) +extern int bt_dev_index; +#endif + #endif /* __INC_BOARD_H */ diff --git a/boards/posix/native_posix/cmdline.c b/boards/posix/native_posix/cmdline.c index e2d5219a909..3c5591c53c2 100644 --- a/boards/posix/native_posix/cmdline.c +++ b/boards/posix/native_posix/cmdline.c @@ -5,11 +5,13 @@ */ #include +#include #include "cmdline_common.h" #include "zephyr/types.h" #include "hw_models_top.h" #include "cmdline.h" #include "toolchain.h" +#include "board.h" static int s_argc, test_argc; static char **s_argv, **test_argv; @@ -36,6 +38,22 @@ static void cmd_seed_found(char *argv, int offset) } #endif +#if defined(CONFIG_BT_USERCHAN) +int bt_dev_index = -1; + +static void cmd_bt_dev_found(char *argv, int offset) +{ + if (strncmp(&argv[offset], "hci", 3) || strlen(&argv[offset]) < 4) { + posix_print_error_and_exit("Error: Invalid Bluetooth device " + "name '%s' (should be e.g. hci0)\n", + &argv[offset]); + return; + } + + bt_dev_index = strtol(&argv[offset + 3], NULL, 10); +} +#endif + /** * Handle possible command line arguments. * @@ -66,6 +84,13 @@ void native_handle_cmd_line(int argc, char *argv[]) "97229 (decimal), 0x17BCD (hex), or 0275715 (octal)"}, #endif +#if defined(CONFIG_BT_USERCHAN) + { false, true, false, + "bt-dev", "hciX", 's', + NULL, cmd_bt_dev_found, + "A local HCI device to be used for Bluetooth (e.g. hci0)" }, +#endif + {true, false, false, "testargs", "arg", 'l', (void *)NULL, NULL, @@ -94,6 +119,13 @@ void native_handle_cmd_line(int argc, char *argv[]) argv[i]); } } + +#if defined(CONFIG_BT_USERCHAN) + if (bt_dev_index < 0) { + posix_print_error_and_exit("Error: Bluetooth device missing. " + "Specify one using --bt-dev=hciN\n"); + } +#endif } /** diff --git a/boards/posix/native_posix/doc/board.rst b/boards/posix/native_posix/doc/board.rst index e3f3daf6367..c842c394662 100644 --- a/boards/posix/native_posix/doc/board.rst +++ b/boards/posix/native_posix/doc/board.rst @@ -411,6 +411,16 @@ The following peripherals are currently provided with this board: Note that this device can only be used with Linux hosts, and that the user needs elevated permissions. +**Bluetooth controller**: + It's possible to use the host's Bluetooth adapter as a Bluetooth + controller for Zephyr. To do this the HCI device needs to be passed as + a command line option to ``zephyr.exe``. For example, to use ``hci0``, + use ``sudo zephyr.exe --bt-dev=hci0``. Using the device requires root + privileges (or the CAP_NET_ADMIN POSIX capability, to be exact) so + ``zephyr.exe`` needs to be run through ``sudo``. The chosen HCI device + must be powered down and support Bluetooth Low Energy (i.e. support the + Bluetooth specification version 4.0 or greater). + Shell support ************* diff --git a/boards/posix/native_posix/native_posix_defconfig b/boards/posix/native_posix/native_posix_defconfig index 30a652a63ac..3fde45a174b 100644 --- a/boards/posix/native_posix/native_posix_defconfig +++ b/boards/posix/native_posix/native_posix_defconfig @@ -6,3 +6,4 @@ CONFIG_SERIAL=y CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=1000000 CONFIG_COVERAGE=y CONFIG_FAKE_ENTROPY_NATIVE_POSIX=y +CONFIG_BT_USERCHAN=y diff --git a/drivers/bluetooth/hci/CMakeLists.txt b/drivers/bluetooth/hci/CMakeLists.txt index ba266a6c17a..8c3e3dcfa3a 100644 --- a/drivers/bluetooth/hci/CMakeLists.txt +++ b/drivers/bluetooth/hci/CMakeLists.txt @@ -1,3 +1,4 @@ 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_USERCHAN userchan.c) diff --git a/drivers/bluetooth/hci/Kconfig b/drivers/bluetooth/hci/Kconfig index 3250a472bde..e6a5727369f 100644 --- a/drivers/bluetooth/hci/Kconfig +++ b/drivers/bluetooth/hci/Kconfig @@ -43,6 +43,16 @@ config BT_SPI additional platform specific knowledge may need to be added as devices are. +config BT_USERCHAN + bool "HCI User Channel based driver" + depends on BOARD_NATIVE_POSIX + help + This driver provides access to the local Linux host's Bluetooth + adapter using a User Channel HCI socket to the Linux kernel. It + is only intended to be used with the native POSIX build of Zephyr. + The Bluetooth adapter must be powered off in order for Zephyr to + be able to use it. + config BT_NO_DRIVER bool "No default HCI driver" help diff --git a/drivers/bluetooth/hci/userchan.c b/drivers/bluetooth/hci/userchan.c new file mode 100644 index 00000000000..8072612dadc --- /dev/null +++ b/drivers/bluetooth/hci/userchan.c @@ -0,0 +1,218 @@ +/* userchan.c - HCI User Channel based Bluetooth driver */ + +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) +#include "common/log.h" + +#define BTPROTO_HCI 1 +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_CHANNEL_USER 1 + +#define SOL_HCI 0 + +#define H4_CMD 0x01 +#define H4_ACL 0x02 +#define H4_SCO 0x03 +#define H4_EVT 0x04 + +static BT_STACK_NOINIT(rx_thread_stack, + CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE); +static struct k_thread rx_thread_data; + +static int uc_fd = -1; + +static struct net_buf *get_rx(const u8_t *buf) +{ + if (buf[0] == H4_EVT && (buf[1] == BT_HCI_EVT_CMD_COMPLETE || + buf[1] == BT_HCI_EVT_CMD_STATUS)) { + return bt_buf_get_cmd_complete(K_FOREVER); + } + + if (buf[0] == H4_ACL) { + return bt_buf_get_rx(BT_BUF_ACL_IN, K_FOREVER); + } else { + return bt_buf_get_rx(BT_BUF_EVT, K_FOREVER); + } +} + +static bool uc_ready(void) +{ + struct pollfd pollfd = { .fd = uc_fd, .events = POLLIN }; + + return (poll(&pollfd, 1, 0) == 1); +} + +static void rx_thread(void *p1, void *p2, void *p3) +{ + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + BT_DBG("started"); + + while (1) { + static u8_t frame[512]; + struct net_buf *buf; + ssize_t len; + + if (!uc_ready()) { + k_sleep(K_MSEC(20)); + continue; + } + + BT_DBG("calling read()"); + + len = read(uc_fd, frame, sizeof(frame)); + if (len < 0) { + if (errno == EINTR) { + k_yield(); + continue; + } + + BT_ERR("Reading socket failed, errno %d", errno); + close(uc_fd); + uc_fd = -1; + return; + } + + buf = get_rx(frame); + net_buf_add_mem(buf, &frame[1], len - 1); + + BT_DBG("Calling bt_recv(%p)", buf); + + if (frame[0] == H4_EVT && bt_hci_evt_is_prio(frame[1])) { + bt_recv_prio(buf); + } else { + bt_recv(buf); + } + + k_yield(); + } +} + +static int uc_send(struct net_buf *buf) +{ + BT_DBG("buf %p type %u len %u", buf, bt_buf_get_type(buf), buf->len); + + if (uc_fd < 0) { + BT_ERR("User channel not open"); + return -EIO; + } + + switch (bt_buf_get_type(buf)) { + case BT_BUF_ACL_OUT: + net_buf_push_u8(buf, H4_ACL); + break; + case BT_BUF_CMD: + net_buf_push_u8(buf, H4_CMD); + break; + default: + BT_ERR("Unknown buffer type"); + return -EINVAL; + } + + if (write(uc_fd, buf->data, buf->len) < 0) { + return -errno; + } + + net_buf_unref(buf); + return 0; +} + +static int user_chan_open(u16_t index) +{ + struct sockaddr_hci addr; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + BTPROTO_HCI); + if (fd < 0) { + return -errno; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = index; + addr.hci_channel = HCI_CHANNEL_USER; + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + int err = -errno; + + close(fd); + return err; + } + + return fd; +} + +static int uc_open(void) +{ + if (bt_dev_index < 0) { + BT_ERR("No Bluetooth device specified"); + return -ENODEV; + } + + BT_DBG("hci%d", bt_dev_index); + + uc_fd = user_chan_open(bt_dev_index); + if (uc_fd < 0) { + return uc_fd; + } + + BT_DBG("User Channel opened as fd %d", uc_fd); + + k_thread_create(&rx_thread_data, rx_thread_stack, + K_THREAD_STACK_SIZEOF(rx_thread_stack), + rx_thread, NULL, NULL, NULL, + K_PRIO_COOP(CONFIG_BT_RX_PRIO - 1), + 0, K_NO_WAIT); + + BT_DBG("returning"); + + return 0; +} + +static const struct bt_hci_driver drv = { + .name = "HCI User Channel", + .bus = BT_HCI_DRIVER_BUS_UART, + .open = uc_open, + .send = uc_send, +}; + +static int _bt_uc_init(struct device *unused) +{ + ARG_UNUSED(unused); + + bt_hci_driver_register(&drv); + + return 0; +} + +SYS_INIT(_bt_uc_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/subsys/bluetooth/common/Kconfig b/subsys/bluetooth/common/Kconfig index 6bac05a7f1f..dfdd2c0a406 100644 --- a/subsys/bluetooth/common/Kconfig +++ b/subsys/bluetooth/common/Kconfig @@ -18,7 +18,7 @@ config BT_HCI_VS_EXT config BT_HCI_VS_EXT_DETECT bool "Use heuristics to guess HCI vendor extensions support in advance" depends on BT_HCI_VS_EXT && !BT_CTLR - default y if BOARD_QEMU_X86 || BOARD_QEMU_CORTEX_M3 + default y if BOARD_QEMU_X86 || BOARD_QEMU_CORTEX_M3 || BOARD_NATIVE_POSIX help Use some heuristics to try to guess in advance whether the controller supports the HCI vendor extensions in advance, in order to prevent diff --git a/subsys/bluetooth/host/Kconfig b/subsys/bluetooth/host/Kconfig index 0ee47333491..55561a5ebbd 100644 --- a/subsys/bluetooth/host/Kconfig +++ b/subsys/bluetooth/host/Kconfig @@ -57,6 +57,7 @@ config BT_HCI_TX_STACK_SIZE default 256 if BT_H5 default 416 if BT_SPI default 640 if BT_CTLR + default 256 if BT_USERCHAN config BT_HCI_TX_PRIO # Hidden option for Co-Operative Tx thread priority @@ -82,6 +83,7 @@ config BT_HCI_RESERVE default 0 if BT_H4 default 1 if BT_H5 default 1 if BT_SPI + default 1 if BT_USERCHAN config BT_RECV_IS_RX_THREAD # Hidden option set by the HCI driver to indicate that there's @@ -212,7 +214,7 @@ config BT_CONN_TX_MAX config BT_ATT_ENFORCE_FLOW bool "Enforce strict flow control semantics for incoming PDUs" default y - default n if BOARD_QEMU_CORTEX_M3 || BOARD_QEMU_X86 + default n if BOARD_QEMU_CORTEX_M3 || BOARD_QEMU_X86 || BOARD_NATIVE_POSIX help Enforce flow control rules on incoming PDUs, preventing a peer from sending new requests until a previous one has been responded