676 lines
18 KiB
C
676 lines
18 KiB
C
/****************************************************************************
|
|
* drivers/wireless/bluetooth/bt_bridge.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <debug.h>
|
|
#include <string.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/atomic.h>
|
|
#include <nuttx/net/snoop.h>
|
|
|
|
#include <nuttx/wireless/bluetooth/bt_bridge.h>
|
|
#include <nuttx/wireless/bluetooth/bt_driver.h>
|
|
#include <nuttx/wireless/bluetooth/bt_ioctl.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define BT_FILTER_CONN_COUNT 16
|
|
#define BT_FILTER_OPCODE_COUNT 16
|
|
|
|
#define BT_FILTER_TYPE_BT 0
|
|
#define BT_FILTER_TYPE_BLE 1
|
|
#define BT_FILTER_TYPE_COUNT 2
|
|
|
|
#define BT_FILTER_CMD_RESET 0
|
|
#define BT_FILTER_CMD_COUNT 1
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct bt_filter_s
|
|
{
|
|
int type;
|
|
uint16_t opcode[BT_FILTER_OPCODE_COUNT];
|
|
uint16_t handle[BT_FILTER_CONN_COUNT];
|
|
};
|
|
|
|
struct bt_bridge_device_s
|
|
{
|
|
struct bt_driver_s driver;
|
|
struct bt_filter_s filter;
|
|
FAR struct bt_bridge_s *bridge;
|
|
};
|
|
|
|
struct bt_bridge_s
|
|
{
|
|
struct bt_bridge_device_s device[BT_FILTER_TYPE_COUNT];
|
|
FAR struct bt_driver_s *driver;
|
|
#ifdef CONFIG_BLUETOOTH_BRIDGE_BTSNOOP
|
|
FAR struct snoop_s *snoop;
|
|
#endif /* CONFIG_BLUETOOTH_BRIDGE_BTSNOOP */
|
|
atomic_uint refs;
|
|
bool dispatched[BT_FILTER_CMD_COUNT];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const uint8_t g_bt_filter_bt_evt_table[] =
|
|
{
|
|
BT_HCI_EVT_INQUIRY_COMPLETE,
|
|
BT_HCI_EVT_CONN_COMPLETE,
|
|
BT_HCI_EVT_CONN_REQUEST,
|
|
BT_HCI_EVT_AUTH_COMPLETE,
|
|
BT_HCI_EVT_REMOTE_NAME_REQ_COMPLETE,
|
|
BT_HCI_EVT_REMOTE_FEATURES,
|
|
BT_HCI_EVT_ROLE_CHANGE,
|
|
BT_HCI_EVT_PIN_CODE_REQ,
|
|
BT_HCI_EVT_LINK_KEY_NOTIFY,
|
|
BT_HCI_EVT_LINK_KEY_REQ,
|
|
BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI,
|
|
BT_HCI_EVT_REMOTE_EXT_FEATURES,
|
|
BT_HCI_EVT_SYNC_CONN_COMPLETE,
|
|
BT_HCI_EVT_EXTENDED_INQUIRY_RESULT,
|
|
BT_HCI_EVT_IO_CAPA_RESP,
|
|
BT_HCI_EVT_IO_CAPA_REQ,
|
|
BT_HCI_EVT_SSP_COMPLETE,
|
|
BT_HCI_EVT_USER_CONFIRM_REQ,
|
|
BT_HCI_EVT_USER_PASSKEY_REQ,
|
|
BT_HCI_EVT_USER_PASSKEY_NOTIFY,
|
|
BT_HCI_EVT_PAGE_SCAN_REP_MODE_CHNG,
|
|
BT_HCI_EVT_CONN_PKT_TYPE_CHANGE,
|
|
BT_HCI_EVT_READ_CLOCK_OFF_COMP,
|
|
BT_HCI_EVT_MAX_SLOTS_CHANGED,
|
|
BT_HCI_EVT_MODE_CHANGE,
|
|
BT_HCI_EVT_QOS_SETUP_COMP,
|
|
BT_HCI_EVT_LINK_SUPER_TOUT_CHANGED,
|
|
};
|
|
|
|
static const uint8_t g_bt_filter_ble_evt_table[] =
|
|
{
|
|
BT_HCI_EVT_LE_META_EVENT,
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static bool bt_filter_set(FAR uint16_t *array, int size, uint16_t old,
|
|
uint16_t new)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
if (array[i] == old)
|
|
{
|
|
array[i] = new;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool bt_filter_ogf_is_valid(uint8_t ogf)
|
|
{
|
|
switch (ogf)
|
|
{
|
|
case BT_OGF_BASEBAND:
|
|
case BT_OGF_LINK_CTRL:
|
|
case BT_OGF_INFO:
|
|
case BT_OGF_LE:
|
|
case BT_OGF_VS_RTK:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool bt_filter_set_handle(FAR uint16_t *handle, int size,
|
|
uint16_t old, uint16_t new)
|
|
{
|
|
return bt_filter_set(handle, size, old, new);
|
|
}
|
|
|
|
static bool bt_filter_alloc_handle(FAR struct bt_filter_s *filter,
|
|
uint16_t handle)
|
|
{
|
|
return bt_filter_set_handle(filter->handle, BT_FILTER_CONN_COUNT,
|
|
0, handle);
|
|
}
|
|
|
|
static bool bt_filter_free_handle(FAR struct bt_filter_s *filter,
|
|
uint16_t handle)
|
|
{
|
|
return bt_filter_set_handle(filter->handle, BT_FILTER_CONN_COUNT,
|
|
handle, 0);
|
|
}
|
|
|
|
static bool bt_filter_has_handle(FAR struct bt_filter_s *filter,
|
|
uint16_t handle)
|
|
{
|
|
return bt_filter_set_handle(filter->handle, BT_FILTER_CONN_COUNT,
|
|
handle, handle);
|
|
}
|
|
|
|
static bool bt_filter_set_opcode(FAR uint16_t *opcode, int size,
|
|
uint16_t old, uint16_t new)
|
|
{
|
|
return bt_filter_set(opcode, size, old, new);
|
|
}
|
|
|
|
static bool bt_filter_alloc_opcode(FAR struct bt_filter_s *filter,
|
|
uint16_t opcode)
|
|
{
|
|
return bt_filter_set_opcode(filter->opcode, BT_FILTER_OPCODE_COUNT,
|
|
0, opcode);
|
|
}
|
|
|
|
static bool bt_filter_free_opcode(FAR struct bt_filter_s *filter,
|
|
uint16_t opcode)
|
|
{
|
|
return bt_filter_set_opcode(filter->opcode, BT_FILTER_OPCODE_COUNT,
|
|
opcode, 0);
|
|
}
|
|
|
|
static bool bt_filter_can_recv(FAR struct bt_filter_s *filter,
|
|
enum bt_buf_type_e type,
|
|
FAR const uint8_t *buffer, size_t buflen)
|
|
{
|
|
FAR const uint8_t *evt_table;
|
|
int size;
|
|
int i;
|
|
|
|
if (type == BT_EVT)
|
|
{
|
|
if (filter->type == BT_FILTER_TYPE_BLE)
|
|
{
|
|
evt_table = g_bt_filter_bt_evt_table;
|
|
size = sizeof(g_bt_filter_bt_evt_table);
|
|
}
|
|
else
|
|
{
|
|
evt_table = g_bt_filter_ble_evt_table;
|
|
size = sizeof(g_bt_filter_ble_evt_table);
|
|
}
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
if (buffer[0] == evt_table[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
switch (buffer[0])
|
|
{
|
|
case BT_HCI_EVT_LE_META_EVENT:
|
|
if (buffer[2] == BT_HCI_EVT_LE_CONN_COMPLETE)
|
|
{
|
|
FAR struct bt_hci_evt_le_conn_complete_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[3];
|
|
if (evt->status == 0)
|
|
{
|
|
return bt_filter_alloc_handle(filter, evt->handle);
|
|
}
|
|
}
|
|
else if (buffer[2] == BT_HCI_EVT_LE_ENH_CONN_COMPLETE)
|
|
{
|
|
FAR struct bt_hci_evt_le_enh_conn_complete_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[3];
|
|
if (evt->status == 0)
|
|
{
|
|
return bt_filter_alloc_handle(filter, evt->handle);
|
|
}
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_CONN_COMPLETE:
|
|
{
|
|
FAR struct bt_hci_evt_conn_complete_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
if (evt->status == 0)
|
|
{
|
|
return bt_filter_alloc_handle(filter, evt->handle);
|
|
}
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
|
|
{
|
|
FAR struct bt_hci_evt_num_completed_packets_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
return bt_filter_has_handle(filter, evt->h[0].handle);
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_REMOTE_VERSION_INFO:
|
|
{
|
|
FAR struct bt_hci_evt_remote_version_info_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
return bt_filter_has_handle(filter, evt->handle);
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_ENCRYPT_CHANGE:
|
|
{
|
|
FAR struct bt_hci_evt_encrypt_change_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
return bt_filter_has_handle(filter, evt->handle);
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE:
|
|
{
|
|
FAR struct bt_hci_evt_encrypt_key_refresh_complete_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
return bt_filter_has_handle(filter, evt->handle);
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_DISCONN_COMPLETE:
|
|
{
|
|
FAR struct bt_hci_evt_disconn_complete_s *evt;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
return bt_filter_free_handle(filter, evt->handle);
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_CMD_COMPLETE:
|
|
{
|
|
FAR struct hci_evt_cmd_complete_s *evt;
|
|
uint8_t ogf;
|
|
|
|
evt = (FAR void *)&buffer[2];
|
|
ogf = evt->opcode >> 10;
|
|
|
|
if (bt_filter_ogf_is_valid(ogf))
|
|
{
|
|
return bt_filter_free_opcode(filter, evt->opcode);
|
|
}
|
|
else if (BT_OGF_LINK_POLICY == ogf || BT_OGF_STATUS == ogf)
|
|
{
|
|
if (filter->type == BT_FILTER_TYPE_BLE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case BT_HCI_EVT_CMD_STATUS:
|
|
{
|
|
FAR struct bt_hci_evt_cmd_status_s *stat;
|
|
uint8_t ogf;
|
|
|
|
stat = (FAR void *)&buffer[2];
|
|
ogf = stat->opcode >> 10;
|
|
|
|
if (bt_filter_ogf_is_valid(ogf))
|
|
{
|
|
return bt_filter_free_opcode(filter, stat->opcode);
|
|
}
|
|
|
|
if (BT_OGF_LINK_POLICY == ogf || BT_OGF_STATUS == ogf)
|
|
{
|
|
if (filter->type == BT_FILTER_TYPE_BLE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if (type == BT_ACL_IN)
|
|
{
|
|
FAR struct bt_hci_acl_hdr_s *acl = (FAR void *)&buffer[0];
|
|
|
|
/* When in HCI ACL Data packets, connection handle is
|
|
* the first 3 octets of the packet
|
|
*/
|
|
|
|
return bt_filter_has_handle(filter, acl->handle & 0x0fff);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool bt_filter_can_send(FAR struct bt_filter_s *filter,
|
|
enum bt_buf_type_e type,
|
|
FAR uint8_t *buffer, size_t buflen)
|
|
{
|
|
uint16_t opcode;
|
|
uint8_t ogf;
|
|
int i;
|
|
|
|
if (type == BT_CMD)
|
|
{
|
|
opcode = (uint16_t)buffer[1] << 8 | (uint16_t)buffer[0];
|
|
ogf = buffer[1] >> 2;
|
|
|
|
switch (opcode)
|
|
{
|
|
case BT_HCI_OP_SET_EVENT_MASK:
|
|
{
|
|
/* Override the all event bits roughly to avoid the
|
|
* bt/ble host specific mask deliver to controller.
|
|
*/
|
|
|
|
memset(&buffer[3], 0xff, 7);
|
|
buffer[10] = 0x3f;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bt_filter_ogf_is_valid(ogf))
|
|
{
|
|
if (!bt_filter_alloc_opcode(filter, opcode))
|
|
{
|
|
nerr("Unable to set opcode 0x%04x.\n", opcode);
|
|
|
|
for (i = 0; i < BT_FILTER_OPCODE_COUNT; i++)
|
|
{
|
|
nerr("PENDING opcode: %04x.\n", filter->opcode[i]);
|
|
}
|
|
|
|
memset(filter->opcode, 0, sizeof(filter->opcode));
|
|
|
|
bt_filter_alloc_opcode(filter, opcode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int bt_bridge_open(FAR struct bt_driver_s *drv)
|
|
{
|
|
FAR struct bt_bridge_device_s *device =
|
|
(FAR struct bt_bridge_device_s *)drv;
|
|
FAR struct bt_bridge_s *bridge = device->bridge;
|
|
FAR struct bt_driver_s *driver = bridge->driver;
|
|
|
|
if (atomic_fetch_add(&bridge->refs, 1) == 0)
|
|
{
|
|
int ret = driver->open(driver);
|
|
if (ret < 0)
|
|
{
|
|
atomic_fetch_sub(&bridge->refs, 1);
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int bt_bridge_send_reset_response(FAR struct bt_driver_s *driver)
|
|
{
|
|
uint8_t reset_response[6];
|
|
|
|
reset_response[0] = BT_HCI_EVT_CMD_COMPLETE;
|
|
reset_response[1] = 0x04;
|
|
reset_response[2] = 0x01;
|
|
reset_response[3] = BT_HCI_OP_RESET & 0xff;
|
|
reset_response[4] = (BT_HCI_OP_RESET >> 8) & 0xff;
|
|
reset_response[5] = BT_HCI_SUCCESS;
|
|
|
|
return bt_netdev_receive(driver, BT_EVT, reset_response,
|
|
sizeof(reset_response));
|
|
}
|
|
|
|
static bool bt_bridge_filter_command(FAR struct bt_bridge_s *bridge,
|
|
FAR struct bt_driver_s *driver,
|
|
FAR uint8_t *data)
|
|
{
|
|
uint16_t opcode = BT_LE162HOST(((uint16_t *)data)[0]);
|
|
|
|
switch (opcode)
|
|
{
|
|
case BT_HCI_OP_RESET:
|
|
if (bridge->dispatched[BT_FILTER_CMD_RESET])
|
|
{
|
|
bt_bridge_send_reset_response(driver);
|
|
return true;
|
|
}
|
|
|
|
bridge->dispatched[BT_FILTER_CMD_RESET] = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int bt_bridge_send(FAR struct bt_driver_s *drv,
|
|
enum bt_buf_type_e type,
|
|
FAR void *data, size_t len)
|
|
{
|
|
FAR struct bt_bridge_device_s *device =
|
|
(FAR struct bt_bridge_device_s *)drv;
|
|
FAR struct bt_bridge_s *bridge = device->bridge;
|
|
FAR struct bt_driver_s *driver = bridge->driver;
|
|
irqstate_t flags;
|
|
|
|
flags = enter_critical_section();
|
|
|
|
if (bt_bridge_filter_command(bridge, drv, data))
|
|
{
|
|
leave_critical_section(flags);
|
|
return len;
|
|
}
|
|
|
|
if (bt_filter_can_send(&device->filter, type, data, len))
|
|
{
|
|
leave_critical_section(flags);
|
|
#ifdef CONFIG_BLUETOOTH_BRIDGE_BTSNOOP
|
|
snoop_dump(bridge->snoop, data - drv->head_reserve,
|
|
len + drv->head_reserve, 0,
|
|
SNOOP_DIRECTION_FLAG_SENT);
|
|
#endif /* CONFIG_BLUETOOTH_BRIDGE_BTSNOOP */
|
|
|
|
return driver->send(driver, type, data, len);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return 0;
|
|
}
|
|
|
|
static void bt_bridge_close(FAR struct bt_driver_s *drv)
|
|
{
|
|
FAR struct bt_bridge_device_s *device =
|
|
(FAR struct bt_bridge_device_s *)drv;
|
|
FAR struct bt_bridge_s *bridge = device->bridge;
|
|
FAR struct bt_driver_s *driver = bridge->driver;
|
|
|
|
if (atomic_fetch_sub(&bridge->refs, 1) == 1)
|
|
{
|
|
driver->close(driver);
|
|
};
|
|
}
|
|
|
|
static int bt_bridge_receive(FAR struct bt_driver_s *drv,
|
|
enum bt_buf_type_e type,
|
|
FAR void *data, size_t len)
|
|
{
|
|
FAR struct bt_bridge_s *bridge = (FAR struct bt_bridge_s *)drv->priv;
|
|
FAR struct bt_bridge_device_s *device;
|
|
FAR struct bt_driver_s *driver;
|
|
irqstate_t flags;
|
|
int i;
|
|
|
|
flags = enter_critical_section();
|
|
for (i = 0; i < BT_FILTER_TYPE_COUNT; i++)
|
|
{
|
|
device = &bridge->device[i];
|
|
driver = &device->driver;
|
|
if (bt_filter_can_recv(&device->filter, type, data, len))
|
|
{
|
|
int ret;
|
|
|
|
leave_critical_section(flags);
|
|
ret = bt_netdev_receive(driver, type, data, len);
|
|
|
|
#ifdef CONFIG_BLUETOOTH_BRIDGE_BTSNOOP
|
|
snoop_dump(bridge->snoop, data - drv->head_reserve,
|
|
len + drv->head_reserve, 0,
|
|
SNOOP_DIRECTION_FLAG_RECV);
|
|
#endif /* CONFIG_BLUETOOTH_BRIDGE_BTSNOOP */
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return 0;
|
|
}
|
|
|
|
static int bt_bridge_ioctl(FAR struct bt_driver_s *drv, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
FAR struct bt_bridge_device_s *device =
|
|
(FAR struct bt_bridge_device_s *)drv;
|
|
FAR struct bt_bridge_s *bridge = device->bridge;
|
|
FAR struct bt_driver_s *driver = bridge->driver;
|
|
int ret;
|
|
|
|
switch (cmd)
|
|
{
|
|
#ifdef CONFIG_BLUETOOTH_BRIDGE_BTSNOOP
|
|
case SIOCBTSNOOPOPEN:
|
|
{
|
|
FAR const char *filename = (FAR const char *)((uintptr_t)arg);
|
|
FAR struct snoop_s *snoop = (FAR struct snoop_s *)
|
|
kmm_zalloc(sizeof(struct snoop_s));
|
|
if (!snoop)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = snoop_open(snoop, filename, SNOOP_DATALINK_HCI_UART, true);
|
|
if (ret == OK)
|
|
{
|
|
bridge->snoop = snoop;
|
|
}
|
|
else
|
|
{
|
|
kmm_free(snoop);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SIOCBTSNOOPCLOSE:
|
|
{
|
|
FAR struct snoop_s *snoop = bridge->snoop;
|
|
|
|
bridge->snoop = NULL;
|
|
ret = snoop_close(snoop);
|
|
kmm_free(snoop);
|
|
break;
|
|
}
|
|
#endif /* CONFIG_BLUETOOTH_BRIDGE_BTSNOOP */
|
|
|
|
/* hci ioctl operation */
|
|
|
|
default:
|
|
{
|
|
if (driver->ioctl)
|
|
{
|
|
ret = driver->ioctl(driver, cmd, arg);
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOTTY;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bt_device_init(FAR struct bt_bridge_s *bridge,
|
|
FAR struct bt_driver_s **drv, uint8_t type)
|
|
{
|
|
FAR struct bt_bridge_device_s *device = &bridge->device[type];
|
|
FAR struct bt_driver_s *driver = &device->driver;
|
|
|
|
device->bridge = bridge;
|
|
device->filter.type = type;
|
|
|
|
*drv = &device->driver;
|
|
|
|
driver->head_reserve = bridge->driver->head_reserve;
|
|
driver->open = bt_bridge_open;
|
|
driver->send = bt_bridge_send;
|
|
driver->close = bt_bridge_close;
|
|
driver->ioctl = bt_bridge_ioctl;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: bt_bridge_register
|
|
*
|
|
* Description:
|
|
* Register the Bluetooth BT/BLE dual mode bridge driver
|
|
*
|
|
****************************************************************************/
|
|
|
|
int bt_bridge_register(FAR struct bt_driver_s *hcidrv,
|
|
FAR struct bt_driver_s **btdrv,
|
|
FAR struct bt_driver_s **bledrv)
|
|
{
|
|
FAR struct bt_bridge_s *bridge;
|
|
|
|
if (!hcidrv || !btdrv || !bledrv)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
bridge = kmm_zalloc(sizeof(struct bt_bridge_s));
|
|
if (!bridge)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bridge->driver = hcidrv;
|
|
|
|
hcidrv->receive = bt_bridge_receive;
|
|
hcidrv->priv = bridge;
|
|
|
|
bt_device_init(bridge, btdrv, BT_FILTER_TYPE_BT);
|
|
bt_device_init(bridge, bledrv, BT_FILTER_TYPE_BLE);
|
|
|
|
return OK;
|
|
}
|