|  | /* @file | 
|  | * @brief Bluetooth PACS | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2020 Intel Corporation | 
|  | * Copyright (c) 2022-2023 Nordic Semiconductor ASA | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/sys/check.h> | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/init.h> | 
|  |  | 
|  | #include <zephyr/bluetooth/bluetooth.h> | 
|  | #include <zephyr/bluetooth/conn.h> | 
|  | #include <zephyr/bluetooth/gatt.h> | 
|  | #include <zephyr/bluetooth/audio/audio.h> | 
|  | #include <zephyr/bluetooth/audio/pacs.h> | 
|  | #include <zephyr/sys/slist.h> | 
|  | #include "../host/conn_internal.h" | 
|  | #include "../host/hci_core.h" | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(bt_pacs, CONFIG_BT_PACS_LOG_LEVEL); | 
|  |  | 
|  | #include "common/bt_str.h" | 
|  |  | 
|  | #include "audio_internal.h" | 
|  | #include "pacs_internal.h" | 
|  | #include "bap_unicast_server.h" | 
|  |  | 
|  | #define PAC_NOTIFY_TIMEOUT	K_MSEC(10) | 
|  | #define READ_BUF_SEM_TIMEOUT    K_MSEC(50) | 
|  |  | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | static uint32_t pacs_src_location; | 
|  | static sys_slist_t src_pacs_list = SYS_SLIST_STATIC_INIT(&src_pacs_list); | 
|  | static uint16_t src_supported_contexts; | 
|  | #endif /* CONFIG_BT_PAC_SRC */ | 
|  |  | 
|  | #if defined(CONFIG_BT_PAC_SNK) | 
|  | static uint32_t pacs_snk_location; | 
|  | static sys_slist_t snk_pacs_list = SYS_SLIST_STATIC_INIT(&snk_pacs_list); | 
|  | static uint16_t snk_supported_contexts; | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  |  | 
|  | static uint16_t src_available_contexts = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; | 
|  | static uint16_t snk_available_contexts = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; | 
|  |  | 
|  | 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, | 
|  | }; | 
|  |  | 
|  | static 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); | 
|  | } clients[CONFIG_BT_MAX_PAIRED]; | 
|  |  | 
|  | static atomic_t notify_rdy; | 
|  |  | 
|  | 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(clients); i++) { | 
|  | if (atomic_test_bit(clients[i].flags, FLAG_ACTIVE) && | 
|  | bt_addr_le_eq(&clients[i].addr, bt_conn_get_dst(conn))) { | 
|  | return &clients[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void pacs_set_notify_bit(int bit) | 
|  | { | 
|  | for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) { | 
|  | if (atomic_test_bit(clients[i].flags, FLAG_ACTIVE)) { | 
|  | atomic_set_bit(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 (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 (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", 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: | 
|  | return snk_supported_contexts | BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | return src_supported_contexts | BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; | 
|  | #endif /* CONFIG_BT_PAC_SRC */ | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return BT_AUDIO_CONTEXT_TYPE_PROHIBITED; | 
|  | } | 
|  |  | 
|  | 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", 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", 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_pacs_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", 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 (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", 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_pacs_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", 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 (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(enum bt_audio_dir dir) | 
|  | { | 
|  | switch (dir) { | 
|  | #if defined(CONFIG_BT_PAC_SNK) | 
|  | case BT_AUDIO_DIR_SINK: | 
|  | return &snk_pacs_list; | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | return &src_pacs_list; | 
|  | #endif /* CONFIG_BT_PAC_SRC */ | 
|  | default: | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | #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(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_SNK, \ | 
|  | BT_PACS_SNK_PROP, \ | 
|  | BT_GATT_PERM_READ_ENCRYPT, \ | 
|  | _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)) | 
|  |  | 
|  | #define BT_PACS_SNK_LOC(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC, \ | 
|  | BT_PACS_SNK_LOC_PROP, \ | 
|  | BT_PACS_SNK_LOC_PERM, \ | 
|  | _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),)) | 
|  |  | 
|  | #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(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_SRC, \ | 
|  | BT_PACS_SRC_PROP, \ | 
|  | BT_GATT_PERM_READ_ENCRYPT, \ | 
|  | _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)) | 
|  |  | 
|  | #define BT_PACS_SRC_LOC(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC, \ | 
|  | BT_PACS_SRC_LOC_PROP, \ | 
|  | BT_PACS_SRC_LOC_PERM, \ | 
|  | _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),)) | 
|  |  | 
|  | #define BT_PAC_AVAILABLE_CONTEXT(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_AVAILABLE_CONTEXT, \ | 
|  | BT_GATT_CHRC_READ|BT_GATT_CHRC_NOTIFY, \ | 
|  | BT_GATT_PERM_READ_ENCRYPT, \ | 
|  | _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(_read) \ | 
|  | BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT, \ | 
|  | BT_PACS_SUPPORTED_CONTEXT_PROP, \ | 
|  | BT_GATT_PERM_READ_ENCRYPT, \ | 
|  | _read, NULL, NULL), \ | 
|  | IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, \ | 
|  | (BT_AUDIO_CCC(supported_context_cfg_changed),)) | 
|  |  | 
|  | BT_GATT_SERVICE_DEFINE(pacs_svc, | 
|  | BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS), | 
|  | #if defined(CONFIG_BT_PAC_SNK) | 
|  | BT_PAC_SNK(snk_read) | 
|  | #if defined(CONFIG_BT_PAC_SNK_LOC) | 
|  | BT_PACS_SNK_LOC(snk_loc_read) | 
|  | #endif /* CONFIG_BT_PAC_SNK_LOC */ | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | BT_PAC_SRC(src_read) | 
|  | #if defined(CONFIG_BT_PAC_SRC_LOC) | 
|  | BT_PACS_SRC_LOC(src_loc_read) | 
|  | #endif /* CONFIG_BT_PAC_SRC_LOC */ | 
|  | #endif /* CONFIG_BT_PAC_SRC */ | 
|  | BT_PAC_AVAILABLE_CONTEXT(available_contexts_read) | 
|  | BT_PAC_SUPPORTED_CONTEXT(supported_context_read) | 
|  | ); | 
|  |  | 
|  | #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 *pacs; | 
|  | 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; | 
|  | } | 
|  |  | 
|  | pacs = pacs_get(dir); | 
|  | __ASSERT(pacs, "Failed to get pacs.\n"); | 
|  | get_pac_records(pacs, &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(¬ify_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(¶ms, 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 */ | 
|  | atomic_set(¬ify_rdy, 1); | 
|  |  | 
|  | err = bt_gatt_notify_cb(conn, ¶ms); | 
|  | if (err != 0) { | 
|  | atomic_clear(¬ify_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_get(¬ify_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(clients); i++) { | 
|  | if (atomic_test_bit(clients[i].flags, FLAG_ACTIVE) && | 
|  | bt_addr_le_eq(bt_conn_get_dst(conn), &clients[i].addr)) { | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Else add the device */ | 
|  | for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) { | 
|  | if (!atomic_test_bit(clients[i].flags, FLAG_ACTIVE)) { | 
|  | atomic_set_bit(clients[i].flags, FLAG_ACTIVE); | 
|  | memcpy(&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(clients); i++) { | 
|  | /* Check if match, and if active, if so, reset */ | 
|  | if (atomic_test_bit(clients[i].flags, FLAG_ACTIVE) && | 
|  | bt_addr_le_eq(peer, &clients[i].addr)) { | 
|  | for (size_t j = 0U; j < FLAG_NUM; j++) { | 
|  | atomic_clear_bit(clients[i].flags, j); | 
|  | } | 
|  | (void)memset(&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 err) | 
|  | { | 
|  | LOG_DBG("%s changed security level to %d", bt_addr_le_str(bt_conn_get_dst(conn)), level); | 
|  |  | 
|  | if (err != 0 || conn->encrypt == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) { | 
|  | for (size_t j = 0U; j < FLAG_NUM; j++) { | 
|  | if (atomic_test_bit(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 (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 (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 */ | 
|  | } | 
|  |  | 
|  | static struct bt_conn_cb 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(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(clients); i++) { | 
|  | /* Check if device is registered, it not, add it */ | 
|  | if (!atomic_test_bit(clients[i].flags, FLAG_ACTIVE)) { | 
|  | char addr_str[BT_ADDR_LE_STR_LEN]; | 
|  |  | 
|  | atomic_set_bit(clients[i].flags, FLAG_ACTIVE); | 
|  | memcpy(&clients[i].addr, &info->addr, sizeof(bt_addr_le_t)); | 
|  | bt_addr_le_to_str(&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(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_cb_register(&conn_callbacks); | 
|  | 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(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) | 
|  | { | 
|  | switch (dir) { | 
|  | case BT_AUDIO_DIR_SINK: | 
|  | return set_available_contexts(contexts, &snk_available_contexts, | 
|  | supported_context_get(dir)); | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | return set_available_contexts(contexts, &src_available_contexts, | 
|  | supported_context_get(dir)); | 
|  | } | 
|  |  | 
|  | 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 (contexts != NULL) { | 
|  | client->snk_available_contexts = UINT_TO_POINTER(*contexts); | 
|  | } else { | 
|  | client->snk_available_contexts = NULL; | 
|  | } | 
|  |  | 
|  | break; | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | if (contexts != NULL) { | 
|  | client->src_available_contexts = UINT_TO_POINTER(*contexts); | 
|  | } else { | 
|  | client->src_available_contexts = NULL; | 
|  | } | 
|  |  | 
|  | break; | 
|  | #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) | 
|  | supported_contexts = &snk_supported_contexts; | 
|  | available_contexts = &snk_available_contexts; | 
|  | break; | 
|  | #endif /* CONFIG_BT_PAC_SNK */ | 
|  | return -ENOTSUP; | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | #if defined(CONFIG_BT_PAC_SRC) | 
|  | supported_contexts = &src_supported_contexts; | 
|  | available_contexts = &src_available_contexts; | 
|  | break; | 
|  | #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: | 
|  | return snk_available_contexts; | 
|  | case BT_AUDIO_DIR_SOURCE: | 
|  | return src_available_contexts; | 
|  | } | 
|  |  | 
|  | return BT_AUDIO_CONTEXT_TYPE_PROHIBITED; | 
|  | } | 
|  |  | 
|  | 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_PROHIBITED; | 
|  | } | 
|  |  | 
|  | return pacs_get_available_contexts_for_conn(conn, dir); | 
|  | } |