zephyr/subsys/bluetooth/audio/pacs.c
Emil Gydesen 425f3f4b0a Bluetooth: Audio: Rename ctx type prohibited to none
The context type PROHIBITED has been removed from
the Bluetooth assigned numbers document. It is, however,
still used in some profiles to indicate either "No context
type supported" or "None available", and thus a rename
to "NONE" makes sense.

It is still treating similar to prohibited in some cases,
as it is not a valid context type in all cases.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2025-05-08 01:58:01 +02:00

1610 lines
44 KiB
C

/* @file
* @brief Bluetooth PACS
*/
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2022-2023 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 <sys/types.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net_buf.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include "common/bt_str.h"
#include "audio_internal.h"
#include "bap_unicast_server.h"
#include "pacs_internal.h"
LOG_MODULE_REGISTER(bt_pacs, CONFIG_BT_PACS_LOG_LEVEL);
#define PAC_NOTIFY_TIMEOUT K_MSEC(10)
#define READ_BUF_SEM_TIMEOUT K_MSEC(50)
#if defined(CONFIG_BT_PAC_SRC)
static sys_slist_t src_pac_list = SYS_SLIST_STATIC_INIT(&src_pac_list);
static uint16_t src_supported_contexts;
#if defined(CONFIG_BT_PAC_SRC_LOC)
static uint32_t pacs_src_location;
#endif /* CONFIG_BT_PAC_SRC_LOC */
#endif /* CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SNK)
static sys_slist_t snk_pac_list = SYS_SLIST_STATIC_INIT(&snk_pac_list);
static uint16_t snk_supported_contexts;
#if defined(CONFIG_BT_PAC_SNK_LOC)
static uint32_t pacs_snk_location;
#endif /* CONFIG_BT_PAC_SNK_LOC */
#endif /* CONFIG_BT_PAC_SNK */
static uint16_t src_available_contexts = BT_AUDIO_CONTEXT_TYPE_NONE;
static uint16_t snk_available_contexts = BT_AUDIO_CONTEXT_TYPE_NONE;
enum {
FLAG_ACTIVE,
FLAG_SINK_PAC_CHANGED,
FLAG_SINK_AUDIO_LOCATIONS_CHANGED,
FLAG_SOURCE_PAC_CHANGED,
FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED,
FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED,
FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED,
FLAG_NUM,
};
enum {
PACS_FLAG_REGISTERED,
PACS_FLAG_SVC_CHANGING,
PACS_FLAG_NOTIFY_RDY,
PACS_FLAG_SNK_PAC,
PACS_FLAG_SNK_LOC,
PACS_FLAG_SRC_PAC,
PACS_FLAG_SRC_LOC,
PACS_FLAG_NUM,
};
struct pacs_client {
bt_addr_le_t addr;
#if defined(CONFIG_BT_PAC_SNK)
/* Sink Available Contexts override value */
uint16_t *snk_available_contexts;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
/* Source Available Contexts override value */
uint16_t *src_available_contexts;
#endif /* CONFIG_BT_PAC_SRC */
/* Pending notification flags */
ATOMIC_DEFINE(flags, FLAG_NUM);
};
static struct pacs {
ATOMIC_DEFINE(flags, PACS_FLAG_NUM);
struct pacs_client clients[CONFIG_BT_MAX_PAIRED];
} pacs;
static K_SEM_DEFINE(read_buf_sem, 1, 1);
NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, BT_ATT_MAX_ATTRIBUTE_LEN);
static int pacs_gatt_notify(struct bt_conn *conn,
const struct bt_uuid *uuid,
const struct bt_gatt_attr *attr,
const void *data,
uint16_t len);
static void deferred_nfy_work_handler(struct k_work *work);
static K_WORK_DEFINE(deferred_nfy_work, deferred_nfy_work_handler);
struct pac_records_build_data {
struct bt_pacs_read_rsp *rsp;
struct net_buf_simple *buf;
};
static struct pacs_client *client_lookup_conn(const struct bt_conn *conn)
{
__ASSERT_NO_MSG(conn != NULL);
for (size_t i = 0; i < ARRAY_SIZE(pacs.clients); i++) {
if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) &&
bt_addr_le_eq(&pacs.clients[i].addr, bt_conn_get_dst(conn))) {
return &pacs.clients[i];
}
}
return NULL;
}
static void pacs_set_notify_bit(int bit)
{
for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) {
if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) {
atomic_set_bit(pacs.clients[i].flags, bit);
}
}
}
static bool build_pac_records(const struct bt_pacs_cap *cap, void *user_data)
{
struct pac_records_build_data *data = user_data;
const struct bt_audio_codec_cap *codec_cap = cap->codec_cap;
struct net_buf_simple *buf = data->buf;
struct net_buf_simple_state state;
struct bt_pac_codec *pac_codec;
net_buf_simple_save(buf, &state);
if (net_buf_simple_tailroom(buf) < sizeof(*pac_codec)) {
goto fail;
}
pac_codec = net_buf_simple_add(buf, sizeof(*pac_codec));
pac_codec->id = codec_cap->id;
pac_codec->cid = sys_cpu_to_le16(codec_cap->cid);
pac_codec->vid = sys_cpu_to_le16(codec_cap->vid);
if (net_buf_simple_tailroom(buf) < (sizeof(struct bt_pac_ltv_data) + codec_cap->data_len)) {
goto fail;
}
net_buf_simple_add_u8(buf, codec_cap->data_len);
net_buf_simple_add_mem(buf, codec_cap->data, codec_cap->data_len);
if (net_buf_simple_tailroom(buf) < (sizeof(struct bt_pac_ltv_data) + codec_cap->meta_len)) {
goto fail;
}
net_buf_simple_add_u8(buf, codec_cap->meta_len);
net_buf_simple_add_mem(buf, codec_cap->meta, codec_cap->meta_len);
data->rsp->num_pac++;
return true;
fail:
__ASSERT(false, "No space for %p", cap);
net_buf_simple_restore(buf, &state);
return false;
}
static void foreach_cap(sys_slist_t *list, bt_pacs_cap_foreach_func_t func,
void *user_data)
{
struct bt_pacs_cap *cap;
SYS_SLIST_FOR_EACH_CONTAINER(list, cap, _node) {
if (!func(cap, user_data)) {
break;
}
}
}
static void get_pac_records(sys_slist_t *list, struct net_buf_simple *buf)
{
struct pac_records_build_data data;
/* Reset if buffer before using */
net_buf_simple_reset(buf);
data.rsp = net_buf_simple_add(buf, sizeof(*data.rsp));
data.rsp->num_pac = 0;
data.buf = buf;
foreach_cap(list, build_pac_records, &data);
}
static void available_context_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
static enum bt_audio_context pacs_get_available_contexts_for_conn(struct bt_conn *conn,
enum bt_audio_dir dir)
{
const struct pacs_client *client;
client = client_lookup_conn(conn);
if (client == NULL) {
LOG_DBG("No client context for conn %p", (void *)conn);
return bt_pacs_get_available_contexts(dir);
}
switch (dir) {
case BT_AUDIO_DIR_SINK:
#if defined(CONFIG_BT_PAC_SNK)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC) &&
client->snk_available_contexts != NULL) {
return POINTER_TO_UINT(client->snk_available_contexts);
}
#endif /* CONFIG_BT_PAC_SNK */
break;
case BT_AUDIO_DIR_SOURCE:
#if defined(CONFIG_BT_PAC_SRC)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC) &&
client->src_available_contexts != NULL) {
return POINTER_TO_UINT(client->src_available_contexts);
}
#endif /* CONFIG_BT_PAC_SRC */
break;
}
return bt_pacs_get_available_contexts(dir);
}
static ssize_t available_contexts_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(
pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(
pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE)),
};
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &context,
sizeof(context));
}
#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)
static void supported_context_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
static uint16_t supported_context_get(enum bt_audio_dir dir)
{
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_DIR_SINK:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
return snk_supported_contexts | BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
}
break;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_DIR_SOURCE:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
return src_supported_contexts | BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
}
break;
#endif /* CONFIG_BT_PAC_SRC */
default:
break;
}
return BT_AUDIO_CONTEXT_TYPE_NONE;
}
static ssize_t supported_context_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SOURCE)),
};
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &context,
sizeof(context));
}
static int set_available_contexts(uint16_t contexts, uint16_t *available,
uint16_t supported)
{
if (contexts & ~supported) {
return -ENOTSUP;
}
if (contexts == *available) {
return 0;
}
*available = contexts;
pacs_set_notify_bit(FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED);
k_work_submit(&deferred_nfy_work);
return 0;
}
static int set_supported_contexts(uint16_t contexts, uint16_t *supported,
uint16_t *available)
{
int err;
uint16_t tmp_supported = *supported;
uint16_t tmp_available = *available;
/* Ensure unspecified is always supported */
contexts |= BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
if (*supported == contexts) {
return 0;
}
*supported = contexts;
/* Update available contexts if needed*/
if ((contexts & *available) != *available) {
err = set_available_contexts(contexts & *available, available, contexts);
if (err) {
*available = tmp_available;
*supported = tmp_supported;
return err;
}
}
if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED);
k_work_submit(&deferred_nfy_work);
}
return 0;
}
#if defined(CONFIG_BT_PAC_SNK)
static ssize_t snk_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
ssize_t ret_val;
int err;
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
err = k_sem_take(&read_buf_sem, READ_BUF_SEM_TIMEOUT);
if (err != 0) {
LOG_DBG("Failed to take read_buf_sem: %d", err);
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
get_pac_records(&snk_pac_list, &read_buf);
ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data,
read_buf.len);
k_sem_give(&read_buf_sem);
return ret_val;
}
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
static const struct bt_uuid *pacs_snk_uuid = BT_UUID_PACS_SNK;
static void snk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SNK_LOC)
static ssize_t snk_loc_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint32_t location = sys_cpu_to_le32(pacs_snk_location);
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &location,
sizeof(location));
}
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
static const struct bt_uuid *pacs_snk_loc_uuid = BT_UUID_PACS_SNK_LOC;
static void snk_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
static void set_snk_location(enum bt_audio_location audio_location)
{
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_LOC)) {
if (audio_location == pacs_snk_location) {
return;
}
pacs_snk_location = audio_location;
if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SINK_AUDIO_LOCATIONS_CHANGED);
k_work_submit(&deferred_nfy_work);
}
}
}
#else
static void set_snk_location(enum bt_audio_location location)
{
return;
}
#endif /* CONFIG_BT_PAC_SNK_LOC */
#if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE)
static ssize_t snk_loc_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *data,
uint16_t len, uint16_t offset, uint8_t flags)
{
enum bt_audio_location location;
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len != sizeof(location)) {
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
location = (enum bt_audio_location)sys_get_le32(data);
if (location > BT_AUDIO_LOCATION_MASK) {
LOG_DBG("Invalid location value: 0x%08X", location);
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
set_snk_location(location);
return len;
}
#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */
#if defined(CONFIG_BT_PAC_SRC)
static ssize_t src_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
ssize_t ret_val;
int err;
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
err = k_sem_take(&read_buf_sem, READ_BUF_SEM_TIMEOUT);
if (err != 0) {
LOG_DBG("Failed to take read_buf_sem: %d", err);
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
get_pac_records(&src_pac_list, &read_buf);
ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data,
read_buf.len);
k_sem_give(&read_buf_sem);
return ret_val;
}
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
static const struct bt_uuid *pacs_src_uuid = BT_UUID_PACS_SRC;
static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
#endif /* CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SRC_LOC)
static ssize_t src_loc_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint32_t location = sys_cpu_to_le32(pacs_src_location);
LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, len, offset);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &location,
sizeof(location));
}
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
static const struct bt_uuid *pacs_src_loc_uuid = BT_UUID_PACS_SRC_LOC;
static void src_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("attr %p value 0x%04x", attr, value);
}
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
static void set_src_location(enum bt_audio_location audio_location)
{
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_LOC)) {
if (audio_location == pacs_src_location) {
return;
}
pacs_src_location = audio_location;
if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)) {
pacs_set_notify_bit(FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED);
k_work_submit(&deferred_nfy_work);
}
}
}
#else
static void set_src_location(enum bt_audio_location location)
{
return;
}
#endif /* CONFIG_BT_PAC_SRC_LOC */
#if defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE)
static ssize_t src_loc_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *data,
uint16_t len, uint16_t offset, uint8_t flags)
{
uint32_t location;
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len != sizeof(location)) {
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
location = (enum bt_audio_location)sys_get_le32(data);
if (location > BT_AUDIO_LOCATION_MASK) {
LOG_DBG("Invalid location value: 0x%08X", location);
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
set_src_location(location);
return len;
}
#endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE */
static sys_slist_t *pacs_get_pac(enum bt_audio_dir dir)
{
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_DIR_SINK:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
return &snk_pac_list;
}
return NULL;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_DIR_SOURCE:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
return &src_pac_list;
}
return NULL;
#endif /* CONFIG_BT_PAC_SRC */
default:
return NULL;
}
}
#if defined(CONFIG_BT_PAC_SNK)
#define BT_PACS_SNK_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SNK \
BT_AUDIO_CHRC(BT_UUID_PACS_SNK, BT_PACS_SNK_PROP, BT_GATT_PERM_READ_ENCRYPT, snk_read, \
NULL, NULL), \
IF_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE, (BT_AUDIO_CCC(snk_cfg_changed),))
#define BT_PACS_SNK_LOC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (|BT_GATT_CHRC_WRITE)) \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PACS_SNK_LOC_PERM \
BT_GATT_PERM_READ_ENCRYPT \
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (|BT_GATT_PERM_WRITE_ENCRYPT))
/* declaration + value [+ cccd] */
#define PACS_SNK_PAC_CHAR_ATTR_COUNT COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE), (3), (2))
#else
#define BT_PAC_SNK
#define PACS_SNK_PAC_CHAR_ATTR_COUNT 0
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SNK_LOC)
#define BT_PACS_SNK_LOC \
BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC, BT_PACS_SNK_LOC_PROP, BT_PACS_SNK_LOC_PERM, \
snk_loc_read, \
COND_CODE_1(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (snk_loc_write), (NULL)), NULL),\
IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (BT_AUDIO_CCC(snk_loc_cfg_changed),))
/* declaration + value [+ cccd] */
#define PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT \
COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE), (3), (2))
#else
#define BT_PACS_SNK_LOC
#define PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT 0
#endif /* CONFIG_BT_PAC_SNK_LOC*/
#if defined(CONFIG_BT_PAC_SRC)
#define BT_PACS_SRC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SRC \
BT_AUDIO_CHRC(BT_UUID_PACS_SRC, BT_PACS_SRC_PROP, BT_GATT_PERM_READ_ENCRYPT, src_read, \
NULL, NULL), \
IF_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE, (BT_AUDIO_CCC(src_cfg_changed),))
#define BT_PACS_SRC_LOC_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (|BT_GATT_CHRC_WRITE)) \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PACS_SRC_LOC_PERM \
BT_GATT_PERM_READ_ENCRYPT \
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (|BT_GATT_PERM_WRITE_ENCRYPT))
/* declaration + value [+ cccd] */
#define PACS_SRC_PAC_CHAR_ATTR_COUNT COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE), (3), (2))
#else
#define BT_PAC_SRC
#define PACS_SRC_PAC_CHAR_ATTR_COUNT 0
#endif
#if defined(CONFIG_BT_PAC_SRC_LOC)
#define BT_PACS_SRC_LOC \
BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC, BT_PACS_SRC_LOC_PROP, BT_PACS_SRC_LOC_PERM, \
src_loc_read, \
COND_CODE_1(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (src_loc_write), (NULL)), NULL),\
IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (BT_AUDIO_CCC(src_loc_cfg_changed),))
/* declaration + value [+ cccd] */
#define PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT \
COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE), (3), (2))
#else
#define BT_PACS_SRC_LOC
#define PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT 0
#endif
#define BT_PAC_AVAILABLE_CONTEXT \
BT_AUDIO_CHRC(BT_UUID_PACS_AVAILABLE_CONTEXT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, available_contexts_read, NULL, NULL), \
BT_AUDIO_CCC(available_context_cfg_changed),
#define BT_PACS_SUPPORTED_CONTEXT_PROP \
BT_GATT_CHRC_READ \
IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, (|BT_GATT_CHRC_NOTIFY))
#define BT_PAC_SUPPORTED_CONTEXT \
BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT, BT_PACS_SUPPORTED_CONTEXT_PROP, \
BT_GATT_PERM_READ_ENCRYPT, supported_context_read, NULL, NULL), \
IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, \
(BT_AUDIO_CCC(supported_context_cfg_changed),))
#define BT_PACS_SERVICE_DEFINITION() { \
BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS), \
BT_PAC_SNK \
BT_PACS_SNK_LOC \
BT_PAC_SRC \
BT_PACS_SRC_LOC \
BT_PAC_AVAILABLE_CONTEXT \
BT_PAC_SUPPORTED_CONTEXT \
}
static const struct bt_gatt_attr _pacs_attrs[] = BT_PACS_SERVICE_DEFINITION();
static struct bt_gatt_attr pacs_attrs[] = BT_PACS_SERVICE_DEFINITION();
static struct bt_gatt_service pacs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(pacs_attrs);
static void configure_pacs_char(const struct bt_pacs_register_param *param)
{
const uint8_t first_attr_offset = 1U;
struct bt_gatt_attr *svc_attrs =
&pacs_svc.attrs[first_attr_offset]; /* first attribute is the service */
uint8_t attrs_to_rem = 0U;
uint8_t first_to_rem = 0U;
/* Remove the Sink PAC and Location */
#if defined(CONFIG_BT_PAC_SNK_LOC)
if (!param->snk_loc) {
first_to_rem = PACS_SNK_PAC_CHAR_ATTR_COUNT;
attrs_to_rem = PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT;
}
#endif /* CONFIG_BT_PAC_SNK_LOC */
#if defined(CONFIG_BT_PAC_SNK)
if (!param->snk_pac) {
first_to_rem = 0U;
attrs_to_rem = PACS_SNK_PAC_CHAR_ATTR_COUNT + PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT;
}
#endif /* CONFIG_BT_PAC_SNK */
if (attrs_to_rem > 0U) {
for (uint8_t i = first_to_rem + attrs_to_rem;
i < pacs_svc.attr_count - first_attr_offset; i++) {
svc_attrs[i - attrs_to_rem] = svc_attrs[i];
}
pacs_svc.attr_count -= attrs_to_rem;
}
#if defined(CONFIG_BT_PAC_SRC)
/* Set first_to_rem to the start of Source PAC Char, for cleaner offset calc */
const uint8_t src_pac_offset =
(PACS_SNK_PAC_CHAR_ATTR_COUNT + PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT) - attrs_to_rem;
attrs_to_rem = 0U;
/* Remove the Source PAC and Location */
#if defined(CONFIG_BT_PAC_SRC_LOC)
if (!param->src_loc) {
first_to_rem = src_pac_offset + PACS_SRC_PAC_CHAR_ATTR_COUNT;
attrs_to_rem = PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT;
}
#endif /* CONFIG_BT_PAC_SRC_LOC */
if (!param->src_pac) {
first_to_rem = src_pac_offset;
attrs_to_rem = PACS_SRC_PAC_CHAR_ATTR_COUNT + PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT;
}
if (attrs_to_rem > 0U) {
for (uint8_t i = first_to_rem + attrs_to_rem;
i < pacs_svc.attr_count - first_attr_offset; i++) {
svc_attrs[i - attrs_to_rem] = svc_attrs[i];
}
pacs_svc.attr_count -= attrs_to_rem;
}
#endif /* CONFIG_BT_PAC_SRC */
}
static bool valid_pacs_register_param(const struct bt_pacs_register_param *param)
{
bool any_pac_registered = false;
if (param == NULL) {
LOG_DBG("param is NULL");
return false;
}
#if defined(CONFIG_BT_PAC_SNK)
any_pac_registered |= param->snk_pac;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SNK_LOC)
if (param->snk_loc && !param->snk_pac) {
LOG_DBG("Cannot register snk_loc without snk_pac");
return false;
}
#endif /* CONFIG_BT_PAC_SNK_LOC */
#if defined(CONFIG_BT_PAC_SRC)
any_pac_registered |= param->src_pac;
#endif /* CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SRC_LOC)
if (param->src_loc && !param->src_pac) {
LOG_DBG("Cannot register src_loc without src_pac");
return false;
}
#endif /* CONFIG_BT_PAC_SRC_LOC */
if (!any_pac_registered) {
LOG_DBG("Neither snk_pac or src_pac registered");
return false;
}
return true;
}
int bt_pacs_register(const struct bt_pacs_register_param *param)
{
int err = 0;
if (!valid_pacs_register_param(param)) {
return -EINVAL;
}
if (atomic_test_and_set_bit(pacs.flags, PACS_FLAG_REGISTERED)) {
LOG_DBG("PACS already registered");
return -EALREADY;
}
/* Save registration param so we can guard functions accordingly */
#if defined(CONFIG_BT_PAC_SNK)
atomic_set_bit_to(pacs.flags, PACS_FLAG_SNK_PAC, param->snk_pac);
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SNK_LOC)
atomic_set_bit_to(pacs.flags, PACS_FLAG_SNK_LOC, param->snk_loc);
#endif /* CONFIG_BT_PAC_SNK_LOC */
#if defined(CONFIG_BT_PAC_SRC)
atomic_set_bit_to(pacs.flags, PACS_FLAG_SRC_PAC, param->src_pac);
#endif /* CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SRC_LOC)
atomic_set_bit_to(pacs.flags, PACS_FLAG_SRC_LOC, param->src_loc);
#endif /* CONFIG_BT_PAC_SRC_LOC */
/* Remove characteristics if necessary */
configure_pacs_char(param);
err = bt_gatt_service_register(&pacs_svc);
if (err != 0) {
LOG_DBG("Failed to register ASCS in gatt DB: %d", err);
atomic_clear_bit(pacs.flags, PACS_FLAG_REGISTERED);
return -ENOEXEC;
}
return 0;
}
int bt_pacs_unregister(void)
{
int err;
if (!atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) {
LOG_DBG("No pacs instance registered");
return -EALREADY;
}
if (atomic_test_and_set_bit(pacs.flags, PACS_FLAG_SVC_CHANGING)) {
LOG_DBG("Service change already in progress");
atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING);
return -EBUSY;
}
err = bt_gatt_service_unregister(&pacs_svc);
/* If unregistration was successful, make sure to reset pacs_attrs so it can be used for
* new registrations
*/
if (err != 0) {
LOG_DBG("Failed to unregister PACS");
atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING);
return err;
}
/* Restore to original definition */
memcpy(pacs_svc.attrs, &_pacs_attrs, sizeof(_pacs_attrs));
pacs_svc.attr_count = ARRAY_SIZE(pacs_attrs);
atomic_clear_bit(pacs.flags, PACS_FLAG_REGISTERED);
atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING);
return err;
}
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) || defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
static int pac_notify_loc(struct bt_conn *conn, enum bt_audio_dir dir)
{
uint32_t location_le;
int err;
const struct bt_uuid *uuid;
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
case BT_AUDIO_DIR_SINK:
location_le = sys_cpu_to_le32(pacs_snk_location);
uuid = pacs_snk_loc_uuid;
break;
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
case BT_AUDIO_DIR_SOURCE:
location_le = sys_cpu_to_le32(pacs_src_location);
uuid = pacs_src_loc_uuid;
break;
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
default:
return -EINVAL;
}
err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs, &location_le, sizeof(location_le));
if (err != 0 && err != -ENOTCONN) {
LOG_WRN("PACS notify_loc failed: %d", err);
return err;
}
return 0;
}
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE || CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) || defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir)
{
int err = 0;
sys_slist_t *pac;
const struct bt_uuid *uuid;
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
case BT_AUDIO_DIR_SINK:
uuid = pacs_snk_uuid;
break;
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
case BT_AUDIO_DIR_SOURCE:
uuid = pacs_src_uuid;
break;
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
default:
return -EINVAL;
}
err = k_sem_take(&read_buf_sem, K_NO_WAIT);
if (err != 0) {
LOG_DBG("Failed to take read_buf_sem: %d", err);
return err;
}
pac = pacs_get_pac(dir);
__ASSERT(pac, "Failed to get pacs.\n");
get_pac_records(pac, &read_buf);
err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs,
read_buf.data, read_buf.len);
if (err != 0 && err != -ENOTCONN) {
LOG_WRN("PACS notify failed: %d", err);
}
k_sem_give(&read_buf_sem);
if (err == -ENOTCONN) {
return 0;
} else {
return 0;
}
}
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE || CONFIG_BT_PAC_SRC_NOTIFIABLE */
static int available_contexts_notify(struct bt_conn *conn)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(
pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(
pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE)),
};
int err;
err = pacs_gatt_notify(conn, BT_UUID_PACS_AVAILABLE_CONTEXT, pacs_svc.attrs,
&context, sizeof(context));
if (err != 0 && err != -ENOTCONN) {
LOG_WRN("Available Audio Contexts notify failed: %d", err);
return err;
}
return 0;
}
static int supported_contexts_notify(struct bt_conn *conn)
{
struct bt_pacs_context context = {
.snk = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SINK)),
.src = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SOURCE)),
};
int err;
err = pacs_gatt_notify(conn, BT_UUID_PACS_SUPPORTED_CONTEXT, pacs_svc.attrs,
&context, sizeof(context));
if (err != 0 && err != -ENOTCONN) {
LOG_WRN("Supported Audio Contexts notify failed: %d", err);
return err;
}
return 0;
}
void pacs_gatt_notify_complete_cb(struct bt_conn *conn, void *user_data)
{
/* Notification done, clear bit and reschedule work */
atomic_clear_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY);
k_work_submit(&deferred_nfy_work);
}
static int pacs_gatt_notify(struct bt_conn *conn,
const struct bt_uuid *uuid,
const struct bt_gatt_attr *attr,
const void *data,
uint16_t len)
{
int err;
struct bt_gatt_notify_params params;
memset(&params, 0, sizeof(params));
params.uuid = uuid;
params.attr = attr;
params.data = data;
params.len = len;
params.func = pacs_gatt_notify_complete_cb;
/* Mark notification in progress */
if (atomic_test_bit(pacs.flags, PACS_FLAG_SVC_CHANGING) ||
!atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) {
return 0;
}
atomic_set_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY);
err = bt_gatt_notify_cb(conn, &params);
if (err != 0) {
atomic_clear_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY);
}
if (err && err != -ENOTCONN) {
return err;
}
return 0;
}
static void notify_cb(struct bt_conn *conn, void *data)
{
struct pacs_client *client;
struct bt_conn_info info;
int err = 0;
LOG_DBG("");
err = bt_conn_get_info(conn, &info);
if (err != 0) {
LOG_ERR("Failed to get conn info: %d", err);
return;
}
if (info.state != BT_CONN_STATE_CONNECTED) {
/* Not connected */
return;
}
client = client_lookup_conn(conn);
if (client == NULL) {
return;
}
/* Check if we have unverified notifications in progress */
if (atomic_test_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY)) {
return;
}
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
if (atomic_test_bit(client->flags, FLAG_SINK_PAC_CHANGED)) {
LOG_DBG("Notifying Sink PAC");
err = pac_notify(conn, BT_AUDIO_DIR_SINK);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SINK_PAC_CHANGED);
}
}
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)
if (atomic_test_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED)) {
LOG_DBG("Notifying Sink Audio Location");
err = pac_notify_loc(conn, BT_AUDIO_DIR_SINK);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED);
}
}
#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
if (atomic_test_bit(client->flags, FLAG_SOURCE_PAC_CHANGED)) {
LOG_DBG("Notifying Source PAC");
err = pac_notify(conn, BT_AUDIO_DIR_SOURCE);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SOURCE_PAC_CHANGED);
}
}
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)
if (atomic_test_and_clear_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED)) {
LOG_DBG("Notifying Source Audio Location");
err = pac_notify_loc(conn, BT_AUDIO_DIR_SOURCE);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED);
}
}
#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
if (atomic_test_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED)) {
LOG_DBG("Notifying Available Contexts");
err = available_contexts_notify(conn);
if (!err) {
atomic_clear_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED);
}
}
if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) &&
atomic_test_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED)) {
LOG_DBG("Notifying Supported Contexts");
err = supported_contexts_notify(conn);
if (!err) {
atomic_clear_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED);
}
}
}
static void deferred_nfy_work_handler(struct k_work *work)
{
bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL);
}
static void pacs_auth_pairing_complete(struct bt_conn *conn, bool bonded)
{
LOG_DBG("%s paired (%sbonded)", bt_addr_le_str(bt_conn_get_dst(conn)),
bonded ? "" : "not ");
if (!bonded) {
return;
}
/* Check if already in list, and do nothing if it is */
for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) {
if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) &&
bt_addr_le_eq(bt_conn_get_dst(conn), &pacs.clients[i].addr)) {
return;
}
}
/* Else add the device */
for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) {
if (!atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) {
atomic_set_bit(pacs.clients[i].flags, FLAG_ACTIVE);
memcpy(&pacs.clients[i].addr, bt_conn_get_dst(conn), sizeof(bt_addr_le_t));
/* Send out all pending notifications */
k_work_submit(&deferred_nfy_work);
return;
}
}
}
static void pacs_bond_deleted(uint8_t id, const bt_addr_le_t *peer)
{
/* Find the device entry to delete */
for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) {
/* Check if match, and if active, if so, reset */
if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) &&
bt_addr_le_eq(peer, &pacs.clients[i].addr)) {
for (size_t j = 0U; j < FLAG_NUM; j++) {
atomic_clear_bit(pacs.clients[i].flags, j);
}
(void)memset(&pacs.clients[i].addr, 0, sizeof(bt_addr_le_t));
return;
}
}
}
static void pacs_security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err sec_err)
{
struct bt_conn_info info;
int err;
LOG_DBG("%s changed security level to %d", bt_addr_le_str(bt_conn_get_dst(conn)), level);
if (sec_err != BT_SECURITY_ERR_SUCCESS || level <= BT_SECURITY_L1) {
return;
}
err = bt_conn_get_info(conn, &info);
if (err < 0) {
__ASSERT_NO_MSG(false);
return;
}
if (!bt_le_bond_exists(info.id, info.le.dst)) {
return;
}
for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) {
for (size_t j = 0U; j < FLAG_NUM; j++) {
if (atomic_test_bit(pacs.clients[i].flags, j)) {
/**
* It's enough that one flag is set, as the defer work will go
* through all notifiable characteristics
*/
k_work_submit(&deferred_nfy_work);
return;
}
}
}
}
static void pacs_disconnected(struct bt_conn *conn, uint8_t reason)
{
struct pacs_client *client;
client = client_lookup_conn(conn);
if (client == NULL) {
return;
}
#if defined(CONFIG_BT_PAC_SNK)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC) &&
client->snk_available_contexts != NULL) {
uint16_t old = POINTER_TO_UINT(client->snk_available_contexts);
uint16_t new;
client->snk_available_contexts = NULL;
new = pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK);
atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, old != new);
}
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC) &&
client->src_available_contexts != NULL) {
uint16_t old = POINTER_TO_UINT(client->src_available_contexts);
uint16_t new;
client->src_available_contexts = NULL;
new = pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE);
atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, old != new);
}
#endif /* CONFIG_BT_PAC_SRC */
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.security_changed = pacs_security_changed,
.disconnected = pacs_disconnected,
};
static struct bt_conn_auth_info_cb auth_callbacks = {
.pairing_complete = pacs_auth_pairing_complete,
.bond_deleted = pacs_bond_deleted
};
void bt_pacs_cap_foreach(enum bt_audio_dir dir, bt_pacs_cap_foreach_func_t func, void *user_data)
{
sys_slist_t *pac;
CHECKIF(func == NULL) {
LOG_ERR("func is NULL");
return;
}
pac = pacs_get_pac(dir);
if (!pac) {
return;
}
foreach_cap(pac, func, user_data);
}
static void add_bonded_addr_to_client_list(const struct bt_bond_info *info, void *data)
{
for (uint8_t i = 0; i < ARRAY_SIZE(pacs.clients); i++) {
/* Check if device is registered, it not, add it */
if (!atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) {
char addr_str[BT_ADDR_LE_STR_LEN];
atomic_set_bit(pacs.clients[i].flags, FLAG_ACTIVE);
memcpy(&pacs.clients[i].addr, &info->addr, sizeof(bt_addr_le_t));
bt_addr_le_to_str(&pacs.clients[i].addr, addr_str, sizeof(addr_str));
LOG_DBG("Added %s to bonded list\n", addr_str);
return;
}
}
}
/* Register Audio Capability */
int bt_pacs_cap_register(enum bt_audio_dir dir, struct bt_pacs_cap *cap)
{
const struct bt_audio_codec_cap *codec_cap;
static bool callbacks_registered;
sys_slist_t *pac;
if (!cap || !cap->codec_cap) {
return -EINVAL;
}
codec_cap = cap->codec_cap;
pac = pacs_get_pac(dir);
if (!pac) {
return -EINVAL;
}
/* Restore bonding list */
bt_foreach_bond(BT_ID_DEFAULT, add_bonded_addr_to_client_list, NULL);
LOG_DBG("cap %p dir %s codec_cap id 0x%02x codec_cap cid 0x%04x codec_cap vid 0x%04x", cap,
bt_audio_dir_str(dir), codec_cap->id, codec_cap->cid, codec_cap->vid);
sys_slist_append(pac, &cap->_node);
if (!callbacks_registered) {
bt_conn_auth_info_cb_register(&auth_callbacks);
callbacks_registered = true;
}
if (IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE) && dir == BT_AUDIO_DIR_SINK) {
pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED);
k_work_submit(&deferred_nfy_work);
}
if (IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE) && dir == BT_AUDIO_DIR_SOURCE) {
pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED);
k_work_submit(&deferred_nfy_work);
}
return 0;
}
/* Unregister Audio Capability */
int bt_pacs_cap_unregister(enum bt_audio_dir dir, struct bt_pacs_cap *cap)
{
sys_slist_t *pac;
if (!cap) {
return -EINVAL;
}
pac = pacs_get_pac(dir);
if (!pac) {
return -EINVAL;
}
LOG_DBG("cap %p dir %s", cap, bt_audio_dir_str(dir));
if (!sys_slist_find_and_remove(pac, &cap->_node)) {
return -ENOENT;
}
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE)
case BT_AUDIO_DIR_SINK:
pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED);
k_work_submit(&deferred_nfy_work);
break;
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE) */
#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE)
case BT_AUDIO_DIR_SOURCE:
pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED);
k_work_submit(&deferred_nfy_work);
break;
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
default:
return -EINVAL;
}
return 0;
}
int bt_pacs_set_location(enum bt_audio_dir dir, enum bt_audio_location location)
{
switch (dir) {
case BT_AUDIO_DIR_SINK:
set_snk_location(location);
break;
case BT_AUDIO_DIR_SOURCE:
set_src_location(location);
break;
default:
return -EINVAL;
}
return 0;
}
int bt_pacs_set_available_contexts(enum bt_audio_dir dir, enum bt_audio_context contexts)
{
if (!atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) {
return -EINVAL;
}
switch (dir) {
case BT_AUDIO_DIR_SINK:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
return set_available_contexts(contexts, &snk_available_contexts,
supported_context_get(dir));
}
return -EINVAL;
case BT_AUDIO_DIR_SOURCE:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
return set_available_contexts(contexts, &src_available_contexts,
supported_context_get(dir));
}
return -EINVAL;
}
return -EINVAL;
}
int bt_pacs_conn_set_available_contexts_for_conn(struct bt_conn *conn, enum bt_audio_dir dir,
enum bt_audio_context *contexts)
{
enum bt_audio_context old = pacs_get_available_contexts_for_conn(conn, dir);
struct bt_conn_info info = { 0 };
struct pacs_client *client;
int err;
client = client_lookup_conn(conn);
if (client == NULL) {
return -ENOENT;
}
err = bt_conn_get_info(conn, &info);
if (err < 0) {
LOG_ERR("Could not get conn info: %d", err);
return err;
}
switch (dir) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_DIR_SINK:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
if (contexts != NULL) {
client->snk_available_contexts = UINT_TO_POINTER(*contexts);
} else {
client->snk_available_contexts = NULL;
}
break;
}
return -EINVAL;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_DIR_SOURCE:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
if (contexts != NULL) {
client->src_available_contexts = UINT_TO_POINTER(*contexts);
} else {
client->src_available_contexts = NULL;
}
break;
}
return -EINVAL;
#endif /* CONFIG_BT_PAC_SRC */
default:
return -EINVAL;
}
if (pacs_get_available_contexts_for_conn(conn, dir) == old) {
/* No change. Skip notification */
return 0;
}
atomic_set_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED);
/* Send notification on encrypted link only */
if (info.security.level > BT_SECURITY_L1) {
k_work_submit(&deferred_nfy_work);
}
return 0;
}
int bt_pacs_set_supported_contexts(enum bt_audio_dir dir, enum bt_audio_context contexts)
{
uint16_t *supported_contexts = NULL;
uint16_t *available_contexts = NULL;
switch (dir) {
case BT_AUDIO_DIR_SINK:
#if defined(CONFIG_BT_PAC_SNK)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
supported_contexts = &snk_supported_contexts;
available_contexts = &snk_available_contexts;
break;
}
return -EINVAL;
#endif /* CONFIG_BT_PAC_SNK */
return -ENOTSUP;
case BT_AUDIO_DIR_SOURCE:
#if defined(CONFIG_BT_PAC_SRC)
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
supported_contexts = &src_supported_contexts;
available_contexts = &src_available_contexts;
break;
}
return -EINVAL;
#endif /* CONFIG_BT_PAC_SRC */
return -ENOTSUP;
default:
return -EINVAL;
}
if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) || *supported_contexts == 0) {
return set_supported_contexts(contexts, supported_contexts, available_contexts);
}
return -EALREADY;
}
enum bt_audio_context bt_pacs_get_available_contexts(enum bt_audio_dir dir)
{
switch (dir) {
case BT_AUDIO_DIR_SINK:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) {
return snk_available_contexts;
}
break;
case BT_AUDIO_DIR_SOURCE:
if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) {
return src_available_contexts;
}
break;
}
return BT_AUDIO_CONTEXT_TYPE_NONE;
}
enum bt_audio_context bt_pacs_get_available_contexts_for_conn(struct bt_conn *conn,
enum bt_audio_dir dir)
{
CHECKIF(conn == NULL) {
LOG_ERR("NULL conn");
return BT_AUDIO_CONTEXT_TYPE_NONE;
}
return pacs_get_available_contexts_for_conn(conn, dir);
}
struct codec_cap_lookup_id_data {
const struct bt_pac_codec *codec_id;
const struct bt_audio_codec_cap *codec_cap;
};
static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data)
{
struct codec_cap_lookup_id_data *data = user_data;
if (cap->codec_cap->id == data->codec_id->id &&
cap->codec_cap->cid == data->codec_id->cid &&
cap->codec_cap->vid == data->codec_id->vid) {
data->codec_cap = cap->codec_cap;
return false;
}
return true;
}
const struct bt_audio_codec_cap *bt_pacs_get_codec_cap(enum bt_audio_dir dir,
const struct bt_pac_codec *codec_id)
{
struct codec_cap_lookup_id_data lookup_data = {
.codec_id = codec_id,
.codec_cap = NULL,
};
bt_pacs_cap_foreach(dir, codec_lookup_id, &lookup_data);
return lookup_data.codec_cap;
}