| /**************************************************************************** |
| * wireless/bluetooth/bt_hcicore.c |
| * |
| * 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 |
| |