/* * Copyright (c) 2023-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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: case BT_CAP_COMMON_PROC_TYPE_DISTRIBUTE_BROADCAST_CODE: 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; }