nuttx/drivers/wireless/bluetooth/bt_uart_shim.c
Dave Marples 05bbbec3e1 Add support for the BCM43438A1 Bluetooth capability. It also adds a serial 'shim' to allow any regular serial port that can support a Bluetooth H4 interface (i.e. it has RTS/CTS) to be used to drive a Bluetooth device (Get a handle to it via hci_uart_getdevice("/dev/xxx") and then pass it to the btuart_register function.
Most of the bluetooth and wifi chips appear to need external firmware, and the 43438 is no exception. Fortunately, since Cypress got involved, these are much more straightforward to obtain and are shipped as part of their SDK, which is downloadable from their website.  Those firmwares are already provided as C arrays, so their names just need updating to;

const unsigned char bt_firmware_hcd -> The bt firmware array.

const int bt_firmware_len = sizeof(bt_firmware_hcd);
2019-09-21 07:16:37 -06:00

490 lines
15 KiB
C

/****************************************************************************
* drivers/wireless/bluetooth/bt_uart_shim.c
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Dave Marples <dave@marples.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <semaphore.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <nuttx/arch.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/semaphore.h>
#include <nuttx/serial/tioctl.h>
#include <nuttx/wireless/bluetooth/bt_uart.h>
#include <termios.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define HCIUART_DEFAULT_SPEED 115200
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure is the variable state of the binding to the UART */
struct hciuart_state_s
{
/* Registered Rx callback */
btuart_rxcallback_t callback; /* Rx callback function */
void *arg; /* Rx callback argument */
int h; /* File handle to serial device */
struct file f; /* File structure, detached */
sem_t dready; /* Semaphore used by the poll operation */
bool enabled; /* Flag indicating that reception is enabled */
int serialmontask; /* The receive serial octets task handle */
volatile struct pollfd p; /* Polling structure for serial monitor task */
};
struct hciuart_config_s
{
/* Setup the interface from the upper to the lower */
struct btuart_lowerhalf_s lower; /* Generic UART lower half */
struct hciuart_state_s state; /* Variable state */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* UART Lower-Half Methods */
static void hciuart_rxattach(const struct btuart_lowerhalf_s *lower,
btuart_rxcallback_t callback, void *arg);
static void hciuart_rxenable(const struct btuart_lowerhalf_s *lower,
bool enable);
static int hciuart_setbaud(const struct btuart_lowerhalf_s *lower,
uint32_t baud);
static ssize_t hciuart_read(const struct btuart_lowerhalf_s *lower,
void *buffer, size_t buflen);
static ssize_t hciuart_write(const struct btuart_lowerhalf_s *lower,
const void *buffer, size_t buflen);
static ssize_t hciuart_rxdrain(const struct btuart_lowerhalf_s *lower);
/* This structure is the configuration of the HCI UART shim */
static struct btuart_lowerhalf_s lowerstatic =
{
.rxattach = hciuart_rxattach,
.rxenable = hciuart_rxenable,
.setbaud = hciuart_setbaud,
.read = hciuart_read,
.write = hciuart_write,
.rxdrain = hciuart_rxdrain
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct hciuart_config_s *g_n; /* This is held global because its
* inconvenient to pass to the task */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: hciuart_rxattach
*
* Description:
* Attach/detach the upper half Rx callback.
*
* rxattach() allows the upper half logic to attach a callback function
* that will be used to inform the upper half that an Rx frame is
* available. This callback will, most likely, be invoked in the
* context of an interrupt callback. The receive() method should then
* be invoked in order to receive the obtain the Rx frame data.
*
****************************************************************************/
static void
hciuart_rxattach(const struct btuart_lowerhalf_s *lower,
btuart_rxcallback_t callback, void *arg)
{
struct hciuart_config_s *config = (struct hciuart_config_s *)lower;
struct hciuart_state_s *state;
irqstate_t flags;
state = &config->state;
/* If the callback is NULL, then we are detaching */
flags = spin_lock_irqsave();
if (callback == NULL)
{
/* Disable Rx callbacks and detach the Rx callback */
state->callback = NULL;
state->arg = NULL;
}
/* Otherwise, we are attaching */
else
{
state->callback = NULL;
state->arg = arg;
state->callback = callback;
}
hciuart_setbaud(lower, HCIUART_DEFAULT_SPEED);
spin_unlock_irqrestore(flags);
}
/****************************************************************************
* Name: hciuart_rxenable
*
* Description:
* Enable/disable RX callbacks from the HCI UART.
*
* hciuart_rxenable() may be used to enable or disable callback events.
* This probably translates to enabling and disabled Rx interrupts at
* the UART. NOTE: Rx event notification should be done sparingly:
* Rx data overrun may occur when Rx events are disabled!
*
****************************************************************************/
static void hciuart_rxenable(const struct btuart_lowerhalf_s *lower,
bool enable)
{
struct hciuart_config_s *config = (struct hciuart_config_s *)lower;
struct hciuart_state_s *s = &config->state;
irqstate_t flags = spin_lock_irqsave();
if ((enable) && (!s->enabled))
{
wlinfo("Enable\n");
s->enabled = true;
}
else
{
s->enabled = false;
wlinfo("Disable\n");
}
spin_unlock_irqrestore(flags);
}
/****************************************************************************
* Name: hciuart_setbaud
*
* Description:
* The HCI UART comes up with some initial BAUD rate. Some support
* auto-BAUD detection, some support writing a configuration file to
* select the initial BAUD. The simplest strategy, however, is simply
* to use the HCI UART's default initial BAUD to perform the basic
* bring up, then send a vendor-specific command to increase the HCI
* UARTs BAUD. This method then may be used to adjust the lower half
* driver to the new HCI UART BAUD.
*
****************************************************************************/
static int
hciuart_setbaud(const struct btuart_lowerhalf_s *lower, uint32_t baud)
{
struct hciuart_config_s *config = (struct hciuart_config_s *)lower;
struct hciuart_state_s *state = &config->state;
int ret;
struct termios tio;
#ifndef CONFIG_SERIAL_TERMIOS
# error TERMIOS Support needed for hciuart_setbaud
#endif
ret = file_ioctl(&state->f, TCGETS, (long unsigned int)&tio);
if (ret)
{
wlerr("hciuart_setbaud: ERROR during TCGETS\n");
return ret;
}
if (baud != 0)
{
cfsetspeed(&tio, baud);
}
/* To be a H4 interface, CTS/RTS are needed */
tio.c_cflag |= CRTS_IFLOW | CCTS_OFLOW;
ret = file_ioctl(&state->f, TCSETS, (long unsigned int)&tio);
if (ret)
{
wlerr("hciuart_setbaud: ERROR during TCSETS, does UART support CTS/RTS?\n");
return ret;
}
return OK;
}
/****************************************************************************
* Name: hciuart_read
*
* Description:
* Read UART data.
*
* hciuart_read() after receipt of a callback notifying the upper half of
* the availability of Rx frame, the upper half may call the receive()
* method in order to obtain the buffered Rx frame data.
*
****************************************************************************/
static ssize_t
hciuart_read(const struct btuart_lowerhalf_s *lower,
void *buffer, size_t buflen)
{
struct hciuart_config_s *config = (struct hciuart_config_s *)lower;
struct hciuart_state_s *state = &config->state;
size_t ntotal;
wlinfo("config %p buffer %p buflen %lu\n", config, buffer, (size_t) buflen);
/* NOTE: This assumes that the caller has exclusive access to the Rx buffer,
* i.e., one lower half instance can server only one upper half!
*/
ntotal = file_read(&state->f, buffer, buflen);
return ntotal;
}
/****************************************************************************
* Name: hciuart_write
*
* Description:
* Write UART data.
*
* hciuart_write() will add the outgoing frame to the Tx buffer and will
* return immediately. This function may block only in the event that
* there is insufficient buffer space to hold the Tx frame data. In that
* case the lower half will block until there is sufficient to buffer
* the entire outgoing packet.
*
****************************************************************************/
static ssize_t
hciuart_write(const struct btuart_lowerhalf_s *lower,
const void *buffer, size_t buflen)
{
const struct hciuart_config_s *config
= (const struct hciuart_config_s *)lower;
struct hciuart_state_s *state = &config->state;
wlinfo("config %p buffer %p buflen %lu\n", config, buffer, (size_t) buflen);
buflen = file_write(&state->f, buffer, buflen);
return buflen;
}
/****************************************************************************
* Name: hciuart_rxdrain
*
* Description:
* Flush/drain all buffered RX data
*
****************************************************************************/
static ssize_t hciuart_rxdrain(const struct btuart_lowerhalf_s *lower)
{
struct hciuart_config_s *config = (struct hciuart_config_s *)lower;
struct hciuart_state_s *s = &config->state;
file_ioctl(&s->f, TCDRN, 0);
return 0;
}
/****************************************************************************
* Name: hcicollecttask
*
* Description:
* Loop and alert when serial data arrive
*
****************************************************************************/
static int hcicollecttask(int argc, FAR char **argv)
{
struct hciuart_state_s *s = &g_n->state;
file_poll(&s->f, &s->p, true);
for (; ; )
{
/* Wait for data to arrive */
int ret = nxsem_wait(s->p.sem);
if (ret < 0)
{
wlwarn("Poll interrupted %d\n", ret);
continue;
}
/* These flags can change dynamically as new events occur, so snapshot */
irqstate_t flags = enter_critical_section();
uint32_t tevents = s->p.revents;
s->p.revents = 0;
leave_critical_section(flags);
wlinfo("Poll completed %d\n", tevents);
/* Given the nature of file_poll, there are multiple reasons why
* we might be here, so make sure we only consider the read.
*/
if (tevents & POLLIN)
{
if (!s->enabled)
{
/* We aren't expected to be listening, so drop these data */
wlwarn("Dropping data\n");
hciuart_rxdrain(&g_n->lower);
}
else
{
if (s->callback != NULL)
{
wlinfo("Activating callback\n");
s->callback(&g_n->lower, s->arg);
}
else
{
wlwarn("Dropping data (no CB)\n");
hciuart_rxdrain(&g_n->lower);
}
}
}
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: hci_uart_getdevice
*
* Description:
* Get a pointer to the device that will be used to communicate with the
* regular serial port on the HCI.
*
* Input Paramters:
* Entry in filesystem hirearchy for device
*
* Returned Value:
* Pointer to device interface
*
****************************************************************************/
void *hci_uart_getdevice(char *path)
{
struct hciuart_state_s *s;
int f2;
/* Get the memory for this shim instance */
g_n = (struct hciuart_config_s *)kmm_zalloc(sizeof(struct hciuart_config_s));
if (!g_n)
{
return 0;
}
s = &g_n->state;
f2 = open(path, O_RDWR | O_BINARY);
if (f2 < 0)
{
kmm_free(g_n);
g_n = 0;
return 0;
}
/* Detach the file and give it somewhere to be kept */
s->h = file_detach(f2, &s->f);
/* Hook the routines in */
memcpy(&g_n->lower, &lowerstatic, sizeof(struct btuart_lowerhalf_s));
/* Put materials into poll structure */
nxsem_init(&s->dready, 0, 0);
nxsem_setprotocol(&s->dready, SEM_PRIO_NONE);
s->p.fd = s->h;
s->p.events = POLLIN;
s->p.sem = &s->dready;
s->enabled = true;
s->serialmontask = kthread_create("HCICollect",
CONFIG_BLUETOOTH_TXCONN_PRIORITY,
1024, hcicollecttask, NULL);
return g_n;
}