From 7a5bf593450c20bd4da313fa7d2c2a6320cf35d4 Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 6 Sep 2024 16:02:51 +0800 Subject: [PATCH 001/498] BTsnoop: add snoop log filter for a2dp audio bug: v/42934 Signed-off-by: duqunbo --- service/utils/btsnoop_filter.c | 514 +++++++++++++++++++++++++++++++++ service/utils/btsnoop_filter.h | 91 ++++++ 2 files changed, 605 insertions(+) create mode 100644 service/utils/btsnoop_filter.c create mode 100644 service/utils/btsnoop_filter.h diff --git a/service/utils/btsnoop_filter.c b/service/utils/btsnoop_filter.c new file mode 100644 index 00000000..023c299f --- /dev/null +++ b/service/utils/btsnoop_filter.c @@ -0,0 +1,514 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include + +#include "utils/btsnoop_filter.h" +#include "utils/btsnoop_log.h" +#include "utils/log.h" + +typedef struct { + uint64_t filter_items; + bt_list_t* acl_connection_list; +} g_snoop_filter_global_t; + +static g_snoop_filter_global_t g_snoop_filter = { 0 }; + +#define L2CAP_NULL_IDENTIFIER_CID 0x0000 +#define L2CAP_SIGNALING_CHANNEL_CID 0x0001 +#define L2CAP_LE_SIGNALING_CHANNEL_CID 0x0005 + +#define GET_HCI_H4_PLAYLOAD(pkt) ((pkt) + 1) +#define GET_HCI_H4_PLAYLOAD_SIZE(pkt_size) ((pkt_size)-1) +#define GET_L2CAP_PACKET_DATA(pkt) ((pkt) + 6) +#define GET_L2CAP_PACKET_PLATLOAD_SIZE(pkt_size) ((pkt_size)-6) +#define GET_HCI_EVENT_PLAYLOAD(pkt) ((pkt) + 2) +#define GET_HCI_EVENT_PLAYLOAD_SIZE(pkt_size) ((pkt_size)-2) +#define GET_L2CAP_COMMAND_DATA(pkt) ((pkt) + 4) +#define GET_L2CAP_COMMAND_DATA_SIZE(pkt_size) ((pkt_size)-4) + +#define GET_HCI_TYPE(hci_pkt) ((hci_pkt)[0]) +#define GET_HCI_EVENT_CODE(evt) ((evt)[0]) +#define GET_ACL_CONNECTION_HANDLE_FROM_ACL_DATA(pkt) (((uint16_t*)(pkt))[0] & 0x0FFF) +#define GET_PB_FLAG_FROM_ACL_DATA(pkt) (((pkt)[1] >> 4) & 0x3) +#define GET_ACL_CONNECTION_HANDLE_FROM_CONNECT_COMPELTE_EVENT(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 3)) +#define GET_STATUS_FROM_CONNECT_COMPELTE_EVENT(pkt) (((uint8_t*)(pkt))[2]) +#define GET_ACL_CONNECTION_HANDLE_FROM_DISCONNECT_COMPELTE_EVENT(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 3)) +#define GET_STATUS_FROM_DISCONNECT_COMPELTE_EVENT(pkt) (((uint8_t*)(pkt))[2]) +#define GET_L2CAP_CID(pkt) (*(uint16_t*)(pkt)) +#define GET_L2CAP_COMMAND_CODE(pkt) ((pkt)[2]) +#define GET_L2CAP_CONNECTION_REQ_COMMAND_PSM(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 6)) +#define GET_L2CAP_CONNECTION_REQ_COMMAND_SCID(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 8)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_SCID(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 8)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_DCID(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 6)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_STATUS(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 10)) +#define GET_L2CAP_DISCONNECTION_RSP_COMMAND_SCID(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 6)) +#define GET_L2CAP_DISCONNECTION_RSP_COMMAND_DCID(pkt) (*(uint16_t*)(((uint8_t*)(pkt)) + 8)) + +#define GET_L2CAP_RSP_DERIVE_CIDS(pkt, is_receive, local_cid, peer_cid, get_scid, get_dcid) \ + do { \ + if (is_receive) { \ + local_cid = get_scid(pkt); \ + peer_cid = get_dcid(pkt); \ + } else { \ + local_cid = get_dcid(pkt); \ + peer_cid = get_scid(pkt); \ + response_cids_info.peer_cid = peer_cid; \ + } \ + } while (0) + +static void free_l2cap_cid_item(void* data) +{ + free(data); +} + +static btsnoop_filter_acl_info_t* malloc_acl_connection_item(uint16_t acl_connection_handle) +{ + btsnoop_filter_acl_info_t* item; + + item = zalloc(sizeof(btsnoop_filter_acl_info_t)); + if (NULL == item) { + return NULL; + } + + item->connection_handle = acl_connection_handle; + item->filter_cids = bt_list_new(free_l2cap_cid_item); + + if (NULL == item->filter_cids) { + free(item); + return NULL; + } + + return item; +} + +static void free_acl_connection_item(void* data) +{ + btsnoop_filter_acl_info_t* info = (btsnoop_filter_acl_info_t*)data; + + bt_list_free(info->filter_cids); + free(data); +} + +static bool compare_acl_connection_item(void* data, void* context) +{ + return ((btsnoop_filter_acl_info_t*)data)->connection_handle == *(uint16_t*)context; +} + +static btsnoop_filter_l2cap_channel_info_t* malloc_filter_cid_item(uint16_t local_cid, uint16_t peer_cid, uint16_t psm, btsnoop_l2cap_state_t state) +{ + btsnoop_filter_l2cap_channel_info_t* new_item; + + new_item = (btsnoop_filter_l2cap_channel_info_t*)zalloc(sizeof(btsnoop_filter_l2cap_channel_info_t)); + + if (NULL == new_item) + return NULL; + + new_item->cids.local_cid = local_cid; + new_item->cids.peer_cid = peer_cid; + new_item->psm = psm; + new_item->state = state; + return new_item; +} + +static bool compare_l2cap_local_and_remote_cid_item(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* tmp_data = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_l2cap_channel_cids_t* tmp_context = (btsnoop_l2cap_channel_cids_t*)context; + + return (tmp_data->cids.local_cid == tmp_context->local_cid) && (tmp_data->cids.peer_cid == tmp_context->peer_cid); +} + +static bool compare_l2cap_local_or_remote_cid_item(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* tmp_data = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_l2cap_channel_cids_t* tmp_context = (btsnoop_l2cap_channel_cids_t*)context; + + return (tmp_data->cids.local_cid == tmp_context->local_cid) || (tmp_data->cids.peer_cid == tmp_context->peer_cid); +} + +static bool compare_l2cap_local_cid_item(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* tmp_data = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_l2cap_channel_cids_t* tmp_context = (btsnoop_l2cap_channel_cids_t*)context; + + return (tmp_data->cids.local_cid == tmp_context->local_cid); +} + +static int handle_hci_command(uint8_t* pkt, uint32_t pkt_size) +{ + // TODO: handle_hci_command + return 0; +} + +static btsnoop_filter_flag_t handle_rfcomm_data(uint8_t* pkt, uint32_t pkt_size) +{ + // TODO: handle_rfcomm_data + return BTSNOOP_FILTER_UNFILTER; +} + +static bool check_channel_need_filtered(btsnoop_filter_acl_info_t* acl_info, uint16_t psm, uint16_t scid, uint8_t is_receive) +{ + assert(acl_info); + switch (psm) { + case BTSNOOP_PSM_AVDTP: + if ((acl_info->avdtp_signal_ch.peer_cid != L2CAP_NULL_IDENTIFIER_CID) || (acl_info->avdtp_signal_ch.local_cid != L2CAP_NULL_IDENTIFIER_CID)) + return true; + + if (is_receive) + acl_info->avdtp_signal_ch.peer_cid = scid; + else + acl_info->avdtp_signal_ch.local_cid = scid; + + break; + default: + break; + } + return false; +} + +static void handle_l2cap_connection_request(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + assert(acl_info); + uint16_t psm, scid; + btsnoop_filter_flag_t filter_flag = BTSNOOP_FILTER_MAX; + btsnoop_filter_l2cap_channel_info_t* data = NULL; + + psm = GET_L2CAP_CONNECTION_REQ_COMMAND_PSM(pkt); + + switch (psm) { + case BTSNOOP_PSM_RFCOMM: + filter_flag = handle_rfcomm_data(pkt, pkt_size); + break; + case BTSNOOP_PSM_AVDTP: + filter_flag = BTSNOOP_FILTER_A2DP_AUDIO; + break; + case BTSNOOP_PSM_AVCTP_BROWSING: + filter_flag = BTSNOOP_FILTER_AVCTP_BROWSING; + break; + case BTSNOOP_PSM_ATT: + filter_flag = BTSNOOP_FILTER_ATT; + break; + default: + break; + } + + if (!(g_snoop_filter.filter_items & (1ULL << filter_flag))) { + return; + } + + scid = GET_L2CAP_CONNECTION_REQ_COMMAND_SCID(pkt); + + if (!check_channel_need_filtered(acl_info, psm, scid, is_receive)) { + return; + } + + if (is_receive) { + data = malloc_filter_cid_item(L2CAP_NULL_IDENTIFIER_CID, scid, psm, BTSNOOP_L2CAP_STATE_CONNECTING); + } else { + data = malloc_filter_cid_item(scid, L2CAP_NULL_IDENTIFIER_CID, psm, BTSNOOP_L2CAP_STATE_CONNECTING); + } + + if (NULL == data) { + BT_LOGE("malloc filter cid item failed!"); + return; + } + + bt_list_add_tail(acl_info->filter_cids, data); +} + +static void handle_acl_info_connection_response(btsnoop_filter_acl_info_t* acl_info, uint16_t local_cid, uint16_t peer_cid) +{ + assert(acl_info); + if (acl_info->avdtp_signal_ch.local_cid == local_cid) { + acl_info->avdtp_signal_ch.peer_cid = peer_cid; + } else if (acl_info->avdtp_signal_ch.peer_cid == peer_cid) { + acl_info->avdtp_signal_ch.local_cid = local_cid; + } +} + +static void handle_l2cap_connection_response(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + assert(acl_info); + uint16_t status, local_cid, peer_cid; + btsnoop_filter_l2cap_channel_info_t* channel_info = NULL; + btsnoop_l2cap_channel_cids_t response_cids_info = { 0 }; + + GET_L2CAP_RSP_DERIVE_CIDS(pkt, is_receive, local_cid, peer_cid, GET_L2CAP_CONNECTION_RSP_COMMAND_SCID, GET_L2CAP_CONNECTION_RSP_COMMAND_DCID); + + if (is_receive) { + response_cids_info.local_cid = local_cid; + } else { + response_cids_info.peer_cid = peer_cid; + } + + status = GET_L2CAP_CONNECTION_RSP_COMMAND_STATUS(pkt); + + switch (status) { + case BTSNOOP_L2CAP_RSP_STATUS_SUCCESSFUL: + handle_acl_info_connection_response(acl_info, local_cid, peer_cid); + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_and_remote_cid_item, &response_cids_info); + if (NULL == channel_info) + return; + + if (is_receive) { + channel_info->cids.peer_cid = peer_cid; + } else { + channel_info->cids.local_cid = local_cid; + } + + channel_info->state = BTSNOOP_L2CAP_STATE_CONNECTED; + break; + case BTSNOOP_L2CAP_RSP_STATUS_PENDING: + break; + default: + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_and_remote_cid_item, &response_cids_info); + if (NULL == channel_info) + return; + + bt_list_remove(acl_info->filter_cids, channel_info); + break; + } +} + +static void handle_acl_info_disconnection_response(btsnoop_filter_acl_info_t* acl_info, uint16_t local_cid, uint16_t peer_cid) +{ + assert(acl_info); + if ((acl_info->avdtp_signal_ch.local_cid == local_cid) && (acl_info->avdtp_signal_ch.peer_cid == peer_cid)) { + acl_info->avdtp_signal_ch.peer_cid = L2CAP_NULL_IDENTIFIER_CID; + acl_info->avdtp_signal_ch.local_cid = L2CAP_NULL_IDENTIFIER_CID; + } +} + +static void handle_l2cap_disconnection_response(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + assert(acl_info); + uint16_t local_cid, peer_cid; + btsnoop_filter_l2cap_channel_info_t* channel_info = NULL; + btsnoop_l2cap_channel_cids_t response_cids_info = { 0 }; + + GET_L2CAP_RSP_DERIVE_CIDS(pkt, is_receive, local_cid, peer_cid, GET_L2CAP_DISCONNECTION_RSP_COMMAND_SCID, GET_L2CAP_DISCONNECTION_RSP_COMMAND_DCID); + response_cids_info.local_cid = local_cid; + response_cids_info.peer_cid = peer_cid; + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_or_remote_cid_item, &response_cids_info); + + handle_acl_info_disconnection_response(acl_info, local_cid, peer_cid); + bt_list_remove(acl_info->filter_cids, channel_info); +} + +static void handle_l2cap_signaling_channel_data(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + assert(acl_info); + uint8_t command_code = GET_L2CAP_COMMAND_CODE(pkt); + + switch (command_code) { + case BTSNOOP_L2CAP_CODE_CONNECTION_REQUEST: + handle_l2cap_connection_request(acl_info, is_receive, pkt, pkt_size); + break; + case BTSNOOP_L2CAP_CODE_CONNECTION_RESPONSE: + handle_l2cap_connection_response(acl_info, is_receive, pkt, pkt_size); + break; + case BTSNOOP_L2CAP_CODE_DISCONNECTION_RESPONSE: + handle_l2cap_disconnection_response(acl_info, is_receive, pkt, pkt_size); + break; + default: + break; + } +} + +static bool handle_acl_data(uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + btsnoop_l2cap_channel_cids_t acl_cids; + uint16_t connection_handle; + btsnoop_filter_acl_info_t* acl_info; + uint8_t* l2cap_packet; + uint32_t l2cap_pkt_size; + uint16_t acl_cid; + uint8_t pb_flag; + + l2cap_packet = GET_L2CAP_PACKET_DATA(pkt); + l2cap_pkt_size = GET_L2CAP_PACKET_PLATLOAD_SIZE(pkt_size); + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_ACL_DATA(pkt); + acl_info = bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle); + + if (NULL == acl_info) { + BT_LOGE("The acl connection information does not exist."); + return false; + } + + pb_flag = GET_PB_FLAG_FROM_ACL_DATA(pkt); + if (pb_flag == BTSNOOP_ACL_PB_CONTINUING) { + acl_cid = acl_info->prev_acl_cid; + } else { + acl_cid = GET_L2CAP_CID(l2cap_packet); + acl_info->prev_acl_cid = acl_cid; + } + + if (acl_cid == L2CAP_SIGNALING_CHANNEL_CID || acl_cid == L2CAP_LE_SIGNALING_CHANNEL_CID) { + handle_l2cap_signaling_channel_data(acl_info, is_receive, l2cap_packet, l2cap_pkt_size); + } else { + acl_cids.local_cid = acl_cid; + if (NULL != bt_list_find(acl_info->filter_cids, compare_l2cap_local_cid_item, &acl_cids)) { + return true; + } + } + + return false; +} + +static int handle_sco_data(uint8_t* pkt, uint32_t pkt_size) +{ + return 1; +} + +static void handle_hci_event_connect_complete(uint8_t* pkt, uint32_t pkt_size) +{ + uint16_t connection_handle; + btsnoop_filter_acl_info_t* acl_info; + + if (GET_STATUS_FROM_CONNECT_COMPELTE_EVENT(pkt) != BTSNOOP_HCI_EVENT_STATUS_SUCCESS) { + return; + } + + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_CONNECT_COMPELTE_EVENT(pkt); + if (NULL == g_snoop_filter.acl_connection_list) { + BT_LOGE("The BTsnoop filter is not initialized."); + return; + } + + if (NULL != bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle)) { + BT_LOGE("The acl connection information already exists."); + return; + } + + if (NULL == (acl_info = malloc_acl_connection_item(connection_handle))) { + BT_LOGE("malloc acl connection item failed."); + return; + } + + bt_list_add_tail(g_snoop_filter.acl_connection_list, acl_info); + + return; +} + +static void handle_hci_event_disconnect_complete(uint8_t* pkt, uint32_t pkt_size) +{ + btsnoop_filter_acl_info_t* acl_info; + uint16_t connection_handle; + + if (GET_STATUS_FROM_DISCONNECT_COMPELTE_EVENT(pkt) != BTSNOOP_HCI_EVENT_STATUS_SUCCESS) { + return; + } + + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_DISCONNECT_COMPELTE_EVENT(pkt); + acl_info = (btsnoop_filter_acl_info_t*)bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle); + + if (NULL == acl_info) { + BT_LOGE("The acl connection information does not exist!"); + return; + } + + bt_list_remove(g_snoop_filter.acl_connection_list, acl_info); + + return; +} + +static bool handle_hci_event(uint8_t* pkt, uint32_t pkt_size) +{ + uint16_t event_code = GET_HCI_EVENT_CODE(pkt); + + switch (event_code) { + case BTSNOOP_CONNECT_COMPLETE: + handle_hci_event_connect_complete(pkt, pkt_size); + break; + case BTSNOOP_DISCONNECT_COMPLETE: + handle_hci_event_disconnect_complete(pkt, pkt_size); + break; + default: + break; + } + return 0; +} + +static int handle_iso_data(uint8_t* hci_pkt, uint32_t hci_pkt_size) +{ + return 1; +} + +bool filter_can_filter(uint8_t is_receive, uint8_t* hci_pkt, uint32_t hci_pkt_size) +{ + uint8_t* pkt_data; + uint32_t pkt_size; + uint8_t hci_type; + + hci_type = GET_HCI_TYPE(hci_pkt); + pkt_data = GET_HCI_H4_PLAYLOAD(hci_pkt); + pkt_size = GET_HCI_H4_PLAYLOAD_SIZE(hci_pkt_size); + + switch (hci_type) { + case BTSNOOP_HCI_TYPE_HCI_COMMAND: + return handle_hci_command(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_ACL_DATA: + return handle_acl_data(is_receive, pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_SCO_DATA: + return handle_sco_data(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_HCI_EVENT: + return handle_hci_event(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_ISO_DATA: + return handle_iso_data(pkt_data, pkt_size); + default: + return 0; + } + + return 0; +} + +int filter_init() +{ + g_snoop_filter.acl_connection_list = bt_list_new(free_acl_connection_item); + + if (NULL == g_snoop_filter.acl_connection_list) + return BT_STATUS_NOMEM; + + return BT_STATUS_SUCCESS; +} + +void filter_uninit() +{ + bt_list_free(g_snoop_filter.acl_connection_list); + g_snoop_filter.acl_connection_list = NULL; +} + +int filter_set_filter_flag(btsnoop_filter_flag_t filter_flag) +{ + if (filter_flag < 0 || filter_flag >= BTSNOOP_FILTER_MAX) { + return BT_STATUS_PARM_INVALID; + } + + g_snoop_filter.filter_items |= 1ULL << filter_flag; + + return BT_STATUS_SUCCESS; +} + +int filter_remove_filter_flag(btsnoop_filter_flag_t filter_flag) +{ + if (filter_flag < 0 || filter_flag >= BTSNOOP_FILTER_MAX) { + return BT_STATUS_PARM_INVALID; + } + + g_snoop_filter.filter_items &= ~(1ULL << filter_flag); + + return BT_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/service/utils/btsnoop_filter.h b/service/utils/btsnoop_filter.h new file mode 100644 index 00000000..5954f869 --- /dev/null +++ b/service/utils/btsnoop_filter.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SNOOP_FILTER_H__ +#define __BT_SNOOP_FILTER_H__ + +#include "bt_list.h" +#include "bt_status.h" +#include "btsnoop_log.h" + +#define BTSNOOP_HCI_EVENT_STATUS_SUCCESS 0x00 +typedef enum { + BTSNOOP_HCI_TYPE_HCI_COMMAND = 0x01, + BTSNOOP_HCI_TYPE_ACL_DATA, + BTSNOOP_HCI_TYPE_SCO_DATA, + BTSNOOP_HCI_TYPE_HCI_EVENT, + BTSNOOP_HCI_TYPE_ISO_DATA +} btsnoop_hci_t; + +typedef enum { + BTSNOOP_PSM_RFCOMM = 0x0003, + BTSNOOP_PSM_AVDTP = 0x0019, + BTSNOOP_PSM_AVCTP_BROWSING = 0x001B, + BTSNOOP_PSM_ATT = 0x001F, +} btsnoop_psms_t; + +typedef enum { + BTSNOOP_CONNECT_COMPLETE = 0x03, + BTSNOOP_DISCONNECT_COMPLETE = 0x05, +} btsnoop_hci_event_t; + +typedef enum { + BTSNOOP_ACL_PB_NON_FLUSHABLE = 0x00, + BTSNOOP_ACL_PB_CONTINUING = 0x01, + BTSNOOP_ACL_PB_FLUSHABLE = 0x02, +} btsnoop_acl_pb_flag_t; + +typedef enum { + BTSNOOP_L2CAP_CODE_CONNECTION_REQUEST = 0x02, + BTSNOOP_L2CAP_CODE_CONNECTION_RESPONSE = 0x03, + BTSNOOP_L2CAP_CODE_DISCONNECTION_RESPONSE = 0x07, +} btsnoop_l2cap_code_t; + +typedef enum { + BTSNOOP_L2CAP_STATE_DISCONECTED = 0x00, + BTSNOOP_L2CAP_STATE_CONNECTING = 0x01, + BTSNOOP_L2CAP_STATE_CONNECTED = 0x02 +} btsnoop_l2cap_state_t; + +typedef enum { + BTSNOOP_L2CAP_RSP_STATUS_SUCCESSFUL = 0x00, + BTSNOOP_L2CAP_RSP_STATUS_PENDING = 0x01, +} btsnoop_l2cap_rsp_status_t; + +typedef struct { + uint16_t local_cid; + uint16_t peer_cid; +} btsnoop_l2cap_channel_cids_t; + +typedef struct { + uint16_t connection_handle; + btsnoop_l2cap_channel_cids_t avdtp_signal_ch; + uint16_t prev_acl_cid; + bt_list_t* filter_cids; +} btsnoop_filter_acl_info_t; + +typedef struct { + btsnoop_l2cap_channel_cids_t cids; + uint16_t psm; + btsnoop_l2cap_state_t state; +} btsnoop_filter_l2cap_channel_info_t; + +int filter_init(); +void filter_uninit(); +bool filter_can_filter(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size); +int filter_set_filter_flag(btsnoop_filter_flag_t filter_flag); +int filter_remove_filter_flag(btsnoop_filter_flag_t filter_flag); +#endif //__SNOOP_FILTER_H__ \ No newline at end of file -- Gitee From 00992cc39b5a3b56dfd682985166ff8d18f92653 Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 6 Sep 2024 16:03:40 +0800 Subject: [PATCH 002/498] BTsnoop: Add a snoop log write mechanism. bug: v/42800 Signed-off-by: duqunbo --- Kconfig | 6 + service/utils/btsnoop_writer.c | 277 +++++++++++++++++++++++++++++++++ service/utils/btsnoop_writer.h | 24 +++ 3 files changed, 307 insertions(+) create mode 100644 service/utils/btsnoop_writer.c create mode 100644 service/utils/btsnoop_writer.h diff --git a/Kconfig b/Kconfig index f68f0307..9a746994 100644 --- a/Kconfig +++ b/Kconfig @@ -435,6 +435,12 @@ config LE_DLF_SUPPORT endif #BLUETOOTH_BLE_SUPPORT endif #BLUETOOTH_SERVICE +config MAX_SNOOP_FILE_SIZE + int "Maximum size of the snoop log file" + default 1048576 + help + Maximum size of the snoop log file + config BLUETOOTH_TOOLS bool "Enable bluetooth profile test tools" default n diff --git a/service/utils/btsnoop_writer.c b/service/utils/btsnoop_writer.c new file mode 100644 index 00000000..a70b6cb9 --- /dev/null +++ b/service/utils/btsnoop_writer.c @@ -0,0 +1,277 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bt_time.h" +#include "btsnoop_log.h" +#include "btsnoop_writer.h" + +#define CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/data/misc/bt/snoop" +#define SNOOP_FILE_NAME_MAX_LEN 256 +#define SNOOP_FILE_NAME_PREFIX "snoop_" +#define SNOOP_FILE_TYPE 1002 + +#define write_snoop_file(buf, buf_size) \ + do { \ + ret = write(g_using_file.snoop_fd, buf, buf_size); \ + if (ret < 0) \ + syslog(LOG_ERR, "snoop log header write ret:%d, error:%d\n", ret, errno); \ + else \ + g_using_file.size += ret; \ + \ + } while (0) + +typedef struct { + int snoop_fd; + size_t size; +} btsnoop_file_t; + +struct btsnoop_file_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +}; + +struct btsnoop_pkt_hdr { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags: 1 hci cmd */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ +}; + +static time_t time_base; +static uint32_t ms_base; +static btsnoop_file_t g_using_file = { 0 }; + +static void close_snoop_file(void) +{ + if (g_using_file.snoop_fd > 0) { + fsync(g_using_file.snoop_fd); + close(g_using_file.snoop_fd); + g_using_file.snoop_fd = 0; + g_using_file.size = 0; + } +} +static uint32_t get_current_time_ms(void) +{ + return (uint32_t)(get_os_timestamp_us() / 1000); +} + +static unsigned long byteswap_ulong(unsigned long val) +{ + unsigned char* byte_val = (unsigned char*)&val; + return ((unsigned long)byte_val[3] + ((unsigned long)byte_val[2] << 8) + ((unsigned long)byte_val[1] << 16) + ((unsigned long)byte_val[0] << 24)); +} + +static int get_latest_file_and_clean_others(char* out_latest_file, bool clean_files) +{ + DIR* dir; + struct dirent* entry; + struct stat file_stat; + char full_path[SNOOP_FILE_NAME_MAX_LEN]; + time_t latest_time = -1; + char latest_file[SNOOP_FILE_NAME_MAX_LEN] = ""; + + dir = opendir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH); + if (dir == NULL) { + syslog(LOG_ERR, "snoop folder open fail:%d", errno); + return BT_STATUS_FAIL; + } + + while ((entry = readdir(dir)) != NULL) { + snprintf(full_path, sizeof(full_path), "%s/%s", CONFIG_BLUETOOTH_SNOOP_LOG_PATH, entry->d_name); + if (strncmp(entry->d_name, SNOOP_FILE_NAME_PREFIX, strlen(SNOOP_FILE_NAME_PREFIX)) != 0) { + continue; + } + + if (stat(full_path, &file_stat) != 0) { + syslog(LOG_ERR, "get snoop file stat fail:%d", errno); + continue; + } + + if (!S_ISREG(file_stat.st_mode)) { + continue; + } + + if (clean_files && file_stat.st_mtime > latest_time) { + remove(latest_file); + } else if (clean_files && latest_time != -1) { + remove(full_path); + } + + if (latest_time == -1 || file_stat.st_mtime > latest_time) { + latest_time = file_stat.st_mtime; + strncpy(latest_file, full_path, SNOOP_FILE_NAME_MAX_LEN); + } + } + + if (NULL != out_latest_file) { + strncpy(out_latest_file, latest_file, SNOOP_FILE_NAME_MAX_LEN); + } + + closedir(dir); + + return BT_STATUS_SUCCESS; +} + +int btsnoop_create_new_file(void) +{ + struct btsnoop_file_hdr hdr; + time_t rawtime; + struct tm* info; + char ts_str[80]; + char file_name[128]; + int ret; + + close_snoop_file(); + + if (-1 == mkdir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH, 0777) && errno != EEXIST) { + syslog(LOG_ERR, "snoop folder create fail:%d", errno); + return -errno; + } + + time_base = time(NULL); + ms_base = get_current_time_ms(); + + time(&rawtime); + info = localtime(&rawtime); + if (info == NULL) { + return -1; + } + + snprintf(ts_str, sizeof(ts_str), "%d%02d%02d_%02d%02d%02d", + info->tm_year + 1900, + info->tm_mon + 1, + info->tm_mday, + info->tm_hour, + info->tm_min, + info->tm_sec); + snprintf(file_name, sizeof(file_name), CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/" SNOOP_FILE_NAME_PREFIX "%s_%" PRIu32 ".log", ts_str, ms_base); + + ret = open(file_name, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (ret < 0) { + g_using_file.snoop_fd = -1; + return ret; + } + + g_using_file.snoop_fd = ret; + g_using_file.size = 0; + syslog(LOG_ERR, "create fd:%d", ret); + + memcpy(hdr.id, "btsnoop", sizeof(hdr.id)); + hdr.version = byteswap_ulong(1); + hdr.type = byteswap_ulong(SNOOP_FILE_TYPE); + + write_snoop_file(&hdr, sizeof(hdr)); + return ret; +} + +int open_snoop_file(char* latest_file) +{ + int fd; + size_t file_size; + + fd = open(latest_file, O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + + if (fd < 0) { + syslog(LOG_ERR, "open snoop file fail:%d", errno); + return fd; + } + + file_size = lseek(fd, 0, SEEK_END); + + g_using_file.snoop_fd = fd; + g_using_file.size = file_size; + + return BT_STATUS_SUCCESS; +} + +int writer_init() +{ + DIR* dir; + char latest_file[SNOOP_FILE_NAME_MAX_LEN]; + + dir = opendir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH); + if (dir == NULL) { + closedir(dir); + return btsnoop_create_new_file(); + } + closedir(dir); + + get_latest_file_and_clean_others(latest_file, false); + + if (latest_file[0] == '\0') { + return btsnoop_create_new_file(); + } + + return open_snoop_file(latest_file); +} + +void writer_uninit() +{ + close_snoop_file(); +} + +int writer_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len) +{ + struct btsnoop_pkt_hdr pkt; + uint32_t ms; + int ret; + + if (g_using_file.snoop_fd < 0) + return BT_STATUS_FAIL; + + if (g_using_file.size + sizeof(pkt) + len > CONFIG_MAX_SNOOP_FILE_SIZE) { + get_latest_file_and_clean_others(NULL, true); + ret = btsnoop_create_new_file(); + if (ret < 0) + return ret; + } + + ms = get_current_time_ms() - ms_base; + const uint64_t sec = (uint32_t)(time_base + ms / 1000 + 8 * 3600); + const uint64_t usec = (uint32_t)((ms % 1000) * 1000); + uint64_t nts = (sec - (int64_t)946684800) * (int64_t)1000000 + usec; + uint32_t* d = (uint32_t*)&pkt.ts; + uint32_t* s = (uint32_t*)&nts; + + pkt.size = byteswap_ulong(len); + pkt.len = pkt.size; + pkt.drops = 0; + pkt.flags = (is_recieve) ? byteswap_ulong(0x01) : 0; + nts += (0x4A676000) + (((int64_t)0x00E03AB4) << 32); + d[0] = byteswap_ulong(s[1]); + d[1] = byteswap_ulong(s[0]); + + write_snoop_file(&pkt, sizeof(pkt)); + write_snoop_file(p, len); + + fsync(g_using_file.snoop_fd); + + return BT_STATUS_SUCCESS; +} diff --git a/service/utils/btsnoop_writer.h b/service/utils/btsnoop_writer.h new file mode 100644 index 00000000..f4c602d5 --- /dev/null +++ b/service/utils/btsnoop_writer.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SNOOP_WRITER_H__ +#define __BT_SNOOP_WRITER_H__ + +int writer_init(); +void writer_uninit(); +int writer_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len); + +#endif \ No newline at end of file -- Gitee From 93f8ae4ec045141a186005ff44a12072fbc3e68d Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 6 Sep 2024 16:06:45 +0800 Subject: [PATCH 003/498] BTsnoop: add btsnoop API bug: v/42717 Signed-off-by: duqunbo --- framework/api/bt_trace.c | 40 ++++ framework/include/bt_trace.h | 70 +++++++ framework/socket/bt_trace.c | 58 ++++++ service/ipc/socket/include/bt_message.h | 4 + service/ipc/socket/include/bt_message_log.h | 41 ++++ service/ipc/socket/include/bt_socket.h | 2 + service/ipc/socket/src/bt_socket_log.c | 87 +++++++++ service/ipc/socket/src/bt_socket_server.c | 2 + service/utils/btsnoop_log.c | 197 ++++---------------- service/utils/btsnoop_log.h | 13 +- service/utils/log.h | 3 +- service/utils/log_server.c | 17 +- tools/log.c | 61 +++++- 13 files changed, 422 insertions(+), 173 deletions(-) create mode 100644 framework/api/bt_trace.c create mode 100644 framework/include/bt_trace.h create mode 100644 framework/socket/bt_trace.c create mode 100644 service/ipc/socket/include/bt_message_log.h create mode 100644 service/ipc/socket/src/bt_socket_log.c diff --git a/framework/api/bt_trace.c b/framework/api/bt_trace.c new file mode 100644 index 00000000..c0f5869f --- /dev/null +++ b/framework/api/bt_trace.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_trace.h" +#include "bluetooth.h" +#include "bt_internal.h" +#include "utils/btsnoop_log.h" +#include "utils/log.h" + +void BTSYMBOLS(bluetooth_enable_btsnoop_log)(bt_instance_t* ins) +{ + bt_log_module_enable(LOG_ID_SNOOP, false); +} + +void BTSYMBOLS(bluetooth_disable_btsnoop_log)(bt_instance_t* ins) +{ + bt_log_module_disable(LOG_ID_SNOOP, false); +} + +void BTSYMBOLS(bluetooth_set_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + btsnoop_set_filter(filter_flag); +} + +void BTSYMBOLS(bluetooth_remove_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + btsnoop_remove_filter(filter_flag); +} diff --git a/framework/include/bt_trace.h b/framework/include/bt_trace.h new file mode 100644 index 00000000..e8f7e068 --- /dev/null +++ b/framework/include/bt_trace.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LOG_API_H__ +#define __BT_LOG_API_H__ +#ifdef __cplusplus +extern "C" { +#endif +#include "bluetooth.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +typedef enum { + BTSNOOP_FILTER_A2DP_AUDIO, + BTSNOOP_FILTER_AVCTP_BROWSING, + BTSNOOP_FILTER_ATT, + BTSNOOP_FILTER_SPP, + BTSNOOP_FILTER_MAX, + BTSNOOP_FILTER_UNFILTER, +} btsnoop_filter_flag_t; + +/** + * @brief Enable bluetooth btsnoop log + * + * @param ins - bluetooth client instance. + */ +void BTSYMBOLS(bluetooth_enable_btsnoop_log)(bt_instance_t* ins); + +/** + * @brief Disable bluetooth btsnoop log + * + * @param ins - bluetooth client instance. + */ +void BTSYMBOLS(bluetooth_disable_btsnoop_log)(bt_instance_t* ins); + +/** + * @brief Set a filter flag in the btsnoop log + * + * @param ins - bluetooth client instance. + * @param filter_flag - the flag bit for filtering specified data in the btsnoop log. + */ +void BTSYMBOLS(bluetooth_set_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag); + +/** + * @brief Remove a filter flag in the btsnoop log + * + * @param ins - bluetooth client instance. + * @param filter_flag - the flag bit for filtering specified data in the btsnoop log. + */ +void BTSYMBOLS(bluetooth_remove_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_LOG_API_H__ */ diff --git a/framework/socket/bt_trace.c b/framework/socket/bt_trace.c new file mode 100644 index 00000000..44d7958a --- /dev/null +++ b/framework/socket/bt_trace.c @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_trace.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "utils/btsnoop_log.h" + +void bluetooth_enable_btsnoop_log(bt_instance_t* ins) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_ENABLE); +} + +void bluetooth_disable_btsnoop_log(bt_instance_t* ins) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_DISABLE); +} + +void bluetooth_set_btsnoop_filter(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + packet.log_pl._bt_log_set_flag.filter_flag = filter_flag; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_SET_FILTER); +} + +void bluetooth_remove_btsnoop_filter(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + packet.log_pl._bt_log_remove_flag.filter_flag = filter_flag; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_REMOVE_FILTER); +} \ No newline at end of file diff --git a/service/ipc/socket/include/bt_message.h b/service/ipc/socket/include/bt_message.h index dda6cdae..8483a032 100644 --- a/service/ipc/socket/include/bt_message.h +++ b/service/ipc/socket/include/bt_message.h @@ -38,6 +38,7 @@ extern "C" { #include "bt_message_hfp_hf.h" #include "bt_message_hid_device.h" #include "bt_message_l2cap.h" +#include "bt_message_log.h" #include "bt_message_manager.h" #include "bt_message_pan.h" #include "bt_message_scan.h" @@ -61,6 +62,7 @@ typedef enum { #include "bt_message_hfp_hf.h" #include "bt_message_hid_device.h" #include "bt_message_l2cap.h" +#include "bt_message_log.h" #include "bt_message_manager.h" #include "bt_message_pan.h" #include "bt_message_scan.h" @@ -161,6 +163,8 @@ typedef struct bt_message_l2cap_t l2cap_pl; bt_message_l2cap_callbacks_t l2cap_cb; + + bt_message_log_t log_pl; }; } bt_message_packet_t; #pragma pack() diff --git a/service/ipc/socket/include/bt_message_log.h b/service/ipc/socket/include/bt_message_log.h new file mode 100644 index 00000000..2a4572b2 --- /dev/null +++ b/service/ipc/socket/include/bt_message_log.h @@ -0,0 +1,41 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_LOG_MESSAGE_START, + BT_LOG_ENABLE, + BT_LOG_DISABLE, + BT_LOG_SET_FILTER, + BT_LOG_REMOVE_FILTER, + BT_LOG_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_LOG_CALLBACK_START, + BT_LOG_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_LOG_H__ +#define _BT_MESSAGE_LOG_H__ + + typedef union { + struct { + uint32_t filter_flag; + } _bt_log_set_flag, + _bt_log_remove_flag; +} bt_message_log_t; + +#endif /* _BT_MESSAGE_LOG_H__ */ \ No newline at end of file diff --git a/service/ipc/socket/include/bt_socket.h b/service/ipc/socket/include/bt_socket.h index b2b2cedf..e6930c6b 100644 --- a/service/ipc/socket/include/bt_socket.h +++ b/service/ipc/socket/include/bt_socket.h @@ -189,6 +189,8 @@ void bt_socket_server_l2cap_process(service_poll_t* poll, int bt_socket_client_l2cap_callback(service_poll_t* poll, int fd, bt_instance_t* ins, bt_message_packet_t* packet); +void bt_socket_server_log_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); #ifdef __cplusplus } #endif diff --git a/service/ipc/socket/src/bt_socket_log.c b/service/ipc/socket/src/bt_socket_log.c new file mode 100644 index 00000000..ce052720 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_log.c @@ -0,0 +1,87 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bt_internal.h" + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "bt_trace.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_log_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_LOG_ENABLE: { + BTSYMBOLS(bluetooth_enable_btsnoop_log) + (ins); + break; + } + case BT_LOG_DISABLE: { + BTSYMBOLS(bluetooth_disable_btsnoop_log) + (ins); + break; + } + case BT_LOG_SET_FILTER: { + BTSYMBOLS(bluetooth_set_btsnoop_filter) + (ins, packet->log_pl._bt_log_set_flag.filter_flag); + break; + } + case BT_LOG_REMOVE_FILTER: { + BTSYMBOLS(bluetooth_remove_btsnoop_filter) + (ins, packet->log_pl._bt_log_remove_flag.filter_flag); + break; + } + default: + break; + } +} + +#endif \ No newline at end of file diff --git a/service/ipc/socket/src/bt_socket_server.c b/service/ipc/socket/src/bt_socket_server.c index 46b8a9a8..ddcb746c 100644 --- a/service/ipc/socket/src/bt_socket_server.c +++ b/service/ipc/socket/src/bt_socket_server.c @@ -212,6 +212,8 @@ static int bt_socket_server_receive(service_poll_t* poll, int fd, void* userdata } else if (packet->code > BT_L2CAP_MESSAGE_START && packet->code < BT_L2CAP_MESSAGE_END) { bt_socket_server_l2cap_process(poll, fd, ins, packet); #endif + } else if (packet.code > BT_LOG_MESSAGE_START && packet.code < BT_LOG_MESSAGE_END) { + bt_socket_server_log_process(poll, fd, ins, &packet); } else { BT_LOGE("%s, Unhandled message:%" PRIu32, __func__, packet->code); assert(0); diff --git a/service/utils/btsnoop_log.c b/service/utils/btsnoop_log.c index cd21db0b..abc2cfb8 100644 --- a/service/utils/btsnoop_log.c +++ b/service/utils/btsnoop_log.c @@ -13,174 +13,71 @@ * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ - -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include "bt_time.h" +#include "btsnoop_filter.h" #include "btsnoop_log.h" +#include "btsnoop_writer.h" +#include "log.h" #ifndef CONFIG_BLUETOOTH_SNOOP_LOG #define CONFIG_BLUETOOTH_SNOOP_LOG 1 #endif -#ifdef CONFIG_BLUETOOTH_SNOOP_LOG -#define CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/data/misc/bt/snoop" -#endif - -struct btsnoop_file_hdr { - uint8_t id[8]; /* Identification Pattern */ - uint32_t version; /* Version Number = 1 */ - uint32_t type; /* Datalink Type */ -}; - -struct btsnoop_pkt_hdr { - uint32_t size; /* Original Length */ - uint32_t len; /* Included Length */ - uint32_t flags; /* Packet Flags: 1 hci cmd */ - uint32_t drops; /* Cumulative Drops */ - uint64_t ts; /* Timestamp microseconds */ - // uint8_t data[0]; /* Packet Data */ -}; - -static int snoop_fd = -1; -static time_t time_base; -static uint32_t ms_base; static pthread_mutex_t snoop_lock = PTHREAD_MUTEX_INITIALIZER; +static bool snoop_enable = false; -static uint32_t get_current_time_ms(void) -{ - return (uint32_t)(get_os_timestamp_us() / 1000); -} - -static unsigned long byteswap_ulong(unsigned long val) -{ - unsigned char* byte_val = (unsigned char*)&val; - return ((unsigned long)byte_val[3] + ((unsigned long)byte_val[2] << 8) + ((unsigned long)byte_val[1] << 16) + ((unsigned long)byte_val[0] << 24)); -} - -static void btsnoop_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len) -{ - struct btsnoop_pkt_hdr pkt; - uint32_t ms; - int ret; - - if (snoop_fd < 0) - return; - - ms = get_current_time_ms() - ms_base; - const uint64_t sec = (uint32_t)(time_base + ms / 1000 + 8 * 3600); - const uint64_t usec = (uint32_t)((ms % 1000) * 1000); - uint64_t nts = (sec - (int64_t)946684800) * (int64_t)1000000 + usec; - uint32_t* d = (uint32_t*)&pkt.ts; - uint32_t* s = (uint32_t*)&nts; - - pkt.size = byteswap_ulong(len); - pkt.len = pkt.size; - pkt.drops = 0; - pkt.flags = (is_recieve) ? byteswap_ulong(0x01) : 0; - nts += (0x4A676000) + (((int64_t)0x00E03AB4) << 32); - d[0] = byteswap_ulong(s[1]); - d[1] = byteswap_ulong(s[0]); - ret = write(snoop_fd, &pkt, sizeof(pkt)); - if (ret < 0) - syslog(LOG_ERR, "snoop log header write ret:%d, error:%d\n", ret, errno); - - ret = write(snoop_fd, p, len); - if (ret < 0) - syslog(LOG_ERR, "snoop log data write ret:%d, error:%d\n", ret, errno); - - fsync(snoop_fd); -} - -int btsnoop_create_new_file(void) +void btsnoop_log_capture(uint8_t recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size) { - struct btsnoop_file_hdr hdr; - time_t rawtime; - struct tm* info; - char ts_str[80]; - char file_name[128]; - int ret; - +#if CONFIG_BLUETOOTH_SNOOP_LOG pthread_mutex_lock(&snoop_lock); - if (snoop_fd > 0) { - close(snoop_fd); - snoop_fd = -1; - } - - if (-1 == mkdir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH, 0777) && errno != EEXIST) { - syslog(LOG_ERR, "snoop folder create fail:%d", errno); - pthread_mutex_unlock(&snoop_lock); - return -errno; - } - time_base = time(NULL); - ms_base = get_current_time_ms(); - - time(&rawtime); - info = localtime(&rawtime); - if (info == NULL) { + if (!snoop_enable) { pthread_mutex_unlock(&snoop_lock); - return -1; + return; } - - snprintf(ts_str, sizeof(ts_str), "%d%02d%02d_%02d%02d%02d", - info->tm_year + 1900, - info->tm_mon + 1, - info->tm_mday, - info->tm_hour, - info->tm_min, - info->tm_sec); - snprintf(file_name, sizeof(file_name), CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/snoop_%s_%" PRIu32 ".log", ts_str, ms_base); - - ret = open(file_name, O_RDWR | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); - if (ret < 0) { - snoop_fd = -1; + if (filter_can_filter(recieve, hci_pkt, hci_pkt_size)) { pthread_mutex_unlock(&snoop_lock); - return ret; + return; } - snoop_fd = ret; - memcpy(hdr.id, "btsnoop", 8); - hdr.version = byteswap_ulong(1); - hdr.type = byteswap_ulong(1002); - ret = write(snoop_fd, &hdr, sizeof(hdr)); pthread_mutex_unlock(&snoop_lock); - return ret; + writer_write_log(recieve, hci_pkt, hci_pkt_size); +#endif } -void btsnoop_close_file(void) +int btsnoop_log_init(void) { - pthread_mutex_lock(&snoop_lock); - if (snoop_fd > 0) { - fsync(snoop_fd); - close(snoop_fd); - snoop_fd = -1; - } - pthread_mutex_unlock(&snoop_lock); + if (pthread_mutex_init(&snoop_lock, NULL) < 0) + return BT_STATUS_FAIL; + return BT_STATUS_SUCCESS; +} + +void btsnoop_log_uninit(void) +{ + pthread_mutex_destroy(&snoop_lock); } -bt_status_t btsnoop_log_open(void) +int btsnoop_log_enable(void) { #if CONFIG_BLUETOOTH_SNOOP_LOG - if (pthread_mutex_init(&snoop_lock, NULL) < 0) + pthread_mutex_lock(&snoop_lock); + if (writer_init() < 0) { + syslog(LOG_ERR, "%s fail", __func__); + pthread_mutex_unlock(&snoop_lock); return BT_STATUS_FAIL; + } - if (btsnoop_create_new_file() < 0) { + if (filter_init() < 0) { syslog(LOG_ERR, "%s fail", __func__); + pthread_mutex_unlock(&snoop_lock); return BT_STATUS_FAIL; } + snoop_enable = true; + + pthread_mutex_unlock(&snoop_lock); return BT_STATUS_SUCCESS; #else syslog(LOG_WARNING, "%s\n", "CONFIG_BLUETOOTH_SNOOP_LOG not set"); @@ -188,35 +85,23 @@ bt_status_t btsnoop_log_open(void) #endif } -void btsnoop_log_capture(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size) +void btsnoop_log_disable(void) { #if CONFIG_BLUETOOTH_SNOOP_LOG pthread_mutex_lock(&snoop_lock); - btsnoop_write_log(is_recieve, hci_pkt, hci_pkt_size); + snoop_enable = false; + filter_uninit(); + writer_uninit(); pthread_mutex_unlock(&snoop_lock); #endif } -void btsnoop_log_close(void) -{ -#if CONFIG_BLUETOOTH_SNOOP_LOG - btsnoop_close_file(); - pthread_mutex_destroy(&snoop_lock); -#endif -} - -void btsnoop_log_filter_add_l2cid(uint16_t cid) +int btsnoop_set_filter(btsnoop_filter_flag_t filter_flag) { + return filter_set_filter_flag(filter_flag); } -void btsnoop_log_filter_remove_l2cid(uint16_t cid) +int btsnoop_remove_filter(btsnoop_filter_flag_t filter_flag) { -} - -void btsnoop_log_filter_add_packet_type(uint16_t packet_type) -{ -} - -void btsnoop_log_filter_remove_packet_type(uint16_t packet_type) -{ -} + return filter_remove_filter_flag(filter_flag); +} \ No newline at end of file diff --git a/service/utils/btsnoop_log.h b/service/utils/btsnoop_log.h index 4f25851e..dd181a45 100644 --- a/service/utils/btsnoop_log.h +++ b/service/utils/btsnoop_log.h @@ -17,13 +17,18 @@ #ifndef __BT_SNOOP_LOG_H__ #define __BT_SNOOP_LOG_H__ +#include "bt_list.h" #include "bt_status.h" +#include "bt_trace.h" + #include -int btsnoop_create_new_file(void); -void btsnoop_close_file(void); -bt_status_t btsnoop_log_open(void); void btsnoop_log_capture(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size); -void btsnoop_log_close(void); +int btsnoop_log_init(void); +void btsnoop_log_uninit(void); +int btsnoop_log_enable(void); +void btsnoop_log_disable(void); +int btsnoop_set_filter(btsnoop_filter_flag_t filter_flag); +int btsnoop_remove_filter(btsnoop_filter_flag_t filter_flag); #endif //__BT_SNOOP_LOG_H__ \ No newline at end of file diff --git a/service/utils/log.h b/service/utils/log.h index 17d04f15..2cf714fa 100644 --- a/service/utils/log.h +++ b/service/utils/log.h @@ -98,5 +98,6 @@ extern bool bt_log_print_check(uint8_t level); void bt_log_server_init(void); void bt_log_server_cleanup(void); - +void bt_log_module_enable(int id, bool changed); +void bt_log_module_disable(int id, bool changed); #endif diff --git a/service/utils/log_server.c b/service/utils/log_server.c index c1f45aa5..e37c9996 100644 --- a/service/utils/log_server.c +++ b/service/utils/log_server.c @@ -107,7 +107,7 @@ static int stack_log_setup(void) return 0; } -static void bt_log_module_enable(int id, bool changed) +void bt_log_module_enable(int id, bool changed) { const char* property = NULL; syslog(LOG_DEBUG, "%s, id %d\n", __func__, id); @@ -116,7 +116,7 @@ static void bt_log_module_enable(int id, bool changed) if (g_logger.snoop_enable) return; - if (btsnoop_log_open() != BT_STATUS_SUCCESS) { + if (btsnoop_log_enable() != BT_STATUS_SUCCESS) { syslog(LOG_ERR, "%s\n", "enable snoop log fail"); return; } @@ -149,7 +149,7 @@ static void bt_log_module_enable(int id, bool changed) syslog(LOG_INFO, "%s enabled\n", log_id_str(id)); } -static void bt_log_module_disable(int id, bool changed) +void bt_log_module_disable(int id, bool changed) { const char* property = NULL; syslog(LOG_DEBUG, "%s id %d\n", __func__, id); @@ -158,7 +158,7 @@ static void bt_log_module_disable(int id, bool changed) if (!g_logger.snoop_enable) return; - btsnoop_log_close(); + btsnoop_log_disable(); g_logger.snoop_enable = 0; property = PERSIST_BT_SNOOP_LOG_EN; break; @@ -252,9 +252,12 @@ void bt_log_server_init(void) stack_log_setup(); /** snoop log init */ + if (btsnoop_log_init() != BT_STATUS_SUCCESS) + syslog(LOG_ERR, "init snoop log fail\n"); + g_logger.snoop_enable = property_get_int32(PERSIST_BT_SNOOP_LOG_EN, 0); if (g_logger.snoop_enable) { - if (btsnoop_log_open() != BT_STATUS_SUCCESS) + if (btsnoop_log_enable() != BT_STATUS_SUCCESS) syslog(LOG_ERR, "%s\n", "enable snoop log fail"); } @@ -288,7 +291,9 @@ void bt_log_server_cleanup(void) /** snoop log deinit */ if (g_logger.snoop_enable) - btsnoop_log_close(); + btsnoop_log_disable(); + + btsnoop_log_uninit(); /** stack log deinit */ if (g_logger.stack_enable) diff --git a/tools/log.c b/tools/log.c index 3d776d2a..21dfe099 100644 --- a/tools/log.c +++ b/tools/log.c @@ -39,10 +39,14 @@ #include "bt_debug.h" #include "bt_tools.h" +#include "bt_trace.h" +#include "utils/btsnoop_log.h" static int enable_cmd(void* handle, int argc, char* argv[]); static int disable_cmd(void* handle, int argc, char* argv[]); static int mask_cmd(void* handle, int argc, char* argv[]); +static int filter_cmd(void* handle, int argc, char* argv[]); +static int unfilter_cmd(void* handle, int argc, char* argv[]); static int unmask_cmd(void* handle, int argc, char* argv[]); static int level_cmd(void* handle, int argc, char* argv[]); @@ -64,7 +68,14 @@ static bt_command_t g_log_tables[] = { "\t\t\t AVDTP: 11\n" "\t\t\t AVRCP: 12\n" "\t\t\t HFP: 14\n" }, - { "unmask", unmask_cmd, 0, "\"Disable Stack Profile & Protocol Log \"" }, + { "unmask", unmask_cmd, 0, "\"Filter hci data \"" }, + { "filter", filter_cmd, 0, "\"Filter hci data written to btsnoop \"" + "\t\t\tData type Enum:\n" + "\t\t\tAudio data: 0\n" + "\t\t\tAVCTP browsing data: 1\n" + "\t\t\tATT data: 2\n" + "\t\t\tSPP data: 3\n" }, + { "unfilter", unfilter_cmd, 0, "\"Disable Stack Profile & Protocol Log \"" }, { "level", level_cmd, 0, "\"Set framework log level, (OFF:0,ERR:3,WARN:4,INFO:6,DBG:7)\"" }, }; @@ -85,15 +96,17 @@ static void property_change_commit(int bit) #endif } -static int log_control(char* id, int enable) +static int log_control(void* handle, char* id, int enable) { #ifdef CONFIG_KVDB if (strncmp(id, "stack", strlen("stack")) == 0) { property_set_int32("persist.bluetooth.log.stack_enable", enable); property_change_commit(1); } else if (strncmp(id, "snoop", strlen("snoop")) == 0) { - property_set_int32("persist.bluetooth.log.snoop_enable", enable); - property_change_commit(3); + if (enable) + bluetooth_enable_btsnoop_log(handle); + else + bluetooth_disable_btsnoop_log(handle); } else return CMD_INVALID_PARAM; @@ -108,7 +121,7 @@ static int enable_cmd(void* handle, int argc, char* argv[]) if (argc < 1) return CMD_PARAM_NOT_ENOUGH; - return log_control(argv[0], 1); + return log_control(handle, argv[0], 1); } static int disable_cmd(void* handle, int argc, char* argv[]) @@ -116,7 +129,7 @@ static int disable_cmd(void* handle, int argc, char* argv[]) if (argc < 1) return CMD_PARAM_NOT_ENOUGH; - return log_control(argv[0], 0); + return log_control(handle, argv[0], 0); } static int mask_cmd(void* handle, int argc, char* argv[]) @@ -144,6 +157,42 @@ static int mask_cmd(void* handle, int argc, char* argv[]) #endif } +static int filter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_set_btsnoop_filter(handle, bit); + } + } + + return CMD_OK; +} + +static int unfilter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_remove_btsnoop_filter(handle, bit); + } + } + + return CMD_OK; +} + static int unmask_cmd(void* handle, int argc, char* argv[]) { if (argc < 1) -- Gitee From cdc100c370b96d8f0ea68d4b9351542f5086f79c Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 6 Dec 2024 12:05:02 +0800 Subject: [PATCH 004/498] BTsnoop: Fixed cherry-pick conflicts. bug: v/42717 Signed-off-by: duqunbo --- service/ipc/socket/src/bt_socket_server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/ipc/socket/src/bt_socket_server.c b/service/ipc/socket/src/bt_socket_server.c index ddcb746c..f6196e43 100644 --- a/service/ipc/socket/src/bt_socket_server.c +++ b/service/ipc/socket/src/bt_socket_server.c @@ -212,8 +212,8 @@ static int bt_socket_server_receive(service_poll_t* poll, int fd, void* userdata } else if (packet->code > BT_L2CAP_MESSAGE_START && packet->code < BT_L2CAP_MESSAGE_END) { bt_socket_server_l2cap_process(poll, fd, ins, packet); #endif - } else if (packet.code > BT_LOG_MESSAGE_START && packet.code < BT_LOG_MESSAGE_END) { - bt_socket_server_log_process(poll, fd, ins, &packet); + } else if (packet->code > BT_LOG_MESSAGE_START && packet->code < BT_LOG_MESSAGE_END) { + bt_socket_server_log_process(poll, fd, ins, packet); } else { BT_LOGE("%s, Unhandled message:%" PRIu32, __func__, packet->code); assert(0); -- Gitee From 37557b75603f603cf6d6f1cd9decc49d8c38c9eb Mon Sep 17 00:00:00 2001 From: duqunbo Date: Mon, 9 Dec 2024 17:56:24 +0800 Subject: [PATCH 005/498] BT Snoop: Add persist property to configure the location of the snoop log bug: v/42717 Signed-off-by: duqunbo --- service/utils/btsnoop_log.c | 4 ++- service/utils/btsnoop_log.h | 5 ++- service/utils/btsnoop_writer.c | 61 +++++++++++++++++++++++++--------- service/utils/btsnoop_writer.h | 1 + service/utils/log_server.c | 9 ++++- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/service/utils/btsnoop_log.c b/service/utils/btsnoop_log.c index abc2cfb8..3f6398fa 100644 --- a/service/utils/btsnoop_log.c +++ b/service/utils/btsnoop_log.c @@ -47,10 +47,12 @@ void btsnoop_log_capture(uint8_t recieve, uint8_t* hci_pkt, uint32_t hci_pkt_siz #endif } -int btsnoop_log_init(void) +int btsnoop_log_init(char* path) { if (pthread_mutex_init(&snoop_lock, NULL) < 0) return BT_STATUS_FAIL; + + set_snoop_file_path(path); return BT_STATUS_SUCCESS; } diff --git a/service/utils/btsnoop_log.h b/service/utils/btsnoop_log.h index dd181a45..a4847075 100644 --- a/service/utils/btsnoop_log.h +++ b/service/utils/btsnoop_log.h @@ -23,8 +23,11 @@ #include +#define CONFIG_BLUETOOTH_SNOOP_LOG_DEFAULT_PATH "/data/misc/bt/snoop" +#define SNOOP_PATH_MAX_LEN 255 + void btsnoop_log_capture(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size); -int btsnoop_log_init(void); +int btsnoop_log_init(char* path); void btsnoop_log_uninit(void); int btsnoop_log_enable(void); void btsnoop_log_disable(void); diff --git a/service/utils/btsnoop_writer.c b/service/utils/btsnoop_writer.c index a70b6cb9..b637cc6a 100644 --- a/service/utils/btsnoop_writer.c +++ b/service/utils/btsnoop_writer.c @@ -29,9 +29,14 @@ #include "btsnoop_log.h" #include "btsnoop_writer.h" -#define CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/data/misc/bt/snoop" -#define SNOOP_FILE_NAME_MAX_LEN 256 -#define SNOOP_FILE_NAME_PREFIX "snoop_" +#define SNOOP_FILE_NAME_PREFIX "/snoop_" +#define SNOOP_FILE_NAME_PREFIX_LEN 7 +#define SNOOP_FILE_NAME_DATE_LEN 80 +#define SNOOP_FILE_NAME_SUFFIX "%s_%" PRIu32 ".log" +#define SNOOP_FILE_NAME_SUFFIX_LEN (20 + SNOOP_FILE_NAME_DATE_LEN) +#define SNOOP_FILE_NAME SNOOP_FILE_NAME_PREFIX SNOOP_FILE_NAME_SUFFIX +#define SNOOP_FILE_NAME_LEN (SNOOP_FILE_NAME_PREFIX_LEN + SNOOP_FILE_NAME_SUFFIX_LEN) +#define SNOOP_FILE_FULL_NAME_MAX_LEN (SNOOP_FILE_NAME_LEN + SNOOP_PATH_MAX_LEN) #define SNOOP_FILE_TYPE 1002 #define write_snoop_file(buf, buf_size) \ @@ -66,6 +71,7 @@ struct btsnoop_pkt_hdr { static time_t time_base; static uint32_t ms_base; static btsnoop_file_t g_using_file = { 0 }; +static char g_snoop_file_path[SNOOP_PATH_MAX_LEN + 1]; static void close_snoop_file(void) { @@ -92,18 +98,31 @@ static int get_latest_file_and_clean_others(char* out_latest_file, bool clean_fi DIR* dir; struct dirent* entry; struct stat file_stat; - char full_path[SNOOP_FILE_NAME_MAX_LEN]; time_t latest_time = -1; - char latest_file[SNOOP_FILE_NAME_MAX_LEN] = ""; + char* full_path; + char* latest_file; - dir = opendir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH); + full_path = zalloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + if (full_path == NULL) { + return BT_STATUS_FAIL; + } + + latest_file = zalloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + if (latest_file == NULL) { + free(full_path); + return BT_STATUS_FAIL; + } + + dir = opendir(g_snoop_file_path); if (dir == NULL) { syslog(LOG_ERR, "snoop folder open fail:%d", errno); + free(latest_file); + free(full_path); return BT_STATUS_FAIL; } while ((entry = readdir(dir)) != NULL) { - snprintf(full_path, sizeof(full_path), "%s/%s", CONFIG_BLUETOOTH_SNOOP_LOG_PATH, entry->d_name); + snprintf(full_path, SNOOP_FILE_FULL_NAME_MAX_LEN, "%s/%s", g_snoop_file_path, entry->d_name); if (strncmp(entry->d_name, SNOOP_FILE_NAME_PREFIX, strlen(SNOOP_FILE_NAME_PREFIX)) != 0) { continue; } @@ -125,16 +144,18 @@ static int get_latest_file_and_clean_others(char* out_latest_file, bool clean_fi if (latest_time == -1 || file_stat.st_mtime > latest_time) { latest_time = file_stat.st_mtime; - strncpy(latest_file, full_path, SNOOP_FILE_NAME_MAX_LEN); + strlcpy(latest_file, full_path, SNOOP_FILE_FULL_NAME_MAX_LEN); } } if (NULL != out_latest_file) { - strncpy(out_latest_file, latest_file, SNOOP_FILE_NAME_MAX_LEN); + strlcpy(out_latest_file, latest_file, SNOOP_FILE_FULL_NAME_MAX_LEN); } closedir(dir); + free(latest_file); + free(full_path); return BT_STATUS_SUCCESS; } @@ -143,13 +164,13 @@ int btsnoop_create_new_file(void) struct btsnoop_file_hdr hdr; time_t rawtime; struct tm* info; - char ts_str[80]; - char file_name[128]; + char ts_str[SNOOP_FILE_NAME_DATE_LEN + 1]; int ret; + char* full_file_name; close_snoop_file(); - if (-1 == mkdir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH, 0777) && errno != EEXIST) { + if (-1 == mkdir(g_snoop_file_path, 0777) && errno != EEXIST) { syslog(LOG_ERR, "snoop folder create fail:%d", errno); return -errno; } @@ -170,10 +191,13 @@ int btsnoop_create_new_file(void) info->tm_hour, info->tm_min, info->tm_sec); - snprintf(file_name, sizeof(file_name), CONFIG_BLUETOOTH_SNOOP_LOG_PATH "/" SNOOP_FILE_NAME_PREFIX "%s_%" PRIu32 ".log", ts_str, ms_base); - ret = open(file_name, O_RDWR | O_CREAT | O_TRUNC, + full_file_name = malloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + snprintf(full_file_name, SNOOP_FILE_FULL_NAME_MAX_LEN, "%s" SNOOP_FILE_NAME, g_snoop_file_path, ts_str, ms_base); + ret = open(full_file_name, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + free(full_file_name); + if (ret < 0) { g_using_file.snoop_fd = -1; return ret; @@ -211,12 +235,17 @@ int open_snoop_file(char* latest_file) return BT_STATUS_SUCCESS; } +void set_snoop_file_path(char* path) +{ + strlcpy(g_snoop_file_path, path, SNOOP_PATH_MAX_LEN); +} + int writer_init() { DIR* dir; - char latest_file[SNOOP_FILE_NAME_MAX_LEN]; + char latest_file[SNOOP_FILE_FULL_NAME_MAX_LEN + 1] = ""; - dir = opendir(CONFIG_BLUETOOTH_SNOOP_LOG_PATH); + dir = opendir(g_snoop_file_path); if (dir == NULL) { closedir(dir); return btsnoop_create_new_file(); diff --git a/service/utils/btsnoop_writer.h b/service/utils/btsnoop_writer.h index f4c602d5..7c2d82a5 100644 --- a/service/utils/btsnoop_writer.h +++ b/service/utils/btsnoop_writer.h @@ -20,5 +20,6 @@ int writer_init(); void writer_uninit(); int writer_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len); +void set_snoop_file_path(char* path); #endif \ No newline at end of file diff --git a/service/utils/log_server.c b/service/utils/log_server.c index e37c9996..5117e533 100644 --- a/service/utils/log_server.c +++ b/service/utils/log_server.c @@ -41,6 +41,8 @@ enum { #define PERSIST_BT_STACK_LOG_EN "persist.bluetooth.log.stack_enable" #define PERSIST_BT_STACK_LOG_MASK "persist.bluetooth.log.stack_mask" #define PERSIST_BT_SNOOP_LOG_EN "persist.bluetooth.log.snoop_enable" +#define PERSIST_BT_SNOOP_FILE_PATH "persist.bluetooth.log.snoop_path" + // #define PERSIST_BT_SNOOP_LOG_CID_MASK "persist.bluetooth.log.snoop_cid_mask" // #define PERSIST_BT_SNOOP_LOG_PKT_MASK "persist.bluetooth.log.snoop_pkt_mask" @@ -241,6 +243,8 @@ static void property_monitor_cb(service_poll_t* poll, void bt_log_server_init(void) { #if defined(CONFIG_KVDB) && defined(__NuttX__) + char path[SNOOP_PATH_MAX_LEN]; + /** framework log init */ g_logger.framework_level = property_get_int32(PERSIST_BT_FRAMEWORK_LOG_LEVEL, DEFAULT_BT_LOG_LEVEL); @@ -251,8 +255,11 @@ void bt_log_server_init(void) if (g_logger.stack_enable) stack_log_setup(); + if (property_get_binary(PERSIST_BT_SNOOP_FILE_PATH, path, SNOOP_PATH_MAX_LEN) <= 0) { + strlcpy(path, CONFIG_BLUETOOTH_SNOOP_LOG_DEFAULT_PATH, SNOOP_PATH_MAX_LEN); + } /** snoop log init */ - if (btsnoop_log_init() != BT_STATUS_SUCCESS) + if (btsnoop_log_init(path) != BT_STATUS_SUCCESS) syslog(LOG_ERR, "init snoop log fail\n"); g_logger.snoop_enable = property_get_int32(PERSIST_BT_SNOOP_LOG_EN, 0); -- Gitee From 9e984687bcc1b555f0391e02047ea7405e48aa07 Mon Sep 17 00:00:00 2001 From: zhangyuan20 Date: Fri, 8 Nov 2024 20:54:07 +0800 Subject: [PATCH 006/498] [bugfix]Vela-Android:fix build error bug: v/46742 Signed-off-by: zhangyuan20 --- Android.bp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Android.bp b/Android.bp index 5bfdd543..d2fd128b 100644 --- a/Android.bp +++ b/Android.bp @@ -19,6 +19,7 @@ frameworkBluetooth_cc_library { srcs : [ "framework/common/*.c", "framework/socket/*.c", + "service/common/bt_time.c", "service/common/index_allocator.c", "service/ipc/socket/src/bt_socket_client.c", "service/ipc/socket/src/bt_socket_adapter.c", @@ -102,6 +103,7 @@ frameworkBluetooth_cc_binary { local_include_dirs : [ "framework/include", "service", + "service/common", "service/utils", ], -- Gitee From ea41c2be6a39964b5c097881e83cdf47189f8010 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 7 Jan 2025 23:08:26 +0800 Subject: [PATCH 007/498] HFP: hold +CIEV responses until current call list is updated. bug: v/52252 Rootcause: Android behavior requires AT+CLCC command only after +CIEV received, and shall not be updated when not requested. Signed-off-by: Zihao Gao --- .../profiles/hfp_hf/hfp_hf_state_machine.c | 24 ++++++++++++++++--- service/profiles/include/hfp_hf_service.h | 7 ++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index 3d038e17..cb00a91a 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -43,6 +43,15 @@ const static char voip_call_number[][HFP_PHONENUM_DIGITS_MAX] = { "10000001" }; +#define HFP_HF_REPORT_CIEV_AND_CACHE(_hfsm, _ciev) \ + do { \ + if (_hfsm->call_status.last_reported._ciev##_status != _hfsm->call_status._ciev##_status) { \ + BT_LOGD("%s, %s = %d", __func__, #_ciev, _hfsm->call_status._ciev##_status); \ + hf_service_notify_##_ciev(&hfsm->addr, _hfsm->call_status._ciev##_status); \ + hfsm->call_status.last_reported._ciev##_status = hfsm->call_status._ciev##_status; \ + } \ + } while (0) + typedef struct _hf_state_machine { state_machine_t sm; bt_address_t addr; @@ -337,6 +346,13 @@ static void update_current_calls(hf_state_machine_t* hfsm, hfp_current_call_t* c bt_list_add_tail(hfsm->update_calls, call); } +static void hf_service_fake_ciev(hf_state_machine_t* hfsm) +{ + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, call); + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, callsetup); + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, callheld); +} + static void query_current_calls_final(hf_state_machine_t* hfsm) { BT_LOGD("Query current call final"); @@ -344,6 +360,8 @@ static void query_current_calls_final(hf_state_machine_t* hfsm) bt_list_t* clist = hfsm->current_calls; bt_list_t* ulist = hfsm->update_calls; + hf_service_fake_ciev(hfsm); + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { hfp_current_call_t* ccall = bt_list_node(cnode); hfp_current_call_t* ucall = bt_list_find(ulist, call_index_cmp, &ccall->index); @@ -659,21 +677,21 @@ static void update_call_status(state_machine_t* sm, uint32_t event, uint32_t sta hfsm->call_status.call_timestamp_us = current_timestamp_us; BT_LOGD("%s: call:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.call_status, hfsm->call_status.call_timestamp_us); - hf_service_notify_call(&hfsm->addr, status); + // hf_service_notify_call(&hfsm->addr, status); break; case HF_STACK_EVENT_CALLSETUP: hfsm->call_status.callsetup_status = (hfp_callsetup_t)status; hfsm->call_status.callsetup_timestamp_us = current_timestamp_us; BT_LOGD("%s: callsetup:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.callsetup_status, hfsm->call_status.callsetup_timestamp_us); - hf_service_notify_callsetup(&hfsm->addr, status); + // hf_service_notify_callsetup(&hfsm->addr, status); break; case HF_STACK_EVENT_CALLHELD: hfsm->call_status.callheld_status = (hfp_callheld_t)status; hfsm->call_status.callheld_timestamp_us = current_timestamp_us; BT_LOGD("%s: callheld:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.callheld_status, hfsm->call_status.callsetup_timestamp_us); - hf_service_notify_callheld(&hfsm->addr, status); + // hf_service_notify_callheld(&hfsm->addr, status); break; default: break; diff --git a/service/profiles/include/hfp_hf_service.h b/service/profiles/include/hfp_hf_service.h index 5284fc57..fc4e7586 100644 --- a/service/profiles/include/hfp_hf_service.h +++ b/service/profiles/include/hfp_hf_service.h @@ -33,6 +33,12 @@ typedef enum { HFP_HF_STATE_AUDIO_CONNECTED } hfp_hf_state_t; +typedef struct { + hfp_call_t call_status; + hfp_callsetup_t callsetup_status; + hfp_callheld_t callheld_status; +} hfp_hf_call_cache_t; + typedef struct { hfp_call_t call_status; uint64_t call_timestamp_us; @@ -41,6 +47,7 @@ typedef struct { hfp_callheld_t callheld_status; uint64_t callheld_timestamp_us; uint64_t dialing_timestamp_us; + hfp_hf_call_cache_t last_reported; #ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER uint64_t webchat_flag_timestamp_us; #endif -- Gitee From 662cbd6027c69867ac75f446ccf3e9006bb033ed Mon Sep 17 00:00:00 2001 From: duqunbo Date: Tue, 11 Feb 2025 21:08:43 +0800 Subject: [PATCH 008/498] Revert "HFP: Adds a function to delete list node resources" bug: v/53473 This reverts commit 34efc6ea903f48edaac7d39dc4d2a3f269ae16b5. Reason for revert: The release function causes repeated releases Signed-off-by: duqunbo --- service/profiles/hfp_hf/hfp_hf_state_machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index cb00a91a..c70eb873 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -1502,7 +1502,7 @@ hf_state_machine_t* hf_state_machine_new(bt_address_t* addr, void* context) hfsm->service = context; hfsm->codec = HFP_CODEC_CVSD; memcpy(&hfsm->addr, addr, sizeof(bt_address_t)); - hfsm->update_calls = bt_list_new(hf_call_delete); + hfsm->update_calls = bt_list_new(NULL); hfsm->current_calls = bt_list_new(hf_call_delete); hfsm->media_volume = INVALID_MEDIA_VOLUME; list_initialize(&hfsm->pending_actions); -- Gitee From 05fea2668f41a78b71c31ba579e8ae6037ed8491 Mon Sep 17 00:00:00 2001 From: jialu Date: Wed, 12 Feb 2025 17:15:36 +0800 Subject: [PATCH 009/498] Bluetooth: Fix memory leak. bug: v/51671 Rootcause: When open transport fails, the memory of transport is not released, leading to memory leakage. --- service/profiles/audio_interface/audio_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/profiles/audio_interface/audio_control.c b/service/profiles/audio_interface/audio_control.c index 2adaa954..eec7c4fd 100644 --- a/service/profiles/audio_interface/audio_control.c +++ b/service/profiles/audio_interface/audio_control.c @@ -251,7 +251,7 @@ void audio_ctrl_cleanup(uint8_t profile_id) case PROFILE_HFP_AG: case PROFILE_HFP_HF: if (g_audio_ctrl_transport) { - audio_transport_close(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL); + audio_transport_close(g_audio_ctrl_transport, AUDIO_TRANS_CH_ID_ALL); } break; default: -- Gitee From 0a374b482c680a365b32a907b1d5744c21c09acc Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 28 Nov 2024 15:02:18 +0800 Subject: [PATCH 010/498] Android-vela: profile-add bt_socket disconnect handling bug: v/53721 destory profile resource as bt_socket disconnect. Signed-off-by: liuxiang18 --- .../profiles/a2dp/sink/a2dp_sink_service.c | 10 +++++++++ .../a2dp/source/a2dp_source_service.c | 10 +++++++++ .../avrcp/target/avrcp_target_service.c | 20 ++++++++++++++++- service/profiles/hfp_ag/hfp_ag_service.c | 10 +++++++++ service/profiles/hfp_hf/hfp_hf_service.c | 10 +++++++++ service/profiles/hid/hid_device_service.c | 21 +++++++++++++++++- service/profiles/pan/panu_service.c | 21 +++++++++++++++++- service/profiles/spp/spp_service.c | 22 ++++++++++++++++++- 8 files changed, 120 insertions(+), 4 deletions(-) diff --git a/service/profiles/a2dp/sink/a2dp_sink_service.c b/service/profiles/a2dp/sink/a2dp_sink_service.c index 50ddb495..d7adc0a1 100644 --- a/service/profiles/a2dp/sink/a2dp_sink_service.c +++ b/service/profiles/a2dp/sink/a2dp_sink_service.c @@ -56,6 +56,7 @@ static a2dp_sink_global_t g_a2dp_sink = { 0 }; static void sink_startup(void* data); static void sink_shutdown(void* data); +static bool a2dp_sink_unregister_callbacks(void** remote, void* cookie); static void set_active_peer(bt_address_t* bd_addr) { @@ -301,7 +302,16 @@ static void a2dp_sink_process_msg(profile_msg_t* msg) case PROFILE_EVT_A2DP_OFFLOADING: g_a2dp_sink.offloading = msg->data.valuebool; break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + if (ins->a2dp_sink_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + a2dp_sink_unregister_callbacks(NULL, ins->a2dp_sink_cookie); + ins->a2dp_sink_cookie = NULL; + } + break; + } default: break; } diff --git a/service/profiles/a2dp/source/a2dp_source_service.c b/service/profiles/a2dp/source/a2dp_source_service.c index 666d6c58..106702f5 100644 --- a/service/profiles/a2dp/source/a2dp_source_service.c +++ b/service/profiles/a2dp/source/a2dp_source_service.c @@ -60,6 +60,7 @@ void do_in_a2dp_service(a2dp_event_t* a2dp_event); static void source_shutdown(void* data); static void source_startup(void* data); +static bool a2dp_source_unregister_callbacks(void** remote, void* cookie); static void set_active_peer(bt_address_t* bd_addr, uint16_t acl_hdl) { @@ -465,7 +466,16 @@ static void a2dp_source_process_msg(profile_msg_t* msg) case PROFILE_EVT_A2DP_OFFLOADING: g_a2dp_source.offloading = msg->data.valuebool; break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + if (ins->a2dp_source_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + a2dp_source_unregister_callbacks(NULL, ins->a2dp_source_cookie); + ins->a2dp_source_cookie = NULL; + } + break; + } default: break; } diff --git a/service/profiles/avrcp/target/avrcp_target_service.c b/service/profiles/avrcp/target/avrcp_target_service.c index 461e5c29..7ab16974 100644 --- a/service/profiles/avrcp/target/avrcp_target_service.c +++ b/service/profiles/avrcp/target/avrcp_target_service.c @@ -650,6 +650,24 @@ static int avrcp_target_dump(void) return 0; } +static void avrcp_target_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->avrcp_target_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + avrcp_target_unregister_callbacks((void**)&ins, ins->avrcp_target_cookie); + ins->avrcp_target_cookie = NULL; + } + break; + } + default: + break; + } +} + static int avrcp_target_get_state(void) { return 1; @@ -664,7 +682,7 @@ static const profile_service_t avrcp_target_service = { .init = avrcp_target_init, .startup = avrcp_target_startup, .shutdown = avrcp_target_shutdown, - .process_msg = NULL, + .process_msg = avrcp_target_process_msg, .get_state = avrcp_target_get_state, .get_profile_interface = get_avrcp_target_profile_interface, .cleanup = avrcp_target_cleanup, diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c index f988af26..8e962bde 100644 --- a/service/profiles/hfp_ag/hfp_ag_service.c +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -76,6 +76,7 @@ typedef struct ****************************************************************************/ bt_status_t hfp_ag_send_message(hfp_ag_msg_t* msg); static void hfp_ag_process_message(void* data); +static bool hfp_ag_unregister_callbacks(void** remote, void* cookie); /**************************************************************************** * Private Data @@ -439,7 +440,16 @@ static void hfp_ag_process_msg(profile_msg_t* msg) case PROFILE_EVT_HFP_OFFLOADING: g_ag_service.offloading = msg->data.valuebool; break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + if (ins->hfp_ag_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hfp_ag_unregister_callbacks((void**)&ins, ins->hfp_ag_cookie); + ins->hfp_ag_cookie = NULL; + } + break; + } default: break; } diff --git a/service/profiles/hfp_hf/hfp_hf_service.c b/service/profiles/hfp_hf/hfp_hf_service.c index 689ea798..adfe9ad1 100644 --- a/service/profiles/hfp_hf/hfp_hf_service.c +++ b/service/profiles/hfp_hf/hfp_hf_service.c @@ -73,6 +73,7 @@ typedef struct ****************************************************************************/ bt_status_t hfp_hf_send_message(hfp_hf_msg_t* msg); static hf_state_machine_t* get_state_machine(bt_address_t* addr); +static bool hfp_hf_unregister_callbacks(void** remote, void* cookie); /**************************************************************************** * Private Data @@ -414,7 +415,16 @@ static void hfp_hf_process_msg(profile_msg_t* msg) case PROFILE_EVT_HFP_OFFLOADING: g_hfp_service.offloading = msg->data.valuebool; break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + if (ins->hfp_hf_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hfp_hf_unregister_callbacks((void**)&ins, ins->hfp_hf_cookie); + ins->hfp_hf_cookie = NULL; + } + break; + } default: break; } diff --git a/service/profiles/hid/hid_device_service.c b/service/profiles/hid/hid_device_service.c index b78b9332..87b9f16e 100644 --- a/service/profiles/hid/hid_device_service.c +++ b/service/profiles/hid/hid_device_service.c @@ -129,6 +129,7 @@ typedef struct { * Private Data ****************************************************************************/ static hid_device_handle_t g_hidd_handle = { .started = false }; +static bool hid_device_unregister_callbacks(void** remote, void* cookie); /**************************************************************************** * Private Functions @@ -291,6 +292,24 @@ static void hid_device_cleanup(void) pthread_mutex_destroy(&g_hidd_handle.hid_lock); } +static void hid_device_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->hidd_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hid_device_unregister_callbacks((void**)&ins, ins->hidd_cookie); + ins->hidd_cookie = NULL; + } + break; + } + default: + break; + } +} + static int hid_device_get_state(void) { return 1; @@ -652,7 +671,7 @@ static const profile_service_t hid_device_service = { .init = hid_device_init, .startup = hid_device_startup, .shutdown = hid_device_shutdown, - .process_msg = NULL, + .process_msg = hid_device_process_msg, .get_state = hid_device_get_state, .get_profile_interface = get_device_profile_interface, .cleanup = hid_device_cleanup, diff --git a/service/profiles/pan/panu_service.c b/service/profiles/pan/panu_service.c index 9afdce14..e512754a 100644 --- a/service/profiles/pan/panu_service.c +++ b/service/profiles/pan/panu_service.c @@ -98,6 +98,7 @@ static uint8_t* pan_read_buf = NULL; static pan_conn_t* pan_find_conn(bt_address_t* addr); static void pan_conn_close(pan_conn_t* conn); +static bool pan_unregister_callbacks(void** remote, void* cookie); static uint8_t pan_conns(void) { @@ -551,6 +552,24 @@ static bt_status_t pan_shutdown(profile_on_shutdown_t cb) return BT_STATUS_SUCCESS; } +static void pan_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->panu_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + pan_unregister_callbacks((void**)&ins, ins->panu_cookie); + ins->panu_cookie = NULL; + } + break; + } + default: + break; + } +} + static int pan_get_state(void) { return 1; @@ -653,7 +672,7 @@ static const profile_service_t pan_service = { .init = pan_init, .startup = pan_startup, .shutdown = pan_shutdown, - .process_msg = NULL, + .process_msg = pan_process_msg, .get_state = pan_get_state, .get_profile_interface = get_pan_profile_interface, .cleanup = pan_cleanup, diff --git a/service/profiles/spp/spp_service.c b/service/profiles/spp/spp_service.c index 0c9741bb..02297e5d 100644 --- a/service/profiles/spp/spp_service.c +++ b/service/profiles/spp/spp_service.c @@ -154,6 +154,7 @@ typedef struct { static int do_spp_write(spp_device_t* device, uint8_t* buffer, uint16_t length); static void spp_server_cleanup_devices(spp_server_t* server); static void spp_proxy_connection_callback(euv_pipe_t* handle, int status, void* user_data); +static bt_status_t spp_unregister_app(void** remote, void* handle); /**************************************************************************** * Private Data @@ -947,6 +948,25 @@ static bt_status_t spp_shutdown(profile_on_shutdown_t cb) return BT_STATUS_SUCCESS; } +static void spp_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->spp_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + void* handle = NULL; + spp_unregister_app(&handle, ins->spp_cookie); + ins->spp_cookie = NULL; + } + break; + } + default: + break; + } +} + static int spp_get_state(void) { return 1; @@ -1327,7 +1347,7 @@ static const profile_service_t spp_service = { .init = spp_init, .startup = spp_startup, .shutdown = spp_shutdown, - .process_msg = NULL, + .process_msg = spp_process_msg, .get_state = spp_get_state, .get_profile_interface = get_spp_profile_interface, .cleanup = spp_cleanup, -- Gitee From 125564b0d6fbe51a19783bc399baf681c6aa9c35 Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 28 Nov 2024 15:02:18 +0800 Subject: [PATCH 011/498] Android-vela: bt_client-add bt_socket disconnect handling bug: v/53721 Fix lost BT connection between android and vela as vela/Android reboot. Signed-off-by: liuxiang18 --- framework/include/bluetooth.h | 3 +++ service/ipc/socket/src/bt_socket_client.c | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/framework/include/bluetooth.h b/framework/include/bluetooth.h index 87ca634c..fb2cb55b 100644 --- a/framework/include/bluetooth.h +++ b/framework/include/bluetooth.h @@ -416,6 +416,8 @@ typedef bool (*bt_allocator_t)(void** data, uint32_t size); typedef void (*bt_hci_event_callback_t)(bt_hci_event_t* hci_event, void* context); +typedef void (*bt_ipc_disconnected_cb_t)(void* cookie, void* user_data, int status); + typedef struct bt_instance { uint32_t app_id; #ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC @@ -449,6 +451,7 @@ typedef struct bt_instance { void* context; uv_mutex_t lock; + bt_ipc_disconnected_cb_t disconnected; callbacks_list_t* adapter_callbacks; callbacks_list_t* a2dp_sink_callbacks; callbacks_list_t* a2dp_source_callbacks; diff --git a/service/ipc/socket/src/bt_socket_client.c b/service/ipc/socket/src/bt_socket_client.c index 2f4af017..4052f97d 100644 --- a/service/ipc/socket/src/bt_socket_client.c +++ b/service/ipc/socket/src/bt_socket_client.c @@ -270,6 +270,7 @@ static void bt_socket_client_handle_event(uv_poll_t* poll, int status, int event { uv_os_fd_t fd; int ret; + bt_instance_t* ins = poll->data; ret = uv_fileno((uv_handle_t*)poll, &fd); if (ret) { @@ -279,6 +280,10 @@ static void bt_socket_client_handle_event(uv_poll_t* poll, int status, int event if (status != 0 || events & UV_DISCONNECT) { thread_loop_remove_poll(poll); + if (ins && ins->disconnected) { + BT_LOGE("%s socket disconnect, status = %d, events = %d", __func__, status, events); + ins->disconnected((void*)ins, NULL, status); + } } else if (events & UV_READABLE) { ret = bt_socket_client_receive(poll, fd, poll->data); if (ret != BT_STATUS_SUCCESS) @@ -437,7 +442,7 @@ int bt_socket_client_init(bt_instance_t* ins, int family, } } while (retry--); - poll = thread_loop_poll_fd(ins->client_loop, ins->peer_fd, UV_READABLE, + poll = thread_loop_poll_fd(ins->client_loop, ins->peer_fd, UV_READABLE | UV_DISCONNECT, bt_socket_client_handle_event, ins); if (poll == NULL) { bt_socket_client_deinit(ins); -- Gitee From 9caaedd2453e11a71165b9c2fb3707237e51692e Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 28 Nov 2024 15:02:18 +0800 Subject: [PATCH 012/498] Android-vela: bt_server-add bt_socket disconnect handling bug: v/53721 Fix lost BT connection between android and vela as vela/Android reboot. Signed-off-by: liuxiang18 --- service/ipc/socket/src/bt_socket_server.c | 23 ++++++++++++++++++++++- service/profiles/service_manager.h | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/service/ipc/socket/src/bt_socket_server.c b/service/ipc/socket/src/bt_socket_server.c index f6196e43..9deb2f3a 100644 --- a/service/ipc/socket/src/bt_socket_server.c +++ b/service/ipc/socket/src/bt_socket_server.c @@ -50,6 +50,7 @@ #include "bt_socket.h" #include "callbacks_list.h" #include "service_loop.h" +#include "service_manager.h" #include "utils/log.h" @@ -223,11 +224,30 @@ static int bt_socket_server_receive(service_poll_t* poll, int fd, void* userdata return bt_socket_server_send(ins, packet, packet->code); } +static void bt_unregister_callbacks(bt_instance_t* ins) +{ + bt_message_packet_t packet; + profile_msg_t msg; + + // unreigster adapter callback + packet.code = BT_ADAPTER_UNREGISTER_CALLBACK; + bt_socket_server_adapter_process(ins->poll, ins->peer_fd, ins, &packet); + + // unregsiter profile callback + msg.event = PROFILE_EVT_REMOTE_DETACH; + msg.data.data = ins; + service_manager_processmsg(&msg); + + // TODO: unregister other Profile callback(GATT, LE ADV, LE SCAN) +} + static void bt_socket_server_ins_release(bt_instance_t* ins) { struct list_node* node; struct list_node* tmp; + bt_unregister_callbacks(ins); + if (ins->poll) service_loop_remove_poll(ins->poll); @@ -261,6 +281,7 @@ static void bt_socket_server_handle_event(service_poll_t* poll, } if (revent & POLL_ERROR || revent & POLL_DISCONNECT) { + BT_LOGE("%s, revent = %d", __func__, revent); bt_socket_server_ins_release(ins); } else if (revent & POLL_READABLE) { ret = bt_socket_server_receive(poll, fd, userdata); @@ -309,7 +330,7 @@ static void bt_socket_server_callback(service_poll_t* poll, list_initialize(&remote_ins->msg_queue); remote_ins->peer_fd = fd; - remote_ins->poll = service_loop_poll_fd(fd, POLL_READABLE, + remote_ins->poll = service_loop_poll_fd(fd, POLL_READABLE | POLL_DISCONNECT, bt_socket_server_handle_event, remote_ins); if (!remote_ins->poll) goto error; diff --git a/service/profiles/service_manager.h b/service/profiles/service_manager.h index 0a51ba1e..7e5c94be 100644 --- a/service/profiles/service_manager.h +++ b/service/profiles/service_manager.h @@ -35,6 +35,7 @@ typedef enum { PROFILE_EVT_A2DP_OFFLOADING = 1, PROFILE_EVT_HFP_OFFLOADING, PROFILE_EVT_LEA_OFFLOADING, + PROFILE_EVT_REMOTE_DETACH, } profile_event_t; typedef struct -- Gitee From ef834274d47973225abd670937047ba49ee479bc Mon Sep 17 00:00:00 2001 From: duqunbo Date: Thu, 13 Feb 2025 20:13:10 +0800 Subject: [PATCH 013/498] A2DP sink: Switch stream.ready to false when A2DP sink is disconnected. bug: v/53677 rootcause:When the A2DP sink disconnects, it does not send the A2DP_CTRL_CMD_STOP command. As a result, the bluetoothd daemon does not receive the STOP action, and the stream.ready flag is not reset. If stream.ready remains unreset, during the next connection, audio data may start being sent to the media layer before the media component issues the A2DP_CTRL_CMD_START command. This could lead to unintended audio streaming behavior. Signed-off-by: duqunbo --- service/profiles/a2dp/sink/a2dp_sink_audio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/service/profiles/a2dp/sink/a2dp_sink_audio.c b/service/profiles/a2dp/sink/a2dp_sink_audio.c index 340d5a45..0585e3cb 100644 --- a/service/profiles/a2dp/sink/a2dp_sink_audio.c +++ b/service/profiles/a2dp/sink/a2dp_sink_audio.c @@ -230,6 +230,11 @@ bool a2dp_sink_on_connection_changed(bool connected) if (connected) { a2dp_control_update_audio_config(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, 1); } else { + /* When disconnected, the Media framework should send AUDIO_CTRL_CMD_STOP to notify us that + it is no longer ready to receive data via audio data channel. However, due to a logic issue, + the media currently cannot send AUDIO_CTRL_CMD_STOP upon disconnection. As a workaround, + we are proactively setting sink_stream.ready to false in such scenario. */ + sink_stream.ready = false; a2dp_sink_on_stopped(); a2dp_control_update_audio_config(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, 0); } -- Gitee From 103b1c69972e87382028479f61adc83df5c42a08 Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 14 Feb 2025 15:21:50 +0800 Subject: [PATCH 014/498] Perform the property_commit operation in uv_work. bug: v/53341 rootcasue:Synchronizing the property_commit operation will block the bluetoothd thread, so it needs to be performed asynchronously. Signed-off-by: duqunbo --- service/common/storage_property.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/service/common/storage_property.c b/service/common/storage_property.c index 0eef786e..1989fd61 100644 --- a/service/common/storage_property.c +++ b/service/common/storage_property.c @@ -62,6 +62,11 @@ typedef struct { uint8_t value[0]; } bt_property_value_t; +static void storage_commit(service_work_t* work, void* userdata) +{ + property_commit(); +} + static int storage_set_key(const char* key, void* data, size_t length) { int ret; @@ -71,7 +76,7 @@ static int storage_set_key(const char* key, void* data, size_t length) BT_LOGE("key %s set error!", key); return ret; } - property_commit(); + service_loop_work(NULL, storage_commit, NULL); return ret; } @@ -192,7 +197,7 @@ int bt_storage_save_adapter_info(adapter_storage_t* adapter) property_set_int32_oneway(BT_KVDB_ADAPTERINFO_IOCAP, adapter->io_capability); property_set_int32_oneway(BT_KVDB_ADAPTERINFO_SCAN, adapter->scan_mode); property_set_int32_oneway(BT_KVDB_ADAPTERINFO_BOND, adapter->bondable); - property_commit(); + service_loop_work(NULL, storage_commit, NULL); return 0; } @@ -351,9 +356,10 @@ static void bt_storage_delete(char* key, uint16_t items, char* prop_name) addr = (bt_address_t*)prop_value->value + i; GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); property_delete(prop_name); - property_commit(); } + free(prop_value); + service_loop_work(NULL, storage_commit, NULL); } int bt_storage_save_bonded_device(remote_device_properties_t* remote, uint16_t size) -- Gitee From 87cc93041b0ace33d944577ee0e1dd0a2f084c2b Mon Sep 17 00:00:00 2001 From: duqunbo Date: Fri, 14 Feb 2025 11:44:49 +0800 Subject: [PATCH 015/498] storage: Delete the commit action after the db is modified. bug: v/53723 rootcause: The uv_db performs the commit operation automatically. Signed-off-by: duqunbo --- service/common/storage.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/service/common/storage.c b/service/common/storage.c index ffec8584..28f2a701 100644 --- a/service/common/storage.c +++ b/service/common/storage.c @@ -48,8 +48,6 @@ static uv_db_t* storage_handle = NULL; static void key_set_callback(int status, const char* key, uv_buf_t value, void* cookie) { free(value.base); - if (status == 0) - uv_db_commit(storage_handle); } static void key_get_callback(int status, const char* key, uv_buf_t value, void* cookie) -- Gitee From d764d2828fde611f34a2b4d77823c013b143e75f Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Wed, 4 Dec 2024 13:54:03 +0800 Subject: [PATCH 016/498] Android-Vela: Adapter SPP in Android 14 bug: v/53727 open flag CONFIG_RPMSG_UART if defined(Android14) Signed-off-by: liuxiang18 --- framework/include/bt_config.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/framework/include/bt_config.h b/framework/include/bt_config.h index 80b1b40e..ebd0fb32 100644 --- a/framework/include/bt_config.h +++ b/framework/include/bt_config.h @@ -61,7 +61,7 @@ #define CONFIG_BLUETOOTH_LEA_SOURCE_DATA_PATH "lea_source_data" #define CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN 80 #define CONFIG_BLUETOOTH_SCO_CTRL_PATH "sco_ctrl" -//#define CONFIG_BLUETOOTH_L2CAP 1 +// #define CONFIG_BLUETOOTH_L2CAP 1 #define CONFIG_BLUETOOTH_L2CAP_OUTGOING_MTU 2048 #define CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS 8 #define CONFIG_BLUETOOTH_GATTS_MAX_ATTRIBUTE_NUM 10 @@ -78,14 +78,14 @@ #define CONFIG_BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS 8 #define CONFIG_BLUETOOTH_MAX_REGISTER_NUM 4 #define CONFIG_BLUETOOTH_FRAMEWORK 1 -//#define CONFIG_BLUETOOTH_FRAMEWORK_LOCAL 1 +// #define CONFIG_BLUETOOTH_FRAMEWORK_LOCAL 1 #define CONFIG_BLUETOOTH_FRAMEWORK_SOCKET_IPC 1 #define CONFIG_BLUETOOTH_SOCKET_PORT 6001 #define CONFIG_BLUETOOTH_SERVICE 1 -//#define CONFIG_BLUETOOTH_SERVER 1 +// #define CONFIG_BLUETOOTH_SERVER 1 #define CONFIG_BLUETOOTH_SERVER_NAME "bluetoothd" #define CONFIG_BLUETOOTH_IPC_JOIN_LOOP 1 -//#define CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL 7 +// #define CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL 7 #define CONFIG_BLUETOOTH_SERVICE_HCI_UART_NAME "/dev/ttyHCI0" #define CONFIG_BLUETOOTH_STACK_BREDR_BLUELET 1 #define CONFIG_BLUETOOTH_STACK_LE_BLUELET 1 @@ -98,12 +98,14 @@ #define CONFIG_NET_RPMSG 1 // Socket: via IPv4 -//#define CONFIG_NET_IPv4 1 -//#define CONFIG_BLUETOOTH_NET_IPv4 1 +// #define CONFIG_NET_IPv4 1 +// #define CONFIG_BLUETOOTH_NET_IPv4 1 #define CONFIG_INADDR_LOOPBACK 0x0A000202 +// SPP +#define CONFIG_RPMSG_UART 1 // SPP via RPMsg UART "/dev/ttyDROID" -//#define CONFIG_RPMSG_UART 1 +// #define CONFIG_RPMSG_UART 1 // SPP via RPMsg socket/pipe #define CONFIG_BLUETOOTH_SPP_RPMSG_NET 1 @@ -112,8 +114,6 @@ #if defined(ANDROID_12) // Socket: RPMsg #define CONFIG_BLUETOOTH_RPMSG_CPUNAME "ap" -// SPP -#define CONFIG_RPMSG_UART 1 /********************* O61 Project Only *********************/ #elif defined(ANDROID_14) @@ -130,7 +130,7 @@ #endif -//############################################################################ +// ############################################################################ #define CONFIG_y 1 #define CONFIG_m 2 -- Gitee From bc9bdd5b97ad6b686dc21e9995662905b5ff37d3 Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Thu, 31 Oct 2024 10:50:54 +0800 Subject: [PATCH 017/498] ipc: socket ipc support async call bug: v/46316 1.add api bt_socket_client_send_with_reply Signed-off-by: fangzhenwei --- framework/include/bluetooth.h | 27 +- framework/socket/bluetooth.c | 48 +++ service/ipc/socket/include/bt_message.h | 1 + service/ipc/socket/include/bt_socket.h | 26 +- service/ipc/socket/src/bt_socket_adapter.c | 9 +- service/ipc/socket/src/bt_socket_client.c | 331 ++++++++++++++++++--- 6 files changed, 396 insertions(+), 46 deletions(-) diff --git a/framework/include/bluetooth.h b/framework/include/bluetooth.h index fb2cb55b..4781a681 100644 --- a/framework/include/bluetooth.h +++ b/framework/include/bluetooth.h @@ -412,11 +412,14 @@ enum { BLUETOOTH_USER, }; +typedef struct bt_instance bt_instance_t; + typedef bool (*bt_allocator_t)(void** data, uint32_t size); typedef void (*bt_hci_event_callback_t)(bt_hci_event_t* hci_event, void* context); -typedef void (*bt_ipc_disconnected_cb_t)(void* cookie, void* user_data, int status); +typedef void (*bt_ipc_connected_cb_t)(bt_instance_t* ins, void* user_data); +typedef void (*bt_ipc_disconnected_cb_t)(bt_instance_t* ins, void* user_data, int status); typedef struct bt_instance { uint32_t app_id; @@ -478,6 +481,7 @@ typedef struct bt_instance { bt_list_t* gattc_remote_list; bt_list_t* gatts_remote_list; + void* priv; #endif } bt_instance_t; @@ -538,6 +542,27 @@ bt_status_t BTSYMBOLS(bluetooth_stop_service)(bt_instance_t* ins, enum profile_i bool BTSYMBOLS(bluetooth_set_external_uv)(bt_instance_t* ins, uv_loop_t* ext_loop); +/* + Async instance +*/ + +/** + * @brief Create bluetooth async client instance + * + * @param loop uv_loop_t + * @param connected client instance connected callback + * @param disconnected client instance disconnected callback + * @return bt_instance_t* - ins on success, NULL on failure. + */ +bt_instance_t* bluetooth_create_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data); + +/** + * @brief Delete bluetooth async client instance + * + * @param ins bt_instance_t* + */ +void bluetooth_delete_async_instance(bt_instance_t* ins); + #ifdef __cplusplus } #endif diff --git a/framework/socket/bluetooth.c b/framework/socket/bluetooth.c index 363f4a95..e685e1b1 100644 --- a/framework/socket/bluetooth.c +++ b/framework/socket/bluetooth.c @@ -78,6 +78,45 @@ bt_instance_t* bluetooth_create_instance(void) return ins; } +bt_instance_t* bluetooth_create_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data) +{ + bt_status_t status; + bt_instance_t* ins; + + ins = zalloc(sizeof(bt_instance_t)); + if (ins == NULL) { + return NULL; + } + +#if defined(CONFIG_BLUETOOTH_SERVER) + status = bt_socket_async_client_init(ins, loop, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#elif defined(CONFIG_NET_RPMSG) + status = bt_socket_async_client_init(ins, loop, AF_RPMSG, + "bluetooth", CONFIG_BLUETOOTH_RPMSG_CPUNAME, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#elif defined(CONFIG_NET_IPv4) + status = bt_socket_async_client_init(ins, loop, AF_INET, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#else + status = bt_socket_async_client_init(ins, loop, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#endif + + if (status != BT_STATUS_SUCCESS) { + free(ins); + return NULL; + } + + status = manager_create_instance(PTR2INT(uint64_t) ins, BLUETOOTH_SYSTEM, + "local", getpid(), 0, &ins->app_id); + if (status != BT_STATUS_SUCCESS) { + bt_socket_client_deinit(ins); + free(ins); + ins = NULL; + } + + return ins; +} bt_instance_t* bluetooth_find_instance(pid_t pid) { bt_status_t status; @@ -114,6 +153,15 @@ void bluetooth_delete_instance(bt_instance_t* ins) free(ins); } +void bluetooth_delete_async_instance(bt_instance_t* ins) +{ + BT_SOCKET_INS_VALID(ins, ); + + manager_delete_instance(ins->app_id); + bt_socket_async_client_deinit(ins); + free(ins); +} + bt_status_t bluetooth_start_service(bt_instance_t* ins, enum profile_id id) { bt_status_t status; diff --git a/service/ipc/socket/include/bt_message.h b/service/ipc/socket/include/bt_message.h index 8483a032..000a5a4f 100644 --- a/service/ipc/socket/include/bt_message.h +++ b/service/ipc/socket/include/bt_message.h @@ -96,6 +96,7 @@ typedef enum { typedef struct { uint32_t code; /* bt_message_type_t */ + uint64_t context; union { bt_manager_result_t manager_r; bt_adapter_result_t adpt_r; diff --git a/service/ipc/socket/include/bt_socket.h b/service/ipc/socket/include/bt_socket.h index e6930c6b..a4787645 100644 --- a/service/ipc/socket/include/bt_socket.h +++ b/service/ipc/socket/include/bt_socket.h @@ -33,6 +33,23 @@ #define nitems(_a) (sizeof(_a) / sizeof(0 [(_a)])) #endif /* nitems */ +typedef struct { + void* user_data; + uv_loop_t* loop; + uv_connect_t conn_req; + + uv_pipe_t* pipe; + bt_instance_t* ins; + bt_ipc_connected_cb_t connected; + bt_ipc_disconnected_cb_t disconnected; + + bt_message_packet_t* packet; + uint32_t offset; + + bt_list_t* pending_queue; + callbacks_list_t* adapter_callbacks; +} bt_socket_async_client_t; + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -49,6 +66,11 @@ extern "C" { #endif /* Client */ +typedef void (*bt_socket_reply_cb_t)(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata); +int bt_socket_async_client_init(bt_instance_t* ins, uv_loop_t* loop, int family, + const char* name, const char* cpu, int port, bt_ipc_connected_cb_t connected, + bt_ipc_disconnected_cb_t disconnected, void* user_data); +void bt_socket_async_client_deinit(bt_instance_t* ins); int bt_socket_client_init(bt_instance_t* ins, int family, const char* name, const char* cpu, @@ -62,6 +84,8 @@ int bt_socket_client_sendrecv(bt_instance_t* ins, bt_message_packet_t* packet, bt_message_type_t code); +int bt_socket_client_send_with_reply(bt_instance_t* ins, bt_message_packet_t* packet, + bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata); /* Server */ int bt_socket_server_init(const char* name, int port); @@ -82,7 +106,7 @@ void bt_socket_server_adapter_process(service_poll_t* poll, int fd, bt_instance_t* ins, bt_message_packet_t* packet); int bt_socket_client_adapter_callback(service_poll_t* poll, - int fd, bt_instance_t* ins, bt_message_packet_t* packet); + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); /* Device */ diff --git a/service/ipc/socket/src/bt_socket_adapter.c b/service/ipc/socket/src/bt_socket_adapter.c index 50d0dcd5..1b3b3f26 100644 --- a/service/ipc/socket/src/bt_socket_adapter.c +++ b/service/ipc/socket/src/bt_socket_adapter.c @@ -49,7 +49,7 @@ #define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) -#define CBLIST (ins->adapter_callbacks) +#define CBLIST (__async ? __async->adapter_callbacks : ins->adapter_callbacks) /**************************************************************************** * Private Types @@ -518,8 +518,13 @@ void bt_socket_server_adapter_process(service_poll_t* poll, #endif int bt_socket_client_adapter_callback(service_poll_t* poll, - int fd, bt_instance_t* ins, bt_message_packet_t* packet) + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) { + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + switch (packet->code) { case BT_ADAPTER_ON_ADAPTER_STATE_CHANGED: { CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, diff --git a/service/ipc/socket/src/bt_socket_client.c b/service/ipc/socket/src/bt_socket_client.c index 4052f97d..fc316815 100644 --- a/service/ipc/socket/src/bt_socket_client.c +++ b/service/ipc/socket/src/bt_socket_client.c @@ -72,68 +72,68 @@ typedef struct _work_msg { * Private Functions ****************************************************************************/ -static void bt_socket_client_msg_process(bt_client_msg_t* msg) -{ - bt_message_packet_t* packet = &msg->packet; +typedef void (*bt_socket_callback_t)(void*, int, bt_instance_t*, bt_message_packet_t*); - if (packet->code > BT_ADAPTER_CALLBACK_START && packet->code < BT_ADAPTER_CALLBACK_END) { - bt_socket_client_adapter_callback(NULL, -1, msg->ins, packet); +static void bt_socket_client_callback_process(bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + static const struct { + int start; + int end; + bt_socket_callback_t callback; + } callback_map[] = { + { BT_ADAPTER_CALLBACK_START, BT_ADAPTER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_adapter_callback }, #ifdef CONFIG_BLUETOOTH_HFP_AG - } else if (packet->code > BT_HFP_AG_CALLBACK_START && packet->code < BT_HFP_AG_CALLBACK_END) { - bt_socket_client_hfp_ag_callback(NULL, -1, msg->ins, &msg->packet); + { BT_HFP_AG_CALLBACK_START, BT_HFP_AG_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hfp_ag_callback }, #endif #ifdef CONFIG_BLUETOOTH_HFP_HF - } else if (packet->code > BT_HFP_HF_CALLBACK_START && packet->code < BT_HFP_HF_CALLBACK_END) { - bt_socket_client_hfp_hf_callback(NULL, -1, msg->ins, &msg->packet); + { BT_HFP_HF_CALLBACK_START, BT_HFP_HF_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hfp_hf_callback }, #endif #ifdef CONFIG_BLUETOOTH_A2DP - } else if (msg->packet.code > BT_A2DP_SINK_CALLBACK_START && msg->packet.code < BT_A2DP_SINK_CALLBACK_END) { - bt_socket_client_a2dp_sink_callback(NULL, -1, msg->ins, &msg->packet); - } else if (msg->packet.code > BT_A2DP_SOURCE_CALLBACK_START && msg->packet.code < BT_A2DP_SOURCE_CALLBACK_END) { - bt_socket_client_a2dp_source_callback(NULL, -1, msg->ins, &msg->packet); + { BT_A2DP_SINK_CALLBACK_START, BT_A2DP_SINK_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_a2dp_sink_callback }, + { BT_A2DP_SOURCE_CALLBACK_START, BT_A2DP_SOURCE_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_a2dp_source_callback }, #endif #ifdef CONFIG_BLUETOOTH_AVRCP_TARGET - } else if (msg->packet.code > BT_AVRCP_TARGET_CALLBACK_START && msg->packet.code < BT_AVRCP_TARGET_CALLBACK_END) { - bt_socket_client_avrcp_target_callback(NULL, -1, msg->ins, &msg->packet); -#endif -#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL - } else if (msg->packet.code > BT_AVRCP_CONTROL_CALLBACK_START && msg->packet.code < BT_AVRCP_CONTROL_CALLBACK_END) { - bt_socket_client_avrcp_control_callback(NULL, -1, msg->ins, &msg->packet); + { BT_AVRCP_TARGET_CALLBACK_START, BT_AVRCP_TARGET_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_avrcp_target_callback }, #endif #ifdef CONFIG_BLUETOOTH_BLE_ADV - } else if (packet->code > BT_ADVERTISER_CALLBACK_START && packet->code < BT_ADVERTISER_CALLBACK_END) { - bt_socket_client_advertiser_callback(NULL, -1, msg->ins, packet); + { BT_ADVERTISER_CALLBACK_START, BT_ADVERTISER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_advertiser_callback }, #endif #ifdef CONFIG_BLUETOOTH_BLE_SCAN - } else if (packet->code > BT_SCAN_CALLBACK_START && packet->code < BT_SCAN_CALLBACK_END) { - bt_socket_client_scan_callback(NULL, -1, msg->ins, packet); + { BT_SCAN_CALLBACK_START, BT_SCAN_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_scan_callback }, #endif #ifdef CONFIG_BLUETOOTH_GATT - } else if (packet->code > BT_GATT_CLIENT_CALLBACK_START && packet->code < BT_GATT_CLIENT_CALLBACK_END) { - bt_socket_client_gattc_callback(NULL, -1, msg->ins, packet); - } else if (packet->code > BT_GATT_SERVER_CALLBACK_START && packet->code < BT_GATT_SERVER_CALLBACK_END) { - bt_socket_client_gatts_callback(NULL, -1, msg->ins, packet); + { BT_GATT_CLIENT_CALLBACK_START, BT_GATT_CLIENT_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_gattc_callback }, + { BT_GATT_SERVER_CALLBACK_START, BT_GATT_SERVER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_gatts_callback }, #endif #ifdef CONFIG_BLUETOOTH_SPP - } else if (packet->code > BT_SPP_CALLBACK_START && packet->code < BT_SPP_CALLBACK_END) { - bt_socket_client_spp_callback(NULL, -1, msg->ins, packet); + { BT_SPP_CALLBACK_START, BT_SPP_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_spp_callback }, #endif #ifdef CONFIG_BLUETOOTH_PAN - } else if (packet->code > BT_PAN_CALLBACK_START && packet->code < BT_PAN_CALLBACK_END) { - bt_socket_client_pan_callback(NULL, -1, msg->ins, packet); + { BT_PAN_CALLBACK_START, BT_PAN_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_pan_callback }, #endif #ifdef CONFIG_BLUETOOTH_HID_DEVICE - } else if (packet->code > BT_HID_DEVICE_CALLBACK_START && packet->code < BT_HID_DEVICE_CALLBACK_END) { - bt_socket_client_hid_device_callback(NULL, -1, msg->ins, packet); + { BT_HID_DEVICE_CALLBACK_START, BT_HID_DEVICE_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hid_device_callback }, #endif #ifdef CONFIG_BLUETOOTH_L2CAP - } else if (packet->code > BT_L2CAP_CALLBACK_START && packet->code < BT_L2CAP_CALLBACK_END) { - bt_socket_client_l2cap_callback(NULL, -1, msg->ins, packet); + { BT_L2CAP_CALLBACK_START, BT_L2CAP_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_l2cap_callback }, #endif - } else { - BT_LOGE("%s, Unhandled message: %d", __func__, (int)packet->code); + }; + + for (size_t i = 0; i < sizeof(callback_map) / sizeof(callback_map[0]); ++i) { + if (packet->code > callback_map[i].start && packet->code < callback_map[i].end) { + callback_map[i].callback(NULL, -1, ins, packet); + return; + } } + BT_LOGE("%s, Unhandled message: %d", __func__, (int)packet->code); +} + +static void bt_socket_client_msg_process(bt_client_msg_t* msg) +{ + bt_message_packet_t* packet = &msg->packet; + + bt_socket_client_callback_process(msg->ins, packet, false); free(msg); } @@ -159,7 +159,7 @@ static void bt_socket_client_async_cb(uv_async_t* handle) } } -static bt_status_t bt_socket_client_async_to_external(bt_instance_t* ins, bt_client_msg_t* msg) +static bt_status_t callback_send_to_external(bt_instance_t* ins, bt_client_msg_t* msg) { uv_mutex_lock(&ins->lock); if (!ins->external_async) { @@ -191,7 +191,7 @@ static void bt_socket_client_after_work(uv_work_t* req, int status) free(req); } -static bt_status_t bt_socket_client_queue_work(bt_instance_t* ins, bt_client_msg_t* msg) +static bt_status_t callback_send_to_queue_work(bt_instance_t* ins, bt_client_msg_t* msg) { uv_work_t* work = zalloc(sizeof(*work)); if (work == NULL) @@ -248,13 +248,13 @@ static int bt_socket_client_receive(uv_poll_t* poll, int fd, void* userdata) msg->ins = ins; memcpy(&msg->packet, packet, sizeof(*packet)); if (ins->external_loop) { - bt_status_t status = bt_socket_client_async_to_external(ins, msg); + bt_status_t status = callback_send_to_external(ins, msg); if (status != BT_STATUS_SUCCESS) { free(msg); return status; } } else { - if (bt_socket_client_queue_work(ins, msg) != BT_STATUS_SUCCESS) { + if (callback_send_to_queue_work(ins, msg) != BT_STATUS_SUCCESS) { free(msg); return BT_STATUS_FAIL; } @@ -282,7 +282,7 @@ static void bt_socket_client_handle_event(uv_poll_t* poll, int status, int event thread_loop_remove_poll(poll); if (ins && ins->disconnected) { BT_LOGE("%s socket disconnect, status = %d, events = %d", __func__, status, events); - ins->disconnected((void*)ins, NULL, status); + ins->disconnected(ins, NULL, status); } } else if (events & UV_READABLE) { ret = bt_socket_client_receive(poll, fd, poll->data); @@ -504,3 +504,250 @@ void bt_socket_client_deinit(bt_instance_t* ins) thread_loop_exit(ins->client_loop); free(ins->client_loop); } + +/* + Async client +*/ +typedef struct { + bt_message_type_t code; + // bt_message_packet_t* packet; + bt_socket_reply_cb_t reply_cb; + void* cb; + void* userdata; +} bt_message_context_t; + +typedef struct { + uv_write_t req; + bt_message_packet_t packet; + bt_socket_async_client_t* priv; +} bt_socket_write_t; + +static void bt_socket_alloc_cb(uv_handle_t* handle, + size_t suggested_size, uv_buf_t* buf) +{ + bt_socket_async_client_t* priv = uv_handle_get_data(handle); + + if (priv->packet == NULL) { + priv->packet = malloc(sizeof(bt_message_packet_t)); + if (priv->packet == NULL) { + BT_LOGE("malloc failed"); + buf = NULL; + suggested_size = 0; + return; + } + + priv->offset = 0; + } + + if (priv->offset == 0) { + buf->base = (char*)priv->packet; + buf->len = sizeof(bt_message_packet_t); + } else { + buf->base = ((char*)priv->packet) + priv->offset; + buf->len = sizeof(bt_message_packet_t) - priv->offset; + } +} + +static int bt_socket_async_client_handle_packet(bt_instance_t* ins, bt_message_packet_t* packet) +{ + bt_socket_async_client_t* priv = ins->priv; + + if (packet->code > BT_MESSAGE_START && packet->code < BT_MESSAGE_END) { + bt_message_context_t* ctx = (bt_message_context_t*)(uintptr_t)packet->context; + bt_message_context_t* head = bt_list_node(bt_list_head(priv->pending_queue)); + + assert(head == ctx); + + if (ctx && ctx->reply_cb) + ctx->reply_cb(ins, packet, ctx->cb, ctx->userdata); + + bt_list_remove_node(priv->pending_queue, bt_list_head(priv->pending_queue)); + } else if (packet->code > BT_CALLBACK_START && packet->code < BT_CALLBACK_END) { + bt_socket_client_callback_process(ins, packet, true); + } else { + assert(0); + } + + return BT_STATUS_SUCCESS; +} + +static void bt_socket_read_cb(uv_stream_t* stream, + ssize_t nread, const uv_buf_t* buf) +{ + bt_socket_async_client_t* priv = uv_handle_get_data((uv_handle_t*)stream); + + if (nread > 0) { + assert(priv->offset + nread <= sizeof(bt_message_packet_t)); + priv->offset += nread; + + if (priv->offset == sizeof(bt_message_packet_t)) { + bt_socket_async_client_handle_packet(priv->ins, priv->packet); + priv->offset = 0; + } + } else if (nread < 0) { + if (priv->disconnected) + priv->disconnected(priv->ins, priv->user_data, nread); + } +} + +static void bt_socket_close_cb(uv_handle_t* handle) +{ + free(handle); +} + +static void bt_socket_connect_cb(uv_connect_t* req, int status) +{ + bt_socket_async_client_t* priv = uv_req_get_data((uv_req_t*)req); + + if (status != 0) { + BT_LOGE("bt async client connect failed: %s", uv_strerror(status)); + if (priv->disconnected) + priv->disconnected(priv->ins, priv->user_data, status); + } else { + BT_LOGI("bt async client connect success"); + uv_read_start((uv_stream_t*)priv->pipe, bt_socket_alloc_cb, bt_socket_read_cb); + if (priv->connected) + priv->connected(priv->ins, priv->user_data); + } +} + +static void bt_socket_write_cb(uv_write_t* req, int status) +{ + free(req); +} + +static void bt_socket_context_free(void* data) +{ + /* callback to user the request was canceled because the instance was released? */ + + /* free data */ + free(data); +} + +int bt_socket_client_send_with_reply(bt_instance_t* ins, bt_message_packet_t* packet, + bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata) +{ + uv_buf_t buf; + bt_message_context_t* ctx; + bt_socket_async_client_t* priv; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + priv = ins->priv; + + ctx = malloc(sizeof(bt_message_context_t)); + if (!ctx) + return BT_STATUS_NOMEM; + + ctx->code = code; + ctx->reply_cb = reply; + ctx->cb = cb; + ctx->userdata = userdata; + + packet->code = code; + packet->context = (uintptr_t)ctx; + + bt_socket_write_t* wreq = malloc(sizeof(bt_socket_write_t)); + if (wreq == NULL) { + free(ctx); + return BT_STATUS_NOMEM; + } + + wreq->priv = priv; + memcpy(&wreq->packet, packet, sizeof(bt_message_packet_t)); + buf = uv_buf_init((char*)&wreq->packet, sizeof(bt_message_packet_t)); + if (uv_write(&wreq->req, (uv_stream_t*)priv->pipe, &buf, 1, bt_socket_write_cb) != 0) { + free(wreq); + free(ctx); + return BT_STATUS_FAIL; + } + + bt_list_add_tail(priv->pending_queue, ctx); + + return BT_STATUS_SUCCESS; +} + +int bt_socket_async_client_init(bt_instance_t* ins, uv_loop_t* loop, int family, + const char* name, const char* cpu, int port, bt_ipc_connected_cb_t connected, + bt_ipc_disconnected_cb_t disconnected, void* user_data) +{ + int ret; + bt_socket_async_client_t* priv; + + if (ins == NULL || loop == NULL) + return BT_STATUS_PARM_INVALID; + + priv = calloc(1, sizeof(bt_socket_async_client_t)); + if (priv == NULL) + return BT_STATUS_NOMEM; + + priv->user_data = user_data; + priv->loop = loop; + priv->ins = ins; + priv->connected = connected; + priv->disconnected = disconnected; + priv->pending_queue = bt_list_new(bt_socket_context_free); + + if (family == AF_LOCAL || family == AF_RPMSG) { + priv->pipe = malloc(sizeof(uv_pipe_t)); + if (priv->pipe == NULL) + goto fail; + + ret = uv_pipe_init(priv->loop, priv->pipe, 0); + if (ret != 0) + goto fail; + + uv_handle_set_data((uv_handle_t*)priv->pipe, priv); + uv_req_set_data((uv_req_t*)&priv->conn_req, priv); + + if (family == AF_LOCAL) { + char path[UNIX_PATH_MAX]; + + snprintf(path, UNIX_PATH_MAX, BLUETOOTH_SOCKADDR_NAME, name); + uv_pipe_connect(&priv->conn_req, priv->pipe, path, bt_socket_connect_cb); + } +#ifdef CONFIG_NET_RPMSG + else if (family == AF_RPMSG) { + char rp_name[RPMSG_SOCKET_NAME_SIZE]; + + snprintf(rp_name, RPMSG_SOCKET_NAME_SIZE, BLUETOOTH_SOCKADDR_NAME, name); + uv_pipe_rpmsg_connect(&priv->conn_req, priv->pipe, rp_name, cpu, bt_socket_connect_cb); + } +#endif + else { + return BT_STATUS_NOT_SUPPORTED; + } + } + + ins->priv = priv; + + return BT_STATUS_SUCCESS; +fail: + bt_socket_async_client_deinit(ins); + return BT_STATUS_FAIL; +} + +void bt_socket_async_client_deinit(bt_instance_t* ins) +{ + bt_socket_async_client_t* priv; + + if (ins == NULL) + return; + + priv = ins->priv; + if (priv == NULL) + return; + + uv_read_stop((uv_stream_t*)priv->pipe); + + if (priv->pending_queue) { + bt_list_free(priv->pending_queue); + priv->pending_queue = NULL; + } + + if (priv->pipe) + uv_close((uv_handle_t*)priv->pipe, bt_socket_close_cb); + + free(priv->packet); + free(priv); + ins->priv = NULL; +} \ No newline at end of file -- Gitee From af8fd8b11ccf04cb0829bf4ebc2ff489547eae55 Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Thu, 17 Oct 2024 10:48:47 +0800 Subject: [PATCH 018/498] ipc: add framework async api config bug: v/44888 Signed-off-by: fangzhenwei --- CMakeLists.txt | 5 +++++ Kconfig | 6 ++++++ Makefile | 3 +++ 3 files changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1f5991d..f5710903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,11 @@ if(CONFIG_BLUETOOTH) file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/framework/socket/*.c) list(APPEND CSRCS ${APPEND_FILES}) + if (CONFIG_BLUETOOTH_FRAMEWORK_ASYNC) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/framework/socket/async/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/ipc/socket/include) else() diff --git a/Kconfig b/Kconfig index 9a746994..598c20e0 100644 --- a/Kconfig +++ b/Kconfig @@ -311,6 +311,12 @@ choice config BLUETOOTH_FRAMEWORK_SOCKET_IPC bool "use socket ipc api" endchoice + +config BLUETOOTH_FRAMEWORK_ASYNC + bool "Enable bluetooth framework async api" + default n + help + Enable bluetooth framework async api endif #BLUETOOTH_FRAMEWORK if BLUETOOTH_FRAMEWORK_SOCKET_IPC diff --git a/Makefile b/Makefile index 10de8749..65d90814 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,9 @@ endif CSRCS += service/ipc/socket/src/*.c CSRCS += framework/socket/*.c CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/service/ipc/socket/include +ifeq ($(CONFIG_BLUETOOTH_FRAMEWORK_ASYNC), y) + CSRCS += framework/socket/async/*.c +endif #CONFIG_BLUETOOTH_FRAMEWORK_ASYNC else endif endif -- Gitee From c2534e128656eea4271971bea6db0c4e373eefcb Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Thu, 17 Oct 2024 10:50:49 +0800 Subject: [PATCH 019/498] ipc: add bt-async-api common callback type bug: v/44888 Signed-off-by: fangzhenwei --- framework/include/bt_async.h | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 framework/include/bt_async.h diff --git a/framework/include/bt_async.h b/framework/include/bt_async.h new file mode 100644 index 00000000..e9456722 --- /dev/null +++ b/framework/include/bt_async.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_ASYNC_H__ +#define __BT_ASYNC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +typedef void (*bt_status_cb_t)(bt_instance_t* ins, bt_status_t status, void* userdata); +typedef void (*bt_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata); +typedef void (*bt_uuids_cb_t)(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t size, void* userdata); +typedef void (*bt_device_type_cb_t)(bt_instance_t* ins, bt_status_t status, bt_device_type_t dtype, void* userdata); +typedef void (*bt_bool_cb_t)(bt_instance_t* ins, bt_status_t status, bool bbool, void* userdata); +typedef void (*bt_string_cb_t)(bt_instance_t* ins, bt_status_t status, const char* str, void* userdata); +typedef void (*bt_s8_cb_t)(bt_instance_t* ins, bt_status_t status, int8_t val, void* userdata); +typedef void (*bt_u8_cb_t)(bt_instance_t* ins, bt_status_t status, uint8_t val, void* userdata); +typedef void (*bt_u16_cb_t)(bt_instance_t* ins, bt_status_t status, uint16_t val, void* userdata); +typedef void (*bt_u32_cb_t)(bt_instance_t* ins, bt_status_t status, uint32_t val, void* userdata); +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file -- Gitee From b36782c516a25564520b1a6d05d88ede9d7cd37e Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Fri, 29 Nov 2024 14:19:06 +0800 Subject: [PATCH 020/498] ipc: adapter async api bug: v/44888 Signed-off-by: fangzhenwei --- framework/include/bt_adapter.h | 65 ++- framework/socket/async/bt_adapter_async.c | 639 ++++++++++++++++++++++ 2 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 framework/socket/async/bt_adapter_async.c diff --git a/framework/include/bt_adapter.h b/framework/include/bt_adapter.h index 4b7c33eb..4280647c 100644 --- a/framework/include/bt_adapter.h +++ b/framework/include/bt_adapter.h @@ -1130,8 +1130,71 @@ bt_status_t BTSYMBOLS(bt_adapter_set_afh_channel_classification)(bt_instance_t* */ bt_status_t BTSYMBOLS(bt_adapter_set_auto_sniff)(bt_instance_t* ins, bt_auto_sniff_params_t* params); +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +typedef void (*bt_register_callback_cb_t)(bt_instance_t* ins, bt_status_t status, void* cookie, void* userdata); +typedef void (*bt_adapter_get_state_cb_t)(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata); +typedef void (*bt_adapter_get_device_type_cb_t)(bt_instance_t* ins, bt_status_t status, bt_device_type_t type, void* userdata); +typedef void (*bt_adapter_get_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata); +typedef void (*bt_adapter_get_io_capability_cb_t)(bt_instance_t* ins, bt_status_t status, bt_io_capability_t cap, void* userdata); +typedef void (*bt_adapter_get_scan_mode_cb_t)(bt_instance_t* ins, bt_status_t status, bt_scan_mode_t mode, void* userdata); +typedef void (*bt_adapter_get_devices_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, int num, void* userdata); +typedef void (*bt_adapter_get_uuids_cb_t)(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t size, void* userdata); +typedef void (*bt_adapter_get_le_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, ble_addr_type_t type, void* userdata); + +bt_status_t bt_adapter_register_callback_async(bt_instance_t* ins, + const adapter_callbacks_t* adapter_cbs, bt_register_callback_cb_t cb, void* userdata); +bt_status_t bt_adapter_unregister_callback_async(bt_instance_t* ins, void* cookie, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_enable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_enable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_state_async(bt_instance_t* ins, bt_adapter_get_state_cb_t get_state_cb, void* userdata); +bt_status_t bt_adapter_is_le_enabled_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_type_async(bt_instance_t* ins, bt_device_type_cb_t get_dtype_cb, void* userdata); +bt_status_t bt_adapter_set_discovery_filter_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_start_discovery_async(bt_instance_t* ins, uint32_t timeout, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_cancel_discovery_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_discovering_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_address_async(bt_instance_t* ins, bt_address_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_name_async(bt_instance_t* ins, const char* name, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_name_async(bt_instance_t* ins, bt_string_cb_t get_name_cb, void* userdata); +bt_status_t bt_adapter_get_uuids_async(bt_instance_t* ins, bt_uuids_cb_t get_uuids_cb, void* userdata); +bt_status_t bt_adapter_set_scan_mode_async(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_scan_mode_async(bt_instance_t* ins, bt_adapter_get_scan_mode_cb_t get_scan_mode_cb, void* userdata); +bt_status_t bt_adapter_set_device_class_async(bt_instance_t* ins, uint32_t cod, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_device_class_async(bt_instance_t* ins, bt_u32_cb_t get_cod_cb, void* userdata); +bt_status_t bt_adapter_set_io_capability_async(bt_instance_t* ins, bt_io_capability_t cap, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_io_capability_async(bt_instance_t* ins, bt_adapter_get_io_capability_cb_t get_ioc_cb, void* userdata); +bt_status_t bt_adapter_set_inquiry_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_page_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_io_capability_async(bt_instance_t* ins, uint32_t le_io_cap, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_le_io_capability_async(bt_instance_t* ins, bt_u32_cb_t get_le_ioc_cb, void* userdata); +bt_status_t bt_adapter_get_le_address_async(bt_instance_t* ins, bt_adapter_get_le_address_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_address_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_identity_address_async(bt_instance_t* ins, bt_address_t* addr, bool public, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_appearance_async(bt_instance_t* ins, uint16_t appearance, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_le_appearance_async(bt_instance_t* ins, bt_u16_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_enable_key_derivation_async(bt_instance_t* ins, + bool brkey_to_lekey, bool lekey_to_brkey, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_add_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_remove_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_bonded_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_bonded_cb, void* userdata); +bt_status_t bt_adapter_get_connected_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_connected_cb, void* userdata); +bt_status_t bt_adapter_set_afh_channel_classification_async(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_auto_sniff_async(bt_instance_t* ins, bt_auto_sniff_params_t* params, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disconnect_all_devices_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_bredr_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_le_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_leaudio_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +#endif /* CONFIG_BLUETOOTH_FRAMEWORK_ASYNC */ + #ifdef __cplusplus } #endif - #endif /* __BT_ADAPTER_H__ */ diff --git a/framework/socket/async/bt_adapter_async.c b/framework/socket/async/bt_adapter_async.c new file mode 100644 index 00000000..dfd325e8 --- /dev/null +++ b/framework/socket/async/bt_adapter_async.c @@ -0,0 +1,639 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include + +#include "bt_adapter.h" +#include "bt_async.h" +#include "bt_socket.h" + +typedef struct { + void* userdata; + void* cookie; +} bt_register_callback_data_t; + +static void adapter_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, userdata); +} + +static void adapter_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.bbool, userdata); +} + +static void adapter_uint16_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u16_cb_t ret_cb = (bt_u16_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.v16, userdata); +} + +static void adapter_uint32_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u32_cb_t ret_cb = (bt_u32_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.v32, userdata); +} + +static void adapter_get_state_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_state_cb_t ret_cb = (bt_adapter_get_state_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.state, userdata); +} + +static void adapter_get_device_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_type_cb_t ret_cb = (bt_device_type_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.dtype, userdata); +} + +static void adapter_get_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_address_cb_t ret_cb = (bt_address_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, &packet->adpt_pl._bt_adapter_get_address.addr, userdata); +} + +static void adapter_get_name_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_name.name, userdata); +} + +static void adapter_get_uuids_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_uuids_cb_t ret_cb = (bt_uuids_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_uuids.uuids, packet->adpt_pl._bt_adapter_get_uuids.size, userdata); +} + +static void adapter_get_scan_mode_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_scan_mode_cb_t ret_cb = (bt_adapter_get_scan_mode_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.mode, userdata); +} + +static void adapter_get_io_capability_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_io_capability_cb_t ret_cb = (bt_adapter_get_io_capability_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.ioc, userdata); +} + +static void adapter_get_devices_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_devices_cb_t ret_cb = (bt_adapter_get_devices_cb_t)cb; + + if (ret_cb) { + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_bonded_devices.addr, + packet->adpt_pl._bt_adapter_get_bonded_devices.num, userdata); + } +} + +static void adapter_get_le_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_le_address_cb_t ret_cb = (bt_adapter_get_le_address_cb_t)cb; + + if (ret_cb) { + ret_cb(ins, packet->adpt_r.status, &packet->adpt_pl._bt_adapter_get_le_address.addr, + packet->adpt_pl._bt_adapter_get_le_address.type, userdata); + } +} + +static void adapter_register_callback_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_register_callback_data_t* data = userdata; + bt_socket_async_client_t* priv = ins->priv; + bt_register_callback_cb_t ret_cb = (bt_register_callback_cb_t)cb; + + if (packet->adpt_r.status != BT_STATUS_SUCCESS || !ret_cb) { + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + } + + if (ret_cb) { + ret_cb(ins, packet->adpt_r.status, data->cookie, data->userdata); + } + + free(data); +} + +bt_status_t bt_adapter_register_callback_async(bt_instance_t* ins, + const adapter_callbacks_t* adapter_cbs, bt_register_callback_cb_t cb, void* userdata) +{ + bt_register_callback_data_t* data; + bt_socket_async_client_t* priv; + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + priv = ins->priv; + if (!priv) + return BT_STATUS_IPC_ERROR; + + if (priv->adapter_callbacks) { + handle = bt_remote_callbacks_register(priv->adapter_callbacks, NULL, (void*)adapter_cbs); + cb(ins, BT_STATUS_SUCCESS, handle, userdata); + return BT_STATUS_SUCCESS; + } + + priv->adapter_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (priv->adapter_callbacks == NULL) + return BT_STATUS_NOMEM; + +#ifdef CONFIG_BLUETOOTH_FEATURE + handle = bt_remote_callbacks_register(priv->adapter_callbacks, ins, (void*)adapter_cbs); +#else + handle = bt_remote_callbacks_register(priv->adapter_callbacks, NULL, (void*)adapter_cbs); +#endif + + if (handle == NULL) { + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + return BT_STATUS_NO_RESOURCES; + } + + data = calloc(1, sizeof(bt_register_callback_data_t)); + data->userdata = userdata; + data->cookie = handle; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_REGISTER_CALLBACK, adapter_register_callback_reply, cb, data); + if (status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + free(data); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_adapter_unregister_callback_async(bt_instance_t* ins, void* cookie, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + bt_socket_async_client_t* priv; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + priv = ins->priv; + if (!priv || !priv->adapter_callbacks) + return BT_STATUS_IPC_ERROR; + + bt_remote_callbacks_unregister(priv->adapter_callbacks, NULL, cookie); + if (bt_callbacks_list_count(priv->adapter_callbacks) > 0) { + return BT_STATUS_SUCCESS; + } + + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_UNREGISTER_CALLBACK, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_enable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_ENABLE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_disable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISABLE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_enable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_ENABLE_LE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_disable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISABLE_LE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_state_async(bt_instance_t* ins, bt_adapter_get_state_cb_t get_state_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_STATE, adapter_get_state_reply, (void*)get_state_cb, userdata); +} + +bt_status_t bt_adapter_is_le_enabled_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_LE_ENABLED, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_type_async(bt_instance_t* ins, bt_device_type_cb_t get_dtype_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_TYPE, adapter_get_device_type_reply, (void*)get_dtype_cb, userdata); +} + +bt_status_t bt_adapter_set_discovery_filter_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_adapter_start_discovery_async(bt_instance_t* ins, uint32_t timeout, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_start_discovery.v32 = timeout; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_START_DISCOVERY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_cancel_discovery_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_CANCEL_DISCOVERY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_discovering_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_DISCOVERING, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_address_async(bt_instance_t* ins, bt_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_ADDRESS, adapter_get_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_name_async(bt_instance_t* ins, const char* name, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (strlen(name) > sizeof(packet.adpt_pl._bt_adapter_set_name.name)) + return BT_STATUS_PARM_INVALID; + + memset(packet.adpt_pl._bt_adapter_set_name.name, 0, sizeof(packet.adpt_pl._bt_adapter_set_name.name)); + strncpy(packet.adpt_pl._bt_adapter_set_name.name, name, sizeof(packet.adpt_pl._bt_adapter_set_name.name) - 1); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_NAME, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_name_async(bt_instance_t* ins, bt_string_cb_t get_name_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_NAME, adapter_get_name_reply, (void*)get_name_cb, userdata); +} + +bt_status_t bt_adapter_get_uuids_async(bt_instance_t* ins, bt_uuids_cb_t get_uuids_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_UUIDS, adapter_get_uuids_reply, (void*)get_uuids_cb, userdata); +} + +bt_status_t bt_adapter_set_scan_mode_async(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_scan_mode.mode = mode; + packet.adpt_pl._bt_adapter_set_scan_mode.bondable = bondable; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_SCAN_MODE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_scan_mode_async(bt_instance_t* ins, bt_adapter_get_scan_mode_cb_t get_scan_mode_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_SCAN_MODE, adapter_get_scan_mode_reply, (void*)get_scan_mode_cb, userdata); +} + +bt_status_t bt_adapter_set_device_class_async(bt_instance_t* ins, uint32_t cod, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_device_class.v32 = cod; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_DEVICE_CLASS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_device_class_async(bt_instance_t* ins, bt_u32_cb_t get_cod_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_DEVICE_CLASS, adapter_uint32_reply, (void*)get_cod_cb, userdata); +} + +bt_status_t bt_adapter_set_io_capability_async(bt_instance_t* ins, bt_io_capability_t cap, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_io_capability.cap = cap; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_IO_CAPABILITY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_io_capability_async(bt_instance_t* ins, bt_adapter_get_io_capability_cb_t get_ioc_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_IO_CAPABILITY, adapter_get_io_capability_reply, (void*)get_ioc_cb, userdata); +} + +bt_status_t bt_adapter_set_inquiry_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.window = window; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_INQUIRY_SCAN_PARAMETERS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_page_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_page_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.window = window; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_PAGE_SCAN_PARAMETERS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_io_capability_async(bt_instance_t* ins, uint32_t le_io_cap, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_io_capability.v32 = le_io_cap; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_IO_CAPABILITY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_le_io_capability_async(bt_instance_t* ins, bt_u32_cb_t get_le_ioc_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_IO_CAPABILITY, adapter_uint32_reply, (void*)get_le_ioc_cb, userdata); +} + +bt_status_t bt_adapter_get_le_address_async(bt_instance_t* ins, bt_adapter_get_le_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_ADDRESS, adapter_get_le_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_address_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_address.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_ADDRESS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_identity_address_async(bt_instance_t* ins, bt_address_t* addr, bool public, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_identity_address.addr, addr, sizeof(*addr)); + packet.adpt_pl._bt_adapter_set_le_identity_address.pub = public; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_IDENTITY_ADDRESS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_appearance_async(bt_instance_t* ins, uint16_t appearance, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_appearance.v16 = appearance; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_APPEARANCE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_le_appearance_async(bt_instance_t* ins, bt_u16_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_APPEARANCE, adapter_uint16_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_enable_key_derivation_async(bt_instance_t* ins, + bool brkey_to_lekey, bool lekey_to_brkey, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_le_enable_key_derivation.brkey_to_lekey = brkey_to_lekey; + packet.adpt_pl._bt_adapter_le_enable_key_derivation.lekey_to_brkey = lekey_to_brkey; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_ENABLE_KEY_DERIVATION, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_add_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_add_whitelist.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_ADD_WHITELIST, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_remove_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_remove_whitelist.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_REMOVE_WHITELIST, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_bonded_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_bonded_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_bonded_devices.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_BONDED_DEVICES, adapter_get_devices_reply, (void*)get_bonded_cb, userdata); +} + +bt_status_t bt_adapter_get_connected_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_connected_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_connected_devices.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_CONNECTED_DEVICES, adapter_get_devices_reply, (void*)get_connected_cb, userdata); +} + +bt_status_t bt_adapter_set_afh_channel_classification_async(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_afh_channel_classification.central_frequency = central_frequency; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.band_width = band_width; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.number = number; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_AFH_CHANNEL_CLASSFICATION, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_auto_sniff_async(bt_instance_t* ins, bt_auto_sniff_params_t* params, bt_status_cb_t cb, void* userdata) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_adapter_disconnect_all_devices_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISCONNECT_ALL_DEVICES, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_bredr_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_BREDR, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_le_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_LE, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_leaudio_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_LEAUDIO, adapter_bool_reply, (void*)cb, userdata); +} -- Gitee From 0b9a73f2e53cf6a70cb92c8bd7e96669902f6793 Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Wed, 16 Oct 2024 15:59:14 +0800 Subject: [PATCH 021/498] ipc: device async api bug: v/44888 Signed-off-by: fangzhenwei --- framework/include/bt_device.h | 42 ++ framework/socket/async/bt_device_async.c | 511 +++++++++++++++++++++++ 2 files changed, 553 insertions(+) create mode 100644 framework/socket/async/bt_device_async.c diff --git a/framework/include/bt_device.h b/framework/include/bt_device.h index 139a36f4..d1ab15a2 100644 --- a/framework/include/bt_device.h +++ b/framework/include/bt_device.h @@ -833,6 +833,48 @@ bt_status_t BTSYMBOLS(bt_device_enable_enhanced_mode)(bt_instance_t* ins, bt_add */ bt_status_t BTSYMBOLS(bt_device_disable_enhanced_mode)(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode); +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +typedef void (*bt_device_get_bond_state_cb_t)(bt_instance_t* ins, bt_status_t status, bond_state_t bstate, void* userdata); +typedef void (*bt_device_get_address_type_cb_t)(bt_instance_t* ins, bt_status_t status, ble_addr_type_t atype, void* userdata); + +bt_status_t bt_device_get_identity_address_async(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_cb_t cb, void* userdata); +bt_status_t bt_device_get_address_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_get_address_type_cb_t cb, void* userdata); +bt_status_t bt_device_get_device_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_type_cb_t cb, void* userdata); +bt_status_t bt_device_get_name_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata); +bt_status_t bt_device_get_device_class_async(bt_instance_t* ins, bt_address_t* addr, bt_u32_cb_t cb, void* userdata); +bt_status_t bt_device_get_uuids_async(bt_instance_t* ins, bt_address_t* addr, bt_uuids_cb_t cb, void* userdata); +bt_status_t bt_device_get_appearance_async(bt_instance_t* ins, bt_address_t* addr, bt_u16_cb_t cb, void* userdata); +bt_status_t bt_device_get_rssi_async(bt_instance_t* ins, bt_address_t* addr, bt_s8_cb_t cb, void* userdata); +bt_status_t bt_device_get_alias_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata); +bt_status_t bt_device_set_alias_async(bt_instance_t* ins, bt_address_t* addr, const char* alias, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_is_connected_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_is_encrypted_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_is_bond_initiate_local_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_get_bond_state_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_device_get_bond_state_cb_t cb, void* userdata); +bt_status_t bt_device_is_bonded_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_connect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_le_async(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t type, ble_connect_params_t* param, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_le_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_phy_async(bt_instance_t* ins, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_create_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_remove_bond_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_cancel_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_pair_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pairing_confirmation_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pin_code_async(bt_instance_t* ins, bt_address_t* addr, bool accept, char* pincode, int len, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pass_key_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_legacy_tk_async(bt_instance_t* ins, bt_address_t* addr, bt_128key_t tk_val, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_sc_remote_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_get_le_sc_local_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +#endif + #ifdef __cplusplus } #endif diff --git a/framework/socket/async/bt_device_async.c b/framework/socket/async/bt_device_async.c new file mode 100644 index 00000000..3d2e1fbd --- /dev/null +++ b/framework/socket/async/bt_device_async.c @@ -0,0 +1,511 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_async.h" +#include "bt_device.h" +#include "bt_message.h" +#include "bt_socket.h" + +static void device_s8_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_s8_cb_t ret_cb = (bt_s8_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, (int8_t)packet->devs_r.v8, userdata); +} + +static void device_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, userdata); +} + +static void device_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.bbool, userdata); +} + +static void device_u16_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u16_cb_t ret_cb = (bt_u16_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.v16, userdata); +} + +static void device_u32_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u32_cb_t ret_cb = (bt_u32_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.v32, userdata); +} + +static void device_get_device_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_type_cb_t ret_cb = (bt_device_type_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.dtype, userdata); +} + +static void device_get_name_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_name.name, userdata); +} + +static void device_get_uuids_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_uuids_cb_t ret_cb = (bt_uuids_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_uuids.uuids, packet->devs_pl._bt_device_get_uuids.size, userdata); +} + +static void device_get_alias_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_alias.alias, userdata); +} + +static void device_get_bond_state_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_get_bond_state_cb_t ret_cb = (bt_device_get_bond_state_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.bstate, userdata); +} + +static void device_get_identity_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_address_cb_t ret_cb = (bt_address_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, &packet->devs_pl._bt_device_addr.addr, userdata); +} + +static void device_get_address_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_get_address_type_cb_t ret_cb = (bt_device_get_address_type_cb_t)cb; + + if (ret_cb) + ret_cb(ins, packet->devs_r.status, packet->devs_r.atype, userdata); +} + +static int bt_device_send_async(bt_instance_t* ins, bt_address_t* addr, + bt_message_packet_t* packet, bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata) +{ + memcpy(&packet->devs_pl._bt_device_addr.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, packet, code, reply, cb, userdata); +} + +bt_status_t bt_device_get_identity_address_async(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, bd_addr, &packet, BT_DEVICE_GET_IDENTITY_ADDRESS, device_get_identity_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_address_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_get_address_type_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_ADDRESS_TYPE, device_get_address_type_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_device_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_type_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_DEVICE_TYPE, device_get_device_type_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_name_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_name.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_NAME, device_get_name_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_device_class_async(bt_instance_t* ins, bt_address_t* addr, bt_u32_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_DEVICE_CLASS, device_u32_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_uuids_async(bt_instance_t* ins, bt_address_t* addr, bt_uuids_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_uuids.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_UUIDS, device_get_uuids_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_appearance_async(bt_instance_t* ins, bt_address_t* addr, bt_u16_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_APPEARANCE, device_u16_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_rssi_async(bt_instance_t* ins, bt_address_t* addr, bt_s8_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_RSSI, device_s8_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_alias_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_alias.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_ALIAS, device_get_alias_reply, cb, userdata); +} + +bt_status_t bt_device_set_alias_async(bt_instance_t* ins, bt_address_t* addr, + const char* alias, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_alias.addr, addr, sizeof(*addr)); + strncpy(packet.devs_pl._bt_device_set_alias.alias, alias, + sizeof(packet.devs_pl._bt_device_set_alias.alias) - 1); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_ALIAS, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_connected_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_connected.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_CONNECTED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_encrypted_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_encrypted.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_ENCRYPTED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_bond_initiate_local_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_bond_initiate_local.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_BOND_INITIATE_LOCAL, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_bond_state_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_device_get_bond_state_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_get_bond_state.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_BOND_STATE, device_get_bond_state_reply, cb, userdata); +} + +bt_status_t bt_device_is_bonded_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_bonded.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_BONDED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_le_async(bt_instance_t* ins, + bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_connect_le.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_le.type = type; + memcpy(&packet.devs_pl._bt_device_connect_le.param, param, sizeof(*param)); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT_LE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_le_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT_LE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_request_reply_async(bt_instance_t* ins, bt_address_t* addr, + bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_connect_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_request_reply.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CONNECT_REQUEST_REPLY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT_ALL_PROFILE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT_ALL_PROFILE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_phy_async(bt_instance_t* ins, + bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_phy.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_le_phy.tx_phy = tx_phy; + packet.devs_pl._bt_device_set_le_phy.rx_phy = rx_phy; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_PHY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_create_bond_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_create_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_create_bond.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CREATE_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_remove_bond_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_remove_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_remove_bond.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_REMOVE_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_cancel_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_cancel_bond.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CANCEL_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_pair_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_pair_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_pair_request_reply.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_PAIR_REQUEST_REPLY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pairing_confirmation_async(bt_instance_t* ins, bt_address_t* addr, + uint8_t transport, bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_pairing_confirmation.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pairing_confirmation.transport = transport; + packet.devs_pl._bt_device_set_pairing_confirmation.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PAIRING_CONFIRMATION, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pin_code_async(bt_instance_t* ins, bt_address_t* addr, bool accept, + char* pincode, int len, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + if (len > sizeof(packet.devs_pl._bt_device_set_pin_code.pincode)) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.devs_pl._bt_device_set_pin_code.addr, addr, sizeof(*addr)); + memcpy(&packet.devs_pl._bt_device_set_pin_code.pincode, pincode, len); + packet.devs_pl._bt_device_set_pin_code.len = len; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PIN_CODE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pass_key_async(bt_instance_t* ins, bt_address_t* addr, + uint8_t transport, bool accept, uint32_t passkey, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_pass_key.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pass_key.transport = transport; + packet.devs_pl._bt_device_set_pass_key.accept = accept; + packet.devs_pl._bt_device_set_pass_key.passkey = passkey; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PASS_KEY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_legacy_tk_async(bt_instance_t* ins, bt_address_t* addr, + bt_128key_t tk_val, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_legacy_tk.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.addr)); + memcpy(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val, tk_val, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_LEGACY_TK, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_sc_remote_oob_data_async(bt_instance_t* ins, bt_address_t* addr, + bt_128key_t c_val, bt_128key_t r_val, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val, c_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val, r_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_le_sc_local_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA, device_status_reply, (void*)cb, userdata); +} \ No newline at end of file -- Gitee From df714f48fa7208ba26972cfb0999c0cc552f505e Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Fri, 29 Nov 2024 14:20:12 +0800 Subject: [PATCH 022/498] bttool: create uvloop thread to execute command bug: v/46315 Signed-off-by: fangzhenwei --- tools/bt_tools.c | 287 ++++++++++++++++++++++++++++++++++------------- tools/bt_tools.h | 10 ++ 2 files changed, 220 insertions(+), 77 deletions(-) diff --git a/tools/bt_tools.c b/tools/bt_tools.c index ce36c767..8feb0e5f 100644 --- a/tools/bt_tools.c +++ b/tools/bt_tools.c @@ -79,9 +79,6 @@ static int quit_cmd(void* handle, int argc, char** argv); static bt_instance_t* g_bttool_ins = NULL; static void* adapter_callback = NULL; -static void* adapter_callback2 = NULL; -static pthread_mutex_t bt_lock; -static pthread_cond_t disable_cond; static bool g_cmd_had_inited = false; static struct { @@ -406,23 +403,6 @@ static const char* cmd_err_str(int err_code) return "Correct code ?"; } -#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL -static void do_disable_wait(void* handle) -{ - pthread_mutex_lock(&bt_lock); - bt_adapter_disable(handle); - pthread_cond_wait(&disable_cond, &bt_lock); - pthread_mutex_unlock(&bt_lock); -} - -static void disable_done_signal(void* handle) -{ - pthread_mutex_lock(&bt_lock); - pthread_cond_signal(&disable_cond); - pthread_mutex_unlock(&bt_lock); -} -#endif - static int enable_cmd(void* handle, int argc, char** argv) { bt_adapter_enable(handle); @@ -1454,7 +1434,7 @@ static void usage(void) static void show_version(void) { - printf("Version :1.0.1"); + printf("Version :2.0.1"); } static int execute_command(void* handle, int argc, char* argv[]) @@ -1504,17 +1484,10 @@ static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) /* code */ bt_tool_uninit(g_bttool_ins); } else if (state == BT_ADAPTER_STATE_OFF) { -#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL - disable_done_signal(g_bttool_ins); -#endif + /* do something */ } } -static void on_adapter_state_changed_cb_2(void* cookie, bt_adapter_state_t state) -{ - PRINT("Context2:%p, Adapter state changed: %d", cookie, state); -} - static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) { PRINT("Discovery state: %s", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); @@ -1663,10 +1636,6 @@ const static adapter_callbacks_t g_adapter_cbs = { .on_remote_uuids_changed = on_remote_uuids_changed_cb, }; -const static adapter_callbacks_t g_adapter_cbs_2 = { - .on_adapter_state_changed = on_adapter_state_changed_cb_2, -}; - int execute_command_in_table_offset(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[], uint8_t offset) { int ret; @@ -1691,19 +1660,152 @@ int execute_command_in_table(void* handle, bt_command_t* table, uint32_t table_s return execute_command_in_table_offset(handle, table, table_size, argc, argv, 1); } -int main(int argc, char** argv) +static int bttool_ins_init(bttool_t* bttool) { - int opt; + pthread_setschedprio(pthread_self(), CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY); + g_bttool_ins = bluetooth_create_instance(); + if (g_bttool_ins == NULL) { + PRINT("create instance error\n"); + return -1; + } + + adapter_callback = bt_adapter_register_callback(g_bttool_ins, &g_adapter_cbs); + if (bt_adapter_get_state(g_bttool_ins) == BT_ADAPTER_STATE_ON) + bt_tool_init(g_bttool_ins); + + return 0; +} + +static void bttool_ins_uninit(bttool_t* bttool) +{ + bt_tool_uninit(g_bttool_ins); + bt_adapter_unregister_callback(g_bttool_ins, adapter_callback); + bluetooth_delete_instance(g_bttool_ins); + g_bttool_ins = NULL; + adapter_callback = NULL; +} + +#ifdef CONFIG_LIBUV_EXTENSION +static void handle_close_cb(uv_handle_t* handle) +{ + uv_stop(uv_handle_get_loop(handle)); +} + +static void bttool_execute_command_cb(uv_async_queue_t* handle, void* buffer) +{ + int ret; int _argc = 0; char* _argv[32]; - char* buffer = NULL; - char* saveptr; + char* saveptr = NULL; + char* tmpstr = buffer; + bttool_t* bttool = handle->data; + + memset(_argv, 0, sizeof(_argv)); + + // 1. split command + while ((tmpstr = strtok_r(tmpstr, " ", &saveptr)) != NULL) { + _argv[_argc] = tmpstr; + _argc++; + tmpstr = NULL; + } + + // 2. execute command + if (_argc > 0) { + ret = execute_command(g_bttool_ins, _argc, _argv); + if (ret != CMD_OK) { + if (ret == -2) { + bttool_ins_uninit(bttool); + uv_async_queue_close(handle, handle_close_cb); + } else + PRINT("cmd execute error: [%s]", cmd_err_str(ret)); + } + } + + // 3. free buffer alloced by getline() + free(buffer); +} + +static void bttool_command_uvloop_run(bttool_t* bttool) +{ int ret; - size_t len; -#ifndef __NuttX__ - size_t size; -#endif + /* This code is used to initialize the async queue. */ + ret = uv_async_queue_init(&bttool->loop, &bttool->async, bttool_execute_command_cb); + if (ret != 0) { + PRINT("%s async error: %d", __func__, ret); + uv_loop_close(&bttool->loop); + return; + } + + bttool->async.data = bttool; + uv_sem_post(&bttool->ready); + + /* This code is used to start the event loop until there are no more events to process. */ + uv_run(&bttool->loop, UV_RUN_DEFAULT); + + /* The assert() function is used to check the return value of uv_loop_close(). + If the return value is 0, it means that the loop is closed successfully, + otherwise it means an error occurs. + */ + assert(uv_loop_close(&bttool->loop) == 0); +} + +static void bttool_thread(void* data) +{ + bttool_t* bttool = data; + + /* Initialize the event loop, the loop is available + before the asynchronous instance is created. + */ + uv_loop_init(&bttool->loop); + + /* initialize synchronous or asynchronous instance. + and register callbacks. + */ + bttool_ins_init(bttool); + + /* This code is used to start the event loop until there are no more events to process. */ + bttool_command_uvloop_run(bttool); +} + +static int bttool_create_thread(bttool_t* bttool) +{ + int ret; + + ret = uv_sem_init(&bttool->ready, 0); + if (ret != 0) { + PRINT("%s sem init error: %d", __func__, ret); + return ret; + } + + ret = uv_thread_create(&bttool->thread, bttool_thread, (void*)bttool); + if (ret != 0) { + PRINT("loop thread create :%d", ret); + return ret; + } + + pthread_setname_np(bttool->thread, "bttool-cmd-exec"); + uv_sem_wait(&bttool->ready); + uv_sem_destroy(&bttool->ready); + + return 0; +} + +static void bttool_quit(bttool_t* bttool) +{ + char* buffer = malloc(5); + + strcpy(buffer, "quit"); + uv_async_queue_send(&bttool->async, buffer); +} + +int main(int argc, char** argv) +{ + int opt; + char* buffer = NULL; + int ret; + size_t len, size = 0; + bttool_t bttool; while ((opt = getopt_long(argc, argv, "h-v-d", main_options, NULL)) != -1) { switch (opt) { @@ -1719,42 +1821,83 @@ int main(int argc, char** argv) } } -#ifdef __NuttX__ - buffer = malloc(CONFIG_NSH_LINELEN); - if (!buffer) - return -ENOMEM; + // Call the bttool_create_thread function to create a new thread + // If thread creation fails, the return value is non-zero + ret = bttool_create_thread(&bttool); + if (ret != 0) + return ret; + + while (1) { + printf("bttool> "); + fflush(stdout); + + len = getline(&buffer, &size, stdin); + if (-1 == len) { + bttool_quit(&bttool); + break; + } + + buffer[len] = '\0'; + if (buffer[0] == '!') { +#ifdef CONFIG_SYSTEM_SYSTEM + system(buffer + 1); #endif + continue; + } - pthread_mutex_init(&bt_lock, NULL); - pthread_cond_init(&disable_cond, NULL); - pthread_setschedprio(pthread_self(), CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY); - g_bttool_ins = bluetooth_create_instance(); - if (g_bttool_ins == NULL) { - PRINT("create instance error\n"); - free(buffer); - return -1; + if (buffer[len - 1] == '\n') + buffer[len - 1] = '\0'; + + if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "q") == 0) { + uv_async_queue_send(&bttool.async, buffer); + break; + } + + uv_async_queue_send(&bttool.async, buffer); + + buffer = NULL; } - adapter_callback = bt_adapter_register_callback(g_bttool_ins, &g_adapter_cbs); - adapter_callback2 = bt_adapter_register_callback(g_bttool_ins, &g_adapter_cbs_2); - if (bt_adapter_get_state(g_bttool_ins) == BT_ADAPTER_STATE_ON) - bt_tool_init(g_bttool_ins); + uv_thread_join(&bttool.thread); + + return 0; +} +#else /* CONFIG_LIBUV_EXTENSION */ +int main(int argc, char** argv) +{ + int opt; + int _argc = 0; + char* _argv[32]; + char* buffer = NULL; + char* saveptr; + int ret; + size_t len, size = 0; + + while ((opt = getopt_long(argc, argv, "h-v-d", main_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + exit(0); + case 'v': + show_version(); + exit(0); + break; + default: + break; + } + } + + bttool_ins_init(NULL); while (1) { printf("bttool> "); fflush(stdout); memset(_argv, 0, sizeof(_argv)); -#ifdef __NuttX__ - len = readline_stream(buffer, CONFIG_NSH_LINELEN, stdin, stdout); -#else len = getline(&buffer, &size, stdin); - if (-1 == len) - continue; -#endif buffer[len] = '\0'; if (len < 0) - continue; + goto quit; if (buffer[0] == '!') { #ifdef CONFIG_SYSTEM_SYSTEM @@ -1786,20 +1929,10 @@ int main(int argc, char** argv) } } - if (bt_adapter_get_state(g_bttool_ins) != BT_ADAPTER_STATE_OFF) { -#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL - do_disable_wait(g_bttool_ins); -#endif - } - - bt_tool_uninit(g_bttool_ins); - bt_adapter_unregister_callback(g_bttool_ins, adapter_callback2); - bt_adapter_unregister_callback(g_bttool_ins, adapter_callback); - bluetooth_delete_instance(g_bttool_ins); +quit: + bttool_ins_uninit(NULL); free(buffer); - g_bttool_ins = NULL; - adapter_callback = NULL; - return 0; } +#endif /* CONFIG_LIBUV_EXTENSION */ \ No newline at end of file diff --git a/tools/bt_tools.h b/tools/bt_tools.h index 8626fae7..68d8e04e 100644 --- a/tools/bt_tools.h +++ b/tools/bt_tools.h @@ -33,6 +33,9 @@ #include "bt_debug.h" #include "utils.h" +#include "uv.h" +#include "uv_async_queue.h" + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -72,6 +75,13 @@ /**************************************************************************** * Public Types ****************************************************************************/ +typedef struct { + uv_loop_t loop; + uv_async_queue_t async; + uv_thread_t thread; + uv_sem_t ready; +} bttool_t; + typedef struct { char* cmd; /* command */ int (*func)(void* handle, int argc, char** argv); /* command func */ -- Gitee From 0ea5c8d0f592ab3401eedd8c4e8e7efcfd4f4c7a Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Mon, 4 Nov 2024 15:43:30 +0800 Subject: [PATCH 023/498] bttool: add adapter async api test cmd bug: v/46323 Signed-off-by: fangzhenwei --- CMakeLists.txt | 4 + Makefile | 3 + tools/async/gap.c | 1622 +++++++++++++++++++++++++++++++++++++++++++++ tools/bt_tools.c | 44 +- tools/bt_tools.h | 5 + 5 files changed, 1669 insertions(+), 9 deletions(-) create mode 100644 tools/async/gap.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f5710903..0c69ae70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -300,6 +300,10 @@ if(CONFIG_BLUETOOTH) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/utils.c) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/log.c) + if(CONFIG_BLUETOOTH_FRAMEWORK_ASYNC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/gap.c) + endif() + if(CONFIG_BLUETOOTH_BLE_ADV) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/adv.c) endif() diff --git a/Makefile b/Makefile index 65d90814..e7fd8228 100644 --- a/Makefile +++ b/Makefile @@ -204,6 +204,9 @@ ifeq ($(CONFIG_BLUETOOTH_TOOLS), y) CSRCS += tools/utils.c CSRCS += tools/log.c CSRCS += tools/uv_thread_loop.c +ifeq ($(CONFIG_BLUETOOTH_FRAMEWORK_ASYNC), y) + CSRCS += tools/async/gap.c +endif ifeq ($(CONFIG_BLUETOOTH_BLE_ADV), y) CSRCS += tools/adv.c endif diff --git a/tools/async/gap.c b/tools/async/gap.c new file mode 100644 index 00000000..534b17dd --- /dev/null +++ b/tools/async/gap.c @@ -0,0 +1,1622 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_async.h" +#include "bt_tools.h" +#include "utils.h" + +static void usage(void); +static int usage_cmd(void* handle, int argc, char** argv); +static int enable_cmd(void* handle, int argc, char** argv); +static int disable_cmd(void* handle, int argc, char** argv); +static int discovery_cmd(void* handle, int argc, char** argv); +static int get_state_cmd(void* handle, int argc, char** argv); +static int set_adapter_cmd(void* handle, int argc, char** argv); +static int get_adapter_cmd(void* handle, int argc, char** argv); +static int set_scanmode_cmd(void* handle, int argc, char** argv); +static int get_scanmode_cmd(void* handle, int argc, char** argv); +static int set_iocap_cmd(void* handle, int argc, char** argv); +static int get_iocap_cmd(void* handle, int argc, char** argv); +static int get_local_addr_cmd(void* handle, int argc, char** argv); +static int get_appearance_cmd(void* handle, int argc, char** argv); +static int set_appearance_cmd(void* handle, int argc, char** argv); +static int set_le_addr_cmd(void* handle, int argc, char** argv); +static int get_le_addr_cmd(void* handle, int argc, char** argv); +static int set_identity_addr_cmd(void* handle, int argc, char** argv); +static int set_scan_parameters_cmd(void* handle, int argc, char** argv); +static int get_local_name_cmd(void* handle, int argc, char** argv); +static int set_local_name_cmd(void* handle, int argc, char** argv); +static int get_local_cod_cmd(void* handle, int argc, char** argv); +static int set_local_cod_cmd(void* handle, int argc, char** argv); +static int pair_cmd(void* handle, int argc, char** argv); +static int pair_set_auto_cmd(void* handle, int argc, char** argv); +static int pair_reply_cmd(void* handle, int argc, char** argv); +static int pair_set_pincode_cmd(void* handle, int argc, char** argv); +static int pair_set_passkey_cmd(void* handle, int argc, char** argv); +static int pair_set_confirm_cmd(void* handle, int argc, char** argv); +static int pair_set_tk_cmd(void* handle, int argc, char** argv); +static int pair_set_oob_cmd(void* handle, int argc, char** argv); +static int pair_get_oob_cmd(void* handle, int argc, char** argv); +static int connect_cmd(void* handle, int argc, char** argv); +static int disconnect_cmd(void* handle, int argc, char** argv); +static int le_connect_cmd(void* handle, int argc, char** argv); +static int le_disconnect_cmd(void* handle, int argc, char** argv); +static int create_bond_cmd(void* handle, int argc, char** argv); +static int cancel_bond_cmd(void* handle, int argc, char** argv); +static int remove_bond_cmd(void* handle, int argc, char** argv); +static int device_show_cmd(void* handle, int argc, char** argv); +static int device_set_alias_cmd(void* handle, int argc, char** argv); +static int get_bonded_devices_cmd(void* handle, int argc, char** argv); +static int get_connected_devices_cmd(void* handle, int argc, char** argv); +static int search_cmd(void* handle, int argc, char** argv); +static int start_service_cmd(void* handle, int argc, char** argv); +static int stop_service_cmd(void* handle, int argc, char** argv); +static int set_phy_cmd(void* handle, int argc, char** argv); +static int dump_cmd(void* handle, int argc, char** argv); +static int quit_cmd(void* handle, int argc, char** argv); + +static struct option le_conn_options[] = { + { "addr", required_argument, 0, 'a' }, + { "type", required_argument, 0, 't' }, + { "defaults", no_argument, 0, 'd' }, + { "filter", required_argument, 0, 'f' }, + { "phy", required_argument, 0, 'p' }, + { "latency", required_argument, 0, 'l' }, + { "conn_interval_min", required_argument, 0, 0 }, + { "conn_interval_max", required_argument, 0, 0 }, + { "timeout", required_argument, 0, 'T' }, + { "scan_interval", required_argument, 0, 0 }, + { "scan_window", required_argument, 0, 0 }, + { "min_ce_length", required_argument, 0, 0 }, + { "max_ce_length", required_argument, 0, 0 }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +#define LE_CONN_USAGE "\n" \ + "\t -a or --addr, peer le device address\n" \ + "\t -t or --type, peer le device address type, address type(0:public,1:random,2:public_id,3:random_id)\n" \ + "\t -d or --default, use default parameter\n" \ + "\t -f or --filter, connection filter policy, (0:addr,1:whitelist)\n" \ + "\t -p or --phy, init phy type, (0:1M,1:2M,2:Coded)\n" \ + "\t -l or --latency, connection latency Range: 0x0000 to 0x01F3\n" \ + "\t --conn_interval_min, Range: 0x0006 to 0x0C80\n" \ + "\t --conn_interval_max, Range: 0x0006 to 0x0C80\n" \ + "\t -T or --timeout, supervision timeout Range: 0x000A to 0x0C80\n" \ + "\t --scan_interval, Range: 0x0004 to 0x4000\n" \ + "\t --scan_window, Range: 0x0004 to 0x4000\n" \ + "\t --min_ce_length, Range: 0x0000 to 0xFFFF\n" \ + "\t --max_ce_length, Range: 0x0000 to 0xFFFF\n" + +#define INQUIRY_USAGE "inquiry device\n" \ + "\t\t\t- start (Range: 1-48, i.e., 1.28-61.44s)\n" \ + "\t\t\t- stop" + +#define SET_LE_PHY_USAGE "set le tx and rx phy, params: (0:1M, 1:2M, 2:CODED)" + +static bt_command_t g_async_cmd_tables[] = { + { "enable", enable_cmd, 0, "enable stack" }, + { "disable", disable_cmd, 0, "disable stack" }, + { "state", get_state_cmd, 0, "get adapter state" }, + { "inquiry", discovery_cmd, 0, INQUIRY_USAGE }, + { "set", set_adapter_cmd, 0, "set adapter information, input \'set help\' show usage" }, + { "get", get_adapter_cmd, 0, "get adapter information, input \'get help\' show usage" }, + { "pair", pair_cmd, 0, "reply pair request, input \'pair help\' show usage" }, + { "connect", connect_cmd, 0, "connect classic peer device, params: " }, + { "disconnect", disconnect_cmd, 0, "disconnect peer device, params: " }, + { "leconnect", le_connect_cmd, 1, "connect le peer device, input \'leconnect -h\' show usage" }, + { "ledisconnect", le_disconnect_cmd, 0, "disconnect le peer device, params: " }, + { "createbond", create_bond_cmd, 0, "create bond, params: (0:BLE, 1:BREDR)" }, + { "cancelbond", cancel_bond_cmd, 0, "cancel bond, params: " }, + { "removebond", remove_bond_cmd, 0, "remove bond, params: (0:BLE, 1:BREDR)" }, + { "setalias", device_set_alias_cmd, 0, "set device alias, params: " }, + { "device", device_show_cmd, 0, "show device information, params: " }, + { "search", search_cmd, 0, "service serach , Not implemented" }, + { "start", start_service_cmd, 0, "start profile service, Not implemented" }, + { "stop", stop_service_cmd, 0, "stop profile service, Not implemented" }, + { "setphy", set_phy_cmd, 0, SET_LE_PHY_USAGE }, +#ifdef CONFIG_BLUETOOTH_BLE_ADV + { "adv", adv_command_exec, 0, "advertising cmd, input \'adv\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + { "scan", scan_command_exec, 0, "scan cmd, input \'scan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + { "a2dpsnk", a2dp_sink_command_exec, 0, "a2dp sink cmd, input \'a2dpsnk\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + { "a2dpsrc", a2dp_src_command_exec, 0, "a2dp source cmd, input \'a2dpsrc\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + { "hf", hfp_hf_command_exec, 0, "hands-free cmd, input \'hf\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + { "ag", hfp_ag_command_exec, 0, "audio-gateway cmd, input \'ag\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_SPP + { "spp", spp_command_exec, 0, "serial port cmd, input \'spp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + { "hidd", hidd_command_exec, 0, "hid device cmd, input \'hidd\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_PAN + { "pan", pan_command_exec, 0, "pan cmd, input \'pan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT + { "gattc", gattc_command_exec, 0, "gatt client cmd input \'gattc\' show usage" }, + { "gatts", gatts_command_exec, 0, "gatt server cmd input \'gatts\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + { "leas", leas_command_exec, 0, "lea server cmd, input \'leas\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + { "mcp", lea_mcp_command_exec, 0, "leaudio mcp cmd, input \'mcp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + { "ccp", lea_ccp_command_exec, 0, "lea ccp cmd, input \'ccp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + { "vmics", vmics_command_exec, 0, "vcp/micp server cmd, input \'vmics\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT + { "leac", leac_command_exec, 0, "lea client cmd, input \'leac\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + { "mcs", lea_mcs_command_exec, 0, "leaudio mcp cmd, input \'mcs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + { "tbs", lea_tbs_command_exec, 0, "lea tbs cmd, input \'tbs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + { "vmicp", vmicp_command_exec, 0, "vcp/micp client cmd, input \'vmicp\' show usage" }, +#endif + { "dump", dump_cmd, 0, "dump adapter state" }, + { "log", log_command, 0, "log control command" }, + { "help", usage_cmd, 0, "Usage for bttools" }, + { "quit", quit_cmd, 0, "Quit" }, + { "q", quit_cmd, 0, "Quit" }, +}; + +#define SET_IOCAP_USAGE "params: (0:displayonly, 1:yes&no, 2:keyboardonly, 3:no-in/no-out 4:keyboard&display)" +#define SET_CLASS_USAGE "params: , range in 0x0-0xFFFFFC, the 2 least significant shall be 0b00, example: 0x00640404" +#define SET_SCANPARAMS_USAGE "set scan parameters, params: (0: INQUIRY, 1: PAGE), (0: standard, 1: interlaced), (range in 18-4096), (range in 17-4096)" + +static bt_command_t g_set_cmd_tables[] = { + { "scanmode", set_scanmode_cmd, 0, "params: (0:none, 1:connectable 2:connectable&discoverable)" }, + { "iocap", set_iocap_cmd, 0, SET_IOCAP_USAGE }, + { "name", set_local_name_cmd, 0, "params: , example \"vela-bt\"" }, + { "class", set_local_cod_cmd, 0, SET_CLASS_USAGE }, + { "appearance", set_appearance_cmd, 0, "set le adapter appearance, params: " }, + { "leaddr", set_le_addr_cmd, 0, "set ble adapter addr, params: " }, + { "id", set_identity_addr_cmd, 0, "set ble identity addr, params: " }, + { "scanparams", set_scan_parameters_cmd, 0, SET_SCANPARAMS_USAGE }, + { "help", NULL, 0, "show set help info" }, + //{ "", , "set " }, +}; + +static bt_command_t g_get_cmd_tables[] = { + { "scanmode", get_scanmode_cmd, 0, "get adapter scan mode" }, + { "iocap", get_iocap_cmd, 0, "get adapter io capability" }, + { "addr", get_local_addr_cmd, 0, "get adapter local addr" }, + { "leaddr", get_le_addr_cmd, 0, "get ble adapter addr" }, + { "name", get_local_name_cmd, 0, "get adapter local name" }, + { "appearance", get_appearance_cmd, 0, "get le adapter appearance" }, + { "class", get_local_cod_cmd, 0, "get adapter local class of device" }, + { "bonded", get_bonded_devices_cmd, 0, "get bonded devices, params:(0:BLE, 1:BREDR)" }, + { "connected", get_connected_devices_cmd, 0, "get connected devices params:(0:BLE, 1:BREDR)" }, + { "help", NULL, 0, "show get help info" }, + //{ "", , "get " }, +}; + +#define PAIR_PASSKEY_USAGE "input ssp passkey, params: (0:BLE, 1:BREDR)(0 :reject, 1: accept)" +#define PAIR_CONFIRM_USAGE "set ssp confirmation, params: (0:BLE, 1:BREDR)(0 :reject, 1: accept)" + +static bt_command_t g_pair_cmd_tables[] = { + { "auto", pair_set_auto_cmd, 0, "enable pair auto reply, params: (0:disable, 1:enable)" }, + { "reply", pair_reply_cmd, 0, "reply the pair request, params: (0 :reject, 1: accept)" }, + { "pin", pair_set_pincode_cmd, 0, "input pin code, params: (0 :reject, 1: accept)" }, + { "passkey", pair_set_passkey_cmd, 0, PAIR_PASSKEY_USAGE }, + { "confirm", pair_set_confirm_cmd, 0, PAIR_CONFIRM_USAGE }, + { "set_tk", pair_set_tk_cmd, 0, "set oob temporary key for le legacy pairing: " }, + { "set_oob", pair_set_oob_cmd, 0, "set remote oob data for le sc pairing: " }, + { "get_oob", pair_get_oob_cmd, 0, "get local oob data for le sc pairing: " }, + { "help", NULL, 0, "show pair help info" }, + //{ "", , "set " }, +}; + +static void* adapter_callback_async = NULL; +static bool g_cmd_had_inited = false; + +extern bt_instance_t* g_bttool_ins; +extern bool g_auto_accept_pair; +extern bond_state_t g_bond_state; + +static void status_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + PRINT("%s status: %d", __func__, status); +} + +static void bt_tool_init(void* handle) +{ + if (g_cmd_had_inited) + return; + + g_cmd_had_inited = true; +} + +static void bt_tool_uninit(void* handle) +{ + if (!g_cmd_had_inited) + return; + + g_cmd_had_inited = false; +} + +static int enable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_enable_async(handle, status_cb, NULL); + return CMD_OK; +} + +static int disable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_disable_async(handle, status_cb, NULL); + return CMD_OK; +} + +static void get_state_cb(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata) +{ + PRINT("%s state: %d", __func__, state); +} + +static int get_state_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_state_async(handle, get_state_cb, NULL); + return CMD_OK; +} + +static int discovery_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (!strcmp(argv[0], "start")) { + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int timeout = atoi(argv[1]); + if (timeout <= 0 || timeout > 48) { + PRINT("%s, invalid timeout value:%d", __func__, timeout); + return CMD_INVALID_PARAM; + } + + PRINT("start discovery timeout:%d", timeout); + if (bt_adapter_start_discovery_async(handle, timeout, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else if (!strcmp(argv[0], "stop")) { + if (bt_adapter_cancel_discovery_async(handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else { + return CMD_USAGE_FAULT; + } + + return CMD_OK; +} + +static void set_usage(void) +{ + printf("Usage:\n" + "\tset [options] [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_set_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_set_cmd_tables[i].cmd, g_set_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tset help\n"); +} + +static void get_usage(void) +{ + printf("Usage:\n" + "\tget [options] [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_get_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_get_cmd_tables[i].cmd, g_get_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tget help\n"); +} + +static void pair_usage(void) +{ + printf("Usage:\n" + "\tpair [options] [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_pair_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_pair_cmd_tables[i].cmd, g_pair_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tpair help\n"); +} + +static int set_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + set_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_set_cmd_tables, ARRAY_SIZE(g_set_cmd_tables), argc, argv); + if (ret != CMD_OK) + set_usage(); + + return ret; +} + +static int get_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + get_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_get_cmd_tables, ARRAY_SIZE(g_get_cmd_tables), argc, argv); + if (ret != CMD_OK) + get_usage(); + + return ret; +} + +static int set_scanmode_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int scanmode = atoi(argv[0]); + if (scanmode > BT_BR_SCAN_MODE_CONNECTABLE_DISCOVERABLE) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_scan_mode_async(handle, scanmode, 1, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Scan Mode:%d set success", scanmode); + return CMD_OK; +} + +static void get_scanmode_cb(bt_instance_t* ins, bt_status_t status, bt_scan_mode_t mode, void* userdata) +{ + PRINT("Scan Mode:%d", mode); +} + +static int get_scanmode_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_scan_mode_async(handle, get_scanmode_cb, NULL); + return CMD_OK; +} + +static int set_iocap_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + + int iocap = *argv[0] - '0'; + if (iocap < BT_IO_CAPABILITY_DISPLAYONLY || iocap > BT_IO_CAPABILITY_KEYBOARDDISPLAY) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_io_capability_async(handle, iocap, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("IO Capability:%d set success", iocap); + return CMD_OK; +} + +static void get_iocap_cb(bt_instance_t* ins, bt_status_t status, bt_io_capability_t iocap, void* userdata) +{ + PRINT("IO Capability:%d", iocap); +} + +static int get_iocap_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_io_capability_async(handle, get_iocap_cb, NULL); + return CMD_OK; +} + +static void get_local_addr_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata) +{ + PRINT_ADDR("Local Address:[%s]", addr); +} + +static int get_local_addr_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_address_async(handle, get_local_addr_cb, NULL); + return CMD_OK; +} + +static void get_appearance_cb(bt_instance_t* ins, bt_status_t status, uint16_t appearance, void* userdata) +{ + PRINT("Le appearance:0x%04x", appearance); +} + +static int get_appearance_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_le_appearance_async(handle, get_appearance_cb, NULL); + return CMD_OK; +} + +static int set_appearance_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t appearance = strtoul(argv[0], NULL, 16); + bt_adapter_set_le_appearance_async(handle, appearance, status_cb, NULL); + PRINT("Set Le appearance:0x%04" PRIx32 "", appearance); + + return CMD_OK; +} + +static int set_le_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + bt_adapter_set_le_address_async(handle, &addr, status_cb, NULL); + + return CMD_OK; +} + +static void get_le_addr_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, ble_addr_type_t type, void* userdata) +{ + PRINT_ADDR("LE Address:%s, type:%d", addr, type); +} + +static int get_le_addr_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_le_address_async(handle, get_le_addr_cb, NULL); + return CMD_OK; +} + +static int set_identity_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) { + return CMD_INVALID_PARAM; + } + + bt_adapter_set_le_identity_address_async(handle, &addr, type, status_cb, NULL); + + return CMD_OK; +} + +static int set_scan_parameters_cmd(void* handle, int argc, char** argv) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int is_page = atoi(argv[0]); + if (is_page != 0 && is_page != 1) + return CMD_INVALID_PARAM; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) + return CMD_INVALID_PARAM; + + int interval = atoi(argv[2]); + if (interval < 0x12 || interval > 0x1000) + return CMD_INVALID_PARAM; + + int window = atoi(argv[3]); + if (window < 0x11 || window > 0x1000) + return CMD_INVALID_PARAM; + + if (!is_page) + bt_adapter_set_inquiry_scan_parameters_async(handle, type, interval, window, status_cb, NULL); + else + bt_adapter_set_page_scan_parameters_async(handle, type, interval, window, status_cb, NULL); + + return CMD_OK; +} + +static void get_local_name_cb(bt_instance_t* ins, bt_status_t status, const char* name, void* userdata) +{ + PRINT("Local Name:%s", name); +} + +static int get_local_name_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_name_async(handle, get_local_name_cb, NULL); + return CMD_OK; +} + +static int set_local_name_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + char* name = argv[0]; + if (strlen(name) > 63) { + PRINT("name length to long"); + return CMD_INVALID_PARAM; + } + + if (bt_adapter_set_name_async(handle, name, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local Name:%s set success", name); + return CMD_OK; +} + +static void get_local_cod_cb(bt_instance_t* ins, bt_status_t status, uint32_t cod, void* userdata) +{ + PRINT("Local class of device: 0x%08" PRIx32 ", is HEADSET: %s", cod, IS_HEADSET(cod) ? "true" : "false"); +} + +static int get_local_cod_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_device_class_async(handle, get_local_cod_cb, NULL); + return CMD_OK; +} + +static int set_local_cod_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t cod = strtol(argv[0], NULL, 16); + + if (cod > 0xFFFFFF || cod & 0x3) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_device_class_async(handle, cod, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local class of device:0x%08" PRIx32 " set success", cod); + return CMD_OK; +} + +static int pair_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + pair_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_pair_cmd_tables, ARRAY_SIZE(g_pair_cmd_tables), argc, argv); + if (ret != CMD_OK) + pair_usage(); + + return ret; +} + +extern bool g_auto_accept_pair; + +static int pair_set_auto_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + switch (*argv[0]) { + case '0': + g_auto_accept_pair = false; + break; + case '1': + g_auto_accept_pair = true; + break; + default: + return CMD_INVALID_PARAM; + break; + } + + PRINT("Auto accept pair:%s", g_auto_accept_pair ? "Enable" : "Disable"); + + return CMD_OK; +} + +static int pair_reply_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_pair_request_reply_async(handle, &addr, reply, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pair request %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static int pair_set_pincode_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + char* pincode = NULL; + uint8_t pincode_len = 0; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + pincode = argv[2]; + pincode_len = strlen(pincode); + } + + /* TODO: Check bond state*/ + if (bt_device_set_pin_code_async(handle, &addr, reply, pincode, pincode_len, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pincode request %s, code:%s", argv[0], reply ? "Accept" : "Reject", pincode); + return CMD_OK; +} + +static int pair_set_passkey_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + int passkey = 0; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + char tmp[7] = { 0 }; + strncpy(tmp, argv[3], 6); + passkey = atoi(tmp); + if (passkey > 1000000) { + PRINT("Invalid passkey"); + return CMD_INVALID_PARAM; + } + } + + /* TODO: Check bond state*/ + if (bt_device_set_pass_key_async(handle, &addr, transport, reply, passkey, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] passkey request %s, passkey:%d", argv[0], reply ? "Accept" : "Reject", passkey); + return CMD_OK; +} + +static int pair_set_confirm_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_set_pairing_confirmation_async(handle, &addr, transport, reply, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] ssp confirmation %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static void str2hex(char* src_str, uint8_t* dest_buf, uint8_t hex_number) +{ + uint8_t i; + uint8_t lb, hb; + + for (i = 0; i < hex_number; i++) { + lb = src_str[(i << 1) + 1]; + hb = src_str[i << 1]; + if (hb >= '0' && hb <= '9') { + dest_buf[i] = hb - '0'; + } else if (hb >= 'A' && hb < 'G') { + dest_buf[i] = hb - 'A' + 10; + } else if (hb >= 'a' && hb < 'g') { + dest_buf[i] = hb - 'a' + 10; + } else { + dest_buf[i] = 0; + } + + dest_buf[i] <<= 4; + if (lb >= '0' && lb <= '9') { + dest_buf[i] += lb - '0'; + } else if (lb >= 'A' && lb < 'G') { + dest_buf[i] += lb - 'A' + 10; + } else if (lb >= 'a' && lb < 'g') { + dest_buf[i] += lb - 'a' + 10; + } + } +} + +static int pair_set_tk_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t tk_val; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of temporary key is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], tk_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_legacy_tk_async(handle, &addr, tk_val, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set oob temporary key for le legacy pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_set_oob_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t c_val; + bt_128key_t r_val; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of confirmation value is insufficient"); + return CMD_INVALID_PARAM; + } + + if (strlen(argv[2]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of random value is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], c_val, sizeof(bt_128key_t)); + str2hex(argv[2], r_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_sc_remote_oob_data_async(handle, &addr, c_val, r_val, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set remote oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_get_oob_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_get_le_sc_local_oob_data_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Get local oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int connect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_connect_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] connecting", argv[0]); + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int le_connect_cmd(void* handle, int argc, char** argv) +{ + int opt, index = 0; + bt_address_t addr; + ble_addr_type_t addrtype = BT_LE_ADDR_TYPE_PUBLIC; + ble_connect_params_t params = { + .use_default_params = false, + .filter_policy = BT_LE_CONNECT_FILTER_POLICY_ADDR, + .init_phy = BT_LE_1M_PHY, + .scan_interval = 20, /* 12.5 ms */ + .scan_window = 20, /* 12.5 ms */ + .connection_interval_min = 24, /* 30 ms */ + .connection_interval_max = 24, /* 30 ms */ + .connection_latency = 0, + .supervision_timeout = 18, /* 180 ms */ + .min_ce_length = 0, + .max_ce_length = 0, + }; + + bt_addr_set_empty(&addr); + optind = 1; + while ((opt = getopt_long(argc, argv, "a:t:f:p:l:T:dh", le_conn_options, + &index)) + != -1) { + switch (opt) { + case 'a': { + if (bt_addr_str2ba(optarg, &addr) < 0) { + PRINT("Invalid addr:%s", optarg); + return CMD_INVALID_ADDR; + } + + } break; + case 't': { + int32_t type = atoi(optarg); + addrtype = type; + } break; + case 'd': { + params.use_default_params = true; + } break; + case 'f': { + int32_t filter = atoi(optarg); + if (filter != BT_LE_CONNECT_FILTER_POLICY_ADDR && filter != BT_LE_CONNECT_FILTER_POLICY_WHITE_LIST) { + PRINT("Invalid filter:%s", optarg); + return CMD_INVALID_PARAM; + } + + params.filter_policy = filter; + } break; + case 'p': { + int32_t phy = atoi(optarg); + if (!phy_is_vaild(phy)) { + PRINT("Invalid phy:%s", optarg); + return CMD_INVALID_PARAM; + } + params.init_phy = phy; + } break; + case 'l': { + int32_t latency = atoi(optarg); + if (latency < 0 || latency > 0x01F3) { + PRINT("Invalid latency:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_latency = latency; + } break; + case 'T': { + int32_t timeout = atoi(optarg); + if (timeout < 0x0A || timeout > 0x0C80) { + PRINT("Invalid supervision_timeout:%s", optarg); + return CMD_INVALID_PARAM; + } + params.supervision_timeout = timeout; + } break; + case 'h': { + PRINT("%s", LE_CONN_USAGE); + } break; + case 0: { + const char* curopt = le_conn_options[index].name; + int32_t val = atoi(optarg); + + if (strncmp(curopt, "conn_interval_min", strlen("conn_interval_min")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_min:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_min = val; + } else if (strncmp(curopt, "conn_interval_max", strlen("conn_interval_max")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_max:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_max = val; + } else if (strncmp(curopt, "scan_interval", strlen("scan_interval")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_interval:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_interval = val; + } else if (strncmp(curopt, "scan_window", strlen("scan_window")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_window:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_window = val; + } else if (strncmp(curopt, "min_ce_length", strlen("min_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid min_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.min_ce_length = val; + } else if (strncmp(curopt, "max_ce_length", strlen("max_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid max_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.max_ce_length = val; + } else { + return CMD_INVALID_OPT; + } + } break; + default: + return CMD_INVALID_OPT; + } + } + + if (bt_addr_is_empty(&addr)) + return CMD_INVALID_ADDR; + + if (bt_device_connect_le_async(handle, &addr, addrtype, ¶ms, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int le_disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect_le_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("LE Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int create_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_create_bond_async(handle, &addr, transport, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] create bond", argv[0]); + return CMD_OK; +} + +static int cancel_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + /* TODO: Check bond state*/ + if (bt_device_cancel_bond_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] cancel bond", argv[0]); + return CMD_OK; +} + +static int remove_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_remove_bond_async(handle, &addr, transport, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] remove bond", argv[0]); + return CMD_OK; +} + +static int set_phy_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int tx_phy, rx_phy; + tx_phy = atoi(argv[1]); + rx_phy = atoi(argv[2]); + if (!phy_is_vaild(tx_phy) || !phy_is_vaild(rx_phy)) { + PRINT("Invalid phy parameter, tx:%d, rx:%d", tx_phy, rx_phy); + return CMD_INVALID_PARAM; + } + + bt_device_set_le_phy_async(handle, &addr, tx_phy, rx_phy, status_cb, NULL); + + return CMD_OK; +} + +static const char* bond_state_to_string(bond_state_t state) +{ + switch (state) { + case BOND_STATE_NONE: + return "BOND_NONE"; + case BOND_STATE_BONDING: + return "BONDING"; + case BOND_STATE_BONDED: + return "BONDED"; + default: + return "UNKNOWN"; + } +} + +static void get_device_alias_cb(bt_instance_t* ins, bt_status_t status, const char* alias, void* userdata) +{ + PRINT("\tAlias: %s", alias); +} + +static void get_device_name_cb(bt_instance_t* ins, bt_status_t status, const char* alias, void* userdata) +{ + PRINT("\tNmae: %s", alias); +} + +static void get_device_class_cb(bt_instance_t* ins, bt_status_t status, uint32_t class, void* userdata) +{ + PRINT("\tClass: 0x%08" PRIx32 "", class); +} + +static void get_device_type_cb(bt_instance_t* ins, bt_status_t status, bt_device_type_t type, void* userdata) +{ + PRINT("\tDeviceType: %d", type); +} + +static void is_connected_cb(bt_instance_t* ins, bt_status_t status, bool connected, void* userdata) +{ + PRINT("\tIsConnected: %d", connected); +} + +static void is_encrypted_cb(bt_instance_t* ins, bt_status_t status, bool encrypted, void* userdata) +{ + PRINT("\tIsEncrypted: %d", encrypted); +} + +static void is_bonded_cb(bt_instance_t* ins, bt_status_t status, bool bonded, void* userdata) +{ + PRINT("\tIsBonded: %d", bonded); +} + +static void get_bond_state_cb(bt_instance_t* ins, bt_status_t status, bond_state_t state, void* userdata) +{ + PRINT("\tBondState: %s", bond_state_to_string(state)); +} + +static void is_bond_initiate_local_cb(bt_instance_t* ins, bt_status_t status, bool initiate, void* userdata) +{ + PRINT("\tIsBondInitiateLocal: %d", initiate); +} + +static void get_uuids_cb(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t uuid_cnt, void* userdata) +{ + PRINT("\tUUIDs:[%d]", uuid_cnt); + for (int i = 0; i < uuid_cnt; i++) { + char uuid_str[40] = { 0 }; + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\t\tuuid[%-2d]: %s", i, uuid_str); + } +} + +static void device_dump(void* handle, bt_address_t* addr, bt_transport_t transport) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("device [%s]", addr_str); + if (transport == BT_TRANSPORT_BREDR) { + bt_device_get_name_async(handle, addr, get_device_name_cb, NULL); + bt_device_get_alias_async(handle, addr, get_device_alias_cb, NULL); + bt_device_get_device_class_async(handle, addr, get_device_class_cb, NULL); + bt_device_get_device_type_async(handle, addr, get_device_type_cb, NULL); + bt_device_is_connected_async(handle, addr, transport, is_connected_cb, NULL); + bt_device_is_encrypted_async(handle, addr, transport, is_encrypted_cb, NULL); + bt_device_is_bonded_async(handle, addr, transport, is_bonded_cb, NULL); + bt_device_get_bond_state_async(handle, addr, transport, get_bond_state_cb, NULL); + bt_device_is_bond_initiate_local_async(handle, addr, transport, is_bond_initiate_local_cb, NULL); + bt_device_get_uuids_async(handle, addr, get_uuids_cb, NULL); + } else { + bt_device_is_connected_async(handle, addr, transport, is_connected_cb, NULL); + bt_device_is_encrypted_async(handle, addr, transport, is_encrypted_cb, NULL); + bt_device_is_bonded_async(handle, addr, transport, is_bonded_cb, NULL); + bt_device_get_bond_state_async(handle, addr, transport, get_bond_state_cb, NULL); + bt_device_is_bond_initiate_local_async(handle, addr, transport, is_bond_initiate_local_cb, NULL); + } +} + +static int device_show_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + device_dump(handle, &addr, BT_TRANSPORT_BREDR); + + return CMD_OK; +} + +static int device_set_alias_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) > 63) { + PRINT("alias length too long"); + return CMD_INVALID_PARAM; + } + + bt_device_set_alias_async(handle, &addr, argv[1], status_cb, NULL); + PRINT("Device: [%s] alias:%s set success", argv[0], argv[1]); + return CMD_OK; +} + +static void get_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, int transport, void* userdata) +{ + for (int i = 0; i < num; i++) { + device_dump(ins, addrs + i, transport); + } +} + +static void get_br_bonded_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("BREDR bonded device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static void get_le_bonded_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("LE bonded device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static int get_bonded_devices_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_bonded_devices_async(handle, transport, + transport == BT_TRANSPORT_BREDR ? get_br_bonded_devices_cb : get_le_bonded_devices_cb, NULL); + + return CMD_OK; +} + +static void get_br_connected_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("BREDR connected device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static void get_le_connected_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("LE connected device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} +static int get_connected_devices_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_connected_devices_async(handle, transport, + transport == BT_TRANSPORT_BREDR ? get_br_connected_devices_cb : get_le_connected_devices_cb, NULL); + return CMD_OK; +} + +static int search_cmd(void* handle, int argc, char** argv) +{ + PRINT("%s", __func__); + return CMD_OK; +} + +static int start_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int stop_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int usage_cmd(void* handle, int argc, char** argv) +{ + if (argc == 2 && !strcmp(argv[1], "me!!!")) + return -2; + + usage(); + + return CMD_OK; +} + +static int quit_cmd(void* handle, int argc, char** argv) +{ + return -2; +} + +static void usage(void) +{ + printf("Usage:\n" + "\tbttool [options] [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_async_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_async_cmd_tables[i].cmd, g_async_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tbttool --help\n"); +} + +int execute_async_command(void* handle, int argc, char* argv[]) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(g_async_cmd_tables); i++) { + if (strlen(g_async_cmd_tables[i].cmd) == strlen(argv[0]) && strncmp(g_async_cmd_tables[i].cmd, argv[0], strlen(argv[0])) == 0) { + if (g_async_cmd_tables[i].func) { + if (g_async_cmd_tables[i].opt) + ret = g_async_cmd_tables[i].func(handle, argc, &argv[0]); + else + ret = g_async_cmd_tables[i].func(handle, argc - 1, &argv[1]); + if (g_async_cmd_tables[i].func == quit_cmd) + return -2; + return ret; + } + } + } + + PRINT("UnKnow command %s", argv[0]); + usage(); + + return CMD_UNKNOWN; +} + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + PRINT("Context:%p, Adapter state changed: %d", cookie, state); + if (state == BT_ADAPTER_STATE_ON) { + + bt_tool_init(g_bttool_ins); + /* get name */ + bt_adapter_get_name_async(g_bttool_ins, get_local_name_cb, NULL); + /* get io cap */ + bt_adapter_get_io_capability_async(g_bttool_ins, get_iocap_cb, NULL); + /* get class */ + bt_adapter_get_device_class_async(g_bttool_ins, get_local_cod_cb, NULL); + /* get scan mode */ + bt_adapter_get_scan_mode_async(g_bttool_ins, get_scanmode_cb, NULL); + /* enable key derivation */ + bt_adapter_le_enable_key_derivation_async(g_bttool_ins, true, true, status_cb, NULL); + bt_adapter_set_page_scan_parameters_async(g_bttool_ins, BT_BR_SCAN_TYPE_INTERLACED, 0x400, 0x24, status_cb, NULL); + } else if (state == BT_ADAPTER_STATE_TURNING_OFF) { + /* code */ + bt_tool_uninit(g_bttool_ins); + } else if (state == BT_ADAPTER_STATE_OFF) { + /* do something */ + } +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + PRINT("Discovery state: %s", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + PRINT_ADDR("Inquiring: device [%s], name: %s, cod: %08" PRIx32 ", is HEADSET: %s, rssi: %d", + &result->addr, result->name, result->cod, IS_HEADSET(result->cod) ? "true" : "false", result->rssi); +} + +static void on_scan_mode_changed_cb(void* cookie, bt_scan_mode_t mode) +{ + PRINT("Adapter new scan mode: %d", mode); +} + +static void on_device_name_changed_cb(void* cookie, const char* device_name) +{ + PRINT("Adapter update device name: %s", device_name); +} + +static void on_pair_request_cb(void* cookie, bt_address_t* addr) +{ + if (g_auto_accept_pair) + bt_device_pair_request_reply_async(g_bttool_ins, addr, true, status_cb, NULL); + + PRINT_ADDR("Incoming pair request from [%s] %s", addr, g_auto_accept_pair ? "auto accepted" : "please reply"); +} + +#define LINK_TYPE(trans_) (trans_ == BT_TRANSPORT_BREDR ? "BREDR" : "LE") + +static void on_pair_display_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t passkey) +{ + uint8_t ret = 0; + char buff[128] = { 0 }; + char buff1[64] = { 0 }; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + sprintf(buff, "Pair Display [%s][%s]", addr_str, LINK_TYPE(transport)); + switch (type) { + case PAIR_TYPE_PASSKEY_CONFIRMATION: + if (!g_auto_accept_pair) { + sprintf(buff1, "[SSP][CONFIRM][%" PRIu32 "] please reply:", passkey); + break; + } + ret = bt_device_set_pairing_confirmation_async(g_bttool_ins, addr, transport, true, status_cb, NULL); + sprintf(buff1, "[SSP][CONFIRM] Auto confirm [%" PRIu32 "] %s", passkey, ret == BT_STATUS_SUCCESS ? "SUCCESS" : "FAILED"); + break; + case PAIR_TYPE_PASSKEY_ENTRY: + sprintf(buff1, "[SSP][ENTRY][%" PRIu32 "], please reply:", passkey); + break; + case PAIR_TYPE_CONSENT: + sprintf(buff1, "[SSP][CONSENT]"); + break; + case PAIR_TYPE_PASSKEY_NOTIFICATION: + sprintf(buff1, "[SSP][NOTIFY][%" PRIu32 "]", passkey); + break; + case PAIR_TYPE_PIN_CODE: + sprintf(buff1, "[PIN] please reply:"); + break; + } + strcat(buff, buff1); + PRINT("%s", buff); +} + +static void on_connect_request_cb(void* cookie, bt_address_t* addr) +{ + bt_device_connect_request_reply_async(g_bttool_ins, addr, true, status_cb, NULL); + PRINT_ADDR("Incoming connect request from [%s], auto accepted", addr); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + PRINT_ADDR("Device [%s][%s] connection state: %d", addr, LINK_TYPE(transport), state); +} + +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + g_bond_state = state; + PRINT_ADDR("Device [%s][%s] bond state: %s, is_ctkd: %d", addr, LINK_TYPE(transport), bond_state_to_string(state), is_ctkd); +} + +static void on_le_sc_local_oob_data_got_cb(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + PRINT_ADDR("Generate local oob data for le secure connection pairing with [%s]:", addr); + + printf("\tConfirmation value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", c_val[i]); + } + printf("\n"); + + printf("\tRandom value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", r_val[i]); + } + printf("\n"); +} + +static void on_remote_name_changed_cb(void* cookie, bt_address_t* addr, const char* name) +{ + PRINT_ADDR("Device [%s] name changed: %s", addr, name); +} + +static void on_remote_alias_changed_cb(void* cookie, bt_address_t* addr, const char* alias) +{ + PRINT_ADDR("Device [%s] alias changed: %s", addr, alias); +} + +static void on_remote_cod_changed_cb(void* cookie, bt_address_t* addr, uint32_t cod) +{ + PRINT_ADDR("Device [%s] class changed: 0x%08" PRIx32 "", addr, cod); +} + +static void on_remote_uuids_changed_cb(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + char uuid_str[40] = { 0 }; + + PRINT_ADDR("Device [%s] uuids changed", addr); + + if (size) { + PRINT("UUIDs:[%d]", size); + for (int i = 0; i < size; i++) { + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\tuuid[%-2d]: %s", i, uuid_str); + } + } +} + +const static adapter_callbacks_t g_adapter_async_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_scan_mode_changed = on_scan_mode_changed_cb, + .on_device_name_changed = on_device_name_changed_cb, + .on_pair_request = on_pair_request_cb, + .on_pair_display = on_pair_display_cb, + .on_connect_request = on_connect_request_cb, + .on_connection_state_changed = on_connection_state_changed_cb, + .on_bond_state_changed = on_bond_state_changed_cb, + .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, + .on_remote_name_changed = on_remote_name_changed_cb, + .on_remote_alias_changed = on_remote_alias_changed_cb, + .on_remote_cod_changed = on_remote_cod_changed_cb, + .on_remote_uuids_changed = on_remote_uuids_changed_cb, +}; + +static void register_callback_cb(bt_instance_t* ins, bt_status_t status, void* cookie, void* userdata) +{ + *(void**)userdata = cookie; +} + +static void state_on_cb(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata) +{ + PRINT("%s state: %d", __func__, state); + + if (state == BT_ADAPTER_STATE_ON) + bt_tool_init(g_bttool_ins); +} + +static void ipc_connected(bt_instance_t* ins, void* userdata) +{ + PRINT("ipc connected"); + + bt_adapter_register_callback_async(ins, &g_adapter_async_cbs, register_callback_cb, &adapter_callback_async); + bt_adapter_get_state_async(ins, state_on_cb, NULL); +} + +static void ipc_disconnected(bt_instance_t* ins, void* userdata, int status) +{ + PRINT("ipc disconnected"); +} + +int bttool_async_ins_init(bttool_t* bttool) +{ + g_bttool_ins = bluetooth_create_async_instance(&bttool->loop, ipc_connected, ipc_disconnected, (void*)bttool); + if (g_bttool_ins == NULL) { + PRINT("create instance error\n"); + return -1; + } + + return 0; +} + +void bttool_async_ins_uninit(bttool_t* bttool) +{ + bt_tool_uninit(g_bttool_ins); + bt_adapter_unregister_callback_async(g_bttool_ins, adapter_callback_async, NULL, NULL); + bluetooth_delete_async_instance(g_bttool_ins); + g_bttool_ins = NULL; + adapter_callback_async = NULL; +} \ No newline at end of file diff --git a/tools/bt_tools.c b/tools/bt_tools.c index 8feb0e5f..19c3f2ba 100644 --- a/tools/bt_tools.c +++ b/tools/bt_tools.c @@ -77,9 +77,11 @@ static int set_phy_cmd(void* handle, int argc, char** argv); static int dump_cmd(void* handle, int argc, char** argv); static int quit_cmd(void* handle, int argc, char** argv); -static bt_instance_t* g_bttool_ins = NULL; +bt_instance_t* g_bttool_ins = NULL; static void* adapter_callback = NULL; static bool g_cmd_had_inited = false; +bool g_auto_accept_pair = true; +bond_state_t g_bond_state = BOND_STATE_NONE; static struct { int cmd_err_code; @@ -96,6 +98,7 @@ static struct { }; static struct option main_options[] = { + { "async", 0, 0, 'a' }, { "help", 0, 0, 'h' }, { "version", 0, 0, 'v' }, { 0, 0, 0, 0 } @@ -739,9 +742,6 @@ static int pair_cmd(void* handle, int argc, char** argv) return ret; } -static bool g_auto_accept_pair = true; -static bond_state_t g_bond_state = BOND_STATE_NONE; - static int pair_set_auto_cmd(void* handle, int argc, char** argv) { if (argc < 1) @@ -1711,10 +1711,22 @@ static void bttool_execute_command_cb(uv_async_queue_t* handle, void* buffer) // 2. execute command if (_argc > 0) { - ret = execute_command(g_bttool_ins, _argc, _argv); + if (bttool->async_api) { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + ret = execute_async_command(g_bttool_ins, _argc, _argv); +#else + ret = CMD_INVALID_OPT; +#endif + } else + ret = execute_command(g_bttool_ins, _argc, _argv); if (ret != CMD_OK) { if (ret == -2) { - bttool_ins_uninit(bttool); + if (bttool->async_api) { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool_async_ins_uninit(bttool); +#endif + } else + bttool_ins_uninit(bttool); uv_async_queue_close(handle, handle_close_cb); } else PRINT("cmd execute error: [%s]", cmd_err_str(ret)); @@ -1762,7 +1774,13 @@ static void bttool_thread(void* data) /* initialize synchronous or asynchronous instance. and register callbacks. */ - bttool_ins_init(bttool); + if (!bttool->async_api) { + bttool_ins_init(bttool); + } else { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool_async_ins_init(bttool); +#endif + } /* This code is used to start the event loop until there are no more events to process. */ bttool_command_uvloop_run(bttool); @@ -1805,10 +1823,18 @@ int main(int argc, char** argv) char* buffer = NULL; int ret; size_t len, size = 0; - bttool_t bttool; + bttool_t bttool = { .async_api = false }; - while ((opt = getopt_long(argc, argv, "h-v-d", main_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "a-h-v-d", main_options, NULL)) != -1) { switch (opt) { + case 'a': +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool.async_api = true; + break; +#else + PRINT("async not supported"); + return -1; +#endif case 'h': usage(); exit(0); diff --git a/tools/bt_tools.h b/tools/bt_tools.h index 68d8e04e..b68f2655 100644 --- a/tools/bt_tools.h +++ b/tools/bt_tools.h @@ -80,6 +80,7 @@ typedef struct { uv_async_queue_t async; uv_thread_t thread; uv_sem_t ready; + bool async_api; } bttool_t; typedef struct { @@ -89,6 +90,10 @@ typedef struct { char* help; /* usage */ } bt_command_t; +int execute_async_command(void* handle, int argc, char* argv[]); +int bttool_async_ins_init(bttool_t* bttool); +void bttool_async_ins_uninit(bttool_t* bttool); + int execute_command_in_table(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[]); int execute_command_in_table_offset(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[], uint8_t offset); -- Gitee From 3d3a7ac582c1cd7e09e5503af8e78522634ae46b Mon Sep 17 00:00:00 2001 From: chengkai Date: Mon, 27 Jan 2025 10:59:13 +0800 Subject: [PATCH 024/498] bluetooth: fix zephyr 4.0 gatt build break bug: v/53041 Signed-off-by: chengkai --- CMakeLists.txt | 17 +++++++++++++++++ Makefile | 15 +++++++++++++++ .../stacks/zephyr/sal_adapter_le_interface.c | 1 - .../stacks/zephyr/sal_le_advertise_interface.c | 6 +++--- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c69ae70..41530fe7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,23 @@ if(CONFIG_BLUETOOTH) endif() endif() + if(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_adapter_le_interface.c) + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_le_advertise_interface.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_le_scan_interface.c) + endif() + + if(CONFIG_BLUETOOTH_GATT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_gatt_client_interface.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_gatt_server_interface.c) + endif() + endif() + if(NOT CONFIG_BLUETOOTH_BLE_AUDIO) file(GLOB EXLUDE_FILES ${BLUETOOTH_DIR}/service/stacks/bluelet/sal_lea_*) list(REMOVE_ITEM CSRCS ${EXLUDE_FILES}) diff --git a/Makefile b/Makefile index e7fd8228..1eddaa08 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,21 @@ endif #CONFIG_BLUETOOTH_A2DP ifneq ($(CONFIG_BLUETOOTH_AVRCP_CONTROL)$(CONFIG_BLUETOOTH_AVRCP_TARGET),) CSRCS += service/stacks/zephyr/sal_avrcp_interface.c endif #CONFIG_BLUETOOTH_AVRCP_CONTROL/CONFIG_BLUETOOTH_AVRCP_TARGET + +ifeq ($(CONFIG_BLUETOOTH_STACK_LE_ZBLUE), y) + CSRCS += service/stacks/zephyr/sal_adapter_le_interface.c +ifeq ($(CONFIG_BLUETOOTH_BLE_ADV), y) + CSRCS += service/stacks/zephyr/sal_le_advertise_interface.c +endif #CONFIG_BLUETOOTH_BLE_ADV +ifeq ($(CONFIG_BLUETOOTH_BLE_SCAN), y) + CSRCS += service/stacks/zephyr/sal_le_scan_interface.c +endif #CONFIG_BLUETOOTH_BLE_SCAN +ifeq ($(CONFIG_BLUETOOTH_GATT), y) + CSRCS += service/stacks/zephyr/sal_gatt_client_interface.c + CSRCS += service/stacks/zephyr/sal_gatt_server_interface.c +endif #CONFIG_BLUETOOTH_GATT +endif #CONFIG_BLUETOOTH_STACK_LE_ZBLUE + endif ifeq ($(CONFIG_BLUETOOTH_BLE_AUDIO),) CSRCS := $(filter-out $(wildcard service/stacks/bluelet/sal_lea_*),$(wildcard $(CSRCS))) diff --git a/service/stacks/zephyr/sal_adapter_le_interface.c b/service/stacks/zephyr/sal_adapter_le_interface.c index 6b447bee..3a7e4ff3 100644 --- a/service/stacks/zephyr/sal_adapter_le_interface.c +++ b/service/stacks/zephyr/sal_adapter_le_interface.c @@ -727,7 +727,6 @@ static void STACK_CALL(create_bond)(void* args) { sal_adapter_req_t* req = args; struct bt_conn* conn; - struct bt_conn_info info; int err; conn = get_le_conn_from_addr(&req->addr); diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c index acee4d89..60aa8714 100644 --- a/service/stacks/zephyr/sal_le_advertise_interface.c +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -103,20 +103,20 @@ static bt_status_t zblue_le_ext_convert_param(ble_adv_params_t* params, struct b case BT_LE_ADV_DIRECT_IND: case BT_LE_ADV_SCAN_IND: param->options |= BT_LE_ADV_OPT_SCANNABLE; - param->options |= BT_LE_ADV_OPT_CONNECTABLE; + param->options |= BT_LE_ADV_OPT_CONN; break; case BT_LE_ADV_NONCONN_IND: param->options |= BT_LE_ADV_OPT_EXT_ADV; break; case BT_LE_SCAN_RSP: - param->options |= BT_LE_ADV_OPT_CONNECTABLE; + param->options |= BT_LE_ADV_OPT_CONN; param->options |= BT_LE_ADV_OPT_SCANNABLE; param->options |= BT_LE_ADV_OPT_EXT_ADV; break; case BT_LE_LEGACY_ADV_IND: case BT_LE_LEGACY_ADV_DIRECT_IND: case BT_LE_LEGACY_ADV_SCAN_IND: - param->options |= BT_LE_ADV_OPT_CONNECTABLE; + param->options |= BT_LE_ADV_OPT_CONN; break; case BT_LE_LEGACY_ADV_NONCONN_IND: break; -- Gitee From 26c85626174feae17b448f92f2d75ee7d984d4cc Mon Sep 17 00:00:00 2001 From: chengkai Date: Mon, 27 Jan 2025 11:04:50 +0800 Subject: [PATCH 025/498] bluetooth: fix bredr and gatt co-exist connection fail bug: v/53041 Signed-off-by: chengkai --- .../zephyr/sal_adapter_classic_interface.c | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index f30a5cc2..779f2ea3 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -36,6 +36,7 @@ #include +#include "sal_adapter_le_interface.h" #include "sal_interface.h" #include "utils/log.h" @@ -1113,18 +1114,45 @@ connection_state_t bt_sal_get_connection_state(bt_controller_id_t id, bt_address uint16_t bt_sal_get_acl_connection_handle(bt_controller_id_t id, bt_address_t* addr, bt_transport_t trasnport) { -#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT UNUSED(id); struct bt_conn_info info; - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct bt_conn* conn = NULL; + + if (trasnport == BT_TRANSPORT_BLE) { +#ifdef CONFIG_BLUETOOTH_LE_SUPPORT + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } - bt_conn_get_info(conn, &info); - bt_conn_unref(conn); + if (bt_conn_get_info(conn, &info)) { + BT_LOGE("%s, bt_conn_get_info fail", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } - return info.handle; -#else - return BT_INVALID_CONNECTION_HANDLE; + return info.handle; +#endif + } else if (trasnport == BT_TRANSPORT_BREDR) { +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } + + if (bt_conn_get_info(conn, &info)) { + BT_LOGE("%s, bt_conn_get_info fail", __func__); + bt_conn_unref(conn); + return BT_INVALID_CONNECTION_HANDLE; + } + + bt_conn_unref(conn); + return info.handle; #endif + } + + return BT_INVALID_CONNECTION_HANDLE; } uint16_t bt_sal_get_sco_connection_handle(bt_controller_id_t id, bt_address_t* addr) -- Gitee From f1bdb828cedd8a908b1d7e6ee706643843e98c29 Mon Sep 17 00:00:00 2001 From: chengkai Date: Mon, 27 Jan 2025 11:56:29 +0800 Subject: [PATCH 026/498] bluetooth: fix ci build break with clang format check bug: v/53041 Signed-off-by: chengkai --- service/stacks/zephyr/sal_adapter_classic_interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index 779f2ea3..c10a7785 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -29,10 +29,10 @@ #include "service_loop.h" #include +#include #include #include #include -#include #include -- Gitee From c73eb645a61e947ddc5333ff538b5a67a4ad2f76 Mon Sep 17 00:00:00 2001 From: youhaopan Date: Fri, 14 Feb 2025 11:59:19 +0800 Subject: [PATCH 027/498] After processing the AVRC_GET_ELEMENT_ATTRIBUTES_RSP message, the AVRCP CT service does not free the allocated memory, which leads to memory leaks. --- service/profiles/avrcp/avrcp_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/profiles/avrcp/avrcp_msg.c b/service/profiles/avrcp/avrcp_msg.c index c3785343..544e4daf 100644 --- a/service/profiles/avrcp/avrcp_msg.c +++ b/service/profiles/avrcp/avrcp_msg.c @@ -52,7 +52,7 @@ avrcp_msg_t* avrcp_msg_new(rc_msg_id_t msg, bt_address_t* bd_addr) void avrcp_msg_destory(avrcp_msg_t* avrcp_msg) { - if (avrcp_msg->id == AVRC_GET_ELEMENT_ATTR_REQ) { + if (avrcp_msg->id == AVRC_GET_ELEMENT_ATTRIBUTES_RSP) { for (int i = 0; i < avrcp_msg->data.attrs.count; i++) { if (avrcp_msg->data.attrs.attrs[i] != NULL) { free(avrcp_msg->data.attrs.attrs[i]); -- Gitee From d26e4a639888d5718653c55721805f0d68c478dc Mon Sep 17 00:00:00 2001 From: jialu Date: Wed, 6 Nov 2024 23:19:15 +0800 Subject: [PATCH 028/498] Bluetooth: Introducing the AVRCP function. bug: v/46573 Rootcause: Add comments to the AVRCP .h file and improve function documentation. Signed-off-by: jialu --- framework/include/bt_avrcp.h | 33 ++++++ framework/include/bt_avrcp_control.h | 67 ++++++++++-- framework/include/bt_avrcp_target.h | 152 +++++++++++++++++++++++---- 3 files changed, 222 insertions(+), 30 deletions(-) diff --git a/framework/include/bt_avrcp.h b/framework/include/bt_avrcp.h index 9a482f74..65a09ce0 100644 --- a/framework/include/bt_avrcp.h +++ b/framework/include/bt_avrcp.h @@ -21,6 +21,9 @@ #include "bt_addr.h" #include "bt_device.h" +/** + * @cond + */ #define AVRCP_MAX_ATTR_COUNT 9 #define AVRCP_ATTR_MAX_TIELE_LEN 64 #define AVRCP_ATTR_MAX_ARTIST_LEN 64 @@ -153,7 +156,37 @@ typedef enum { AVRCP_ATTR_PLAYING_TIME_MS, AVRCP_ATTR_COVER_ART_HANDLE } avrcp_media_attr_type_t; +/** + * @endcond + */ +/** + * @brief Callback for AVRCP connection state changed. + * + * There are four states for an AVRCP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DISCONNECTING. During the initialization phase of the AVRCP, + * it is necessary to register callback functions. This callback is triggered + * when there is a change in the state of the AVRCP connection. + * + * Stable States: + * DISCONNECTED: The initial state. + * CONNECTED: The AVRCP connection is established. + * Transient states: + * CONNECTING: The AVRCP connection is being established. + * DISCONNECTING: The AVRCP connection is being terminated. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - AVRCP profile connection state. + * + * **Example:** + * @code +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + printf("AVRCP connection state is: %d", state); +} + * @endcode + */ typedef void (*avrcp_connection_state_callback)(void* cookie, bt_address_t* addr, profile_connection_state_t state); #endif /* __BT_AVRCP_H__ */ diff --git a/framework/include/bt_avrcp_control.h b/framework/include/bt_avrcp_control.h index 137531b5..f19fd96f 100644 --- a/framework/include/bt_avrcp_control.h +++ b/framework/include/bt_avrcp_control.h @@ -21,7 +21,6 @@ /** * @cond */ - typedef struct { uint32_t attr_id; uint16_t chr_set; @@ -45,35 +44,85 @@ typedef struct { avrcp_connection_state_callback connection_state_cb; avrcp_get_element_attribute_cb get_element_attribute_cb; } avrcp_control_callbacks_t; - /** * @endcond */ /** - * @brief Register callback functions to AVRCP Control + * @brief Register callback functions to AVRCP Controller service. + * + * When initializing an AVRCP Controller, an application should register callback functions + * to the AVRCP Controller service. Subsequently, the AVRCP Controller service will + * notify the application of any state changes via the registered callback functions. + * + * Callback functions includes: + * * connection_state_cb + * * get_element_attribute_cb * * @param ins - Bluetooth client instance. - * @param callbacks - AVRCP Control callback functions. - * @return void* - Callbacks cookie. + * @param callbacks - AVRCP Controller callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL, + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). + * + * **Example:** + * @code +static const avrcp_control_callbacks_t avrcp_control_cbs = { + .size = sizeof(avrcp_control_cbs), + .connection_state_cb = avrcp_control_connection_state_cb, + .get_element_attribute_cb = avrcp_control_get_element_attribute_cb, +}; + +void avrcp_control_init(void* ins) +{ + static void* control_cbks_cookie; + + control_cbks_cookie = bt_avrcp_control_register_callbacks(ins, &avrcp_control_cbs); +} + * @endcode */ void* BTSYMBOLS(bt_avrcp_control_register_callbacks)(bt_instance_t* ins, const avrcp_control_callbacks_t* callbacks); /** - * @brief Unregister callback functions to AVRCP Control + * @brief Unregister callback functions from AVRCP Controller service. + * + * An application may use this interface to stop listening on the AVRCP Controller + * callbacks and to release the associated resources. * * @param ins - Bluetooth client instance. * @param cookie - Callbacks cookie. - * @return bool - True, if unregister success, false otherwise. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +void avrcp_control_uninit(void* ins) +{ + bt_avrcp_control_unregister_callbacks(ins, control_cbks_cookie); +} + * @endcode */ bool BTSYMBOLS(bt_avrcp_control_unregister_callbacks)(bt_instance_t* ins, void* cookie); /** - * @brief Get element attribute from peer device. + * @brief Get element attributes from AVRCP Target. + * + * This function is used when an application wants to obtain song information + * from an AVRCP Target device, including title, artist name, album name, track + * number, total number of tracks, genre, playing time. * * @param ins - Bluetooth client instance. - * @param addr - Remote BT address. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +bt_status_t start_get_element_attributes(bt_instance_t* ins, bt_address_t* addr) +{ + bt_status_t ret = bt_avrcp_control_get_element_attributes(ins, addr); + + return ret; +} */ bt_status_t BTSYMBOLS(bt_avrcp_control_get_element_attributes)(bt_instance_t* ins, bt_address_t* addr); diff --git a/framework/include/bt_avrcp_target.h b/framework/include/bt_avrcp_target.h index 0d40ceb8..9bade18f 100644 --- a/framework/include/bt_avrcp_target.h +++ b/framework/include/bt_avrcp_target.h @@ -25,38 +25,68 @@ extern "C" { #endif /** - * @brief Received Get Play Status request from CT. + * @brief Callback for playing status request from AVRCP Controller. + * + * During the initialization process of an AVRCP target, callback functions are registered. + * This callback is triggered when a request for the current player status is received by + * the AVRCP Target from an AVRCP Controller. + * + * The status of the player includes: playing, paused, stopped, seek forward, and seek rewind. * * @param cookie - Callback cookie. - * @param addr - Remote BT address. - * @return void. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_get_play_status_request_cb(void* cookie, bt_address_t* addr) +{ + avrcp_play_status_t status = AVRCP_PLAY_STATUS_PLAYING; + uint32_t song_len = 180000; + uint32_t song_pos = 10000; + bt_instance_t* ins = cookie; + bt_avrcp_target_get_play_status_response(ins, addr, status, song_len, song_pos); +} + * @endcode */ typedef void (*avrcp_received_get_play_status_request_callback)(void* cookie, bt_address_t* addr); /** - * @brief Received register notification request callback. + * @brief Callback for register notification request. + * + * All player application attributes can be registered as events by an AVRCP Controller. + * When an AVRCP Controller has registered a specific event to an AVRCP Target, the Target + * shall notify the Controller on change in value of the registered event. In particular, + * a notify command terminates after providing a corresponding change. * * @param cookie - Callback cookie. - * @param addr - Remote BT address. - * @param event - The event for which the CT requires notifications. - * @param interval - Only works in PLAY_POS_CHANGED event. + * @param addr - The Bluetooth address of the peer device. + * @param event - The event that the AVRCP Controller wants to be notified about. + * @param interval - Applicable only for PLAY_POS_CHANGED event. + * + * **Example:** + * @code +uint16_t notify_event = 0; +static void on_register_notification_request_cb(void* cookie, bt_address_t* addr, avrcp_notification_event_t event, uint32_t interval) +{ + notify_event |= event; +} + * @endcode */ typedef void (*avrcp_received_register_notification_request_callback)(void* cookie, bt_address_t* addr, avrcp_notification_event_t event, uint32_t interval); /** - * @brief Received panel operation from CT. + * @brief Callback for panel operation. * * @param cookie - Callback cookie. - * @param addr - Remote BT address. + * @param addr - The Bluetooth address of the peer device. * @param op - Panel operation. - * @param state - Key state + * @param state - Key state. */ typedef void (*avrcp_received_panel_operation_callback)(void* cookie, bt_address_t* addr, uint8_t op, uint8_t state); /** * @cond */ - typedef struct { size_t size; avrcp_connection_state_callback connection_state_cb; @@ -64,49 +94,129 @@ typedef struct { avrcp_received_register_notification_request_callback received_register_notification_request_cb; avrcp_received_panel_operation_callback received_panel_operation_cb; } avrcp_target_callbacks_t; - /** * @endcond */ /** - * @brief Register callback functions to AVRCP Target + * @brief Register callback functions to AVRCP Target service. + * + * When initializing an AVRCP Target, an application should register callback functions + * to the AVRCP Target service. Subsequently, the AVRCP Target service will notify the + * application of any state changes via the registered callback functions. + * + * Callback functions includes: + * * connection_state_cb + * * received_get_play_status_request_cb + * * received_register_notification_request_cb + * * received_panel_operation_cb + * * * @param ins - Bluetooth client instance. * @param callbacks - AVRCP Target callback functions. - * @return void* - Callbacks cookie. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL, + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). + * + * **Example:** + * @code +const static avrcp_target_callbacks_t g_avrcp_target_cbs = { + .size = sizeof(avrcp_target_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .received_get_play_status_request_cb = on_get_play_status_request_cb, + .received_register_notification_request_cb = on_register_notification_request_cb, + .received_panel_operation_cb = on_received_panel_operation_cb, +}; + +void avrcp_target_init(void* ins) +{ + static void* target_cbks_cookie; + + target_cbks_cookie = bt_avrcp_target_register_callbacks(ins, &g_avrcp_target_cbs); +} + * @endcode */ void* BTSYMBOLS(bt_avrcp_target_register_callbacks)(bt_instance_t* ins, const avrcp_target_callbacks_t* callbacks); /** - * @brief Unregister callback functions to AVRCP Target + * @brief Unregister callback functions to AVRCP Target service. + * + * An application may use this interface to stop listening on the AVRCP Target + * callbacks and to release the associated resources. * * @param ins - Bluetooth client instance. * @param cookie - Callbacks cookie. - * @return bool - True, if unregister success, false otherwise. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +void avrcp_target_uninit(void* ins) +{ + bt_avrcp_target_unregister_callbacks(ins, target_cbks_cookie); +} */ bool BTSYMBOLS(bt_avrcp_target_unregister_callbacks)(bt_instance_t* ins, void* cookie); /** - * @brief Send response of Get Play Status request to CT. + * @brief Tell the AVRCP Controller the current state of the player. + * + * An application of an AVRCP Target will call this interface to send the current + * status of the player when "received_get_play_status_request_cb" event is triggered. + * Additionally, the total length and the position of the current song should also + * be returned as described in the AVRCP profile. In particular, if this Target does + * not support total length and current position of the song, then the Target shall + * return 0xFFFFFFFF. * * @param ins - Bluetooth client instance. - * @param addr - Remote BT address. + * @param addr - The Bluetooth address of the peer device. * @param status - Current play status. * @param song_len - Song length in ms. * @param song_pos - Current position in ms. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +static void on_get_play_status_request_cb(void* cookie, bt_address_t* addr) +{ + avrcp_play_status_t status = AVRCP_PLAY_STATUS_PLAYING; + uint32_t song_len = 0xFFFFFFFF; + uint32_t song_pos = 0xFFFFFFFF; + bt_instance_t* ins = cookie; + bt_avrcp_target_get_play_status_response(ins, addr, status, song_len, song_pos); +} + * @endcode */ bt_status_t BTSYMBOLS(bt_avrcp_target_get_play_status_response)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t status, uint32_t song_len, uint32_t song_pos); /** - * @brief Notify playback status if peer device had register playback notification + * @brief Notify the status of a player if peer device has registered the corresponding event. + * + * If the status of a player changes and an AVRCP Controller has registered for that event, + * the AVRCP target should use that interface to send a change notification to the Controller + * with the current status. In particular, after this notification, the registered notification + * event by the AVRCP Controller becomes invalid. * * @param ins - Bluetooth client instance. - * @param addr - Remote BT address. - * @param status - current play status. + * @param addr - The Bluetooth address of the peer device. + * @param status - Current play status. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +uint16_t notify_event; + +static void avrcp_target_play_status_changed(void* cookie, bt_address_t* addr, avrcp_play_status_t status) +{ + bt_instance_t* ins = cookie; + if (notify_event & AVRCP_NOTIFICATION_EVENT_PLAY_STATUS_CHANGED) { + bt_avrcp_target_play_status_notify(ins, addr, status); + + notify_event &= ~AVRCP_NOTIFICATION_EVENT_PLAY_STATUS_CHANGED; + } +} + * @endcode */ bt_status_t BTSYMBOLS(bt_avrcp_target_play_status_notify)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t status); -- Gitee From 6431afd394144cfcd24a227cfe88fa32efd7dc8b Mon Sep 17 00:00:00 2001 From: jialu Date: Mon, 25 Nov 2024 18:14:57 +0800 Subject: [PATCH 029/498] Bluetooth: Introducing the A2DP function. bug: v/43699 Rootcause: Add comments to the A2DP .h file and improve function documentation. Signed-off-by: jialu --- framework/include/bt_a2dp.h | 59 +++++++- framework/include/bt_a2dp_sink.h | 219 ++++++++++++++++++++++----- framework/include/bt_a2dp_source.h | 231 ++++++++++++++++++++++++----- 3 files changed, 426 insertions(+), 83 deletions(-) diff --git a/framework/include/bt_a2dp.h b/framework/include/bt_a2dp.h index a59168f6..1256813d 100644 --- a/framework/include/bt_a2dp.h +++ b/framework/include/bt_a2dp.h @@ -21,6 +21,9 @@ #include "bt_addr.h" #include "bt_device.h" +/** + * @cond + */ /** * @brief A2DP audio state */ @@ -29,23 +32,63 @@ typedef enum { A2DP_AUDIO_STATE_STOPPED, A2DP_AUDIO_STATE_STARTED, } a2dp_audio_state_t; +/** + * @endcond + */ /** - * @brief A2DP connection state changed callback + * @brief Callback for A2DP connection state changed. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DISCONNECTING. During the initialization phase of the A2DP, + * it is necessary to register callback functions. This callback is triggered + * when there is a change in the state of the A2DP connection. * - * @param cookie - callback cookie. - * @param addr - address of peer A2DP device. - * @param state - connection state. + * Stable States: + * DISCONNECTED: The initial state. + * CONNECTED: The A2DP connection is established. + * Transient states: + * CONNECTING: The A2DP connection is being established. + * DISCONNECTING: The A2DP connection is being terminated. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - A2DP profile connection state. + * + * **Example:** + * @code +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + printf("A2DP connection state is: %d", state); +} + * @endcode */ typedef void (*a2dp_connection_state_callback)(void* cookie, bt_address_t* addr, profile_connection_state_t state); /** - * @brief A2DP audio connection state changed callback + * @brief Callback for A2DP audio state changed. + * + * There are three states for A2DP audio, namely SUSPEND, STOPPED, + * and STARTED. It is important to note that a callback function + * is triggered whenever a change occurs in the audio state. + * + * Stable States: + * SUSPEND: The stream is suspended. + * STOPPED: The stream is stopped. + * STARTED: The stream is started. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - A2DP audio connection state. * - * @param cookie - callback cookie. - * @param addr - address of peer A2DP device. - * @param state - audio connection state. + * **Example:** + * @code +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + printf("A2DP audio state is: %d", state); +} + * @endcode */ typedef void (*a2dp_audio_state_callback)(void* cookie, bt_address_t* addr, a2dp_audio_state_t state); diff --git a/framework/include/bt_a2dp_sink.h b/framework/include/bt_a2dp_sink.h index e7bdae3e..34e4098b 100644 --- a/framework/include/bt_a2dp_sink.h +++ b/framework/include/bt_a2dp_sink.h @@ -25,15 +25,30 @@ extern "C" { #endif /** - * @brief A2DP audio sink config changed callback + * @brief Callback for A2DP audio configuration. * - * @param cookie - callback cookie. - * @param addr - address of peer A2DP source device. + * During the initialization process of the A2DP, callback functions are registered. + * This callbacks is triggered whenever a change occurs in the audio configuration + * of an A2DP connection. + * + * @param cookie - Callbacks cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + printf("A2DP audio sink configuration is changed"); +} + * @endcode */ typedef void (*a2dp_audio_sink_config_callback)(void* cookie, bt_address_t* addr); /** - * @brief A2DP sink callback structure + * @cond + */ +/** + * @brief A2DP Sink callback structure * */ typedef struct { @@ -43,79 +58,209 @@ typedef struct { a2dp_audio_state_callback audio_state_cb; a2dp_audio_sink_config_callback audio_sink_config_cb; } a2dp_sink_callbacks_t; +/** + * @endcond + */ /** - * @brief Register callback functions to A2DP sink service + * @brief Register callback functions to A2DP Sink service. + * + * When initializing the A2DP Sink, an application should register callback functions + * to the A2DP Sink service, which includes a connection state callback function, an + * audio state callback function, and an audio sink configuration callback function. + * Subsequently, the A2DP Sink service will notify the application of any state + * changes via the registered callback functions. + * + * @param ins - Buetooth client instance. + * @param callbacks - A2DP Sink callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). * - * @param ins - bluetooth client instance. - * @param id - A2DP sink callback functions. - * @return void* - callbacks cookie. + * **Example:** + * @code +static const a2dp_sink_callbacks_t a2dp_sink_cbs = { + sizeof(a2dp_sink_cbs), + a2dp_sink_connection_state_cb, + a2dp_sink_audio_state_cb, + a2dp_sink_audio_config_cb, +}; + +void a2dp_sink_init(void* ins) +{ + static void* sink_cbks_cookie; + + sink_cbks_cookie = bt_a2dp_sink_register_callbacks(ins, &a2dp_sink_cbs); +} + * @endcode */ void* BTSYMBOLS(bt_a2dp_sink_register_callbacks)(bt_instance_t* ins, const a2dp_sink_callbacks_t* callbacks); /** - * @brief Unregister callback functions to a2dp sink service + * @brief Unregister callback functions from A2DP Sink service. + * + * An application may use this interface to stop listening on the A2DP Sink + * callbacks and to release the associated resources. + * + * @param ins - Buetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. * - * @param ins - bluetooth client instance. - * @param id - callbacks cookie. - * @return true - on callback unregister success - * @return false - on callback cookie not found + * **Example:** + * @code +static void* sink_cbks_cookie = bt_a2dp_sink_register_callbacks(ins, &a2dp_sink_cbs); + +void a2dp_sink_uninit(void* ins) +{ + bt_a2dp_sink_unregister_callbacks(ins, sink_cbks_cookie); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_sink_unregister_callbacks)(bt_instance_t* ins, void* cookie); /** - * @brief Check A2DP sink is connected + * @brief Check if A2DP is already connected. + * + * This function serves the purpose of verifying the connection status of A2DP. + * It is important to note that the A2DP profile is deemed connected solely when + * the A2DP Sink is either in the Opened or Started state. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. - * @return true - connected. - * @return false - not connected. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - A2DP Sink connected. + * @return false - A2DP Sink not connected. + * + * **Example:** + * @code +void a2dp_sink_connected(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_sink_is_connected(ins, addr); + + printf("A2DP sink is %s", ret? "connected" : "not connected"); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_sink_is_connected)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Check A2DP sink is playing + * @brief Check if the stream is being transmitted. + * + * This function is used to verify the streaming status of the A2DP Sink. + * The A2DP Sink can only be deemed as playing when it is in the Started + * state and the audio is also in the Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - Stream is being transmitted . + * @return false - No stream transmission. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. - * @return true - playing. - * @return false - not playing. + * **Example** + * @code +void a2dp_sink_playing(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_sink_is_playing(ins, addr); + + printf("A2DP sink is %s", ret? "playing" : "not playing"); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_sink_is_playing)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief get A2DP sink connection state + * @brief Obtain A2DP Sink connection state. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DIACONNECTED. This function is used by the application of + * the A2DP Sink to obtain the current state of the A2DP profile connection. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. - * @return profile_connection_state_t - connection state. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - A2DP profile connection state. + * + * **Example** + * @code +int get_state_cmd(void* ins, bt_address_t* addr) +{ + int state = bt_a2dp_sink_get_connection_state(ins, addr); + + printf("A2DP sink connection state is: %d", state); + + return state; +} + * @endcode */ profile_connection_state_t BTSYMBOLS(bt_a2dp_sink_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Establish connection with peer A2DP device + * @brief Establish connection with peer A2DP device. + * + * This function is used to establish an A2DP profile connection with a designated + * peer device. Following the successful creation of the A2DP connection, the A2DP + * Sink transitions to an Opened state and subsequently notifies the application of + * its CONNECTED state. Upon reception of audio data from the A2DP Source device, + * the A2DP Sink then proceeds to forward the audio data to the Media. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_sink_connect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_connect(ins, addr); + + printf("A2DP sink connect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_sink_connect)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Disconnect from peer A2DP device + * @brief Disconnect from peer A2DP device. + * + * This function is utilized for the purpose of disconnecting an A2DP profile connection + * with a designated peer device. Upon successful disconnection of the A2DP connection, + * the A2DP Sink transitions into an Idle state and subsequently notifies the application + * of the DISCONNECTED state. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_sink_disconnect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_disconnect(ins, addr); + + printf("A2DP sink disconnect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_sink_disconnect)(bt_instance_t* ins, bt_address_t* addr); -/** - * @brief set a peer A2DP source device as active device +/* + * @brief Set a peer A2DP Source device as active device. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP source device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int enable_source_device(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_set_active_device(ins, addr); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_sink_set_active_device)(bt_instance_t* ins, bt_address_t* addr); diff --git a/framework/include/bt_a2dp_source.h b/framework/include/bt_a2dp_source.h index c8763824..5df6aca8 100644 --- a/framework/include/bt_a2dp_source.h +++ b/framework/include/bt_a2dp_source.h @@ -26,13 +26,28 @@ extern "C" { #endif /** - * @brief A2DP audio source config changed callback + * @brief Callback for A2DP audio configuration. * - * @param cookie - callback cookie. - * @param addr - address of peer A2DP sink device. + * During the initialization process of the A2DP, callback functions are registered. + * This callbacks is triggered whenever a change occurs in the audio configuration + * of an A2DP connection. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + printf("A2DP audio source configuration is changed"); +} + * @endcode */ typedef void (*a2dp_audio_source_config_callback)(void* cookie, bt_address_t* addr); +/** + * @cond + */ /** * @brief A2DP source callback structure * @@ -44,87 +59,227 @@ typedef struct { a2dp_audio_state_callback audio_state_cb; a2dp_audio_source_config_callback audio_source_config_cb; } a2dp_source_callbacks_t; +/** + * @endcond + */ /** - * @brief Register callback functions to A2DP source service + * @brief Register callback functions to A2DP Source service. + * + * When the A2DP Source is initialized, an application registers various callback + * functions to the A2DP Source service, such as the connection state callback function, + * audio state callback function, and audio source configuration callback function. + * The A2DP Source service will notify the application of any state changes via the + * registered callback functions. + * + * @param ins - Bluetooth client instance. + * @param callbacks - A2DP Source callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. + * NULL if the callback is already registered or registration fails. * - * @param ins - bluetooth client instance. - * @param id - A2DP source callback functions. - * @return void* - callbacks cookie. + * **Example:** + * @code +static const a2dp_source_callbacks_t a2dp_src_cbs = { + sizeof(a2dp_src_cbs), + a2dp_src_connection_state_cb, + a2dp_src_audio_state_cb, + a2dp_src_audio_source_config_cb, +}; + +void a2dp_source_uninit(void* ins) +{ + static void* src_cbks_cookie; + + src_cbks_cookie = bt_a2dp_source_register_callbacks(ins, &a2dp_src_cbs); +} + * @endcode */ void* BTSYMBOLS(bt_a2dp_source_register_callbacks)(bt_instance_t* ins, const a2dp_source_callbacks_t* callbacks); /** - * @brief Unregister callback functions to A2DP source service + * @brief Unregister callback functions to A2DP Source service. * - * @param ins - bluetooth client instance. - * @param id - callbacks cookie. - * @return true - on callback unregister success. - * @return false - on callback cookie not found. + * An application may use this interface to stop listening on the A2DP Source + * callbacks and to release the associated resources. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +static void* src_cbks_cookie = bt_a2dp_source_register_callbacks(ins, &a2dp_src_cbs); + +void a2dp_source_uninit(void* ins) +{ + bt_a2dp_source_unregister_callbacks(ins, src_cbks_cookie); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_source_unregister_callbacks)(bt_instance_t* ins, void* cookie); + /** - * @brief Check A2DP source is connected + * @brief Check if A2DP is already connected. + * + * This function serves the purpose of verifying the connection status of A2DP. + * It is important to note that the A2DP profile is deemed connected solely when + * the A2DP Source is either in the Opened or Started state. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. - * @return true - connected. - * @return false - not connected. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - A2DP Source connected. + * @return false - A2DP Source not connected. + * + * **Example:** + * @code +void a2dp_source_connected(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_source_is_connected(ins, addr); + + printf("A2DP source is %s", ret? "connected" : "not connected"); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_source_is_connected)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Check A2DP source is playing + * @brief Check if the stream is being transmitted. + * + * This function is used to verify the streaming status of the A2DP Source. + * The A2DP Source can only be deemed as playing when it is in the Started + * state and the audio is also in the Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - Stream is being transmitted. + * @return false - No stream transmission. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. - * @return true - playing. - * @return false - not playing. + * **Example** + * @code +void a2dp_source_playing(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_source_is_playing(ins, addr); + + printf("A2DP source is %s", ret? "playing" : "not playing"); +} + * @endcode */ bool BTSYMBOLS(bt_a2dp_source_is_playing)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief get A2DP source connection state + * @brief Obtain A2DP Source connection state. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DIACONNECTED. This function is used by the application of + * the A2DP Source to obtain the current state of the A2DP profile connection. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. - * @return profile_connection_state_t - connection state. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - A2DP profile connection state. + * + * **Example** + * @code +int get_state_cmd(void* ins, bt_address_t* addr) +{ + int state = bt_a2dp_source_get_connection_state(ins, addr); + + printf("A2DP source connection state is: %d", state); + + return state; +} + * @endcode */ profile_connection_state_t BTSYMBOLS(bt_a2dp_source_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Establish connection with peer A2DP device + * @brief Establish connection with peer A2DP device. + * + * This function is used to establish an A2DP profile connection with a designated + * peer device. Upon successful establishment of the A2DP connection, the A2DP Source + * transitions to an Opened state and notifies the application of its CONNECTED status. + * Upon receipt of audio data from the media, the A2DP Source subsequently relays the + * audio data to the A2DP Sink. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_source_connect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_connect(ins, &addr); + + printf("A2DP source connect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_source_connect)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief Disconnect from peer A2DP device + * @brief Disconnect from peer A2DP device. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. + * This function is used to remove an A2DP profile connection with a specific peer device. Once + * the A2DP connection is removed, the A2DP Source turns into the Idle state and reports the + * DISCONNECTED state to the application. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_source_disconnect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_disconnect(ins, addr); + + printf("A2DP source disconnect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_source_disconnect)(bt_instance_t* ins, bt_address_t* addr); /** - * @brief set a peer A2DP sink device as silence device + * @brief Set a peer A2DP Sink device as silence device. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param silence - True, switch to silence mode to keep this device inactive. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int disable_sink_device(void* ins, bt_address_t* addr, bool silence) +{ + int ret = bt_a2dp_source_set_silence_device(ins, addr, silence); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_source_set_silence_device)(bt_instance_t* ins, bt_address_t* addr, bool silence); /** - * @brief set a peer A2DP sink device as active device + * @brief Set a peer A2DP Sink device as active device. * - * @param ins - bluetooth client instance. - * @param addr - address of peer A2DP sink device. + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int enable_sink_device(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_set_active_device(ins, addr); + + return ret; +} + * @endcode */ bt_status_t BTSYMBOLS(bt_a2dp_source_set_active_device)(bt_instance_t* ins, bt_address_t* addr); -- Gitee From 325dff6e147b5dad8332585e0eb57c20f6932eeb Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Wed, 12 Feb 2025 16:47:53 +0800 Subject: [PATCH 030/498] Vela-Android: fix IPC disconnect processing bug: v/53702 Rootcause: Android(socket client) becomes blocked indefinitely when awaiting a semaphore, as the Vela(socket server) undergoes an unexpected reboot without properly releasing the semaphore. This failure in semaphore release prevents the client from proceeding with subsequent operations, resulting in a system deadlock. Signed-off-by: liuxiang18 --- service/ipc/socket/src/bt_socket_client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/service/ipc/socket/src/bt_socket_client.c b/service/ipc/socket/src/bt_socket_client.c index fc316815..ac1b9a79 100644 --- a/service/ipc/socket/src/bt_socket_client.c +++ b/service/ipc/socket/src/bt_socket_client.c @@ -279,6 +279,7 @@ static void bt_socket_client_handle_event(uv_poll_t* poll, int status, int event } if (status != 0 || events & UV_DISCONNECT) { + uv_sem_post(&ins->message_processed); thread_loop_remove_poll(poll); if (ins && ins->disconnected) { BT_LOGE("%s socket disconnect, status = %d, events = %d", __func__, status, events); -- Gitee From 2ce6671ea9c326d5eb2883c6e1da9e294413ce59 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 25 Feb 2025 17:21:03 +0800 Subject: [PATCH 031/498] Bluetooth: update the include path for Vela Bluetooth interfaces. bug: v/46835 Signed-off-by: Zihao Gao --- Make.defs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Make.defs b/Make.defs index 8e4c2619..1b319544 100644 --- a/Make.defs +++ b/Make.defs @@ -17,7 +17,7 @@ ifeq ($(CONFIG_BLUETOOTH), y) CONFIGURED_APPS += $(APPDIR)/frameworks/connectivity/bluetooth -CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/include -CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/include +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/framework/include +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/framework/include endif -- Gitee From dbdf857451e27d633e04aa4ae94bc5dbebe4b8f2 Mon Sep 17 00:00:00 2001 From: duqunbo Date: Thu, 27 Feb 2025 15:48:03 +0800 Subject: [PATCH 032/498] Move the save adapter property to the uv_work thread. bug: v/54729 rootcause:The number of KVDB connections that can be established is limited. If multiple one-way modification operations are triggered rapidly, it may cause the number of connections to exceed the upper limit. Signed-off-by: duqunbo --- service/common/storage_property.c | 37 ++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/service/common/storage_property.c b/service/common/storage_property.c index 1989fd61..a41797c7 100644 --- a/service/common/storage_property.c +++ b/service/common/storage_property.c @@ -62,6 +62,22 @@ typedef struct { uint8_t value[0]; } bt_property_value_t; +static void storage_save_adapter_info(service_work_t* work, void* userdata) +{ + adapter_storage_t* adapter = (adapter_storage_t*)userdata; + property_set_binary(BT_KVDB_ADAPTERINFO_NAME, adapter->name, sizeof(adapter->name), false); + property_set_int32(BT_KVDB_ADAPTERINFO_COD, adapter->class_of_device); + property_set_int32(BT_KVDB_ADAPTERINFO_IOCAP, adapter->io_capability); + property_set_int32(BT_KVDB_ADAPTERINFO_SCAN, adapter->scan_mode); + property_set_int32(BT_KVDB_ADAPTERINFO_BOND, adapter->bondable); + property_commit(); +} + +static void storage_save_adapter_info_complete(service_work_t* work, void* userdata) +{ + free(userdata); +} + static void storage_commit(service_work_t* work, void* userdata) { property_commit(); @@ -192,12 +208,21 @@ static void adapter_properties_default(adapter_storage_t* prop) int bt_storage_save_adapter_info(adapter_storage_t* adapter) { - property_set_binary(BT_KVDB_ADAPTERINFO_NAME, adapter->name, sizeof(adapter->name), true); - property_set_int32_oneway(BT_KVDB_ADAPTERINFO_COD, adapter->class_of_device); - property_set_int32_oneway(BT_KVDB_ADAPTERINFO_IOCAP, adapter->io_capability); - property_set_int32_oneway(BT_KVDB_ADAPTERINFO_SCAN, adapter->scan_mode); - property_set_int32_oneway(BT_KVDB_ADAPTERINFO_BOND, adapter->bondable); - service_loop_work(NULL, storage_commit, NULL); + adapter_storage_t* adapter_copy; + + adapter_copy = (adapter_storage_t*)malloc(sizeof(adapter_storage_t)); + if (!adapter_copy) { + BT_LOGE("adapter_copy malloc failed!"); + return -ENOMEM; + } + memcpy(adapter_copy, adapter, sizeof(adapter_storage_t)); + + if (service_loop_work(adapter_copy, storage_save_adapter_info, storage_save_adapter_info_complete) == NULL) { + BT_LOGE("service_loop_work failed!"); + storage_save_adapter_info_complete(NULL, adapter_copy); + return -EINVAL; + } + return 0; } -- Gitee From efa41cd560a667df646aa62a5d341915efcd610e Mon Sep 17 00:00:00 2001 From: duqunbo Date: Thu, 27 Feb 2025 20:23:38 +0800 Subject: [PATCH 033/498] Remove the restriction that prevents entering the A2DP started state before the local media codec configuration is fully set up. bug: v/54811 rootcause:Once the codec negotiation is completed, regardless of whether the local media codec configuration has been set up, the A2DP should not be restricted from entering the started state. Signed-off-by: duqunbo --- service/profiles/a2dp/a2dp_state_machine.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/service/profiles/a2dp/a2dp_state_machine.c b/service/profiles/a2dp/a2dp_state_machine.c index f7474a55..788028f7 100644 --- a/service/profiles/a2dp/a2dp_state_machine.c +++ b/service/profiles/a2dp/a2dp_state_machine.c @@ -688,11 +688,10 @@ static bool opened_process_event(state_machine_t* sm, uint32_t event, void* p_da a2dp_sm->delay_start_timer = NULL; } - if (!a2dp_sm->audio_ready) { + if (!a2dp_sm->audio_ready && a2dp_sm->peer_sep == SEP_SNK) { BT_LOGW("A2dp device is not ready: %s", stack_event_to_string(event)); break; } - a2dp_audio_on_started(a2dp_sm->peer_sep, true); hsm_transition_to(sm, &started_state); break; @@ -947,7 +946,14 @@ static bool started_process_event(state_machine_t* sm, uint32_t event, void* p_d break; case DEVICE_CODEC_STATE_CHANGE_EVT: + a2dp_sm->audio_ready = true; a2dp_report_audio_config_state(a2dp_sm, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SNK) { + BT_LOGE("Codec reconfiguration should not be performed during the Started state, as a source."); + break; + } + + a2dp_audio_setup_codec(a2dp_sm->peer_sep, &a2dp_sm->addr); break; case OFFLOAD_STOP_REQ: -- Gitee From 2ba77635d44bdf05284fe1443b1b7efab8a44369 Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Tue, 25 Feb 2025 20:12:47 +0800 Subject: [PATCH 034/498] sal: set pairing_confirm cb to NULL by default bug: v/53803 SSP just work pairing confirm dont notify app Signed-off-by: fangzhenwei --- service/stacks/zephyr/sal_adapter_classic_interface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index c10a7785..6cc1785c 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -155,7 +155,6 @@ static struct bt_conn_auth_info_cb g_conn_auth_info_cbs = { static struct bt_conn_auth_cb g_conn_auth_cbs = { .cancel = zblue_on_cancel, - .pairing_confirm = zblue_on_pairing_confirm, .pincode_entry = zblue_on_pincode_entry }; @@ -627,27 +626,32 @@ bt_status_t bt_sal_set_io_capability(bt_controller_id_t id, bt_io_capability_t c g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; g_conn_auth_cbs.passkey_entry = NULL; g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; break; case BT_IO_CAPABILITY_DISPLAYYESNO: g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; g_conn_auth_cbs.passkey_entry = NULL; g_conn_auth_cbs.passkey_confirm = zblue_on_passkey_confirm; + g_conn_auth_cbs.pairing_confirm = zblue_on_pairing_confirm; break; case BT_IO_CAPABILITY_KEYBOARDONLY: g_conn_auth_cbs.passkey_display = NULL; g_conn_auth_cbs.passkey_entry = zblue_on_passkey_entry; g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; break; case BT_IO_CAPABILITY_KEYBOARDDISPLAY: g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; g_conn_auth_cbs.passkey_entry = zblue_on_passkey_entry; g_conn_auth_cbs.passkey_confirm = zblue_on_passkey_confirm; + g_conn_auth_cbs.pairing_confirm = zblue_on_pairing_confirm; break; case BT_IO_CAPABILITY_NOINPUTNOOUTPUT: default: g_conn_auth_cbs.passkey_display = NULL; g_conn_auth_cbs.passkey_entry = NULL; g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; break; } -- Gitee From e5730bc9ad9c152926eeff69309a8b1d9710d37d Mon Sep 17 00:00:00 2001 From: duqunbo Date: Tue, 25 Feb 2025 15:31:51 +0800 Subject: [PATCH 035/498] BTsnoop: Use uv_work to resolve contention when multiple threads write snoop files at the same time. bug: v/52618 rootcause:there is competition when both multiplethreads write HCI to snoop at the same time. Signed-off-by: duqunbo --- service/utils/btsnoop_log.c | 53 ++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/service/utils/btsnoop_log.c b/service/utils/btsnoop_log.c index 3f6398fa..c80dee87 100644 --- a/service/utils/btsnoop_log.c +++ b/service/utils/btsnoop_log.c @@ -20,6 +20,7 @@ #include "btsnoop_log.h" #include "btsnoop_writer.h" #include "log.h" +#include "service_loop.h" #ifndef CONFIG_BLUETOOTH_SNOOP_LOG #define CONFIG_BLUETOOTH_SNOOP_LOG 1 @@ -28,22 +29,60 @@ static pthread_mutex_t snoop_lock = PTHREAD_MUTEX_INITIALIZER; static bool snoop_enable = false; -void btsnoop_log_capture(uint8_t recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size) +typedef struct { + uint8_t receive; + uint8_t pad[3]; + uint32_t hci_pkt_size; + uint8_t hci_pkt[]; +} btsnoop_hci_command_t; + +static void write_log(service_work_t* work, void* userdata) +{ + btsnoop_hci_command_t* hci_command = (btsnoop_hci_command_t*)userdata; + + if (hci_command == NULL) { + BT_LOGE("hci_pkt is null"); + return; + } + + writer_write_log(hci_command->receive, hci_command->hci_pkt, hci_command->hci_pkt_size); +} + +static void write_log_complete(service_work_t* work, void* userdata) +{ + free(userdata); +} + +void btsnoop_log_capture(uint8_t receive, uint8_t* hci_pkt, uint32_t hci_pkt_size) { #if CONFIG_BLUETOOTH_SNOOP_LOG + btsnoop_hci_command_t* hci_command; pthread_mutex_lock(&snoop_lock); if (!snoop_enable) { - pthread_mutex_unlock(&snoop_lock); - return; + goto error; } - if (filter_can_filter(recieve, hci_pkt, hci_pkt_size)) { - pthread_mutex_unlock(&snoop_lock); - return; + + if (filter_can_filter(receive, hci_pkt, hci_pkt_size)) { + goto error; + } + + hci_command = (btsnoop_hci_command_t*)malloc(sizeof(btsnoop_hci_command_t) + hci_pkt_size); + if (hci_command == NULL) { + BT_LOGE("malloc fail"); + goto error; + } + + hci_command->receive = receive; + hci_command->hci_pkt_size = hci_pkt_size; + memcpy(hci_command->hci_pkt, hci_pkt, hci_pkt_size); + + if (service_loop_work(hci_command, write_log, write_log_complete) == NULL) { + write_log_complete(NULL, hci_command); } +error: pthread_mutex_unlock(&snoop_lock); - writer_write_log(recieve, hci_pkt, hci_pkt_size); #endif } -- Gitee From dab03213d12b696c02c6ac85f70434c432fe93ad Mon Sep 17 00:00:00 2001 From: zhangyuan20 Date: Tue, 4 Mar 2025 17:52:43 +0800 Subject: [PATCH 036/498] framework/bluetooth: Fixed the problem that bt_device_get_alias method returns wrong alias. bug: v/54666 bt_device_get_alias method returns 62 bytes when alias length is 63 bytes, because the length is reduced by 1 when using strlcpy. --- framework/socket/bt_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/socket/bt_device.c b/framework/socket/bt_device.c index 1c60f48a..22ed7a75 100644 --- a/framework/socket/bt_device.c +++ b/framework/socket/bt_device.c @@ -177,7 +177,7 @@ bool bt_device_get_alias(bt_instance_t* ins, bt_address_t* addr, char* alias, ui } strlcpy(alias, packet.devs_pl._bt_device_get_alias.alias, - MIN(length, sizeof(packet.devs_pl._bt_device_get_alias.alias) - 1)); + MIN(length, sizeof(packet.devs_pl._bt_device_get_alias.alias))); return packet.devs_r.status; } -- Gitee From 9996fa9118371792f7b58de6a9ca16884c84f012 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Wed, 5 Mar 2025 18:10:52 +0800 Subject: [PATCH 037/498] Audio Control: update socket control procedures. bug: v/55255 Signed-off-by: Zihao Gao --- .../profiles/audio_interface/audio_control.c | 53 +++++++++---------- service/profiles/hfp_ag/hfp_ag_service.c | 4 +- service/profiles/hfp_hf/hfp_hf_service.c | 4 +- service/profiles/include/audio_control.h | 4 +- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/service/profiles/audio_interface/audio_control.c b/service/profiles/audio_interface/audio_control.c index eec7c4fd..40e426b5 100644 --- a/service/profiles/audio_interface/audio_control.c +++ b/service/profiles/audio_interface/audio_control.c @@ -223,42 +223,37 @@ static void audio_ctrl_cb(uint8_t ch_id, audio_transport_event_t event) } } -bt_status_t audio_ctrl_init(uint8_t profile_id) +bt_status_t audio_ctrl_init(void) { - switch (profile_id) { - case PROFILE_HFP_AG: - case PROFILE_HFP_HF: - if (g_audio_ctrl_transport == NULL) { - g_audio_ctrl_transport = audio_transport_init(get_service_uv_loop()); - } - if (!audio_transport_open(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, - CONFIG_BLUETOOTH_SCO_CTRL_PATH, audio_ctrl_cb)) { - BT_LOGE("fail to open audio transport"); - return BT_STATUS_FAIL; - } - break; - default: - BT_LOGW("%s, unknown profile id: %d", __func__, profile_id); - break; + BT_LOGI("%s, enter", __func__); + + if (g_audio_ctrl_transport) { + BT_LOGD("%s, repeated attempt", __func__); + return BT_STATUS_SUCCESS; + } + + g_audio_ctrl_transport = audio_transport_init(get_service_uv_loop()); + if (!audio_transport_open(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, + CONFIG_BLUETOOTH_SCO_CTRL_PATH, audio_ctrl_cb)) { + BT_LOGE("fail to open audio transport"); + goto error; } + BT_LOGI("%s, success", __func__); + return BT_STATUS_SUCCESS; + +error: + audio_ctrl_cleanup(); + return BT_STATUS_FAIL; } -void audio_ctrl_cleanup(uint8_t profile_id) +void audio_ctrl_cleanup(void) { - switch (profile_id) { - case PROFILE_HFP_AG: - case PROFILE_HFP_HF: - if (g_audio_ctrl_transport) { - audio_transport_close(g_audio_ctrl_transport, AUDIO_TRANS_CH_ID_ALL); - } - break; - default: - BT_LOGW("%s, unknown profile id: %d", __func__, profile_id); - break; + if (g_audio_ctrl_transport) { + audio_transport_close(g_audio_ctrl_transport, AUDIO_TRANS_CH_ID_ALL); + g_audio_ctrl_transport = NULL; } - /* TODO: close audio transport when all profile closed */ - g_audio_ctrl_transport = NULL; + BT_LOGI("%s, done", __func__); } diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c index 8e962bde..9bb84d89 100644 --- a/service/profiles/hfp_ag/hfp_ag_service.c +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -398,7 +398,7 @@ static bt_status_t hfp_ag_init(void) { bt_status_t ret; - ret = audio_ctrl_init(PROFILE_HFP_AG); + ret = audio_ctrl_init(); if (ret != BT_STATUS_SUCCESS) { BT_LOGE("%s: failed to start audio control channel", __func__); return ret; @@ -409,7 +409,7 @@ static bt_status_t hfp_ag_init(void) static void hfp_ag_cleanup(void) { - audio_ctrl_cleanup(PROFILE_HFP_AG); + audio_ctrl_cleanup(); } static bt_status_t hfp_ag_startup(profile_on_startup_t cb) diff --git a/service/profiles/hfp_hf/hfp_hf_service.c b/service/profiles/hfp_hf/hfp_hf_service.c index adfe9ad1..43dfe0a3 100644 --- a/service/profiles/hfp_hf/hfp_hf_service.c +++ b/service/profiles/hfp_hf/hfp_hf_service.c @@ -373,7 +373,7 @@ static bt_status_t hfp_hf_init(void) { bt_status_t ret; - ret = audio_ctrl_init(PROFILE_HFP_HF); + ret = audio_ctrl_init(); if (ret != BT_STATUS_SUCCESS) { BT_LOGE("%s: failed to start audio control channel", __func__); return ret; @@ -384,7 +384,7 @@ static bt_status_t hfp_hf_init(void) static void hfp_hf_cleanup(void) { - audio_ctrl_cleanup(PROFILE_HFP_HF); + audio_ctrl_cleanup(); } static bt_status_t hfp_hf_startup(profile_on_startup_t cb) diff --git a/service/profiles/include/audio_control.h b/service/profiles/include/audio_control.h index ac385a31..3ea96c96 100644 --- a/service/profiles/include/audio_control.h +++ b/service/profiles/include/audio_control.h @@ -40,7 +40,7 @@ #include "bt_status.h" void audio_ctrl_send_control_event(uint8_t profile_id, audio_ctrl_evt_t evt); -bt_status_t audio_ctrl_init(uint8_t profile_id); -void audio_ctrl_cleanup(uint8_t profile_id); +bt_status_t audio_ctrl_init(void); +void audio_ctrl_cleanup(void); #endif -- Gitee From b20ffc7cdf284a67cb0307198ed81e57855b1741 Mon Sep 17 00:00:00 2001 From: chengkai Date: Thu, 20 Mar 2025 11:39:40 +0800 Subject: [PATCH 038/498] bluetooth: fix spp coverity null reference bug: v/20042 Signed-off-by: chengkai --- service/profiles/spp/spp_service.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/profiles/spp/spp_service.c b/service/profiles/spp/spp_service.c index 02297e5d..e0fbc4e9 100644 --- a/service/profiles/spp/spp_service.c +++ b/service/profiles/spp/spp_service.c @@ -611,8 +611,8 @@ static void spp_proxy_connection_callback(euv_pipe_t* handle, int status, void* #endif device = find_spp_device_by_handle(handle); - if (!device->handle) { - BT_LOGE("%s, handle null", __func__); + if (!device || !device->handle) { + BT_LOGE("%s, device or handle null", __func__); return; } -- Gitee From f0266fc8bf4c06f329428efe37ac3cb39aa4e457 Mon Sep 17 00:00:00 2001 From: chejinxian1 Date: Wed, 19 Mar 2025 23:44:58 +0800 Subject: [PATCH 039/498] API: Add a new callback interface to obtain bond states. bug: v/54141 Rootcause: The new interface will inform the upper-layer application of the previous bond state, thus distinguishing the different scenarios that may occur during the bonding process. Signed-off-by: chejinxian1 --- framework/include/bt_adapter.h | 33 ++++++++++++++++--- .../ipc/socket/include/bt_message_adapter.h | 3 +- service/ipc/socket/src/bt_socket_adapter.c | 18 +++++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/framework/include/bt_adapter.h b/framework/include/bt_adapter.h index 4280647c..13ad9754 100644 --- a/framework/include/bt_adapter.h +++ b/framework/include/bt_adapter.h @@ -47,7 +47,7 @@ typedef enum { * @brief Adapter State Changed Callback. * * State Transition Diagram: - * + * * +---------------------------+ * | BT_ADAPTER_STATE_OFF | * +---------------------------+ @@ -93,7 +93,7 @@ typedef enum { * +---------------------------+ * | BT_ADAPTER_STATE_OFF | * +---------------------------+ - * + * * State Descriptions: * - `BT_ADAPTER_STATE_OFF`: The initial state. The adapter is off. * - `BT_ADAPTER_STATE_BLE_TURNING_ON`: BLE is in the process of being turned on. @@ -102,14 +102,14 @@ typedef enum { * - `BT_ADAPTER_STATE_ON`: The Bluetooth adapter is fully on. * - `BT_ADAPTER_STATE_TURNING_OFF`: The Bluetooth adapter is turning off. * - `BT_ADAPTER_STATE_BLE_TURNING_OFF`: BLE is turning off. - * + * * Callback invoked when the Bluetooth adapter state changes. * * @param cookie - User-defined context: * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. - * + * * @param state - The new state of the Bluetooth adapter, as defined in @ref bt_adapter_state_t. * * **Example:** @@ -344,6 +344,8 @@ typedef void (*on_connection_state_changed_callback)(void* cookie, bt_address_t* * * Callback function invoked when the bond state changes. * + * @note The way the callback is invoked locally will no longer be supported. + * * @param cookie - User-defined context: * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. @@ -363,6 +365,28 @@ void on_bond_state_changed(void* cookie, bt_address_t* addr, bt_transport_t tran */ typedef void (*on_bond_state_changed_callback)(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd); +/** + * @brief Bond state changed callback. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param transport - Transport type, see @ref bt_transport_t (0: LE, 1: BR/EDR). + * @param previous_state - Previous bond state, see @ref bond_state_t. + * @param current_state - Current bond state, see @ref bond_state_t. + * @param is_ctkd - Indicates whether to use cross-transport key derivation, true if cross-transport key derivation is used. + * + * **Example:** + * @code + * void on_bond_state_changed(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) + * { + * printf("Bond state changed: %d -> %d\n", previous_state, current_state); + * } + */ +typedef void (*on_bond_state_changed_callback_extra)(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd); + /** * @brief Got local OOB data for LE secure connection pairing callback. * @@ -512,6 +536,7 @@ typedef struct { on_connect_request_callback on_connect_request; on_connection_state_changed_callback on_connection_state_changed; on_bond_state_changed_callback on_bond_state_changed; + on_bond_state_changed_callback_extra on_bond_state_changed_extra; on_le_sc_local_oob_data_got_callback on_le_sc_local_oob_data_got; on_remote_name_changed_callback on_remote_name_changed; on_remote_alias_changed_callback on_remote_alias_changed; diff --git a/service/ipc/socket/include/bt_message_adapter.h b/service/ipc/socket/include/bt_message_adapter.h index ef42f21a..616bcce9 100644 --- a/service/ipc/socket/include/bt_message_adapter.h +++ b/service/ipc/socket/include/bt_message_adapter.h @@ -224,7 +224,8 @@ BT_ADAPTER_MESSAGE_START, struct { bt_address_t addr; uint8_t transport; /* bt_transport_t */ - uint8_t state; /* bond_state_t */ + uint8_t previous_state; /* bond_state_t */ + uint8_t current_state; /* bond_state_t */ uint8_t is_ctkd; /* boolean */ } _on_bond_state_changed; diff --git a/service/ipc/socket/src/bt_socket_adapter.c b/service/ipc/socket/src/bt_socket_adapter.c index 1b3b3f26..3c1efb99 100644 --- a/service/ipc/socket/src/bt_socket_adapter.c +++ b/service/ipc/socket/src/bt_socket_adapter.c @@ -162,14 +162,15 @@ static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, } static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, - bt_transport_t transport, bond_state_t state, bool is_ctkd) + bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) { bt_message_packet_t packet = { 0 }; bt_instance_t* ins = cookie; memcpy(&packet.adpt_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); packet.adpt_cb._on_bond_state_changed.transport = transport; - packet.adpt_cb._on_bond_state_changed.state = state; + packet.adpt_cb._on_bond_state_changed.previous_state = previous_state; + packet.adpt_cb._on_bond_state_changed.current_state = current_state; packet.adpt_cb._on_bond_state_changed.is_ctkd = is_ctkd; bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_BOND_STATE_CHANGED); @@ -249,7 +250,7 @@ const static adapter_callbacks_t g_adapter_socket_cbs = { .on_pair_display = on_pair_display_cb, .on_connect_request = on_connect_request_cb, .on_connection_state_changed = on_connection_state_changed_cb, - .on_bond_state_changed = on_bond_state_changed_cb, + .on_bond_state_changed_extra = on_bond_state_changed_cb, .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, .on_remote_name_changed = on_remote_name_changed_cb, .on_remote_alias_changed = on_remote_alias_changed_cb, @@ -587,11 +588,20 @@ int bt_socket_client_adapter_callback(service_poll_t* poll, break; } case BT_ADAPTER_ON_BOND_STATE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_bond_state_changed_extra, + &packet->adpt_cb._on_bond_state_changed.addr, + packet->adpt_cb._on_bond_state_changed.transport, + packet->adpt_cb._on_bond_state_changed.previous_state, + packet->adpt_cb._on_bond_state_changed.current_state, + packet->adpt_cb._on_bond_state_changed.is_ctkd); + + // Compatible with on_bond_state_changed callback. CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed, &packet->adpt_cb._on_bond_state_changed.addr, packet->adpt_cb._on_bond_state_changed.transport, - packet->adpt_cb._on_bond_state_changed.state, + packet->adpt_cb._on_bond_state_changed.current_state, packet->adpt_cb._on_bond_state_changed.is_ctkd); break; } -- Gitee From da50a7d7061a78e845ac2c1ffc8b8dce9dfa5701 Mon Sep 17 00:00:00 2001 From: chejinxian1 Date: Thu, 20 Mar 2025 21:59:38 +0800 Subject: [PATCH 040/498] Adapter: Modify bond state notification method. bug: v/54141 Rootcause: Modify the implementation of the bond state notification to support reporting the previous state. To more accurately report the bond state at the Framework layer, this modification will trigger a notification event when changing the bond state. To avoid long-term occupation of the adapter_lock, the method of do_in_service_loop is used to execute the actual notification. Signed-off-by: chejinxian1 --- service/src/adapter_internel.h | 6 +++++ service/src/adapter_service.c | 44 +++++++++++++++++++++++++--------- service/src/device.c | 22 ++++++++++++++++- service/src/device.h | 2 +- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/service/src/adapter_internel.h b/service/src/adapter_internel.h index aeb02a6c..4a912727 100644 --- a/service/src/adapter_internel.h +++ b/service/src/adapter_internel.h @@ -48,6 +48,12 @@ enum { LE_SC_LOCAL_OOB_DATA_GOT_EVT, }; +typedef struct { + void* device; + bond_state_t previous_state; + bool is_ctkd; +} bond_state_change_message_t; + typedef struct { bt_address_t addr; // Remote BT address ble_addr_type_t addr_type; // if link type is ble connection type diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index e128819b..d58a5d91 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -118,6 +118,30 @@ static void adapter_unlock(void) pthread_mutex_unlock(&g_adapter_service.adapter_lock); } +static void adapter_notify_bond_state(void* data) +{ + bond_state_change_message_t* msg = (bond_state_change_message_t*)data; + bt_device_t* device; + bt_address_t* addr; + bt_transport_t transport; + bond_state_t current_state; + + if (!msg) { + BT_LOGE("msg is NULL"); + return; + } + + device = (bt_device_t*)msg->device; + adapter_lock(); + addr = device_get_address(device); + transport = device_get_transport(device); + current_state = device_get_bond_state(device); + adapter_unlock(); + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed_extra, addr, transport, + msg->previous_state, current_state, msg->is_ctkd); + free(msg); +} + static bt_device_t* adapter_find_device(const bt_address_t* addr, bt_transport_t transport) { bt_list_node_t* node; @@ -317,7 +341,7 @@ static void bonded_device_loaded(void* data, uint16_t length, uint16_t items) device_set_device_type(device, remote->device_type); device_set_link_key(device, remote->link_key); device_set_link_key_type(device, remote->link_key_type); - device_set_bond_state(device, BOND_STATE_BONDED); + device_set_bond_state(device, BOND_STATE_BONDED, false, NULL); load_remote_uuids(remote, device); bt_list_add_tail(g_adapter_service.devices, device); bt_addr_ba2str(&remote->addr, addr_str); @@ -362,7 +386,7 @@ static void le_bonded_device_loaded(void* data, uint16_t length, uint16_t items) BT_LOGD("load ble bonded device successfully:"); for (int i = 0; i < items; i++) { bt_device_t* device = adapter_find_create_le_device(&remote->addr, remote->addr_type); - device_set_bond_state(device, BOND_STATE_BONDED); + device_set_bond_state(device, BOND_STATE_BONDED, false, NULL); device_set_smp_key(device, remote->smp_key); device_set_identity_address(device, (bt_address_t*)remote->smp_key); bt_addr_ba2str(&remote->addr, addr_str); @@ -495,7 +519,7 @@ static void process_pin_request_evt(bt_address_t* addr, uint32_t cod, } if (device_get_bond_state(device) != BOND_STATE_BONDING) - device_set_bond_state(device, BOND_STATE_BONDING); + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); adapter_unlock(); /* send pin code request notification*/ send_pair_display_notification(addr, BT_TRANSPORT_BREDR, PAIR_TYPE_PIN_CODE, 0x0); @@ -533,7 +557,7 @@ static void process_ssp_request_evt(bt_address_t* addr, uint8_t transport, } if (device_get_bond_state(device) != BOND_STATE_BONDING) - device_set_bond_state(device, BOND_STATE_BONDING); + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); adapter_unlock(); /* send ssp request notification*/ send_pair_display_notification(addr, transport, ssp_type, pass_key); @@ -549,7 +573,7 @@ static void process_bond_state_change_evt(bt_address_t* addr, bond_state_t state if (transport == BT_TRANSPORT_BREDR) { device = adapter_find_create_classic_device(addr); if (state == BOND_STATE_BONDED) { - device_set_bond_state(device, BOND_STATE_BONDED); + device_set_bond_state(device, BOND_STATE_BONDED, is_ctkd, adapter_notify_bond_state); bt_sal_get_remote_device_info(PRIMARY_ADAPTER, addr, &remote); device_set_device_type(device, remote.device_type); /* update bonded device info */ @@ -574,10 +598,8 @@ static void process_bond_state_change_evt(bt_address_t* addr, bond_state_t state #endif } - device_set_bond_state(device, state); + device_set_bond_state(device, state, is_ctkd, adapter_notify_bond_state); adapter_unlock(); - /* send bond state change notification */ - CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed, addr, transport, state, is_ctkd); } static void process_service_search_done_evt(bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) @@ -643,7 +665,7 @@ static void process_link_key_removed_evt(bt_address_t* addr, bt_status_t status) device = adapter_find_create_classic_device(addr); device_delete_link_key(device); if (device_get_bond_state(device) == BOND_STATE_BONDED) - device_set_bond_state(device, BOND_STATE_NONE); + device_set_bond_state(device, BOND_STATE_NONE, false, adapter_notify_bond_state); /* remove bond device */ adapter_update_bonded_device(); adapter_unlock(); @@ -2699,7 +2721,7 @@ bt_status_t adapter_remove_bond(bt_address_t* addr, uint8_t transport) return BT_STATUS_FAIL; } - device_set_bond_state(device, BOND_STATE_NONE); + device_set_bond_state(device, BOND_STATE_NONE, false, adapter_notify_bond_state); #ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT if (transport == BT_TRANSPORT_BREDR) { device_delete_link_key(device); @@ -2729,7 +2751,7 @@ bt_status_t adapter_cancel_bond(bt_address_t* addr) bt_status_t status = bt_sal_cancel_bond(PRIMARY_ADAPTER, addr, BT_TRANSPORT_BREDR); if (status == BT_STATUS_SUCCESS) - device_set_bond_state(device, BOND_STATE_CANCELING); + device_set_bond_state(device, BOND_STATE_CANCELING, false, adapter_notify_bond_state); adapter_unlock(); return status; diff --git a/service/src/device.c b/service/src/device.c index 9e1ad9f4..c51a37c4 100644 --- a/service/src/device.c +++ b/service/src/device.c @@ -31,6 +31,7 @@ #include "bt_utils.h" #include "bt_uuid.h" #include "device.h" +#include "service_loop.h" #include "utils/log.h" #define BASE_UUID16_OFFSET 12 @@ -339,9 +340,28 @@ bond_state_t device_get_bond_state(bt_device_t* device) return device->remote.bond_state; } -void device_set_bond_state(bt_device_t* device, bond_state_t state) +void device_set_bond_state(bt_device_t* device, bond_state_t state, bool is_ctkd, void* notify_change) { + bond_state_change_message_t* msg; + bond_state_t prev_state = device->remote.bond_state; + + if (prev_state == state) + return; + device->remote.bond_state = state; + if (!notify_change) + return; + + msg = zalloc(sizeof(bond_state_change_message_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->device = device; + msg->previous_state = prev_state; + msg->is_ctkd = is_ctkd; + do_in_service_loop(notify_change, msg); } bool device_is_bonded(bt_device_t* device) diff --git a/service/src/device.h b/service/src/device.h index 7bc79f10..795f292d 100644 --- a/service/src/device.h +++ b/service/src/device.h @@ -65,7 +65,7 @@ void device_set_local_role(bt_device_t* device, bt_link_role_t role); void device_set_bond_initiate_local(bt_device_t* device, bool initiate_local); bool device_is_bond_initiate_local(bt_device_t* device); bond_state_t device_get_bond_state(bt_device_t* device); -void device_set_bond_state(bt_device_t* device, bond_state_t state); +void device_set_bond_state(bt_device_t* device, bond_state_t state, bool is_ctkd, void* notify_change); bool device_is_bonded(bt_device_t* device); uint8_t* device_get_link_key(bt_device_t* device); void device_set_link_key(bt_device_t* device, bt_128key_t link_key); -- Gitee From 4585d98356b10be33319f62a8abb2c298a645c04 Mon Sep 17 00:00:00 2001 From: chejinxian1 Date: Thu, 20 Mar 2025 22:25:06 +0800 Subject: [PATCH 041/498] Adapter: Modify the timing of entering bonding state. bug: v/54141 Rootcause: After agreeing to exchange IO capabilities, the upper layer was notified to enter the bonding state, but the Framework did not actually enter bonding. The purpose of entering bonding here is to avoid the situation where insufficient IO capabilities prevent the corresponding information from being obtained from the protocol stack to enter bonding. Signed-off-by: chejinxian1 --- service/src/adapter_service.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index d58a5d91..4ffda4bd 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -2769,9 +2769,9 @@ bt_status_t adapter_pair_request_reply(bt_address_t* addr, bool accept) bt_status_t status; status = bt_sal_pair_reply(PRIMARY_ADAPTER, addr, accept ? 0 : HCI_ERR_PAIRING_NOT_ALLOWED); if (status == BT_STATUS_SUCCESS && accept) { - /* callback bonding */ - CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed, - addr, BT_TRANSPORT_BREDR, BOND_STATE_BONDING, false); + adapter_lock(); + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); + adapter_unlock(); } return status; -- Gitee From 373ffff3e685c9fa32077890aba58d9dd67b91c4 Mon Sep 17 00:00:00 2001 From: chejinxian1 Date: Thu, 20 Mar 2025 22:33:15 +0800 Subject: [PATCH 042/498] bttool: Adapt to the latest bond status callback interface. bug: v/54141 Rootcause: Modify the callback function for obtaining the bond state in bttool. Signed-off-by: chejinxian1 --- tools/bt_tools.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/bt_tools.c b/tools/bt_tools.c index 19c3f2ba..cadec953 100644 --- a/tools/bt_tools.c +++ b/tools/bt_tools.c @@ -1565,10 +1565,12 @@ static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, bt_ PRINT_ADDR("Device [%s][%s] connection state: %d", addr, LINK_TYPE(transport), state); } -static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, + bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) { - g_bond_state = state; - PRINT_ADDR("Device [%s][%s] bond state: %s, is_ctkd: %d", addr, LINK_TYPE(transport), bond_state_to_string(state), is_ctkd); + g_bond_state = current_state; + PRINT_ADDR("Device [%s][%s] bond state: %s -> %s, is_ctkd: %d", addr, LINK_TYPE(transport), + bond_state_to_string(previous_state), bond_state_to_string(current_state), is_ctkd); } static void on_le_sc_local_oob_data_got_cb(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) @@ -1628,7 +1630,7 @@ const static adapter_callbacks_t g_adapter_cbs = { .on_pair_display = on_pair_display_cb, .on_connect_request = on_connect_request_cb, .on_connection_state_changed = on_connection_state_changed_cb, - .on_bond_state_changed = on_bond_state_changed_cb, + .on_bond_state_changed_extra = on_bond_state_changed_cb, .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, .on_remote_name_changed = on_remote_name_changed_cb, .on_remote_alias_changed = on_remote_alias_changed_cb, -- Gitee From d05689966fe67fb4e85e8c6b17cbb7f0103449ec Mon Sep 17 00:00:00 2001 From: chejinxian1 Date: Thu, 20 Mar 2025 22:55:18 +0800 Subject: [PATCH 043/498] Adapter: Cancel reporting BOND_STATE_CANCELING. bug: v/54141 Rootcause: BOND_STATE_CANCELING is the transitional state from BOND_STATE_BONDING to BOND_STATE_BONDED in the case of actively canceling the pairing. Since only the upper layer initiates the pairing cancellation will enter this state, the upper layer application does not care about entering BOND_STATE_CANCELING, so filter out this state report. This is more in line with the habits of Bluetooth software development. Signed-off-by: chejinxian1 --- service/src/adapter_service.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index 4ffda4bd..a2de828e 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -125,6 +125,7 @@ static void adapter_notify_bond_state(void* data) bt_address_t* addr; bt_transport_t transport; bond_state_t current_state; + bond_state_t previous_state; if (!msg) { BT_LOGE("msg is NULL"); @@ -137,8 +138,19 @@ static void adapter_notify_bond_state(void* data) transport = device_get_transport(device); current_state = device_get_bond_state(device); adapter_unlock(); + previous_state = msg->previous_state; + if (previous_state == BOND_STATE_CANCELING) { + if (current_state != BOND_STATE_NONE) { + BT_LOGE("previous state is canceling, but current state is not none"); + free(msg); + return; + } else { + previous_state = BOND_STATE_BONDING; // report bonding -> none + } + } + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed_extra, addr, transport, - msg->previous_state, current_state, msg->is_ctkd); + previous_state, current_state, msg->is_ctkd); free(msg); } @@ -2751,7 +2763,7 @@ bt_status_t adapter_cancel_bond(bt_address_t* addr) bt_status_t status = bt_sal_cancel_bond(PRIMARY_ADAPTER, addr, BT_TRANSPORT_BREDR); if (status == BT_STATUS_SUCCESS) - device_set_bond_state(device, BOND_STATE_CANCELING, false, adapter_notify_bond_state); + device_set_bond_state(device, BOND_STATE_CANCELING, false, NULL); // Filter out the reporting of BOND_STATE_CANCELING. adapter_unlock(); return status; -- Gitee From a280d6c94525c86cf2917f502a6b4d1934915464 Mon Sep 17 00:00:00 2001 From: zhangyuan20 Date: Thu, 27 Mar 2025 12:09:40 +0800 Subject: [PATCH 044/498] Android-Vela: reset call status when hf is disconnected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bug: v/57401 rootcause: When hfp is disconnected, “last_reported” is not reset, causing the call status to be out of sync after reconnection Signed-off-by: zhangyuan20 --- service/profiles/hfp_hf/hfp_hf_state_machine.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index c70eb873..38c35bf3 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -410,6 +410,10 @@ static void state_machine_reset_calls(hf_state_machine_t* hfsm) if (hfsm->connect_timer) service_loop_cancel_timer(hfsm->connect_timer); hfsm->recognition_active = false; + + hfsm->call_status.last_reported.call_status = HFP_CALL_NO_CALLS_IN_PROGRESS; + hfsm->call_status.last_reported.callheld_status = HFP_CALLHELD_NONE; + hfsm->call_status.last_reported.callsetup_status = HFP_CALLSETUP_NONE; } static void update_remote_features(hf_state_machine_t* hfsm, uint32_t remote_features) -- Gitee From 3433b1e41d995d09c2e5421799be1673df609a39 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Thu, 20 Mar 2025 19:01:24 +0800 Subject: [PATCH 045/498] HFP: prevent repeated SCO disconnection bug: v/57380 Root cause: The duplicated SCO disconnection won't complete and would obstruct the HCI queue for 10 seconds. Signed-off-by: Zihao Gao --- .../profiles/hfp_hf/hfp_hf_state_machine.c | 50 +++++++++++++------ .../profiles/include/hfp_hf_state_machine.h | 2 + 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index 38c35bf3..0c70162e 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -627,6 +627,28 @@ static bool check_sco_allowed(state_machine_t* sm) return true; } +static void try_disconnect_audio(hf_state_machine_t* hfsm) +{ + BT_ADDR_LOG("Try disconnect audio for :%s", &hfsm->addr); + + if (flag_isset(hfsm, PENDING_AUDIO_DISCONNECT)) { + BT_LOGD("Previous audio disconnection is pending"); + return; + } + + if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_LOGE("Failed to disconnect audio"); + return; + } + + // Should set flag when SCO not connected? + if (hf_state_machine_get_state(hfsm) == HFP_HF_STATE_AUDIO_CONNECTED) { + flag_set(hfsm, PENDING_AUDIO_DISCONNECT); + } else { + BT_LOGW("SCO not connected"); + } +} + #ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER static void channel_type_verdict(state_machine_t* sm, uint32_t event, uint32_t status, uint64_t current_timestamp_us) @@ -642,8 +664,7 @@ static void channel_type_verdict(state_machine_t* sm, uint32_t event, uint32_t s BT_LOGD("%s: this might be a video chat from WeChat", __func__); hfsm->call_status.webchat_flag_timestamp_us = current_timestamp_us; if (hf_state_machine_get_state(hfsm) == HFP_HF_STATE_AUDIO_CONNECTED && !check_sco_allowed(sm)) { - if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) - BT_ADDR_LOG("Terminate audio failed for :%s", &hfsm->addr); + try_disconnect_audio(hfsm); } } } @@ -1188,8 +1209,7 @@ static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p } break; case HF_DISCONNECT_AUDIO: - if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) - BT_ADDR_LOG("Disconnect audio failed for :%s", &hfsm->addr); + try_disconnect_audio(hfsm); // Should set flag when SCO not connected? break; case HF_VOICE_RECOGNITION_START: if (!hfsm->recognition_active) { @@ -1265,6 +1285,9 @@ static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p hsm_transition_to(sm, &audio_on_state); break; case HFP_AUDIO_STATE_DISCONNECTED: + BT_LOGW("SCO disconnected without connected"); + flag_clear(hfsm, PENDING_AUDIO_DISCONNECT); + break; default: break; } @@ -1313,9 +1336,9 @@ static void audio_on_enter(state_machine_t* sm) bt_media_set_sco_available(); } else { BT_LOGI("SCO is not allowed"); - if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) - BT_ADDR_LOG("Terminate audio failed for :%s", &hfsm->addr); + try_disconnect_audio(hfsm); } + hf_service_notify_audio_state_changed(&hfsm->addr, HFP_AUDIO_STATE_CONNECTED); } @@ -1343,6 +1366,8 @@ static void audio_on_exit(state_machine_t* sm) } } + flag_clear(hfsm, PENDING_AUDIO_DISCONNECT); + hf_service_notify_audio_state_changed(&hfsm->addr, HFP_AUDIO_STATE_DISCONNECTED); } @@ -1362,10 +1387,7 @@ static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_ hsm_transition_to(sm, &disconnected_state); break; case HF_DISCONNECT_AUDIO: - status = bt_sal_hfp_hf_disconnect_audio(&hfsm->addr); - if (status != BT_STATUS_SUCCESS) { - BT_LOGE("Disconnect Sco connection failed"); - } + try_disconnect_audio(hfsm); break; case HF_VOICE_RECOGNITION_STOP: if (hfsm->recognition_active) { @@ -1451,9 +1473,7 @@ static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_ if (result != HCI_SUCCESS) { BT_LOGE("HF_OFFLOAD_START fail, status:0x%0x", result); audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); - if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) { - BT_ADDR_LOG("Terminate audio failed for :%s", &hfsm->addr); - } + try_disconnect_audio(hfsm); break; } @@ -1464,9 +1484,7 @@ static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_ flag_clear(hfsm, PENDING_OFFLOAD_START); hfsm->offload_timer = NULL; audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); - if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) { - BT_ADDR_LOG("Terminate audio failed for :%s", &hfsm->addr); - } + try_disconnect_audio(hfsm); break; } case HF_OFFLOAD_STOP_REQ: diff --git a/service/profiles/include/hfp_hf_state_machine.h b/service/profiles/include/hfp_hf_state_machine.h index 63b9267a..6ed87807 100644 --- a/service/profiles/include/hfp_hf_state_machine.h +++ b/service/profiles/include/hfp_hf_state_machine.h @@ -24,6 +24,8 @@ typedef enum pending_state { PENDING_NONE = 0x0, PENDING_OFFLOAD_START = 0x02, PENDING_OFFLOAD_STOP = 0x04, + // PENDING_DISCONNECT = 0x08, + PENDING_AUDIO_DISCONNECT = 0x10, } pending_state_t; typedef struct _hf_state_machine hf_state_machine_t; -- Gitee From 9a1646cda724c240fa350bbb4fc11014ca138892 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Wed, 2 Apr 2025 16:37:26 +0800 Subject: [PATCH 046/498] HFP: report direction change if happens. bug: v/57592 Root cause: the direction of the current call is not reported correctly if two calls have identical indexes but opposite directions in consecutive CLCC responses. Signed-off-by: Zihao Gao --- service/profiles/hfp_hf/hfp_hf_state_machine.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index 0c70162e..89caaaa7 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -376,8 +376,10 @@ static void query_current_calls_final(hf_state_machine_t* hfsm) if (!cnode) break; } else { - if (ucall->state != ccall->state || ucall->mpty != ccall->mpty || strcmp(ucall->number, ccall->number)) { + if (ucall->dir != ccall->dir || ucall->state != ccall->state + || ucall->mpty != ccall->mpty || strcmp(ucall->number, ccall->number)) { /* call state or mutil part or number changed, notify changed */ + ccall->dir = ucall->dir; ccall->state = ucall->state; ccall->mpty = ucall->mpty; snprintf(ccall->number, HFP_PHONENUM_DIGITS_MAX, "%s", ucall->number); -- Gitee From 3ab4cfdf8a4b7ad11c145aad74d0cdc47675dea4 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Thu, 3 Apr 2025 10:13:39 +0800 Subject: [PATCH 047/498] bluetooth: Fix the HFP memory leak issue. bug: v/57317 rootcause: When shutdown, the started status of HFP is set to false, and subsequent events will not be processed, causing the memory of messages to not be released, leading to memory leaks. Signed-off-by: jialu --- service/profiles/hfp_ag/hfp_ag_service.c | 4 +++- service/profiles/hfp_hf/hfp_hf_service.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c index 9bb84d89..21e44872 100644 --- a/service/profiles/hfp_ag/hfp_ag_service.c +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -280,8 +280,10 @@ static void hfp_ag_process_message(void* data) { hfp_ag_msg_t* msg = (hfp_ag_msg_t*)data; - if (!g_ag_service.started && msg->event != AG_STARTUP) + if (!g_ag_service.started && msg->event != AG_STARTUP) { + hfp_ag_msg_destory(msg); return; + } switch (msg->event) { case AG_STARTUP: diff --git a/service/profiles/hfp_hf/hfp_hf_service.c b/service/profiles/hfp_hf/hfp_hf_service.c index 43dfe0a3..82fc01b7 100644 --- a/service/profiles/hfp_hf/hfp_hf_service.c +++ b/service/profiles/hfp_hf/hfp_hf_service.c @@ -248,8 +248,10 @@ static void hfp_hf_process_message(void* data) { hfp_hf_msg_t* msg = (hfp_hf_msg_t*)data; - if (!g_hfp_service.started && msg->event != HF_STARTUP) + if (!g_hfp_service.started && msg->event != HF_STARTUP) { + hfp_hf_msg_destroy(msg); return; + } switch (msg->event) { case HF_STARTUP: -- Gitee From ffc40206b6b4189d9b838dbe6b69968a5ded8dae Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Thu, 3 Apr 2025 14:14:37 +0800 Subject: [PATCH 048/498] adapter: try get remote name when linkkey report bug: v/56645 Signed-off-by: fangzhenwei --- service/src/adapter_service.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index a2de828e..2a3475f8 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -658,6 +658,12 @@ static void process_link_key_update_evt(bt_address_t* addr, bt_128key_t link_key adapter_lock(); device = adapter_find_create_classic_device(addr); + if (!device_check_flag(device, DFLAG_NAME_SET | DFLAG_GET_RMT_NAME)) { + BT_LOGD("linkkey notify, request remote name..."); + bt_sal_get_remote_name(PRIMARY_ADAPTER, addr); + device_set_flags(device, DFLAG_GET_RMT_NAME); + } + device_set_link_key(device, link_key); device_set_link_key_type(device, type); adapter_update_bonded_device(); -- Gitee From 34be6d2e545c946230f32f69a7c7ad3199c994a8 Mon Sep 17 00:00:00 2001 From: fangzhenwei Date: Tue, 11 Mar 2025 10:45:16 +0800 Subject: [PATCH 049/498] Fix bttool stackoverflow bug: v/55027 increase bttool-cmd-exec stacksize to 8192, uv and IPC packet buffer used 3200, high risk stackoverflow when executing cmd. Signed-off-by: fangzhenwei --- tools/bt_tools.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/bt_tools.c b/tools/bt_tools.c index cadec953..ca64d143 100644 --- a/tools/bt_tools.c +++ b/tools/bt_tools.c @@ -1791,6 +1791,10 @@ static void bttool_thread(void* data) static int bttool_create_thread(bttool_t* bttool) { int ret; + uv_thread_options_t options = { + .flags = UV_THREAD_HAS_STACK_SIZE, + .stack_size = 8192, + }; ret = uv_sem_init(&bttool->ready, 0); if (ret != 0) { @@ -1798,7 +1802,7 @@ static int bttool_create_thread(bttool_t* bttool) return ret; } - ret = uv_thread_create(&bttool->thread, bttool_thread, (void*)bttool); + ret = uv_thread_create_ex(&bttool->thread, &options, bttool_thread, (void*)bttool); if (ret != 0) { PRINT("loop thread create :%d", ret); return ret; -- Gitee From 0a064b19d574e723e0634d3121dca55413f47d8c Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Mon, 10 Mar 2025 14:59:14 +0800 Subject: [PATCH 050/498] bluetooth: adapter: Solve the problem of scanmode setting failure. bug: v/55642 rootcause: When the discoverable mode changes and iscan=false, bt_br_set_discoverable will not be called. Additionally, when the discoverable or connectable mode changes, it does not necessarily mean that the value of scanmode in storage will change. Signed-off-by: jialu --- .../stacks/zephyr/sal_adapter_classic_interface.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index 6cc1785c..6998c720 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -713,6 +713,7 @@ static void STACK_CALL(set_scan_mode)(void* args) sal_adapter_req_t* req = args; bool iscan = false; bool pscan = false; + int ret; switch (req->adpt.scanmode.scan_mode) { case BT_SCAN_MODE_NONE: @@ -730,20 +731,12 @@ static void STACK_CALL(set_scan_mode)(void* args) break; } - int ret = bt_br_set_connectable(pscan); + ret = bt_br_set_visibility(iscan, pscan); if (ret != 0 && ret != -EALREADY) { - BT_LOGE("%s set connectable failed:%d", __func__, ret); + BT_LOGE("%s set scanmode failed:%d", __func__, ret); return; } - if (iscan) { - ret = bt_br_set_discoverable(iscan); - if (ret != 0 && ret != -EALREADY) { - BT_LOGE("%s set discoverable failed:%d", __func__, ret); - return; - } - } - if (ret == 0) adapter_on_scan_mode_changed(req->adpt.scanmode.scan_mode); } -- Gitee From f190162fd61fdedd02f74c12462b5e61c39ad230 Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 17 Apr 2025 17:28:09 +0800 Subject: [PATCH 051/498] adv: fix different adv type paramters set. bug: v/58660 Setting different advertising parameters for different advertising type. Signed-off-by: liuxiang18 --- .../zephyr/sal_le_advertise_interface.c | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c index 60aa8714..ea96cedb 100644 --- a/service/stacks/zephyr/sal_le_advertise_interface.c +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -100,38 +100,61 @@ static bt_status_t zblue_le_ext_convert_param(ble_adv_params_t* params, struct b switch (params->adv_type) { case BT_LE_ADV_IND: - case BT_LE_ADV_DIRECT_IND: + case BT_LE_EXT_ADV_IND: + param->options |= BT_LE_ADV_OPT_CONN; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + break; case BT_LE_ADV_SCAN_IND: + case BT_LE_EXT_ADV_SCAN_IND: param->options |= BT_LE_ADV_OPT_SCANNABLE; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + break; + case BT_LE_ADV_DIRECT_IND: + case BT_LE_EXT_ADV_DIRECT_IND: param->options |= BT_LE_ADV_OPT_CONN; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + param->options |= BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY; break; + case BT_LE_SCAN_RSP: + case BT_LE_EXT_SCAN_RSP: case BT_LE_ADV_NONCONN_IND: + case BT_LE_EXT_ADV_NONCONN_IND: param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; break; - case BT_LE_SCAN_RSP: + case BT_LE_LEGACY_ADV_IND: param->options |= BT_LE_ADV_OPT_CONN; param->options |= BT_LE_ADV_OPT_SCANNABLE; - param->options |= BT_LE_ADV_OPT_EXT_ADV; break; - case BT_LE_LEGACY_ADV_IND: case BT_LE_LEGACY_ADV_DIRECT_IND: - case BT_LE_LEGACY_ADV_SCAN_IND: param->options |= BT_LE_ADV_OPT_CONN; break; - case BT_LE_LEGACY_ADV_NONCONN_IND: + case BT_LE_LEGACY_ADV_SCAN_IND: + param->options |= BT_LE_ADV_OPT_SCANNABLE; break; + case BT_LE_LEGACY_ADV_NONCONN_IND: case BT_LE_LEGACY_SCAN_RSP: - param->options |= BT_LE_ADV_OPT_SCANNABLE; break; default: BT_LOGE("%s, le ext adv convert fail, invalid adv_type:%d", __func__, params->adv_type); return BT_STATUS_PARM_INVALID; } + switch (params->own_addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + param->options |= BT_LE_ADV_OPT_USE_IDENTITY; + break; + } + param->interval_min = params->interval; param->interval_max = params->interval; - if (params->adv_type == BT_LE_ADV_DIRECT_IND) { + if (params->adv_type == BT_LE_ADV_DIRECT_IND + || params->adv_type == BT_LE_EXT_ADV_DIRECT_IND + || params->adv_type == BT_LE_LEGACY_ADV_DIRECT_IND) { addr.type = params->peer_addr_type; memcpy(&addr.a, ¶ms->peer_addr, sizeof(bt_address_t)); param->peer = &addr; -- Gitee From 3b45f7d405863e098c37bbaaaa41d8ccf3cf8651 Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 17 Apr 2025 17:37:20 +0800 Subject: [PATCH 052/498] adv: fix adv data and scan rsp set error. bug: v/58660 scan_rsp must be NULL when adv is not scannable if adv is ext_adv. And adv_data is the same. Signed-off-by: liuxiang18 --- .../zephyr/sal_le_advertise_interface.c | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c index ea96cedb..c5eb8e1c 100644 --- a/service/stacks/zephyr/sal_le_advertise_interface.c +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -334,14 +334,17 @@ static void STACK_CALL(start_adv)(void* args) ret = BT_STATUS_SUCCESS; done: - free(req->adpt.start_adv.adv_data); - free(req->adpt.start_adv.scan_rsp_data); + if (req->adpt.start_adv.adv_data) + free(req->adpt.start_adv.adv_data); + if (req->adpt.start_adv.scan_rsp_data) + free(req->adpt.start_adv.scan_rsp_data); } bt_status_t bt_sal_le_start_adv(bt_controller_id_t id, uint8_t adv_id, ble_adv_params_t* params, uint8_t* adv_data, uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len) { sal_adapter_req_t* req; int ret; + bool ext_adv; req = sal_adapter_req(id, adv_id, STACK_CALL(start_adv)); if (!req) { @@ -356,29 +359,47 @@ bt_status_t bt_sal_le_start_adv(bt_controller_id_t id, uint8_t adv_id, ble_adv_p goto error; } - req->adpt.start_adv.adv_data = malloc(adv_len); - if (!req->adpt.start_adv.adv_data) { - BT_LOGE("%s, malloc fail", __func__); - ret = BT_STATUS_NOMEM; - goto error; - } + ext_adv = (req->adpt.start_adv.param.options & BT_LE_ADV_OPT_EXT_ADV) ? true : false; - memcpy(req->adpt.start_adv.adv_data, adv_data, adv_len); - req->adpt.start_adv.adv_len = adv_len; + if ((!(req->adpt.start_adv.param.options & BT_LE_ADV_OPT_SCANNABLE) && ext_adv) + || !ext_adv) { + req->adpt.start_adv.adv_data = malloc(adv_len); + if (!req->adpt.start_adv.adv_data) { + BT_LOGE("%s, malloc fail", __func__); + ret = BT_STATUS_NOMEM; + goto error; + } - req->adpt.start_adv.scan_rsp_data = malloc(scan_rsp_len); - if (!req->adpt.start_adv.scan_rsp_data) { - BT_LOGE("%s, malloc fail", __func__); - ret = BT_STATUS_NOMEM; - goto error; + memcpy(req->adpt.start_adv.adv_data, adv_data, adv_len); + req->adpt.start_adv.adv_len = adv_len; + } else { + req->adpt.start_adv.adv_data = NULL; + req->adpt.start_adv.adv_len = 0; } - memcpy(req->adpt.start_adv.scan_rsp_data, scan_rsp_data, scan_rsp_len); - req->adpt.start_adv.scan_rsp_len = scan_rsp_len; + if (((req->adpt.start_adv.param.options & BT_LE_ADV_OPT_SCANNABLE) && ext_adv) + || !ext_adv) { + req->adpt.start_adv.scan_rsp_data = malloc(scan_rsp_len); + if (!req->adpt.start_adv.scan_rsp_data) { + BT_LOGE("%s, malloc fail", __func__); + ret = BT_STATUS_NOMEM; + goto error; + } + + memcpy(req->adpt.start_adv.scan_rsp_data, scan_rsp_data, scan_rsp_len); + req->adpt.start_adv.scan_rsp_len = scan_rsp_len; + } else { + req->adpt.start_adv.scan_rsp_data = NULL; + req->adpt.start_adv.scan_rsp_len = 0; + } return sal_send_req(req); error: + if (req->adpt.start_adv.adv_data) + free(req->adpt.start_adv.adv_data); + if (req->adpt.start_adv.scan_rsp_data) + free(req->adpt.start_adv.scan_rsp_data); free(req); return ret; }; -- Gitee From 33d5869f4e61948421797319fffee2e411b9198d Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Tue, 8 Apr 2025 19:54:09 +0800 Subject: [PATCH 053/498] Adv: adapt ext_adv channel map choice. bug: v/55672 adapt adv paramter for channel map field. Signed-off-by: liuxiang18 --- .../stacks/zephyr/sal_le_advertise_interface.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c index c5eb8e1c..46901107 100644 --- a/service/stacks/zephyr/sal_le_advertise_interface.c +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -149,6 +149,23 @@ static bt_status_t zblue_le_ext_convert_param(ble_adv_params_t* params, struct b break; } + switch (params->channel_map) { + case BT_LE_ADV_CHANNEL_37_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_38 | BT_LE_ADV_OPT_DISABLE_CHAN_39; + break; + case BT_LE_ADV_CHANNEL_38_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_37 | BT_LE_ADV_OPT_DISABLE_CHAN_39; + break; + case BT_LE_ADV_CHANNEL_39_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_37 | BT_LE_ADV_OPT_DISABLE_CHAN_38; + break; + case BT_LE_ADV_CHANNEL_DEFAULT: + break; + default: + BT_LOGE("%s, le ext adv convert fail, invalid channel_map:%d", __func__, params->channel_map); + return BT_STATUS_PARM_INVALID; + } + param->interval_min = params->interval; param->interval_max = params->interval; -- Gitee From 105a30bd502a35c045911aa414d898e3eb8ff758 Mon Sep 17 00:00:00 2001 From: chengkai Date: Fri, 11 Apr 2025 15:49:34 +0800 Subject: [PATCH 054/498] bluetooth: fix SCAN_TYPE build break bug: v/55753 Signed-off-by: chengkai --- framework/include/bt_le_scan.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/include/bt_le_scan.h b/framework/include/bt_le_scan.h index e02393b6..6bee25d1 100644 --- a/framework/include/bt_le_scan.h +++ b/framework/include/bt_le_scan.h @@ -22,6 +22,10 @@ extern "C" { #include +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE +#include +#endif + #include "bluetooth.h" #include "bt_le_advertiser.h" #ifndef BTSYMBOLS -- Gitee From 52a0935d277388150f9722218687795a2955290b Mon Sep 17 00:00:00 2001 From: chengkai Date: Fri, 11 Apr 2025 15:58:19 +0800 Subject: [PATCH 055/498] bluetooth: fix le addr invlaid bug: v/55753 Signed-off-by: chengkai --- service/src/adapter_service.c | 14 ++++++++++++-- service/stacks/include/sal_adapter_le_interface.h | 2 +- service/stacks/zephyr/sal_adapter_le_interface.c | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index 2a3475f8..66e38a56 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -1149,10 +1149,20 @@ void adapter_on_le_enabled(bool enablebt) #ifdef CONFIG_BLUETOOTH_BLE_SUPPORT adapter_service_t* adapter = &g_adapter_service; int ret; + char addrstr[BT_ADDR_STR_LENGTH]; BT_LOGD("%s, enablebt:%d", __func__, enablebt); - /* get le address async */ - bt_sal_le_get_address(PRIMARY_ADAPTER); + + /* get le address */ + ret = bt_sal_le_get_address(PRIMARY_ADAPTER, &adapter->le_properties.addr); + if (ret < 0) { + BT_LOGE("%s, get le address fail, ret:%d", __func__, ret); + bt_addr_set_empty(&adapter->le_properties.addr); + } + + bt_addr_ba2str(&props->addr, addrstr); + BT_LOGD("%s, le_addr:%s", __func__, addrstr); + /* set le io capability ? */ /* set appearance ? */ /* load bonded device to stack ? SMP keys */ diff --git a/service/stacks/include/sal_adapter_le_interface.h b/service/stacks/include/sal_adapter_le_interface.h index 9ff1b53a..a6f92aee 100644 --- a/service/stacks/include/sal_adapter_le_interface.h +++ b/service/stacks/include/sal_adapter_le_interface.h @@ -33,7 +33,7 @@ bt_status_t bt_sal_le_set_io_capability(bt_controller_id_t id, bt_io_capability_ bt_status_t bt_sal_le_set_static_identity(bt_controller_id_t id, bt_address_t* addr); bt_status_t bt_sal_le_set_public_identity(bt_controller_id_t id, bt_address_t* addr); bt_status_t bt_sal_le_set_address(bt_controller_id_t id, bt_address_t* addr); -bt_status_t bt_sal_le_get_address(bt_controller_id_t id); +bt_status_t bt_sal_le_get_address(bt_controller_id_t id, bt_address_t* addr); bt_status_t bt_sal_le_set_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t prop_cnt); bt_status_t bt_sal_le_get_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t* prop_cnt); bt_status_t bt_sal_le_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t type, ble_connect_params_t* params); diff --git a/service/stacks/zephyr/sal_adapter_le_interface.c b/service/stacks/zephyr/sal_adapter_le_interface.c index 3a7e4ff3..c5bc3846 100644 --- a/service/stacks/zephyr/sal_adapter_le_interface.c +++ b/service/stacks/zephyr/sal_adapter_le_interface.c @@ -613,10 +613,19 @@ bt_status_t bt_sal_le_set_address(bt_controller_id_t id, bt_address_t* addr) SAL_NOT_SUPPORT; } -bt_status_t bt_sal_le_get_address(bt_controller_id_t id) +bt_status_t bt_sal_le_get_address(bt_controller_id_t id, bt_address_t* addr) { - /* stack handle this case: */ - SAL_NOT_SUPPORT; + UNUSED(id); + bt_addr_le_t got = { 0 }; + size_t count = 1; + + SAL_CHECK_PARAM(addr); + + bt_id_get(&got, &count); + bt_addr_set(addr, (uint8_t*)&got.a); + + SAL_ASSERT(got.type == BT_ADDR_LE_PUBLIC); + return BT_STATUS_SUCCESS; } bt_status_t bt_sal_le_set_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t prop_cnt) -- Gitee From 9c8caec77577a25a6d9c936d381dce875ed2992d Mon Sep 17 00:00:00 2001 From: chengkai Date: Thu, 17 Apr 2025 17:15:08 +0800 Subject: [PATCH 056/498] bluetooth: fix bredr noinputoutput pair fail bug: v/46022 rootcause: noinputoutput do not meet unauthenticated request, then set to level l2. Signed-off-by: chengkai --- service/stacks/zephyr/sal_adapter_classic_interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index 6998c720..258cf390 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -1220,7 +1220,7 @@ static void STACK_CALL(create_bond)(void* args) bond_state_t state = BOND_STATE_NONE; struct bt_conn* conn; - conn = bt_conn_pair_br((bt_addr_t*)&req->addr, BT_SECURITY_L3); + conn = bt_conn_pair_br((bt_addr_t*)&req->addr, BT_SECURITY_L2); if (conn) { state = BOND_STATE_BONDING; bt_conn_unref(conn); -- Gitee From 0096aeaaaa494f50ca93c6f7836115fabc9db6fa Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Fri, 21 Mar 2025 17:34:49 +0800 Subject: [PATCH 057/498] bluetooth: core: release reference after using ACL conn. bug: v/56422 rootcause: In the bt_conn_lookup_addr_br() function used by AVRCP and A2DP, a reference to a conn is obtained. After using the conn, the reference is not released, causing the reference to remain non-zero after the ACL is disconnected, leading to uncleaned resources. When the connection attempts exceed 5 times, connection failures may occur. Signed-off-by: jialu --- service/stacks/include/sal_interface.h | 10 ++++++ .../zephyr/sal_adapter_classic_interface.c | 2 ++ service/stacks/zephyr/sal_avrcp_interface.c | 36 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/service/stacks/include/sal_interface.h b/service/stacks/include/sal_interface.h index e2fba818..f91f34d3 100644 --- a/service/stacks/include/sal_interface.h +++ b/service/stacks/include/sal_interface.h @@ -73,6 +73,16 @@ typedef struct bt_stack_info { } \ } +#define SAL_CHECK_RET_WITH_CONN(cond, expect, conn) \ + { \ + int __ret = cond; \ + if (__ret != expect) { \ + BT_LOGE("[%s] return:%d", __func__, __ret); \ + bt_conn_unref(conn); \ + return BT_STATUS_FAIL; \ + } \ + } + #define SAL_ASSERT(cond) \ { \ assert(cond); \ diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index 258cf390..da2c3386 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -1257,6 +1257,8 @@ static void STACK_CALL(cancel_bond)(void* args) SAL_CHECK(bt_conn_auth_cancel(conn), 0); SAL_CHECK(bt_br_unpair((bt_addr_t*)&req->addr), 0); + + bt_conn_unref(conn); } #endif diff --git a/service/stacks/zephyr/sal_avrcp_interface.c b/service/stacks/zephyr/sal_avrcp_interface.c index a7c31eb0..84e5897c 100644 --- a/service/stacks/zephyr/sal_avrcp_interface.c +++ b/service/stacks/zephyr/sal_avrcp_interface.c @@ -372,7 +372,9 @@ bt_status_t bt_sal_avrcp_control_connect(bt_controller_id_t id, bt_address_t* ad #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); - SAL_CHECK_RET(bt_avrcp_cttg_connect(conn), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_cttg_connect(conn), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -385,7 +387,9 @@ bt_status_t bt_sal_avrcp_control_disconnect(bt_controller_id_t id, bt_address_t* #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); - SAL_CHECK_RET(bt_avrcp_cttg_disconnect(conn), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_cttg_disconnect(conn), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -401,10 +405,14 @@ bt_status_t bt_sal_avrcp_control_send_pass_through_cmd(bt_controller_id_t id, uint8_t op_id = sal_op_2_zephyr_op(key_code); bool push = key_state == AVRCP_KEY_PRESSED ? true : false; - if (op_id == AVRCP_OPERATION_ID_UNDEFINED) + if (op_id == AVRCP_OPERATION_ID_UNDEFINED) { + bt_conn_unref(conn); return BT_STATUS_PARM_INVALID; + } - SAL_CHECK_RET(bt_avrcp_ct_pass_through_cmd(conn, op_id, push), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_ct_pass_through_cmd(conn, op_id, push), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -442,7 +450,9 @@ bt_status_t bt_sal_avrcp_target_set_absolute_volume(bt_controller_id_t id, bt_ad value.c_param[2] = volume; /* data */ value.c_param[3] = 0; /* Not used */ - SAL_CHECK_RET(bt_avrcp_ct_set_absolute_volume(conn, value.i_param), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_ct_set_absolute_volume(conn, value.i_param), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -456,7 +466,9 @@ bt_status_t bt_sal_avrcp_control_get_capabilities(bt_controller_id_t id, bt_addr #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)bd_addr); - SAL_CHECK_RET(bt_pts_avrcp_ct_get_capabilities(conn), 0); + SAL_CHECK_RET_WITH_CONN(bt_pts_avrcp_ct_get_capabilities(conn), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -469,7 +481,9 @@ bt_status_t bt_sal_avrcp_control_get_playback_state(bt_controller_id_t id, bt_ad #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)bd_addr); - SAL_CHECK_RET(bt_avrcp_ct_get_play_status(conn), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_ct_get_play_status(conn), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -527,7 +541,9 @@ bt_status_t bt_sal_avrcp_control_volume_changed_notify(bt_controller_id_t id, #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)bd_addr); - SAL_CHECK_RET(bt_avrcp_tg_notify_change(conn, volume), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_tg_notify_change(conn, volume), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else @@ -541,7 +557,9 @@ bt_status_t bt_sal_avrcp_control_get_element_attributes(bt_controller_id_t id, #ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)bd_addr); - SAL_CHECK_RET(bt_avrcp_ct_get_id3_info(conn), 0); + SAL_CHECK_RET_WITH_CONN(bt_avrcp_ct_get_id3_info(conn), 0, conn); + + bt_conn_unref(conn); return BT_STATUS_SUCCESS; #else -- Gitee From 85ccc445a373dc06baead5578e98e87e85bb4afa Mon Sep 17 00:00:00 2001 From: chengkai Date: Fri, 28 Mar 2025 11:54:12 +0800 Subject: [PATCH 058/498] bluetooth: add bt memory debug tool. bug: v/59567 rootcause: add bt memory debug, support memory out-of-bounds check, peak memory printing, and memory leak check --- CMakeLists.txt | 4 + Kconfig | 8 ++ Makefile | 4 + debug/bt_memory.c | 166 ++++++++++++++++++++++++++++++++++ debug/bt_memory_sample.c | 53 +++++++++++ framework/include/bt_memory.h | 48 ++++++++++ 6 files changed, 283 insertions(+) create mode 100644 debug/bt_memory.c create mode 100644 debug/bt_memory_sample.c create mode 100644 framework/include/bt_memory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 41530fe7..8fb10e29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,10 @@ if(CONFIG_BLUETOOTH) list(APPEND INCDIR ${BLUETOOTH_DIR}/service/vhal) endif() + if(CONFIG_BLUETOOTH_DEBUG_MEMORY) + list(APPEND CSRCS ${BLUETOOTH_DIR}/debug/bt_memory.c) + endif() + if(CONFIG_BLUETOOTH_TOOLS) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/utils.c) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/log.c) diff --git a/Kconfig b/Kconfig index 598c20e0..9ab49415 100644 --- a/Kconfig +++ b/Kconfig @@ -47,6 +47,14 @@ config BLUETOOTH_DEBUG_TIME_UNIT_US Enable this option to use microseconds (us) for Bluetooth debug time. If disabled, milliseconds (ms) will be used by default. +config BLUETOOTH_DEBUG_MEMORY + bool "Enable Bluetooth Debug Memory" + default n + help + Enable this option to override standard memory allocation functions + (malloc, calloc, free) with Bluetooth-specific versions (bt_malloc, etc). + Useful for tracking memory usage and debugging in Bluetooth modules. + config BLUETOOTH_BREDR_SUPPORT bool "BREDR support" default y diff --git a/Makefile b/Makefile index 1eddaa08..eaf8cf1b 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,10 @@ else CSRCS += service/common/storage.c endif +ifeq ($(CONFIG_BLUETOOTH_DEBUG_MEMORY),y) +CSRCS += debug/bt_memory.c +endif + ifeq ($(CONFIG_BLUETOOTH_SERVICE), y) CSRCS += service/common/bt_time.c CSRCS += service/common/service_loop.c diff --git a/debug/bt_memory.c b/debug/bt_memory.c new file mode 100644 index 00000000..7d716e19 --- /dev/null +++ b/debug/bt_memory.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_memory.h" + +#include +#include +#include +#include +#include +#include +#include + +#define GUARD_SIZE 16 +#define GUARD_HEAD 0xAA +#define GUARD_TAIL 0xBB + +typedef struct bt_mem_node { + void* addr; + size_t size; + const char* file; + int line; + unsigned char guard[GUARD_SIZE]; + struct bt_mem_node* next; +} bt_mem_node_t; + +typedef struct bt_mem_manager { + bt_mem_node_t* mem_list; + pthread_mutex_t list_lock; + size_t current_mem; + size_t peak_mem; +} bt_mem_manager_t; + +static bt_mem_manager_t g_mem_manager = { + .mem_list = NULL, + .list_lock = PTHREAD_MUTEX_INITIALIZER, + .current_mem = 0, + .peak_mem = 0, +}; + +void* bt_malloc_hook(size_t size, const char* file, int line) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + void* raw; + bt_mem_node_t* head; + + raw = malloc(size + sizeof(bt_mem_node_t) + GUARD_SIZE); + if (!raw) { + syslog(LOG_ALERT, "[bt_memory] malloc failed, size: %zu, file: %s, line: %d", size, file, line); + return NULL; + } + + head = (bt_mem_node_t*)raw; + *head = (bt_mem_node_t) { + .addr = (char*)raw + sizeof(bt_mem_node_t), + .size = size, + .file = file, + .line = line, + .next = NULL + }; + + memset(head->guard, GUARD_HEAD, sizeof(head->guard)); + memset((char*)head->addr + size, GUARD_TAIL, GUARD_SIZE); + + pthread_mutex_lock(&mem_manager->list_lock); + mem_manager->current_mem += size; + if (mem_manager->current_mem > mem_manager->peak_mem) { + mem_manager->peak_mem = mem_manager->current_mem; + } + + head->next = mem_manager->mem_list; + mem_manager->mem_list = head; + pthread_mutex_unlock(&mem_manager->list_lock); + + return head->addr; +} + +void* bt_calloc_hook(size_t num, size_t size, const char* file, int line) +{ + size_t total; + void* ptr; + + total = num * size; + ptr = bt_malloc_hook(total, file, line); + if (!ptr) { + syslog(LOG_ALERT, "[bt_memory] calloc failed, num: %zu, size: %zu, file: %s, line: %d", num, size, file, line); + return NULL; + } + + memset(ptr, 0, total); + return ptr; +} + +void bt_free_hook(void* ptr) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + bt_mem_node_t** pp; + bool found = false; + + if (!ptr) { + return; + } + + pthread_mutex_lock(&mem_manager->list_lock); + + pp = &mem_manager->mem_list; + while (*pp) { + if ((*pp)->addr == ptr) { + bt_mem_node_t* node = *pp; + + for (int i = 0; i < GUARD_SIZE; i++) { + if (((uint8_t)node->guard[i] != GUARD_HEAD) || ((uint8_t)((char*)ptr + node->size)[i] != GUARD_TAIL)) { + syslog(LOG_ALERT, "[bt_memory] Buffer overflow at %s:%d", node->file, node->line); + assert(0); + } + } + + mem_manager->current_mem -= node->size; + *pp = node->next; + found = true; + free(node); + break; + } + pp = &(*pp)->next; + } + + pthread_mutex_unlock(&mem_manager->list_lock); + + if (!found) { + syslog(LOG_ALERT, "[bt_memory] Freeing unallocated memory %p", ptr); + assert(0); + } +} + +void bt_report_leak(void) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + bt_mem_node_t* node; + + pthread_mutex_lock(&mem_manager->list_lock); + + syslog(LOG_ALERT, "[bt_memory] ===== Memory Leak Report ====="); + syslog(LOG_ALERT, "[bt_memory] Peak memory: %zu bytes", mem_manager->peak_mem); + + node = mem_manager->mem_list; + while (node) { + syslog(LOG_ALERT, "[bt_memory] Leak %p (%zu bytes) at %s:%d", + node->addr, node->size, node->file, node->line); + node = node->next; + } + syslog(LOG_ALERT, "[bt_memory] ===== End of Report ====="); + + pthread_mutex_unlock(&mem_manager->list_lock); +} diff --git a/debug/bt_memory_sample.c b/debug/bt_memory_sample.c new file mode 100644 index 00000000..29d359d6 --- /dev/null +++ b/debug/bt_memory_sample.c @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "bt_memory.h" + +void test_leak() +{ + void* p1 = bt_malloc(128); + void* p2 = bt_malloc(256); +} + +void test_overflow() +{ + char* buf = (char*)bt_malloc(16); + memset(buf, 0, 20); + bt_free(buf); +} + +void test_double_free() +{ + void* p = bt_malloc(64); + bt_free(p); + bt_free(p); +} + +int main() +{ + test_leak(); + + test_overflow(); + + test_double_free(); + + void* p2 = bt_malloc(500); + + bt_report_leak(); + return 0; +} \ No newline at end of file diff --git a/framework/include/bt_memory.h b/framework/include/bt_memory.h new file mode 100644 index 00000000..c6d38442 --- /dev/null +++ b/framework/include/bt_memory.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_MEMORY_H_ +#define _BT_MEMORY_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONFIG_BLUETOOTH_DEBUG_MEMORY) +void* bt_malloc_hook(size_t size, const char* file, int line); +void* bt_calloc_hook(size_t n, size_t size, const char* file, int line); +void bt_free_hook(void* ptr); +void bt_report_leak(void); + +#define bt_malloc(size) bt_malloc_hook(size, __FILE__, __LINE__) +#define bt_calloc(n, size) bt_calloc_hook(n, size, __FILE__, __LINE__) +#define bt_free(ptr) bt_free_hook(ptr) +#define bt_zalloc(size) bt_calloc(1, size) +#else +#define bt_malloc(size) malloc(size) +#define bt_calloc(n, size) calloc(n, size) +#define bt_free(ptr) free(ptr) +#define bt_zalloc(size) zalloc(size) +#endif // CONFIG_BLUETOOTH_DEBUG_MEMORY + +#ifdef __cplusplus +} +#endif + +#endif // _BT_MEMORY_H_ \ No newline at end of file -- Gitee From f78006da6425d3325a2447c7dce10838d0413d39 Mon Sep 17 00:00:00 2001 From: chengkai Date: Mon, 28 Apr 2025 19:27:18 +0800 Subject: [PATCH 059/498] bluetooth: add spp ping test tool bug: v/59588 Signed-off-by: chengkai --- tools/spp.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 160 insertions(+), 16 deletions(-) diff --git a/tools/spp.c b/tools/spp.c index 0f54a6bf..631e5a54 100644 --- a/tools/spp.c +++ b/tools/spp.c @@ -19,6 +19,7 @@ #include "bt_config.h" #include "bt_list.h" #include "bt_spp.h" +#include "bt_time.h" #include "bt_tools.h" #include "bt_uuid.h" #include "euv_pipe.h" @@ -40,6 +41,12 @@ typedef struct { uint16_t len; uint32_t state; char* name; + + /* spp ping test */ + int size; + int count; + int delay; + int timeout; } spp_cmd_t; typedef struct { @@ -50,6 +57,7 @@ typedef struct { TRANS_WRITING, TRANS_SENDING, TRANS_RECVING, + TRANS_ECHO, } state; uint8_t* bulk_buf; int32_t bulk_count; @@ -58,6 +66,7 @@ typedef struct { uint32_t received_size; uint64_t start_timestamp; uint64_t end_timestamp; + int seq; } transmit_context_t; static int start_server_cmd(void* handle, int argc, char* argv[]); @@ -66,6 +75,7 @@ static int connect_cmd(void* handle, int argc, char* argv[]); static int disconnect_cmd(void* handle, int argc, char* argv[]); static int write_cmd(void* handle, int argc, char* argv[]); static int speed_test_cmd(void* handle, int argc, char* argv[]); +static int ping_test_cmd(void* handle, int argc, char* argv[]); static int dump_cmd(void* handle, int argc, char* argv[]); static const char* TRANS_START = "START:"; @@ -78,6 +88,15 @@ static void* spp_app_handle = NULL; static uv_loop_t spp_thread_loop = { 0 }; static transmit_context_t trans_ctx = { 0 }; +static struct option spp_ping_options[] = { + { "port", required_argument, 0, 'p' }, + { "size", required_argument, 0, 's' }, + { "count", required_argument, 0, 'c' }, + { "timeout", required_argument, 0, 't' }, + { "delay", required_argument, 0, 'd' }, + { 0, 0, 0, 0 } +}; + static bt_command_t g_spp_tables[] = { { "start", start_server_cmd, 0, "\"start spp server param: (range in [1,28]) \"" }, { "stop", stop_server_cmd, 0, "\"stop spp server param: (range in [1,28])\"" }, @@ -85,6 +104,7 @@ static bt_command_t g_spp_tables[] = { { "disconnect", disconnect_cmd, 0, "\"disconnect peer device param:
\"" }, { "write", write_cmd, 0, "\"write data to peer param: \"" }, { "speed", speed_test_cmd, 0, "\"performance test param: \" note:iteration * 990 shoule less than free memory" }, + { "ping", ping_test_cmd, 0, "\"ping test param: [-p port] [-s size] [-c count] [-t timeout] [-d delay ms]" }, { "dump", dump_cmd, 0, "\"dump spp current state\"" }, }; @@ -191,6 +211,66 @@ static void speed_test_start(void* cmd) PRINT("transmit start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size); } +static void ping_test_start(void* cmd) +{ + spp_cmd_t* msg = cmd; + spp_device_t* device; + transmit_context_t* ctx = &trans_ctx; + uint16_t port = msg->port; + uint16_t counts = msg->count; + int size = msg->size; + int timeout = msg->timeout; + int delay = msg->delay; + + device = find_device_by_port(port); + if (!device) { + PRINT("Device not found for port:%d", port); + return; + } + + ctx->handle = device->pipe; + ctx->state = TRANS_ECHO; + ctx->bulk_length = size; + ctx->bulk_buf = malloc(size); + if (!ctx->bulk_buf) { + PRINT("malloc bulk_buf failed"); + return; + } + + BT_LOGD("spp ping start, counts:%d, size:%d, timeout:%d, delay:%d", counts, size, timeout, delay); + for (size_t seq = 1; seq <= counts; seq++) { + struct timespec ts; + char header[20]; + + snprintf(header, sizeof(header), "ECHO:%d", seq); + + if (ctx->bulk_length < strlen(header)) { + PRINT("bulk_length is too small"); + goto end; + } + + memcpy(ctx->bulk_buf, header, strlen(header)); + memset(ctx->bulk_buf + strlen(header), 0xA5, ctx->bulk_length - strlen(header)); + ctx->seq = seq; + ctx->start_timestamp = get_timestamp_msec(); + + euv_pipe_write(device->pipe, ctx->bulk_buf, ctx->bulk_length, NULL); + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout; + if (sem_timedwait(&spp_send_sem, &ts) < 0) { + PRINT("spp ping test timeout"); + spp_trans_reset(); + goto end; + } + + usleep(delay * 1000); + } + +end: + free(ctx->bulk_buf); +} + static void spp_data_received(euv_pipe_t* handle, const uint8_t* buf, ssize_t size) { transmit_context_t* ctx = &trans_ctx; @@ -235,6 +315,18 @@ static void spp_data_received(euv_pipe_t* handle, const uint8_t* buf, ssize_t si spp_trans_reset(); } break; + case TRANS_ECHO: + if (strncmp((const char*)buf, "ECHO", strlen("ECHO")) == 0) { + uint64_t start_timestamp; + + ctx->handle = handle; + start_timestamp = get_timestamp_msec(); + + PRINT("%d bytes from port(%d): seq=%d time=%" PRIu64 " ms", strlen((const char*)buf), ctx->port, ctx->seq, (start_timestamp - ctx->start_timestamp)); + lib_dumpbuffer("spp recv:", buf, size); + sem_post(&spp_send_sem); + } + break; default: break; } @@ -378,9 +470,9 @@ static int start_server_cmd(void* handle, int argc, char* argv[]) if (argc < 1) return CMD_PARAM_NOT_ENOUGH; - uint16_t scn = atoi(argv[0]); + uint16_t scn = atoi(argv[1]); if (argc == 2) - uuid = strtol(argv[1], NULL, 16); + uuid = strtol(argv[2], NULL, 16); else uuid = BT_UUID_SERVCLASS_SERIAL_PORT; @@ -400,7 +492,7 @@ static int stop_server_cmd(void* handle, int argc, char* argv[]) if (argc < 1) return CMD_PARAM_NOT_ENOUGH; - uint16_t scn = atoi(argv[0]); + uint16_t scn = atoi(argv[1]); bt_spp_server_stop(handle, spp_app_handle, scn); return CMD_OK; @@ -417,13 +509,13 @@ static int connect_cmd(void* handle, int argc, char* argv[]) if (argc < 2) return CMD_PARAM_NOT_ENOUGH; - if (bt_addr_str2ba(argv[0], &addr) < 0) + if (bt_addr_str2ba(argv[1], &addr) < 0) return CMD_INVALID_ADDR; - scn = atoi(argv[1]); + scn = atoi(argv[2]); if (argc == 3) - uuid = strtol(argv[2], NULL, 16); + uuid = strtol(argv[3], NULL, 16); else uuid = BT_UUID_SERVCLASS_SERIAL_PORT; @@ -433,7 +525,7 @@ static int connect_cmd(void* handle, int argc, char* argv[]) return CMD_ERROR; } - PRINT("%s, address:%s scn:%d, port:%d, uuid:0x%04x", __func__, argv[0], scn, port, uuid); + PRINT("%s, address:%s scn:%d, port:%d, uuid:0x%04x", __func__, argv[1], scn, port, uuid); return CMD_OK; } @@ -465,10 +557,10 @@ static int disconnect_cmd(void* handle, int argc, char* argv[]) return CMD_ERROR; msg->handle = handle; - msg->port = atoi(argv[1]); - bt_addr_str2ba(argv[0], &msg->addr); + msg->port = atoi(argv[2]); + bt_addr_str2ba(argv[1], &msg->addr); - PRINT("%s, address:%s port:%d", __func__, argv[0], msg->port); + PRINT("%s, address:%s port:%d", __func__, argv[1], msg->port); do_in_thread_loop(&spp_thread_loop, spp_disconnect, msg); return CMD_OK; @@ -510,8 +602,8 @@ static int write_cmd(void* handle, int argc, char* argv[]) if (argc < 2) return CMD_PARAM_NOT_ENOUGH; - port = atoi(argv[0]); - buf = (uint8_t*)strdup(argv[1]); + port = atoi(argv[1]); + buf = (uint8_t*)strdup(argv[2]); spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); if (!msg) { @@ -521,7 +613,7 @@ static int write_cmd(void* handle, int argc, char* argv[]) msg->port = port; msg->buf = buf; - msg->len = strlen(argv[1]); + msg->len = strlen(argv[2]); do_in_thread_loop(&spp_thread_loop, spp_write, msg); return CMD_OK; @@ -534,8 +626,8 @@ static int speed_test_cmd(void* handle, int argc, char* argv[]) if (argc < 2) return CMD_PARAM_NOT_ENOUGH; - port = atoi(argv[0]); - times = atoi(argv[1]); + port = atoi(argv[1]); + times = atoi(argv[2]); if (port < 0 || times < 0) return CMD_INVALID_PARAM; @@ -559,6 +651,58 @@ static int speed_test_cmd(void* handle, int argc, char* argv[]) return CMD_OK; } +static int ping_test_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + int delay = 200; // dealy 200 ms + int count = 1; + int timeout = 1; + int size = 50; + int port = 0; + + optind = 0; + while ((opt = getopt_long(argc, argv, "+d:c:t:s:p:", spp_ping_options, + NULL)) + != -1) { + switch (opt) { + case 'd': + delay = atoi(optarg); + break; + case 'c': + count = atoi(optarg); + break; + case 't': + timeout = atoi(optarg); + break; + case 's': + size = atoi(optarg); + break; + case 'p': + port = atoi(optarg); + break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + spp_cmd_t* msg = zalloc(sizeof(spp_cmd_t)); + if (!msg) + return CMD_ERROR; + + msg->delay = delay; + msg->count = count; + msg->timeout = timeout; + msg->size = size; + msg->port = port; + BT_LOGD("delay:%d, count:%d, timeout:%d, size:%d, port:%d", delay, count, timeout, size, port); + + ping_test_start(msg); + free(msg); + + return CMD_OK; +} + static int dump_cmd(void* handle, int argc, char* argv[]) { return CMD_OK; @@ -593,7 +737,7 @@ int spp_command_exec(void* handle, int argc, char* argv[]) int ret = CMD_USAGE_FAULT; if (argc > 0) - ret = execute_command_in_table(handle, g_spp_tables, ARRAY_SIZE(g_spp_tables), argc, argv); + ret = execute_command_in_table_offset(handle, g_spp_tables, ARRAY_SIZE(g_spp_tables), argc - 1, argv, 0); if (ret < 0) usage(); -- Gitee From 19b7522f4200d6e2c1f963dd1729135b35344df8 Mon Sep 17 00:00:00 2001 From: chengkai Date: Mon, 28 Apr 2025 22:54:48 +0800 Subject: [PATCH 060/498] bluetooth: add bt trace tool bug: v/59594 Signed-off-by: chengkai --- CMakeLists.txt | 4 + Kconfig | 13 +++ Makefile | 4 + debug/bt_trace.c | 111 ++++++++++++++++++++++++++ debug/bt_trace_analyzer.py | 124 +++++++++++++++++++++++++++++ debug/bt_trace_sample.c | 54 +++++++++++++ debug/latency_by_tag.png | Bin 0 -> 225123 bytes framework/include/bt_sched_trace.h | 45 +++++++++++ 8 files changed, 355 insertions(+) create mode 100644 debug/bt_trace.c create mode 100644 debug/bt_trace_analyzer.py create mode 100644 debug/bt_trace_sample.c create mode 100644 debug/latency_by_tag.png create mode 100644 framework/include/bt_sched_trace.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb10e29..a574dead 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,10 @@ if(CONFIG_BLUETOOTH) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/common/storage.c) endif() + if(CONFIG_BLUETOOTH_DEBUG_TRACE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/debug/bt_trace.c) + endif() + if(CONFIG_BLUETOOTH_SERVICE) list( APPEND diff --git a/Kconfig b/Kconfig index 9ab49415..4845c857 100644 --- a/Kconfig +++ b/Kconfig @@ -55,6 +55,19 @@ config BLUETOOTH_DEBUG_MEMORY (malloc, calloc, free) with Bluetooth-specific versions (bt_malloc, etc). Useful for tracking memory usage and debugging in Bluetooth modules. +config BLUETOOTH_DEBUG_TRACE + bool "Enable Bluetooth Debug Trace" + default n + help + Enable bluetooth debug trace tools. + +if BLUETOOTH_DEBUG_TRACE +config BLUETOOTH_TRACE_BUFFER_SIZE + int "Bluetooth Trace Buffer Size" + default 512 + +endif #BLUETOOTH_DEBUG_TRACE + config BLUETOOTH_BREDR_SUPPORT bool "BREDR support" default y diff --git a/Makefile b/Makefile index eaf8cf1b..ded566fa 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,10 @@ ifeq ($(CONFIG_BLUETOOTH_DEBUG_MEMORY),y) CSRCS += debug/bt_memory.c endif +ifeq ($(CONFIG_BLUETOOTH_DEBUG_TRACE), y) +CSRCS += service/debug/bt_trace.c +endif + ifeq ($(CONFIG_BLUETOOTH_SERVICE), y) CSRCS += service/common/bt_time.c CSRCS += service/common/service_loop.c diff --git a/debug/bt_trace.c b/debug/bt_trace.c new file mode 100644 index 00000000..398688e8 --- /dev/null +++ b/debug/bt_trace.c @@ -0,0 +1,111 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_sched_trace.h" + +#include +#include +#include +#include +#include + +#include "utils/log.h" + +#define DUMP_THRESHOLD 256 + +typedef struct { + char tag[MAX_TAG_LEN]; + uint64_t timestamp; + uint32_t latency_us; +} bt_latency_record_t; + +typedef struct { + bt_latency_record_t buffer[CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE]; + atomic_uint head; + atomic_uint tail; +} bt_trace_manager_t; + +static bt_trace_manager_t g_trace_manager = { 0 }; + +static inline uint64_t get_monotonic_ns() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000UL + ts.tv_nsec; +} + +void bt_note_start(void) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + + atomic_store(&trace_manager->head, 0); + atomic_store(&trace_manager->tail, 0); +} + +void bt_note_stop(void) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + char log_buf[DUMP_THRESHOLD]; + int written = 0; + + while (trace_manager->tail != trace_manager->head) { + bt_latency_record_t* p = &trace_manager->buffer[trace_manager->tail % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE]; + + written += snprintf(log_buf + written, sizeof(log_buf) - written, + "[TAG=%s][TS=%lu][LAT=%uus]\n", + p->tag, p->timestamp, p->latency_us); + trace_manager->tail = (trace_manager->tail + 1) % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE; + written = written % DUMP_THRESHOLD; + + if (written > DUMP_THRESHOLD) { + BT_LOGD("%s", log_buf); + written = 0; + } + } + + if (written > 0) { + BT_LOGD("%s", log_buf); + } +} + +void bt_note_begin(const char* tag, bt_timepoint_t* point) +{ + if (strlen(tag) > MAX_TAG_LEN) { + BT_LOGD("tag is too long, max length is %d\n", MAX_TAG_LEN); + return; + } + + strlcpy(point->tag, tag, MAX_TAG_LEN); + point->start_ns = get_monotonic_ns(); +} + +void bt_note_end(const char* tag, bt_timepoint_t* point) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + uint64_t end; + uint32_t idx; + + if (strlen(tag) > MAX_TAG_LEN) { + BT_LOGD("tag is too long, max length is %d\n", MAX_TAG_LEN); + return; + } + + end = get_monotonic_ns(); + idx = atomic_fetch_add(&trace_manager->head, 1) % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE; + strlcpy(trace_manager->buffer[idx].tag, point->tag, MAX_TAG_LEN); + + trace_manager->buffer[idx].timestamp = end; + trace_manager->buffer[idx].latency_us = (end - point->start_ns) / 1000; +} \ No newline at end of file diff --git a/debug/bt_trace_analyzer.py b/debug/bt_trace_analyzer.py new file mode 100644 index 00000000..bfc15e3b --- /dev/null +++ b/debug/bt_trace_analyzer.py @@ -0,0 +1,124 @@ +############################################################################ +# Copyright (C) 2025 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import re +import argparse +import pandas as pd +import matplotlib.pyplot as plt +from datetime import datetime +from pathlib import Path + +def parse_args(): + parser = argparse.ArgumentParser(description='Latency log analyzer with tag support') + parser.add_argument('--log', required=True, + help='Input log file path') + parser.add_argument('--output-img', default='latency_by_tag.png', + help='Output image path (default: latency_by_tag.png)') + parser.add_argument('--output-report', + help='Output report file path (optional)') + return parser.parse_args() + +def parse_log_line(line): + pattern = r"\[TAG=([^$$]+)\]\[TS=(\d+)\]\[LAT=(\d+)us\]" + + if match := re.search(pattern, line): + return { + "tag": match.group(1), + "timestamp": datetime.fromtimestamp(int(match.group(2))/1e9), + "latency": int(match.group(3)) + } + return None + +def load_log_data(log_path): + try: + with open(log_path, 'r') as f: + records = [] + for line in f: + if record := parse_log_line(line.strip()): + records.append(record) + return pd.DataFrame(records) + except FileNotFoundError: + raise SystemExit(f"Error: Log file {log_path} not found") + + +def generate_latency_plot(df, output_path): + plt.figure(figsize=(15, 8)) + colors = plt.cm.tab10.colors + + for idx, (tag, group) in enumerate(df.groupby('tag')): + group = group.sort_values('timestamp') + plt.plot(group['timestamp'], group['latency'], + color=colors[idx % 10], + marker='o', markersize=3, + linestyle='-', linewidth=1, + label=tag) + + plt.title('Latency Timeline by Tag') + plt.xlabel('Timestamp') + plt.ylabel('Latency (μs)') + plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + plt.grid(True, alpha=0.3) + plt.xticks(rotation=45) + plt.tight_layout() + + Path(output_path).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def generate_stat_report(df, output_path=None): + stats = [] + for tag, group in df.groupby('tag'): + desc = group['latency'].describe(percentiles=[.5, .95, .99]) + stats.append({ + 'Tag': tag, + 'Count': desc['count'], + 'Mean': desc['mean'], + 'Min': desc['min'], + '50%': desc['50%'], + '95%': desc['95%'], + '99%': desc['max'], + 'Max': desc['max'] + }) + + report_df = pd.DataFrame(stats) + report_str = report_df.to_string(index=False, float_format='%.2f') + + if output_path: + Path(output_path).parent.mkdir(parents=True, exist_ok=True) + with open(output_path, 'w') as f: + f.write("=== Latency Statistics Report ===\n") + f.write(report_str) + print(f"Report saved to {output_path}") + else: + print("\n" + report_str) + +def main(): + args = parse_args() + + df = load_log_data(args.log) + if df.empty: + raise SystemExit("Error: No valid records found in log file") + + generate_latency_plot(df, args.output_img) + print(f"Visualization saved to {args.output_img}") + + if args.output_report: + generate_stat_report(df, args.output_report) + else: + generate_stat_report(df) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/debug/bt_trace_sample.c b/debug/bt_trace_sample.c new file mode 100644 index 00000000..d5f2dd1b --- /dev/null +++ b/debug/bt_trace_sample.c @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_sched_trace.h" + +#include + +void test_func1() +{ + bt_timepoint_t tp1; + + bt_trace_begin("test_func1", &tp1); + usleep(rand() % 1000); + bt_trace_end("test_func1", &tp1); +} + +void test_func2() +{ + bt_timepoint_t tp1; + + bt_trace_begin("test_func2", &tp1); + usleep(rand() % 1000); + bt_trace_end("test_func2", &tp1); +} + +int main(int argc, char* argv[]) +{ + + bt_trace_start(); + + test_func1(); + test_func2(); + + test_func1(); + test_func2(); + + test_func1(); + test_func2(); + + bt_trace_stop(); + return 0; +} \ No newline at end of file diff --git a/debug/latency_by_tag.png b/debug/latency_by_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..a0eb147da1bb08fd4348fba3020566b558d3f2ac GIT binary patch literal 225123 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 literal 0 HcmV?d00001 diff --git a/framework/include/bt_sched_trace.h b/framework/include/bt_sched_trace.h new file mode 100644 index 00000000..59068434 --- /dev/null +++ b/framework/include/bt_sched_trace.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include + +#ifndef _BT_SCHED_TRACE_H_ +#define _BT_SCHED_TRACE_H_ + +#define MAX_TAG_LEN 16 + +typedef struct { + uint64_t start_ns; + char tag[MAX_TAG_LEN]; +} bt_timepoint_t; + +#ifdef CONFIG_BLUETOOTH_DEBUG_TRACE +void bt_note_start(void); +void bt_note_stop(void); +void bt_note_begin(const char* tag, bt_timepoint_t* point); +void bt_note_end(const char* tag, bt_timepoint_t* point); + +#define bt_trace_start() bt_note_start() +#define bt_trace_stop() bt_note_stop() +#define bt_trace_begin(tag, point) bt_note_begin(tag, point) +#define bt_trace_end(tag, point) bt_note_end(tag, point) +#else +#define bt_trace_start() +#define bt_trace_stop() +#define bt_trace_begin(tag, point) +#define bt_trace_end(tag, point) +#endif + +#endif // _BT_SCHED_TRACE_H_ \ No newline at end of file -- Gitee From 9e86f04eccf1e1bbf2329d41d5d83e202b07cdf4 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Tue, 22 Apr 2025 13:54:41 +0800 Subject: [PATCH 061/498] bluetooth: add active connect sample code bug: v/58892 Signed-off-by: jialu --- sample_code/createbond/app_bt_gap.c | 66 ++++ sample_code/createbond/app_bt_message_gap.h | 88 +++++ sample_code/createbond/createbond.c | 405 ++++++++++++++++++++ sample_code/createbond/createbond.h | 75 ++++ 4 files changed, 634 insertions(+) create mode 100644 sample_code/createbond/app_bt_gap.c create mode 100644 sample_code/createbond/app_bt_message_gap.h create mode 100644 sample_code/createbond/createbond.c create mode 100644 sample_code/createbond/createbond.h diff --git a/sample_code/createbond/app_bt_gap.c b/sample_code/createbond/app_bt_gap.c new file mode 100644 index 00000000..96663c1b --- /dev/null +++ b/sample_code/createbond/app_bt_gap.c @@ -0,0 +1,66 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "createbond.h" + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_SET_SCANMODE: + bt_adapter_set_scan_mode(g_bt_ins, msg->gap_req._bt_adapter_set_scan_mode.mode, + msg->gap_req._bt_adapter_set_scan_mode.bondable); + break; + case APP_BT_GAP_SET_IO_CAPABILITY: + bt_adapter_set_io_capability(g_bt_ins, msg->gap_req._bt_adapter_set_io_capability.cap); + break; + case APP_BT_GAP_START_DISCOVERY: + bt_adapter_start_discovery(g_bt_ins, msg->gap_req._bt_adapter_start_discovery.timeout); + break; + case APP_BT_GAP_CREATE_BOND: + bt_status_t ret = bt_device_create_bond(g_bt_ins, &msg->gap_req._bt_device_create_bond.addr, + msg->gap_req._bt_device_create_bond.transport); + if (ret != BT_STATUS_SUCCESS) { + LOGE("create bond failed, after removing the bound device, try again\n"); + } + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + break; + case APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED: + if (msg->gap_cb._on_discovery_state_changed.state == BT_DISCOVERY_STATE_STARTED) { + LOGI("Discovery started"); + break; + } + LOGI("Discovery stopped"); + break; + case APP_BT_GAP_ON_CONNECTION_STATE_CHANGED: + LOGI("Connection state changed: %d", msg->gap_cb._on_connection_state_changed.state); + break; + case APP_BT_GAP_ON_BOND_STATE_CHANGED: + LOGI("Bond state changed: %d", msg->gap_cb._on_bond_state_changed.state); + if (msg->gap_cb._on_bond_state_changed.state == BOND_STATE_BONDED) { + // The sample code is used to test active connection/pairing/binding, + // so after device is bonded, reset the running flag. + bt_adapter_disable(g_bt_ins); + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/createbond/app_bt_message_gap.h b/sample_code/createbond/app_bt_message_gap.h new file mode 100644 index 00000000..d52c2310 --- /dev/null +++ b/sample_code/createbond/app_bt_message_gap.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_START_DISCOVERY, + APP_BT_GAP_CREATE_BOND, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_DISCOVERY_RESULT, + APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED, + APP_BT_GAP_ON_CONNECTION_STATE_CHANGED, + APP_BT_GAP_ON_BOND_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + uint8_t mode; /* bt_scan_mode_t */ + uint8_t bondable; /* boolean */ + } _bt_adapter_set_scan_mode; + + struct { + uint8_t cap; /* bt_io_capability_t */ + } _bt_adapter_set_io_capability; + + struct { + uint32_t timeout; + } _bt_adapter_start_discovery; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + } _bt_device_create_bond; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* bond_state_t */ + uint8_t is_ctkd; /* boolean */ + } _on_bond_state_changed; + + struct { + uint8_t state; /* bt_discovery_state_t */ + } _on_discovery_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/createbond/createbond.c b/sample_code/createbond/createbond.c new file mode 100644 index 00000000..ecd64343 --- /dev/null +++ b/sample_code/createbond/createbond.c @@ -0,0 +1,405 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "createbond.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief peer device address. + * + * @note The address is in reverse order. If the address of the peer device + * is 11:22:33:44:55:66, it should be written as 66:55:44:33:22:11 here. + */ +static const bt_address_t test_remote_addr = { + { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 } +}; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Set the scanning mode to make the device locally connectable and discoverable. + */ +static void app_bt_set_scan_mode(bt_scan_mode_t mode, bool bondable) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_SCANMODE; + node->data.gap_req._bt_adapter_set_scan_mode.mode = mode; + node->data.gap_req._bt_adapter_set_scan_mode.bondable = bondable; + app_list_add_tail(&node->node); +} + +/** + * @brief Set io capability to NOINPUTNOOUTPUT. + */ +static void app_bt_set_io_capability(bt_io_capability_t capability) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_IO_CAPABILITY; + node->data.gap_req._bt_adapter_set_io_capability.cap = capability; + app_list_add_tail(&node->node); +} + +void bt_gap_init(void) +{ + app_bt_set_io_capability(BT_IO_CAPABILITY_NOINPUTNOOUTPUT); + + app_bt_set_scan_mode(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true); +} + +/** + * @brief Discover nearby Bluetooth devices. + */ +void app_bt_discovery(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_START_DISCOVERY; + node->data.gap_req._bt_adapter_start_discovery.timeout = 2; + app_list_add_tail(&node->node); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) + return; + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + bt_gap_init(); + app_bt_discovery(); + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +/** + * @brief Connection state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_CONNECTION_STATE_CHANGED; + node->data.gap_cb._on_connection_state_changed.state = state; + node->data.gap_cb._on_connection_state_changed.transport = transport; + memcpy(&node->data.gap_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Bond state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_bond_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_BOND_STATE_CHANGED; + node->data.gap_cb._on_bond_state_changed.state = state; + node->data.gap_cb._on_bond_state_changed.transport = transport; + node->data.gap_cb._on_bond_state_changed.is_ctkd = is_ctkd; + memcpy(&node->data.gap_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Discovery result callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_result_callback(void* cookie, bt_discovery_result_t* remote) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&remote->addr, addr_str); + + LOGI("Discovery result: device [%s], name: %s, code: %08x, is HEADSET: %s, rssi: %d\n", + addr_str, remote->name, remote->cod, + IS_HEADSET(remote->cod) ? "true" : "false", + remote->rssi); +} + +static void app_create_bond(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_CREATE_BOND; + node->data.gap_req._bt_device_create_bond.transport = BT_TRANSPORT_BREDR; + memcpy(&node->data.gap_req._bt_device_create_bond.addr, &test_remote_addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Discovery state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_state_changed_callback(void* cookie, bt_discovery_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED; + node->data.gap_cb._on_discovery_state_changed.state = state; + + app_list_add_tail(&node->node); + + if (state == BT_DISCOVERY_STATE_STOPPED) + app_create_bond(); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_discovery_result = gap_discovery_result_callback, + .on_discovery_state_changed = gap_discovery_state_changed_callback, + .on_connection_state_changed = gap_connection_state_changed_callback, + .on_bond_state_changed = gap_bond_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + app_bt_gap_handle_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/createbond/createbond.h b/sample_code/createbond/createbond.h new file mode 100644 index 00000000..be446258 --- /dev/null +++ b/sample_code/createbond/createbond.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file -- Gitee From 345d20932e7a142c49e02df566e0ca704094f6f9 Mon Sep 17 00:00:00 2001 From: openvela-robot Date: Tue, 6 May 2025 16:37:05 +0800 Subject: [PATCH 062/498] workflows add stale.yml --- .github/workflows/stale.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..8f8fc3cb --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,11 @@ +name: 'Close stale issues and PR' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: # 允许手动触发 + +jobs: + stale: + uses: open-vela/public-actions/.github/workflows/stale.yml@trunk + secrets: inherit + -- Gitee From 34f8822ad0a78a6122dec1dd09c7c22d2fd9e43f Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Mon, 3 Mar 2025 17:39:54 +0800 Subject: [PATCH 063/498] bluetooth: A2DP zblue 4.0 SAL bug: v/55032 zephyr4.0 A2DP adapter Signed-off-by: Lu Jia --- Makefile | 1 + service/stacks/zephyr/sal_a2dp_interface.c | 1637 +++++++++++++++---- service/stacks/zephyr/sal_avrcp_interface.c | 22 +- 3 files changed, 1350 insertions(+), 310 deletions(-) diff --git a/Makefile b/Makefile index ded566fa..2e214ae0 100644 --- a/Makefile +++ b/Makefile @@ -330,6 +330,7 @@ endif ifneq ($(CONFIG_BLUETOOTH_STACK_BREDR_ZBLUE)$(CONFIG_BLUETOOTH_STACK_LE_ZBLUE),) CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/service/stacks/zephyr/include CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/external/zblue/zblue/port/include/ + CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/external/zblue/zblue/subsys/bluetooth/host CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/external/zblue/zblue/port/include/kernel/include endif CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/service/ipc diff --git a/service/stacks/zephyr/sal_a2dp_interface.c b/service/stacks/zephyr/sal_a2dp_interface.c index 32f27e20..6d95e0dd 100644 --- a/service/stacks/zephyr/sal_a2dp_interface.c +++ b/service/stacks/zephyr/sal_a2dp_interface.c @@ -15,40 +15,544 @@ ***************************************************************************/ #define LOG_TAG "sal_a2dp" +#include #include #include #include -#include "bluetooth.h" +#include "a2dp_device.h" #include "bt_addr.h" +#include "bt_list.h" +#include "bt_utils.h" #include "sal_a2dp_sink_interface.h" #include "sal_a2dp_source_interface.h" #include "sal_interface.h" #include "sal_zblue.h" - -#include -#include - -#include "bt_utils.h" #include "utils/log.h" +#include +#include +#include + #ifdef CONFIG_BLUETOOTH_A2DP #include "a2dp_codec.h" -static void zblue_on_connected(struct bt_conn* conn); -static void zblue_on_disconnected(struct bt_conn* conn); -static void zblue_on_media_handler(struct bt_conn* conn, uint8_t* data, uint16_t len); -static int zblue_on_media_state_req(struct bt_conn* conn, uint8_t state); -static void zblue_on_seted_codec(struct bt_conn* conn, struct bt_a2dp_media_codec* codec, uint8_t cp_type); +#define A2DP_PEER_ENDPOINT_MAX 10 -static struct bt_a2dp_app_cb a2dp_cbks = { - .connected = zblue_on_connected, - .disconnected = zblue_on_disconnected, - .media_handler = zblue_on_media_handler, - .media_state_req = zblue_on_media_state_req, - .seted_codec = zblue_on_seted_codec, +#define BT_A2DP_MEDIA_CONNECTED(state) (state | 0xF0) +#define BT_A2DP_SIGNALING_CONNECTED(state) (state | 0x0F) +#define BT_A2DP_MEDIA_DISCONNECTED(state) (state & 0x0F) +#define BT_A2DP_SIGNALING_DISCONNECTED(state) (state & 0xF0) + +#define BT_A2DP_FIND_MEDIA_CONNECTION(state) (state & 0xF0) +#define BT_A2DP_FIND_SIGNALING_CONNECTION(state) (state & 0x0F) + +typedef enum { + A2DP_INT = 0, + A2DP_ACP = 1, +} a2dp_int_acp_t; + +struct zblue_a2dp_info_t { + struct bt_a2dp* a2dp; + struct bt_conn* conn; + struct bt_a2dp_stream stream; + bt_address_t bd_addr; + uint8_t peer_endpoint_count; + struct bt_a2dp_codec_ie peer_sbc_capabilities[A2DP_PEER_ENDPOINT_MAX]; + struct bt_a2dp_ep found_peer_endpoint[A2DP_PEER_ENDPOINT_MAX]; + a2dp_int_acp_t int_acp; + uint8_t role; + bool is_cleanup; // cleanup flag,if true, free bt_a2dp_conn + + /* + * The upper 8 bits represent the status of the media channel, + * and the lower 8 bits represent the status of the signaling channel. + */ + uint8_t state; + bool disconnect; // disconnect flag, Avoid repeatedly disconnecting A2DP during cleanup. + uint8_t codec_type; // The codec type to be set during reconfiguration. }; +static bt_list_t* bt_a2dp_conn = NULL; + +NET_BUF_POOL_DEFINE(bt_a2dp_tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +/* codec information elements for the endpoint */ +static struct bt_a2dp_codec_ie sbc_src_ie = { + .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ + .codec_ie = { + 0x23, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, +}; + +static struct bt_a2dp_ep a2dp_sbc_src_endpoint_local = { + .codec_type = 0x00, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&sbc_src_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 0, /* BT_AVDTP_SOURCE */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie src_sbc_ie_default[] = { + { + .len = 4, + .codec_ie = { + 0x21, + 0x15, + 0x02, + 0x35, + }, + }, + { + .len = 4, + .codec_ie = { + 0x22, + 0x15, + 0x02, + 0x35, + }, + }, +}; + +static struct bt_a2dp_codec_cfg src_sbc_cfg_preferred[] = { + { + .codec_config = &src_sbc_ie_default[0], + }, + { + .codec_config = &src_sbc_ie_default[1], + }, +}; +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +/* codec information elements for the endpoint */ +static struct bt_a2dp_codec_ie sbc_snk_ie = { + .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ + .codec_ie = { + 0x33, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, +}; + +static struct bt_a2dp_ep a2dp_sbc_snk_endpoint_local = { + .codec_type = 0x00, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&sbc_snk_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 1, /* BT_AVDTP_SINK */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie snk_sbc_ie_default[] = { + { + .len = 4, + .codec_ie = { + 0x21, + 0x15, + 0x02, + 0x35, + }, + }, + { + .len = 4, + .codec_ie = { + 0x22, + 0x15, + 0x02, + 0x35, + }, + }, + { + .len = 4, + .codec_ie = { + 0x11, + 0x15, + 0x02, + 0x35, + }, + }, + { + .len = 4, + .codec_ie = { + 0x12, + 0x15, + 0x02, + 0x35, + }, + }, +}; + +static struct bt_a2dp_codec_cfg snk_sbc_cfg_preferred[] = { + { + .codec_config = &snk_sbc_ie_default[0], + }, + { + .codec_config = &snk_sbc_ie_default[1], + }, + { + .codec_config = &snk_sbc_ie_default[2], + }, + { + .codec_config = &snk_sbc_ie_default[3], + } +}; +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static struct bt_a2dp_codec_ie aac_src_ie = { + .len = 6, /* BT_A2DP_MPEG_2_4_IE_LENGTH */ + .codec_ie = { + 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ + 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ + 0x0C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, +}; + +static struct bt_a2dp_ep a2dp_aac_src_endpoint_local = { + .codec_type = 0x02, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&aac_src_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 0, /* BT_AVDTP_SOURCE */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie src_aac_ie_default[] = { + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x08, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x04, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg src_aac_cfg_preferred[] = { + { + .codec_config = &src_aac_ie_default[0], + }, + { + .codec_config = &src_aac_ie_default[1], + }, +}; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static struct bt_a2dp_codec_ie aac_snk_ie = { + .len = 6, /* BT_A2DP_MPEG_2_4_IE_LENGTH */ + .codec_ie = { + 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ + 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ + 0x8C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, +}; + +static struct bt_a2dp_ep a2dp_aac_snk_endpoint_local = { + .codec_type = 0x02, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&aac_snk_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 1, /* BT_AVDTP_SINK */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie snk_aac_ie_default[] = { + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x08, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x04, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x00, 0x18, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x00, 0x14, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg snk_aac_cfg_preferred[] = { + { + .codec_config = &snk_aac_ie_default[0], + }, + { + .codec_config = &snk_aac_ie_default[1], + }, + { + .codec_config = &snk_aac_ie_default[2], + }, + { + .codec_config = &snk_aac_ie_default[3], + } +}; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +#endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ + +#define BT_SDP_RECORD(_attrs) \ + { \ + .attrs = _attrs, \ + .attr_count = ARRAY_SIZE((_attrs)), \ + } + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static struct bt_sdp_attribute a2dp_source_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AUDIO_SOURCE_SVCLASS) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0100U) }, ) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_ADVANCED_AUDIO_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0103U) }, ) }, )), + BT_SDP_SERVICE_NAME("A2DPSink"), + BT_SDP_SUPPORTED_FEATURES(0x0001U), +}; + +static struct bt_sdp_record a2dp_source_rec = BT_SDP_RECORD(a2dp_source_attrs); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static struct bt_sdp_attribute a2dp_sink_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), /* 35 03 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_AUDIO_SINK_SVCLASS) /* 11 0B */ + }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), /* 35 10 */ + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) /* 01 00 */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) /* 00 19 */ + }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) /* 00 19 */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(0x0100U) /* AVDTP version: 01 00 */ + }, ) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), /* 35 08 */ + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_ADVANCED_AUDIO_SVCLASS) /* 11 0d */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(0x0103U) /* 01 03 */ + }, ) }, )), + BT_SDP_SERVICE_NAME("A2DPSink"), + BT_SDP_SUPPORTED_FEATURES(0x0001U), +}; + +static struct bt_sdp_record a2dp_sink_rec = BT_SDP_RECORD(a2dp_sink_attrs); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + +static void a2dp_info_destory(void* data) +{ + free(data); +} + +static bool bt_a2dp_info_find_a2dp(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info) + return false; + + return a2dp_info->a2dp == context; +} + +static bool bt_a2dp_info_find_addr(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info || !context) + return false; + + return memcmp(&a2dp_info->bd_addr, context, sizeof(bt_address_t)) == 0; +} + +static bool bt_a2dp_info_find_conn(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info) + return false; + + return a2dp_info->conn == context; +} + +bt_status_t bt_sal_a2dp_get_role(struct bt_conn* conn, uint8_t* a2dp_role) +{ + if (!bt_a2dp_conn) + return BT_STATUS_PARM_INVALID; + + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + + if (!a2dp_info) + return BT_STATUS_PARM_INVALID; + + if (a2dp_info->role == SEP_INVALID) + return BT_STATUS_PARM_INVALID; + + *a2dp_role = a2dp_info->role; + + return BT_STATUS_SUCCESS; +} + static a2dp_codec_index_t zephyr_codec_2_sal_codec(uint8_t codec) { switch (codec) { @@ -66,359 +570,685 @@ static a2dp_codec_index_t zephyr_codec_2_sal_codec(uint8_t codec) } } -#define CASE_RETURN_SBC_SAMPLE_RATE(sbc_sample_rate) \ - case BT_A2DP_SBC_##sbc_sample_rate: \ - return sbc_sample_rate; - -static uint32_t zephyr_sbc_sample_rate_2_sal_sample_rate(uint8_t sbc_sample_rate) +static a2dp_codec_channel_mode_t zephyr_sbc_channel_mode_2_sal_channel_mode( + struct bt_a2dp_codec_sbc_params* sbc_codec) { - switch (sbc_sample_rate) { - CASE_RETURN_SBC_SAMPLE_RATE(48000) - CASE_RETURN_SBC_SAMPLE_RATE(44100) - CASE_RETURN_SBC_SAMPLE_RATE(32000) - CASE_RETURN_SBC_SAMPLE_RATE(16000) - DEFAULT_BREAK() + if (sbc_codec->config[0] & (A2DP_SBC_CH_MODE_JOINT | A2DP_SBC_CH_MODE_STREO | A2DP_SBC_CH_MODE_DUAL)) { + return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; + } else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_MONO) { + return BTS_A2DP_CODEC_CHANNEL_MODE_MONO; + } else { + BT_LOGW("%s, invalid channel mode", __func__); + return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; } - BT_LOGW("%s, invalid sample rate: 0x%x", __func__, sbc_sample_rate); - return 44100; } -#define CHECK_RETURN_AAC_SAMPLE_RATE(aac_sample_rate) \ - if (aac_sample_rate & BT_A2DP_AAC_##aac_sample_rate) \ - return aac_sample_rate; +static bt_status_t check_local_remote_codec_sbc(uint8_t* local_ie, uint8_t* remote_ie, uint8_t* prefered_ie) +{ + uint8_t bit_map = 0; + uint8_t bit_pool_min = 0; + uint8_t bit_pool_max = 0; + + struct bt_a2dp_codec_sbc_params* local_sbc_codec = (struct bt_a2dp_codec_sbc_params*)local_ie; + struct bt_a2dp_codec_sbc_params* remote_sbc_codec = (struct bt_a2dp_codec_sbc_params*)remote_ie; + struct bt_a2dp_codec_sbc_params* prefered_sbc_codec = (struct bt_a2dp_codec_sbc_params*)prefered_ie; + + bit_map = (BT_A2DP_SBC_SAMP_FREQ(local_sbc_codec) & BT_A2DP_SBC_SAMP_FREQ(remote_sbc_codec) & BT_A2DP_SBC_SAMP_FREQ(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; -static uint32_t zephyr_aac_sample_rate_2_sal_sample_rate(uint16_t aac_sample_rate) + bit_map = (BT_A2DP_SBC_CHAN_MODE(local_sbc_codec) & BT_A2DP_SBC_CHAN_MODE(remote_sbc_codec) & BT_A2DP_SBC_CHAN_MODE(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_BLK_LEN(local_sbc_codec) & BT_A2DP_SBC_BLK_LEN(remote_sbc_codec) & BT_A2DP_SBC_BLK_LEN(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_SUB_BAND(local_sbc_codec) & BT_A2DP_SBC_SUB_BAND(remote_sbc_codec) & BT_A2DP_SBC_SUB_BAND(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_ALLOC_MTHD(local_sbc_codec) & BT_A2DP_SBC_ALLOC_MTHD(remote_sbc_codec) & BT_A2DP_SBC_ALLOC_MTHD(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_pool_min = MAX(local_ie[2], MAX(remote_ie[2], prefered_ie[2])); + bit_pool_max = MIN(local_ie[3], MIN(remote_ie[3], prefered_ie[3])); + + if (bit_pool_min > bit_pool_max) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t check_local_remote_codec_aac(uint8_t* local_ie, uint8_t* remote_ie, uint8_t* prefered_ie) { - CHECK_RETURN_AAC_SAMPLE_RATE(96000) - CHECK_RETURN_AAC_SAMPLE_RATE(88200) - CHECK_RETURN_AAC_SAMPLE_RATE(64000) - CHECK_RETURN_AAC_SAMPLE_RATE(48000) - CHECK_RETURN_AAC_SAMPLE_RATE(44100) - CHECK_RETURN_AAC_SAMPLE_RATE(32000) - CHECK_RETURN_AAC_SAMPLE_RATE(24000) - CHECK_RETURN_AAC_SAMPLE_RATE(22050) - CHECK_RETURN_AAC_SAMPLE_RATE(16000) - CHECK_RETURN_AAC_SAMPLE_RATE(12000) - CHECK_RETURN_AAC_SAMPLE_RATE(11025) - CHECK_RETURN_AAC_SAMPLE_RATE(8000) + return BT_STATUS_FAIL; +} - BT_LOGW("%s, invalid sample rate: 0x%x", __func__, aac_sample_rate); - return 44100; +static int find_remote_codec(struct bt_a2dp_ep* local_ep, struct zblue_a2dp_info_t* a2dp_info, struct bt_a2dp_codec_cfg* config) +{ + int index = 0; + bt_status_t flag; + + for (; index < a2dp_info->peer_endpoint_count; index++) { + if (local_ep->codec_type != a2dp_info->found_peer_endpoint[index].codec_type) + continue; + + if (local_ep->sep.sep_info.inuse != 0) + continue; + + if (a2dp_info->found_peer_endpoint[index].sep.sep_info.inuse != 0) + continue; + + if (local_ep->sep.sep_info.media_type != a2dp_info->found_peer_endpoint[index].sep.sep_info.media_type) + continue; + + if (local_ep->sep.sep_info.tsep == a2dp_info->found_peer_endpoint[index].sep.sep_info.tsep) + continue; + + if (local_ep->codec_cap->len != a2dp_info->found_peer_endpoint[index].codec_cap->len) + continue; + + if (local_ep->codec_type == BT_A2DP_SBC) { + flag = check_local_remote_codec_sbc(local_ep->codec_cap->codec_ie, + a2dp_info->found_peer_endpoint[index].codec_cap->codec_ie, config->codec_config->codec_ie); + if (flag == BT_STATUS_SUCCESS) + return index; + } else if (local_ep->codec_type == BT_A2DP_MPEG2) { + flag = check_local_remote_codec_aac(local_ep->codec_cap->codec_ie, + a2dp_info->found_peer_endpoint[index].codec_cap->codec_ie, config->codec_config->codec_ie); + if (flag == BT_STATUS_SUCCESS) + return index; + } + } + index = -1; + return index; } -static a2dp_codec_channel_mode_t zephyr_sbc_channel_mode_2_sal_channel_mode(uint8_t sbc_channel_mode) +static void zblue_on_stream_configured(struct bt_a2dp_stream* stream) { - switch (sbc_channel_mode) { - case BT_A2DP_SBC_JOINT_STEREO: - case BT_A2DP_SBC_STEREO: - case BT_A2DP_SBC_DUAL_CHANNEL: - return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; - case BT_A2DP_SBC_MONO: - return BTS_A2DP_CODEC_CHANNEL_MODE_MONO; + a2dp_event_t* event; + a2dp_codec_config_t codec_config; /* framework codec */ + struct bt_a2dp_codec_ie* codec_cfg; /* zblue codec */ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + a2dp_info->role = stream->local_ep->sep.sep_info.tsep; + codec_config.codec_type = zephyr_codec_2_sal_codec(stream->local_ep->codec_type); + codec_cfg = &stream->codec_config; + + switch (stream->local_ep->codec_type) { + case BT_A2DP_SBC: + codec_config.sample_rate = bt_a2dp_sbc_get_sampling_frequency( + (struct bt_a2dp_codec_sbc_params*)&codec_cfg->codec_ie[0]); + codec_config.bits_per_sample = BTS_A2DP_CODEC_BITS_PER_SAMPLE_16; + codec_config.channel_mode = zephyr_sbc_channel_mode_2_sal_channel_mode( + (struct bt_a2dp_codec_sbc_params*)&codec_cfg->codec_ie[0]); + codec_config.packet_size = 1024; + memcpy(codec_config.specific_info, codec_cfg->codec_ie, sizeof(codec_cfg->codec_ie)); + break; + case BT_A2DP_MPEG2: + break; default: - BT_LOGW("%s, invalid channel mode: 0x%x", __func__, sbc_channel_mode); - return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; + BT_LOGE("%s, codec not supported: 0x%x", __func__, stream->local_ep->codec_type); + return; + } + + if ((a2dp_info->role == SEP_SRC) || (a2dp_info->role == SEP_SNK && a2dp_info->int_acp == A2DP_INT)) + SAL_CHECK_RET(bt_a2dp_stream_establish(stream), 0); + + event = a2dp_event_new(CODEC_CONFIG_EVT, &a2dp_info->bd_addr); + event->event_data.data = malloc(sizeof(codec_config)); + memcpy(event->event_data.data, &codec_config, sizeof(codec_config)); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(event); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(event); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } } -static a2dp_codec_channel_mode_t zephyr_aac_channel_mode_2_sal_channel_mode(uint8_t aac_channel_mode) +static void zblue_on_stream_established(struct bt_a2dp_stream* stream) { - switch (aac_channel_mode) { - case BT_A2DP_AAC_CHANNELS_1: - return BTS_A2DP_CODEC_CHANNEL_MODE_MONO; - case BT_A2DP_AAC_CHANNELS_2: - return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; - default: - BT_LOGW("%s, invalid channel mode: 0x%x", __func__, aac_channel_mode); - return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + a2dp_info->state = BT_A2DP_MEDIA_CONNECTED(a2dp_info->state); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(CONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(CONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } } -static void zblue_on_connected(struct bt_conn* conn) +static void zblue_on_stream_released(struct bt_a2dp_stream* stream) { - uint8_t role = bt_a2dp_get_a2dp_role(conn); - bt_address_t bd_addr; + struct zblue_a2dp_info_t* a2dp_info; + BT_LOGI("%s, stream released", __func__); + + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } - if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); return; + } + + a2dp_info->state = BT_A2DP_MEDIA_DISCONNECTED(a2dp_info->state); - if (role == BT_A2DP_CH_SOURCE) { + if (a2dp_info->role == SEP_SRC) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - bt_sal_a2dp_source_event_callback(a2dp_event_new(CONNECTED_EVT, &bd_addr)); + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &a2dp_info->bd_addr)); + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ + } else { /* SEP_SNK */ #ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(a2dp_event_new(CONNECTED_EVT, &bd_addr)); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &a2dp_info->bd_addr)); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } + if (a2dp_info->disconnect == true && BT_A2DP_SIGNALING_CONNECTED(a2dp_info->state)) { + bt_a2dp_disconnect(a2dp_info->a2dp); + } } -static void zblue_on_disconnected(struct bt_conn* conn) +static void zblue_on_stream_started(struct bt_a2dp_stream* stream) { - bt_address_t bd_addr; + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + /* TODO: check if a2dp stream should be accepted */ + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } +} + +static void zblue_on_stream_suspended(struct bt_a2dp_stream* stream) +{ + struct zblue_a2dp_info_t* a2dp_info; - if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); return; + } + if (a2dp_info->role == SEP_SRC) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &bd_addr)); + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ #ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &bd_addr)); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } } #ifdef CONFIG_BLUETOOTH_A2DP_SINK -static void zblue_on_media_handler(struct bt_conn* conn, uint8_t* data, uint16_t len) +static void zblue_on_stream_recv(struct bt_a2dp_stream* stream, + struct net_buf* buf, uint16_t seq_num, uint32_t ts) { - bt_address_t bd_addr; a2dp_event_t* event; a2dp_sink_packet_t* packet; - uint8_t* p; - uint8_t offset; - uint16_t seq, pktlen; + uint8_t num_of_frames; + uint16_t seq; uint32_t timestamp; + struct zblue_a2dp_info_t* a2dp_info; - if (data == NULL) + if (buf == NULL || buf->data == NULL) return; - if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); return; + } - p = data; - offset = 12 + (*p & 0x0F) * 4; // rtp header + ssrc + csrc - if (len < offset) { - BT_LOGE("%s, invalid length: %d", __func__, len); + if (!buf->len) { + BT_LOGE("%s, invalid length: %d", __func__, buf->len); return; } - pktlen = len - offset; - p += 2; - BE_STREAM_TO_UINT16(seq, p); - BE_STREAM_TO_UINT32(timestamp, p); - packet = a2dp_sink_new_packet(timestamp, seq, data + offset, pktlen); + seq = seq_num; + timestamp = ts; + packet = a2dp_sink_new_packet(timestamp, seq, buf->data, buf->len); + if (packet == NULL) { BT_LOGE("%s, packet malloc failed", __func__); return; } - event = a2dp_event_new(DATA_IND_EVT, &bd_addr); + event = a2dp_event_new(DATA_IND_EVT, &a2dp_info->bd_addr); event->event_data.packet = packet; bt_sal_a2dp_sink_event_callback(event); } #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ -static int zblue_on_media_state_req(struct bt_conn* conn, uint8_t state) +static struct bt_a2dp_stream_ops stream_ops = { + .configured = zblue_on_stream_configured, + .established = zblue_on_stream_established, + .released = zblue_on_stream_released, + .started = zblue_on_stream_started, + .suspended = zblue_on_stream_suspended, + .aborted = NULL, +#if defined(CONFIG_BLUETOOTH_A2DP_SINK) + .recv = zblue_on_stream_recv, +#endif +#if defined(CONFIG_BLUETOOTH_A2DP_SOURCE) + .sent = NULL, +#endif +}; + +static uint8_t bt_a2dp_discover_endpoint_cb(struct bt_a2dp* a2dp, + struct bt_a2dp_ep_info* info, struct bt_a2dp_ep** ep) { - uint8_t role = bt_a2dp_get_a2dp_role(conn); - bt_address_t bd_addr; - if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) - return -1; + int remote_index = 0; + uint8_t local_index = 0; + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGW("a2dp not found"); + return BT_A2DP_DISCOVER_EP_STOP; + } + + if (info) { + a2dp_info->found_peer_endpoint[a2dp_info->peer_endpoint_count].codec_cap = &a2dp_info->peer_sbc_capabilities[a2dp_info->peer_endpoint_count]; + if (ep != NULL) + *ep = &a2dp_info->found_peer_endpoint[a2dp_info->peer_endpoint_count++]; + else + return BT_A2DP_DISCOVER_EP_CONTINUE; + + if (a2dp_info->peer_endpoint_count >= A2DP_PEER_ENDPOINT_MAX) + return BT_A2DP_DISCOVER_EP_STOP; + + return BT_A2DP_DISCOVER_EP_CONTINUE; + } - switch (state) { - case BT_A2DP_MEDIA_STATE_OPEN: - if (role == BT_A2DP_CH_SOURCE) { + bt_a2dp_stream_cb_register(&a2dp_info->stream, &stream_ops); + + if (a2dp_info->role == SEP_SRC) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - // bt_sal_a2dp_source_event_callback(a2dp_event_new(CONNECTED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ -#ifdef CONFIG_BLUETOOTH_A2DP_SINK - // bt_sal_a2dp_sink_event_callback(a2dp_event_new(CONNECTED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + local_index = 0; + while (local_index < ARRAY_SIZE(src_sbc_cfg_preferred)) { + remote_index = find_remote_codec(&a2dp_sbc_src_endpoint_local, a2dp_info, + &src_sbc_cfg_preferred[local_index]); + if (remote_index < 0) { + local_index++; + continue; + } + memcpy(&a2dp_info->stream.codec_config, src_sbc_cfg_preferred[local_index].codec_config, sizeof(struct bt_a2dp_codec_ie)); + + bt_a2dp_stream_config(a2dp, &a2dp_info->stream, + &a2dp_sbc_src_endpoint_local, &a2dp_info->found_peer_endpoint[remote_index], + &src_sbc_cfg_preferred[local_index]); + return BT_A2DP_DISCOVER_EP_STOP; } - return 0; - case BT_A2DP_MEDIA_STATE_START: - if (role == BT_A2DP_CH_SOURCE) { -#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - /* TODO: check if a2dp stream should be accepted */ - bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + local_index = 0; + while (local_index < ARRAY_SIZE(src_aac_cfg_preferred)) { + remote_index = find_remote_codec(&a2dp_aac_src_endpoint_local, a2dp_info, + &src_aac_cfg_preferred[local_index]); + if (remote_index < 0) { + local_index++; + continue; + } + memcpy(&a2dp_info->stream.codec_config, src_aac_cfg_preferred[local_index].codec_config, sizeof(struct bt_a2dp_codec_ie)); + + bt_a2dp_stream_config(a2dp, &a2dp_info->stream, + &a2dp_aac_src_endpoint_local, &a2dp_info->found_peer_endpoint[remote_index], + &src_aac_cfg_preferred[local_index]); + return BT_A2DP_DISCOVER_EP_STOP; + } +#endif +#endif + } else if (a2dp_info->role == SEP_SNK && a2dp_info->int_acp == A2DP_INT) { #ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + local_index = 0; + while (local_index < ARRAY_SIZE(snk_sbc_cfg_preferred)) { + remote_index = find_remote_codec(&a2dp_sbc_snk_endpoint_local, a2dp_info, + &snk_sbc_cfg_preferred[local_index]); + if (remote_index < 0) { + local_index++; + continue; + } + memcpy(&a2dp_info->stream.codec_config, snk_sbc_cfg_preferred[local_index].codec_config, sizeof(struct bt_a2dp_codec_ie)); + + bt_a2dp_stream_config(a2dp, &a2dp_info->stream, + &a2dp_sbc_snk_endpoint_local, &a2dp_info->found_peer_endpoint[remote_index], + &snk_sbc_cfg_preferred[local_index]); + return BT_A2DP_DISCOVER_EP_STOP; } - return 0; - case BT_A2DP_MEDIA_STATE_CLOSE: - if (role == BT_A2DP_CH_SOURCE) { -#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ -#ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &bd_addr)); -#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + local_index = 0; + while (local_index < ARRAY_SIZE(snk_aac_cfg_preferred)) { + remote_index = find_remote_codec(&a2dp_aac_snk_endpoint_local, a2dp_info, + &snk_aac_cfg_preferred[local_index]); + if (remote_index < 0) { + local_index++; + continue; + } + memcpy(&a2dp_info->stream.codec_config, snk_aac_cfg_preferred[local_index].codec_config, sizeof(struct bt_a2dp_codec_ie)); + + bt_a2dp_stream_config(a2dp, &a2dp_info->stream, + &a2dp_aac_snk_endpoint_local, &a2dp_info->found_peer_endpoint[remote_index], + &snk_aac_cfg_preferred[local_index]); + return BT_A2DP_DISCOVER_EP_STOP; } - return 0; +#endif +#endif + } - case BT_A2DP_MEDIA_STATE_SUSPEND: - if (role == BT_A2DP_CH_SOURCE) { + return BT_A2DP_DISCOVER_EP_STOP; +} + +// TODO: Check if there is another implementation that is more appropriate. +static struct bt_avdtp_sep_info peer_seps[10]; +struct bt_a2dp_discover_param bt_discover_param = { + .cb = bt_a2dp_discover_endpoint_cb, + .seps_info = &peer_seps[0], /* it saves endpoint info internally. */ + .sep_count = A2DP_PEER_ENDPOINT_MAX, +}; + +static void zblue_on_connected(struct bt_a2dp* a2dp, int err) +{ + struct zblue_a2dp_info_t* a2dp_info; + struct bt_conn* conn; + BT_LOGI("%s", __func__); + + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + + if (a2dp_info) { + BT_LOGW("a2dp_info already exists"); + a2dp_info->state = BT_A2DP_SIGNALING_CONNECTED(a2dp_info->state); + if (a2dp_info->int_acp == A2DP_INT) { + bt_a2dp_discover(a2dp, &bt_discover_param); + return; + } + } + + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGW("malloc fail"); + return; + } + + conn = bt_a2dp_get_conn(a2dp); + if (conn == NULL) { + BT_LOGE("conn is null"); + free(a2dp_info); + return; + } + + bt_conn_unref(conn); + + if (bt_sal_get_remote_address(conn, &a2dp_info->bd_addr) != BT_STATUS_SUCCESS) { + free(a2dp_info); + return; + } + + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + memset(&a2dp_info->stream, 0, sizeof(a2dp_info->stream)); + a2dp_info->peer_endpoint_count = 0; + a2dp_info->int_acp = A2DP_ACP; + a2dp_info->role = SEP_INVALID; + a2dp_info->is_cleanup = false; + a2dp_info->state = BT_A2DP_SIGNALING_CONNECTED(a2dp_info->state); + a2dp_info->disconnect = false; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); +} + +static void bt_list_remove_a2dp_info(struct zblue_a2dp_info_t* a2dp_info) +{ + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } + if (a2dp_info == NULL) { + BT_LOGE("%s, a2dp_info is null", __func__); + return; + } + if (a2dp_info->state != 0) + return; + + if (a2dp_info->is_cleanup && (bt_list_length(bt_a2dp_conn) == 1)) { + BT_LOGI("cleanup done, free bt_a2dp_conn"); + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + return; + } + + BT_LOGI("a2dp disconnected, remove a2dp_info"); + bt_list_remove(bt_a2dp_conn, a2dp_info); +} + +static void zblue_on_disconnected(struct bt_a2dp* a2dp) +{ + struct zblue_a2dp_info_t* a2dp_info; + BT_LOGI("%s", __func__); + + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGW("a2dp_info not found"); + return; + } + a2dp_info->state = BT_A2DP_SIGNALING_DISCONNECTED(a2dp_info->state); + + if (a2dp_info->role == SEP_SRC) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &bd_addr)); + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &a2dp_info->bd_addr)); + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ + } else { /* SEP_SNK */ #ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &bd_addr)); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_CLOSED_EVT, &a2dp_info->bd_addr)); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ - } - return 0; + } - default: + bt_list_remove_a2dp_info(a2dp_info); +} + +static int zblue_on_config_req(struct bt_a2dp* a2dp, struct bt_a2dp_ep* ep, + struct bt_a2dp_codec_cfg* codec_cfg, struct bt_a2dp_stream** stream, + uint8_t* rsp_err_code) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp_info not found", __func__); + *rsp_err_code = BT_AVDTP_BAD_STATE; return -1; } - return -1; + *stream = &a2dp_info->stream; /* The a2dp_stream saved in SAL is assigned a value in zblue. */ + bt_a2dp_stream_cb_register(&a2dp_info->stream, &stream_ops); + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; } -static void zblue_on_seted_codec(struct bt_conn* conn, struct bt_a2dp_media_codec* codec, uint8_t cp_type) +static int zblue_on_reconfig_req(struct bt_a2dp_stream* stream, struct bt_a2dp_codec_cfg* codec_cfg, + uint8_t* rsp_err_code) { - uint8_t role = bt_a2dp_get_a2dp_role(conn); - bt_address_t bd_addr; - a2dp_event_t* event; - a2dp_codec_config_t codec_config; + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} - if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) - return; +static void zblue_on_config_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, config fail: %d", __func__, rsp_err_code); +} - codec_config.codec_type = zephyr_codec_2_sal_codec(codec->head.codec_type); +static int zblue_on_establish_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} - switch (codec->head.codec_type) { - case BT_A2DP_SBC: - codec_config.sample_rate = zephyr_sbc_sample_rate_2_sal_sample_rate(codec->sbc.freq); - codec_config.bits_per_sample = BTS_A2DP_CODEC_BITS_PER_SAMPLE_16; - codec_config.channel_mode = zephyr_sbc_channel_mode_2_sal_channel_mode(codec->sbc.channel_mode); - codec_config.packet_size = 1024; - memcpy(codec_config.specific_info, &codec->sbc + sizeof(struct bt_a2dp_media_codec_head), - sizeof(struct bt_a2dp_media_sbc_codec) - sizeof(struct bt_a2dp_media_codec_head)); - break; - case BT_A2DP_MPEG2: - codec_config.sample_rate = zephyr_aac_sample_rate_2_sal_sample_rate(codec->aac.freq0 << 4 | codec->aac.freq1); - codec_config.bits_per_sample = BTS_A2DP_CODEC_BITS_PER_SAMPLE_16; - codec_config.channel_mode = zephyr_aac_channel_mode_2_sal_channel_mode(codec->aac.channels); - codec_config.packet_size = 1024; - memcpy(codec_config.specific_info, &codec->aac + sizeof(struct bt_a2dp_media_codec_head), - sizeof(struct bt_a2dp_media_aac_codec) - sizeof(struct bt_a2dp_media_codec_head)); - break; - default: - BT_LOGE("%s, codec not supported: 0x%x", __func__, codec->head.codec_type); +static void zblue_on_establish_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code == 0) return; - } - event = a2dp_event_new(CODEC_CONFIG_EVT, &bd_addr); - event->event_data.data = malloc(sizeof(codec_config)); - memcpy(event->event_data.data, &codec_config, sizeof(codec_config)); + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp_info not found", __func__); + return; + } - if (role == BT_A2DP_CH_SOURCE) { + if (a2dp_info->role == SEP_SRC) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - bt_sal_a2dp_source_event_callback(event); + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ - } else { /* BT_A2DP_CH_SINK */ + } else { /* SEP_SNK */ #ifdef CONFIG_BLUETOOTH_A2DP_SINK - bt_sal_a2dp_sink_event_callback(event); + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } } -#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE -static const uint8_t a2dp_sbc_src_codec[] = { - 0x00, /* BT_A2DP_AUDIO << 4 */ - 0x00, /* BT_A2DP_SBC */ - 0x23, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ - 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ - 0x02, /* min bitpool */ - 0x35, /* max bitpool */ -}; +static int zblue_on_release_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} -static struct bt_a2dp_endpoint a2dp_sbc_src_endpoint = { - .info.codec = (struct bt_a2dp_media_codec*)&a2dp_sbc_src_codec, - .info.a2dp_cp_scms_t = 0, - .info.a2dp_delay_report = 0, -}; -#endif +static void zblue_on_release_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code == 0) + return; -#ifdef CONFIG_BLUETOOTH_A2DP_SINK -static const uint8_t a2dp_sbc_snk_codec[] = { - 0x00, /* BT_A2DP_AUDIO << 4 */ - 0x00, /* BT_A2DP_SBC */ - 0x33, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ - 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ - 0x02, /* min bitpool */ - 0x35, /* max bitpool */ -}; + BT_LOGE("%s, close fail: %d", __func__, rsp_err_code); + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp_info not found", __func__); + return; + } -static struct bt_a2dp_endpoint a2dp_sbc_snk_endpoint = { - .info.codec = (struct bt_a2dp_media_codec*)&a2dp_sbc_snk_codec, - .info.a2dp_cp_scms_t = 0, - .info.a2dp_delay_report = 0, -}; -#endif + a2dp_info->state = BT_A2DP_MEDIA_DISCONNECTED(a2dp_info->state); + if (a2dp_info->disconnect == true && BT_A2DP_SIGNALING_CONNECTED(a2dp_info->state)) { + BT_LOGI("Failed to disconnect the media channel, disconnect the signaling channel"); + bt_a2dp_disconnect(a2dp_info->a2dp); + } +} -#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC -#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE -static const uint8_t a2dp_aac_src_codec[] = { - 0x00, /* BT_A2DP_AUDIO << 4 */ - 0x02, /* BT_A2DP_MPEG2 */ - 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ - 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ - 0x0C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ -#ifdef A2DP_AAC_MAX_BIT_RATE - 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ - ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ - (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ -#else - 0xFF, /* VBR | bit rate[22:16] */ - 0xFF, /* bit rate[15:8] */ - 0xFF, /* bit rate[7:0]*/ -#endif /* A2DP_AAC_MAX_BIT_RATE */ -}; +static int zblue_on_start_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} -static struct bt_a2dp_endpoint a2dp_aac_src_endpoint = { - .info.codec = (struct bt_a2dp_media_codec*)&a2dp_aac_src_codec, - .info.a2dp_cp_scms_t = 0, - .info.a2dp_delay_report = 0, -}; -#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +static void zblue_on_start_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, start fail: %d", __func__, rsp_err_code); +} -#ifdef CONFIG_BLUETOOTH_A2DP_SINK -static const uint8_t a2dp_aac_snk_codec[] = { - 0x00, /* BT_A2DP_AUDIO << 4 */ - 0x02, /* BT_A2DP_MPEG2 */ - 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ - 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ - 0x8C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ -#ifdef A2DP_AAC_MAX_BIT_RATE - 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ - ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ - (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ -#else - 0xFF, /* VBR | bit rate[22:16] */ - 0xFF, /* bit rate[15:8] */ - 0xFF, /* bit rate[7:0]*/ -#endif /* A2DP_AAC_MAX_BIT_RATE */ -}; +static int zblue_on_suspend_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} +static void zblue_on_suspend_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, suspend fail: %d", __func__, rsp_err_code); +} -static struct bt_a2dp_endpoint a2dp_aac_snk_endpoint = { - .info.codec = (struct bt_a2dp_media_codec*)&a2dp_aac_snk_codec, - .info.a2dp_cp_scms_t = 0, - .info.a2dp_delay_report = 0, +static struct bt_a2dp_cb a2dp_cbks = { + .connected = zblue_on_connected, + .disconnected = zblue_on_disconnected, + .config_req = zblue_on_config_req, + .reconfig_req = zblue_on_reconfig_req, + .config_rsp = zblue_on_config_rsp, + .establish_req = zblue_on_establish_req, + .establish_rsp = zblue_on_establish_rsp, + .release_req = zblue_on_release_req, + .release_rsp = zblue_on_release_rsp, + .start_req = zblue_on_start_req, + .start_rsp = zblue_on_start_rsp, + .suspend_req = zblue_on_suspend_req, + .suspend_rsp = zblue_on_suspend_rsp, + .abort_req = NULL, + .abort_rsp = NULL, }; -#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ -#endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ bt_status_t bt_sal_a2dp_source_init(uint8_t max_connections) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - uint8_t media_type = BT_A2DP_AUDIO; - uint8_t role = BT_A2DP_EP_SOURCE; + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 0; /* BT_AVDTP_SOURCE */ + int ret; + + if (!bt_a2dp_conn) + bt_a2dp_conn = bt_list_new(a2dp_info_destory); + + if (bt_list_length(bt_a2dp_conn)) { + bt_list_clear(bt_a2dp_conn); + } + + bt_sdp_register_service(&a2dp_source_rec); /* Mandatory support for SBC */ - SAL_CHECK_RET(bt_a2dp_register_endpoint(&a2dp_sbc_src_endpoint, media_type, role), 0); + ret = bt_a2dp_register_ep(&a2dp_sbc_src_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } #ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC /* Optional support for AAC */ - SAL_CHECK_RET(bt_a2dp_register_endpoint(&a2dp_aac_src_endpoint, media_type, role), 0); + ret = bt_a2dp_register_ep(&a2dp_aac_src_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } #endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ SAL_CHECK_RET(bt_a2dp_register_cb(&a2dp_cbks), 0); @@ -432,15 +1262,33 @@ bt_status_t bt_sal_a2dp_source_init(uint8_t max_connections) bt_status_t bt_sal_a2dp_sink_init(uint8_t max_connections) { #ifdef CONFIG_BLUETOOTH_A2DP_SINK - uint8_t media_type = BT_A2DP_AUDIO; - uint8_t role = BT_A2DP_EP_SINK; + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 1; /* BT_AVDTP_SINK */ + int ret; + + if (!bt_a2dp_conn) { + bt_a2dp_conn = bt_list_new(a2dp_info_destory); + } + + if (bt_list_length(bt_a2dp_conn)) { + bt_list_clear(bt_a2dp_conn); + } + bt_sdp_register_service(&a2dp_sink_rec); /* Mandatory support for SBC */ - SAL_CHECK_RET(bt_a2dp_register_endpoint(&a2dp_sbc_snk_endpoint, media_type, role), 0); + ret = bt_a2dp_register_ep(&a2dp_sbc_snk_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } #ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC /* Optional support for AAC */ - SAL_CHECK_RET(bt_a2dp_register_endpoint(&a2dp_aac_snk_endpoint, media_type, role), 0); + ret = bt_a2dp_register_ep(&a2dp_aac_snk_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } #endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ SAL_CHECK_RET(bt_a2dp_register_cb(&a2dp_cbks), 0); @@ -451,24 +1299,54 @@ bt_status_t bt_sal_a2dp_sink_init(uint8_t max_connections) #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } -void bt_sal_a2dp_source_cleanup(void) -{ - /* Not supported */ -} - -void bt_sal_a2dp_sink_cleanup(void) -{ - /* Not supported */ -} - bt_status_t bt_sal_a2dp_source_connect(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + struct bt_a2dp* a2dp; + + if (!conn) { + BT_LOGE("%s, acl not connected", __func__); + return BT_STATUS_FAIL; + } + + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + if (a2dp_info) { + BT_LOGW("%s, A2DP connect already exists", __func__); + goto error; + } + + a2dp = bt_a2dp_connect(conn); + if (!a2dp) { + BT_LOGE("%s, A2DP connect failed", __func__); + goto error; + } - SAL_CHECK_RET(bt_a2dp_connect(conn, BT_A2DP_CH_SOURCE), 0); + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGE("%s, malloc failed", __func__); + goto error; + } + memcpy(&a2dp_info->bd_addr, addr, sizeof(bt_address_t)); + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + memset(&a2dp_info->stream, 0, sizeof(a2dp_info->stream)); + a2dp_info->peer_endpoint_count = 0; + a2dp_info->int_acp = A2DP_INT; + a2dp_info->role = SEP_SRC; + a2dp_info->is_cleanup = false; + a2dp_info->state = 0; + a2dp_info->disconnect = false; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); + bt_conn_unref(conn); return BT_STATUS_SUCCESS; + +error: + bt_conn_unref(conn); + return BT_STATUS_FAIL; #else return BT_STATUS_NOT_SUPPORTED; #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ @@ -478,23 +1356,92 @@ bt_status_t bt_sal_a2dp_sink_connect(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SINK struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + struct bt_a2dp* a2dp; + + if (!conn) { + BT_LOGE("%s, acl not connected", __func__); + return BT_STATUS_FAIL; + } - SAL_CHECK_RET(bt_a2dp_connect(conn, BT_A2DP_CH_SINK), 0); + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + if (a2dp_info) { + BT_LOGW("%s, A2DP connect already exists", __func__); + goto error; + } + + a2dp = bt_a2dp_connect(conn); + if (!a2dp) { + BT_LOGE("%s, A2DP connect failed", __func__); + goto error; + } + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGE("%s, malloc failed", __func__); + goto error; + } + + memcpy(&a2dp_info->bd_addr, addr, sizeof(bt_address_t)); + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + memset(&a2dp_info->stream, 0, sizeof(a2dp_info->stream)); + a2dp_info->peer_endpoint_count = 0; + a2dp_info->int_acp = A2DP_INT; + a2dp_info->role = SEP_SNK; + a2dp_info->is_cleanup = false; + a2dp_info->state = 0; + a2dp_info->disconnect = false; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); + bt_conn_unref(conn); return BT_STATUS_SUCCESS; + +error: + bt_conn_unref(conn); + return BT_STATUS_FAIL; #else return BT_STATUS_NOT_SUPPORTED; #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } +static bt_status_t bt_sal_a2dp_disconnect(struct zblue_a2dp_info_t* a2dp_info) +{ + int res_media = 0; + int res_signaling = 0; + + if (!a2dp_info) + return BT_STATUS_SUCCESS; + + if (a2dp_info->disconnect) { + BT_LOGW("%s, disconnecting", __func__); + return BT_STATUS_SUCCESS; + } + + a2dp_info->disconnect = true; + if (BT_A2DP_FIND_MEDIA_CONNECTION(a2dp_info->state)) { + BT_LOGW("%s, media connection exists, disconnect", __func__); + return bt_a2dp_stream_release(&a2dp_info->stream); + } else if (BT_A2DP_FIND_SIGNALING_CONNECTION(a2dp_info->state)) { + BT_LOGW("%s, signaling connection exists, disconnect", __func__); + return bt_a2dp_disconnect(a2dp_info->a2dp); + } + + return BT_STATUS_SUCCESS; +} + bt_status_t bt_sal_a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; - SAL_CHECK_RET(bt_a2dp_disconnect(conn), 0); + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } - return BT_STATUS_SUCCESS; + return bt_sal_a2dp_disconnect(a2dp_info); #else return BT_STATUS_NOT_SUPPORTED; #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ @@ -503,11 +1450,15 @@ bt_status_t bt_sal_a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* a bt_status_t bt_sal_a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SINK - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; - SAL_CHECK_RET(bt_a2dp_disconnect(conn), 0); + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } - return BT_STATUS_SUCCESS; + return bt_sal_a2dp_disconnect(a2dp_info); #else return BT_STATUS_NOT_SUPPORTED; #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ @@ -516,9 +1467,15 @@ bt_status_t bt_sal_a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* add bt_status_t bt_sal_a2dp_source_start_stream(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } - SAL_CHECK_RET(bt_a2dp_start(conn), 0); + SAL_CHECK_RET(bt_a2dp_stream_start(&a2dp_info->stream), 0); return BT_STATUS_SUCCESS; #else @@ -529,9 +1486,14 @@ bt_status_t bt_sal_a2dp_source_start_stream(bt_controller_id_t id, bt_address_t* bt_status_t bt_sal_a2dp_source_suspend_stream(bt_controller_id_t id, bt_address_t* addr) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } - SAL_CHECK_RET(bt_a2dp_suspend(conn), 0); + SAL_CHECK_RET(bt_a2dp_stream_suspend(&a2dp_info->stream), 0); return BT_STATUS_SUCCESS; #else @@ -543,31 +1505,38 @@ bt_status_t bt_sal_a2dp_source_send_data(bt_controller_id_t id, bt_address_t* re uint8_t* buf, uint16_t nbytes, uint8_t nb_frames, uint64_t timestamp, uint32_t seq) { #ifdef CONFIG_BLUETOOTH_A2DP_SOURCE - struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)remote_addr); - uint8_t* data = buf; - uint16_t len; + struct net_buf* media_packet_buf; + struct zblue_a2dp_info_t* a2dp_info; + int ret; if (!buf) return BT_STATUS_PARM_INVALID; - data[0] = 0x80; /* Version 0b10, Padding 0b0, Extension 0b0, CSRC 0b0000 */ - data[1] = 0x60; /* Marker 0b0, Payload Type 0b1100000 */ - data[2] = (uint8_t)(seq >> 8); - data[3] = (uint8_t)(seq); - data[4] = (uint8_t)(timestamp >> 24); - data[5] = (uint8_t)(timestamp >> 16); - data[6] = (uint8_t)(timestamp >> 8); - data[7] = (uint8_t)(timestamp); - data[8] = 0x00; /* SSRC(MSB) */ - data[9] = 0x00; /* SSRC */ - data[10] = 0x00; /* SSRC */ - data[11] = 0x01; /* SSRC(LSB) */ + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, remote_addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + media_packet_buf = net_buf_alloc(&bt_a2dp_tx_pool, K_FOREVER); + + // Reserve space for the A2DP header + net_buf_reserve(media_packet_buf, BT_A2DP_STREAM_BUF_RESERVE); - len = nbytes + AVDTP_RTP_HEADER_LEN; + // buf = Media Packet Header(AVDTP_RTP_HEADER_LEN) + Media Payload + // nbytes = Media Payload Length + net_buf_add_mem(media_packet_buf, &buf[AVDTP_RTP_HEADER_LEN], nbytes); - SAL_CHECK_RET(bt_a2dp_send_audio_data(conn, data, len), 0); + ret = bt_a2dp_stream_send(&a2dp_info->stream, media_packet_buf, seq, timestamp); + if (ret < 0) + goto error; return BT_STATUS_SUCCESS; + +error: + BT_LOGE("%s, bt_a2dp_stream_send failed, ret: %d", __func__, ret); + net_buf_unref(media_packet_buf); + return BT_STATUS_FAIL; #else return BT_STATUS_NOT_SUPPORTED; #endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ @@ -583,4 +1552,70 @@ bt_status_t bt_sal_a2dp_sink_start_stream(bt_controller_id_t id, bt_address_t* a #endif /* CONFIG_BLUETOOTH_A2DP_SINK */ } -#endif /* CONFIG_BLUETOOTH_A2DP */ +void bt_sal_a2dp_source_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_list_t* list = bt_a2dp_conn; + bt_list_node_t* node; + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 0; /* BT_AVDTP_SOURCE */ + + if (!list) + return; + + bt_sdp_unregister_service(&a2dp_source_rec); + bt_a2dp_unregister_ep(&a2dp_sbc_src_endpoint_local); + + if (bt_list_length(bt_a2dp_conn) == 0) { + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + return; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + struct zblue_a2dp_info_t* a2dp_info = bt_list_node(node); + + a2dp_info->is_cleanup = true; + bt_sal_a2dp_disconnect(a2dp_info); + } + + return; +#else + return; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +void bt_sal_a2dp_sink_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_list_t* list = bt_a2dp_conn; + bt_list_node_t* node; + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 1; /* BT_AVDTP_SINK */ + + if (!list) + return; + + bt_sdp_unregister_service(&a2dp_sink_rec); + bt_a2dp_unregister_ep(&a2dp_sbc_snk_endpoint_local); + + if (bt_list_length(bt_a2dp_conn) == 0) { + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + return; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + struct zblue_a2dp_info_t* a2dp_info = bt_list_node(node); + + a2dp_info->is_cleanup = true; + bt_sal_a2dp_disconnect(a2dp_info); + } + + return; +#else + return; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +#endif /* CONFIG_BLUETOOTH_A2DP */ \ No newline at end of file diff --git a/service/stacks/zephyr/sal_avrcp_interface.c b/service/stacks/zephyr/sal_avrcp_interface.c index 84e5897c..81920ed7 100644 --- a/service/stacks/zephyr/sal_avrcp_interface.c +++ b/service/stacks/zephyr/sal_avrcp_interface.c @@ -28,8 +28,6 @@ #include "sal_interface.h" #include "sal_zblue.h" -#include -#include #include #include "bt_utils.h" @@ -37,6 +35,8 @@ #if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARGET) +extern bt_status_t bt_sal_a2dp_get_role(struct bt_conn* conn, uint8_t* a2dp_role); + static void zblue_on_connected(struct bt_conn* conn); static void zblue_on_disconnected(struct bt_conn* conn); static void zblue_on_notify(struct bt_conn* conn, uint8_t event_id, uint8_t status); @@ -169,9 +169,11 @@ static void zblue_on_notify(struct bt_conn* conn, uint8_t event_id, uint8_t stat { bt_address_t bd_addr; avrcp_msg_t* msg; + uint8_t role; + bt_status_t get_role_status; #ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME - uint8_t role = bt_a2dp_get_a2dp_role(conn); + get_role_status = bt_sal_a2dp_get_role(conn, &role); #endif if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) @@ -193,21 +195,23 @@ static void zblue_on_notify(struct bt_conn* conn, uint8_t event_id, uint8_t stat #endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL */ #ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME case BT_AVRCP_EVENT_VOLUME_CHANGED: - if (role == BT_A2DP_CH_SOURCE) { -#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) + if (get_role_status != 0 || role == 0 /* SEP_SRC */) { /* Note: This callback can be triggered when a set absolute volume response is received */ msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_ABSVOL_RSP, &bd_addr); msg->data.absvol.volume = status; bt_sal_avrcp_control_event_callback(msg); -#endif /* CONFIG_BLUETOOTH_AVRCP_TARGET */ - } else { /* BT_A2DP_CH_SINK */ -#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + } +#elif defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) + if (get_role_status != 0 || role == 1 /* SEP_SNK */) { /* Note: This callback can be triggered when a set absolute volume command is received */ msg = avrcp_msg_new(AVRC_SET_ABSOLUTE_VOLUME, &bd_addr); msg->data.absvol.volume = status; bt_sal_avrcp_control_event_callback(msg); -#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL */ } +#else + break; +#endif break; #endif /* CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME */ default: -- Gitee From b4f552e0a19df80c6f7a9619e10bad97bda012cb Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Wed, 7 May 2025 21:03:13 +0800 Subject: [PATCH 064/498] Bluetooth: Increase supported codec parameters. bug: v/58343 Signed-off-by: jialu --- service/stacks/zephyr/sal_a2dp_interface.c | 112 ++++++++++++++++----- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/service/stacks/zephyr/sal_a2dp_interface.c b/service/stacks/zephyr/sal_a2dp_interface.c index 6d95e0dd..72525cf4 100644 --- a/service/stacks/zephyr/sal_a2dp_interface.c +++ b/service/stacks/zephyr/sal_a2dp_interface.c @@ -84,7 +84,7 @@ NET_BUF_POOL_DEFINE(bt_a2dp_tx_pool, CONFIG_BT_MAX_CONN, static struct bt_a2dp_codec_ie sbc_src_ie = { .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ .codec_ie = { - 0x23, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x2B, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ 0x02, /* min bitpool */ 0x35, /* max bitpool */ @@ -107,19 +107,28 @@ static struct bt_a2dp_codec_ie src_sbc_ie_default[] = { { .len = 4, .codec_ie = { - 0x21, - 0x15, - 0x02, - 0x35, + 0x28, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, { .len = 4, .codec_ie = { - 0x22, - 0x15, - 0x02, - 0x35, + 0x22, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x21, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, }; @@ -131,6 +140,9 @@ static struct bt_a2dp_codec_cfg src_sbc_cfg_preferred[] = { { .codec_config = &src_sbc_ie_default[1], }, + { + .codec_config = &src_sbc_ie_default[2], + }, }; #endif @@ -139,7 +151,7 @@ static struct bt_a2dp_codec_cfg src_sbc_cfg_preferred[] = { static struct bt_a2dp_codec_ie sbc_snk_ie = { .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ .codec_ie = { - 0x33, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x3F, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ 0x02, /* min bitpool */ 0x35, /* max bitpool */ @@ -162,37 +174,73 @@ static struct bt_a2dp_codec_ie snk_sbc_ie_default[] = { { .len = 4, .codec_ie = { - 0x21, - 0x15, - 0x02, - 0x35, + 0x28, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x24, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, { .len = 4, .codec_ie = { - 0x22, - 0x15, - 0x02, - 0x35, + 0x22, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, { .len = 4, .codec_ie = { - 0x11, - 0x15, - 0x02, - 0x35, + 0x21, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, { .len = 4, .codec_ie = { - 0x12, - 0x15, - 0x02, - 0x35, + 0x18, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x14, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x12, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x11, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + 0x35, /* max bitpool */ }, }, }; @@ -209,6 +257,18 @@ static struct bt_a2dp_codec_cfg snk_sbc_cfg_preferred[] = { }, { .codec_config = &snk_sbc_ie_default[3], + }, + { + .codec_config = &snk_sbc_ie_default[4], + }, + { + .codec_config = &snk_sbc_ie_default[5], + }, + { + .codec_config = &snk_sbc_ie_default[6], + }, + { + .codec_config = &snk_sbc_ie_default[7], } }; #endif -- Gitee From 41ee8d5ae503613512f0728ed146ec26ea9feb3e Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Tue, 8 Apr 2025 19:48:58 +0800 Subject: [PATCH 065/498] Adv: adapt ext_adv duration paramter. bug: v/55694 Signed-off-by: liuxiang18 --- .../zephyr/sal_le_advertise_interface.c | 28 ++++++++++++++----- tools/adv.c | 1 + 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c index 46901107..a737c63d 100644 --- a/service/stacks/zephyr/sal_le_advertise_interface.c +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -42,6 +42,7 @@ typedef void (*sal_func_t)(void* args); typedef union { struct { struct bt_le_adv_param param; + struct bt_le_ext_adv_start_param ext_param; uint8_t* adv_data; uint16_t adv_len; uint8_t* scan_rsp_data; @@ -73,12 +74,7 @@ static struct bt_le_ext_adv_cb g_adv_cb = { .connected = ext_adv_connected, }; -static void ext_adv_sent(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_sent_info* info) -{ - BT_LOGD("%s ", __func__); -} - -static void ext_adv_connected(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_connected_info* info) +static void ext_adv_terminated_cb(struct bt_le_ext_adv* adv) { int index; @@ -94,6 +90,20 @@ static void ext_adv_connected(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_co zblue_le_ext_delete(g_adv_sets[index]); } +static void ext_adv_sent(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_sent_info* info) +{ + BT_LOGD("%s ", __func__); + + ext_adv_terminated_cb(adv); +} + +static void ext_adv_connected(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_connected_info* info) +{ + BT_LOGD("%s ", __func__); + + ext_adv_terminated_cb(adv); +} + static bt_status_t zblue_le_ext_convert_param(ble_adv_params_t* params, struct bt_le_adv_param* param) { static bt_addr_le_t addr; @@ -340,7 +350,7 @@ static void STACK_CALL(start_adv)(void* args) goto done; } - ret = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + ret = bt_le_ext_adv_start(adv, &req->adpt.start_adv.ext_param); if (ret) { BT_LOGE("%s, le ext adv start fail, err:%d", __func__, ret); ret = BT_STATUS_FAIL; @@ -410,6 +420,10 @@ bt_status_t bt_sal_le_start_adv(bt_controller_id_t id, uint8_t adv_id, ble_adv_p req->adpt.start_adv.scan_rsp_len = 0; } + if (params->duration) { + req->adpt.start_adv.ext_param.timeout = params->duration; + } + return sal_send_req(req); error: diff --git a/tools/adv.c b/tools/adv.c index f1c5e7b9..71552427 100644 --- a/tools/adv.c +++ b/tools/adv.c @@ -231,6 +231,7 @@ static int start_adv_cmd(void* handle, int argc, char* argv[]) return CMD_INVALID_PARAM; } PRINT("duration: %" PRId32 " ms", duration * 10); + params.duration = duration; } break; case 'P': { bt_address_t peeraddr; -- Gitee From a1dd529643f20e8f42e36dbdaa63d1bc36bc89da Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 24 Apr 2025 16:08:34 +0800 Subject: [PATCH 066/498] bluetooth: fix adv stop failure after disable stack. bug: v/58061 disable stack won't reset controller, causing still transmit advertising if disable without stop adv. Signed-off-by: liuxiang18 --- service/src/adapter_state.c | 2 +- service/src/advertising.c | 2 +- service/src/scan_manager.c | 2 +- .../zephyr/sal_adapter_classic_interface.c | 18 ++++++++++++++++-- .../stacks/zephyr/sal_adapter_le_interface.c | 19 +++++++++++++++---- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/service/src/adapter_state.c b/service/src/adapter_state.c index 753dcf34..e34dd0e8 100644 --- a/service/src/adapter_state.c +++ b/service/src/adapter_state.c @@ -450,6 +450,7 @@ static void ble_turning_off_enter(state_machine_t* sm) #ifdef CONFIG_BLUETOOTH_BLE_SUPPORT /* LE profile service shotdown */ service_manager_shutdown(BT_TRANSPORT_BLE); + adapter_on_le_disabled(); const state_t* prev = hsm_get_previous_state(sm); adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_BLE_TURNING_OFF); #else @@ -463,7 +464,6 @@ static void ble_turning_off_exit(state_machine_t* sm) adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; ADAPTER_DBG_EXIT(sm); stm->ble_enabled = false; - adapter_on_le_disabled(); #endif } diff --git a/service/src/advertising.c b/service/src/advertising.c index eaa4c412..312f52f7 100644 --- a/service/src/advertising.c +++ b/service/src/advertising.c @@ -389,5 +389,5 @@ void adv_manager_init(void) void adv_manager_cleanup(void) { - do_in_service_loop(advertisers_cleanup, NULL); + advertisers_cleanup(NULL); } diff --git a/service/src/scan_manager.c b/service/src/scan_manager.c index 11f55b7b..50981857 100644 --- a/service/src/scan_manager.c +++ b/service/src/scan_manager.c @@ -557,7 +557,7 @@ void scan_manager_init(void) void scan_manager_cleanup(void) { - do_in_service_loop(cleanup_scanner, NULL); + cleanup_scanner(NULL); } void scanner_dump(bt_scanner_t* scanner) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index da2c3386..3d6fafe5 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -532,17 +532,31 @@ bt_status_t bt_sal_enable(bt_controller_id_t id) #endif } +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(brder_disable)(void* args) +{ + bt_disable(); +} +#endif + bt_status_t bt_sal_disable(bt_controller_id_t id) { UNUSED(id); #ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + sal_adapter_req_t* req; + if (!bt_is_ready()) { adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); return BT_STATUS_SUCCESS; } - - bt_disable(); +#ifndef CONFIG_BLUETOOTH_BLE_SUPPORT + req = sal_adapter_req(id, NULL, STACK_CALL(brder_disable)); + if (!req) { + return BT_STATUS_NOMEM; + } + sal_send_req(req); +#endif adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); return BT_STATUS_SUCCESS; diff --git a/service/stacks/zephyr/sal_adapter_le_interface.c b/service/stacks/zephyr/sal_adapter_le_interface.c index c5bc3846..b3bb9040 100644 --- a/service/stacks/zephyr/sal_adapter_le_interface.c +++ b/service/stacks/zephyr/sal_adapter_le_interface.c @@ -426,7 +426,7 @@ void bt_sal_le_cleanup(void) bt_status_t bt_sal_le_enable(bt_controller_id_t id) { if (bt_is_ready()) { - adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_ON); + adapter_on_adapter_state_changed(BLE_STACK_STATE_ON); return BT_STATUS_SUCCESS; } @@ -435,15 +435,26 @@ bt_status_t bt_sal_le_enable(bt_controller_id_t id) return BT_STATUS_SUCCESS; } +static void STACK_CALL(le_disable)(void* args) +{ + bt_disable(); +} + bt_status_t bt_sal_le_disable(bt_controller_id_t id) { + sal_adapter_req_t* req; + if (!bt_is_ready()) { - adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + adapter_on_adapter_state_changed(BLE_STACK_STATE_OFF); return BT_STATUS_SUCCESS; } - SAL_CHECK_RET(bt_disable(), 0); - adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + req = sal_adapter_req(id, NULL, STACK_CALL(le_disable)); + if (!req) { + return BT_STATUS_NOMEM; + } + sal_send_req(req); + adapter_on_adapter_state_changed(BLE_STACK_STATE_OFF); return BT_STATUS_SUCCESS; } -- Gitee From 0a7ba84716fa195bbd2b93bc460f67302236470e Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 24 Apr 2025 13:33:16 +0800 Subject: [PATCH 067/498] bluetooth: fix build error. bug: v/55753 Signed-off-by: liuxiang18 --- service/src/adapter_service.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index 66e38a56..40b15bd2 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -1160,7 +1160,7 @@ void adapter_on_le_enabled(bool enablebt) bt_addr_set_empty(&adapter->le_properties.addr); } - bt_addr_ba2str(&props->addr, addrstr); + bt_addr_ba2str(&adapter->le_properties.addr, addrstr); BT_LOGD("%s, le_addr:%s", __func__, addrstr); /* set le io capability ? */ -- Gitee From c1d515560e81e646e97c45ccdb649ceeaeb9ec0e Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Sun, 13 Apr 2025 15:31:19 +0800 Subject: [PATCH 068/498] Adapter: allow to check local support profiles via uuid. bug: v/59759 Signed-off-by: Zihao Gao --- framework/include/bt_uuid.h | 9 ++++++++ .../ipc/socket/include/bt_message_adapter.h | 2 +- .../profiles/a2dp/sink/a2dp_sink_service.c | 2 +- .../a2dp/source/a2dp_source_service.c | 2 +- service/profiles/hfp_ag/hfp_ag_service.c | 2 +- service/profiles/hfp_hf/hfp_hf_service.c | 2 +- service/profiles/service_manager.c | 22 +++++++++++++++++++ service/profiles/service_manager.h | 1 + service/src/adapter_service.c | 11 +++++++++- 9 files changed, 47 insertions(+), 6 deletions(-) diff --git a/framework/include/bt_uuid.h b/framework/include/bt_uuid.h index 88d4298b..c8e12eed 100644 --- a/framework/include/bt_uuid.h +++ b/framework/include/bt_uuid.h @@ -23,6 +23,15 @@ extern "C" { #include #include +/** + * @cond + */ + +#define BT_UUID_A2DP_SRC 0x110A +#define BT_UUID_A2DP_SNK 0x110B +#define BT_UUID_HFP 0x111E +#define BT_UUID_HFP_AG 0x111F + typedef enum { BT_UUID16_TYPE = 2, BT_UUID32_TYPE = 4, diff --git a/service/ipc/socket/include/bt_message_adapter.h b/service/ipc/socket/include/bt_message_adapter.h index 616bcce9..93098490 100644 --- a/service/ipc/socket/include/bt_message_adapter.h +++ b/service/ipc/socket/include/bt_message_adapter.h @@ -126,7 +126,7 @@ BT_ADAPTER_MESSAGE_START, struct { uint16_t size; uint8_t pad[2]; - bt_uuid_t uuids[16]; + bt_uuid_t uuids[BT_UUID_MAX_NUM]; } _bt_adapter_get_uuids; struct { diff --git a/service/profiles/a2dp/sink/a2dp_sink_service.c b/service/profiles/a2dp/sink/a2dp_sink_service.c index d7adc0a1..ece25f62 100644 --- a/service/profiles/a2dp/sink/a2dp_sink_service.c +++ b/service/profiles/a2dp/sink/a2dp_sink_service.c @@ -434,7 +434,7 @@ static const profile_service_t a2dp_sink_service = { .name = PROFILE_A2DP_SINK_NAME, .id = PROFILE_A2DP_SINK, .transport = BT_TRANSPORT_BREDR, - .uuid = { BT_UUID128_TYPE, { 0 } }, + .uuid = BT_UUID_DECLARE_16(BT_UUID_A2DP_SNK), .init = a2dp_sink_init, .startup = a2dp_sink_startup, .shutdown = a2dp_sink_shutdown, diff --git a/service/profiles/a2dp/source/a2dp_source_service.c b/service/profiles/a2dp/source/a2dp_source_service.c index 106702f5..47eaeeab 100644 --- a/service/profiles/a2dp/source/a2dp_source_service.c +++ b/service/profiles/a2dp/source/a2dp_source_service.c @@ -618,7 +618,7 @@ static const profile_service_t a2dp_source_service = { .name = PROFILE_A2DP_NAME, .id = PROFILE_A2DP, .transport = BT_TRANSPORT_BREDR, - .uuid = { BT_UUID128_TYPE, { 0 } }, + .uuid = BT_UUID_DECLARE_16(BT_UUID_A2DP_SRC), .init = a2dp_source_init, .startup = a2dp_source_startup, .shutdown = a2dp_source_shutdown, diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c index 21e44872..f6887f18 100644 --- a/service/profiles/hfp_ag/hfp_ag_service.c +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -955,7 +955,7 @@ static const profile_service_t hfp_ag_service = { .name = PROFILE_HFP_AG_NAME, .id = PROFILE_HFP_AG, .transport = BT_TRANSPORT_BREDR, - .uuid = { BT_UUID128_TYPE, { 0 } }, + .uuid = BT_UUID_DECLARE_16(BT_UUID_HFP_AG), .init = hfp_ag_init, .startup = hfp_ag_startup, .shutdown = hfp_ag_shutdown, diff --git a/service/profiles/hfp_hf/hfp_hf_service.c b/service/profiles/hfp_hf/hfp_hf_service.c index 82fc01b7..39daa999 100644 --- a/service/profiles/hfp_hf/hfp_hf_service.c +++ b/service/profiles/hfp_hf/hfp_hf_service.c @@ -1038,7 +1038,7 @@ static const profile_service_t hfp_hf_service = { .name = PROFILE_HFP_HF_NAME, .id = PROFILE_HFP_HF, .transport = BT_TRANSPORT_BREDR, - .uuid = { BT_UUID128_TYPE, { 0 } }, + .uuid = BT_UUID_DECLARE_16(BT_UUID_HFP), .init = hfp_hf_init, .startup = hfp_hf_startup, .shutdown = hfp_hf_shutdown, diff --git a/service/profiles/service_manager.c b/service/profiles/service_manager.c index a2f844ee..d29a503c 100644 --- a/service/profiles/service_manager.c +++ b/service/profiles/service_manager.c @@ -160,6 +160,28 @@ int service_manager_startup(uint8_t transport) return 0; } +int service_manager_get_uuid(bt_uuid_t* uuids, uint16_t* size) +{ + uint16_t cnt = 0; + bt_uuid_t empty_uuid = BT_UUID_DECLARE_128(0); + + for (int i = 0; i < PROFILE_MAX && cnt < BT_UUID_MAX_NUM; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile == NULL) + continue; + + if (bt_uuid_compare(&empty_uuid, &profile->uuid) == 0) + continue; + + memcpy(uuids++, &profile->uuid, sizeof(bt_uuid_t)); + cnt++; + } + + *size = cnt; + + return 0; +} + int service_manager_processmsg(profile_msg_t* msg) { for (int i = 0; i < PROFILE_MAX; i++) { diff --git a/service/profiles/service_manager.h b/service/profiles/service_manager.h index 7e5c94be..e9d4e378 100644 --- a/service/profiles/service_manager.h +++ b/service/profiles/service_manager.h @@ -74,6 +74,7 @@ typedef struct profile_service { void register_service(const profile_service_t* service); int service_manager_init(void); int service_manager_startup(uint8_t transport); +int service_manager_get_uuid(bt_uuid_t* uuids, uint16_t* size); int service_manager_processmsg(profile_msg_t* msg); int service_manager_shutdown(uint8_t transport); const void* service_manager_get_profile(enum profile_id id); diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index 40b15bd2..49a292ae 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -1907,7 +1907,16 @@ void adapter_get_name(char* name, int size) bt_status_t adapter_get_uuids(bt_uuid_t* uuids, uint16_t* size) { - return BT_STATUS_NOT_SUPPORTED; + bt_status_t status = BT_STATUS_SUCCESS; + + adapter_lock(); + CHECK_ADAPTER_READY(); + + service_manager_get_uuid(uuids, size); + +error: + adapter_unlock(); + return status; } bt_status_t adapter_set_scan_mode(bt_scan_mode_t mode, bool bondable) -- Gitee From 2ca1283ee7af8c2ed45df6402de3e57550c1b8d0 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Tue, 22 Apr 2025 13:56:02 +0800 Subject: [PATCH 069/498] bluetooth: add passive connect sample code bug: v/58892 Signed-off-by: jialu --- sample_code/acceptbond/acceptbond.c | 353 ++++++++++++++++++++ sample_code/acceptbond/acceptbond.h | 75 +++++ sample_code/acceptbond/app_bt_gap.c | 56 ++++ sample_code/acceptbond/app_bt_message_gap.h | 84 +++++ 4 files changed, 568 insertions(+) create mode 100644 sample_code/acceptbond/acceptbond.c create mode 100644 sample_code/acceptbond/acceptbond.h create mode 100644 sample_code/acceptbond/app_bt_gap.c create mode 100644 sample_code/acceptbond/app_bt_message_gap.h diff --git a/sample_code/acceptbond/acceptbond.c b/sample_code/acceptbond/acceptbond.c new file mode 100644 index 00000000..1a25e4a1 --- /dev/null +++ b/sample_code/acceptbond/acceptbond.c @@ -0,0 +1,353 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "acceptbond.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Set the scanning mode to make the device locally connectable and discoverable. + */ +static void app_bt_set_scan_mode(bt_scan_mode_t mode, bool bondable) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_SCANMODE; + node->data.gap_req._bt_adapter_set_scan_mode.mode = mode; + node->data.gap_req._bt_adapter_set_scan_mode.bondable = bondable; + app_list_add_tail(&node->node); +} + +/** + * @brief Set io capability to NOINPUTNOOUTPUT. + */ +static void app_bt_set_io_capability(bt_io_capability_t capability) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_IO_CAPABILITY; + node->data.gap_req._bt_adapter_set_io_capability.cap = capability; + app_list_add_tail(&node->node); +} + +void app_bt_get_local_name() +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_GET_NAME; + app_list_add_tail(&node->node); +} + +void bt_gap_init(void) +{ + app_bt_set_io_capability(BT_IO_CAPABILITY_NOINPUTNOOUTPUT); + + app_bt_get_local_name(); + + app_bt_set_scan_mode(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + bt_gap_init(); + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +/** + * @brief Connection request callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_request_callback(void* cookie, bt_address_t* addr) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_CONNECT_REQUEST_REPLY; + memcpy(&node->data.gap_req._bt_device_connect_request_reply.addr, addr, sizeof(bt_address_t)); + node->data.gap_req._bt_device_connect_request_reply.accept = true; + + app_list_add_tail(&node->node); +} + +/** + * @brief Connection state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_CONNECTION_STATE_CHANGED; + node->data.gap_cb._on_connection_state_changed.state = state; + node->data.gap_cb._on_connection_state_changed.transport = transport; + memcpy(&node->data.gap_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Bond state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_bond_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_BOND_STATE_CHANGED; + node->data.gap_cb._on_bond_state_changed.state = state; + node->data.gap_cb._on_bond_state_changed.transport = transport; + node->data.gap_cb._on_bond_state_changed.is_ctkd = is_ctkd; + memcpy(&node->data.gap_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_connection_state_changed = gap_connection_state_changed_callback, + .on_connect_request = gap_connection_request_callback, + .on_bond_state_changed = gap_bond_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + app_bt_gap_handle_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/acceptbond/acceptbond.h b/sample_code/acceptbond/acceptbond.h new file mode 100644 index 00000000..be446258 --- /dev/null +++ b/sample_code/acceptbond/acceptbond.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/sample_code/acceptbond/app_bt_gap.c b/sample_code/acceptbond/app_bt_gap.c new file mode 100644 index 00000000..1e923030 --- /dev/null +++ b/sample_code/acceptbond/app_bt_gap.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include + +#include "acceptbond.h" + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_GET_NAME: + bt_adapter_get_name(g_bt_ins, msg->gap_req._bt_adapter_get_name.name, BT_NAME_LENGTH); + LOGI("Adapter Name: %s\n", msg->gap_req._bt_adapter_get_name.name); + break; + case APP_BT_GAP_SET_SCANMODE: + bt_adapter_set_scan_mode(g_bt_ins, msg->gap_req._bt_adapter_set_scan_mode.mode, + msg->gap_req._bt_adapter_set_scan_mode.bondable); + break; + case APP_BT_GAP_SET_IO_CAPABILITY: + bt_adapter_set_io_capability(g_bt_ins, msg->gap_req._bt_adapter_set_io_capability.cap); + break; + case APP_BT_GAP_CONNECT_REQUEST_REPLY: + bt_device_connect_request_reply(g_bt_ins, &msg->gap_req._bt_device_connect_request_reply.addr, + msg->gap_req._bt_device_connect_request_reply.accept); + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + break; + case APP_BT_GAP_ON_CONNECTION_STATE_CHANGED: + break; + case APP_BT_GAP_ON_BOND_STATE_CHANGED: + LOGI("Bond state changed: %d", msg->gap_cb._on_bond_state_changed.state); + if (msg->gap_cb._on_bond_state_changed.state == BOND_STATE_BONDED) { + // The sample code is used to test passive connection/pairing/binding, + // so after device is bonded, reset the running flag. + bt_adapter_disable(g_bt_ins); + } + break; + default: + break; + } +} diff --git a/sample_code/acceptbond/app_bt_message_gap.h b/sample_code/acceptbond/app_bt_message_gap.h new file mode 100644 index 00000000..041c5723 --- /dev/null +++ b/sample_code/acceptbond/app_bt_message_gap.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_GET_NAME, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_CONNECT_REQUEST_REPLY, + APP_BT_GAP_CONNECT_DISCONNECT, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_CONNECTION_STATE_CHANGED, + APP_BT_GAP_ON_BOND_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + char name[BT_NAME_LENGTH]; + } _bt_adapter_set_name, + _bt_adapter_get_name; + + struct { + uint8_t mode; /* bt_scan_mode_t */ + uint8_t bondable; /* boolean */ + } _bt_adapter_set_scan_mode; + + struct { + uint8_t cap; /* bt_io_capability_t */ + } _bt_adapter_set_io_capability; + + struct { + bt_address_t addr; + uint8_t accept; /* boolean */ + } _bt_device_connect_request_reply; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* bond_state_t */ + uint8_t is_ctkd; /* boolean */ + } _on_bond_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file -- Gitee From 30c3badc4d21360d405388c4e870a19d0d519539 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Thu, 8 May 2025 20:21:10 +0800 Subject: [PATCH 070/498] bluetooth: add discovery sample code bug: v/58892 Signed-off-by: jialu --- sample_code/discovery/app_bt_gap.c | 45 ++++ sample_code/discovery/app_bt_message_gap.h | 56 ++++ sample_code/discovery/discovery.c | 287 +++++++++++++++++++++ sample_code/discovery/discovery.h | 75 ++++++ 4 files changed, 463 insertions(+) create mode 100644 sample_code/discovery/app_bt_gap.c create mode 100644 sample_code/discovery/app_bt_message_gap.h create mode 100644 sample_code/discovery/discovery.c create mode 100644 sample_code/discovery/discovery.h diff --git a/sample_code/discovery/app_bt_gap.c b/sample_code/discovery/app_bt_gap.c new file mode 100644 index 00000000..01cc8fe9 --- /dev/null +++ b/sample_code/discovery/app_bt_gap.c @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "discovery.h" + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_START_DISCOVERY: + bt_adapter_start_discovery(g_bt_ins, msg->gap_req._bt_adapter_start_discovery.timeout); + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + break; + case APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED: + if (msg->gap_cb._on_discovery_state_changed.state == BT_DISCOVERY_STATE_STARTED) { + LOGI("Discovery started"); + break; + } + LOGI("Discovery stopped"); + // This sample code is used to test discovering nearby Bluetooth devices, + // so after the discovery process ends, Bluetooth is turned off. + bt_adapter_disable(g_bt_ins); + + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/discovery/app_bt_message_gap.h b/sample_code/discovery/app_bt_message_gap.h new file mode 100644 index 00000000..2b567e22 --- /dev/null +++ b/sample_code/discovery/app_bt_message_gap.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_START_DISCOVERY, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_DISCOVERY_RESULT, + APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + uint32_t timeout; + } _bt_adapter_start_discovery; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + uint8_t state; /* bt_discovery_state_t */ + } _on_discovery_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/discovery/discovery.c b/sample_code/discovery/discovery.c new file mode 100644 index 00000000..b1a5f157 --- /dev/null +++ b/sample_code/discovery/discovery.c @@ -0,0 +1,287 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "discovery.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Discover nearby Bluetooth devices. + */ +void app_bt_discovery(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_START_DISCOVERY; + node->data.gap_req._bt_adapter_start_discovery.timeout = 2; + app_list_add_tail(&node->node); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) + return; + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) + app_bt_discovery(); + else if (state == BT_ADAPTER_STATE_OFF) + app_demo.running = 0; +} + +/** + * @brief Discovery result callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_result_callback(void* cookie, bt_discovery_result_t* remote) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&remote->addr, addr_str); + + LOGI("Discovery result: device [%s], name: %s, code: %08x, is HEADSET: %s, rssi: %d\n", + addr_str, remote->name, remote->cod, + IS_HEADSET(remote->cod) ? "true" : "false", + remote->rssi); +} + +/** + * @brief Discovery state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_state_changed_callback(void* cookie, bt_discovery_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED; + node->data.gap_cb._on_discovery_state_changed.state = state; + + app_list_add_tail(&node->node); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_discovery_result = gap_discovery_result_callback, + .on_discovery_state_changed = gap_discovery_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + app_bt_gap_handle_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/discovery/discovery.h b/sample_code/discovery/discovery.h new file mode 100644 index 00000000..be446258 --- /dev/null +++ b/sample_code/discovery/discovery.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file -- Gitee From bc30c84748fbaa2d346a4e7ead8c11ecd43d7325 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Fri, 18 Apr 2025 21:47:08 +0800 Subject: [PATCH 071/498] bluetooth: a2dp: Fix A2DP stuttering issue. bug: v/54099 rootcause: Before playing music, a vendor specific command is sent, but when using the dongle, the controller does not recognize this command and responds slowly, causing audio packets to accumulate in the HCI, resulting in music stuttering. Signed-off-by: jialu --- Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Kconfig b/Kconfig index 4845c857..eaa51959 100644 --- a/Kconfig +++ b/Kconfig @@ -475,11 +475,13 @@ config BLUETOOTH_TOOLS choice prompt "select bt vendor" - default BLUETOOTH_VENDOR_BES + default BLUETOOTH_VENDOR_NONE config BLUETOOTH_VENDOR_BES bool "bluetooth vendor BES" config BLUETOOTH_VENDOR_ACTIONS bool "bluetooth vendor ACTIONS" + config BLUETOOTH_VENDOR_NONE + bool "bluetooth vendor NONE" endchoice config BLUETOOTH_FEATURE -- Gitee From e33574a2871e0fc3e96c53a45b08fc05e12c6f8d Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Tue, 22 Apr 2025 13:52:55 +0800 Subject: [PATCH 072/498] bluetooth: add enable sample code bug: v/58892 Signed-off-by: jialu --- sample_code/enable/app_bt_gap.c | 37 ++++ sample_code/enable/app_bt_message_gap.h | 51 +++++ sample_code/enable/enable.c | 242 ++++++++++++++++++++++++ sample_code/enable/enable.h | 75 ++++++++ 4 files changed, 405 insertions(+) create mode 100644 sample_code/enable/app_bt_gap.c create mode 100644 sample_code/enable/app_bt_message_gap.h create mode 100644 sample_code/enable/enable.c create mode 100644 sample_code/enable/enable.h diff --git a/sample_code/enable/app_bt_gap.c b/sample_code/enable/app_bt_gap.c new file mode 100644 index 00000000..5e532a9e --- /dev/null +++ b/sample_code/enable/app_bt_gap.c @@ -0,0 +1,37 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "enable.h" + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + if (msg->gap_cb._on_adapter_state_changed.state == BT_ADAPTER_STATE_ON) { + // This sample code is used to test opening Bluetooth, + // so after Bluetooth is turned on, turn off Bluetooth. + bt_adapter_disable(g_bt_ins); + } else if (msg->gap_cb._on_adapter_state_changed.state == BT_ADAPTER_STATE_OFF) { + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/enable/app_bt_message_gap.h b/sample_code/enable/app_bt_message_gap.h new file mode 100644 index 00000000..c8938819 --- /dev/null +++ b/sample_code/enable/app_bt_message_gap.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_DISABLE, + APP_BT_GAP_GET_NAME, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/enable/enable.c b/sample_code/enable/enable.c new file mode 100644 index 00000000..3943822c --- /dev/null +++ b/sample_code/enable/enable.c @@ -0,0 +1,242 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "enable.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +void bt_gap_init(bt_instance_t* g_bt_ins) +{ + // Get local device name. + + // Set the scanning mode to make the device locally connectable and discoverable. + + // Set io capability to NOINPUTNOOUTPUT. +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) + return; + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + // bt_gap_init(g_bt_ins); + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + app_bt_gap_handle_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/enable/enable.h b/sample_code/enable/enable.h new file mode 100644 index 00000000..be446258 --- /dev/null +++ b/sample_code/enable/enable.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file -- Gitee From 31f7d0e2104a64de45c5b9608b16a12af50a9db0 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Tue, 22 Apr 2025 12:24:57 +0800 Subject: [PATCH 073/498] bluetooth: add basic sample code bug: v/58892 Signed-off-by: jialu --- sample_code/basic/app_bt_gap.c | 24 +++ sample_code/basic/app_bt_message_gap.h | 44 ++++++ sample_code/basic/basic.c | 211 +++++++++++++++++++++++++ sample_code/basic/basic.h | 74 +++++++++ 4 files changed, 353 insertions(+) create mode 100644 sample_code/basic/app_bt_gap.c create mode 100644 sample_code/basic/app_bt_message_gap.h create mode 100644 sample_code/basic/basic.c create mode 100644 sample_code/basic/basic.h diff --git a/sample_code/basic/app_bt_gap.c b/sample_code/basic/app_bt_gap.c new file mode 100644 index 00000000..07f72721 --- /dev/null +++ b/sample_code/basic/app_bt_gap.c @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "basic.h" + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node) +{ + // Handle Bluetooth message,Call the API interface of Bluetooth. +} diff --git a/sample_code/basic/app_bt_message_gap.h b/sample_code/basic/app_bt_message_gap.h new file mode 100644 index 00000000..6be1f157 --- /dev/null +++ b/sample_code/basic/app_bt_message_gap.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + + } app_bt_message_gap_t; + + typedef union { + + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/basic/basic.c b/sample_code/basic/basic.c new file mode 100644 index 00000000..74b96261 --- /dev/null +++ b/sample_code/basic/basic.c @@ -0,0 +1,211 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include + +#include "basic.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + app_bt_gap_handle_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + // basic demo has no messages to process, so app_demo.running = 0 + app_demo.running = 0; + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/basic/basic.h b/sample_code/basic/basic.h new file mode 100644 index 00000000..cf7b5997 --- /dev/null +++ b/sample_code/basic/basic.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include +#include +#include +#include + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void app_bt_gap_handle_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file -- Gitee From 596b9b42c5c99e31a06eedee282652ef8a9efb9e Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Thu, 8 May 2025 13:36:53 +0800 Subject: [PATCH 074/498] A2DP: Increase codec information check. bug: v/58342 fix PTS issue: A2DP/SNK/AVP/BI-08-C A2DP/SNK/AVP/BI-03-C A2DP/SNK/AVP/BI-16-C A2DP/SNK/AVP/BI-12-C A2DP/SNK/AVP/BI-14-C A2DP/SNK/AVP/BI-13-C A2DP/SNK/AVP/BI-15-C Signed-off-by: Lu Jia --- service/stacks/zephyr/sal_a2dp_interface.c | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/service/stacks/zephyr/sal_a2dp_interface.c b/service/stacks/zephyr/sal_a2dp_interface.c index 72525cf4..3705f0c9 100644 --- a/service/stacks/zephyr/sal_a2dp_interface.c +++ b/service/stacks/zephyr/sal_a2dp_interface.c @@ -1153,12 +1153,127 @@ static void zblue_on_disconnected(struct bt_a2dp* a2dp) bt_list_remove_a2dp_info(a2dp_info); } +static uint8_t bt_avdtp_codec_sanity_check(uint8_t local, uint8_t config, uint8_t offset, uint8_t len, uint8_t err) +{ + uint8_t codec; + + codec = (config >> offset) & ((1 << len) - 1); /* The collection of codec parameters to be checked */ + if (!codec) + return err; + + if ((codec - 1) & codec) { /* The codec parameter to be checked has only one option available. */ + BT_LOGE("%s, Configuration has multiple options.", __func__); + return err; + } + + if (((config & local) >> offset) & ((1 << len) - 1)) { + err = BT_AVDTP_SUCCESS; + } else { + /* Not_Supported error */ + err++; + } + + return err; +} + +static void bt_avdtp_codec_check_sbc(struct bt_a2dp_codec_ie* local, struct bt_a2dp_codec_ie* codec_cfg, uint8_t* rsp_err_code) +{ + if (local->len != codec_cfg->len) { + *rsp_err_code = BT_AVDTP_BAD_LENGTH; + return; + } + + /* Sampling Frequency */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[0], codec_cfg->codec_ie[0], 4, 4, BT_A2DP_INVALID_SAMPLING_FREQUENCY); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Channel Mode */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[0], codec_cfg->codec_ie[0], 0, 4, BT_A2DP_INVALID_CHANNEL_MODE); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Block Length */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 4, 4, BT_A2DP_INVALID_BLOCK_LENGTH); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Subbands */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 2, 2, BT_A2DP_INVALID_SUBBANDS); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Allocation Method */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 0, 2, BT_A2DP_INVALID_ALLOCATION_METHOD); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Bitpool */ + if (codec_cfg->codec_ie[2] > codec_cfg->codec_ie[3]) { + *rsp_err_code = BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE; + return; + } + if ((codec_cfg->codec_ie[2] < 2) || (codec_cfg->codec_ie[2] > 250)) { + *rsp_err_code = BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE; + return; + } + if ((codec_cfg->codec_ie[3] < 2) || (codec_cfg->codec_ie[3] > 250)) { + *rsp_err_code = BT_A2DP_INVALID_MAXIMUM_BITPOOL_VALUE; + return; + } + + if ((codec_cfg->codec_ie[2] < local->codec_ie[2]) || (codec_cfg->codec_ie[2] > local->codec_ie[3])) { + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE; + return; + } + if (codec_cfg->codec_ie[3] > local->codec_ie[3]) { + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE; + return; + } +} + +static void bt_avdtp_set_config_check(struct bt_a2dp_ep* ep, struct bt_a2dp_codec_cfg* codec_cfg, uint8_t* rsp_err_code) +{ + switch (ep->codec_type) { + case BT_A2DP_SBC: + bt_avdtp_codec_check_sbc(ep->codec_cap, codec_cfg->codec_config, rsp_err_code); + break; + case BT_A2DP_MPEG1: + case BT_A2DP_MPEG2: + case BT_A2DP_ATRAC: + case BT_A2DP_VENDOR: + default: + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_CODEC_TYPE; + break; + } +} + static int zblue_on_config_req(struct bt_a2dp* a2dp, struct bt_a2dp_ep* ep, struct bt_a2dp_codec_cfg* codec_cfg, struct bt_a2dp_stream** stream, uint8_t* rsp_err_code) { struct zblue_a2dp_info_t* a2dp_info; + *rsp_err_code = BT_AVDTP_SUCCESS; + + if (ep == NULL || codec_cfg == NULL) { + *rsp_err_code = BT_AVDTP_BAD_STATE; + return -1; + } + + if (ep->sep.sep_info.inuse) { + BT_LOGE("%s, local SEP has already been used.", __func__); + *rsp_err_code = BT_AVDTP_SEP_IN_USE; + return -1; + } + + bt_avdtp_set_config_check(ep, codec_cfg, rsp_err_code); + + if (*rsp_err_code != BT_AVDTP_SUCCESS) { + BT_LOGE("%s, config fail: 0x%02x", __func__, *rsp_err_code); + return -1; + } + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); if (!a2dp_info) { BT_LOGE("%s, a2dp_info not found", __func__); -- Gitee From e30ad11b5cad4cf9e4c25b268bff48c678692c45 Mon Sep 17 00:00:00 2001 From: Lu Jia Date: Fri, 9 May 2025 16:19:09 +0800 Subject: [PATCH 075/498] bluetooth: Fix crash caused by bt_conn_unref(NULL) bug: v/60000 Signed-off-by: jialu --- service/stacks/include/sal_interface.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/stacks/include/sal_interface.h b/service/stacks/include/sal_interface.h index f91f34d3..e606c31b 100644 --- a/service/stacks/include/sal_interface.h +++ b/service/stacks/include/sal_interface.h @@ -78,7 +78,8 @@ typedef struct bt_stack_info { int __ret = cond; \ if (__ret != expect) { \ BT_LOGE("[%s] return:%d", __func__, __ret); \ - bt_conn_unref(conn); \ + if (conn) \ + bt_conn_unref(conn); \ return BT_STATUS_FAIL; \ } \ } -- Gitee From 42d02982cfc7bd4185da0eaa10ec931c25904e0e Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Sat, 21 Dec 2024 23:03:50 +0800 Subject: [PATCH 076/498] bluetooth: implement re-connect method - timeout re-connect bug: v/50798 re-connect as connection timeout(0x08) Signed-off-by: liuxiang18 --- service/profiles/a2dp/a2dp_state_machine.c | 11 + .../profiles/hfp_hf/hfp_hf_state_machine.c | 3 + service/src/adapter_service.c | 2 +- service/src/connection_manager.c | 445 +++++++++++++++++- service/src/connection_manager.h | 6 +- 5 files changed, 454 insertions(+), 13 deletions(-) diff --git a/service/profiles/a2dp/a2dp_state_machine.c b/service/profiles/a2dp/a2dp_state_machine.c index 788028f7..195a432f 100644 --- a/service/profiles/a2dp/a2dp_state_machine.c +++ b/service/profiles/a2dp/a2dp_state_machine.c @@ -55,6 +55,7 @@ #include "audio_control.h" #include "bt_avrcp.h" #include "bt_utils.h" +#include "connection_manager.h" #include "hci_parser.h" #include "media_system.h" #include "power_manager.h" @@ -390,6 +391,11 @@ static void idle_enter(state_machine_t* sm) a2dp_sm->audio_ready = false; if (prev_state != NULL) { bt_pm_conn_close(PROFILE_A2DP, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_disconnected(&a2dp_sm->addr, PROFILE_A2DP_SINK); +#endif + } a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, PROFILE_STATE_DISCONNECTED); if (a2dp_sm->avrcp_timer) { @@ -579,6 +585,11 @@ static void opened_enter(state_machine_t* sm) } bt_pm_conn_open(PROFILE_A2DP, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_connected(&a2dp_sm->addr, PROFILE_A2DP_SINK); +#endif + } a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, PROFILE_STATE_CONNECTED); } diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c index 89caaaa7..0b8c3703 100644 --- a/service/profiles/hfp_hf/hfp_hf_state_machine.c +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -26,6 +26,7 @@ #include "bt_list.h" #include "bt_utils.h" #include "bt_vendor.h" +#include "connection_manager.h" #include "hci_parser.h" #include "hfp_hf_service.h" #include "hfp_hf_state_machine.h" @@ -468,6 +469,7 @@ static void disconnected_enter(state_machine_t* sm) hfsm->need_query = false; if (hsm_get_previous_state(sm)) { bt_pm_conn_close(PROFILE_HFP_HF, &hfsm->addr); + bt_cm_disconnected(&hfsm->addr, PROFILE_HFP_HF); bt_media_remove_listener(hfsm->volume_listener); hfsm->spk_volume = 0; hfsm->mic_volume = 0; @@ -1160,6 +1162,7 @@ static void connected_enter(state_machine_t* sm) HF_DBG_ENTER(sm, &hfsm->addr); bt_pm_conn_open(PROFILE_HFP_HF, &hfsm->addr); + bt_cm_connected(&hfsm->addr, PROFILE_HFP_HF); if (hfsm->need_query) { bt_sal_hfp_hf_get_current_calls(&hfsm->addr); diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index 49a292ae..7bd3947a 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -819,7 +819,7 @@ static void process_connection_state_changed_evt(bt_address_t* addr, acl_state_p } if (acl_params->connection_state == CONNECTION_STATE_DISCONNECTED) - bt_cm_process_disconnect_event(addr, acl_params->transport); + bt_cm_process_disconnect_event(addr, acl_params->transport, acl_params->hci_reason_code); /* send connection changed notification */ CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_connection_state_changed, addr, diff --git a/service/src/connection_manager.c b/service/src/connection_manager.c index 14047e24..4667ff22 100644 --- a/service/src/connection_manager.c +++ b/service/src/connection_manager.c @@ -16,19 +16,218 @@ #define LOG_TAG "connection_manager" #include "connection_manager.h" +#include "adapter_internel.h" #include "bluetooth.h" +#include "hci_error.h" +#include "service_loop.h" +#include "service_manager.h" + +#ifdef CONFIG_BLUETOOTH_HFP_HF +#include "hfp_hf_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +#include "a2dp_sink_service.h" +#endif #ifdef CONFIG_LE_DLF_SUPPORT #include "connection_manager_dlf.h" #endif -typedef struct -{ +#include "utils/log.h" + +#define CM_RECONNECT_INTERVAL (8000) /* reconnect Interval */ +#define PROFILE_CONNECT_INTERVAL (500) /* Interval between HFP and A2DP */ +#define CM_RECONNECT_TIMES ((60 * 30) / 8) /* Continuous 30-mins reconnect */ + +#define FLAG_NONE (0) +#define FLAG_HFP_HF (1 << (PROFILE_HFP_HF)) +#define FLAG_A2DP_SINK (1 << (PROFILE_A2DP_SINK)) + +typedef struct { + service_timer_t* timer; + bt_address_t peer_addr; + bool active; + uint32_t retry_times; +} bt_cm_timer_t; + +typedef struct { bool inited; + bool busy; + bool reconnect_enable; + bool connect_a2dp_flag; + bt_address_t connecting_addr; + uint32_t profile_flags; + bt_cm_timer_t cm_timer; + service_timer_t* a2dp_conn_timer; } bt_connection_manager_t; static bt_connection_manager_t g_connection_manager; +static bt_status_t bt_cm_profile_connect(bt_address_t* addr, uint8_t transport); + +static void bt_cm_set_flags(uint32_t flags) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->profile_flags |= flags; +} + +static void bt_cm_clear_flags(uint32_t flags) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->profile_flags &= ~flags; +} + +static void bt_cm_stop_timer(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (!cm_timer->active) + return; + + cm_timer->active = false; + cm_timer->retry_times = 0; + bt_addr_set_empty(&cm_timer->peer_addr); + service_loop_cancel_timer(cm_timer->timer); + cm_timer->timer = NULL; +} + +static void bt_cm_enable_conn(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + bt_cm_clear_flags(FLAG_HFP_HF | FLAG_A2DP_SINK); + bt_cm_stop_timer(); + manager->connect_a2dp_flag = false; + manager->busy = false; +} + +static bool bt_cm_is_busy(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + return manager->busy; +} + +static void bt_cm_disable_conn() +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->busy = true; + manager->profile_flags = 0; +} + +#ifdef CONFIG_BLUETOOTH_HFP_HF +static bt_status_t bt_cm_hfp_connect(bt_address_t* addr) +{ + hfp_hf_interface_t* hfp_hf_profile; + + hfp_hf_profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + return hfp_hf_profile->connect(addr); +} + +static bt_status_t bt_cm_hfp_disconnect(bt_address_t* addr) +{ + hfp_hf_interface_t* hfp_hf_profile; + + hfp_hf_profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + return hfp_hf_profile->disconnect(addr); +} +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static bt_status_t bt_cm_a2dpsnk_connect(bt_address_t* addr) +{ + a2dp_sink_interface_t* a2dp_snk_profile; + + a2dp_snk_profile = (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); + return a2dp_snk_profile->connect(addr); +} + +static bt_status_t bt_cm_a2dpsnk_disconnect(bt_address_t* addr) +{ + a2dp_sink_interface_t* a2dp_snk_profile; + + a2dp_snk_profile = (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); + return a2dp_snk_profile->disconnect(addr); +} +#endif + +static void bt_cm_connect_a2dp_cb(service_timer_t* timer, void* userdata) +{ + bt_connection_manager_t* manager = (bt_connection_manager_t*)userdata; + + bt_cm_profile_connect(&manager->connecting_addr, BT_TRANSPORT_BREDR); +} + +static bt_status_t bt_cm_profile_connect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_status_t status = BT_STATUS_SUCCESS; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if ((manager->profile_flags & FLAG_HFP_HF) && !manager->connect_a2dp_flag) { /* connect HFP_HF */ +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_cm_hfp_connect(addr); + + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s connect HFP_HF failed", __func__); + return status; + } +#endif + if ((manager->profile_flags & FLAG_A2DP_SINK) == 0) + return BT_STATUS_SUCCESS; + + /* delay 500ms to ensure HFP connection is established first*/ + manager->connect_a2dp_flag = true; + memcpy(&manager->connecting_addr, addr, sizeof(bt_address_t)); + manager->a2dp_conn_timer = service_loop_timer_no_repeating(PROFILE_CONNECT_INTERVAL, bt_cm_connect_a2dp_cb, manager); + } else if (manager->profile_flags & FLAG_A2DP_SINK) { /* connect A2DP_SINK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + manager->connect_a2dp_flag = false; + status = bt_cm_a2dpsnk_connect(addr); +#endif + } + + return status; +} + +static bt_status_t bt_cm_profile_disconnect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_status_t status = BT_STATUS_SUCCESS; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_cm_hfp_disconnect(addr); + + if (status != BT_STATUS_SUCCESS) + return status; +#endif + + if (manager->a2dp_conn_timer) { + manager->connect_a2dp_flag = false; + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + } +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + status = bt_cm_a2dpsnk_disconnect(addr); +#endif + + return status; +} + void bt_cm_init(void) { bt_connection_manager_t* manager = &g_connection_manager; @@ -36,6 +235,9 @@ void bt_cm_init(void) if (manager->inited) return; + bt_cm_enable_conn(); + bt_cm_set_flags(FLAG_NONE); + manager->reconnect_enable = false; manager->inited = true; } @@ -53,6 +255,236 @@ void bt_cm_cleanup(void) manager->inited = false; } +static bool bt_cm_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +static void bt_cm_profile_falgs_set(void) +{ +#if 0 + bt_uuid_t* uuids = NULL; + uint16_t uuid_cnt = 0; + bt_uuid_t uuid16_hfp_ag; + bt_uuid_t uuid16_a2dp; + + bt_uuid16_create(&uuid16_hfp_ag, 0x111F); /* HFP AG UUID */ + bt_uuid16_create(&uuid16_a2dp, 0x110A); /* A2DP UUID */ + adapter_get_remote_uuids(addr, &uuids, &uuid_cnt, bt_cm_allocator); + if (uuid_cnt) { + for (int i = 0; i < uuid_cnt; i++) { + if (!bt_uuid_compare(&uuids[i], &uuid16_hfp_ag)) { +#ifdef CONFIG_BLUETOOTH_HFP_HF + bt_cm_set_flags(FLAG_HFP_HF); +#endif + } else if (!bt_uuid_compare(&uuids[i], &uuid16_a2dp)) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_set_flags(FLAG_A2DP_SINK); +#endif + } + } + } + + free(uuids); + uuids = NULL; +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + bt_cm_set_flags(FLAG_HFP_HF); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_set_flags(FLAG_A2DP_SINK); +#endif +} + +bt_status_t bt_cm_device_connect(bt_address_t* peer_addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_address_t* addrs = NULL; + bt_address_t addr; + int num = 0; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if (!manager->reconnect_enable) + manager->reconnect_enable = true; + + if (bt_cm_is_busy()) { + BT_LOGE("%s processing reconnecting", __func__); + return BT_STATUS_BUSY; + } + + if (bt_addr_is_empty(peer_addr)) { + adapter_get_bonded_devices(transport, &addrs, &num, bt_cm_allocator); + if (!num) { + BT_LOGE("%s no device to connect", __func__); + return BT_STATUS_FAIL; + } + + memcpy(&addr, &addrs[num - 1], sizeof(bt_address_t)); /* connect last device in bonded list */ + free(addrs); + BT_LOGD("%s connect to last device", __func__); + } else { + memcpy(&addr, peer_addr, sizeof(bt_address_t)); + } + + bt_cm_profile_falgs_set(); + return bt_cm_profile_connect(&addr, transport); +} + +bt_status_t bt_cm_device_disconnect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if (!bt_addr_compare(&cm_timer->peer_addr, addr)) { + bt_cm_enable_conn(); + } + + return bt_cm_profile_disconnect(addr, BT_TRANSPORT_BREDR); +} + +void bt_cm_connected(bt_address_t* addr, uint8_t profile_id) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (bt_addr_compare(&cm_timer->peer_addr, addr)) { + return; + } + + BT_LOGD("%s connect success, profile_id: %d", __func__, profile_id); + switch (profile_id) { + case PROFILE_HFP_HF: { + bt_cm_clear_flags(FLAG_HFP_HF); + } + case PROFILE_A2DP_SINK: { + bt_cm_clear_flags(FLAG_A2DP_SINK); + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + } + default: + break; + } + + if ((manager->profile_flags & (FLAG_HFP_HF | FLAG_A2DP_SINK)) == 0) { + BT_LOGD("%s no profile to connect", __func__); + bt_cm_enable_conn(); + } +} + +void bt_cm_disconnected(bt_address_t* addr, uint8_t profile_id) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (bt_addr_compare(&cm_timer->peer_addr, addr)) { + return; + } + + BT_LOGD("%s connect failed, profile_id: %d", __func__, profile_id); + switch (profile_id) { + case PROFILE_A2DP_SINK: { + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + } + default: + break; + } +} + +static void bt_cm_timeout_cb(service_timer_t* timer, void* userdata) +{ + bt_cm_timer_t* cm_timer = (bt_cm_timer_t*)userdata; + + if (!cm_timer->active) { + BT_LOGE("%s timer is not actice", __func__); + return; + } + + cm_timer->retry_times++; + if (cm_timer->retry_times > CM_RECONNECT_TIMES) { + bt_cm_enable_conn(); + } + + BT_LOGD("%s reconnect start, retry_times = %" PRIu32, __func__, cm_timer->retry_times); + bt_cm_profile_connect(&cm_timer->peer_addr, BT_TRANSPORT_BREDR); +} + +static bool bt_cm_start_timer(bt_address_t* peer_addr) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (cm_timer->active) { + BT_LOGW("%s timer is active", __func__); + return false; + } + + BT_LOGD("%s CM connect start", __func__); + cm_timer->active = true; + memcpy(&cm_timer->peer_addr, peer_addr, sizeof(bt_address_t)); + if (cm_timer->timer) { + service_loop_cancel_timer(cm_timer->timer); + } + + cm_timer->timer = service_loop_timer(CM_RECONNECT_INTERVAL, CM_RECONNECT_INTERVAL, bt_cm_timeout_cb, cm_timer); + + return true; +} + +static void bt_cm_process_reconnection(bt_address_t* addr, uint32_t hci_reason_code) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (hci_reason_code == HCI_ERR_CONNECTION_TERMINATED_BY_LOCAL_HOST + || hci_reason_code == HCI_ERR_REMOTE_USER_TERMINATED_CONNECTION) { + if (!bt_addr_compare(&cm_timer->peer_addr, addr)) { + bt_cm_enable_conn(); + } + + return; + } + + if (bt_cm_is_busy() || (hci_reason_code != HCI_ERR_CONNECTION_TIMEOUT)) + return; + + BT_LOGD("%s reconnect start", __func__); + bt_cm_disable_conn(); + bt_cm_profile_falgs_set(); + if (bt_cm_start_timer(addr)) + bt_cm_profile_connect(addr, BT_TRANSPORT_BREDR); +} + +void bt_cm_process_disconnect_event(bt_address_t* addr, uint8_t transport, uint32_t hci_reason_code) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + if (transport == BT_TRANSPORT_BLE) { +#ifdef CONFIG_LE_DLF_SUPPORT + bt_cm_disable_dlf(addr); +#endif + return; + } + + if (manager->reconnect_enable) + bt_cm_process_reconnection(addr, hci_reason_code); +} + bt_status_t bt_cm_enable_enhanced_mode(bt_address_t* addr, uint8_t mode) { switch (mode) { @@ -77,13 +509,4 @@ bt_status_t bt_cm_disable_enhanced_mode(bt_address_t* addr, uint8_t mode) default: return BT_STATUS_NOT_SUPPORTED; } -} - -void bt_cm_process_disconnect_event(bt_address_t* addr, uint8_t transport) -{ - if (transport == BT_TRANSPORT_BLE) { -#ifdef CONFIG_LE_DLF_SUPPORT - bt_cm_disable_dlf(addr); -#endif - } } \ No newline at end of file diff --git a/service/src/connection_manager.h b/service/src/connection_manager.h index 5c50c24f..d1cbe55e 100644 --- a/service/src/connection_manager.h +++ b/service/src/connection_manager.h @@ -26,6 +26,10 @@ void bt_cm_cleanup(void); bt_status_t bt_cm_enable_enhanced_mode(bt_address_t* peer_addr, uint8_t mode); bt_status_t bt_cm_disable_enhanced_mode(bt_address_t* peer_addr, uint8_t mode); -void bt_cm_process_disconnect_event(bt_address_t* peer_addr, uint8_t transport); +void bt_cm_process_disconnect_event(bt_address_t* addr, uint8_t transport, uint32_t hci_reason_code); +void bt_cm_disconnected(bt_address_t* addr, uint8_t profile_id); +void bt_cm_connected(bt_address_t* addr, uint8_t profile_id); +bt_status_t bt_cm_device_connect(bt_address_t* addr, uint8_t transport); +bt_status_t bt_cm_device_disconnect(bt_address_t* addr, uint8_t transport); #endif /*__BT_CONNECTION_MANAGER_H__*/ \ No newline at end of file -- Gitee From 9e92e669c8ea641e9184b25a85a36e3eb8c92572 Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 26 Dec 2024 19:42:01 +0800 Subject: [PATCH 077/498] bluetooth:connect/disconnect profile API bug: v/50798 Signed-off-by: liuxiang18 --- framework/api/bt_device.c | 10 +++++ framework/include/bt_device.h | 38 +++++++++++++++++++ framework/socket/bt_device.c | 34 +++++++++++++++++ .../ipc/socket/include/bt_message_device.h | 6 ++- service/ipc/socket/src/bt_socket_device.c | 12 ++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/framework/api/bt_device.c b/framework/api/bt_device.c index 6c51d8ad..7bedbbc4 100644 --- a/framework/api/bt_device.c +++ b/framework/api/bt_device.c @@ -109,6 +109,16 @@ bt_status_t BTSYMBOLS(bt_device_disconnect)(bt_instance_t* ins, bt_address_t* ad return adapter_disconnect(addr); } +bt_status_t BTSYMBOLS(bt_device_background_connect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return bt_cm_device_connect(addr, transport); +} + +bt_status_t BTSYMBOLS(bt_device_background_disconnect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return bt_cm_device_disconnect(addr, transport); +} + bt_status_t BTSYMBOLS(bt_device_connect_le)(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t type, diff --git a/framework/include/bt_device.h b/framework/include/bt_device.h index d1ab15a2..0e96cc83 100644 --- a/framework/include/bt_device.h +++ b/framework/include/bt_device.h @@ -718,6 +718,25 @@ if (bt_device_connect(ins, &addr) == BT_STATUS_SUCCESS) { */ bt_status_t BTSYMBOLS(bt_device_connect)(bt_instance_t* ins, bt_address_t* addr); +/** + * @brief Connect to peer device with specific Profiles. And open reconnect method. + * + * @param ins - bluetooth client instance. + * @param addr - remote device address.if addr is NULL, connect last device in bonded list. + * @param transport - transport type (0:BLE, 1:BREDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +if (bt_device_background_connect(ins, &addr, BT_TRANSPORT_BREDR) == BT_STATUS_SUCCESS) { + // connection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_background_connect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + /** * @brief Disconnect from a remote device. * @@ -738,6 +757,25 @@ if (bt_device_disconnect(ins, &addr) == BT_STATUS_SUCCESS) { */ bt_status_t BTSYMBOLS(bt_device_disconnect)(bt_instance_t* ins, bt_address_t* addr); +/** + * @brief Disconnect specific prfoiles. + * + * @param ins - bluetooth client instance. + * @param addr - remote device address. + * @param transport - transport type (0:BLE, 1:BREDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +if (bt_device_background_disconnect(ins, &addr, BT_TRANSPORT_BREDR) == BT_STATUS_SUCCESS) { + // Disconnection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_background_disconnect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + /** * @brief Connect to a remote LE device. * diff --git a/framework/socket/bt_device.c b/framework/socket/bt_device.c index 22ed7a75..b7c5dd49 100644 --- a/framework/socket/bt_device.c +++ b/framework/socket/bt_device.c @@ -288,6 +288,40 @@ bt_status_t bt_device_connect(bt_instance_t* ins, bt_address_t* addr) return packet.devs_r.status; } +bt_status_t bt_device_background_connect(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + if (addr) + memcpy(&packet.devs_pl._bt_device_background_connect, addr, sizeof(*addr)); + else + bt_addr_set_empty(&packet.devs_pl._bt_device_background_connect.addr); + packet.devs_pl._bt_device_background_connect.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_BACKGROUND_CONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_background_disconnect(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_background_disconnect, addr, sizeof(*addr)); + packet.devs_pl._bt_device_background_disconnect.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_BACKGROUND_DISCONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + bt_status_t bt_device_disconnect(bt_instance_t* ins, bt_address_t* addr) { bt_message_packet_t packet; diff --git a/service/ipc/socket/include/bt_message_device.h b/service/ipc/socket/include/bt_message_device.h index 4ab62801..ce9ab275 100644 --- a/service/ipc/socket/include/bt_message_device.h +++ b/service/ipc/socket/include/bt_message_device.h @@ -42,7 +42,9 @@ BT_DEVICE_MESSAGE_START, BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA, BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA, BT_DEVICE_CONNECT, + BT_DEVICE_BACKGROUND_CONNECT, BT_DEVICE_DISCONNECT, + BT_DEVICE_BACKGROUND_DISCONNECT, BT_DEVICE_CONNECT_LE, BT_DEVICE_DISCONNECT_LE, BT_DEVICE_CONNECT_REQUEST_REPLY, @@ -132,7 +134,9 @@ BT_DEVICE_MESSAGE_START, _bt_device_is_encrypted, _bt_device_is_bond_initiate_local, _bt_device_get_bond_state, - _bt_device_is_bonded; + _bt_device_is_bonded, + _bt_device_background_connect, + _bt_device_background_disconnect; struct { bt_address_t addr; diff --git a/service/ipc/socket/src/bt_socket_device.c b/service/ipc/socket/src/bt_socket_device.c index add49102..6942d4c7 100644 --- a/service/ipc/socket/src/bt_socket_device.c +++ b/service/ipc/socket/src/bt_socket_device.c @@ -246,6 +246,18 @@ void bt_socket_server_device_process(service_poll_t* poll, &packet->devs_pl._bt_device_addr.addr); break; } + case BT_DEVICE_BACKGROUND_CONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_background_connect)(ins, + &packet->devs_pl._bt_device_background_connect.addr, + packet->devs_pl._bt_device_background_connect.transport); + break; + } + case BT_DEVICE_BACKGROUND_DISCONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_background_disconnect)(ins, + &packet->devs_pl._bt_device_background_disconnect.addr, + packet->devs_pl._bt_device_background_disconnect.transport); + break; + } case BT_DEVICE_CONNECT_LE: { packet->devs_r.status = BTSYMBOLS(bt_device_connect_le)(ins, &packet->devs_pl._bt_device_connect_le.addr, -- Gitee From 0f364e099fcd6c9af1a9210df37a1a214dcd4ebd Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Wed, 15 Jan 2025 15:22:43 +0800 Subject: [PATCH 078/498] bluetooth: fix memory leaks-a2dp_conn_timer. bug: v/50798 Signed-off-by: liuxiang18 --- service/src/connection_manager.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/service/src/connection_manager.c b/service/src/connection_manager.c index 4667ff22..99f178f4 100644 --- a/service/src/connection_manager.c +++ b/service/src/connection_manager.c @@ -187,6 +187,11 @@ static bt_status_t bt_cm_profile_connect(bt_address_t* addr, uint8_t transport) /* delay 500ms to ensure HFP connection is established first*/ manager->connect_a2dp_flag = true; memcpy(&manager->connecting_addr, addr, sizeof(bt_address_t)); + if (manager->a2dp_conn_timer) { + service_loop_cancel_timer(manager->a2dp_conn_timer); + manager->a2dp_conn_timer = NULL; + } + manager->a2dp_conn_timer = service_loop_timer_no_repeating(PROFILE_CONNECT_INTERVAL, bt_cm_connect_a2dp_cb, manager); } else if (manager->profile_flags & FLAG_A2DP_SINK) { /* connect A2DP_SINK */ #ifdef CONFIG_BLUETOOTH_A2DP_SINK -- Gitee From 12b6e4b3116259aae9df3da94f5de7b9f76af59f Mon Sep 17 00:00:00 2001 From: liuxiang18 Date: Thu, 16 Jan 2025 20:08:19 +0800 Subject: [PATCH 079/498] Bluetooth: Fix the problem of missing "break" statements in the switch case. bug: v/50798 Signed-off-by: liuxiang18 --- service/src/connection_manager.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/service/src/connection_manager.c b/service/src/connection_manager.c index 99f178f4..98b1ca16 100644 --- a/service/src/connection_manager.c +++ b/service/src/connection_manager.c @@ -373,12 +373,14 @@ void bt_cm_connected(bt_address_t* addr, uint8_t profile_id) switch (profile_id) { case PROFILE_HFP_HF: { bt_cm_clear_flags(FLAG_HFP_HF); + break; } case PROFILE_A2DP_SINK: { bt_cm_clear_flags(FLAG_A2DP_SINK); service_loop_cancel_timer(manager->a2dp_conn_timer); bt_addr_set_empty(&manager->connecting_addr); manager->a2dp_conn_timer = NULL; + break; } default: break; @@ -405,6 +407,7 @@ void bt_cm_disconnected(bt_address_t* addr, uint8_t profile_id) service_loop_cancel_timer(manager->a2dp_conn_timer); bt_addr_set_empty(&manager->connecting_addr); manager->a2dp_conn_timer = NULL; + break; } default: break; -- Gitee From e10306b0ce990b82f6dc67f9d78ad47ff9317172 Mon Sep 17 00:00:00 2001 From: zhangyuan20 Date: Thu, 6 Mar 2025 21:49:15 +0800 Subject: [PATCH 080/498] Android-Vela: add clcc interface and callback bug: v/60610 Signed-off-by: zhangyuan20 --- framework/api/bt_hfp_ag.c | 9 +++ framework/include/bt_hfp_ag.h | 63 +++++++++++++++++++ framework/socket/bt_hfp_ag.c | 28 +++++++++ .../ipc/socket/include/bt_message_hfp_ag.h | 17 +++++ service/ipc/socket/src/bt_socket_hfp_ag.c | 27 ++++++++ service/profiles/hfp_ag/hfp_ag_service.c | 13 ++++ .../profiles/hfp_ag/hfp_ag_state_machine.c | 4 ++ service/profiles/include/hfp_ag_service.h | 4 ++ 8 files changed, 165 insertions(+) diff --git a/framework/api/bt_hfp_ag.c b/framework/api/bt_hfp_ag.c index 1d796124..63c472bc 100644 --- a/framework/api/bt_hfp_ag.c +++ b/framework/api/bt_hfp_ag.c @@ -157,4 +157,13 @@ bt_status_t BTSYMBOLS(bt_hfp_ag_send_vendor_specific_at_command)(bt_instance_t* hfp_ag_interface_t* profile = get_profile_service(); return profile->send_vendor_specific_at_command(addr, command, value); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_send_clcc_response)(bt_instance_t* ins, bt_address_t* addr, + uint32_t index, hfp_call_direction_t dir, hfp_ag_call_state_t call, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->send_clcc_response(addr, index, dir, call, mode, mpty, type, number); } \ No newline at end of file diff --git a/framework/include/bt_hfp_ag.h b/framework/include/bt_hfp_ag.h index 2c73f68a..cbf9998e 100644 --- a/framework/include/bt_hfp_ag.h +++ b/framework/include/bt_hfp_ag.h @@ -29,6 +29,8 @@ extern "C" { #define BTSYMBOLS(s) s #endif +#define HFP_PHONE_NUMBER_MAX 80 + /** * @cond */ @@ -305,6 +307,22 @@ typedef void (*hfp_ag_at_cmd_received_callback)(void* cookie, bt_address_t* addr */ typedef void (*hfp_ag_vend_spec_at_cmd_received_callback)(void* cookie, bt_address_t* addr, const char* command, uint16_t company_id, const char* value); +/** + * @brief HFP CLCC command received callback + * + * @param cookie - callback cookie. + * @param addr - address of peer HF device. + * + * **Example:** + * @code + void hfp_ag_clcc_cmd_received_callback(void* cookie, bt_address_t* addr) + { + printf("hfp_ag_clcc_cmd_received_callback\n"); + } + * @endcode + */ +typedef void (*hfp_ag_clcc_cmd_received_callback)(void* cookie, bt_address_t* addr); + /** * @cond */ @@ -327,6 +345,7 @@ typedef struct hfp_ag_dial_call_callback dial_call_cb; hfp_ag_at_cmd_received_callback at_cmd_cb; hfp_ag_vend_spec_at_cmd_received_callback vender_specific_at_cmd_cb; + hfp_ag_clcc_cmd_received_callback clcc_cmd_cb; } hfp_ag_callbacks_t; /** @@ -869,6 +888,50 @@ int app_send_specific_at_command(bt_instance_t* ins, bt_address_t* addr); * @endcode */ bt_status_t BTSYMBOLS(bt_hfp_ag_send_vendor_specific_at_command)(bt_instance_t* ins, bt_address_t* addr, const char* command, const char* value); + +/** + * @brief Send CLCC Response + * + * This function shall be invoked for each call when there are multiple calls. When all calls are + * transmitted, the application shall invoke bt_hfp_ag_clcc_response(ins, addr, 0, 0, 0, 0, NULL) + * to finalize a query procedure. + * + * @param ins - bluetooth client instance. + * @param addr - address of peer HF device. + * @param index - index of the call. + * @param dir - direction of the call. + * @param state - state of the call. + * @param mode - mode of the call. + * @param mpty - whether the call is multi party. + * @param type - type of the call. + * @param number - phone number of the call. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int bt_hfp_ag_send_clcc_response(bt_instance_t* ins, bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + bt_status_t status; + uint32_t index = 1; + hfp_call_direction_t dir = HFP_CALL_DIRECTION_INCOMING; + hfp_ag_call_state_t state = HFP_AG_CALL_STATE_INCOMING; + hfp_call_mode_t mode = HFP_CALL_MODE_VOICE; + hfp_call_mpty_type_t mpty = HFP_CALL_MPTY_TYPE_SINGLE; + hfp_call_addrtype_t type = HFP_CALL_ADDRTYPE_NATIONAL; + char number[] = "12345678900"; + status = bt_hfp_ag_send_clcc_response(ins, addr, index, dir, state, mode, mpty, type, number); + if (status != BT_STATUS_SUCCESS) + printf("send clcc response failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_send_clcc_response)(bt_instance_t* ins, bt_address_t* addr, + uint32_t index, hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number); #ifdef __cplusplus } #endif diff --git a/framework/socket/bt_hfp_ag.c b/framework/socket/bt_hfp_ag.c index 505ffacd..71fadc30 100644 --- a/framework/socket/bt_hfp_ag.c +++ b/framework/socket/bt_hfp_ag.c @@ -341,5 +341,33 @@ bt_status_t bt_hfp_ag_send_vendor_specific_at_command(bt_instance_t* ins, bt_add if (status != BT_STATUS_SUCCESS) return status; + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_send_clcc_response(bt_instance_t* ins, bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.index = index; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.dir = dir; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.state = state; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.mode = mode; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.mpty = mpty; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.type = type; + + if (number) { + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.number, number, sizeof(packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.number)); + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_SEND_CLCC_RESPONSE); + if (status != BT_STATUS_SUCCESS) + return status; + return packet.hfp_ag_r.status; } \ No newline at end of file diff --git a/service/ipc/socket/include/bt_message_hfp_ag.h b/service/ipc/socket/include/bt_message_hfp_ag.h index 23737093..4bd5b3d0 100644 --- a/service/ipc/socket/include/bt_message_hfp_ag.h +++ b/service/ipc/socket/include/bt_message_hfp_ag.h @@ -34,6 +34,7 @@ BT_HFP_AG_MESSAGE_START, BT_HFP_AG_VOLUME_CONTROL, BT_HFP_AG_SEND_AT_COMMAND, BT_HFP_AG_SEND_VENDOR_SPECIFIC_AT_COMMAND, + BT_HFP_AG_SEND_CLCC_RESPONSE, BT_HFP_AG_MESSAGE_END, #endif @@ -50,6 +51,7 @@ BT_HFP_AG_MESSAGE_START, BT_HFP_AG_ON_DIAL_CALL, BT_HFP_AG_ON_AT_COMMAND_RECEIVED, BT_HFP_AG_ON_VENDOR_SPECIFIC_AT_COMMAND_RECEIVED, + BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED, BT_HFP_AG_CALLBACK_END, #endif @@ -123,6 +125,17 @@ BT_HFP_AG_MESSAGE_START, uint8_t pad1[(HFP_COMPANY_PREFIX_LEN_MAX + 1 + 3) / 4 * 4 - (HFP_COMPANY_PREFIX_LEN_MAX + 1)]; char value[HFP_AT_LEN_MAX + 1]; } _bt_hfp_ag_send_vendor_specific_at_cmd; + + struct { + bt_address_t addr; + uint32_t index; + uint8_t dir; + uint8_t state; + uint8_t mode; + uint8_t mpty; + uint8_t type; + char number[HFP_PHONE_NUMBER_MAX + 1]; + } _bt_hfp_ag_send_clcc_response; } bt_message_hfp_ag_t; typedef union { @@ -177,6 +190,10 @@ BT_HFP_AG_MESSAGE_START, uint8_t pad1[(HFP_COMPANY_PREFIX_LEN_MAX + 1 + 3) / 4 * 4 - (HFP_COMPANY_PREFIX_LEN_MAX + 1)]; char value[HFP_AT_LEN_MAX + 1]; } _on_vend_spec_at_cmd_received; + + struct { + bt_address_t addr; + } _on_clcc_cmd_received; } bt_message_hfp_ag_callbacks_t; #ifdef __cplusplus diff --git a/service/ipc/socket/src/bt_socket_hfp_ag.c b/service/ipc/socket/src/bt_socket_hfp_ag.c index 568506a2..c27b907a 100644 --- a/service/ipc/socket/src/bt_socket_hfp_ag.c +++ b/service/ipc/socket/src/bt_socket_hfp_ag.c @@ -192,6 +192,16 @@ static void on_vendor_specific_at_cmd_received_cb(void* cookie, bt_address_t* ad bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_VENDOR_SPECIFIC_AT_COMMAND_RECEIVED); } +static void on_clcc_cmd_received_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_clcc_cmd_received.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED); +} + const static hfp_ag_callbacks_t g_hfp_ag_socket_cbs = { .connection_state_cb = on_connection_state_changed_cb, .audio_state_cb = on_audio_state_changed_cb, @@ -204,6 +214,7 @@ const static hfp_ag_callbacks_t g_hfp_ag_socket_cbs = { .dial_call_cb = on_dial_call_cb, .at_cmd_cb = on_at_cmd_received_cb, .vender_specific_at_cmd_cb = on_vendor_specific_at_cmd_received_cb, + .clcc_cmd_cb = on_clcc_cmd_received_cb, }; /**************************************************************************** @@ -323,6 +334,17 @@ void bt_socket_server_hfp_ag_process(service_poll_t* poll, int fd, packet->hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.cmd, packet->hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.value); break; + case BT_HFP_AG_SEND_CLCC_RESPONSE: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_send_clcc_response)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_send_at_cmd.addr, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.index, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.dir, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.state, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.mode, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.mpty, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.type, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.number[0] ? packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.number : NULL); + break; default: break; } @@ -399,6 +421,11 @@ int bt_socket_client_hfp_ag_callback(service_poll_t* poll, packet->hfp_ag_cb._on_vend_spec_at_cmd_received.company_id, packet->hfp_ag_cb._on_vend_spec_at_cmd_received.value); break; + case BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + clcc_cmd_cb, + &packet->hfp_ag_cb._on_clcc_cmd_received.addr); + break; default: return BT_STATUS_PARM_INVALID; } diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c index f6887f18..82356bfa 100644 --- a/service/profiles/hfp_ag/hfp_ag_service.c +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -689,6 +689,13 @@ bt_status_t hfp_ag_send_vendor_specific_at_command(bt_address_t* addr, const cha return hfp_ag_send_message(msg); } +bt_status_t hfp_ag_send_clcc_response(bt_address_t* addr, uint32_t index, hfp_call_direction_t dir, + hfp_ag_call_state_t state, hfp_call_mode_t mode, hfp_call_mpty_type_t mpty, + hfp_call_addrtype_t type, const char* number) +{ + return bt_sal_hfp_ag_clcc_response(addr, index, dir, state, mode, mpty, type, number); +} + static const hfp_ag_interface_t agInterface = { .size = sizeof(agInterface), .register_callbacks = hfp_ag_register_callbacks, @@ -710,6 +717,7 @@ static const hfp_ag_interface_t agInterface = { .dial_response = hfp_ag_dial_result, .send_at_command = hfp_ag_send_at_command, .send_vendor_specific_at_command = hfp_ag_send_vendor_specific_at_command, + .send_clcc_response = hfp_ag_send_clcc_response, }; static const void* get_ag_profile_interface(void) @@ -786,6 +794,11 @@ void ag_service_notify_vendor_specific_cmd(bt_address_t* addr, const char* comma AG_CALLBACK_FOREACH(g_ag_service.callbacks, vender_specific_at_cmd_cb, addr, command, company_id, value); } +void ag_service_notify_clcc_cmd(bt_address_t* addr) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, clcc_cmd_cb, addr); +} void hfp_ag_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state, profile_connection_reason_t reason, uint32_t remote_features) { diff --git a/service/profiles/hfp_ag/hfp_ag_state_machine.c b/service/profiles/hfp_ag/hfp_ag_state_machine.c index b86c3c51..155bea7f 100644 --- a/service/profiles/hfp_ag/hfp_ag_state_machine.c +++ b/service/profiles/hfp_ag/hfp_ag_state_machine.c @@ -713,8 +713,12 @@ static bool default_process_event(state_machine_t* sm, uint32_t event, void* p_d HFP_CALL_ADDRTYPE_UNKNOWN, HFP_FAKE_NUMBER); bt_sal_hfp_ag_clcc_response(&agsm->addr, 0, 0, 0, 0, 0, 0, NULL); } else { +#ifdef CONFIG_LIB_DBUS /* system call interface */ tele_service_query_current_call(&agsm->addr); +#else + ag_service_notify_clcc_cmd(&agsm->addr); +#endif } break; case AG_STACK_EVENT_AT_COPS_REQUEST: { diff --git a/service/profiles/include/hfp_ag_service.h b/service/profiles/include/hfp_ag_service.h index 7a4b2f9b..05116310 100644 --- a/service/profiles/include/hfp_ag_service.h +++ b/service/profiles/include/hfp_ag_service.h @@ -84,6 +84,7 @@ void ag_service_notify_call_hangup(bt_address_t* addr); void ag_service_notify_call_dial(bt_address_t* addr, const char* number); void ag_service_notify_cmd_received(bt_address_t* addr, const char* at_cmd); void ag_service_notify_vendor_specific_cmd(bt_address_t* addr, const char* command, uint16_t company_id, const char* value); +void ag_service_notify_clcc_cmd(bt_address_t* addr); /* * telephony @@ -126,6 +127,9 @@ typedef struct ag_interface { bt_status_t (*dial_response)(uint8_t result); bt_status_t (*send_at_command)(bt_address_t* addr, const char* at_command); bt_status_t (*send_vendor_specific_at_command)(bt_address_t* addr, const char* command, const char* value); + bt_status_t (*send_clcc_response)(bt_address_t* addr, uint32_t index, hfp_call_direction_t dir, + hfp_ag_call_state_t state, hfp_call_mode_t mode, hfp_call_mpty_type_t mpty, + hfp_call_addrtype_t type, const char* number); } hfp_ag_interface_t; /* -- Gitee From 20f5496c53f35eb366313126b734e10434fe29d9 Mon Sep 17 00:00:00 2001 From: chengkai Date: Tue, 13 May 2025 14:51:38 +0800 Subject: [PATCH 081/498] bluetooth: fix not update remote name bug: v/59741 rootcause: it would not request remote when local has bonded info, then add remote name request when acl connected. Signed-off-by: chengkai --- service/stacks/zephyr/sal_adapter_classic_interface.c | 1 + 1 file changed, 1 insertion(+) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index 3d6fafe5..a1844ab5 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -227,6 +227,7 @@ static void zblue_on_connected(struct bt_conn* conn, uint8_t err) }; zblue_conn_get_addr(conn, &state.addr); + bt_sal_get_remote_name(BT_TRANSPORT_BREDR, &state.addr); adapter_on_connection_state_changed(&state); } -- Gitee From 65db69ebe658594530dcafc3470fc339636dee84 Mon Sep 17 00:00:00 2001 From: chengkai Date: Tue, 13 May 2025 14:46:26 +0800 Subject: [PATCH 082/498] bluetooth: fix not report br/edr disconnect reason bug: v/59936 rootcause: add disconnect reason report Signed-off-by: chengkai --- service/stacks/zephyr/sal_adapter_classic_interface.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/stacks/zephyr/sal_adapter_classic_interface.c b/service/stacks/zephyr/sal_adapter_classic_interface.c index a1844ab5..19c76503 100644 --- a/service/stacks/zephyr/sal_adapter_classic_interface.c +++ b/service/stacks/zephyr/sal_adapter_classic_interface.c @@ -235,7 +235,8 @@ static void zblue_on_disconnected(struct bt_conn* conn, uint8_t reason) { acl_state_param_t state = { .transport = BT_TRANSPORT_BREDR, - .connection_state = CONNECTION_STATE_DISCONNECTED + .connection_state = CONNECTION_STATE_DISCONNECTED, + .hci_reason_code = reason, }; zblue_conn_get_addr(conn, &state.addr); -- Gitee From c8409186f3106dd68b0ed67048b2d3ee2f33ed13 Mon Sep 17 00:00:00 2001 From: Haishen Zhang Date: Wed, 26 Mar 2025 21:56:49 +0800 Subject: [PATCH 083/498] Update Gradle and AGP bug: v/57133 Upgrade AGP dependency from 8.7.2 to 8.9.0 Upgrade Gradle version to 8.11.1 --- tools/test_suite/android/build.gradle | 4 ++-- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/test_suite/android/build.gradle b/tools/test_suite/android/build.gradle index 71bb1bdb..adb7bdbb 100755 --- a/tools/test_suite/android/build.gradle +++ b/tools/test_suite/android/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.7.2' apply false - id 'com.android.library' version '8.7.2' apply false + id 'com.android.application' version '8.9.0' apply false + id 'com.android.library' version '8.9.0' apply false } tasks.register('clean', Delete) { diff --git a/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties index 09523c0e..e2847c82 100755 --- a/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties +++ b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -- Gitee From d104c757538c466bf8cc1bbeedf8a474c50d1c1e Mon Sep 17 00:00:00 2001 From: Haishen Zhang Date: Thu, 27 Mar 2025 09:49:16 +0800 Subject: [PATCH 084/498] BTS: remove "android:screenOrientation" bug: v/57133 The element should not be locked to any orientation so that users can take advantage of the multi-window environments and larger screens available on Android. --- .../android/app/src/main/AndroidManifest.xml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tools/test_suite/android/app/src/main/AndroidManifest.xml b/tools/test_suite/android/app/src/main/AndroidManifest.xml index cdef959d..6802e4dd 100755 --- a/tools/test_suite/android/app/src/main/AndroidManifest.xml +++ b/tools/test_suite/android/app/src/main/AndroidManifest.xml @@ -29,23 +29,19 @@ + android:label="@string/ble_scan" /> + android:label="@string/bredr_inquiry" /> + android:label="@string/ble_central" /> + android:label="@string/ble_peripheral" /> -- Gitee From 6657a18acbf5cf83e1df6335b24df16d70de1dec Mon Sep 17 00:00:00 2001 From: Haishen Zhang Date: Fri, 28 Mar 2025 20:37:19 +0800 Subject: [PATCH 085/498] BTS: Adjust the layout of MainActivity bug: v/57133 1. Change LinearLayout to ConstraintLayout + ScrollView to make it more responsive 2. Add OnOffActivity.java and activity_on_off.xml to do Bluetooth On/Off operations 3. Add permissions in AndroidManifest.xml and request them during runtime 4. Add more debug logs 5. Remove Bluetooth On/Off operations from MainActivity.java --- .../android/app/src/main/AndroidManifest.xml | 11 +- .../LocalAdapter/OnOffActivity.java | 152 ++++++++++++++++++ .../openvela/bluetoothtest/MainActivity.java | 72 ++------- .../app/src/main/res/layout/activity_main.xml | 91 ++++++----- .../src/main/res/layout/activity_on_off.xml | 67 ++++++++ .../app/src/main/res/values/strings.xml | 10 +- .../bluetooth/BluetoothStateObserver.java | 10 ++ 7 files changed, 309 insertions(+), 104 deletions(-) create mode 100644 tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java create mode 100644 tools/test_suite/android/app/src/main/res/layout/activity_on_off.xml diff --git a/tools/test_suite/android/app/src/main/AndroidManifest.xml b/tools/test_suite/android/app/src/main/AndroidManifest.xml index 6802e4dd..1f5cc38b 100755 --- a/tools/test_suite/android/app/src/main/AndroidManifest.xml +++ b/tools/test_suite/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,9 @@ + + + @@ -28,13 +31,17 @@ + android:name=".LocalAdapter.OnOffActivity" + android:label="@string/bredr_on_off" /> + + diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java new file mode 100644 index 00000000..9f1c7c2c --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java @@ -0,0 +1,152 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.LocalAdapter; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import com.openvela.bluetooth.BluetoothStateObserver; +import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +public class OnOffActivity extends AppCompatActivity { + private final String TAG = MainActivity.class.getSimpleName(); + private final int REQUEST_ENABLE_BT = 1; + EditText textNumOfCycles; + EditText textResultDisplay; + private BluetoothStateObserver btStateObserver; + private BluetoothAdapter bluetoothAdapter; + private int timesOfCycles; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_on_off); + listenBluetoothState(); + + BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class); + bluetoothAdapter = bluetoothManager.getAdapter(); + if (bluetoothAdapter == null) { + Log.e(TAG, "onClick: Device doesn't support Bluetooth"); + return; + } + + textNumOfCycles = findViewById(R.id.textNumOfCycles); + textResultDisplay = findViewById(R.id.textResultDisplay); + + Button buttonEnable = findViewById(R.id.button_enable_bluetooth); + buttonEnable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Enable Bluetooth, timesOfCycles = " + timesOfCycles); + enableBluetooth(); + } + }); + + Button buttonDisable = findViewById(R.id.button_disable_bluetooth); + buttonDisable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Disable Bluetooth, timesOfCycles = " + timesOfCycles); + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // Time consuming operation + disableBluetooth(); + return null; + } + @Override + protected void onPostExecute(Void result) { + // Update UI + } + }.execute(); + + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + btStateObserver.unregisterReceiver(); + } + + private void listenBluetoothState() { + btStateObserver = new BluetoothStateObserver(this); + btStateObserver.registerReceiver(new BluetoothStateCallback() { + @Override + public void onEnabled() { + String str = textResultDisplay.getText().toString(); + str = "\r\nBluetoothAdapter is enabled, timesOfCycles = " + timesOfCycles +str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + // Disable Bluetooth again + if (timesOfCycles > 0) + disableBluetooth(); + } + + @Override + public void onDisabled() { + String str = textResultDisplay.getText().toString(); + str = "\r\nBluetoothAdapter is disabled, timesOfCycles = " + timesOfCycles + str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + // Enable Bluetooth again + if (timesOfCycles > 0) + enableBluetooth(); + + timesOfCycles--; + } + }); + } + + private boolean isBluetoothEnabled() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + private void enableBluetooth() { + startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT); + } + + private void disableBluetooth() { + bluetoothAdapter.disable(); + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java index 16dba9a8..e6116f7d 100755 --- a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java @@ -34,85 +34,43 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; import com.openvela.bluetooth.BluetoothStateObserver; import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.LocalAdapter.OnOffActivity; import com.openvela.bluetoothtest.ble.BleScanActivity; import com.openvela.bluetoothtest.ble.BlePeripheralActivity; import com.openvela.bluetoothtest.bredr.BredrInquiryActivity; public class MainActivity extends AppCompatActivity { private final String TAG = MainActivity.class.getSimpleName(); - private final int REQUEST_ENABLE_BT = 1; - - private LinearLayout llBluetoothAdapterTip; - private BluetoothStateObserver btStateObserver; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - initView(); requestBluetoothPermission(); - listenBluetoothState(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - btStateObserver.unregisterReceiver(); - } - - private void initView() { - llBluetoothAdapterTip = findViewById(R.id.ll_adapter_tip); - TextView tvAdapterStates = findViewById(R.id.tv_adapter_states); - - tvAdapterStates.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT); - } - }); } - @RequiresApi(api = Build.VERSION_CODES.S) private void requestBluetoothPermission() { - List permissions = new ArrayList<>(); - permissions.add(Manifest.permission.BLUETOOTH_SCAN); - permissions.add(Manifest.permission.BLUETOOTH_ADVERTISE); - permissions.add(Manifest.permission.BLUETOOTH_CONNECT); - permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); - permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), map -> { - if (!isBluetoothEnabled()) { - llBluetoothAdapterTip.setVisibility(View.VISIBLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + String[] necessaryBluetoothPermissioins = { + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION}; + if (necessaryBluetoothPermissioins.length > 0) { + Log.d(TAG, "Request Bluetooth permissions"); + ActivityCompat.requestPermissions(this, necessaryBluetoothPermissioins, 1); } - }).launch(permissions.toArray(new String[0])); + } } - private void listenBluetoothState() { - btStateObserver = new BluetoothStateObserver(this); - btStateObserver.registerReceiver(new BluetoothStateCallback() { - @Override - public void onEnabled() { - Log.i(TAG, "BluetoothAdapter is enabled!"); - llBluetoothAdapterTip.setVisibility(View.GONE); - } - - @Override - public void onDisabled() { - Log.i(TAG, "BluetoothAdapter is disabled!"); - llBluetoothAdapterTip.setVisibility(View.VISIBLE); - } - }); + public void entryOnOffActivity(View view) { + startActivity(new Intent(this, OnOffActivity.class)); } - - private boolean isBluetoothEnabled() { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); - } - public void entryBredrInquiryActivity(View view) { startActivity(new Intent(this, BredrInquiryActivity.class)); } diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_main.xml b/tools/test_suite/android/app/src/main/res/layout/activity_main.xml index 022a78bd..5623be14 100755 --- a/tools/test_suite/android/app/src/main/res/layout/activity_main.xml +++ b/tools/test_suite/android/app/src/main/res/layout/activity_main.xml @@ -1,54 +1,65 @@ - + tools:context=".MainActivity"> - + android:layout_height="wrap_content"> - + android:layout_marginTop="16dp" + android:onClick="entryOnOffActivity" + android:text="@string/bredr_on_off" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - + android:layout_marginTop="16dp" + android:onClick="entryBredrInquiryActivity" + android:text="@string/bredr_inquiry" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_on_off" + app:layout_constraintStart_toStartOf="@+id/button_on_off" + app:layout_constraintTop_toBottomOf="@+id/button_on_off" /> -