nuttx/wireless/bluetooth/bt_hcicore.c
Alan Carvalho de Assis 5ec51ae6d5 wireless/bluetooth: Validate lengths when parsing advertising reports.
le_adv_report() took the report count and each report's data length from
the event and used them without checking either against the data that was
actually received:

  - the declared data length indexes the RSSI octet, so a length larger
    than the event reads past the end of the buffer;
  - the loop was bounded only by the report count, so a count larger than
    the payload walks off the end of it;
  - bt_buf_consume() only checks its bound with DEBUGASSERT(), so on a
    build without assertions the buffer length underflows rather than
    reporting the problem.

Check that the event is long enough for the count, then check each report
against the remaining length before reading its data or its RSSI, and
stop parsing when a report does not fit.

While here, include the RSSI octet when advancing to the next report.
sizeof() of the report structure does not account for it, because the
data member is a zero-length array, so every report after the first
started one octet early.

Ref: Core v6.0, Vol 4, Part E, 7.7.65.2 (LE Advertising Report event)
Testing: builds for sim:bluetooth with Make; every commit in this series
verified to build individually.  Not yet exercised at runtime - the
scriptable controller that can inject a malformed report is added
separately.

Signed-off-by: Alan C. Assis <acassis@gmail.com>
Assisted-by: Claude Code Opus 5
2026-09-19 18:30:43 -03:00

2418 lines
59 KiB
C

/****************************************************************************
* wireless/bluetooth/bt_hcicore.c
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2016, Intel Corporation
* All rights reserved.
*
* 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 of the copyright holder 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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <sys/param.h>
#include <nuttx/clock.h>
#include <nuttx/kthread.h>
#include <nuttx/spinlock.h>
#include <nuttx/wqueue.h>
#include <nuttx/net/bluetooth.h>
#include <nuttx/wireless/bluetooth/bt_core.h>
#include <nuttx/wireless/bluetooth/bt_hci.h>
#include "bt_queue.h"
#include "bt_buf.h"
#include "bt_keys.h"
#include "bt_conn.h"
#include "bt_l2cap.h"
#include "bt_hcicore.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Wait up to 2.5 seconds for a response. This delay is arbitrary and
* intended only to avoid hangs while waiting for a response. It may need
* to be adjusted.
*/
#define TIMEOUT_MSEC 2500
/****************************************************************************
* Public Data
****************************************************************************/
/* State of connected HCI device.
*
* NOTE: Because this is a global singleton, multiple HCI devices may not
* be supported.
*/
struct bt_dev_s g_btdev;
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
static FAR struct bt_conn_cb_s *g_callback_list;
static bt_le_scan_cb_t *g_scan_dev_found_cb;
#else
static struct bt_hci_cb_s *g_hci_cb;
#endif
/* Lists of pending received messages. One for low priority input that is
* processed on the low priority work queue and one for high priority
* input that is processed on high priority work queue.
*/
static FAR struct bt_bufferlist_s g_lp_rxlist;
static FAR struct bt_bufferlist_s g_hp_rxlist;
/* Work structures: One for high priority and one for low priority work */
static struct work_s g_lp_work;
static struct work_s g_hp_work;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: bt_enqueue_bufwork
*
* Description:
* Add the provided buffer 'buf' to the head selected buffer list 'list'
*
* Input Parameters:
* list - The buffer list to use
* buf - The buffer to be added to the head of the buffer list
*
* Returned Value:
*
****************************************************************************/
static void bt_enqueue_bufwork(FAR struct bt_bufferlist_s *list,
FAR struct bt_buf_s *buf)
{
irqstate_t flags;
flags = spin_lock_irqsave(&list->lock);
buf->flink = list->head;
if (list->head == NULL)
{
list->tail = buf;
}
list->head = buf;
spin_unlock_irqrestore(&list->lock, flags);
}
/****************************************************************************
* Name: bt_dequeue_bufwork
*
* Description:
* Remove and return the buffer at the tail of the buffer list specified
* by 'list'.
*
* Input Parameters:
* list - The buffer list to use
*
* Returned Value:
* A pointer to the buffer that was at the tail of the buffer list. NULL
* is returned if the list was empty.
*
****************************************************************************/
static FAR struct bt_buf_s *
bt_dequeue_bufwork(FAR struct bt_bufferlist_s *list)
{
FAR struct bt_buf_s *buf;
irqstate_t flags;
flags = spin_lock_irqsave(&list->lock);
buf = list->tail;
if (buf != NULL)
{
if (list->head == list->tail)
{
list->head = NULL;
list->tail = NULL;
}
else
{
FAR struct bt_buf_s *prev;
for (prev = list->head;
prev && prev->flink != buf;
prev = prev->flink)
{
}
if (prev != NULL)
{
prev->flink = NULL;
list->tail = prev;
}
}
buf->flink = NULL;
}
spin_unlock_irqrestore(&list->lock, flags);
return buf;
}
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
static void bt_connected(FAR struct bt_conn_s *conn)
{
FAR struct bt_conn_cb_s *cb;
for (cb = g_callback_list; cb; cb = cb->flink)
{
if (cb->connected)
{
cb->connected(conn, cb->context);
}
}
}
static void bt_disconnected(FAR struct bt_conn_s *conn)
{
FAR struct bt_conn_cb_s *cb;
for (cb = g_callback_list; cb; cb = cb->flink)
{
if (cb->disconnected)
{
cb->disconnected(conn, cb->context);
}
}
}
static void hci_acl(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_acl_hdr_s *hdr = (FAR void *)buf->data;
FAR struct bt_conn_s *conn;
uint16_t handle;
uint16_t len = BT_LE162HOST(hdr->len);
uint8_t flags;
wlinfo("buf %p\n", buf);
handle = BT_LE162HOST(hdr->handle);
flags = (handle >> 12);
buf->u.acl.handle = bt_acl_handle(handle);
bt_buf_consume(buf, sizeof(*hdr));
wlinfo("handle %u len %u flags %u\n", buf->u.acl.handle, len, flags);
if (buf->len != len)
{
wlerr("ERROR: ACL data length mismatch (%u != %u)\n",
buf->len, len);
return;
}
conn = bt_conn_lookup_handle(buf->u.acl.handle);
if (!conn)
{
wlerr("ERROR: Unable to find conn for handle %u\n",
buf->u.acl.handle);
return;
}
bt_buf_addref(buf);
bt_conn_receive(conn, buf, flags);
bt_conn_release(conn);
}
/* HCI event processing */
static void hci_encrypt_change(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_encrypt_change_s *evt = (FAR void *)buf->data;
FAR struct bt_conn_s *conn;
uint16_t handle = BT_LE162HOST(evt->handle);
wlinfo("status %u handle %u encrypt 0x%02x\n",
evt->status, handle, evt->encrypt);
if (evt->status)
{
return;
}
conn = bt_conn_lookup_handle(handle);
if (!conn)
{
wlerr("ERROR: Unable to look up conn with handle %u\n", handle);
return;
}
conn->encrypt = evt->encrypt;
bt_l2cap_encrypt_change(conn);
bt_conn_release(conn);
}
static void hci_reset_complete(FAR struct bt_buf_s *buf)
{
uint8_t status = buf->data[0];
wlinfo("status %u\n", status);
if (status)
{
return;
}
g_scan_dev_found_cb = NULL;
g_btdev.scan_enable = BT_LE_SCAN_DISABLE;
g_btdev.scan_filter = BT_LE_SCAN_FILTER_DUP_ENABLE;
}
static void hci_cmd_done(uint16_t opcode, uint8_t status,
FAR struct bt_buf_s *buf)
{
FAR struct bt_buf_s *sent = g_btdev.sent_cmd;
if (sent == NULL)
{
return;
}
if (g_btdev.sent_cmd == NULL)
{
wlerr("ERROR: Request cmd missing!\n");
return;
}
if (g_btdev.sent_cmd->u.hci.opcode != opcode)
{
wlerr("ERROR: Unexpected completion of opcode 0x%04x "
"expected 0x%04x\n",
opcode, g_btdev.sent_cmd->u.hci.opcode);
return;
}
g_btdev.sent_cmd = NULL;
/* If the command was synchronous wake up bt_hci_cmd_send_sync() */
if (sent->u.hci.sync != NULL)
{
FAR sem_t *sem = sent->u.hci.sync;
if (status != 0)
{
wlwarn("WARNING: status %u\n", status);
sent->u.hci.sync = NULL;
}
else
{
sent->u.hci.sync = bt_buf_addref(buf);
}
nxsem_post(sem);
}
bt_buf_release(sent);
}
static void hci_cmd_complete(FAR struct bt_buf_s *buf)
{
FAR struct hci_evt_cmd_complete_s *evt = (FAR void *)buf->data;
uint16_t opcode = BT_LE162HOST(evt->opcode);
FAR uint8_t *status;
wlinfo("opcode %04x\n", opcode);
bt_buf_consume(buf, sizeof(*evt));
/* All command return parameters have a 1-byte status in the beginning, so
* we can safely make this generalization.
*/
status = buf->data;
switch (opcode)
{
case BT_HCI_OP_RESET:
hci_reset_complete(buf);
break;
default:
wlinfo("Unhandled opcode %04x\n", opcode);
break;
}
hci_cmd_done(opcode, *status, buf);
if (evt->ncmd > 0 && g_btdev.ncmd == 0)
{
/* Allow next command to be sent */
g_btdev.ncmd = 1;
nxsem_post(&g_btdev.ncmd_sem);
}
}
static void hci_cmd_status(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_cmd_status_s *evt = (FAR void *)buf->data;
uint16_t opcode = BT_LE162HOST(evt->opcode);
wlinfo("opcode %04x\n", opcode);
bt_buf_consume(buf, sizeof(*evt));
switch (opcode)
{
default:
wlinfo("Unhandled opcode %04x\n", opcode);
break;
}
hci_cmd_done(opcode, evt->status, buf);
if (evt->ncmd && !g_btdev.ncmd)
{
/* Allow next command to be sent */
g_btdev.ncmd = 1;
nxsem_post(&g_btdev.ncmd_sem);
}
}
static void hci_num_completed_packets(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_num_completed_packets_s *evt = (FAR void *)buf->data;
uint16_t num_handles = BT_LE162HOST(evt->num_handles);
uint16_t i;
wlinfo("num_handles %u\n", num_handles);
for (i = 0; i < num_handles; i++)
{
uint16_t handle;
uint16_t count;
handle = BT_LE162HOST(evt->h[i].handle);
count = BT_LE162HOST(evt->h[i].count);
wlinfo("handle %u count %u\n", handle, count);
UNUSED(handle);
while (count--)
{
nxsem_post(&g_btdev.le_pkts_sem);
}
}
}
static void hci_encrypt_key_refresh_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_encrypt_key_refresh_complete_s *evt =
(FAR void *)buf->data;
FAR struct bt_conn_s *conn;
uint16_t handle;
handle = BT_LE162HOST(evt->handle);
wlinfo("status %u handle %u\n", evt->status, handle);
if (evt->status)
{
return;
}
conn = bt_conn_lookup_handle(handle);
if (!conn)
{
wlerr("ERROR: Unable to look up conn with handle %u\n", handle);
return;
}
bt_l2cap_encrypt_change(conn);
bt_conn_release(conn);
}
static void copy_id_addr(FAR struct bt_conn_s *conn,
FAR const bt_addr_le_t *addr)
{
FAR struct bt_keys_s *keys;
/* If we have a keys struct we already know the identity */
if (conn->keys)
{
return;
}
keys = bt_keys_find_irk(addr);
if (keys)
{
bt_addr_le_copy(&conn->dst, &keys->addr);
conn->keys = keys;
}
else
{
bt_addr_le_copy(&conn->dst, addr);
}
}
static int bt_hci_start_scanning(uint8_t scan_type, uint8_t scan_filter)
{
FAR struct bt_buf_s *buf;
FAR struct bt_buf_s *rsp;
FAR struct bt_hci_cp_le_set_scan_params_s *set_param;
FAR struct bt_hci_cp_le_set_scan_enable_s *scan_enable;
int ret;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_PARAMS, sizeof(*set_param));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
set_param = bt_buf_extend(buf, sizeof(*set_param));
memset(set_param, 0, sizeof(*set_param));
set_param->scan_type = scan_type;
/* for the rest parameters apply default values according to spec 4.2,
* vol2, part E, 7.8.10
*/
set_param->interval = BT_HOST2LE16(0x0010);
set_param->window = BT_HOST2LE16(0x0010);
set_param->filter_policy = 0x00;
set_param->addr_type = 0x00;
bt_hci_cmd_send(BT_HCI_OP_LE_SET_SCAN_PARAMS, buf);
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(*scan_enable));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
scan_enable = bt_buf_extend(buf, sizeof(*scan_enable));
memset(scan_enable, 0, sizeof(*scan_enable));
scan_enable->filter_dup = scan_filter;
scan_enable->enable = BT_LE_SCAN_ENABLE;
ret = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_ENABLE, buf, &rsp);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
/* Update scan state in case of success (0) status */
ret = rsp->data[0];
if (!ret)
{
g_btdev.scan_enable = BT_LE_SCAN_ENABLE;
}
bt_buf_release(rsp);
return ret;
}
static int bt_hci_stop_scanning(void)
{
FAR struct bt_buf_s *buf;
FAR struct bt_buf_s *rsp;
FAR struct bt_hci_cp_le_set_scan_enable_s *scan_enable;
int ret;
if (g_btdev.scan_enable == BT_LE_SCAN_DISABLE)
{
wlwarn("WARNING: Scan already disabled\n");
return -EALREADY;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(*scan_enable));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
scan_enable = bt_buf_extend(buf, sizeof(*scan_enable));
memset(scan_enable, 0x0, sizeof(*scan_enable));
scan_enable->filter_dup = 0x00;
scan_enable->enable = BT_LE_SCAN_DISABLE;
ret = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_ENABLE, buf, &rsp);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
/* Update scan state in case of success (0) status */
ret = rsp->data[0];
if (!ret)
{
g_btdev.scan_enable = BT_LE_SCAN_DISABLE;
}
bt_buf_release(rsp);
return ret;
}
static int hci_le_create_conn(FAR const bt_addr_le_t *addr)
{
FAR struct bt_buf_s *buf;
FAR struct bt_hci_cp_le_create_conn_s *cp;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_CONN, sizeof(*cp));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
cp = bt_buf_extend(buf, sizeof(*cp));
memset(cp, 0x0, sizeof(*cp));
bt_addr_le_copy(&cp->peer_addr, addr);
cp->conn_interval_max = BT_HOST2LE16(0x0028);
cp->conn_interval_min = BT_HOST2LE16(0x0018);
cp->scan_interval = BT_HOST2LE16(0x0060);
cp->scan_window = BT_HOST2LE16(0x0030);
cp->supervision_timeout = BT_HOST2LE16(0x07d0);
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN, buf, NULL);
}
static void hci_disconn_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_disconn_complete_s *evt = (FAR void *)buf->data;
uint16_t handle = BT_LE162HOST(evt->handle);
FAR struct bt_conn_s *conn;
wlinfo("status %u handle %u reason %u\n",
evt->status, handle, evt->reason);
if (evt->status)
{
return;
}
conn = bt_conn_lookup_handle(handle);
if (!conn)
{
wlerr("ERROR: Unable to look up conn with handle %u\n", handle);
return;
}
bt_l2cap_disconnected(conn);
bt_disconnected(conn);
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
conn->handle = 0;
if (bt_atomic_testbit(conn->flags, BT_CONN_AUTO_CONNECT))
{
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
bt_le_scan_update();
}
bt_conn_release(conn);
if (g_btdev.adv_enable)
{
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1);
if (buf)
{
memcpy(bt_buf_extend(buf, 1), &g_btdev.adv_enable, 1);
bt_hci_cmd_send(BT_HCI_OP_LE_SET_ADV_ENABLE, buf);
}
}
}
static void le_conn_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_le_conn_complete_s *evt = (FAR void *)buf->data;
uint16_t handle = BT_LE162HOST(evt->handle);
FAR struct bt_conn_s *conn;
FAR struct bt_keys_s *keys;
wlinfo("status %u handle %u role %u %s\n", evt->status, handle,
evt->role, bt_addr_le_str(&evt->peer_addr));
/* Make lookup to check if there's a connection object in CONNECT state
* associated with passed peer LE address.
*/
keys = bt_keys_find_irk(&evt->peer_addr);
if (keys)
{
conn = bt_conn_lookup_state(&keys->addr, BT_CONN_CONNECT);
}
else
{
conn = bt_conn_lookup_state(&evt->peer_addr, BT_CONN_CONNECT);
}
if (evt->status)
{
if (!conn)
{
return;
}
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
/* Drop the reference got by lookup call in CONNECT state. We are now
* in the DISCONNECTED state since no successful LE link been made.
*/
bt_conn_release(conn);
return;
}
if (!conn)
{
conn = bt_conn_add(&evt->peer_addr, evt->role);
}
if (!conn)
{
wlerr("ERROR: Unable to add new conn for handle %u\n", handle);
return;
}
conn->handle = handle;
conn->src.type = BT_ADDR_LE_PUBLIC;
memcpy(conn->src.val, g_btdev.bdaddr.val, sizeof(g_btdev.bdaddr.val));
copy_id_addr(conn, &evt->peer_addr);
conn->le_conn_interval = BT_LE162HOST(evt->interval);
bt_conn_set_state(conn, BT_CONN_CONNECTED);
bt_l2cap_connected(conn);
bt_connected(conn);
bt_conn_release(conn);
bt_le_scan_update();
}
static void check_pending_conn(FAR const bt_addr_le_t *addr, uint8_t evtype,
FAR struct bt_keys_s *keys)
{
FAR struct bt_conn_s *conn;
/* Return if event is not connectible */
if (evtype != BT_LE_ADV_IND && evtype != BT_LE_ADV_DIRECT_IND)
{
return;
}
if (keys)
{
conn = bt_conn_lookup_state(&keys->addr, BT_CONN_CONNECT_SCAN);
}
else
{
conn = bt_conn_lookup_state(addr, BT_CONN_CONNECT_SCAN);
}
if (!conn)
{
return;
}
if (bt_hci_stop_scanning())
{
goto done;
}
if (hci_le_create_conn(addr))
{
goto done;
}
bt_conn_set_state(conn, BT_CONN_CONNECT);
done:
bt_conn_release(conn);
}
static void le_adv_report(FAR struct bt_buf_s *buf)
{
uint8_t num_reports;
if (buf->len < sizeof(num_reports))
{
wlerr("ERROR: Truncated advertising report event\n");
return;
}
num_reports = buf->data[0];
wlinfo("Adv number of reports %u\n", num_reports);
bt_buf_consume(buf, sizeof(num_reports));
while (num_reports--)
{
FAR struct bt_hci_ev_le_advertising_report_s *info;
FAR struct bt_keys_s *keys;
bt_addr_le_t addr;
size_t reportlen;
int8_t rssi;
/* A report is the fixed fields, the advertising data whose length
* they declare, and one octet of RSSI. Both lengths come from the
* controller, so check them against what was actually received
* before using them: the data length indexes the RSSI octet, and
* the total is what locates the next report.
*/
if (buf->len < sizeof(*info))
{
wlerr("ERROR: Truncated advertising report\n");
return;
}
info = (FAR void *)buf->data;
reportlen = sizeof(*info) + info->length + sizeof(rssi);
if (buf->len < reportlen)
{
wlerr("ERROR: Advertising report data length %u exceeds event\n",
info->length);
return;
}
rssi = info->data[info->length];
wlinfo("%s event %u, len %u, rssi %d dBm\n",
bt_addr_le_str(&info->addr), info->evt_type, info->length,
rssi);
keys = bt_keys_find_irk(&info->addr);
if (keys)
{
bt_addr_le_copy(&addr, &keys->addr);
wlinfo("Identity %s matched RPA %s\n",
bt_addr_le_str(&keys->addr), bt_addr_le_str(&info->addr));
}
else
{
bt_addr_le_copy(&addr, &info->addr);
}
if (g_scan_dev_found_cb)
{
g_scan_dev_found_cb(&addr, rssi, info->evt_type,
info->data, info->length);
}
check_pending_conn(&info->addr, info->evt_type, keys);
/* Advance to the next report. The RSSI octet sits after the
* advertising data and belongs to the report, which the previous
* advance left out, so every report after the first started one
* octet early.
*/
bt_buf_consume(buf, reportlen);
}
}
static void le_ltk_request(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_le_ltk_request_s *evt = (FAR void *)buf->data;
FAR struct bt_conn_s *conn;
uint16_t handle;
handle = BT_LE162HOST(evt->handle);
wlinfo("handle %u\n", handle);
conn = bt_conn_lookup_handle(handle);
if (!conn)
{
wlerr("ERROR: Unable to lookup conn for handle %u\n", handle);
return;
}
if (!conn->keys)
{
conn->keys = bt_keys_find(BT_KEYS_SLAVE_LTK, &conn->dst);
}
if (conn->keys && (conn->keys->keys & BT_KEYS_SLAVE_LTK) &&
conn->keys->slave_ltk.rand == evt->rand &&
conn->keys->slave_ltk.ediv == evt->ediv)
{
FAR struct bt_hci_cp_le_ltk_req_reply_s *cp;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_LTK_REQ_REPLY, sizeof(*cp));
if (!buf)
{
wlerr("ERROR: Out of command buffers\n");
goto done;
}
cp = bt_buf_extend(buf, sizeof(*cp));
cp->handle = evt->handle;
memcpy(cp->ltk, conn->keys->slave_ltk.val, 16);
bt_hci_cmd_send(BT_HCI_OP_LE_LTK_REQ_REPLY, buf);
}
else
{
FAR struct bt_hci_cp_le_ltk_req_neg_reply_s *cp;
buf = bt_hci_cmd_create(BT_HCI_OP_LE_LTK_REQ_NEG_REPLY, sizeof(*cp));
if (!buf)
{
wlerr("ERROR: Out of command buffers\n");
goto done;
}
cp = bt_buf_extend(buf, sizeof(*cp));
cp->handle = evt->handle;
bt_hci_cmd_send(BT_HCI_OP_LE_LTK_REQ_NEG_REPLY, buf);
}
done:
bt_conn_release(conn);
}
static int le_param_request(FAR struct bt_buf_s *buf)
{
FAR struct bt_buf_s *reply_buf;
FAR struct bt_hci_cp_le_rem_conn_param_req_reply_s *params_reply;
FAR struct bt_hci_evt_le_rem_conn_param_req_s *params_request;
reply_buf = bt_hci_cmd_create(BT_HCI_OP_LE_REM_CONN_PARAM_REQ_RPLY,
sizeof(*params_reply));
if (!reply_buf)
{
return -ENOBUFS;
}
params_request = (FAR void *)buf->data;
params_reply = bt_buf_extend(reply_buf, sizeof(*params_reply));
memset(params_reply, 0, sizeof(*params_reply));
params_reply->handle = BT_HOST2LE16(params_request->handle);
params_reply->min_interval = BT_HOST2LE16(params_request->min_interval);
params_reply->max_interval = BT_HOST2LE16(params_request->max_interval);
params_reply->latency = BT_HOST2LE16(params_request->latency);
params_reply->timeout = BT_HOST2LE16(params_request->timeout);
params_reply->max_ce_len = BT_HOST2LE16(0xffff);
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_REM_CONN_PARAM_REQ_RPLY,
reply_buf, NULL);
}
static void hci_le_meta_event(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_le_meta_event_s *evt = (FAR void *)buf->data;
bt_buf_consume(buf, sizeof(*evt));
switch (evt->subevent)
{
case BT_HCI_EVT_LE_CONN_COMPLETE:
le_conn_complete(buf);
break;
case BT_HCI_EVT_LE_ADVERTISING_REPORT:
le_adv_report(buf);
break;
case BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE:
break;
case BT_HCI_EVT_LE_LTK_REQUEST:
le_ltk_request(buf);
break;
case BT_HCI_EVT_LE_CONN_PARAM_REQ:
le_param_request(buf);
break;
default:
wlinfo("Unhandled LE event %04x\n", evt->subevent);
break;
}
}
static void hci_event(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_evt_hdr_s *hdr = (FAR void *)buf->data;
wlinfo("event %u\n", hdr->evt);
bt_buf_consume(buf, sizeof(struct bt_hci_evt_hdr_s));
switch (hdr->evt)
{
case BT_HCI_EVT_DISCONN_COMPLETE:
hci_disconn_complete(buf);
break;
case BT_HCI_EVT_ENCRYPT_CHANGE:
hci_encrypt_change(buf);
break;
case BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE:
hci_encrypt_key_refresh_complete(buf);
break;
case BT_HCI_EVT_LE_META_EVENT:
hci_le_meta_event(buf);
break;
default:
wlwarn("WARNING: Unhandled event 0x%02x\n", hdr->evt);
break;
}
}
#endif
/****************************************************************************
* Name: hci_tx_kthread
*
* Description:
* This is a kernel thread that handles sending of commands.
*
* Input Parameters:
* Standard kernel thread arguments
*
* Returned Value:
* Doesn't normally return.
*
****************************************************************************/
static int hci_tx_kthread(int argc, FAR char *argv[])
{
FAR struct bt_driver_s *btdev = g_btdev.btdev;
int ret;
wlinfo("started\n");
for (; g_btdev.tx_status == OK; )
{
FAR struct bt_buf_s *buf;
/* Wait until ncmd > 0 */
ret = nxsem_wait_uninterruptible(&g_btdev.ncmd_sem);
if (ret < 0)
{
wlerr("nxsem_wait_uninterruptible() failed: %d\n", ret);
return EXIT_FAILURE;
}
/* Get next command - wait if necessary */
buf = NULL;
ret = bt_queue_receive(&g_btdev.tx_queue, &buf);
DEBUGASSERT(ret >= 0 && buf != NULL);
UNUSED(ret);
g_btdev.ncmd = 0;
/* Clear out any existing sent command */
if (g_btdev.sent_cmd)
{
wlerr("ERROR: Uncleared pending sent_cmd\n");
bt_buf_release(g_btdev.sent_cmd);
g_btdev.sent_cmd = NULL;
}
/* Allow transmission if module is connected. */
if (g_btdev.tx_status == OK)
{
g_btdev.sent_cmd = bt_buf_addref(buf);
wlinfo("Sending command %04x buf %p to driver\n",
buf->u.hci.opcode, buf);
bt_send(btdev, buf);
}
bt_buf_release(buf);
}
/* Acknowledge the termination request. */
g_btdev.tx_status = ESHUTDOWN;
return EXIT_SUCCESS;
}
/****************************************************************************
* Name: hci_rx_work
*
* Description:
* This work function operates on the low priority work queue using the
* low priority buffer queue.
*
* Input Parameters:
* arg - Indicates which buffer queue should be used
*
* Returned Value:
* None
*
****************************************************************************/
static void hci_rx_work(FAR void *arg)
{
FAR struct bt_bufferlist_s *list = (FAR struct bt_bufferlist_s *)arg;
FAR struct bt_buf_s *buf;
wlinfo("list %p\n", list);
DEBUGASSERT(list != NULL);
while ((buf = bt_dequeue_bufwork(list)) != NULL)
{
wlinfo("buf %p type %u len %u\n", buf, buf->type, buf->len);
/* TODO: Hook monitor callback */
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
switch (buf->type)
{
case BT_ACL_IN:
hci_acl(buf);
break;
case BT_EVT:
hci_event(buf);
break;
default:
wlerr("ERROR: Unknown buf type %u\n", buf->type);
break;
}
#else
g_hci_cb->received(buf, g_hci_cb->context);
#endif
bt_buf_release(buf);
}
}
/****************************************************************************
* Name: priority_rx_work
*
* Description:
* This work function operates on the high priority work thread using the
* high priority buffer queue.
*
* Input Parameters:
* arg - Indicates which buffer queue should be used
*
* Returned Value:
* None
*
****************************************************************************/
static void priority_rx_work(FAR void *arg)
{
FAR struct bt_bufferlist_s *list = (FAR struct bt_bufferlist_s *)arg;
FAR struct bt_buf_s *buf;
wlinfo("list %p\n", list);
DEBUGASSERT(list != NULL);
while ((buf = bt_dequeue_bufwork(list)) != NULL)
{
FAR struct bt_hci_evt_hdr_s *hdr = (FAR void *)buf->data;
wlinfo("buf %p type %u len %u\n", buf, buf->type, buf->len);
/* TODO: Hook monitor callback */
if (buf->type != BT_EVT)
{
wlerr("Unknown buf type %u\n", buf->type);
bt_buf_release(buf);
continue;
}
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
bt_buf_consume(buf, sizeof(struct bt_hci_evt_hdr_s));
switch (hdr->evt)
{
case BT_HCI_EVT_CMD_COMPLETE:
hci_cmd_complete(buf);
break;
case BT_HCI_EVT_CMD_STATUS:
hci_cmd_status(buf);
break;
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
hci_num_completed_packets(buf);
break;
default:
wlerr("Unknown event 0x%02x\n", hdr->evt);
break;
}
#else
UNUSED(hdr);
g_hci_cb->received(buf, g_hci_cb->context);
#endif
bt_buf_release(buf);
}
}
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
static void read_local_features_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_read_local_features_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
memcpy(g_btdev.features, rp->features, sizeof(g_btdev.features));
}
static void read_local_ver_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_read_local_version_info_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
g_btdev.hci_version = rp->hci_version;
g_btdev.hci_revision = BT_LE162HOST(rp->hci_revision);
g_btdev.manufacturer = BT_LE162HOST(rp->manufacturer);
}
static void read_bdaddr_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_read_bd_addr_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
bt_addr_copy(&g_btdev.bdaddr, &rp->bdaddr);
}
static void read_le_features_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_le_read_local_features_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
memcpy(g_btdev.le_features, rp->features, sizeof(g_btdev.le_features));
}
static void read_buffer_size_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_read_buffer_size_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
/* If LE-side has buffers we can ignore the BR/EDR values */
if (g_btdev.le_mtu)
{
return;
}
g_btdev.le_mtu = BT_LE162HOST(rp->acl_max_len);
g_btdev.le_pkts = BT_LE162HOST(rp->acl_max_num);
}
static void le_read_buffer_size_complete(FAR struct bt_buf_s *buf)
{
FAR struct bt_hci_rp_le_read_buffer_size_s *rp = (FAR void *)buf->data;
wlinfo("status %u\n", rp->status);
g_btdev.le_mtu = BT_LE162HOST(rp->le_max_len);
g_btdev.le_pkts = rp->le_max_num;
}
/****************************************************************************
* Name: hci_initialize()
*
* Description:
*
* Input Parameters:
* none
*
* Returned Value:
* None
*
****************************************************************************/
static int hci_initialize(void)
{
FAR struct bt_hci_cp_host_buffer_size_s *hbs;
FAR struct bt_hci_cp_set_event_mask_s *ev;
FAR struct bt_buf_s *buf;
FAR struct bt_buf_s *rsp;
FAR uint8_t *enable;
int ret;
/* Send HCI_RESET */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_RESET, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: BT_HCI_OP_RESET failed: %d\n", ret);
return ret;
}
bt_buf_release(rsp);
/* Read Local Supported Features */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_FEATURES, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: BT_HCI_OP_READ_LOCAL_FEATURES failed: %d\n", ret);
return ret;
}
read_local_features_complete(rsp);
bt_buf_release(rsp);
/* Read Local Version Information */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_VERSION_INFO, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
read_local_ver_complete(rsp);
bt_buf_release(rsp);
/* Read Bluetooth Address */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BD_ADDR, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
read_bdaddr_complete(rsp);
bt_buf_release(rsp);
/* For now we only support LE capable controllers */
if (!lmp_le_capable(g_btdev))
{
wlerr("ERROR: Non-LE capable controller detected!\n");
return -ENODEV;
}
/* Read Low Energy Supported Features */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_LOCAL_FEATURES, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: BT_HCI_OP_LE_READ_LOCAL_FEATURES failed: %d\n", ret);
return ret;
}
read_le_features_complete(rsp);
bt_buf_release(rsp);
/* Read LE Buffer Size */
ret = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_BUFFER_SIZE, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: BT_HCI_OP_LE_READ_BUFFER_SIZE failed: %d\n", ret);
return ret;
}
le_read_buffer_size_complete(rsp);
bt_buf_release(rsp);
buf = bt_hci_cmd_create(BT_HCI_OP_SET_EVENT_MASK, sizeof(*ev));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
ev = bt_buf_extend(buf, sizeof(*ev));
memset(ev, 0, sizeof(*ev));
ev->events[0] |= 0x04; /* Connection Complete */
ev->events[0] |= 0x08; /* Connection Request */
ev->events[0] |= 0x10; /* Disconnection Complete */
ev->events[1] |= 0x08; /* Read Remote Version Information Complete */
ev->events[1] |= 0x20; /* Command Complete */
ev->events[1] |= 0x40; /* Command Status */
ev->events[1] |= 0x80; /* Hardware Error */
ev->events[2] |= 0x04; /* Number of Completed Packets */
ev->events[3] |= 0x02; /* Data Buffer Overflow */
ev->events[7] |= 0x20; /* LE Meta-Event */
if (g_btdev.le_features[0] & BT_HCI_LE_ENCRYPTION)
{
ev->events[0] |= 0x80; /* Encryption Change */
ev->events[5] |= 0x80; /* Encryption Key Refresh Complete */
}
bt_hci_cmd_send_sync(BT_HCI_OP_SET_EVENT_MASK, buf, NULL);
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_EVENT_MASK, sizeof(*ev));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
ev = bt_buf_extend(buf, sizeof(*ev));
memset(ev, 0, sizeof(*ev));
ev->events[0] |= 0xff;
ret = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EVENT_MASK, buf, NULL);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
buf = bt_hci_cmd_create(BT_HCI_OP_HOST_BUFFER_SIZE, sizeof(*hbs));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
hbs = bt_buf_extend(buf, sizeof(*hbs));
memset(hbs, 0, sizeof(*hbs));
hbs->acl_mtu = BT_HOST2LE16(BLUETOOTH_MAX_FRAMELEN -
sizeof(struct bt_hci_acl_hdr_s) -
g_btdev.btdev->head_reserve);
hbs->acl_pkts = BT_HOST2LE16(CONFIG_BLUETOOTH_BUFFER_PREALLOC);
ret = bt_hci_cmd_send(BT_HCI_OP_HOST_BUFFER_SIZE, buf);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send failed: %d\n", ret);
return ret;
}
buf = bt_hci_cmd_create(BT_HCI_OP_SET_CTL_TO_HOST_FLOW, 1);
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
enable = bt_buf_extend(buf, sizeof(*enable));
#ifdef CONFIG_BLUETOOTH_CNTRL_HOST_FLOW_DISABLE
*enable = 0;
#else
*enable = 1;
#endif
ret = bt_hci_cmd_send_sync(BT_HCI_OP_SET_CTL_TO_HOST_FLOW, buf, NULL);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
if (lmp_bredr_capable(g_btdev))
{
FAR struct bt_hci_cp_write_le_host_supp_s *cp;
/* Use BR/EDR buffer size if LE reports zero buffers */
if (!g_btdev.le_mtu)
{
ret = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BUFFER_SIZE, NULL, &rsp);
if (ret < 0)
{
wlerr("ERROR: bt_hci_cmd_send_sync failed: %d\n", ret);
return ret;
}
read_buffer_size_complete(rsp);
bt_buf_release(rsp);
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_WRITE_LE_HOST_SUPP, sizeof(*cp));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
/* Explicitly enable LE for dual-mode controllers */
cp = bt_buf_extend(buf, sizeof *cp);
cp->le = 0x01;
cp->simul = 0x00;
bt_hci_cmd_send_sync(BT_HCI_OP_LE_WRITE_LE_HOST_SUPP, buf, NULL);
}
wlinfo("HCI ver %u rev %u, manufacturer %u\n", g_btdev.hci_version,
g_btdev.hci_revision, g_btdev.manufacturer);
wlinfo("ACL buffers: pkts %u mtu %u\n", g_btdev.le_pkts, g_btdev.le_mtu);
/* Initialize & prime the semaphore for counting controller-side available
* ACL packet buffers.
*/
nxsem_init(&g_btdev.le_pkts_sem, 0, g_btdev.le_pkts);
return 0;
}
/****************************************************************************
* Name: cmd_queue_deinit
*
* Description:
* Threads, fifos and semaphores deinitialization
*
* Input Parameters:
* none
*
* Returned Value:
* None
*
****************************************************************************/
static void cmd_queue_deinit(void)
{
int ret;
FAR struct bt_buf_s *buf;
/* Tell the tx thread that the module is disconnected.
*/
g_btdev.tx_status = ENOTCONN;
/* Make sure the thread is not blocked by the semaphore.
*/
nxsem_post(&g_btdev.ncmd_sem);
/* We create and push a packet into the tx queue to unblock the thread.
* Any packet will do.
*/
buf = bt_hci_cmd_create(BT_HCI_OP_RESET, 0);
if (buf)
{
ret = bt_queue_send(&g_btdev.tx_queue, buf, BT_NORMAL_PRIO);
if (ret < 0)
{
wlerr("ERROR: bt_queue_send() failed: %d\n", ret);
}
}
/* Wait for the tx thread to exit gracefully. */
while (g_btdev.tx_status == ENOTCONN)
{
nxsched_usleep(1000);
}
/* Deinitialization */
nxsem_destroy(&g_btdev.ncmd_sem);
file_mq_close(&g_btdev.tx_queue);
work_cancel(HPWORK, &g_hp_work);
work_cancel(LPWORK, &g_lp_work);
}
#endif
/* threads, fifos and semaphores initialization */
static void cmd_queue_init(void)
{
int ret;
#ifdef CONFIG_BLUETOOTH_TXCMD_PINNED_TO_CORE
cpu_set_t cpuset;
#endif
int pid;
/* When there is a command to be sent to the Bluetooth driver, it queued on
* the Tx queue and received by logic on the Tx kernel thread.
*/
ret = bt_queue_open(BT_HCI_TX, O_RDWR | O_CREAT | O_CLOEXEC,
CONFIG_BLUETOOTH_TXCMD_NMSGS, &g_btdev.tx_queue);
DEBUGASSERT(ret >= 0);
nxsem_init(&g_btdev.ncmd_sem, 0, 1);
g_btdev.ncmd = 1;
g_btdev.tx_status = OK;
#ifdef CONFIG_BLUETOOTH_TXCMD_PINNED_TO_CORE
sched_lock();
#endif
pid = kthread_create("BT HCI Tx", CONFIG_BLUETOOTH_TXCMD_PRIORITY,
CONFIG_BLUETOOTH_TXCMD_STACKSIZE,
hci_tx_kthread, NULL);
DEBUGASSERT(pid > 0);
#ifdef CONFIG_BLUETOOTH_TXCMD_PINNED_TO_CORE
CPU_ZERO(&cpuset);
CPU_SET((CONFIG_BLUETOOTH_TXCMD_CORE - 1), &cpuset);
ret = nxsched_set_affinity(pid, sizeof(cpuset), &cpuset);
if (ret)
{
wlerr("Failed to set affinity error=%d\n", ret);
DEBUGPANIC();
}
sched_unlock();
#endif
UNUSED(ret);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: bt_send
*
* Description:
* Send the provided buffer to the bluetooth driver
*
* Input Parameters:
* btdev - An instance of the low-level drivers interface structure.
* buf - The buffer to be sent by the driver
*
* Returned Value:
* Zero is returned on success; a negated errno value is returned on any
* failure.
*
****************************************************************************/
int bt_send(FAR struct bt_driver_s *btdev,
FAR struct bt_buf_s *buf)
{
/* Send to driver */
return btdev->send(btdev, buf->type, buf->data, buf->len);
}
/****************************************************************************
* Name: bt_initialize
*
* Description:
* Initialize Bluetooth. Must be the called before anything else.
*
* Returned Value:
* Zero on success or (negative) error code otherwise.
*
****************************************************************************/
int bt_initialize(void)
{
FAR struct bt_driver_s *btdev = g_btdev.btdev;
int ret;
wlinfo("btdev %p\n", btdev);
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
g_callback_list = NULL;
g_scan_dev_found_cb = NULL;
#endif
memset(&g_lp_rxlist, 0, sizeof(g_lp_rxlist));
memset(&g_hp_rxlist, 0, sizeof(g_hp_rxlist));
spin_lock_init(&g_hp_rxlist.lock);
spin_lock_init(&g_lp_rxlist.lock);
DEBUGASSERT(btdev != NULL);
bt_buf_initialize();
cmd_queue_init();
ret = btdev->open(btdev);
if (ret < 0)
{
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
cmd_queue_deinit();
#endif
wlerr("ERROR: HCI driver open failed (%d)\n", ret);
return ret;
}
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
ret = hci_initialize();
if (ret < 0)
{
cmd_queue_deinit();
wlerr("ERROR: hci_initialize failed: %d\n", ret);
return ret;
}
ret = bt_l2cap_init();
#endif
return ret;
}
/****************************************************************************
* Name: bt_deinitialize
*
* Description:
* Deinitialize Bluetooth.
*
* Returned Value:
* Zero on success or (negative) error code otherwise.
*
****************************************************************************/
int bt_deinitialize(void)
{
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
FAR struct bt_driver_s *btdev = g_btdev.btdev;
cmd_queue_deinit();
/* Call the bluetooth HCI driver's close function if available. */
if (btdev)
{
if (btdev->close)
{
btdev->close(btdev);
}
}
#endif
return OK;
}
/****************************************************************************
* Name: bt_driver_set
*
* Description:
* Set the Bluetooth low-level driver with the Bluetooth stack.
* This is called from the low-level driver and is part of the driver
* interface prototyped in include/nuttx/wireless/bluetooth/bt_driver.h
*
* This function associates the Bluetooth driver with the Bluetooth stack.
*
* Input Parameters:
* btdev - An instance of the low-level drivers interface structure.
*
* Returned Value:
* Zero is returned on success; a negated errno value is returned on any
* failure.
*
****************************************************************************/
int bt_driver_set(FAR struct bt_driver_s *btdev)
{
DEBUGASSERT(btdev != NULL && btdev->open != NULL && btdev->send != NULL);
if (g_btdev.btdev != NULL)
{
wlwarn("WARNING: Already registered\n");
return -EALREADY;
}
memset(&g_btdev, 0, sizeof(g_btdev));
g_btdev.btdev = btdev;
return 0;
}
/****************************************************************************
* Name: bt_driver_unset
*
* Description:
* Unset a Bluetooth low-level driver previously set with bt_driver_set.
* This may be called from the low-level driver and is part of the driver
* interface prototyped in include/nuttx/wireless/bluetooth/bt_driver.h
*
* Input Parameters:
* btdev - An instance of the low-level drivers interface structure.
*
* Returned Value:
* None
*
****************************************************************************/
void bt_driver_unset(FAR struct bt_driver_s *btdev)
{
g_btdev.btdev = NULL;
}
/****************************************************************************
* Name: bt_receive
*
* Description:
* Called by the Bluetooth low-level driver when new data is received from
* the radio. This may be called from the low-level driver and is part of
* the driver interface prototyped in
* include/nuttx/wireless/bluetooth/bt_driver.h
*
* NOTE: This function will defer all real work to the low or to the high
* priority work queues. Therefore, this function may safely be called
* from interrupt handling logic.
*
* Input Parameters:
* buf - An instance of the buffer structure providing the received frame.
*
* Returned Value:
* None
*
****************************************************************************/
int bt_receive(FAR struct bt_driver_s *btdev, enum bt_buf_type_e type,
FAR void *data, size_t len)
{
FAR struct bt_hci_evt_hdr_s *hdr;
struct bt_buf_s *buf;
int ret;
if (len + BLUETOOTH_H4_HDRLEN > CONFIG_IOB_BUFSIZE)
{
wlerr("ERROR: Data too long\n");
return -EINVAL;
}
wlinfo("data %p len %zu\n", data, len);
/* Critical command complete/status events use the high priority work
* queue.
*/
buf = bt_buf_alloc(type, NULL, BLUETOOTH_H4_HDRLEN);
if (buf == NULL)
{
return -ENOMEM;
}
memcpy(bt_buf_extend(buf, len), data, len);
if (type != BT_ACL_IN)
{
if (type != BT_EVT)
{
wlerr("ERROR: Invalid buf type %u\n", buf->type);
bt_buf_release(buf);
return -EINVAL;
}
/* Command Complete/Status events use high priority messages. */
hdr = (FAR void *)buf->data;
if (hdr->evt == BT_HCI_EVT_CMD_COMPLETE ||
hdr->evt == BT_HCI_EVT_CMD_STATUS ||
hdr->evt == BT_HCI_EVT_NUM_COMPLETED_PACKETS)
{
/* Add the buffer to the high priority Rx buffer list */
bt_enqueue_bufwork(&g_hp_rxlist, buf);
/* If there is already pending work, then do nothing. Otherwise,
* schedule processing of the Rx buffer list on the high priority
* work queue.
*/
if (work_available(&g_hp_work))
{
ret = work_queue(HPWORK, &g_hp_work, priority_rx_work,
&g_hp_rxlist, 0);
if (ret < 0)
{
wlerr("ERROR: Failed to schedule HPWORK: %d\n", ret);
}
}
return OK;
}
}
/* All others use the low priority work queue */
/* Add the buffer to the low priority Rx buffer list */
bt_enqueue_bufwork(&g_lp_rxlist, buf);
/* If there is already pending work, then do nothing. Otherwise, schedule
* processing of the Rx buffer list on the low priority work queue.
*/
if (work_available(&g_lp_work))
{
ret = work_queue(LPWORK, &g_lp_work, hci_rx_work, &g_lp_rxlist, 0);
if (ret < 0)
{
wlerr("ERROR: Failed to schedule LPWORK: %d\n", ret);
}
}
return OK;
}
#ifdef CONFIG_WIRELESS_BLUETOOTH_HOST
/****************************************************************************
* Name: bt_hci_cmd_create
*
* Description:
* Allocate and initialize a buffer for a command
*
* Returned Value:
* A reference to the allocated buffer. NULL could possibly be returned
* on any failure to allocate.
*
****************************************************************************/
FAR struct bt_buf_s *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len)
{
FAR struct bt_hci_cmd_hdr_s *hdr;
FAR struct bt_buf_s *buf;
wlinfo("opcode %04x param_len %u\n", opcode, param_len);
buf = bt_buf_alloc(BT_CMD, NULL, g_btdev.btdev->head_reserve);
if (!buf)
{
wlerr("ERROR: Cannot get free buffer\n");
return NULL;
}
wlinfo("buf %p\n", buf);
buf->u.hci.opcode = opcode;
buf->u.hci.sync = NULL;
hdr = bt_buf_extend(buf, sizeof(*hdr));
hdr->opcode = BT_HOST2LE16(opcode);
hdr->param_len = param_len;
return buf;
}
int bt_hci_cmd_send(uint16_t opcode, FAR struct bt_buf_s *buf)
{
int ret;
if (buf == NULL)
{
buf = bt_hci_cmd_create(opcode, 0);
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
}
wlinfo("opcode %04x len %u\n", opcode, buf->len);
/* Host Number of Completed Packets can ignore the ncmd value and does not
* generate any cmd complete/status events.
*/
if (opcode == BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS)
{
bt_send(g_btdev.btdev, buf);
bt_buf_release(buf);
return 0;
}
ret = bt_queue_send(&g_btdev.tx_queue, buf, BT_NORMAL_PRIO);
if (ret < 0)
{
wlerr("ERROR: bt_queue_send() failed: %d\n", ret);
}
return ret;
}
int bt_hci_cmd_send_sync(uint16_t opcode, FAR struct bt_buf_s *buf,
FAR struct bt_buf_s **rsp)
{
sem_t sync_sem;
int ret;
/* NOTE: This function cannot be called from the rx thread since it relies
* on the very same thread in processing the cmd_complete event and giving
* back the blocking semaphore.
*/
if (buf == NULL)
{
buf = bt_hci_cmd_create(opcode, 0);
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
}
wlinfo("opcode %04x len %u\n", opcode, buf->len);
/* Set up for the wait */
nxsem_init(&sync_sem, 0, 0);
buf->u.hci.sync = &sync_sem;
/* Send the frame */
ret = bt_queue_send(&g_btdev.tx_queue, buf, BT_NORMAL_PRIO);
if (ret < 0)
{
wlerr("ERROR: bt_queue_send() failed: %d\n", ret);
}
else
{
/* Wait for the response to the command. An I/O error will be
* declared if the response does not occur within the timeout
* interval.
*
* REVISIT: The cause of the timeout could be a failure to receive a
* response to a sent frame or, perhaps, a failure to send the frame.
* Should there also be logic to flush any unsent Tx packets?
*/
ret = nxsem_tickwait_uninterruptible(&sync_sem,
MSEC2TICK(TIMEOUT_MSEC));
}
/* Indicate failure if we failed to get the response */
if (ret >= 0)
{
if (buf->u.hci.sync == NULL)
{
wlerr("ERROR: Failed get return parameters\n");
ret = -EIO;
}
else
{
ret = 0;
}
}
else
{
wlerr("ERROR: Failed get response\n");
nxsem_destroy(&sync_sem);
return -EIO;
}
/* Note: if ret < 0 the packet might just be delayed and could still
* be sent. We cannot decrease the ref count since it if it was sent
* it buf could be pointed a completely different request.
*/
if (rsp != NULL)
{
/* If the response is expected provide the sync response */
*rsp = buf->u.hci.sync;
}
else if (buf->u.hci.sync != NULL)
{
/* If a sync response was given but not requested drop it */
bt_buf_release(buf->u.hci.sync);
}
nxsem_destroy(&sync_sem);
return ret;
}
/****************************************************************************
* Name: bt_start_advertising
*
* Description:
* Set advertisement data, scan response data, advertisement parameters
* and start advertising.
*
* Input Parameters:
* type - Advertising type.
* ad - Data to be used in advertisement packets.
* sd - Data to be used in scan response packets.
*
* Returned Value:
* Zero on success or (negative) error code otherwise.
*
****************************************************************************/
int bt_start_advertising(uint8_t type, FAR const struct bt_eir_s *ad,
FAR const struct bt_eir_s *sd)
{
FAR struct bt_buf_s *buf;
FAR struct bt_hci_cp_le_set_adv_data_s *set_data;
FAR struct bt_hci_cp_le_set_adv_data_s *scan_rsp;
FAR struct bt_hci_cp_le_set_adv_parameters_s *set_param;
int i;
if (ad == NULL)
{
goto send_scan_rsp;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_DATA, sizeof(*set_data));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
set_data = bt_buf_extend(buf, sizeof(*set_data));
memset(set_data, 0, sizeof(*set_data));
for (i = 0; ad[i].len > 0; i++)
{
/* Check if ad fit in the remaining buffer */
if (set_data->len + ad[i].len + 1 > 29)
{
break;
}
memcpy(&set_data->data[set_data->len], &ad[i], ad[i].len + 1);
set_data->len += ad[i].len + 1;
}
bt_hci_cmd_send(BT_HCI_OP_LE_SET_ADV_DATA, buf);
send_scan_rsp:
if (sd == NULL)
{
goto send_set_param;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_SCAN_RSP_DATA,
sizeof(*scan_rsp));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
scan_rsp = bt_buf_extend(buf, sizeof(*scan_rsp));
memset(scan_rsp, 0, sizeof(*scan_rsp));
for (i = 0; sd[i].len > 0; i++)
{
/* Check if ad fit in the remaining buffer */
if (scan_rsp->len + sd[i].len + 1 > 29)
{
break;
}
memcpy(&scan_rsp->data[scan_rsp->len], &sd[i], sd[i].len + 1);
scan_rsp->len += sd[i].len + 1;
}
bt_hci_cmd_send(BT_HCI_OP_LE_SET_SCAN_RSP_DATA, buf);
send_set_param:
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_PARAMETERS,
sizeof(*set_param));
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
set_param = bt_buf_extend(buf, sizeof(*set_param));
memset(set_param, 0, sizeof(*set_param));
set_param->min_interval = BT_HOST2LE16(300);
set_param->max_interval = BT_HOST2LE16(300);
set_param->type = type;
set_param->channel_map = 0x07;
bt_hci_cmd_send(BT_HCI_OP_LE_SET_ADV_PARAMETERS, buf);
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1);
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
g_btdev.adv_enable = 0x01;
memcpy(bt_buf_extend(buf, 1), &g_btdev.adv_enable, 1);
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL);
}
/****************************************************************************
* Name: bt_stop_advertising
*
* Description:
* Stops ongoing advertising.
*
* Returned Value:
* Zero on success or (negative) error code otherwise.
*
****************************************************************************/
int bt_stop_advertising(void)
{
FAR struct bt_buf_s *buf;
if (!g_btdev.adv_enable)
{
wlwarn("WARNING: Already advertising\n");
return -EALREADY;
}
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1);
if (buf == NULL)
{
wlerr("ERROR: Failed to create buffer\n");
return -ENOBUFS;
}
g_btdev.adv_enable = 0x00;
memcpy(bt_buf_extend(buf, 1), &g_btdev.adv_enable, 1);
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL);
}
/****************************************************************************
* Name: bt_start_scanning
*
* Description:
* Start LE scanning with and provide results through the specified
* callback.
*
* Input Parameters:
* filter_dups - Enable duplicate filtering (or not).
* cb - Callback to notify scan results.
*
* Returned Value:
* Zero on success or error code otherwise, positive in case
* of protocol error or negative (POSIX) in case of stack internal error
*
****************************************************************************/
int bt_start_scanning(uint8_t scan_filter, bt_le_scan_cb_t cb)
{
/* Return if active scan is already enabled */
if (g_scan_dev_found_cb)
{
wlwarn("WARNING: Already scanning\n");
return -EALREADY;
}
g_scan_dev_found_cb = cb;
g_btdev.scan_filter = scan_filter;
return bt_le_scan_update();
}
/****************************************************************************
* Name: bt_stop_scanning
*
* Description:
* Stops ongoing LE scanning.
*
* Returned Value:
* Zero on success or error code otherwise, positive in case
* of protocol error or negative (POSIX) in case of stack internal error
*
****************************************************************************/
int bt_stop_scanning(void)
{
/* Return if active scanning is already disabled */
if (g_scan_dev_found_cb == NULL)
{
wlwarn("WARNING: Not scanning\n");
return -EALREADY;
}
g_scan_dev_found_cb = NULL;
g_btdev.scan_filter = BT_LE_SCAN_FILTER_DUP_ENABLE;
return bt_le_scan_update();
}
/****************************************************************************
* Name: bt_le_scan_update
*
* Description:
* Used to determine whether to start scan and which scan type should be
* used.
*
* Returned Value:
* Zero on success or error code otherwise, positive in case
* of protocol error or negative (POSIX) in case of stack internal error
*
****************************************************************************/
int bt_le_scan_update(void)
{
FAR struct bt_conn_s *conn;
int ret;
if (g_btdev.scan_enable)
{
if (g_scan_dev_found_cb)
{
return 0;
}
ret = bt_hci_stop_scanning();
if (ret)
{
return ret;
}
}
if (g_scan_dev_found_cb)
{
return bt_hci_start_scanning(BT_LE_SCAN_ACTIVE, g_btdev.scan_filter);
}
conn = bt_conn_lookup_state(BT_ADDR_LE_ANY, BT_CONN_CONNECT_SCAN);
if (!conn)
{
return 0;
}
bt_conn_release(conn);
return bt_hci_start_scanning(BT_LE_SCAN_PASSIVE, g_btdev.scan_filter);
}
/****************************************************************************
* Name: bt_conn_cb_register
*
* Description:
* Register callbacks to monitor the state of connections.
*
* Input Parameters:
* cb - Instance of the callback structure.
*
****************************************************************************/
void bt_conn_cb_register(FAR struct bt_conn_cb_s *cb)
{
cb->flink = g_callback_list;
g_callback_list = cb;
}
FAR const char *bt_addr_str(FAR const bt_addr_t *addr)
{
static char bufs[2][18];
static uint8_t cur;
FAR char *str;
str = bufs[cur++];
cur %= nitems(bufs);
bt_addr_to_str(addr, str, sizeof(bufs[cur]));
return str;
}
FAR const char *bt_addr_le_str(FAR const bt_addr_le_t *addr)
{
static char bufs[2][27];
static uint8_t cur;
FAR char *str;
str = bufs[cur++];
cur %= nitems(bufs);
bt_addr_le_to_str(addr, str, sizeof(bufs[cur]));
return str;
}
#else
/****************************************************************************
* Name: bt_hci_cb_register
*
* Description:
* Register callbacks to handle RAW HCI packets
*
* Input Parameters:
* cb - Instance of the callback structure.
*
* Returned Value:
* None
*
****************************************************************************/
void bt_hci_cb_register(FAR struct bt_hci_cb_s *cb)
{
g_hci_cb = cb;
}
#endif