| /* |
| * Copyright (c) 2023-2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <zephyr/autoconf.h> |
| #include <zephyr/bluetooth/att.h> |
| #include <zephyr/bluetooth/audio/cap.h> |
| #include <zephyr/bluetooth/audio/csip.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/sys/util.h> |
| |
| #include "cap_internal.h" |
| #include "csip_internal.h" |
| |
| LOG_MODULE_REGISTER(bt_cap_common, CONFIG_BT_CAP_COMMON_LOG_LEVEL); |
| |
| #include "common/bt_str.h" |
| |
| static struct bt_cap_common_client bt_cap_common_clients[CONFIG_BT_MAX_CONN]; |
| static const struct bt_uuid *cas_uuid = BT_UUID_CAS; |
| static struct bt_cap_common_proc active_proc; |
| |
| struct bt_cap_common_proc *bt_cap_common_get_active_proc(void) |
| { |
| return &active_proc; |
| } |
| |
| void bt_cap_common_clear_active_proc(void) |
| { |
| (void)memset(&active_proc, 0, sizeof(active_proc)); |
| } |
| |
| void bt_cap_common_start_proc(enum bt_cap_common_proc_type proc_type, size_t proc_cnt) |
| { |
| LOG_DBG("Setting proc to %d for %zu streams", proc_type, proc_cnt); |
| |
| atomic_set_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ACTIVE); |
| active_proc.proc_cnt = proc_cnt; |
| active_proc.proc_type = proc_type; |
| active_proc.proc_done_cnt = 0U; |
| active_proc.proc_initiated_cnt = 0U; |
| } |
| |
| #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) |
| void bt_cap_common_set_subproc(enum bt_cap_common_subproc_type subproc_type) |
| { |
| LOG_DBG("Setting subproc to %d", subproc_type); |
| |
| active_proc.proc_done_cnt = 0U; |
| active_proc.proc_initiated_cnt = 0U; |
| active_proc.subproc_type = subproc_type; |
| } |
| |
| bool bt_cap_common_proc_is_type(enum bt_cap_common_proc_type proc_type) |
| { |
| return active_proc.proc_type == proc_type; |
| } |
| |
| bool bt_cap_common_subproc_is_type(enum bt_cap_common_subproc_type subproc_type) |
| { |
| return active_proc.subproc_type == subproc_type; |
| } |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| |
| struct bt_conn *bt_cap_common_get_member_conn(enum bt_cap_set_type type, |
| const union bt_cap_set_member *member) |
| { |
| if (member == NULL) { |
| return NULL; |
| } |
| |
| if (type == BT_CAP_SET_TYPE_CSIP) { |
| struct bt_cap_common_client *client; |
| |
| /* We have verified that `client` won't be NULL in |
| * `valid_change_volume_param`. |
| */ |
| |
| client = bt_cap_common_get_client_by_csis(member->csip); |
| if (client == NULL) { |
| return NULL; |
| } |
| |
| return client->conn; |
| } |
| |
| return member->member; |
| } |
| |
| bool bt_cap_common_proc_is_active(void) |
| { |
| return atomic_test_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ACTIVE); |
| } |
| |
| bool bt_cap_common_proc_is_aborted(void) |
| { |
| return atomic_test_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ABORTED); |
| } |
| |
| bool bt_cap_common_proc_all_handled(void) |
| { |
| return active_proc.proc_done_cnt == active_proc.proc_initiated_cnt; |
| } |
| |
| bool bt_cap_common_proc_is_done(void) |
| { |
| return active_proc.proc_done_cnt == active_proc.proc_cnt; |
| } |
| |
| void bt_cap_common_abort_proc(struct bt_conn *conn, int err) |
| { |
| if (bt_cap_common_proc_is_aborted()) { |
| /* no-op */ |
| return; |
| } |
| |
| #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) |
| LOG_DBG("Aborting proc %d with subproc %d for %p: %d", active_proc.proc_type, |
| active_proc.subproc_type, (void *)conn, err); |
| #else /* !CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| LOG_DBG("Aborting proc %d for %p: %d", active_proc.proc_type, (void *)conn, err); |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| |
| active_proc.err = err; |
| active_proc.failed_conn = conn; |
| atomic_set_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ABORTED); |
| } |
| |
| #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) |
| static bool active_proc_is_initiator(void) |
| { |
| switch (active_proc.proc_type) { |
| case BT_CAP_COMMON_PROC_TYPE_START: |
| case BT_CAP_COMMON_PROC_TYPE_UPDATE: |
| case BT_CAP_COMMON_PROC_TYPE_STOP: |
| return true; |
| default: |
| return false; |
| } |
| } |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| |
| #if defined(CONFIG_BT_CAP_COMMANDER) |
| static bool active_proc_is_commander(void) |
| { |
| switch (active_proc.proc_type) { |
| case BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE: |
| case BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE: |
| case BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE: |
| case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE: |
| case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE: |
| case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START: |
| case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP: |
| return true; |
| default: |
| return false; |
| } |
| } |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| |
| bool bt_cap_common_conn_in_active_proc(const struct bt_conn *conn) |
| { |
| if (!bt_cap_common_proc_is_active()) { |
| return false; |
| } |
| |
| for (size_t i = 0U; i < active_proc.proc_initiated_cnt; i++) { |
| #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) |
| if (active_proc_is_initiator()) { |
| if (active_proc.proc_param.initiator[i].stream->bap_stream.conn == conn) { |
| return true; |
| } |
| } |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| #if defined(CONFIG_BT_CAP_COMMANDER) |
| if (active_proc_is_commander()) { |
| if (active_proc.proc_param.commander[i].conn == conn) { |
| return true; |
| } |
| } |
| #endif /* CONFIG_BT_CAP_COMMANDER */ |
| } |
| |
| return false; |
| } |
| |
| bool bt_cap_common_stream_in_active_proc(const struct bt_cap_stream *cap_stream) |
| { |
| if (!bt_cap_common_proc_is_active()) { |
| return false; |
| } |
| |
| #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) |
| if (active_proc_is_initiator()) { |
| for (size_t i = 0U; i < active_proc.proc_cnt; i++) { |
| if (active_proc.proc_param.initiator[i].stream == cap_stream) { |
| return true; |
| } |
| } |
| } |
| #endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ |
| |
| return false; |
| } |
| |
| void bt_cap_common_disconnected(struct bt_conn *conn, uint8_t reason) |
| { |
| struct bt_cap_common_client *client = bt_cap_common_get_client_by_acl(conn); |
| |
| if (client->conn != NULL) { |
| bt_conn_unref(client->conn); |
| } |
| (void)memset(client, 0, sizeof(*client)); |
| |
| if (bt_cap_common_conn_in_active_proc(conn)) { |
| bt_cap_common_abort_proc(conn, -ENOTCONN); |
| } |
| } |
| |
| BT_CONN_CB_DEFINE(conn_callbacks) = { |
| .disconnected = bt_cap_common_disconnected, |
| }; |
| |
| struct bt_cap_common_client *bt_cap_common_get_client_by_acl(const struct bt_conn *acl) |
| { |
| if (acl == NULL) { |
| return NULL; |
| } |
| |
| return &bt_cap_common_clients[bt_conn_index(acl)]; |
| } |
| |
| struct bt_cap_common_client * |
| bt_cap_common_get_client_by_csis(const struct bt_csip_set_coordinator_csis_inst *csis_inst) |
| { |
| if (csis_inst == NULL) { |
| return NULL; |
| } |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(bt_cap_common_clients); i++) { |
| struct bt_cap_common_client *client = &bt_cap_common_clients[i]; |
| |
| if (client->csis_inst == csis_inst) { |
| return client; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void cap_common_discover_complete(struct bt_conn *conn, int err, |
| const struct bt_csip_set_coordinator_set_member *member, |
| const struct bt_csip_set_coordinator_csis_inst *csis_inst) |
| { |
| struct bt_cap_common_client *client; |
| |
| client = bt_cap_common_get_client_by_acl(conn); |
| if (client != NULL && client->discover_cb_func != NULL) { |
| const bt_cap_common_discover_func_t cb_func = client->discover_cb_func; |
| |
| client->discover_cb_func = NULL; |
| cb_func(conn, err, member, csis_inst); |
| } |
| } |
| |
| static void csis_client_discover_cb(struct bt_conn *conn, |
| const struct bt_csip_set_coordinator_set_member *member, |
| int err, size_t set_count) |
| { |
| struct bt_cap_common_client *client; |
| |
| if (err != 0) { |
| LOG_DBG("CSIS client discover failed: %d", err); |
| |
| cap_common_discover_complete(conn, err, NULL, NULL); |
| |
| return; |
| } |
| |
| client = bt_cap_common_get_client_by_acl(conn); |
| client->csis_inst = |
| bt_csip_set_coordinator_csis_inst_by_handle(conn, client->csis_start_handle); |
| |
| if (member == NULL || set_count == 0 || client->csis_inst == NULL) { |
| LOG_ERR("Unable to find CSIS for CAS"); |
| |
| cap_common_discover_complete(conn, -ENODATA, NULL, NULL); |
| } else { |
| LOG_DBG("Found CAS with CSIS"); |
| cap_common_discover_complete(conn, 0, member, client->csis_inst); |
| } |
| } |
| |
| static uint8_t bt_cap_common_discover_included_cb(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| if (attr == NULL) { |
| LOG_DBG("CAS CSIS include not found"); |
| |
| cap_common_discover_complete(conn, 0, NULL, NULL); |
| } else { |
| const struct bt_gatt_include *included_service = attr->user_data; |
| struct bt_cap_common_client *client = |
| CONTAINER_OF(params, struct bt_cap_common_client, param); |
| |
| /* If the remote CAS includes CSIS, we first check if we |
| * have already discovered it, and if so we can just retrieve it |
| * and forward it to the application. If not, then we start |
| * CSIS discovery |
| */ |
| client->csis_start_handle = included_service->start_handle; |
| client->csis_inst = bt_csip_set_coordinator_csis_inst_by_handle( |
| conn, client->csis_start_handle); |
| if (client->csis_inst == NULL) { |
| static struct bt_csip_set_coordinator_cb csis_client_cb = { |
| .discover = csis_client_discover_cb, |
| }; |
| static bool csis_cbs_registered; |
| int err; |
| |
| LOG_DBG("CAS CSIS not known, discovering"); |
| |
| if (!csis_cbs_registered) { |
| bt_csip_set_coordinator_register_cb(&csis_client_cb); |
| csis_cbs_registered = true; |
| } |
| |
| err = bt_csip_set_coordinator_discover(conn); |
| if (err != 0) { |
| LOG_DBG("Discover failed (err %d)", err); |
| cap_common_discover_complete(conn, err, NULL, NULL); |
| } |
| } else { |
| const struct bt_csip_set_coordinator_set_member *member = |
| bt_csip_set_coordinator_set_member_by_conn(conn); |
| |
| LOG_DBG("Found CAS with CSIS"); |
| |
| cap_common_discover_complete(conn, 0, member, client->csis_inst); |
| } |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static uint8_t bt_cap_common_discover_cas_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| if (attr == NULL) { |
| cap_common_discover_complete(conn, -ENODATA, NULL, NULL); |
| } else { |
| const struct bt_gatt_service_val *prim_service = attr->user_data; |
| struct bt_cap_common_client *client = |
| CONTAINER_OF(params, struct bt_cap_common_client, param); |
| int err; |
| |
| client->conn = bt_conn_ref(conn); |
| |
| if (attr->handle == prim_service->end_handle) { |
| LOG_DBG("Found CAS without CSIS"); |
| cap_common_discover_complete(conn, 0, NULL, NULL); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| LOG_DBG("Found CAS, discovering included CSIS"); |
| |
| params->uuid = NULL; |
| params->start_handle = attr->handle + 1; |
| params->end_handle = prim_service->end_handle; |
| params->type = BT_GATT_DISCOVER_INCLUDE; |
| params->func = bt_cap_common_discover_included_cb; |
| |
| err = bt_gatt_discover(conn, params); |
| if (err != 0) { |
| LOG_DBG("Discover failed (err %d)", err); |
| |
| cap_common_discover_complete(conn, err, NULL, NULL); |
| } |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| int bt_cap_common_discover(struct bt_conn *conn, bt_cap_common_discover_func_t func) |
| { |
| struct bt_gatt_discover_params *param; |
| struct bt_cap_common_client *client; |
| int err; |
| |
| client = bt_cap_common_get_client_by_acl(conn); |
| if (client->discover_cb_func != NULL) { |
| return -EBUSY; |
| } |
| |
| param = &client->param; |
| param->func = bt_cap_common_discover_cas_cb; |
| param->uuid = cas_uuid; |
| param->type = BT_GATT_DISCOVER_PRIMARY; |
| param->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| param->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; |
| |
| client->discover_cb_func = func; |
| |
| err = bt_gatt_discover(conn, param); |
| if (err != 0) { |
| client->discover_cb_func = NULL; |
| |
| /* Report expected possible errors */ |
| if (err == -ENOTCONN || err == -ENOMEM) { |
| return err; |
| } |
| |
| LOG_DBG("Unexpected err %d from bt_gatt_discover", err); |
| return -ENOEXEC; |
| } |
| |
| return 0; |
| } |