zephyr/subsys/bluetooth/audio/cap_common.c
Emil Gydesen d2fbeffaa9 Bluetooth: BAP: Unicast client Split start and connect
Removes the CIS connection establishment from bt_bap_stream_start
and move the behavior to a new funciton bt_bap_stream_connect.

This has 2 advantages:
1) The behavior of bt_bap_stream_start is much more clear and more aligned
with the spec's behavior for the receiver start ready opcode.
2) It is possible to connect streams in both the enabling
and the QoS configured state with bt_bap_stream_connect as
per the spec. This allows us to pass additional PTS test cases.

To implement this new behavior, samples and tests have been updated.

The CAP Initiator implementation has also been updated
to accomodate for the change in BAP, but the CAP
initiator implementation should work the same for application, except
that it's now possible to do unicast start on ASEs in any order
(https://github.com/zephyrproject-rtos/zephyr/issues/72138).

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2024-06-03 15:42:33 +02:00

423 lines
11 KiB
C

/*
* 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_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;
}
LOG_DBG("Aborting proc %d for %p: %d", active_proc.proc_type, (void *)conn, err);
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:
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_INITIATOR_UNICAST */
}
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;
}