Bluetooth: Add HCI User Channel driver for native POSIX port
Introduce a custom HCI driver for the native POSIX port, which opens a HCI User Channel socket to the Linux kernel to gain access to a local Bluetooth controller. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
parent
b173e4353f
commit
7d9896575b
9 changed files with 280 additions and 2 deletions
|
@ -9,4 +9,8 @@
|
|||
|
||||
#include <soc.h>
|
||||
|
||||
#if defined(CONFIG_BT_USERCHAN)
|
||||
extern int bt_dev_index;
|
||||
#endif
|
||||
|
||||
#endif /* __INC_BOARD_H */
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
*************
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
218
drivers/bluetooth/hci/userchan.c
Normal file
218
drivers/bluetooth/hci/userchan.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
/* userchan.c - HCI User Channel based Bluetooth driver */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <device.h>
|
||||
#include <init.h>
|
||||
#include <board.h>
|
||||
#include <misc/util.h>
|
||||
#include <misc/byteorder.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_driver.h>
|
||||
|
||||
#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);
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue