nuttx/wireless/bluetooth/bt_hcicore.c
Alin Jerpelea db49370189 wireless: migrate to SPDX identifier
Most tools used for compliance and SBOM generation use SPDX identifiers
This change brings us a step closer to an easy SBOM generation.

Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com>
2024-09-11 19:49:34 +08:00

2317 lines
56 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 <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(NULL);
buf->flink = list->head;
if (list->head == NULL)
{
list->tail = buf;
}
list->head = buf;
spin_unlock_irqrestore(NULL, 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(NULL);
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(NULL, 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_conn_receive(conn, buf, flags);
}
/* 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);
if (evt->role == BT_HCI_ROLE_SLAVE)
{
bt_l2cap_update_conn_param(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)
{
FAR struct bt_hci_ev_le_advertising_report_s *info;
uint8_t num_reports = buf->data[0];
wlinfo("Adv number of reports %u\n", num_reports);
info = bt_buf_consume(buf, sizeof(num_reports));
while (num_reports--)
{
int8_t rssi = info->data[info->length];
FAR struct bt_keys_s *keys;
bt_addr_le_t addr;
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);
/* Get next report iteration by moving pointer to right offset in buf
* according to spec 4.2, Vol 2, Part E, 7.7.65.2.
*
* TODO: multiple reports are stored as multiple arrays not one array
* of structs. If num_reports > 0 this will not WORK!
*/
/* Note that info already contains one byte which accounts for RSSI */
info = bt_buf_consume(buf, sizeof(*info) + info->length);
}
}
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 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_LTK_REQUEST:
le_ltk_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] |= 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_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));
*enable = 0x01;
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)
{
nxsig_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));
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_register
*
* Description:
* Register 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_register(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_unregister
*
* Description:
* Unregister a Bluetooth low-level driver previously registered with
* bt_driver_register. 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_unregister(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;
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;
}
}
/* 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